From f96e89956cc90188e4bcad9b1a24329d39945882 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 20 Jan 2026 16:31:02 +0100 Subject: [PATCH 01/21] account switcher: make switching accounts smoother --- .../thoughtcrime/securesms/ConversationListActivity.java | 7 +++++++ .../thoughtcrime/securesms/ConversationListFragment.java | 2 +- .../securesms/accounts/AccountSelectionListFragment.java | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index dd202b3ad..56ccb8b16 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -573,6 +573,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title))); } + public void onProfileSwitched(int profileId) { + refreshAvatar(); + refreshUnreadIndicator(); + refreshTitle(); + conversationListFragment.loadChatlist(); + } + public void onDeleteProfile(int profileId) { deleteProfileId = profileId; boolean result = ScreenLockUtil.applyScreenLock(this, getString(R.string.delete_account), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE); diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationListFragment.java index 7d22831d1..e244cf1c4 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListFragment.java @@ -260,7 +260,7 @@ public class ConversationListFragment extends BaseConversationListFragment }); } - private void loadChatlist() { + public void loadChatlist() { int listflags = 0; if (archive) { listflags |= DcContext.DC_GCL_ARCHIVED_ONLY; diff --git a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java index c5e4bcf0b..1cfd01c4a 100644 --- a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java @@ -247,13 +247,13 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv @Override public void onItemClick(AccountSelectionListItem contact) { - Activity activity = requireActivity(); AccountSelectionListFragment.this.dismiss(); int accountId = contact.getAccountId(); if (accountId == DC_CONTACT_ID_ADD_ACCOUNT) { AccountManager.getInstance().switchAccountAndStartActivity(activity, 0); } else if (accountId != DcHelper.getAccounts(activity).getSelectedAccount().getAccountId()) { - AccountManager.getInstance().switchAccountAndStartActivity(activity, accountId); + AccountManager.getInstance().switchAccount(activity, accountId); + activity.onProfileSwitched(accountId); } } } From a0c6cef0fab7d6bbf50d8aedd045b2c4cc565512 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 20 Jan 2026 19:44:32 +0100 Subject: [PATCH 02/21] add menu item to switch account --- .../org/thoughtcrime/securesms/ConversationListActivity.java | 3 +++ src/main/res/menu/forwarding_menu.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index 56ccb8b16..92bdbf910 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -453,6 +453,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } else if (itemId == R.id.menu_export_attachment) { handleSaveAttachment(); return true; + } else if (itemId == R.id.menu_switch_account) { + AccountManager.getInstance().showSwitchAccountMenu(this); + return true; } return false; diff --git a/src/main/res/menu/forwarding_menu.xml b/src/main/res/menu/forwarding_menu.xml index e19df9ffd..d635d04bc 100644 --- a/src/main/res/menu/forwarding_menu.xml +++ b/src/main/res/menu/forwarding_menu.xml @@ -5,4 +5,7 @@ + + From c1dc0cf127bb0961f2662b67693942d809befd18 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Wed, 21 Jan 2026 00:14:41 +0100 Subject: [PATCH 03/21] ConversationActivity: use currently selected account instead of storing dcContext once on creation, the selected account might change --- .../securesms/ConversationActivity.java | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index dbcc0e113..2a393a917 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -184,7 +184,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ApplicationContext context; private Recipient recipient; - private DcContext dcContext; private Rpc rpc; private DcChat dcChat = new DcChat(0, 0); private int chatId; @@ -198,7 +197,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override protected void onCreate(Bundle state, boolean ready) { this.context = ApplicationContext.getInstance(getApplicationContext()); - this.dcContext = DcHelper.getContext(context); this.rpc = DcHelper.getRpc(context); supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY); @@ -297,7 +295,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity titleView.setTitle(glideRequests, dcChat); - DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId); + try { + int accId = rpc.getSelectedAccountId(); + DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId); + } catch (RpcException e) { + Log.e(TAG, "rpc.getSelectedAccountId() failed", e); + } + attachmentManager.onResume(); } @@ -387,7 +391,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity break; case GROUP_EDIT: - dcChat = dcContext.getChat(chatId); + dcChat = DcHelper.getContext(context).getChat(chatId); titleView.setTitle(glideRequests, dcChat); break; @@ -597,6 +601,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity //////// Event Handlers private void handleEphemeralMessages() { + DcContext dcContext = DcHelper.getContext(context); int preselected = dcContext.getChatEphemeralTimer(chatId); EphemeralMessagesDialog.show(this, preselected, duration -> { dcContext.setChatEphemeralTimer(chatId, (int) duration); @@ -622,6 +627,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleMuteNotifications() { + DcContext dcContext = DcHelper.getContext(context); if(!dcChat.isMuted()) { MuteDialog.show(this, duration -> { dcContext.setChatMuteDuration(chatId, duration); @@ -657,7 +663,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity AlertDialog dialog = new AlertDialog.Builder(this) .setMessage(getString(R.string.ask_leave_group)) .setPositiveButton(leaveLabel, (d, which) -> { - dcContext.removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF); + DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF); Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show(); }) .setNegativeButton(R.string.cancel, null) @@ -666,6 +672,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleArchiveChat() { + DcContext dcContext = DcHelper.getContext(context); int newVisibility = isArchived() ? DcChat.DC_CHAT_VISIBILITY_NORMAL : DcChat.DC_CHAT_VISIBILITY_ARCHIVED; dcContext.setChatVisibility(chatId, newVisibility); @@ -681,7 +688,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity AlertDialog dialog = new AlertDialog.Builder(this) .setMessage(getResources().getString(R.string.ask_delete_named_chat, dcChat.getName())) .setPositiveButton(R.string.delete, (d, which) -> { - dcContext.deleteChat(chatId); + DcHelper.getContext(context).deleteChat(chatId); DirectShareUtil.clearShortcut(this, chatId); finish(); }) @@ -711,6 +718,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleForwarding() { + DcContext dcContext = DcHelper.getContext(context); DcChat dcChat = dcContext.getChat(chatId); if (dcChat.isSelfTalk()) { SendRelayedMessageUtil.immediatelyRelay(this, chatId); @@ -756,9 +764,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (sharedContactId != 0) { addAttachmentContactInfo(sharedContactId); } else if (uriList.isEmpty()) { - dcContext.setDraft(chatId, SendRelayedMessageUtil.createMessage(this, null, getSharedText(this))); + DcHelper.getContext(context).setDraft(chatId, SendRelayedMessageUtil.createMessage(this, null, getSharedText(this))); } else { - dcContext.setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uriList.get(0), getSharedText(this))); + DcHelper.getContext(context).setDraft(chatId, SendRelayedMessageUtil.createMessage(this, uriList.get(0), getSharedText(this))); } initializeDraft(); } @@ -773,7 +781,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private ListenableFuture initializeDraft() { isEditing = false; final SettableFuture future = new SettableFuture<>(); - DcMsg draft = dcContext.getDraft(chatId); + DcMsg draft = DcHelper.getContext(context).getDraft(chatId); final String sharedText = ShareUtil.getSharedText(this); if (!draft.isOk()) { @@ -929,7 +937,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void initializeBackground() { - String backgroundImagePath = Prefs.getBackgroundImagePath(this, dcContext.getAccountId()); + int accId; + try { + accId = rpc.getSelectedAccountId(); + } catch (RpcException e) { + Log.e(TAG, "rpc.getSelectedAccountId() failed", e); + return; + } + + String backgroundImagePath = Prefs.getBackgroundImagePath(this, accId); Drawable background; if(!backgroundImagePath.isEmpty()) { background = Drawable.createFromPath(backgroundImagePath); @@ -958,11 +974,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void initializeResources() { + DcContext dcContext = DcHelper.getContext(context); int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId()); if (accountId != dcContext.getAccountId()) { switchedProfile = true; AccountManager.getInstance().switchAccount(context, accountId); - fragment.dcContext = dcContext = context.getDcContext(); + fragment.dcContext = context.getDcContext(); initializeBackground(); } chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, -1); @@ -1033,11 +1050,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } try { - byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), Collections.singletonList(contactId)).getBytes(); + byte[] vcard = rpc.makeVcard(rpc.getSelectedAccountId(), Collections.singletonList(contactId)).getBytes(); String mimeType = "application/octet-stream"; setMedia(PersistentBlobProvider.getInstance().create(this, vcard, mimeType, "vcard.vcf"), MediaType.DOCUMENT); } catch (RpcException e) { - Log.e(TAG, "makeVcard() failed", e); + Log.e(TAG, "RPC failed", e); } } @@ -1074,6 +1091,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inputPanel.clearQuote(); } + DcContext dcContext = DcHelper.getContext(context); Util.runOnAnyBackgroundThread(() -> { DcMsg msg = null; int recompress = 0; @@ -1202,7 +1220,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (refreshFragment) { fragment.reload(recipient, chatId); - DcHelper.getNotificationCenter(this).updateVisibleChat(dcContext.getAccountId(), chatId); + try { + int accId = rpc.getSelectedAccountId(); + DcHelper.getNotificationCenter(this).updateVisibleChat(accId, chatId); + } catch (RpcException e) { + Log.e(TAG, "rpc.getSelectedAccountId() failed", e); + } } fragment.scrollToBottom(); @@ -1364,6 +1387,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Optional quote = inputPanel.getQuote(); inputPanel.clearQuote(); + DcContext dcContext = DcHelper.getContext(context); DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_STICKER); if (quote.isPresent()) { msg.setQuote(quote.get().getQuotedMsg()); @@ -1484,7 +1508,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (isEditing) composeText.setText(""); isEditing = false; // If you modify these lines you may also want to modify ConversationItem.setQuote(): - Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId())); + Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId())); SlideDeck slideDeck = new SlideDeck(); if (msg.hasFile()) { @@ -1507,7 +1531,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void handleEditMessage(DcMsg msg) { isEditing = true; - Recipient author = new Recipient(this, dcContext.getContact(msg.getFromId())); + Recipient author = new Recipient(this, DcHelper.getContext(context).getContact(msg.getFromId())); SlideDeck slideDeck = new SlideDeck(); String text = msg.getSummarytext(500); @@ -1532,6 +1556,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void handleEvent(@NonNull DcEvent event) { + DcContext dcContext = DcHelper.getContext(context); + if (event.getAccountId() != dcContext.getAccountId()) return; + int eventId = event.getId(); if ((eventId == DcContext.DC_EVENT_CHAT_MODIFIED && event.getData1Int() == chatId) || (eventId == DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED && event.getData1Int() == chatId) @@ -1618,7 +1645,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } String normQuery = query.trim(); - searchResult = dcContext.searchMsgs(chatId, normQuery); + searchResult = DcHelper.getContext(context).searchMsgs(chatId, normQuery); if(searchResult.length>0) { searchResultPosition = searchResult.length - 1; @@ -1649,7 +1676,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity messageRequestBottomView.setVisibility(View.VISIBLE); messageRequestBottomView.setAcceptOnClickListener(v -> { - dcContext.acceptChat(chatId); + DcHelper.getContext(context).acceptChat(chatId); messageRequestBottomView.setVisibility(View.GONE); composePanel.setVisibility(View.VISIBLE); }); @@ -1667,7 +1694,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity // avoid showing compose panel on receiving DC_EVENT_CONTACTS_CHANGED for the chat that is no longer a request after blocking DcHelper.getEventCenter(this).removeObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this); - dcContext.blockChat(chatId); + DcHelper.getContext(context).blockChat(chatId); Bundle extras = new Bundle(); extras.putInt(ConversationListFragment.RELOAD_LIST, 1); handleReturnToConversationList(extras); From 98d54bbe6ab82974e74a9ba151e9f93b8964d38a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Wed, 21 Jan 2026 00:20:02 +0100 Subject: [PATCH 04/21] prevent returning to old chat after switching account --- .../securesms/ConversationListActivity.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index 92bdbf910..912e55ec1 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -16,8 +16,6 @@ */ package org.thoughtcrime.securesms; -import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA; -import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_ENABLED; import static org.thoughtcrime.securesms.connect.DcHelper.CONFIG_PROXY_URL; import static org.thoughtcrime.securesms.util.ShareUtil.acquireRelayMessageContent; @@ -112,6 +110,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private String qrData = null; /** used to store temporarily profile ID to delete after authorization is granted via ScreenLockUtil */ private int deleteProfileId = 0; + private boolean switchedProfile = false; @Override protected void onPreCreate() { @@ -254,6 +253,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit return; } super.onNewIntent(intent); + switchedProfile = false; setIntent(intent); refresh(); conversationListFragment.onNewIntent(); @@ -523,15 +523,16 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit searchToolbar.clearFocus(); final DcContext dcContext = DcHelper.getContext(this); - if (isForwarding(this) && dcContext.getChat(chatId).isSelfTalk()) { + if (isForwarding(this) && dcContext.getChat(chatId).isSelfTalk() && !switchedProfile) { SendRelayedMessageUtil.immediatelyRelay(this, chatId); Toast.makeText(this, DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved), Toast.LENGTH_SHORT).show(); handleResetRelaying(); finish(); } else { Intent intent = new Intent(this, ConversationActivity.class); - intent.putExtra(CHAT_ID_EXTRA, chatId); - intent.putExtra(STARTING_POSITION_EXTRA, startingPosition); + intent.putExtra(ConversationActivity.ACCOUNT_ID_EXTRA, dcContext.getAccountId()); + intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, chatId); + intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition); if (isRelayingMessageContent(this)) { acquireRelayMessageContent(this, intent); } @@ -555,8 +556,13 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit public void onBackPressed() { if (searchToolbar.isVisible()) searchToolbar.collapse(); else if (isRelayingMessageContent(this)) { - handleResetRelaying(); - finish(); + if (switchedProfile) { + finishAffinity(); + startActivity(new Intent(this, ConversationListActivity.class)); + } else { + handleResetRelaying(); + finish(); + } } else super.onBackPressed(); } @@ -577,6 +583,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } public void onProfileSwitched(int profileId) { + switchedProfile = true; refreshAvatar(); refreshUnreadIndicator(); refreshTitle(); From 072242642513f4d997e4c72700c22053dd00b86a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Wed, 21 Jan 2026 00:22:43 +0100 Subject: [PATCH 05/21] allow to forward messages to different account --- .../securesms/ConversationFragment.java | 2 +- .../util/SendRelayedMessageUtil.java | 23 ++++++++++++++++++- .../securesms/util/ShareUtil.java | 22 ++++++++++++------ .../org/thoughtcrime/securesms/util/Util.java | 10 ++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java index 3929de365..44f869e28 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java @@ -457,7 +457,7 @@ public class ConversationFragment extends MessageSelectorFragment private void handleForwardMessage(final Set messageRecords) { Intent composeIntent = new Intent(); int[] msgIds = DcMsg.msgSetToIds(messageRecords); - setForwardingMessageIds(composeIntent, msgIds); + setForwardingMessageIds(composeIntent, msgIds, DcHelper.getContext(getContext()).getAccountId()); ConversationListRelayingActivity.start(this, composeIntent); getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); } diff --git a/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java b/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java index 3a1531a30..bacd5e303 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.util; +import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageAccountId; import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageIDs; import static org.thoughtcrime.securesms.util.ShareUtil.getSharedText; import static org.thoughtcrime.securesms.util.ShareUtil.getSharedUris; @@ -26,6 +27,11 @@ import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; public class SendRelayedMessageUtil { @@ -36,12 +42,27 @@ public class SendRelayedMessageUtil { public static void immediatelyRelay(Activity activity, final Long[] chatIds) { ConversationListRelayingActivity.finishActivity(); if (isForwarding(activity)) { + int forwardedMsgAccId = getForwardedMessageAccountId(activity); int[] forwardedMessageIDs = getForwardedMessageIDs(activity); resetRelayingMessageContent(activity); - if (forwardedMessageIDs == null) return; + if (forwardedMessageIDs == null || forwardedMsgAccId <= 0) return; Util.runOnAnyBackgroundThread(() -> { DcContext dcContext = DcHelper.getContext(activity); + if (forwardedMsgAccId != dcContext.getAccountId()) { + Rpc rpc = DcHelper.getRpc(activity); + int accId = dcContext.getAccountId(); + List list = Util.toList(forwardedMessageIDs); + for (long longChatId : chatIds) { + try { + rpc.forwardMessagesToAccount(forwardedMsgAccId, list, accId, (int)longChatId); + } catch (RpcException e) { + e.printStackTrace(); + } + } + return; + } + for (long longChatId : chatIds) { int chatId = (int) longChatId; if (dcContext.getChat(chatId).isSelfTalk()) { diff --git a/src/main/java/org/thoughtcrime/securesms/util/ShareUtil.java b/src/main/java/org/thoughtcrime/securesms/util/ShareUtil.java index 628b80799..0652cd623 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/ShareUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/ShareUtil.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import java.util.ArrayList; public class ShareUtil { + private static final String FORWARDED_MESSAGE_ACCID = "forwarded_message_accid"; private static final String FORWARDED_MESSAGE_IDS = "forwarded_message_ids"; private static final String SHARED_URIS = "shared_uris"; private static final String SHARED_CONTACT_ID = "shared_contact_id"; @@ -24,11 +25,7 @@ public class ShareUtil { } public static boolean isForwarding(Activity activity) { - try { - return activity.getIntent().getIntArrayExtra(FORWARDED_MESSAGE_IDS) != null; - } catch (NullPointerException npe) { - return false; - } + return getForwardedMessageAccountId(activity) > 0; } public static boolean isSharing(Activity activity) { @@ -63,6 +60,14 @@ public class ShareUtil { } } + public static int getForwardedMessageAccountId(Activity activity) { + try { + return activity.getIntent().getIntExtra(FORWARDED_MESSAGE_ACCID, -1); + } catch (NullPointerException npe) { + return -1; + } + } + static int[] getForwardedMessageIDs(Activity activity) { try { return activity.getIntent().getIntArrayExtra(FORWARDED_MESSAGE_IDS); @@ -111,6 +116,7 @@ public class ShareUtil { public static void resetRelayingMessageContent(Activity activity) { try { + activity.getIntent().removeExtra(FORWARDED_MESSAGE_ACCID); activity.getIntent().removeExtra(FORWARDED_MESSAGE_IDS); activity.getIntent().removeExtra(SHARED_URIS); activity.getIntent().removeExtra(SHARED_CONTACT_ID); @@ -124,7 +130,8 @@ public class ShareUtil { public static void acquireRelayMessageContent(Activity currentActivity, @NonNull Intent newActivityIntent) { if (isForwarding(currentActivity)) { - newActivityIntent.putExtra(FORWARDED_MESSAGE_IDS, getForwardedMessageIDs(currentActivity)); + int accId = getForwardedMessageAccountId(currentActivity); + setForwardingMessageIds(newActivityIntent, getForwardedMessageIDs(currentActivity), accId); } else if (isSharing(currentActivity)) { newActivityIntent.putExtra(IS_SHARING, true); if (isDirectSharing(currentActivity)) { @@ -142,7 +149,8 @@ public class ShareUtil { } } - public static void setForwardingMessageIds(Intent composeIntent, int[] messageIds) { + public static void setForwardingMessageIds(Intent composeIntent, int[] messageIds, int accId) { + composeIntent.putExtra(FORWARDED_MESSAGE_ACCID, accId); composeIntent.putExtra(FORWARDED_MESSAGE_IDS, messageIds); } diff --git a/src/main/java/org/thoughtcrime/securesms/util/Util.java b/src/main/java/org/thoughtcrime/securesms/util/Util.java index fd3a0aadd..5cbd24048 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -53,7 +53,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DecimalFormat; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.CountDownLatch; @@ -111,6 +113,14 @@ public class Util { } } + public static List toList(int[] array) { + ArrayList list = new ArrayList<>(array.length); + for (int i : array) { + list.add(i); + } + return list; + } + public static @NonNull int[] appendInt(@Nullable int[] cur, int val) { if (cur == null) { return new int[] { val }; From 8d1b010d1ba927018e78316a7834ee2c8d90df02 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Wed, 21 Jan 2026 20:21:10 +0100 Subject: [PATCH 06/21] tweak SendRelayedMessageUtil --- .../thoughtcrime/securesms/util/SendRelayedMessageUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java b/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java index bacd5e303..95ca4be73 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/SendRelayedMessageUtil.java @@ -49,9 +49,9 @@ public class SendRelayedMessageUtil { Util.runOnAnyBackgroundThread(() -> { DcContext dcContext = DcHelper.getContext(activity); - if (forwardedMsgAccId != dcContext.getAccountId()) { + int accId = dcContext.getAccountId(); + if (forwardedMsgAccId != accId) { Rpc rpc = DcHelper.getRpc(activity); - int accId = dcContext.getAccountId(); List list = Util.toList(forwardedMessageIDs); for (long longChatId : chatIds) { try { From 9ed355efd1f01fdfc74b1180cafd7632d5b34bfe Mon Sep 17 00:00:00 2001 From: adbenitez Date: Wed, 21 Jan 2026 20:59:39 +0100 Subject: [PATCH 07/21] don't save dcContext in MessageSelectorFragment and derivates --- .../securesms/AllMediaDocumentsFragment.java | 1 - .../securesms/AllMediaGalleryFragment.java | 1 - .../securesms/ConversationActivity.java | 1 - .../securesms/ConversationFragment.java | 20 ++++++++++--------- .../securesms/MessageSelectorFragment.java | 7 ++++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/AllMediaDocumentsFragment.java b/src/main/java/org/thoughtcrime/securesms/AllMediaDocumentsFragment.java index 62407dcd0..afe3a653e 100644 --- a/src/main/java/org/thoughtcrime/securesms/AllMediaDocumentsFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/AllMediaDocumentsFragment.java @@ -54,7 +54,6 @@ public class AllMediaDocumentsFragment public void onCreate(Bundle bundle) { super.onCreate(bundle); - dcContext = DcHelper.getContext(getContext()); chatId = getArguments().getInt(CHAT_ID_EXTRA, -1); viewtype1 = getArguments().getInt(VIEWTYPE1, 0); viewtype2 = getArguments().getInt(VIEWTYPE2, 0); diff --git a/src/main/java/org/thoughtcrime/securesms/AllMediaGalleryFragment.java b/src/main/java/org/thoughtcrime/securesms/AllMediaGalleryFragment.java index 48f8b4635..57103cf02 100644 --- a/src/main/java/org/thoughtcrime/securesms/AllMediaGalleryFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/AllMediaGalleryFragment.java @@ -52,7 +52,6 @@ public class AllMediaGalleryFragment public void onCreate(Bundle bundle) { super.onCreate(bundle); - dcContext = DcHelper.getContext(getContext()); chatId = getArguments().getInt(CHAT_ID_EXTRA, -1); getLoaderManager().initLoader(0, null, this); diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index 346ec5a59..e67d6fa93 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -982,7 +982,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (accountId != dcContext.getAccountId()) { switchedProfile = true; AccountManager.getInstance().switchAccount(context, accountId); - fragment.dcContext = context.getDcContext(); initializeBackground(); } chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, -1); diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java index 44f869e28..3b0a50816 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java @@ -106,7 +106,6 @@ public class ConversationFragment extends MessageSelectorFragment @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - this.dcContext = DcHelper.getContext(getContext()); DcEventCenter eventCenter = DcHelper.getEventCenter(getContext()); eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this); @@ -208,7 +207,7 @@ public class ConversationFragment extends MessageSelectorFragment public void onResume() { super.onResume(); - Util.runOnBackground(() -> dcContext.marknoticedChat((int) chatId)); + Util.runOnBackground(() -> DcHelper.getContext(getContext()).marknoticedChat((int) chatId)); if (list.getAdapter() != null) { list.getAdapter().notifyDataSetChanged(); } @@ -287,7 +286,7 @@ public class ConversationFragment extends MessageSelectorFragment dateDecoration = new StickyHeaderDecoration(adapter, false, false); list.addItemDecoration(dateDecoration); - int freshMsgs = dcContext.getFreshMsgCount((int) chatId); + int freshMsgs = DcHelper.getContext(getContext()).getFreshMsgCount((int) chatId); SetStartingPositionLinearLayoutManager layoutManager = (SetStartingPositionLinearLayoutManager) list.getLayoutManager(); if (startingPosition > -1) { layoutManager.setStartingPosition(startingPosition); @@ -429,6 +428,7 @@ public class ConversationFragment extends MessageSelectorFragment StringBuilder result = new StringBuilder(); + DcContext dcContext = DcHelper.getContext(getContext()); DcMsg prevMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); for (DcMsg msg : dcMsgsList) { if (result.length() > 0) { @@ -485,6 +485,7 @@ public class ConversationFragment extends MessageSelectorFragment private void handleReplyMessagePrivately(final DcMsg msg) { if (getActivity() != null) { + DcContext dcContext = DcHelper.getContext(getContext()); int privateChatId = dcContext.createChatByContactId(msg.getFromId()); DcMsg replyMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); replyMsg.setQuote(msg); @@ -501,6 +502,7 @@ public class ConversationFragment extends MessageSelectorFragment private void handleToggleSave(final Set messageRecords) { DcMsg msg = getSelectedMessageRecord(messageRecords); + DcContext dcContext = DcHelper.getContext(getContext()); if (msg.getSavedMsgId() != 0) { dcContext.deleteMsgs(new int[]{msg.getSavedMsgId()}); } else { @@ -574,7 +576,7 @@ public class ConversationFragment extends MessageSelectorFragment } private void updateLocationButton() { - floatingLocationButton.setVisibility(dcContext.isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE); + floatingLocationButton.setVisibility(DcHelper.getContext(getContext()).isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE); } private void scrollAndHighlight(final int pos, boolean smooth) { @@ -719,7 +721,7 @@ public class ConversationFragment extends MessageSelectorFragment index++; } } - Util.runOnAnyBackgroundThread(() -> dcContext.markseenMsgs(ids)); + Util.runOnAnyBackgroundThread(() -> DcHelper.getContext(getContext()).markseenMsgs(ids)); } private class ConversationFragmentItemClickListener implements ItemClickListener { @@ -761,7 +763,7 @@ public class ConversationFragment extends MessageSelectorFragment startActivity(intent); } else { - String self_mail = dcContext.getConfig("configured_mail_user"); + String self_mail = DcHelper.getContext(getContext()).getConfig("configured_mail_user"); if (self_mail != null && !self_mail.isEmpty() && messageRecord.getText().contains(self_mail) && getListAdapter().getChat().isDeviceTalk()) { @@ -800,7 +802,7 @@ public class ConversationFragment extends MessageSelectorFragment if (foreignChatId != 0 && foreignChatId != chatId) { Intent intent = new Intent(getActivity(), ConversationActivity.class); intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, foreignChatId); - int start = DcMsg.getMessagePosition(original, dcContext); + int start = DcMsg.getMessagePosition(original, DcHelper.getContext(getContext())); intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, start); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); ((ConversationActivity) getActivity()).hideSoftKeyboard(); @@ -816,7 +818,7 @@ public class ConversationFragment extends MessageSelectorFragment @Override public void onJumpToOriginalClicked(DcMsg messageRecord) { - jumpToOriginal(dcContext.getMsg(messageRecord.getOriginalMsgId())); + jumpToOriginal(DcHelper.getContext(getContext()).getMsg(messageRecord.getOriginalMsgId())); } @Override @@ -835,7 +837,7 @@ public class ConversationFragment extends MessageSelectorFragment @Override public void onDownloadClicked(DcMsg messageRecord) { - dcContext.downloadFullMsg(messageRecord.getId()); + DcHelper.getContext(getContext()).downloadFullMsg(messageRecord.getId()); } @Override diff --git a/src/main/java/org/thoughtcrime/securesms/MessageSelectorFragment.java b/src/main/java/org/thoughtcrime/securesms/MessageSelectorFragment.java index 9f86ecf76..8320b05e0 100644 --- a/src/main/java/org/thoughtcrime/securesms/MessageSelectorFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/MessageSelectorFragment.java @@ -32,7 +32,6 @@ public abstract class MessageSelectorFragment implements DcEventCenter.DcEventDelegate { protected ActionMode actionMode; - protected DcContext dcContext; protected abstract void setCorrectMenuVisibility(Menu menu); @@ -48,7 +47,7 @@ public abstract class MessageSelectorFragment protected void handleDisplayDetails(DcMsg dcMsg) { View view = View.inflate(getActivity(), R.layout.message_details_view, null); TextView detailsText = view.findViewById(R.id.details_text); - detailsText.setText(dcContext.getMsgInfo(dcMsg.getId())); + detailsText.setText(DcHelper.getContext(getContext()).getMsgInfo(dcMsg.getId())); AlertDialog d = new AlertDialog.Builder(getActivity()) .setView(view) @@ -62,6 +61,7 @@ public abstract class MessageSelectorFragment } protected void handleDeleteMessages(int chatId, final int[] messageIds) { + DcContext dcContext = DcHelper.getContext(getContext()); DcChat dcChat = dcContext.getChat(chatId); boolean canDeleteForAll = true; if (dcChat.isEncrypted() && dcChat.canSend() && !dcChat.isSelfTalk()) { @@ -135,7 +135,7 @@ public abstract class MessageSelectorFragment protected void handleShowInChat(final DcMsg dcMsg) { Intent intent = new Intent(getContext(), ConversationActivity.class); intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcMsg.getChatId()); - intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, dcContext)); + intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcMsg, DcHelper.getContext(getContext()))); startActivity(intent); } @@ -145,6 +145,7 @@ public abstract class MessageSelectorFragment protected void handleResendMessage(final Set dcMsgsSet) { int[] ids = DcMsg.msgSetToIds(dcMsgsSet); + DcContext dcContext = DcHelper.getContext(getContext()); Util.runOnAnyBackgroundThread(() -> { boolean success = dcContext.resendMsgs(ids); Util.runOnMain(() -> { From eacd089c8f413a28fd52f615eb78d15c04c42e0d Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 22 Jan 2026 17:18:49 +0100 Subject: [PATCH 08/21] improve code in ConversationListActivity --- .../securesms/ConversationListActivity.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index d1e01a143..bae3b8699 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -23,10 +23,12 @@ import static org.thoughtcrime.securesms.util.ShareUtil.getDirectSharingChatId; import static org.thoughtcrime.securesms.util.ShareUtil.getSharedTitle; import static org.thoughtcrime.securesms.util.ShareUtil.isDirectSharing; import static org.thoughtcrime.securesms.util.ShareUtil.isForwarding; +import static org.thoughtcrime.securesms.util.ShareUtil.getForwardedMessageAccountId; import static org.thoughtcrime.securesms.util.ShareUtil.isRelayingMessageContent; import static org.thoughtcrime.securesms.util.ShareUtil.resetRelayingMessageContent; import android.Manifest; +import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.net.Uri; @@ -111,7 +113,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private String qrData = null; /** used to store temporarily profile ID to delete after authorization is granted via ScreenLockUtil */ private int deleteProfileId = 0; - private boolean switchedProfile = false; @Override protected void onPreCreate() { @@ -180,10 +181,16 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit if (searchToolbar.isVisible()) { searchToolbar.collapse(); } else { - if (isRelayingMessageContent(ConversationListActivity.this)) { - if (switchedProfile) { + Activity activity = ConversationListActivity.this; + if (isRelayingMessageContent(activity)) { + int selectedAccId = DcHelper.getContext(activity).getAccountId(); + int initialAccId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId); + if (initialAccId != selectedAccId) { + // allowing to go back is dangerous, it could be activity on previously selected account, + // instead of figuring out account rollback in onResume in each activity (conversation, gallery, media preview, webxdc, etc.) + // just clear the back stack and stay in newly selected account finishAffinity(); - startActivity(new Intent(ConversationListActivity.this, ConversationListActivity.class)); + startActivity(new Intent(activity, ConversationListActivity.class)); return; } else { handleResetRelaying(); @@ -276,7 +283,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit return; } super.onNewIntent(intent); - switchedProfile = false; setIntent(intent); refresh(); conversationListFragment.onNewIntent(); @@ -546,7 +552,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit searchToolbar.clearFocus(); final DcContext dcContext = DcHelper.getContext(this); - if (isForwarding(this) && dcContext.getChat(chatId).isSelfTalk() && !switchedProfile) { + int fwdAccId = getForwardedMessageAccountId(this); + if (fwdAccId == dcContext.getAccountId() && dcContext.getChat(chatId).isSelfTalk()) { SendRelayedMessageUtil.immediatelyRelay(this, chatId); Toast.makeText(this, DynamicTheme.getCheckmarkEmoji(this) + " " + getString(R.string.saved), Toast.LENGTH_SHORT).show(); handleResetRelaying(); @@ -592,7 +599,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } public void onProfileSwitched(int profileId) { - switchedProfile = true; refreshAvatar(); refreshUnreadIndicator(); refreshTitle(); From 2a4bd2a001c480d286a16be17a0baf9190e23201 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 22 Jan 2026 17:31:32 +0100 Subject: [PATCH 09/21] add device messages on account switching --- .../securesms/ConversationListActivity.java | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index bae3b8699..c827004dd 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -122,37 +122,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit @Override protected void onCreate(Bundle icicle, boolean ready) { - // update messages - for new messages, do not reuse or modify strings but create new ones. - // it is not needed to keep all past update messages, however, when deleted, also the strings should be deleted. - try { - DcContext dcContext = DcHelper.getContext(this); - final String deviceMsgLabel = "update_2_0_0_android-h"; - if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) { - DcMsg msg = null; - if (!getIntent().getBooleanExtra(FROM_WELCOME, false)) { - msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); - - // InputStream inputStream = getResources().getAssets().open("device-messages/green-checkmark.jpg"); - // String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg"); - // Util.copy(inputStream, new FileOutputStream(outputFile)); - // msg.setFile(outputFile, "image/jpeg"); - - msg.setText(getString(R.string.update_2_0, "https://delta.chat/donate")); - } - dcContext.addDeviceMsg(deviceMsgLabel, msg); - - if (Prefs.getStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, "").equals(deviceMsgLabel)) { - int deviceChatId = dcContext.getChatIdByContactId(DcContact.DC_CONTACT_ID_DEVICE); - if (deviceChatId != 0) { - dcContext.marknoticedChat(deviceChatId); - } - } - Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel); - } - - } catch(Exception e) { - e.printStackTrace(); - } + addDeviceMessages(getIntent().getBooleanExtra(FROM_WELCOME, false)); // create view setContentView(R.layout.conversation_list_activity); @@ -598,7 +568,42 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title))); } + private void addDeviceMessages(boolean fromWelcome) { + // update messages - for new messages, do not reuse or modify strings but create new ones. + // it is not needed to keep all past update messages, however, when deleted, also the strings should be deleted. + try { + DcContext dcContext = DcHelper.getContext(this); + final String deviceMsgLabel = "update_2_0_0_android-h"; + if (!dcContext.wasDeviceMsgEverAdded(deviceMsgLabel)) { + DcMsg msg = null; + if (!fromWelcome) { + msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); + + // InputStream inputStream = getResources().getAssets().open("device-messages/green-checkmark.jpg"); + // String outputFile = DcHelper.getBlobdirFile(dcContext, "green-checkmark", ".jpg"); + // Util.copy(inputStream, new FileOutputStream(outputFile)); + // msg.setFile(outputFile, "image/jpeg"); + + msg.setText(getString(R.string.update_2_0, "https://delta.chat/donate")); + } + dcContext.addDeviceMsg(deviceMsgLabel, msg); + + if (Prefs.getStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, "").equals(deviceMsgLabel)) { + int deviceChatId = dcContext.getChatIdByContactId(DcContact.DC_CONTACT_ID_DEVICE); + if (deviceChatId != 0) { + dcContext.marknoticedChat(deviceChatId); + } + } + Prefs.setStringPreference(this, Prefs.LAST_DEVICE_MSG_LABEL, deviceMsgLabel); + } + + } catch(Exception e) { + e.printStackTrace(); + } + } + public void onProfileSwitched(int profileId) { + addDeviceMessages(false); refreshAvatar(); refreshUnreadIndicator(); refreshTitle(); From fcd16d9e325ba373a61da5efa12c36a41108e830 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 22 Jan 2026 17:50:16 +0100 Subject: [PATCH 10/21] don't allow to delete or create new profile while sharing/forwarding --- .../securesms/ConversationListActivity.java | 8 +++--- .../AccountSelectionListFragment.java | 25 ++++++++++++------- .../securesms/connect/AccountManager.java | 4 +-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index c827004dd..f9a866529 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -176,10 +176,10 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit TooltipCompat.setTooltipText(searchAction, getText(R.string.search_explain)); TooltipCompat.setTooltipText(selfAvatar, getText(R.string.switch_account)); - selfAvatar.setOnClickListener(v -> AccountManager.getInstance().showSwitchAccountMenu(this)); + selfAvatar.setOnClickListener(v -> AccountManager.getInstance().showSwitchAccountMenu(this, false)); findViewById(R.id.avatar_and_title).setOnClickListener(v -> { if (!isRelayingMessageContent(this)) { - AccountManager.getInstance().showSwitchAccountMenu(this); + AccountManager.getInstance().showSwitchAccountMenu(this, false); } }); @@ -453,7 +453,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit handleSaveAttachment(); return true; } else if (itemId == R.id.menu_switch_account) { - AccountManager.getInstance().showSwitchAccountMenu(this); + AccountManager.getInstance().showSwitchAccountMenu(this, true); return true; } @@ -627,7 +627,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit DcContext selAcc = accounts.getSelectedAccount(); AccountManager.getInstance().switchAccountAndStartActivity(this, selAcc.isOk()? selAcc.getAccountId() : 0); } else { - AccountManager.getInstance().showSwitchAccountMenu(this); + AccountManager.getInstance().showSwitchAccountMenu(this, false); } // title update needed to show "Delta Chat" in case there is only one profile left diff --git a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java index 1cfd01c4a..924b217d0 100644 --- a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java @@ -50,10 +50,12 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv private final ConversationListActivity activity; private RecyclerView recyclerView; private AccountSelectionListAdapter adapter; + private final boolean selectOnly; - public AccountSelectionListFragment(ConversationListActivity activity) { + public AccountSelectionListFragment(ConversationListActivity activity, boolean selectOnly) { super(); this.activity = activity; + this.selectOnly = selectOnly; } @NonNull @@ -61,10 +63,12 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()) .setTitle(R.string.switch_account) - .setNeutralButton(R.string.connectivity, ((dialog, which) -> { - startActivity(new Intent(getActivity(), ConnectivityActivity.class)); - })) .setNegativeButton(R.string.cancel, null); + if (!selectOnly) { + builder.setNeutralButton(R.string.connectivity, ((dialog, which) -> { + startActivity(new Intent(getActivity(), ConnectivityActivity.class)); + })); + } LayoutInflater inflater = requireActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.account_selection_list_fragment, null); @@ -99,18 +103,21 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv DcAccounts accounts = DcHelper.getAccounts(getActivity()); int[] accountIds = accounts.getAll(); - int[] ids = new int[accountIds.length + 1]; + int count = (selectOnly? 0 : 1) + accountIds.length; + int[] ids = new int[count]; int j = 0; for (int accountId : accountIds) { ids[j++] = accountId; } - ids[j] = DC_CONTACT_ID_ADD_ACCOUNT; + if (!selectOnly) ids[j] = DC_CONTACT_ID_ADD_ACCOUNT; adapter.changeData(ids, accounts.getSelectedAccount().getAccountId()); } @Override public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); + if (selectOnly) return; + requireActivity().getMenuInflater().inflate(R.menu.account_item_context, menu); AccountSelectionListItem listItem = (AccountSelectionListItem) v; @@ -188,9 +195,9 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv .setPositiveButton(android.R.string.ok, (d, b) -> { String newTag = inputField.getText().toString().trim(); dcContext.setConfig(CONFIG_PRIVATE_TAG, newTag); - AccountManager.getInstance().showSwitchAccountMenu(activity); + AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly); }) - .setNegativeButton(R.string.cancel, (d, b) -> AccountManager.getInstance().showSwitchAccountMenu(activity)) + .setNegativeButton(R.string.cancel, (d, b) -> AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly)) .show(); } @@ -230,7 +237,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv AlertDialog dialog = new AlertDialog.Builder(activity) .setTitle(R.string.delete_account) .setView(dialogView) - .setNegativeButton(R.string.cancel, (d, which) -> AccountManager.getInstance().showSwitchAccountMenu(activity)) + .setNegativeButton(R.string.cancel, (d, which) -> AccountManager.getInstance().showSwitchAccountMenu(activity, selectOnly)) .setPositiveButton(R.string.delete, (d2, w2) -> activity.onDeleteProfile(accountId)) .show(); Util.redPositiveButton(dialog); diff --git a/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java b/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java index d005c18dc..2bdebe7fe 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java @@ -137,8 +137,8 @@ public class AccountManager { // ui - public void showSwitchAccountMenu(ConversationListActivity activity) { - AccountSelectionListFragment dialog = new AccountSelectionListFragment(activity); + public void showSwitchAccountMenu(ConversationListActivity activity, boolean selectOnly) { + AccountSelectionListFragment dialog = new AccountSelectionListFragment(activity, selectOnly); dialog.show(((FragmentActivity) activity).getSupportFragmentManager(), null); } From 9e1b9775e2a6969be9cc75bf5b6871a5fdbd8b2c Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 22 Jan 2026 18:43:54 +0100 Subject: [PATCH 11/21] fix ConversationListActivity --- .../securesms/ConversationListActivity.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index f9a866529..bec580e89 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -123,6 +123,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit @Override protected void onCreate(Bundle icicle, boolean ready) { addDeviceMessages(getIntent().getBooleanExtra(FROM_WELCOME, false)); + if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) { + getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId()); + } // create view setContentView(R.layout.conversation_list_activity); @@ -254,6 +257,9 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } super.onNewIntent(intent); setIntent(intent); + if (getIntent().getIntExtra(ACCOUNT_ID_EXTRA, -1) <= 0) { + getIntent().putExtra(ACCOUNT_ID_EXTRA, DcHelper.getContext(this).getAccountId()); + } refresh(); conversationListFragment.onNewIntent(); invalidateOptionsMenu(); @@ -266,12 +272,14 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit DcHelper.getNotificationCenter(this).removeAllNotifications(accountId); } if (accountId != dcContext.getAccountId()) { - AccountManager.getInstance().switchAccountAndStartActivity(this, accountId); + AccountManager.getInstance().switchAccount(this, accountId); + onProfileSwitched(accountId); + } else { + refreshAvatar(); + refreshUnreadIndicator(); + refreshTitle(); } - refreshAvatar(); - refreshUnreadIndicator(); - refreshTitle(); handleOpenpgp4fpr(); if (isDirectSharing(this)) { openConversation(getDirectSharingChatId(this), -1); @@ -625,7 +633,12 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit accounts.removeAccount(profileId); if (selected) { DcContext selAcc = accounts.getSelectedAccount(); - AccountManager.getInstance().switchAccountAndStartActivity(this, selAcc.isOk()? selAcc.getAccountId() : 0); + if (selAcc.isOk()) { + AccountManager.getInstance().switchAccount(this, selAcc.getAccountId()); + onProfileSwitched(selAcc.getAccountId()); + } else { + AccountManager.getInstance().switchAccountAndStartActivity(this, 0); + } } else { AccountManager.getInstance().showSwitchAccountMenu(this, false); } From aa032dbb37886acb16ac9dd09caedc136cd2ed81 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Thu, 22 Jan 2026 20:45:33 +0100 Subject: [PATCH 12/21] fix some bugs * avoid using wrong DcContext after switching account in ConversationListActivity * avoid getContext() in ConversationFragment, use Rpc instead --- .../securesms/ConversationActivity.java | 8 +-- .../securesms/ConversationFragment.java | 61 +++++++++++++------ .../securesms/ConversationListActivity.java | 6 +- .../AccountSelectionListFragment.java | 3 +- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index e67d6fa93..ca140a2bd 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -977,9 +977,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void initializeResources() { - DcContext dcContext = DcHelper.getContext(context); - int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId()); - if (accountId != dcContext.getAccountId()) { + int selectedAccId = DcHelper.getContext(context).getAccountId(); + int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId); + if (accountId != selectedAccId) { switchedProfile = true; AccountManager.getInstance().switchAccount(context, accountId); initializeBackground(); @@ -987,7 +987,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity chatId = getIntent().getIntExtra(CHAT_ID_EXTRA, -1); if(chatId == DcChat.DC_CHAT_NO_CHAT) throw new IllegalStateException("can't display a conversation for no chat."); - dcChat = dcContext.getChat(chatId); + dcChat = DcHelper.getContext(context).getChat(chatId); recipient = new Recipient(this, dcChat); glideRequests = GlideApp.with(this); diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java index 3b0a50816..d28040736 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java @@ -67,6 +67,7 @@ import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.views.ConversationAdaptiveActionsToolbar; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -74,6 +75,9 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + @SuppressLint("StaticFieldLeak") public class ConversationFragment extends MessageSelectorFragment { @@ -102,10 +106,12 @@ public class ConversationFragment extends MessageSelectorFragment public boolean isPaused; private Debouncer markseenDebouncer; + private Rpc rpc; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + rpc = DcHelper.getRpc(getContext()); DcEventCenter eventCenter = DcHelper.getEventCenter(getContext()); eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this); @@ -206,8 +212,13 @@ public class ConversationFragment extends MessageSelectorFragment @Override public void onResume() { super.onResume(); - - Util.runOnBackground(() -> DcHelper.getContext(getContext()).marknoticedChat((int) chatId)); + Util.runOnBackground(() -> { + try { + rpc.marknoticedChat(rpc.getSelectedAccountId(), (int) chatId); + } catch (RpcException e) { + Log.e(TAG, "RPC error", e); + } + }); if (list.getAdapter() != null) { list.getAdapter().notifyDataSetChanged(); } @@ -286,7 +297,12 @@ public class ConversationFragment extends MessageSelectorFragment dateDecoration = new StickyHeaderDecoration(adapter, false, false); list.addItemDecoration(dateDecoration); - int freshMsgs = DcHelper.getContext(getContext()).getFreshMsgCount((int) chatId); + int freshMsgs = 0; + try { + freshMsgs = rpc.getFreshMsgCnt(rpc.getSelectedAccountId(), (int) chatId); + } catch (RpcException e) { + Log.e(TAG, "RPC error", e); + } SetStartingPositionLinearLayoutManager layoutManager = (SetStartingPositionLinearLayoutManager) list.getLayoutManager(); if (startingPosition > -1) { layoutManager.setStartingPosition(startingPosition); @@ -457,9 +473,13 @@ public class ConversationFragment extends MessageSelectorFragment private void handleForwardMessage(final Set messageRecords) { Intent composeIntent = new Intent(); int[] msgIds = DcMsg.msgSetToIds(messageRecords); - setForwardingMessageIds(composeIntent, msgIds, DcHelper.getContext(getContext()).getAccountId()); - ConversationListRelayingActivity.start(this, composeIntent); - getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); + try { + setForwardingMessageIds(composeIntent, msgIds, rpc.getSelectedAccountId()); + ConversationListRelayingActivity.start(this, composeIntent); + getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); + } catch (RpcException e) { + Log.e(TAG, "RPC error", e); + } } @SuppressLint("RestrictedApi") @@ -485,7 +505,7 @@ public class ConversationFragment extends MessageSelectorFragment private void handleReplyMessagePrivately(final DcMsg msg) { if (getActivity() != null) { - DcContext dcContext = DcHelper.getContext(getContext()); + DcContext dcContext = DcHelper.getContext(getActivity()); int privateChatId = dcContext.createChatByContactId(msg.getFromId()); DcMsg replyMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); replyMsg.setQuote(msg); @@ -502,11 +522,14 @@ public class ConversationFragment extends MessageSelectorFragment private void handleToggleSave(final Set messageRecords) { DcMsg msg = getSelectedMessageRecord(messageRecords); - DcContext dcContext = DcHelper.getContext(getContext()); - if (msg.getSavedMsgId() != 0) { - dcContext.deleteMsgs(new int[]{msg.getSavedMsgId()}); - } else { - dcContext.saveMsgs(new int[]{msg.getId()}); + try { + if (msg.getSavedMsgId() != 0) { + rpc.deleteMessages(rpc.getSelectedAccountId(), Collections.singletonList(msg.getSavedMsgId())); + } else { + rpc.saveMsgs(rpc.getSelectedAccountId(), Collections.singletonList(msg.getId())); + } + } catch (RpcException e) { + Log.e(TAG, "RPC error", e); } } @@ -712,16 +735,20 @@ public class ConversationFragment extends MessageSelectorFragment return; } - int[] ids = new int[lastPos - firstPos + 1]; - int index = 0; + ArrayList ids = new ArrayList<>(lastPos - firstPos + 1); for(int pos = firstPos; pos <= lastPos; pos++) { DcMsg message = ((ConversationAdapter) list.getAdapter()).getMsg(pos); if (message.getFromId() != DC_CONTACT_ID_SELF) { - ids[index] = message.getId(); - index++; + ids.add(message.getId()); } } - Util.runOnAnyBackgroundThread(() -> DcHelper.getContext(getContext()).markseenMsgs(ids)); + Util.runOnAnyBackgroundThread(() -> { + try { + rpc.markseenMsgs(rpc.getSelectedAccountId(), ids); + } catch (RpcException e) { + Log.e(TAG, "RPC error", e); + } + }); } private class ConversationFragmentItemClickListener implements ItemClickListener { diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java index bec580e89..d1e60710e 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListActivity.java @@ -266,12 +266,12 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } private void refresh() { - DcContext dcContext = DcHelper.getContext(this); - int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, dcContext.getAccountId()); + int selectedAccId = DcHelper.getContext(this).getAccountId(); + int accountId = getIntent().getIntExtra(ACCOUNT_ID_EXTRA, selectedAccId); if (getIntent().getBooleanExtra(CLEAR_NOTIFICATIONS, false)) { DcHelper.getNotificationCenter(this).removeAllNotifications(accountId); } - if (accountId != dcContext.getAccountId()) { + if (accountId != selectedAccId) { AccountManager.getInstance().switchAccount(this, accountId); onProfileSwitched(accountId); } else { diff --git a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java index 924b217d0..7e4e2adff 100644 --- a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java @@ -103,8 +103,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv DcAccounts accounts = DcHelper.getAccounts(getActivity()); int[] accountIds = accounts.getAll(); - int count = (selectOnly? 0 : 1) + accountIds.length; - int[] ids = new int[count]; + int[] ids = new int[(selectOnly? 0 : 1) + accountIds.length]; int j = 0; for (int accountId : accountIds) { ids[j++] = accountId; From db93aeacc0e69580f7a584f3d4561cf6a08a47e8 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 23 Jan 2026 22:05:41 +0100 Subject: [PATCH 13/21] fix event listener in ConversationActivity --- .../securesms/ConversationActivity.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index ca140a2bd..d8ce5e03d 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -245,17 +245,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } }); + setDcEventListener(); + handleRelaying(); + } + + private setDcEventListener() { DcEventCenter eventCenter = DcHelper.getEventCenter(this); - eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this); - eventCenter.addObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this); - eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this); + // first cleanup in case it was already registered for other chat + eventCenter.removeObservers(this); + + eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this); + eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED, this); + eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this); if (!isMultiUser()) { - eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this); - eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this); + eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_INCOMING_MSG, this); + eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSG_READ, this); } - - handleRelaying(); } @Override @@ -281,6 +287,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } }); + setDcEventListener(); // reset event listener handleRelaying(); if (fragment != null) { From a65c677eccab289ad6498795c37306edeb095edb Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 23 Jan 2026 22:10:06 +0100 Subject: [PATCH 14/21] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bcaa3808..9d235e7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Improve switch speed when changing profiles +* Allow to switch profile when sharing or forwarding * Don't notify notification-to-all from in-chat apps if the chat is muted * Allow to see inbox quota for all relays in connectivity screen * Truncate file names in the middle, not at the end; important information are more often at the end From 0d610895d28616cbf16cc86dced7a7b1ec5b3303 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 23 Jan 2026 22:13:59 +0100 Subject: [PATCH 15/21] fix function --- .../java/org/thoughtcrime/securesms/ConversationActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index d8ce5e03d..b11340984 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -249,7 +249,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity handleRelaying(); } - private setDcEventListener() { + private void setDcEventListener() { DcEventCenter eventCenter = DcHelper.getEventCenter(this); // first cleanup in case it was already registered for other chat eventCenter.removeObservers(this); From a99a4ec85b58609c611ef76d5b8ae4a56d609c5b Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 26 Jan 2026 18:23:55 +0100 Subject: [PATCH 16/21] hide delivery checkmark in channels --- .../components/ConversationItemFooter.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index ffc6906ac..47c648ae4 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -15,6 +15,7 @@ import androidx.annotation.Nullable; import com.b44t.messenger.DcMsg; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.DateUtils; public class ConversationItemFooter extends LinearLayout { @@ -27,23 +28,25 @@ public class ConversationItemFooter extends LinearLayout { private DeliveryStatusView deliveryStatusView; private Integer textColor = null; private int callDuration = 0; + private Context context; public ConversationItemFooter(Context context) { super(context); - init(null); + init(context, null); } public ConversationItemFooter(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - init(attrs); + init(context, attrs); } public ConversationItemFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(attrs); + init(context, attrs); } - private void init(@Nullable AttributeSet attrs) { + private void init(Context context, @Nullable AttributeSet attrs) { + this.context = context; inflate(getContext(), R.layout.conversation_item_footer, this); dateView = findViewById(R.id.footer_date); @@ -106,6 +109,10 @@ public class ConversationItemFooter extends LinearLayout { } } + private boolean isOutChannel(@NonNull DcMsg messageRecord) { + return DcHelper.getContext(context).getChat(messageRecord.getChatId()).isOutBroadcast(); + } + private void presentDeliveryStatus(@NonNull DcMsg messageRecord) { // isDownloading is temporary and should be checked first. boolean isDownloading = messageRecord.getDownloadState() == DcMsg.DC_DOWNLOAD_IN_PROGRESS; @@ -114,7 +121,7 @@ public class ConversationItemFooter extends LinearLayout { if (isDownloading) deliveryStatusView.setDownloading(); else if (messageRecord.isPending()) deliveryStatusView.setPending(); else if (messageRecord.isFailed()) deliveryStatusView.setFailed(); - else if (!messageRecord.isOutgoing() || isCall) deliveryStatusView.setNone(); + else if (!messageRecord.isOutgoing() || isCall || isOutChannel(messageRecord)) deliveryStatusView.setNone(); else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead(); else if (messageRecord.isDelivered()) deliveryStatusView.setSent(); else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing(); From 1542fc67ca8816e2cfbaa45549432df509e3d78a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 26 Jan 2026 21:31:41 +0100 Subject: [PATCH 17/21] show number of views a channel message have --- .../components/ConversationItemFooter.java | 39 +++++++++++++++---- src/main/res/drawable/ic_baseline_eye.xml | 9 +++++ .../res/layout/conversation_item_footer.xml | 23 +++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/main/res/drawable/ic_baseline_eye.xml diff --git a/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index 47c648ae4..e8ace5872 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -18,10 +18,15 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.DateUtils; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class ConversationItemFooter extends LinearLayout { private TextView dateView; private TextView editedView; + private TextView viewsLabel; + private ImageView viewsIcon; private ImageView bookmarkIndicatorView; private ImageView emailIndicatorView; private ImageView locationIndicatorView; @@ -29,6 +34,7 @@ public class ConversationItemFooter extends LinearLayout { private Integer textColor = null; private int callDuration = 0; private Context context; + private Rpc rpc; public ConversationItemFooter(Context context) { super(context); @@ -47,10 +53,13 @@ public class ConversationItemFooter extends LinearLayout { private void init(Context context, @Nullable AttributeSet attrs) { this.context = context; + this.rpc = DcHelper.getRpc(context); inflate(getContext(), R.layout.conversation_item_footer, this); dateView = findViewById(R.id.footer_date); editedView = findViewById(R.id.footer_edited); + viewsLabel = findViewById(R.id.footer_views); + viewsIcon = findViewById(R.id.footer_views_icon); bookmarkIndicatorView = findViewById(R.id.footer_bookmark_indicator); emailIndicatorView = findViewById(R.id.footer_email_indicator); locationIndicatorView = findViewById(R.id.footer_location_indicator); @@ -82,13 +91,33 @@ public class ConversationItemFooter extends LinearLayout { } locationIndicatorView.setVisibility(messageRecord.hasLocation() ? View.VISIBLE : View.GONE); - presentDeliveryStatus(messageRecord); + + boolean isOutChannel = DcHelper.getContext(context).getChat(messageRecord.getChatId()).isOutBroadcast(); + + if (isOutChannel && messageRecord.isOutgoing()) { + try { + int accId = rpc.getSelectedAccountId(); + int count = rpc.getMessageReadReceiptCount(accId, messageRecord.getId()); + viewsLabel.setText(String.format("%d", count)); + viewsLabel.setVisibility(View.VISIBLE); + viewsIcon.setVisibility(View.VISIBLE); + } catch (RpcException e) { + e.printStackTrace(); + } + } else { + viewsLabel.setVisibility(View.GONE); + viewsIcon.setVisibility(View.GONE); + } + + presentDeliveryStatus(messageRecord, isOutChannel); } public void setTextColor(int color) { textColor = color; dateView.setTextColor(color); editedView.setTextColor(color); + viewsLabel.setTextColor(color); + viewsIcon.setColorFilter(color); bookmarkIndicatorView.setColorFilter(color); emailIndicatorView.setColorFilter(color); locationIndicatorView.setColorFilter(color); @@ -109,11 +138,7 @@ public class ConversationItemFooter extends LinearLayout { } } - private boolean isOutChannel(@NonNull DcMsg messageRecord) { - return DcHelper.getContext(context).getChat(messageRecord.getChatId()).isOutBroadcast(); - } - - private void presentDeliveryStatus(@NonNull DcMsg messageRecord) { + private void presentDeliveryStatus(@NonNull DcMsg messageRecord, boolean isOutChannel) { // isDownloading is temporary and should be checked first. boolean isDownloading = messageRecord.getDownloadState() == DcMsg.DC_DOWNLOAD_IN_PROGRESS; boolean isCall = messageRecord.getType() == DcMsg.DC_MSG_CALL; @@ -121,7 +146,7 @@ public class ConversationItemFooter extends LinearLayout { if (isDownloading) deliveryStatusView.setDownloading(); else if (messageRecord.isPending()) deliveryStatusView.setPending(); else if (messageRecord.isFailed()) deliveryStatusView.setFailed(); - else if (!messageRecord.isOutgoing() || isCall || isOutChannel(messageRecord)) deliveryStatusView.setNone(); + else if (!messageRecord.isOutgoing() || isCall || isOutChannel) deliveryStatusView.setNone(); else if (messageRecord.isRemoteRead()) deliveryStatusView.setRead(); else if (messageRecord.isDelivered()) deliveryStatusView.setSent(); else if (messageRecord.isPreparing()) deliveryStatusView.setPreparing(); diff --git a/src/main/res/drawable/ic_baseline_eye.xml b/src/main/res/drawable/ic_baseline_eye.xml new file mode 100644 index 000000000..c74c5a758 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_eye.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/layout/conversation_item_footer.xml b/src/main/res/layout/conversation_item_footer.xml index 4016775f0..4a4f2b990 100644 --- a/src/main/res/layout/conversation_item_footer.xml +++ b/src/main/res/layout/conversation_item_footer.xml @@ -62,6 +62,29 @@ android:contentDescription="@string/location" tools:visibility="gone" /> + + + + Date: Mon, 26 Jan 2026 22:29:03 +0100 Subject: [PATCH 18/21] remove checkmark from chatlist for outgoing channel messages --- .../securesms/ConversationListItem.java | 25 +++++++++++-------- .../database/model/ThreadRecord.java | 4 +++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationListItem.java b/src/main/java/org/thoughtcrime/securesms/ConversationListItem.java index ae32e49ac..e68ea21e2 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationListItem.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationListItem.java @@ -124,7 +124,6 @@ public class ConversationListItem extends RelativeLayout this.msgId = msgId; int state = dcSummary.getState(); - int unreadCount = thread.getUnreadCount(); if (highlightSubstring != null) { this.fromView.setText(getHighlightedSpan(recipient.getName(), highlightSubstring)); @@ -151,7 +150,7 @@ public class ConversationListItem extends RelativeLayout thread.getVisibility()==DcChat.DC_CHAT_VISIBILITY_PINNED? R.drawable.ic_pinned_chatlist : 0, 0 ); - setStatusIcons(thread.getVisibility(), state, unreadCount, thread.isContactRequest(), thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK); + setStatusIcons(thread, state); setBatchState(batchMode); setBgColor(thread); @@ -237,14 +236,14 @@ public class ConversationListItem extends RelativeLayout return msgId; } - private void setStatusIcons(int visibility, int state, int unreadCount, boolean isContactRequest, boolean isMuted) { - if (visibility==DcChat.DC_CHAT_VISIBILITY_ARCHIVED) + private void setStatusIcons(ThreadRecord thread, int state) { + if (thread.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED) { archivedBadgeView.setVisibility(View.VISIBLE); - requestBadgeView.setVisibility(isContactRequest ? View.VISIBLE : View.GONE); + requestBadgeView.setVisibility(thread.isContactRequest() ? View.VISIBLE : View.GONE); deliveryStatusIndicator.setNone(); } - else if (isContactRequest) { + else if (thread.isContactRequest()) { requestBadgeView.setVisibility(View.VISIBLE); archivedBadgeView.setVisibility(View.GONE); deliveryStatusIndicator.setNone(); @@ -256,14 +255,16 @@ public class ConversationListItem extends RelativeLayout if (state == DcMsg.DC_STATE_OUT_FAILED) { deliveryStatusIndicator.setFailed(); - } else if (state == DcMsg.DC_STATE_OUT_MDN_RCVD) { - deliveryStatusIndicator.setRead(); - } else if (state == DcMsg.DC_STATE_OUT_DELIVERED) { - deliveryStatusIndicator.setSent(); } else if (state == DcMsg.DC_STATE_OUT_PREPARING) { deliveryStatusIndicator.setPreparing(); } else if (state == DcMsg.DC_STATE_OUT_PENDING) { deliveryStatusIndicator.setPending(); + } else if (thread.isOutChannel()) { + deliveryStatusIndicator.setNone(); + } else if (state == DcMsg.DC_STATE_OUT_MDN_RCVD) { + deliveryStatusIndicator.setRead(); + } else if (state == DcMsg.DC_STATE_OUT_DELIVERED) { + deliveryStatusIndicator.setSent(); } else { deliveryStatusIndicator.setNone(); } @@ -275,9 +276,11 @@ public class ConversationListItem extends RelativeLayout } } - if(unreadCount==0 || isContactRequest) { + int unreadCount = thread.getUnreadCount(); + if(unreadCount==0 || thread.isContactRequest()) { unreadIndicator.setVisibility(View.GONE); } else { + boolean isMuted = thread.isMuted() || chatId == DcChat.DC_CHAT_ID_ARCHIVED_LINK; final int color = getResources().getColor(isMuted ? (ThemeUtil.isDarkTheme(getContext()) ? R.color.unread_count_muted_dark : R.color.unread_count_muted) : R.color.unread_count); unreadIndicator.setImageDrawable(TextDrawable.builder() .beginConfig() diff --git a/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index c41e31d76..428da4776 100644 --- a/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -101,6 +101,10 @@ public class ThreadRecord { return spannable; } + public boolean isOutChannel() { + return recipient.getChat() != null && recipient.getChat().isOutBroadcast(); + } + public int getUnreadCount() { return unreadCount; } From a0f1232261a332b1d3bc59005d0e10e68e549fc3 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 26 Jan 2026 22:30:57 +0100 Subject: [PATCH 19/21] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 652321563..e98854105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Improve switch speed when changing profiles * Allow to switch profile when sharing or forwarding +* Display message views count for channel owners * Don't notify notification-to-all from in-chat apps if the chat is muted * Allow to see inbox quota for all relays in connectivity screen * Truncate file names in the middle, not at the end; important information are more often at the end From 27c0f45430272989ceb057b06d35aa34578300ee Mon Sep 17 00:00:00 2001 From: 72374 <250991390+72374@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:27:56 +0100 Subject: [PATCH 20/21] fix: Change maximum height and width of avatar-images to the maximum allowed by Chatmail Core (#4185) Avatar-images with a resolution larger than 512x512 will be resized by Chatmail Core, while media-quality is set to balanced, which is currently the highest quality-setting. Co-authored-by: Hocuri --- .../java/org/thoughtcrime/securesms/profiles/AvatarHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/thoughtcrime/securesms/profiles/AvatarHelper.java b/src/main/java/org/thoughtcrime/securesms/profiles/AvatarHelper.java index bf98af3d4..e56c5f0c4 100644 --- a/src/main/java/org/thoughtcrime/securesms/profiles/AvatarHelper.java +++ b/src/main/java/org/thoughtcrime/securesms/profiles/AvatarHelper.java @@ -21,7 +21,7 @@ import java.io.IOException; public class AvatarHelper { /* the maximum width/height an avatar should have */ - public static final int AVATAR_SIZE = 640; + public static final int AVATAR_SIZE = 512; public static void setGroupAvatar(Context context, int chatId, Bitmap bitmap) { DcContext dcContext = DcHelper.getContext(context); From a03fd337ef434434967fb33b595ba6c56b419969 Mon Sep 17 00:00:00 2001 From: adbenitez Date: Tue, 27 Jan 2026 01:20:27 +0100 Subject: [PATCH 21/21] throw exception if createAccountsCPtr() returned null pointer --- src/main/java/com/b44t/messenger/DcAccounts.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/b44t/messenger/DcAccounts.java b/src/main/java/com/b44t/messenger/DcAccounts.java index bda939d65..669048b12 100644 --- a/src/main/java/com/b44t/messenger/DcAccounts.java +++ b/src/main/java/com/b44t/messenger/DcAccounts.java @@ -4,6 +4,7 @@ public class DcAccounts { public DcAccounts(String dir) { accountsCPtr = createAccountsCPtr(dir); + if (accountsCPtr == 0) throw new RuntimeException("createAccountsCPtr() returned null pointer"); } @Override