Compare commits

...

83 Commits

Author SHA1 Message Date
adbenitez 923e2a43a4 fix WebxdcActivity: restore addMultiAccountObserver
accidentally reverted to addObserver while resolving merge conflict
2026-06-30 04:48:27 +02:00
adbenitez f29f94f138 Merge remote-tracking branch 'upstream/main' 2026-06-29 20:26:34 +02:00
adbenitez 3a35374a03 allow webxdc in "unselected account" mode 2026-06-29 18:30:23 +02:00
adb fe0793481c Merge pull request #4497 from deltachat/adb/handle-chat-and-webxdc-deletion
when a webxdc is deleted close WebxdcActivity
2026-06-29 18:20:30 +02:00
adbenitez 3c2a672799 Merge branch 'adb/handle-chat-and-webxdc-deletion' of https://github.com/deltachat/deltachat-android into adb/handle-chat-and-webxdc-deletion 2026-06-29 17:33:26 +02:00
adbenitez 2476d17394 be backward compatible with existing shortcuts 2026-06-29 17:30:11 +02:00
adb 5ae021bde3 Merge branch 'main' into adb/handle-chat-and-webxdc-deletion 2026-06-29 16:47:02 +02:00
adb 9f46c242fc Merge pull request #4504 from deltachat/adb/issue-4501
cancel search mode when back is pressed
2026-06-29 16:45:01 +02:00
adbenitez cf36254c1a add margin top to subject field 2026-06-29 00:07:10 +02:00
adb 0d94bf2006 Merge branch 'main' into adb/handle-chat-and-webxdc-deletion 2026-06-28 23:25:46 +02:00
adbenitez 4757150aae apply spotless 2026-06-28 23:21:23 +02:00
adbenitez 0d76ab930f update changelog 2026-06-28 23:16:46 +02:00
adbenitez 5515e2fe3c cancel search mode when back is pressed 2026-06-28 23:14:55 +02:00
adb 508f320b4c Merge pull request #165 from ArcaneChat/make-input-panel-round
enhance the InputPanel: make it pill-like with rounder corners
2026-06-28 19:35:54 +02:00
adbenitez 176e6c94b2 apply spotless 2026-06-28 19:34:25 +02:00
adbenitez deb650970c enhance the InputPanel: make it pill-like with rounder corners 2026-06-28 19:30:10 +02:00
adbenitez 36fc2261f9 improve welcome screen 2026-06-27 02:47:08 +02:00
B. Petersen f11c6baaf6 shorten shortcut for 'Mark as Read'
all of WhatsApp/Signal/Telegram use 'Read' (past tense) for the shortcut of 'Mark as Read',
we also give that as an example in the translators hint.

this works, as there is enough context,
eg. the chat is long-tapped or swiped.

beside consistency, more reasons to follow that path:

- LiquidGlass has larger buttons, it looks better to stay short here

- while 'Mark Read' is short enough, it results in translations being to long -
  the shorter source, differing more from 'Mark as Read',
  should encourage translators to stay shorter

- surely UI should and do work also with longer texts, but it looks better to be short and on point

