Merge branch 'main' into adb/allow-to-edit-and-delete-for-all

This commit is contained in:
adb
2025-03-12 18:50:17 +00:00
committed by GitHub
96 changed files with 1596 additions and 870 deletions
@@ -17,6 +17,7 @@ public class DcContext {
public final static int DC_EVENT_MSG_READ = 2015;
public final static int DC_EVENT_CHAT_MODIFIED = 2020;
public final static int DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021;
public final static int DC_EVENT_CHAT_DELETED = 2023;
public final static int DC_EVENT_CONTACTS_CHANGED = 2030;
public final static int DC_EVENT_LOCATION_CHANGED = 2035;
public final static int DC_EVENT_CONFIGURE_PROGRESS = 2041;
@@ -17,6 +17,7 @@ public class Rpc {
private final Map<Integer, SettableFuture<JsonElement>> requestFutures = new ConcurrentHashMap<>();
private final DcJsonrpcInstance dcJsonrpcInstance;
private int requestId = 0;
private boolean started = false;
private final Gson gson = new GsonBuilder().serializeNulls().create();
public Rpc(DcJsonrpcInstance dcJsonrpcInstance) {
@@ -46,6 +47,7 @@ public class Rpc {
}
public void start() {
started = true;
new Thread(() -> {
while (true) {
try {
@@ -57,7 +59,9 @@ public class Rpc {
}, "jsonrpcThread").start();
}
public SettableFuture<JsonElement> call(String method, Object... params) {
public SettableFuture<JsonElement> call(String method, Object... params) throws RpcException {
if (!started) throw new RpcException("RPC not started yet.");
int id;
synchronized (this) {
id = ++requestId;
@@ -88,6 +88,7 @@ public class ApplicationContext extends MultiDexApplication {
dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath());
rpc = new Rpc(dcAccounts.getJsonrpcInstance());
rpc.start();
AccountManager.getInstance().migrateToDcAccounts(this);
int[] allAccounts = dcAccounts.getAll();
for (int accountId : allAccounts) {
@@ -126,8 +127,6 @@ public class ApplicationContext extends MultiDexApplication {
Log.i("DeltaChat", "shutting down event handler");
}, "eventThread").start();
rpc.start();
// migrating global notifications pref. to per-account config, added 10/July/24
final String NOTIFICATION_PREF = "pref_key_enable_notifications";
boolean isMuted = !Prefs.getBooleanPreference(this, NOTIFICATION_PREF, true);
@@ -333,18 +333,22 @@ public abstract class BaseConversationListFragment extends Fragment implements A
intent.putExtra(ShareActivity.EXTRA_CHAT_ID, chat.getId());
Recipient recipient = new Recipient(activity, chat);
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
Util.runOnAnyBackgroundThread(() -> {
Bitmap avatar = DirectShareUtil.getIconForShortcut(activity, recipient);
ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(activity, "chat-" + dcContext.getAccountId() + "-" + chat.getId())
.setShortLabel(chat.getName())
.setIcon(IconCompat.createWithAdaptiveBitmap(avatar))
.setIntent(intent)
.build();
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
Toast.makeText(activity, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
} else if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
Util.runOnMain(() -> {
if (!ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfoCompat, null)) {
Toast.makeText(activity, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
} else if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
});
});
}
private void updateActionModeItems(Menu menu) {
@@ -490,6 +490,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Log.e(TAG, "cannot set up in-chat-search: ", e);
}
if (!dcChat.canSend() || isEditing) {
MenuItem attachItem = menu.findItem(R.id.menu_add_attachment);
if (attachItem!=null) {
attachItem.setVisible(false);
}
}
super.onPrepareOptionsMenu(menu);
return true;
}
@@ -498,7 +505,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
int itemId = item.getItemId();
if (itemId == R.id.menu_leave) {
if (itemId == R.id.menu_add_attachment) {
handleAddAttachment();
return true;
} else if (itemId == R.id.menu_leave) {
handleLeaveGroup();
return true;
} else if (itemId == R.id.menu_archive_chat) {
@@ -1005,9 +1015,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
final SettableFuture<Integer> future = new SettableFuture<>();
DcMsg msg = null;
Optional<QuoteModel> quote = inputPanel.getQuote();
Integer recompress = 0;
boolean editing = isEditing;
// for a quick ui feedback, we clear the related controls immediately on sending messages.
@@ -1017,122 +1025,114 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel.clearQuote();
}
if (editing) {
int msgId = quote.get().getQuotedMsg().getId();
Util.runOnAnyBackgroundThread(() -> {
Util.runOnAnyBackgroundThread(() -> {
DcMsg msg = null;
int recompress = 0;
if (editing) {
int msgId = quote.get().getQuotedMsg().getId();
if (action == ACTION_SEND_OUT) {
dcContext.sendEditRequest(msgId, body);
} else {
dcContext.setDraft(chatId, null);
}
});
return future;
}
if(slideDeck!=null) {
if (action==ACTION_SEND_OUT) {
attachmentManager.clear(glideRequests, false);
future.set(chatId);
return;
}
try {
if (slideDeck.getWebxdctDraftId() != 0) {
msg = dcContext.getDraft(chatId);
} else {
List<Attachment> attachments = slideDeck.asAttachments();
for (Attachment attachment : attachments) {
String contentType = attachment.getContentType();
if (MediaUtil.isImageType(contentType) && slideDeck.getDocumentSlide() == null) {
msg = new DcMsg(dcContext,
MediaUtil.isGif(contentType) ? DcMsg.DC_MSG_GIF : DcMsg.DC_MSG_IMAGE);
msg.setDimension(attachment.getWidth(), attachment.getHeight());
} else if (MediaUtil.isAudioType(contentType)) {
msg = new DcMsg(dcContext,
attachment.isVoiceNote() ? DcMsg.DC_MSG_VOICE : DcMsg.DC_MSG_AUDIO);
} else if (MediaUtil.isVideoType(contentType) && slideDeck.getDocumentSlide() == null) {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_VIDEO);
recompress = DcMsg.DC_MSG_VIDEO;
} else {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
if(slideDeck!=null) {
if (action==ACTION_SEND_OUT) {
Util.runOnMain(() -> attachmentManager.clear(glideRequests, false));
}
try {
if (slideDeck.getWebxdctDraftId() != 0) {
msg = dcContext.getDraft(chatId);
} else {
List<Attachment> attachments = slideDeck.asAttachments();
for (Attachment attachment : attachments) {
String contentType = attachment.getContentType();
if (MediaUtil.isImageType(contentType) && slideDeck.getDocumentSlide() == null) {
msg = new DcMsg(dcContext,
MediaUtil.isGif(contentType) ? DcMsg.DC_MSG_GIF : DcMsg.DC_MSG_IMAGE);
msg.setDimension(attachment.getWidth(), attachment.getHeight());
} else if (MediaUtil.isAudioType(contentType)) {
msg = new DcMsg(dcContext,
attachment.isVoiceNote() ? DcMsg.DC_MSG_VOICE : DcMsg.DC_MSG_AUDIO);
} else if (MediaUtil.isVideoType(contentType) && slideDeck.getDocumentSlide() == null) {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_VIDEO);
recompress = DcMsg.DC_MSG_VIDEO;
} else {
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
}
String path = attachment.getRealPath(this);
msg.setFileAndDeduplicate(path, attachment.getFileName(), null);
}
String path = attachment.getRealPath(this);
msg.setFileAndDeduplicate(path, attachment.getFileName(), null);
}
if (msg != null) {
msg.setText(body);
}
}
catch(Exception e) {
e.printStackTrace();
}
}
else if (!body.isEmpty()){
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
msg.setText(body);
}
catch(Exception e) {
e.printStackTrace();
if (quote.isPresent()) {
if (msg == null) msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
msg.setQuote(quote.get().getQuotedMsg());
}
}
else if (!body.isEmpty()){
msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
msg.setText(body);
}
if (quote.isPresent()) {
if (msg == null) msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT);
msg.setQuote(quote.get().getQuotedMsg());
}
if (action==ACTION_SEND_OUT) {
// msg may still be null to clear drafts
new AsyncTask<Object, Void, Void>() {
@Override
protected Void doInBackground(Object... param) {
DcMsg msg = (DcMsg)param[0];
Integer recompress = (Integer)param[1];
if (action==ACTION_SEND_OUT) {
// for WEBXDC, drafts are just sent out as is.
// for preparations and other cases, cleanup draft soon.
if (msg == null || msg.getType() != DcMsg.DC_MSG_WEBXDC) {
dcContext.setDraft(dcChat.getId(), null);
}
// for WEBXDC, drafts are just sent out as is.
// for preparations and other cases, cleanup draft soon.
if (msg == null || msg.getType() != DcMsg.DC_MSG_WEBXDC) {
dcContext.setDraft(dcChat.getId(), null);
}
if(msg!=null)
{
boolean doSend = true;
if (recompress==DcMsg.DC_MSG_VIDEO) {
Util.runOnMain(() -> {
progressDialog = ProgressDialog.show(
ConversationActivity.this,
"",
getString(R.string.one_moment),
true,
false
);
});
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, dcChat.getId(), msg);
Util.runOnMain(() -> {
try {
progressDialog.dismiss();
} catch (final IllegalArgumentException e) {
// The activity is finishing/destroyed, do nothing.
}
});
}
if (doSend) {
if (dcContext.sendMsg(dcChat.getId(), msg) == 0) {
Util.runOnMain(()-> Toast.makeText(ConversationActivity.this, dcContext.getLastError(), Toast.LENGTH_LONG).show());
return null;
if(msg!=null) {
boolean doSend = true;
if (recompress==DcMsg.DC_MSG_VIDEO) {
Util.runOnMain(() -> {
if (isFinishing()) return;
progressDialog = ProgressDialog.show(
ConversationActivity.this,
"",
getString(R.string.one_moment),
true,
false
);
});
doSend = VideoRecoder.prepareVideo(ConversationActivity.this, dcChat.getId(), msg);
Util.runOnMain(() -> {
try {
if (progressDialog != null) progressDialog.dismiss();
} catch (final IllegalArgumentException e) {
// The activity is finishing/destroyed, do nothing.
}
}
Util.runOnMain(()-> sendComplete(dcChat.getId()));
});
}
}
else {
dcContext.setDraft(dcChat.getId(), msg);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
future.set(chatId);
if (doSend) {
if (dcContext.sendMsg(dcChat.getId(), msg) == 0) {
Util.runOnMain(()-> Toast.makeText(ConversationActivity.this, dcContext.getLastError(), Toast.LENGTH_LONG).show());
future.set(chatId);
return;
}
}
Util.runOnMain(() -> sendComplete(dcChat.getId()));
}
} else {
dcContext.setDraft(dcChat.getId(), msg);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, msg, recompress);
future.set(chatId);
});
return future;
}
@@ -1351,8 +1351,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Toast.LENGTH_SHORT).show();
}
else {
processComposeControls(ACTION_SEND_OUT);
DcHelper.getNotificationCenter(ConversationActivity.this).maybePlaySendSound(dcChat);
processComposeControls(ACTION_SEND_OUT).addListener(new AssertedSuccessListener<Integer>() {
@Override
public void onSuccess(Integer chatId) {
DcHelper.getNotificationCenter(ConversationActivity.this).maybePlaySendSound(dcChat);
}
});
}
}
@@ -327,6 +327,7 @@ public class ConversationFragment extends MessageSelectorFragment
menu.findItem(R.id.menu_context_edit).setVisible(false);
menu.findItem(R.id.menu_context_reply_privately).setVisible(false);
menu.findItem(R.id.menu_add_to_home_screen).setVisible(false);
menu.findItem(R.id.menu_toggle_save).setVisible(false);
} else {
DcMsg messageRecord = messageRecords.iterator().next();
DcChat chat = getListAdapter().getChat();
@@ -339,6 +340,12 @@ public class ConversationFragment extends MessageSelectorFragment
boolean showReplyPrivately = chat.isMultiUser() && !messageRecord.isOutgoing() && canReply;
menu.findItem(R.id.menu_context_reply_privately).setVisible(showReplyPrivately);
menu.findItem(R.id.menu_add_to_home_screen).setVisible(messageRecord.getType() == DcMsg.DC_MSG_WEBXDC);
boolean saved = messageRecord.getSavedMsgId() != 0;
MenuItem toggleSave = menu.findItem(R.id.menu_toggle_save);
toggleSave.setVisible(messageRecord.canSave() && !chat.isSelfTalk());
toggleSave.setIcon(saved? R.drawable.baseline_bookmark_remove_24 : R.drawable.baseline_bookmark_border_24);
toggleSave.setTitle(saved? R.string.unsave : R.string.save);
}
// if one of the selected items cannot be saved, disable saving.
@@ -495,6 +502,15 @@ public class ConversationFragment extends MessageSelectorFragment
}
}
private void handleToggleSave(final Set<DcMsg> messageRecords) {
DcMsg msg = getSelectedMessageRecord(messageRecords);
if (msg.getSavedMsgId() != 0) {
dcContext.deleteMsgs(new int[]{msg.getSavedMsgId()});
} else {
dcContext.saveMsgs(new int[]{msg.getId()});
}
}
private void reloadList() {
reloadList(false);
}
@@ -987,9 +1003,12 @@ public class ConversationFragment extends MessageSelectorFragment
} else if (itemId == R.id.menu_resend) {
handleResendMessage(getListAdapter().getSelectedItems());
return true;
} else if (itemId == R.id.menu_toggle_save) {
handleToggleSave(getListAdapter().getSelectedItems());
actionMode.finish();
return true;
}
return false;
return false;
}
}
@@ -80,6 +80,7 @@ public class ConversationListFragment extends BaseConversationListFragment
DcEventCenter eventCenter = DcHelper.getEventCenter(requireActivity());
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_INCOMING_MSG, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_MSGS_NOTICED, this);
eventCenter.addMultiAccountObserver(DcContext.DC_EVENT_CHAT_DELETED, this);
eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this);
eventCenter.addObserver(DcContext.DC_EVENT_CONTACTS_CHANGED, this);
eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
@@ -321,7 +322,10 @@ public class ConversationListFragment extends BaseConversationListFragment
@Override
public void handleEvent(@NonNull DcEvent event) {
if (event.getAccountId() != DcHelper.getContext(requireActivity()).getAccountId()) {
final int accId = event.getAccountId();
if (event.getId() == DcContext.DC_EVENT_CHAT_DELETED) {
DcHelper.getNotificationCenter(requireActivity()).removeNotifications(accId, event.getData1Int());
} else if (accId != DcHelper.getContext(requireActivity()).getAccountId()) {
Activity activity = getActivity();
if (activity instanceof ConversationListActivity) {
((ConversationListActivity) activity).refreshUnreadIndicator();
@@ -319,12 +319,15 @@ public class ProfileSettingsFragment extends Fragment
super.onActivityResult(requestCode, resultCode, data);
if (requestCode==REQUEST_CODE_PICK_CONTACT && resultCode==Activity.RESULT_OK && data!=null) {
List<String> selected = data.getStringArrayListExtra("contacts");
for (String addr : selected) {
if (addr!=null) {
int toAddId = dcContext.createContact(null, addr);
dcContext.addContactToChat(chatId, toAddId);
if(selected == null) return;
Util.runOnAnyBackgroundThread(() -> {
for (String addr : selected) {
if (addr!=null) {
int toAddId = dcContext.createContact(null, addr);
dcContext.addContactToChat(chatId, toAddId);
}
}
}
});
}
}
}
@@ -27,6 +27,7 @@ public class ConversationAdaptiveActionsToolbar extends Toolbar {
private static final int ID_ACTION_2 = R.id.menu_context_copy;
private static final int ID_ACTION_3 = R.id.menu_context_share;
private static final int ID_ACTION_4 = R.id.menu_context_forward;
private static final int ID_ACTION_5 = R.id.menu_toggle_save;
private final int maxShown;
@@ -79,7 +80,8 @@ public class ConversationAdaptiveActionsToolbar extends Toolbar {
boolean showAsAction = item.getItemId() == ID_ACTION_1
|| item.getItemId() == ID_ACTION_2
|| item.getItemId() == ID_ACTION_3
|| item.getItemId() == ID_ACTION_4;
|| item.getItemId() == ID_ACTION_4
|| item.getItemId() == ID_ACTION_5;
if (showAsAction && item.isVisible() && nItemsToShow > 0) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);