the string is mainly used by iOS, for android it is used in the notification,
where we switch to the longer form, that is very similar to what we had before
(but i would be fine with both)
2026-06-26 22:11:45 +02:00
adbenitez 6add250258 Merge remote-tracking branch 'upstream/main' 2026-06-26 20:29:09 +02:00
adbenitez d15efbdb44 update changelog 2026-06-26 19:05:21 +02:00
adbenitez 2422f9110c when a webxdc is deleted close WebxdcActivity
also if a chat is deleted, close webxdc, and chat instead of showing
weird "ghost chat"
2026-06-26 18:47:12 +02:00
wchen342 b6162c2044 Fix video capture to query camera formats and enforce 16:9 aspect ratio for video calls (#4496) 2026-06-26 17:34:28 +02:00
adb 75245cfa02 Merge pull request #4495 from deltachat/adb/issue-4494
properly handle messages with ~overrideSenderName
2026-06-24 15:45:49 +02:00
adb 0d19f058fe Merge branch 'main' into adb/issue-4494 2026-06-24 14:59:33 +02:00
adb 911e695187 Merge pull request #4493 from deltachat/adb/issue-4492
properly hide all input&drafting elements on in-chat search
2026-06-24 14:59:03 +02:00
adbenitez b8c8969162 fix typo in variable name 2026-06-24 14:51:47 +02:00
adbenitez 0610520064 also synchronize the changes in initializeContactRequest() 2026-06-23 22:57:14 +02:00
adbenitez 5ae60af73f avoid redundant code block 2026-06-23 22:51:28 +02:00
adbenitez dd6f1015dc Merge branch 'adb/issue-4492' of https://github.com/deltachat/deltachat-android into adb/issue-4492 2026-06-23 22:45:19 +02:00
adbenitez 74ea10ebef properly avoid revealing hidden views while in search mode 2026-06-23 22:44:29 +02:00
adbenitez 877f991131 improve code and fix notifyWebxdc and notifyReaction 2026-06-23 21:33:30 +02:00
adb e556878b2f Merge branch 'main' into adb/issue-4494 2026-06-23 20:21:32 +02:00
adb 99a1fee994 Merge branch 'main' into adb/issue-4492 2026-06-23 20:21:12 +02:00
adb dfd323a89f Merge pull request #4491 from deltachat/adb/allow-select-multiple-files
allow to select multiple files
2026-06-23 20:18:45 +02:00
adbenitez 5a086ff022 avoid input_area wrapper 2026-06-23 19:21:15 +02:00
adbenitez bedaa2c287 tweak comment 2026-06-23 18:43:22 +02:00
adbenitez 50037d5232 fix code format 2026-06-23 18:42:05 +02:00
adbenitez 6756b04da9 properly handle messages with ~overrideSenderName 2026-06-23 18:30:21 +02:00
adbenitez b12ecf66c5 tweak CHANGELOG 2026-06-23 16:21:24 +02:00
adb d05403db02 Merge branch 'adb/allow-select-multiple-files' into adb/issue-4492 2026-06-23 16:20:36 +02:00
adb 2d8344a61f Merge branch 'main' into adb/allow-select-multiple-files 2026-06-23 16:19:47 +02:00
wchen342 44afda55e5 Add notification for missed calls (#4485) 2026-06-23 16:12:16 +02:00
adbenitez bfa6eef604 update CHANGELOG 2026-06-22 20:28:26 +02:00
adbenitez 6327b30350 properly hide all input&drafting elements on in-chat search 2026-06-22 20:02:16 +02:00
adbenitez f92cac4d55 update CHANGELOG 2026-06-22 18:57:49 +02:00
adbenitez 0204fca27d allow to select multiple files 2026-06-22 18:37:30 +02:00
adbenitez eed844469b apply spotless 2026-06-15 23:21:12 +02:00
adbenitez 59ceae46a0 Merge remote-tracking branch 'upstream/main' 2026-06-15 23:05:19 +02:00
adbenitez 693188dcff Merge remote-tracking branch 'upstream/main' 2026-06-15 23:04:36 +02:00
adb 025ff086c8 Merge pull request #4487 from deltachat/prep-2.53.0
prepare 2.53.0
2026-06-15 22:47:25 +02:00
adb a802bfcb61 Merge branch 'main' into prep-2.53.0 2026-06-15 22:46:19 +02:00
adbenitez 1f18bfe2dd prepare 2.53.0 2026-06-15 22:28:18 +02:00
adb dc1bc77925 Merge pull request #4486 from deltachat/update-core-and-stuff-2026-06-15
Update core to 2.53.0
2026-06-15 22:24:28 +02:00
adbenitez ac6383d79a update changelog 2026-06-15 22:10:55 +02:00
adbenitez 59fec8462c update translations 2026-06-15 22:03:27 +02:00
adbenitez d64baa294f update deltachat-core-rust to 'chore(release): prepare for 2.53.0' of 'v2.53.0' 2026-06-15 21:59:16 +02:00
adb e7e14af158 Merge pull request #4482 from deltachat/adb/issue-4481
don't create mailto unencrypted chat if force-encryption is enabled
2026-06-14 01:57:19 +02:00
adb e094e96669 Merge branch 'main' into adb/issue-4481 2026-06-14 01:54:47 +02:00
adb fa56e8ca0d Merge pull request #4483 from deltachat/adb/fix-ci-for-outside-pr
don't try to upload artifacts for PRs from external contributors
2026-06-12 23:15:56 +02:00
wch423 3d5abdcd79 Correct CHANGELOG.md 2026-06-12 16:21:38 +02:00
wchen342 92c4e41b59 Call maybeNetwork() on network unblock (#4476)
Limit maybeNetwork() calls when network changes

* Call maybeNetwork() on network unblock to allow immediate fetch during Doze (API 29+)
* Restrict maybeNetwork() in onAvailable() to API 24-28
2026-06-12 14:11:10 +00:00
adbenitez bbfd0c31da Merge remote-tracking branch 'upstream/main' 2026-06-11 20:19:42 +02:00
adb d5f627be50 Merge pull request #4461 from d2weber/notif_msg_style
feat: use message style notifications
2026-06-11 20:18:38 +02:00
adbenitez f0ca58d53f don't try to upload artifacts for PRs from external contributors 2026-06-11 20:00:35 +02:00
adb 56f5655693 Merge branch 'main' into notif_msg_style 2026-06-11 19:44:16 +02:00
wchen342 310a7f99c8 Remove media notification when audio playback ends naturally (#4472) 2026-06-11 19:25:17 +02:00
adb d0f7fca8c9 Merge branch 'main' into adb/issue-4481 2026-06-11 19:05:27 +02:00
B. Petersen 80de08f980 remove remainings of is_chatmail usage
instead, force_ncryption is checked now,
and this only for for option "New Chat -> New Email".

this also fixes the bug that, for classic relays,
by default "force encryption" is set but we still show "new email".

what's left for now is a check for is_chatmail whether fcm is sufficient or not.
2026-06-11 19:01:50 +02:00
adb 80a0ff0098 Merge pull request #4473 from deltachat/adb/ci-update-ndk-30
update NDK version in CI workflows
2026-06-11 18:53:22 +02:00
adbenitez 9c55de17a3 don't create mailto unencrypted chat if force-encryption is enabled 2026-06-11 18:49:08 +02:00
d2weber 8c1bc8e70e apply messaging style also if !isDisplayMessage() 2026-06-10 10:25:58 +02:00
d2weber f0760d6695 make notification icon nice and big in all cases 2026-06-09 20:47:26 +02:00
adb 6d038e9d7f Merge branch 'main' into adb/ci-update-ndk-30 2026-06-09 18:12:23 +02:00
adbenitez 46be278bf5 update ndk version 2026-06-09 17:58:01 +02:00
wchen342 1577b90047 Merge branch 'main' into notif_msg_style 2026-06-05 20:04:12 +02:00
d2weber 515a84b161 Merge branch 'main' into notif_msg_style 2026-06-04 20:04:12 +02:00
d2weber d757ef59c0 fixup: displayContact without displayMessage 2026-06-04 18:12:03 +02:00
d2weber d0a584d15d fixup: cleanup getAvatar 2026-06-04 11:36:53 +02:00
d2weber f8ec6d2da2 fixup: reactions and webxdc updates 2026-06-04 11:09:24 +02:00
d2weber c3b7e06b86 add isBot to person (not sure if it has any effect) 2026-06-03 22:16:05 +02:00
d2weber d5edcea5d0 fixup: convert timestamp to ms 2026-06-03 22:07:32 +02:00
d2weber 8a3d29fca3 fixup: handle null from getAvatar() 2026-06-03 22:06:15 +02:00
d2weber 6156ee0534 feat: use message style notifications
With MessagingStyle, Notifications can be expanded to show more text of
the message than just a single line.

This feature has been requested in
https://support.delta.chat/t/swipe-down-on-notification-to-expand-it/2742
2026-06-03 19:53:21 +02:00
45 changed files with 1218 additions and 429 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
id: setup-ndk
with:
ndk-version: r27
ndk-version: "r29"
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
+3 -1
View File
@@ -45,7 +45,7 @@ jobs:
- uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
id: setup-ndk
with:
ndk-version: r27
ndk-version: "r29"
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
@@ -77,12 +77,14 @@ jobs:
- name: Upload APK
id: upload
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: app-preview.apk
path: 'build/outputs/apk/foss/debug/*.apk'
- name: Add artifact links to PR
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
+20 -1
View File
@@ -1,5 +1,24 @@
# Delta Chat Android Changelog
## Unreleased
* Allow to select multiple files for sending
* Add notifications for missed calls
* Video call preview now accurately shows what is sent to remote
* Fix: properly hide draft attachment during in-chat search
* Fix: close mini-apps and chats if they are deleted
* Fix: cancel in-chat search when back is pressed, instead of directly returning to chatlist
## v2.53.0
2026-06
* Use message style notifications for longer message previews
* Remove notification after audio playback ends
* Fix: do not allow blocked contacts to use our invite links
* Fix sending mini-app that was used/prepared before sending
* Some more small fixes and updated translations
* Update to core 2.53.0
## v2.52.0
2026-06
@@ -7,7 +26,7 @@
* Fix: Incorrect total time when attaching audio files as draft
* Fix: Audio files in draft showing total time from wrong file
* Fix: Update the channel title after joining if the QR code had an outdated title
* Voice recording will be automatically saved as draft when interrupted
* Voice recording will be automatically saved as draft when interrupted
* Update to core 2.52.0
## v2.51.0
+2 -2
View File
@@ -34,8 +34,8 @@ android {
useLibrary 'org.apache.http.legacy'
defaultConfig {
versionCode 30000745
versionName "2.52.0"
versionCode 30000746
versionName "2.53.0"
applicationId "chat.delta.lite"
multiDexEnabled true
@@ -84,7 +84,7 @@ public class DcAccounts {
public boolean isAllChatmail() {
for (int accountId : getAll()) {
DcContext dcContext = getAccount(accountId);
if (!dcContext.isChatmail()) {
if (dcContext.getConfigInt("is_chatmail") == 0) {
return false;
}
}
@@ -362,10 +362,6 @@ public class DcContext {
return displayname;
}
public boolean isChatmail() {
return getConfigInt("is_chatmail") == 1;
}
public boolean isMuted() {
return getConfigInt("is_muted") == 1;
}
@@ -302,7 +302,10 @@ public class ApplicationContext extends MultiDexApplication {
Log.i(
"DeltaChat",
"++++++++++++++++++ NetworkCallback.onAvailable() #" + debugOnAvailableCount++);
getDcAccounts().maybeNetwork();
// onBlockedStatusChanged is only available on API 29+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getDcAccounts().maybeNetwork();
}
}
@Override
@@ -310,8 +313,13 @@ public class ApplicationContext extends MultiDexApplication {
@NonNull android.net.Network network, boolean blocked) {
Log.i(
"DeltaChat",
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged() #"
"++++++++++++++++++ NetworkCallback.onBlockedStatusChanged("
+ blocked
+ ") #"
+ debugOnBlockedStatusChangedCount++);
if (!blocked) {
getDcAccounts().maybeNetwork();
}
}
@Override
@@ -168,6 +168,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private static final int RECORD_VIDEO = 8;
private static final int PICK_WEBXDC = 9;
private static final Object searchLock = new Object();
private GlideRequests glideRequests;
protected ComposeText composeText;
private AnimatingToggle buttonToggle;
@@ -176,7 +178,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected ConversationTitleView titleView;
private ConversationFragment fragment;
private InputAwareLayout container;
private View composePanel;
private ScaleStableImageView backgroundView;
private MessageRequestsBottomView messageRequestBottomView;
private ProgressDialog progressDialog;
@@ -263,6 +264,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void handleOnBackPressed() {
if (container.isInputOpen()) {
container.hideCurrentInput(composeText);
} else if (searchMenu != null) {
searchCollapse();
} else {
handleReturnToConversationList();
}
@@ -279,6 +282,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
eventCenter.removeObservers(this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_DELETED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
@@ -443,32 +447,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
else mediaType = MediaType.IMAGE;
setMedia(singleUri, mediaType);
} else {
final ClipData multipleUris = data.getClipData();
if (multipleUris != null) {
final int uriCount = multipleUris.getItemCount();
if (uriCount > 0) {
ArrayList<Uri> uriList = new ArrayList<>(uriCount);
for (int i = 0; i < uriCount; i++) {
uriList.add(multipleUris.getItemAt(i).getUri());
}
askSendingFiles(
uriList,
() -> {
Util.runOnAnyBackgroundThread(
() -> {
SendRelayedMessageUtil.sendMultipleMsgs(this, chatId, uriList, null);
});
});
}
}
sendMultipleMsgs(data);
}
break;
case PICK_DOCUMENT:
final String docMimeType = MediaUtil.getMimeType(this, data.getData());
final MediaType docMediaType =
MediaUtil.isAudioType(docMimeType) ? MediaType.AUDIO : MediaType.DOCUMENT;
setMedia(data.getData(), docMediaType);
if (data.getData() != null) { // single Uri
final String docMimeType = MediaUtil.getMimeType(this, data.getData());
final MediaType docMediaType =
MediaUtil.isAudioType(docMimeType) ? MediaType.AUDIO : MediaType.DOCUMENT;
setMedia(data.getData(), docMediaType);
} else {
sendMultipleMsgs(data);
}
break;
case PICK_WEBXDC:
@@ -511,6 +502,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void sendMultipleMsgs(Intent data) {
final ClipData multipleUris = data.getClipData();
if (multipleUris != null) {
final int uriCount = multipleUris.getItemCount();
if (uriCount > 0) {
ArrayList<Uri> uriList = new ArrayList<>(uriCount);
for (int i = 0; i < uriCount; i++) {
uriList.add(multipleUris.getItemAt(i).getUri());
}
askSendingFiles(
uriList,
() -> {
Util.runOnAnyBackgroundThread(
() -> SendRelayedMessageUtil.sendMultipleMsgs(this, chatId, uriList, null));
});
}
}
}
@Override
public void startActivity(Intent intent) {
if (intent.getStringExtra(Browser.EXTRA_APPLICATION_ID) != null) {
@@ -1021,7 +1031,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
attachButton = ViewUtil.findById(this, R.id.attach_button);
composeText = ViewUtil.findById(this, R.id.embedded_text_editor);
emojiPickerContainer = ViewUtil.findById(this, R.id.emoji_picker_container);
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
container = ViewUtil.findById(this, R.id.layout_container);
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
@@ -1047,7 +1056,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// apply padding top to avoid drawing behind top bar
ViewUtil.applyWindowInsets(findViewById(R.id.fragment_content), false, true, false, false);
// apply padding to root to avoid collision with system bars
ViewUtil.applyWindowInsets(findViewById(R.id.root_layout), true, false, true, true);
ViewUtil.applyWindowInsets(findViewById(R.id.root_layout), true, false, true, false);
ViewUtil.applyWindowInsets(emojiPickerContainer, false, false, false, true);
container.addOnKeyboardShownListener(this);
container.addOnKeyboardHiddenListener(backgroundView);
@@ -1137,22 +1147,24 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
recipient = new Recipient(this, dcChat);
glideRequests = GlideApp.with(this);
setComposePanelVisibility(true);
setInputPanelVisibility(true);
initializeContactRequest();
}
private void setComposePanelVisibility(boolean isInitialization) {
private void setInputPanelVisibility(boolean isInitialization) {
int inputPanelVisibility;
boolean isAttachmentHidden;
if (dcChat.canSend()) {
composePanel.setVisibility(View.VISIBLE);
attachmentManager.setHidden(false);
inputPanelVisibility = View.VISIBLE;
isAttachmentHidden = 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);
inputPanelVisibility = View.GONE;
isAttachmentHidden = true;
hideSoftKeyboard();
inputPanel.setSubjectVisible(false);
// FIXME: disabled for now to avoid problems with chat scrolling and keyboard covering input
@@ -1164,6 +1176,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
*/
}
synchronized (searchLock) {
if (searchMenu != null) { // in search mode, don't change visibility directly
beforeSearchInputPanelVisibility = inputPanelVisibility;
beforeSearchAttachmentEditorHidden = isAttachmentHidden;
} else {
inputPanel.setVisibility(inputPanelVisibility);
// attachmentManager.setHidden(isAttachmentHidden);
}
}
}
//////// Helper Methods
@@ -1857,19 +1878,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
dcChat = dcContext.getChat(chatId);
titleView.setTitle(glideRequests, dcChat);
initializeSecurity(isSecureText, isDefaultSms);
setComposePanelVisibility(false);
setInputPanelVisibility(false);
initializeContactRequest();
} else if ((eventId == DcContext.DC_EVENT_INCOMING_MSG
|| eventId == DcContext.DC_EVENT_MSG_READ)
&& event.getData1Int() == chatId) {
dcChat = dcContext.getChat(chatId);
titleView.setTitle(glideRequests, dcChat);
} else if (eventId == DcContext.DC_EVENT_CHAT_DELETED && event.getData1Int() == chatId) {
finish();
}
}
// in-chat search
private int beforeSearchComposeVisibility = View.VISIBLE;
private boolean beforeSearchAttachmentEditorHidden;
private int beforeSearchMsgRequestVisibility;
private int beforeSearchInputPanelVisibility;
private Menu searchMenu = null;
private int[] searchResult = {};
@@ -1890,17 +1915,28 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void searchExpand(final Menu menu, final MenuItem searchItem) {
searchMenu = menu;
synchronized (searchLock) {
searchMenu = menu;
beforeSearchComposeVisibility = composePanel.getVisibility();
composePanel.setVisibility(View.GONE);
beforeSearchAttachmentEditorHidden = attachmentManager.isHidden();
beforeSearchMsgRequestVisibility = messageRequestBottomView.getVisibility();
beforeSearchInputPanelVisibility = inputPanel.getVisibility();
// attachmentManager.setHidden(true);
messageRequestBottomView.setVisibility(View.GONE);
inputPanel.setVisibility(View.GONE);
}
ConversationActivity.this.makeSearchMenuVisible(menu, searchItem);
}
private void searchCollapse() {
searchMenu = null;
composePanel.setVisibility(beforeSearchComposeVisibility);
synchronized (searchLock) {
searchMenu = null;
// attachmentManager.setHidden(beforeSearchAttachmentEditorHidden);
messageRequestBottomView.setVisibility(beforeSearchMsgRequestVisibility);
inputPanel.setVisibility(beforeSearchInputPanelVisibility);
}
// trigger onPrepareOptionsMenu() to restore correct menu visibility
invalidateOptionsMenu();
@@ -1961,16 +1997,29 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void initializeContactRequest() {
if (!dcChat.isContactRequest()) {
messageRequestBottomView.setVisibility(View.GONE);
synchronized (searchLock) {
if (searchMenu != null) { // in search mode, don't change visibility directly
beforeSearchMsgRequestVisibility = View.GONE;
} else {
messageRequestBottomView.setVisibility(View.GONE);
}
}
return;
}
messageRequestBottomView.setVisibility(View.VISIBLE);
synchronized (searchLock) {
if (searchMenu != null) { // in search mode, don't change visibility directly
beforeSearchMsgRequestVisibility = View.VISIBLE;
} else {
messageRequestBottomView.setVisibility(View.VISIBLE);
}
}
messageRequestBottomView.setAcceptOnClickListener(
v -> {
DcHelper.getContext(context).acceptChat(chatId);
messageRequestBottomView.setVisibility(View.GONE);
composePanel.setVisibility(View.VISIBLE);
inputPanel.setVisibility(View.VISIBLE);
});
if (dcChat.getType() == DcChat.DC_CHAT_TYPE_GROUP) {
@@ -260,12 +260,11 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
final String addr = extraEmail[0];
int contactId = dcContext.lookupContactIdByAddr(addr);
if (contactId == 0) {
contactId = dcContext.createContact(null, addr);
if (contactId != 0 || dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) {
if (contactId == 0) contactId = dcContext.createContact(null, addr);
chatId = dcContext.createChatByContactId(contactId);
accId = dcContext.getAccountId();
}
chatId = dcContext.createChatByContactId(contactId);
accId = dcContext.getAccountId();
}
Intent composeIntent;
if (accId != -1 && chatId > 0) {
@@ -68,6 +68,11 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
}
}
@Override
protected boolean allowInLockedMode() {
return true;
}
@Override
protected void onCreate(Bundle state, boolean ready) {
setContentView(R.layout.web_view_activity);
@@ -51,7 +51,6 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.json.JSONObject;
import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.util.IntentUtils;
@@ -62,6 +61,7 @@ import org.thoughtcrime.securesms.util.Util;
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
private static final String TAG = "WebxdcActivity";
private static final String EXTRA_ACCOUNT_ID = "accountId";
private static final String EXTRA_CHAT_ID = "chatId";
private static final String EXTRA_APP_MSG_ID = "appMessageId";
private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
private static final String EXTRA_HREF = "href";
@@ -72,6 +72,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
private DcContext dcContext;
private int accountId;
private Rpc rpc;
private int chatId;
private DcMsg dcAppMsg;
private String baseURL;
private String sourceCodeUrl = "";
@@ -113,7 +114,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
}
dcContext.setConfigInt("ui.maps_version", mapsVersion);
}
openWebxdcActivity(context, msgId, true, href);
openWebxdcActivity(context, msgId, chatId, true, href);
}
public static void openWebxdcActivity(Context context, DcMsg instance) {
@@ -121,18 +122,18 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
}
public static void openWebxdcActivity(Context context, @NonNull DcMsg instance, String href) {
openWebxdcActivity(context, instance.getId(), false, href);
openWebxdcActivity(context, instance.getId(), instance.getChatId(), false, href);
}
public static void openWebxdcActivity(
Context context, int msgId, boolean hideActionBar, String href) {
Context context, int msgId, int chatId, boolean hideActionBar, String href) {
if (!Util.isClickedRecently()) {
context.startActivity(getWebxdcIntent(context, msgId, hideActionBar, href));
context.startActivity(getWebxdcIntent(context, msgId, chatId, hideActionBar, href));
}
}
private static Intent getWebxdcIntent(
Context context, int msgId, boolean hideActionBar, String href) {
Context context, int msgId, int chatId, boolean hideActionBar, String href) {
DcContext dcContext = DcHelper.getContext(context);
int accountId = dcContext.getAccountId();
Intent intent = new Intent(context, WebxdcActivity.class);
@@ -140,6 +141,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
// Unique URI per webxdc instance so FLAG_ACTIVITY_NEW_DOCUMENT can identify the document:
intent.setData(Uri.parse("webxdc://" + accountId + "/" + msgId));
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
intent.putExtra(EXTRA_CHAT_ID, chatId);
intent.putExtra(EXTRA_APP_MSG_ID, msgId);
intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
intent.putExtra(EXTRA_HREF, href);
@@ -150,12 +152,13 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
private static Intent[] getWebxdcIntentWithParentStack(Context context, int msgId) {
DcContext dcContext = DcHelper.getContext(context);
int chatId = dcContext.getMsg(msgId).getChatId();
final Intent chatIntent =
new Intent(context, ConversationActivity.class)
.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId)
.setAction(Intent.ACTION_VIEW);
final Intent webxdcIntent = getWebxdcIntent(context, msgId, false, "");
final Intent webxdcIntent = getWebxdcIntent(context, msgId, chatId, false, "");
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(chatIntent)
@@ -201,12 +204,6 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
}
});
DcEventCenter eventCenter =
DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
int appMessageId = b.getInt(EXTRA_APP_MSG_ID);
accountId = b.getInt(EXTRA_ACCOUNT_ID);
this.dcContext = DcHelper.getContext(getApplicationContext());
@@ -220,6 +217,15 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
finish();
return;
}
chatId = b.getInt(EXTRA_CHAT_ID, dcAppMsg.getChatId());
DcEventCenter eventCenter =
DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSG_DELETED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_DELETED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
// `msg_id` in the subdomain makes sure, different apps using same files do not share the same
// cache entry
@@ -516,6 +522,10 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
Log.e(TAG, "RPC Error", e);
}
});
} else if ((eventId == DcContext.DC_EVENT_MSG_DELETED
&& event.getData2Int() == dcAppMsg.getId())
|| (eventId == DcContext.DC_EVENT_CHAT_DELETED && event.getData1Int() == chatId)) {
finish();
}
}
@@ -564,7 +574,7 @@ public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcE
IconCompat.createWithBitmap(
bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too
// small circle and defamiliarize the icon too much
.setIntent(getWebxdcIntent(context, msgId, false, ""))
.setIntent(getWebxdcIntent(context, msgId, msg.getChatId(), false, ""))
.build();
Toast.makeText(context, R.string.one_moment, Toast.LENGTH_SHORT).show();
@@ -1,10 +1,14 @@
package org.thoughtcrime.securesms;
import android.Manifest;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -71,6 +75,11 @@ public class WelcomeActivity extends BaseActionBarActivity
.setOnClickListener((v) -> showSignInDialogWithPermission());
findViewById(R.id.backup_button).setOnClickListener((v) -> startImportBackup());
AnimatorSet floating =
(AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.floating_logo);
floating.setTarget(findViewById(R.id.welcome_icon));
floating.start();
registerForEvents();
initializeActionBar();
@@ -120,7 +129,15 @@ public class WelcomeActivity extends BaseActionBarActivity
boolean canGoBack = AccountManager.getInstance().canRollbackAccountCreation(this);
supportActionBar.setDisplayHomeAsUpEnabled(canGoBack);
getSupportActionBar().setTitle(canGoBack ? R.string.add_account : R.string.app_name);
if (canGoBack) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
supportActionBar.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
supportActionBar.setElevation(0);
}
supportActionBar.setTitle(R.string.add_account);
} else {
supportActionBar.hide();
}
}
private void registerForEvents() {
@@ -189,7 +206,12 @@ public class WelcomeActivity extends BaseActionBarActivity
File imexDir = DcHelper.getImexDir();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AttachmentManager.selectMediaType(
this, "application/x-tar", null, PICK_BACKUP, StorageUtil.getDownloadUri());
this,
"application/x-tar",
null,
PICK_BACKUP,
StorageUtil.getDownloadUri(),
false);
} else {
final String backupFile = dcContext.imexHasBackup(imexDir.getAbsolutePath());
if (backupFile != null) {
@@ -5,7 +5,11 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationManagerCompat;
import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.R;
@RequiresApi(api = Build.VERSION_CODES.O)
public class CallActionReceiver extends BroadcastReceiver {
@@ -22,6 +26,30 @@ public class CallActionReceiver extends BroadcastReceiver {
CallCoordinator.getInstance(context).declineCall();
} else if (CallActivity.ACTION_HANGUP_CALL.equals(action)) {
CallCoordinator.getInstance(context).hangUp();
} else if (CallActivity.ACTION_CALL_BACK.equals(action)) {
int chatId = intent.getIntExtra(ConversationActivity.CHAT_ID_EXTRA, -1);
int accId = intent.getIntExtra(ConversationActivity.ACCOUNT_ID_EXTRA, -1);
boolean video = intent.getBooleanExtra(CallActivity.EXTRA_STARTS_WITH_VIDEO, false);
if (chatId > 0 && accId > 0) {
NotificationManagerCompat.from(context).cancel(CallCoordinator.NOTIFICATION_ID_MISSED_CALL);
CallCoordinator coordinator = CallCoordinator.getInstance(context);
if (coordinator.hasActiveCall()) {
Toast.makeText(context, R.string.already_in_call, Toast.LENGTH_SHORT).show();
} else {
coordinator.initiateOutgoingCall(accId, chatId, video);
}
}
} else if (CallActivity.ACTION_MESSAGE.equals(action)) {
int chatId = intent.getIntExtra(ConversationActivity.CHAT_ID_EXTRA, -1);
int accId = intent.getIntExtra(ConversationActivity.ACCOUNT_ID_EXTRA, -1);
NotificationManagerCompat.from(context).cancel(CallCoordinator.NOTIFICATION_ID_MISSED_CALL);
if (chatId > 0 && accId > 0) {
Intent convIntent = new Intent(context, ConversationActivity.class);
convIntent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
convIntent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, accId);
convIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(convIntent);
}
}
}
}
@@ -63,6 +63,9 @@ public class CallActivity extends AppCompatActivity {
public static final String ACTION_ANSWER_CALL = BuildConfig.APPLICATION_ID + ".ANSWER_CALL";
public static final String ACTION_DECLINE_CALL = BuildConfig.APPLICATION_ID + ".DECLINE_CALL";
public static final String ACTION_HANGUP_CALL = BuildConfig.APPLICATION_ID + ".HANGUP_CALL";
public static final String ACTION_CALL_BACK = BuildConfig.APPLICATION_ID + ".CALL_BACK";
public static final String ACTION_MESSAGE = BuildConfig.APPLICATION_ID + ".MESSAGE";
public static final String EXTRA_STARTS_WITH_VIDEO = "starts_with_video";
// Views
@@ -57,6 +57,7 @@ import kotlinx.coroutines.Dispatchers;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.FlowKt;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -70,7 +71,18 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
// Notification channels
private static final String CHANNEL_ID_INCOMING = "voip_incoming_calls";
private static final String CHANNEL_ID_ONGOING = "voip_ongoing_calls";
private static final String CHANNEL_ID_MISSED = "voip_missed_calls";
private static final int NOTIFICATION_ID_CALL = 1001;
static final int NOTIFICATION_ID_MISSED_CALL = 1002;
private static final int PI_ANSWER = 0;
private static final int PI_DECLINE = 1;
private static final int PI_FULLSCREEN = 2;
private static final int PI_HANGUP = 3;
private static final int PI_ONGOING_CONTENT = 4;
private static final int PI_MISSED_CONTENT = 5;
private static final int PI_MISSED_CALLBACK = 6;
private static final int PI_MISSED_MESSAGE = 7;
private static final String CALL_IDENTIFIER_SCHEME = "deltachat:";
@@ -123,6 +135,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
private boolean hasNotifiedBackend = false;
private boolean hasAutoSelectedEarpiece = false;
private boolean pendingMediaCapture = false;
private boolean wasAnsweredLocally = false;
private CallControlScope activeCallControlScope;
private CallViewModel activeCallViewModel;
@@ -169,8 +182,14 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
ongoingChannel.setDescription("Notifications for active DeltaChat calls");
ongoingChannel.setSound(null, null);
NotificationChannel missedChannel =
new NotificationChannel(
CHANNEL_ID_MISSED, "Missed Calls", NotificationManager.IMPORTANCE_HIGH);
missedChannel.setDescription("Notifications for missed DeltaChat calls");
notificationManager.createNotificationChannel(incomingChannel);
notificationManager.createNotificationChannel(ongoingChannel);
notificationManager.createNotificationChannel(missedChannel);
}
private void registerTelecom() {
@@ -441,6 +460,8 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
return;
}
wasAnsweredLocally = true;
if (callService != null) {
callService.stopRingtone();
}
@@ -871,7 +892,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
// This event is problematic because it can trigger in both directions,
// in addition to multiple other scenarios which cannot easily be distinguished
// May cause problems in edge cases
onCallEnded(accId, callId);
onCallEnded(accId, callId, startsWithVideo);
break;
}
});
@@ -1022,7 +1043,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
showOrUpdateOngoingNotification(appContext.getString(R.string.call_with, calleeName));
}
private synchronized void onCallEnded(int accId, int callId) {
private synchronized void onCallEnded(int accId, int callId, boolean startsWithVideo) {
Log.d(TAG, "onCallEnded: accId=" + accId + ", callId=" + callId);
if (!hasActiveCall()) {
@@ -1056,6 +1077,10 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
callService.endCall();
}
if (isIncomingCall && !wasAnsweredLocally) {
showMissedCallNotification(activeAccId, activeChatId, startsWithVideo);
}
// Clear active states
cleanupCall(accId, callId);
}
@@ -1127,6 +1152,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
this.hasNotifiedBackend = false;
this.hasAutoSelectedEarpiece = false;
this.pendingMediaCapture = false;
this.wasAnsweredLocally = false;
mainHandler.removeCallbacks(outgoingRingtoneRunnable);
@@ -1417,7 +1443,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
PendingIntent answerPendingIntent =
PendingIntent.getActivity(
this.appContext,
0,
PI_ANSWER,
answerIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
@@ -1428,7 +1454,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
PendingIntent declinePendingIntent =
PendingIntent.getBroadcast(
this.appContext,
1,
PI_DECLINE,
declineIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
@@ -1439,7 +1465,7 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
PendingIntent fullScreenPendingIntent =
PendingIntent.getActivity(
this.appContext,
2,
PI_FULLSCREEN,
fullScreenIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
@@ -1490,6 +1516,87 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
notificationManager.notify(NOTIFICATION_ID_CALL, builder.build());
}
private void showMissedCallNotification(int accId, int chatId, boolean wasVideoCall) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (!hasNotificationPermission()) {
Log.w(TAG, "Cannot show missed call notification: no permission");
return;
}
}
DcContext dcContext = ApplicationContext.getDcAccounts().getAccount(accId);
DcChat dcChat = dcContext.getChat(chatId);
String callerName = CallUtil.getNameFromChat(dcChat);
Intent contentAction = new Intent(appContext, ConversationActivity.class);
contentAction.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
contentAction.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, accId);
contentAction.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent contentIntent =
PendingIntent.getActivity(
appContext,
PI_MISSED_CONTENT,
contentAction,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Intent callBackAction = new Intent(appContext, CallActionReceiver.class);
callBackAction.setAction(CallActivity.ACTION_CALL_BACK);
callBackAction.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
callBackAction.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, accId);
callBackAction.putExtra(CallActivity.EXTRA_STARTS_WITH_VIDEO, wasVideoCall);
PendingIntent callBackIntent =
PendingIntent.getBroadcast(
appContext,
PI_MISSED_CALLBACK,
callBackAction,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Intent messageAction = new Intent(appContext, CallActionReceiver.class);
messageAction.setAction(CallActivity.ACTION_MESSAGE);
messageAction.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId);
messageAction.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, accId);
PendingIntent messageIntent =
PendingIntent.getBroadcast(
appContext,
PI_MISSED_MESSAGE,
messageAction,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
String contentText = appContext.getString(R.string.missed_call);
Notification.Builder builder =
new Notification.Builder(appContext, CHANNEL_ID_MISSED)
.setSmallIcon(R.drawable.icon_notification)
.setContentTitle(callerName)
.setContentText(contentText)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.addAction(
new Notification.Action.Builder(
null, appContext.getString(R.string.call_back), callBackIntent)
.build())
.addAction(
new Notification.Action.Builder(
null, appContext.getString(R.string.chat_input_placeholder), messageIntent)
.build());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setCategory(Notification.CATEGORY_MISSED_CALL);
} else {
builder.setCategory(Notification.CATEGORY_CALL);
}
Icon icon = displayIcon.getValue();
if (icon != null) {
builder.setLargeIcon(icon);
}
notificationManager.notify(NOTIFICATION_ID_MISSED_CALL, builder.build());
}
private Notification buildOngoingCallNotification(
String statusText, String displayName, Icon icon) {
Intent activityIntent = new Intent(this.appContext, CallActivity.class);
@@ -1501,14 +1608,14 @@ public class CallCoordinator implements DcEventCenter.DcEventDelegate {
PendingIntent hangupPendingIntent =
PendingIntent.getBroadcast(
this.appContext,
3,
PI_HANGUP,
hangupIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent contentIntent =
PendingIntent.getActivity(
this.appContext,
4,
PI_ONGOING_CONTENT,
activityIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
@@ -9,10 +9,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import java.util.List;
import org.thoughtcrime.securesms.EglUtils;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerationAndroid;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
@@ -22,6 +24,7 @@ import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
@RequiresApi(Build.VERSION_CODES.M)
public class MediaStreamManager {
private static final String TAG = "MediaStreamManager";
@@ -29,9 +32,9 @@ public class MediaStreamManager {
private static final String AUDIO_TRACK_ID = "audio_track";
private static final String VIDEO_TRACK_ID = "video_track";
private static final int VIDEO_WIDTH = 1280;
private static final int VIDEO_HEIGHT = 720;
private static final int VIDEO_FPS = 30;
private static final int TARGET_WIDTH = 1280;
private static final int TARGET_HEIGHT = 720;
private static final int TARGET_FPS = 30;
private final Context context;
private final PeerConnectionFactory peerConnectionFactory;
@@ -42,6 +45,9 @@ public class MediaStreamManager {
private SurfaceTextureHelper surfaceTextureHelper;
private volatile boolean isFrontCamera = true;
private volatile boolean isCapturing = false;
private volatile String currentDeviceName;
private volatile int currentCaptureWidth;
private volatile int currentCaptureHeight;
public interface Callback {
void onMediaStreamReady(MediaStream stream);
@@ -90,7 +96,6 @@ public class MediaStreamManager {
*
* @return true if the camera is capturing, false if it could not be started
*/
@RequiresApi(api = Build.VERSION_CODES.M)
public synchronized boolean startVideoCapture() {
if (isCapturing) {
return true;
@@ -116,9 +121,23 @@ public class MediaStreamManager {
videoCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
}
videoCapturer.startCapture(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS);
int[] captureFormat = selectCaptureFormat(currentDeviceName);
currentCaptureWidth = captureFormat[0];
currentCaptureHeight = captureFormat[1];
videoCapturer.startCapture(currentCaptureWidth, currentCaptureHeight, TARGET_FPS);
videoSource.adaptOutputFormat(TARGET_WIDTH, TARGET_HEIGHT, TARGET_FPS);
isCapturing = true;
Log.d(TAG, "Video capture started");
Log.d(
TAG,
"Video capture started at "
+ currentCaptureWidth
+ "x"
+ currentCaptureHeight
+ ", adapted to "
+ TARGET_WIDTH
+ "x"
+ TARGET_HEIGHT);
return true;
}
@@ -141,6 +160,60 @@ public class MediaStreamManager {
isCapturing = false;
}
private int[] selectCaptureFormat(@Nullable String deviceName) {
if (deviceName == null) {
Log.w(TAG, "Device name is null, using target dimensions");
return new int[] {TARGET_WIDTH, TARGET_HEIGHT};
}
Camera2Enumerator enumerator = new Camera2Enumerator(context);
List<CameraEnumerationAndroid.CaptureFormat> formats =
enumerator.getSupportedFormats(deviceName);
if (formats == null || formats.isEmpty()) {
Log.w(TAG, "No supported formats for " + deviceName);
return new int[] {TARGET_WIDTH, TARGET_HEIGHT};
}
CameraEnumerationAndroid.CaptureFormat best = null;
int bestPixels = Integer.MAX_VALUE;
for (CameraEnumerationAndroid.CaptureFormat f : formats) {
if (f.width >= TARGET_WIDTH && f.height >= TARGET_HEIGHT) {
int pixels = f.width * f.height;
if (pixels < bestPixels) {
bestPixels = pixels;
best = f;
}
}
}
if (best != null) {
Log.d(
TAG, "Selected capture format: " + best.width + "x" + best.height + " for " + deviceName);
return new int[] {best.width, best.height};
}
CameraEnumerationAndroid.CaptureFormat largest = null;
int largestPixels = 0;
for (CameraEnumerationAndroid.CaptureFormat f : formats) {
int pixels = f.width * f.height;
if (pixels > largestPixels) {
largestPixels = pixels;
largest = f;
}
}
if (largest != null) {
Log.w(
TAG,
"Using largest format " + largest.width + "x" + largest.height + " for " + deviceName);
return new int[] {largest.width, largest.height};
}
return new int[] {TARGET_WIDTH, TARGET_HEIGHT};
}
@Nullable
private VideoCapturer createVideoCapturer() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
@@ -158,6 +231,7 @@ public class MediaStreamManager {
VideoCapturer capturer = enumerator.createCapturer(deviceName, null);
if (capturer != null) {
isFrontCamera = true;
currentDeviceName = deviceName;
return capturer;
}
}
@@ -168,6 +242,7 @@ public class MediaStreamManager {
VideoCapturer capturer = enumerator.createCapturer(deviceName, null);
if (capturer != null) {
isFrontCamera = enumerator.isFrontFacing(deviceName);
currentDeviceName = deviceName;
return capturer;
}
}
@@ -224,6 +299,30 @@ public class MediaStreamManager {
public void onCameraSwitchDone(boolean isFront) {
Log.d(TAG, "switchCamera SUCCESS, isFront=" + isFront);
isFrontCamera = isFront;
currentDeviceName = finalTargetCameraName;
int[] newFormat = selectCaptureFormat(finalTargetCameraName);
if (newFormat[0] != currentCaptureWidth || newFormat[1] != currentCaptureHeight) {
Log.d(
TAG,
"Changing capture format: "
+ currentCaptureWidth
+ "x"
+ currentCaptureHeight
+ " to "
+ newFormat[0]
+ "x"
+ newFormat[1]);
currentCaptureWidth = newFormat[0];
currentCaptureHeight = newFormat[1];
cameraVideoCapturer.changeCaptureFormat(
currentCaptureWidth, currentCaptureHeight, TARGET_FPS);
}
if (videoSource != null) {
videoSource.adaptOutputFormat(TARGET_WIDTH, TARGET_HEIGHT, TARGET_FPS);
}
if (callback != null) callback.onCameraSwitch(isFront);
}
@@ -264,7 +264,10 @@ public class AudioPlaybackViewModel extends ViewModel {
updateCurrentState(false);
} else if (player.getPlaybackState() == Player.STATE_ENDED
&& !player.hasNextMediaItem()) {
mediaController.setPlayWhenReady(false);
mediaController.stop();
mediaController.clearMediaItems();
stopUpdateProgress();
playbackState.setValue(AudioPlaybackState.idle());
}
}
}
@@ -46,7 +46,7 @@ public class DcContactsLoader extends AsyncLoader<DcContactsLoader.Ret> {
if (query == null && addScanQRLink) {
additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_QR_INVITE);
}
if (addCreateContactLink && !dcContext.isChatmail()) {
if (addCreateContactLink && dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) {
additional_items =
Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_CLASSIC_CONTACT);
}
@@ -54,7 +54,7 @@ public class DcContactsLoader extends AsyncLoader<DcContactsLoader.Ret> {
additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_GROUP);
additional_items = Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_BROADCAST);
if (!dcContext.isChatmail()) {
if (dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0) {
additional_items =
Util.appendInt(additional_items, DcContact.DC_CONTACT_ID_NEW_UNENCRYPTED_GROUP);
}
@@ -528,16 +528,20 @@ public class DcHelper {
.show();
}
public static void showInvalidUnencryptedDialog(Context context) {
new AlertDialog.Builder(context)
public static AlertDialog.Builder prepareInvalidUnencryptedDialog(
Context context, AlertDialog.Builder builder) {
return builder
.setMessage(context.getString(R.string.invalid_unencrypted_explanation))
.setNeutralButton(R.string.learn_more, (d, w) -> openHelp(context, "#howtoe2ee"))
.setNegativeButton(
R.string.qrscan_title,
(d, w) -> context.startActivity(new Intent(context, QrActivity.class)))
.setPositiveButton(R.string.ok, null)
.setCancelable(true)
.show();
.setCancelable(true);
}
public static void showInvalidUnencryptedDialog(Context context) {
prepareInvalidUnencryptedDialog(context, new AlertDialog.Builder(context)).show();
}
public static void openHelp(Context context, String section) {
@@ -70,14 +70,13 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.guava.Optional;
import org.thoughtcrime.securesms.util.views.Stub;
public class AttachmentManager {
private static final String TAG = "AttachmentManager";
private final @NonNull Context context;
private final @NonNull Stub<View> attachmentViewStub;
private final @NonNull View attachmentView;
private final @NonNull AttachmentListener attachmentListener;
private RemovableEditableMediaView removableMediaView;
@@ -98,20 +97,16 @@ public class AttachmentManager {
public AttachmentManager(@NonNull Activity activity, @NonNull AttachmentListener listener) {
this.context = activity;
this.attachmentListener = listener;
this.attachmentViewStub = ViewUtil.findStubById(activity, R.id.attachment_editor_stub);
}
private void inflateStub() {
if (!attachmentViewStub.resolved()) {
View root = attachmentViewStub.get();
this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail);
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
this.webxdcView = ViewUtil.findById(root, R.id.attachment_webxdc);
this.vcardView = ViewUtil.findById(root, R.id.attachment_vcard);
// this.mapView = ViewUtil.findById(root, R.id.attachment_location);
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
this.attachmentView = ViewUtil.findById(activity, R.id.attachment_editor);
if (this.attachmentView != null) {
this.thumbnail = ViewUtil.findById(attachmentView, R.id.attachment_thumbnail);
this.audioView = ViewUtil.findById(attachmentView, R.id.attachment_audio);
this.documentView = ViewUtil.findById(attachmentView, R.id.attachment_document);
this.webxdcView = ViewUtil.findById(attachmentView, R.id.attachment_webxdc);
this.vcardView = ViewUtil.findById(attachmentView, R.id.attachment_vcard);
// this.mapView = ViewUtil.findById(attachmentView, R.id.attachment_location);
this.removableMediaView = ViewUtil.findById(attachmentView, R.id.removable_media_view);
removableMediaView.addRemoveClickListener(new RemoveButtonListener());
removableMediaView.setEditClickListener(new EditButtonListener());
@@ -120,10 +115,10 @@ public class AttachmentManager {
}
public void clear(@NonNull GlideRequests glideRequests, boolean animate) {
if (attachmentViewStub.resolved()) {
if (this.attachmentView != null) {
if (animate) {
ViewUtil.fadeOut(attachmentViewStub.get(), 200)
ViewUtil.fadeOut(attachmentView, 200)
.addListener(
new ListenableFuture.Listener<Boolean>() {
@Override
@@ -229,7 +224,6 @@ public class AttachmentManager {
final int height,
final int chatId,
AudioPlaybackViewModel playbackViewModel) {
inflateStub();
final SettableFuture<Boolean> result = new SettableFuture<>();
@@ -445,7 +439,7 @@ public class AttachmentManager {
}
public static void selectDocument(Activity activity, int requestCode) {
selectMediaType(activity, "*/*", null, requestCode);
selectMediaType(activity, "*/*", null, requestCode, null, true);
}
public static void selectWebxdc(Activity activity, int requestCode) {
@@ -483,7 +477,7 @@ public class AttachmentManager {
.ifNecessary()
.withPermanentDenialDialog(
activity.getString(R.string.perm_explain_access_to_storage_denied))
.onAllGranted(() -> selectMediaType(activity, "image/*", null, requestCode))
.onAllGranted(() -> selectMediaType(activity, "image/*", null, requestCode, null, false))
.execute();
}
@@ -608,20 +602,6 @@ public class AttachmentManager {
.execute();
}
public static void selectMediaType(
Activity activity, @NonNull String type, @Nullable String[] extraMimeType, int requestCode) {
selectMediaType(activity, type, extraMimeType, requestCode, null, false);
}
public static void selectMediaType(
Activity activity,
@NonNull String type,
@Nullable String[] extraMimeType,
int requestCode,
@Nullable Uri initialUri) {
selectMediaType(activity, type, extraMimeType, requestCode, initialUri, false);
}
public static void selectMediaType(
Activity activity,
@NonNull String type,
@@ -801,6 +781,10 @@ public class AttachmentManager {
updateVisibility();
}
public boolean isHidden() {
return hidden;
}
private void setAttachmentPresent(boolean isPresent) {
this.attachmentPresent = isPresent;
updateVisibility();
@@ -813,9 +797,9 @@ public class AttachmentManager {
} else {
vis = View.GONE;
}
if (vis == View.GONE && !attachmentViewStub.resolved()) {
if (attachmentView == null) {
return;
}
attachmentViewStub.get().setVisibility(vis);
attachmentView.setVisibility(vis);
}
}
@@ -11,6 +11,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
@@ -23,13 +24,18 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
import androidx.core.app.RemoteInput;
import androidx.core.app.TaskStackBuilder;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcMsg;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.HashMap;
@@ -42,6 +48,7 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShareActivity;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
@@ -61,8 +68,18 @@ public class NotificationCenter {
private volatile long lastAudibleNotification = 0;
private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2);
// Map<accountId, Map<chatId, lines>, contains the last lines of each chat for each account
private final HashMap<Integer, HashMap<Integer, LinkedHashMap<Integer, String>>> inboxes =
private static class NotifData {
final Person sender;
final String text;
NotifData(Person sender, String text) {
this.sender = sender;
this.text = text;
}
}
// notification history of each chat for each account
private final HashMap<Integer, HashMap<Integer, LinkedHashMap<Integer, NotifData>>> inboxes =
new HashMap<>();
public NotificationCenter(Context context) {
@@ -410,31 +427,42 @@ public class NotificationCenter {
DcMsg dcMsg = dcContext.getMsg(msgId);
NotificationPrivacyPreference privacy = Prefs.getNotificationPrivacy(context);
String shortLine =
DcContact sender = dcContext.getContact(dcMsg.getFromId());
String senderName = dcMsg.getSenderName(sender);
String personId = accountId + "-" + dcMsg.getFromId();
if (dcMsg.getOverrideSenderName() != null) {
// we need to treat the contact as a separate Person with different ID
// otherwise the name will be overwritten by future notifications
personId += "-" + senderName;
}
String text =
privacy.isDisplayMessage()
? dcMsg.getSummarytext(2000)
: context.getString(R.string.notify_new_message);
String shortLine = text;
if (dcChat.isMultiUser() && privacy.isDisplayContact()) {
shortLine =
dcMsg.getSenderName(dcContext.getContact(dcMsg.getFromId())) + ": " + shortLine;
shortLine = senderName + ": " + text;
}
String tickerLine = shortLine;
if (!dcChat.isMultiUser() && privacy.isDisplayContact()) {
tickerLine =
dcMsg.getSenderName(dcContext.getContact(dcMsg.getFromId())) + ": " + tickerLine;
if (dcMsg.getOverrideSenderName() != null) {
// There is an "overridden" display name on the message, so, we need to prepend the
// display name to the message,
// i.e. set the shortLine to be the same as the tickerLine.
shortLine = tickerLine;
}
NotifData notifData =
new NotifData(
new Person.Builder()
.setName(senderName)
.setIcon(getAvatarIcon(sender))
.setBot(sender.isBot())
.setKey(personId)
.build(),
text);
String tickerLine = text;
if (privacy.isDisplayContact()) {
tickerLine = senderName + ": " + text;
}
DcMsg quotedMsg = dcMsg.getQuotedMsg();
boolean isMention = dcChat.isMultiUser() && quotedMsg != null && quotedMsg.isOutgoing();
maybeAddNotification(accountId, dcChat, msgId, shortLine, tickerLine, true, isMention);
maybeAddNotification(accountId, dcChat, msgId, notifData, tickerLine, true, isMention);
});
}
@@ -450,16 +478,27 @@ public class NotificationCenter {
// just do nothing.
}
DcContact sender = dcContext.getContact(contactId);
String shortLine =
DcContact contact = dcContext.getContact(contactId);
String text =
context.getString(
R.string.reaction_by_other,
sender.getDisplayName(),
contact.getDisplayName(),
reaction,
dcMsg.getSummarytext(2000));
DcChat dcChat = dcContext.getChat(dcMsg.getChatId());
NotifData notifData =
new NotifData(
new Person.Builder()
.setName(contact.getDisplayName())
.setIcon(getAvatarIcon(contact))
.setBot(contact.isBot())
.setKey(accountId + "-" + contactId)
.build(),
text);
maybeAddNotification(
accountId, dcChat, msgId, shortLine, shortLine, false, dcChat.isMultiUser());
accountId, dcChat, msgId, notifData, text, false, dcChat.isMultiUser());
});
}
@@ -473,6 +512,7 @@ public class NotificationCenter {
DcContext dcContext = context.getDcAccounts().getAccount(accountId);
DcMsg dcMsg = dcContext.getMsg(msgId);
DcChat dcChat = dcContext.getChat(dcMsg.getChatId());
DcMsg parentMsg;
if (dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) {
parentMsg = dcMsg;
@@ -486,10 +526,34 @@ public class NotificationCenter {
JSONObject info = parentMsg.getWebxdcInfo();
final String name = JsonUtils.optString(info, "name");
String shortLine = name.isEmpty() ? text : (name + ": " + text);
DcChat dcChat = dcContext.getChat(dcMsg.getChatId());
String tickerLine = name.isEmpty() ? text : (name + ": " + text);
NotifData notifData;
if (dcChat.isMultiUser()) {
byte[] blob = parentMsg.getWebxdcBlob(JsonUtils.optString(info, "icon"));
notifData =
new NotifData(
new Person.Builder()
.setName(name)
.setIcon(getAvatarIcon(blob))
.setKey(accountId + "-webxdc-" + msgId)
.build(),
text);
} else {
DcContact sender = dcContext.getContact(contactId);
notifData =
new NotifData(
new Person.Builder()
.setName(sender.getDisplayName())
.setIcon(getAvatarIcon(sender))
.setBot(sender.isBot())
.setKey(accountId + "-" + contactId)
.build(),
tickerLine);
}
maybeAddNotification(
accountId, dcChat, msgId, shortLine, shortLine, false, dcChat.isMultiUser());
accountId, dcChat, msgId, notifData, tickerLine, false, dcChat.isMultiUser());
});
}
@@ -498,7 +562,7 @@ public class NotificationCenter {
int accountId,
DcChat dcChat,
int msgId,
String shortLine,
NotifData notifData,
String tickerLine,
boolean playInChatSound,
boolean isMention) {
@@ -538,20 +602,23 @@ public class NotificationCenter {
// the user may eg. have chosen a different sound
String notificationChannel = getNotificationChannel(notificationManager, chatData, dcChat);
LinkedHashMap<Integer, String> messagesForInbox = null;
if (privacy.isDisplayContact() && privacy.isDisplayMessage()) {
LinkedHashMap<Integer, NotifData> messagesForInbox = null;
if (privacy.isDisplayContact()) {
synchronized (inboxes) {
HashMap<Integer, LinkedHashMap<Integer, String>> accountInbox = inboxes.get(accountId);
HashMap<Integer, LinkedHashMap<Integer, NotifData>> accountInbox = inboxes.get(accountId);
if (accountInbox == null) {
accountInbox = new HashMap<>();
inboxes.put(accountId, accountInbox);
}
LinkedHashMap<Integer, String> messages = accountInbox.get(chatId);
LinkedHashMap<Integer, NotifData> messages = accountInbox.get(chatId);
if (messages == null) {
messages = new LinkedHashMap<>();
accountInbox.put(chatId, messages);
}
messages.put(msgId, shortLine);
if (!privacy.isDisplayMessage()) {
messages.clear();
}
messages.put(msgId, notifData);
messagesForInbox = new LinkedHashMap<>(messages);
}
}
@@ -564,7 +631,7 @@ public class NotificationCenter {
dcContext,
dcChat,
notificationChannel,
shortLine,
notifData.text,
tickerLine,
signal,
messagesForInbox,
@@ -583,7 +650,7 @@ public class NotificationCenter {
String contentText,
String ticker,
boolean signal,
LinkedHashMap<Integer, String> messagesForInbox,
LinkedHashMap<Integer, NotifData> messagesForInbox,
int messageCount,
boolean includeSummary) {
try {
@@ -658,7 +725,7 @@ public class NotificationCenter {
NotificationCompat.Action markAsReadAction =
new NotificationCompat.Action(
R.drawable.check, context.getString(R.string.mark_as_read_short), markReadIntent);
R.drawable.check, context.getString(R.string.mark_as_read), markReadIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
NotificationCompat.Action replyAction =
@@ -694,14 +761,42 @@ public class NotificationCenter {
}
}
// Create inbox style (gets visible if the notification is expanded)
if (privacy.isDisplayContact() && privacy.isDisplayMessage() && messagesForInbox != null) {
// Create messaging style
if (privacy.isDisplayContact() && messagesForInbox != null) {
try {
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
for (String line : messagesForInbox.values()) {
inboxStyle.addLine(line);
Intent viewChatIntent = new Intent(context, ShareActivity.class);
viewChatIntent.setAction(Intent.ACTION_SEND);
viewChatIntent.putExtra(ShareActivity.EXTRA_ACC_ID, dcContext.getAccountId());
viewChatIntent.putExtra(ShareActivity.EXTRA_CHAT_ID, dcChat.getId());
ShortcutInfoCompat shortcut =
new ShortcutInfoCompat.Builder(
context, "chat-" + dcContext.getAccountId() + "-" + dcChat.getId())
.setShortLabel(dcChat.getName())
.setLongLived(true)
.setIntent(viewChatIntent)
.build();
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut);
builder.setShortcutInfo(shortcut);
DcContact selfContact = dcContext.getContact(DcContact.DC_CONTACT_ID_SELF);
Person self =
new Person.Builder()
.setName(selfContact.getDisplayName())
.setIcon(getAvatarIcon(selfContact))
.setKey(accountId + "-" + selfContact.getId())
.build();
NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle(self);
if (dcChat.isMultiUser()) {
style.setGroupConversation(true);
style.setConversationTitle(dcChat.getName());
}
builder.setStyle(inboxStyle);
for (Map.Entry<Integer, NotifData> msgEntry : messagesForInbox.entrySet()) {
long timestamp_ms = dcContext.getMsg(msgEntry.getKey()).getSortTimestamp() * 1000;
NotifData notifData = msgEntry.getValue();
style.addMessage(notifData.text, timestamp_ms, notifData.sender);
}
builder.setStyle(style);
} catch (Exception e) {
Log.w(TAG, e);
}
@@ -748,7 +843,7 @@ public class NotificationCenter {
@WorkerThread
private void rebuildNotification(
int accountId, int chatId, LinkedHashMap<Integer, String> messages) {
int accountId, int chatId, LinkedHashMap<Integer, NotifData> messages) {
try {
DcContext dcContext = ApplicationContext.getDcAccounts().getAccount(accountId);
DcChat dcChat = dcContext.getChat(chatId);
@@ -766,9 +861,9 @@ public class NotificationCenter {
// Get the latest message ID (last entry in LinkedHashMap)
Integer latestMsgId = null;
String lastLine = null;
for (Map.Entry<Integer, String> entry : messages.entrySet()) {
for (Map.Entry<Integer, NotifData> entry : messages.entrySet()) {
latestMsgId = entry.getKey();
lastLine = entry.getValue();
lastLine = entry.getValue().text;
}
if (latestMsgId == null || lastLine == null) {
return;
@@ -797,8 +892,35 @@ public class NotificationCenter {
}
}
public Bitmap getAvatar(DcChat dcChat) {
Recipient recipient = new Recipient(context, dcChat);
private static @Nullable IconCompat getAvatarIcon(byte[] blob) {
if (blob == null) {
return null;
}
ByteArrayInputStream is = new ByteArrayInputStream(blob);
BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
Bitmap bitmap = drawable.getBitmap();
return IconCompat.createWithBitmap(bitmap);
}
private @Nullable IconCompat getAvatarIcon(DcChat dcChat) {
Bitmap avatar = getAvatar(dcChat);
return avatar != null ? IconCompat.createWithBitmap(avatar) : null;
}
private @Nullable IconCompat getAvatarIcon(DcContact dcContact) {
Bitmap avatar = getAvatar(dcContact);
return avatar != null ? IconCompat.createWithBitmap(avatar) : null;
}
private @Nullable Bitmap getAvatar(DcChat dcChat) {
return getAvatar(new Recipient(context, dcChat));
}
private @Nullable Bitmap getAvatar(DcContact dcContact) {
return getAvatar(new Recipient(context, dcContact));
}
private @Nullable Bitmap getAvatar(Recipient recipient) {
try {
Drawable drawable;
ContactPhoto contactPhoto = recipient.getContactPhoto(context);
@@ -837,12 +959,12 @@ public class NotificationCenter {
public void removeNotification(int accountId, int chatId, int msgId) {
boolean shouldCancelNotification = false;
boolean removeSummary = false;
LinkedHashMap<Integer, String> remainingMessages = null;
LinkedHashMap<Integer, NotifData> remainingMessages = null;
synchronized (inboxes) {
HashMap<Integer, LinkedHashMap<Integer, String>> accountInbox = inboxes.get(accountId);
HashMap<Integer, LinkedHashMap<Integer, NotifData>> accountInbox = inboxes.get(accountId);
if (accountInbox != null) {
LinkedHashMap<Integer, String> messages = accountInbox.get(chatId);
LinkedHashMap<Integer, NotifData> messages = accountInbox.get(chatId);
if (messages != null) {
messages.remove(msgId);
@@ -875,7 +997,7 @@ public class NotificationCenter {
public void removeNotifications(int accountId, int chatId) {
boolean removeSummary;
synchronized (inboxes) {
HashMap<Integer, LinkedHashMap<Integer, String>> accountInbox = inboxes.get(accountId);
HashMap<Integer, LinkedHashMap<Integer, NotifData>> accountInbox = inboxes.get(accountId);
if (accountInbox == null) {
accountInbox = new HashMap<>();
}
@@ -901,7 +1023,7 @@ public class NotificationCenter {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
String tag = String.valueOf(accountId);
synchronized (inboxes) {
HashMap<Integer, LinkedHashMap<Integer, String>> accountInbox = inboxes.get(accountId);
HashMap<Integer, LinkedHashMap<Integer, NotifData>> accountInbox = inboxes.get(accountId);
notificationManager.cancel(tag, ID_MSG_SUMMARY);
if (accountInbox != null) {
for (Integer chatId : accountInbox.keySet()) {
@@ -18,6 +18,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.b44t.messenger.DcContext;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.google.zxing.BinaryBitmap;
@@ -120,10 +121,12 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
DcContext dcContext = DcHelper.getContext(this);
menu.clear();
getMenuInflater().inflate(R.menu.qr_show, menu);
menu.findItem(R.id.new_classic_contact)
.setVisible(!scanRelay && !DcHelper.getContext(this).isChatmail());
.setVisible(!scanRelay && dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 0);
Util.redMenuItem(menu, R.id.withdraw);
if (tabLayout.getSelectedTabPosition() == TAB_SCAN) {
@@ -314,6 +314,12 @@ public class QrCodeHandler {
private void showFingerprintOrQrSuccess(
AlertDialog.Builder builder, DcLot qrParsed, String name) {
if (qrParsed.getState() == DcContext.DC_QR_ADDR
&& dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 1) {
DcHelper.prepareInvalidUnencryptedDialog(activity, builder);
return;
}
@StringRes
int resId =
qrParsed.getState() == DcContext.DC_QR_ADDR
@@ -57,22 +57,26 @@ public class LongClickCopySpan extends ClickableSpan {
DcContext dcContext = DcHelper.getContext(activity);
int contactId = dcContext.lookupContactIdByAddr(addr);
if (contactId == 0 && dcContext.mayBeValidAddr(addr)) {
contactId = dcContext.createContact(null, addr);
}
DcContact contact = dcContext.getContact(contactId);
if (contact.getId() != 0
DcContact contact = (contactId != 0) ? dcContext.getContact(contactId) : null;
if (contact != null
&& !contact.isBlocked()
&& dcContext.getChatIdByContactId(contact.getId()) != 0) {
openChat(activity, contact);
} else if (contact == null
&& dcContext.getConfigInt(DcHelper.CONFIG_FORCE_ENCRYPTION) == 1) {
DcHelper.showInvalidUnencryptedDialog(activity);
} else {
String name = contact != null ? contact.getDisplayName() : addr;
new AlertDialog.Builder(activity)
.setMessage(
activity.getString(R.string.ask_start_chat_with, contact.getDisplayName()))
.setMessage(activity.getString(R.string.ask_start_chat_with, name))
.setPositiveButton(
android.R.string.ok,
(dialog, which) -> {
openChat(activity, contact);
openChat(
activity,
contact == null
? dcContext.getContact(dcContext.createContact(null, addr))
: contact);
})
.setNegativeButton(R.string.cancel, null)
.show();
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="translationY"
android:valueType="floatType"
android:valueFrom="0dp"
android:valueTo="-8dp"
android:duration="1500"
android:repeatMode="reverse"
android:repeatCount="infinite"
android:interpolator="@android:interpolator/accelerate_decelerate" />
</set>
+13
View File
@@ -0,0 +1,13 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="228dp"
android:height="280dp"
android:viewportWidth="228"
android:viewportHeight="280">
<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="#ffffff" />
<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="#ffffff" />
</vector>
+12
View File
@@ -0,0 +1,12 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="490.3dp"
android:height="72.64dp"
android:viewportWidth="490.3"
android:viewportHeight="72.64">
<path
android:pathData="M17.45,58.55L50.75,58.55L49.76,46.94L18.53,46.94ZM33.92,30.83 L42.83,51.26 42.29,55.13 49.85,70.7L67.13,70.7L33.92,3.74 0.81,70.7L17.99,70.7L25.82,54.5 25.1,51.17ZM85.75,29.3L72.35,29.3v41.4h13.41zM97.72,43.34 L103.57,31.73q-1.08,-1.62 -3.15,-2.52 -2.07,-0.9 -4.32,-0.9 -3.42,0 -6.57,2.25 -3.15,2.25 -5.13,6.12 -1.89,3.87 -1.89,8.82l3.24,4.77q0,-2.97 0.81,-5.04 0.81,-2.07 2.34,-3.15 1.53,-1.08 3.51,-1.08 1.8,0 2.97,0.63 1.26,0.63 2.34,1.71zM119.41,50q0,-3.06 1.44,-5.49 1.44,-2.43 3.96,-3.87 2.61,-1.44 5.58,-1.44 2.25,0 4.59,0.72 2.34,0.72 4.32,2.07 2.07,1.35 3.33,3.33L142.63,32q-2.16,-1.53 -5.4,-2.52 -3.24,-1.08 -7.92,-1.08 -6.84,0 -12.33,2.7 -5.4,2.7 -8.55,7.56 -3.06,4.77 -3.06,11.34 0,6.48 3.06,11.34 3.15,4.86 8.55,7.56 5.49,2.7 12.33,2.7 4.68,0 7.92,-0.99 3.24,-1.08 5.4,-2.79L142.63,54.59q-1.26,1.98 -3.24,3.33 -1.89,1.35 -4.23,2.16 -2.25,0.72 -4.77,0.72 -2.97,0 -5.58,-1.35 -2.52,-1.35 -3.96,-3.69 -1.44,-2.43 -1.44,-5.76zM162.97,57.74q0,-1.53 0.72,-2.52 0.72,-1.08 2.16,-1.62 1.44,-0.54 3.87,-0.54 3.15,0 5.94,0.9 2.88,0.81 5.04,2.43v-6.03q-1.08,-1.17 -3.24,-2.25 -2.16,-1.08 -5.22,-1.8 -2.97,-0.72 -6.75,-0.72 -7.74,0 -11.97,3.51 -4.23,3.42 -4.23,9.36 0,4.23 1.98,7.2 2.07,2.97 5.4,4.5 3.42,1.44 7.38,1.44 3.96,0 7.38,-1.35 3.42,-1.44 5.58,-4.14 2.16,-2.79 2.16,-6.75l-1.44,-5.4q0,2.88 -1.26,4.86 -1.26,1.98 -3.24,2.97 -1.98,0.99 -4.32,0.99 -1.62,0 -2.97,-0.54 -1.35,-0.63 -2.16,-1.71 -0.81,-1.17 -0.81,-2.79zM156.94,41.99q0.9,-0.54 2.88,-1.35 1.98,-0.9 4.59,-1.53 2.7,-0.63 5.49,-0.63 1.89,0 3.33,0.36 1.53,0.36 2.52,1.17 0.99,0.81 1.44,1.98 0.54,1.08 0.54,2.61v26.1h13.05L190.77,41.54q0,-4.32 -2.52,-7.29 -2.52,-2.97 -6.84,-4.5 -4.32,-1.62 -9.81,-1.62 -5.85,0 -10.8,1.53 -4.95,1.53 -8.46,3.24zM227.85,45.5v25.2h14.13L241.98,44.06q0,-7.47 -3.51,-11.61 -3.51,-4.14 -11.25,-4.14 -4.59,0 -7.74,1.89 -3.06,1.89 -4.86,5.31L214.62,29.3L200.94,29.3v41.4h13.68L214.62,45.5q0,-2.43 0.9,-4.14 0.9,-1.71 2.52,-2.61 1.62,-0.9 3.78,-0.9 3.24,0 4.59,1.98 1.44,1.98 1.44,5.67zM273.74,71.6q7.92,0 13.41,-3.06 5.58,-3.06 8.82,-9.18l-12.15,-2.97q-1.53,2.7 -4.14,4.05 -2.52,1.35 -6.12,1.35 -3.15,0 -5.31,-1.35 -2.16,-1.44 -3.24,-4.14 -1.08,-2.7 -1.08,-6.48 0.09,-4.14 1.08,-6.84 1.08,-2.79 3.15,-4.14 2.07,-1.35 5.13,-1.35 2.43,0 4.23,1.08 1.8,1.08 2.79,3.06 0.99,1.98 0.99,4.68 0,0.63 -0.36,1.62 -0.27,0.9 -0.72,1.53l3.24,-4.14h-25.56v7.56h37.8q0.18,-0.63 0.18,-1.53 0,-0.99 0,-1.98 0,-6.66 -2.61,-11.34 -2.61,-4.68 -7.56,-7.11 -4.95,-2.52 -12.15,-2.52 -7.2,0 -12.51,2.61 -5.22,2.61 -8.01,7.47 -2.79,4.86 -2.79,11.52 0,6.57 2.88,11.43 2.88,4.86 8.1,7.56 5.31,2.61 12.51,2.61zM318.56,39.2q0,-5.85 2.61,-9.99 2.61,-4.14 6.84,-6.3 4.32,-2.16 9.45,-2.16 4.5,0 7.92,1.17 3.42,1.08 6.12,3.06 2.7,1.89 4.77,4.05L356.26,12.65q-3.78,-2.97 -8.28,-4.68 -4.5,-1.71 -11.43,-1.71 -7.56,0 -13.95,2.34 -6.39,2.34 -10.98,6.75 -4.59,4.41 -7.11,10.44 -2.52,6.03 -2.52,13.41 0,7.38 2.52,13.41 2.52,6.03 7.11,10.44 4.59,4.41 10.98,6.75 6.39,2.34 13.95,2.34 6.93,0 11.43,-1.71 4.5,-1.71 8.28,-4.68L356.26,49.37q-2.07,2.16 -4.77,4.05 -2.7,1.89 -6.12,3.06 -3.42,1.17 -7.92,1.17 -5.13,0 -9.45,-2.16 -4.23,-2.16 -6.84,-6.3 -2.61,-4.23 -2.61,-9.99zM381.01,0.5L367.33,0.5L367.33,70.7h13.68zM394.24,45.5v25.2h13.59L407.83,43.52q0,-5.13 -1.44,-8.64 -1.44,-3.51 -4.59,-5.31 -3.06,-1.8 -8.19,-1.8 -5.13,0 -8.46,2.43 -3.24,2.34 -4.86,6.39 -1.62,3.96 -1.62,8.91h2.34q0,-2.43 0.9,-4.05 0.9,-1.71 2.52,-2.61 1.62,-0.99 3.78,-0.99 3.33,0 4.68,1.98 1.35,1.98 1.35,5.67zM428.97,57.74q0,-1.53 0.72,-2.52 0.72,-1.08 2.16,-1.62 1.44,-0.54 3.87,-0.54 3.15,0 5.94,0.9 2.88,0.81 5.04,2.43v-6.03q-1.08,-1.17 -3.24,-2.25 -2.16,-1.08 -5.22,-1.8 -2.97,-0.72 -6.75,-0.72 -7.74,0 -11.97,3.51 -4.23,3.42 -4.23,9.36 0,4.23 1.98,7.2 2.07,2.97 5.4,4.5 3.42,1.44 7.38,1.44 3.96,0 7.38,-1.35 3.42,-1.44 5.58,-4.14 2.16,-2.79 2.16,-6.75l-1.44,-5.4q0,2.88 -1.26,4.86 -1.26,1.98 -3.24,2.97 -1.98,0.99 -4.32,0.99 -1.62,0 -2.97,-0.54 -1.35,-0.63 -2.16,-1.71 -0.81,-1.17 -0.81,-2.79zM422.94,41.99q0.9,-0.54 2.88,-1.35 1.98,-0.9 4.59,-1.53 2.7,-0.63 5.49,-0.63 1.89,0 3.33,0.36 1.53,0.36 2.52,1.17 0.99,0.81 1.44,1.98 0.54,1.08 0.54,2.61v26.1h13.05L456.78,41.54q0,-4.32 -2.52,-7.29 -2.52,-2.97 -6.84,-4.5 -4.32,-1.62 -9.81,-1.62 -5.85,0 -10.8,1.53 -4.95,1.53 -8.46,3.24zM462.09,29.3L462.09,40.55L489.8,40.55L489.8,29.3ZM469.38,14.9L469.38,70.7L482.43,70.7L482.43,14.9Z"
android:strokeWidth="0.999939"
android:fillColor="#ffffff"
android:strokeColor="#ffffff" />
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#7b00c8"
android:endColor="#5564da"
android:angle="315" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/input_panel_bg_color" />
<corners android:radius="25dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="2px"
android:color="@color/white" />
<corners android:radius="8dp" />
</shape>
@@ -36,20 +36,19 @@
android:layout_height="0dp"
android:layout_weight="1" />
<ViewStub
android:id="@+id/attachment_editor_stub"
android:inflatedId="@+id/attachment_editor"
android:layout="@layout/conversation_activity_attachment_editor_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView
android:id="@+id/conversation_activity_message_request_bottom_bar"
android:background="?android:attr/windowBackground"
android:background="@drawable/input_panel_rounded_bg"
android:elevation="1dp"
android:outlineProvider="background"
android:visibility="gone"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:paddingRight="16dp"
@@ -5,7 +5,6 @@
android:id="@+id/attachment_editor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/input_panel_bg_color"
android:gravity="center_horizontal"
android:visibility="gone">
@@ -29,9 +28,9 @@
android:contentDescription="@string/menu_add_attachment"
app:thumbnail_radius="@dimen/message_corner_radius"
app:minWidth="100dp"
app:maxWidth="300dp"
app:maxWidth="200dp"
app:minHeight="100dp"
app:maxHeight="300dp" />
app:maxHeight="200dp" />
<org.thoughtcrime.securesms.components.audioplay.AudioView
android:id="@+id/attachment_audio"
@@ -29,9 +29,9 @@
<View
android:id="@+id/bottom_divider"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:background="@drawable/compose_divider_background"
android:background="@android:color/transparent"
android:alpha="1" />
<ImageButton
@@ -8,11 +8,17 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:clickable="true"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:clipChildren="false"
android:clipToPadding="false"
android:background="?attr/input_panel_bg_color">
android:background="@drawable/input_panel_rounded_bg"
android:elevation="1dp"
android:outlineProvider="background">
<org.thoughtcrime.securesms.components.QuoteView
android:id="@+id/quote_view"
@@ -25,6 +31,14 @@
app:message_type="preview"
tools:visibility="visible" />
<include
layout="@layout/conversation_activity_attachment_editor_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/quote_view" />
<EditText
android:id="@+id/subject_text"
style="@style/ComposeEditText"
@@ -32,6 +46,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:hint="@string/subject"
android:inputType="text"
@@ -39,7 +54,7 @@
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/quote_view"
app:layout_constraintTop_toBottomOf="@id/attachment_editor"
tools:visibility="visible" />
<FrameLayout
@@ -69,11 +84,11 @@
android:id="@+id/emoji_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_gravity="bottom"
android:minHeight="40dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:background="@drawable/touch_highlight_background"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:background="@drawable/circle_touch_highlight_background"
android:contentDescription="@string/menu_toggle_keyboard" />
<org.thoughtcrime.securesms.components.ComposeText
@@ -105,11 +120,11 @@
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:src="?quick_camera_icon"
android:paddingLeft="11dp"
android:paddingRight="11dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:background="@drawable/touch_highlight_background"
android:background="@drawable/circle_touch_highlight_background"
android:contentDescription="@string/camera" />
<org.thoughtcrime.securesms.components.MicrophoneRecorderView
@@ -136,15 +151,14 @@
<org.thoughtcrime.securesms.components.AnimatingToggle
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/input_field_frame_layout"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/button_toggle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="6dp"
android:background="@drawable/send_button_bg"
android:layout_gravity="center_vertical">
android:layout_gravity="bottom">
<ImageButton
android:id="@+id/attach_button"
@@ -4,7 +4,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="8dp"
android:layout_margin="4dp"
android:src="@drawable/conversation_attachment_edit"
android:contentDescription="@string/global_menu_edit_desktop"
android:visibility="gone" />
+23 -22
View File
@@ -4,10 +4,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_welcome_gradient"
tools:context=".WelcomeActivity">
<include layout="@layout/status_bar_background" />
<LinearLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
@@ -16,7 +15,7 @@
android:gravity="center_horizontal">
<androidx.legacy.widget.Space
android:layout_weight="3"
android:layout_weight="4"
android:layout_width="match_parent"
android:layout_height="0dp" />
@@ -24,23 +23,25 @@
android:id="@+id/welcome_icon"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="10"
android:gravity="bottom"
android:src="@drawable/intro1"
android:paddingLeft="32dp"
android:paddingRight="32dp"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/welcome_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="4"
android:gravity="center"
android:padding="8dp"
android:src="@drawable/app_logo"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:scaleType="fitCenter"
android:importantForAccessibility="no" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:src="@drawable/app_name_logo"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:scaleType="fitCenter"
android:layout_marginBottom="20dp"
android:text="@string/welcome_chat_over_email"
android:textSize="22sp"
android:textStyle="bold" />
android:importantForAccessibility="no" />
<LinearLayout
android:orientation="vertical"
@@ -50,7 +51,7 @@
android:gravity="center_horizontal">
<Button
style="@style/ButtonPrimary"
style="@style/WhiteButtonPrimary"
android:id="@+id/signup_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -60,7 +61,7 @@
android:text="@string/onboarding_create_instant_account" />
<Button
style="@style/ButtonSecondary"
style="@style/WhiteButtonSecondary"
android:id="@+id/add_as_second_device_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -70,7 +71,7 @@
android:text="@string/multidevice_receiver_title" />
<Button
style="@style/ButtonSecondary"
style="@style/WhiteButtonSecondary"
android:id="@+id/backup_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -82,7 +83,7 @@
</LinearLayout>
<androidx.legacy.widget.Space
android:layout_weight="3"
android:layout_weight="4"
android:layout_width="match_parent"
android:layout_height="0dp" />
+1 -1
View File
@@ -272,7 +272,7 @@
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="channels">Canals</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="new_channel">Nou Canal</string>
<string name="new_channel">Canal nou</string>
<!-- consider keeping the term "channel" as in WhatsApp or Telegram -->
<string name="channel_name">Nom del Canal</string>
<!-- The number of times a message is shown in channels. Use what other messengers are using here, smth. as "N-times seen" or "1 Aufruf" (german) work as well, where "1 impression" or "1 hit" may sound too formal and may be more marketing slang. -->
+1 -1
View File
@@ -673,7 +673,7 @@
<string name="welcome_chat_over_email">Sicherer dezentraler Chat</string>
<string name="scan_invitation_code">Einladungscode scannen</string>
<string name="login_title">Login</string>
<string name="login_advanced_hint">Für fortgeschrittene Benutzer:\n\nVerwende keine Adresse, die du bereits in einer anderen App nutzt.</string>
<string name="login_advanced_hint">Für fortgeschrittene Benutzer.\n\nVerwende keine Adresse, die du bereits in einer anderen App nutzt.</string>
<string name="login_inbox">Posteingang</string>
<string name="login_imap_login">IMAP-Anmeldename</string>
<string name="login_imap_server">IMAP-Server</string>
File diff suppressed because it is too large Load Diff
+3
View File
@@ -450,6 +450,7 @@
<string name="call_answered_elsewhere">Chiamata risposta su un altro dispositivo</string>
<string name="call_requires_camera_permission">Per le videochiamate è necessaria l\'autorizzazione per l\'utilizzo della fotocamera.</string>
<string name="call_requires_mic_permission">Per le chiamate è necessaria l\'autorizzazione per l\'utilizzo del microfono.</string>
<string name="call_requires_connection">Impossibile avviare la chiamata. Assicurati che il tuo dispositivo disponga di una connessione a Internet e riprova.</string>
<!-- Used e.g. in a notifications to describe an ongoing call. "Call" is a noun here, in the meaning of "This is the call with ..." where the placeholder is replaced by the name of the contact. -->
<string name="call_with">Chiama con %1$s</string>
<!-- Use e.g. in a notifications during ringing. Placeholder is relaced by the name of the contact being called. -->
@@ -690,6 +691,7 @@
<string name="welcome_chat_over_email">Chat Decentralizzata Sicura</string>
<string name="scan_invitation_code">Scansiona Codice Invito</string>
<string name="login_title">Accedi</string>
<string name="login_advanced_hint">Questo login è riservato agli utenti avanzati.\n\nNon utilizzare un indirizzo che stai già usando in un\'altra app.</string>
<string name="login_inbox">In Arrivo</string>
<string name="login_imap_login">Nome Accesso IMAP</string>
<string name="login_imap_server">Server IMAP</string>
@@ -724,6 +726,7 @@
<string name="used_for_sending">Utilizzato per l\'invio</string>
<string name="hide_from_contacts">Nascondi dai Contatti</string>
<string name="hidden_from_contacts">Nascosto dai contatti</string>
<string name="enforce_e2ee">Applica Crittografia a Tutti i Ripetitori</string>
<!-- Hint for the list of relays -->
<string name="transport_list_hint">I messaggi vengono ricevuti su tutti i ripetitori.\n\n⚠️ Se modifichi qualcosa qui, assicurati che tutti i tuoi dispositivi abbiano almeno la versione 2.47.0. Altrimenti i dispositivi più vecchi potrebbero non ricevere i messaggi.</string>
<!-- shown if a QR code was scanned that can be used as a relay -->
+1 -1
View File
@@ -24,7 +24,7 @@
<dimen name="message_bubble_bottom_padding">6dp</dimen>
<dimen name="message_bubble_showmore_padding">8dp</dimen>
<dimen name="transparent_footer_padding">2dp</dimen>
<dimen name="media_bubble_remove_button_size">24dp</dimen>
<dimen name="media_bubble_remove_button_size">28dp</dimen>
<dimen name="media_bubble_edit_button_size">24dp</dimen>
<dimen name="media_bubble_default_dimens">210dp</dimen>
<dimen name="media_bubble_min_width">150dp</dimen>
+2 -1
View File
@@ -73,7 +73,7 @@
<!-- Used as an menu entry or button -->
<string name="mark_as_read">Mark as Read</string>
<!-- Used beside an icon with very few space. Shortest text for "Mark as being read". In english, this could be "Read" (past tense of "to read"), in german, this could be "Gelesen". -->
<string name="mark_as_read_short">Mark Read</string>
<string name="mark_as_read_short">Read</string>
<!-- Used as an menu entry or button -->
<string name="mark_as_unread">Mark as Unread</string>
<!-- Used beside an icon with very few space. Shortest text for "Mark as being unread". In english, this could be "Unread" (past tense of "to unread"), in german, this could be "Ungelesen". -->
@@ -435,6 +435,7 @@
<string name="canceled_call">Canceled call</string>
<string name="missed_call">Missed call</string>
<string name="already_in_call">Already in a call</string>
<string name="call_back">Call back</string>
<string name="call_answered_elsewhere">Call answered on another device</string>
<string name="call_requires_camera_permission">Camera permission is required for video calls</string>
<string name="call_requires_mic_permission">Microphone permission is required for calls</string>
+17
View File
@@ -129,6 +129,23 @@
<item name="android:background">@drawable/button_secondary_background</item>
</style>
<style name="WhiteButtonPrimary">
<item name="drawableTint">@color/def_primary</item>
<item name="android:textColor">@color/def_primary</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">8dp</item>
<item name="android:background">@drawable/white_button_bg</item>
</style>
<style name="WhiteButtonSecondary">
<item name="drawableTint">@color/white</item>
<item name="android:textColor">@color/white</item>
<item name="android:drawablePadding">5dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">8dp</item>
<item name="android:background">@drawable/white_button_secondary_background</item>
</style>
<style
name="PreferenceThemeOverlay.Fix"
parent="PreferenceThemeOverlay.v14.Material">