From bca350f7849d76b7e7e69711cfdc02f77d6477fd Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 14 Aug 2019 20:08:24 +0200 Subject: [PATCH] Vertical message action bar --- .../eu/faircode/email/AdapterMessage.java | 890 +++++++++--------- .../res/drawable/baseline_more_horiz_24.xml | 10 + .../main/res/layout/include_message_body.xml | 152 ++- 3 files changed, 597 insertions(+), 455 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_more_horiz_24.xml diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index c1f7551992..df183f94df 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -111,8 +111,6 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; import com.github.chrisbanes.photoview.PhotoView; -import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.google.android.material.bottomnavigation.LabelVisibilityMode; import com.google.android.material.snackbar.Snackbar; import org.jsoup.Jsoup; @@ -225,8 +223,7 @@ public class AdapterMessage extends RecyclerView.Adapter 0 ? View.VISIBLE : View.GONE); - bnvActions.setVisibility(View.VISIBLE); - bnvActions.setTag(null); - for (int i = 0; i < bnvActions.getMenu().size(); i++) - bnvActions.getMenu().getItem(i).setVisible(false); + vSeparatorBody.setVisibility(View.VISIBLE); - ibImages.setVisibility(View.INVISIBLE); ibFull.setVisibility(hasWebView ? View.INVISIBLE : View.GONE); + ibImages.setVisibility(View.INVISIBLE); + + ibReply.setVisibility(View.INVISIBLE); + ibArchive.setVisibility(View.INVISIBLE); + ibMove.setVisibility(View.INVISIBLE); + ibDelete.setVisibility(View.INVISIBLE); + ibMove.setVisibility(View.INVISIBLE); if (textSize != 0) tvBody.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); @@ -1078,7 +1104,7 @@ public class AdapterMessage extends RecyclerView.Adapter() { + @Override + protected EntityIdentity onExecute(Context context, Bundle args) { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + if (message.identity == null) + return null; + + DB db = DB.getInstance(context); + return db.identity().getIdentity(message.identity); + } + + @Override + protected void onExecuted(Bundle args, EntityIdentity identity) { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + + TupleMessageEx amessage = getMessage(); + if (amessage == null || !amessage.id.equals(message.id)) + return; + + String via = (identity == null ? null : MessageHelper.canonicalAddress(identity.email)); + Address[] recipients = message.getAllRecipients(via); + + if (recipients.length == 0 && + message.list_post == null && + message.receipt_to == null && + (answers == 0 && ActivityBilling.isPro(context))) { + onMenuReply(message, "reply"); + return; + } + + PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, ibReply); + popupMenu.inflate(R.menu.menu_reply); + popupMenu.getMenu().findItem(R.id.menu_reply_to_all).setVisible(recipients.length > 0); + popupMenu.getMenu().findItem(R.id.menu_reply_list).setVisible(message.list_post != null); + popupMenu.getMenu().findItem(R.id.menu_reply_receipt).setVisible(message.receipt_to != null); + popupMenu.getMenu().findItem(R.id.menu_reply_answer).setVisible(answers != 0 || !ActivityBilling.isPro(context)); + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem target) { + switch (target.getItemId()) { + case R.id.menu_reply_to_sender: + onMenuReply(message, "reply"); + return true; + case R.id.menu_reply_to_all: + onMenuReply(message, "reply_all"); + return true; + case R.id.menu_reply_list: + onMenuReply(message, "list"); + return true; + case R.id.menu_reply_receipt: + onMenuReply(message, "receipt"); + return true; + case R.id.menu_reply_answer: + onMenuAnswer(message); + return true; + default: + return false; + } + } + }); + popupMenu.show(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:reply"); + } + + private void onMenuReply(TupleMessageEx message, String action) { + Intent reply = new Intent(context, ActivityCompose.class) + .putExtra("action", action) + .putExtra("reference", message.id); + context.startActivity(reply); + } + + private void onMenuAnswer(TupleMessageEx message) { + new SimpleTask>() { + @Override + protected List onExecute(Context context, Bundle args) { + return DB.getInstance(context).answer().getAnswers(false); + } + + @Override + protected void onExecuted(Bundle args, List answers) { + if (answers == null || answers.size() == 0) { + Snackbar snackbar = Snackbar.make( + parentFragment.getView(), + context.getString(R.string.title_no_answers), + Snackbar.LENGTH_LONG); + snackbar.setAction(R.string.title_fix, new View.OnClickListener() { + @Override + public void onClick(View v) { + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); + lbm.sendBroadcast(new Intent(ActivityView.ACTION_EDIT_ANSWERS)); + } + }); + snackbar.show(); + } else { + PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, ibReply); + + int order = 0; + for (EntityAnswer answer : answers) + popupMenu.getMenu().add(Menu.NONE, answer.id.intValue(), order++, answer.name); + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem target) { + if (!ActivityBilling.isPro(context)) { + context.startActivity(new Intent(context, ActivityBilling.class)); + return true; + } + + context.startActivity(new Intent(context, ActivityCompose.class) + .putExtra("action", "reply") + .putExtra("reference", message.id) + .putExtra("answer", (long) target.getItemId())); + return true; + } + }); + + popupMenu.show(); + } + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, new Bundle(), "message:answer"); + } + + private void onActionArchive(TupleMessageEx message) { + properties.move(message.id, EntityFolder.ARCHIVE); + } + + private void onActionMove(TupleMessageEx message, final boolean copy) { + Bundle args = new Bundle(); + args.putString("title", context.getString(copy ? R.string.title_copy_to : R.string.title_move_to_folder)); + args.putLong("account", message.account); + args.putLongArray("disabled", new long[]{message.folder}); + args.putLong("message", message.id); + args.putBoolean("copy", copy); + args.putBoolean("similar", false); + + FragmentDialogFolder fragment = new FragmentDialogFolder(); + fragment.setArguments(args); + fragment.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_MOVE); + fragment.show(parentFragment.getFragmentManager(), "message:move"); + } + + private void onActionMoveOutbox(TupleMessageEx message) { + Bundle args = new Bundle(); + args.putLong("id", message.id); + + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + long id = args.getLong("id"); + + EntityMessage message; + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + message = db.message().getMessage(id); + if (message == null) + return null; + + db.folder().setFolderError(message.folder, null); + + File source = message.getFile(context); + + // Insert into drafts + EntityFolder drafts = db.folder().getFolderByType(message.account, EntityFolder.DRAFTS); + message.id = null; + message.folder = drafts.id; + message.ui_snoozed = null; + message.error = null; + message.id = db.message().insertMessage(message); + + File target = message.getFile(context); + source.renameTo(target); + + List attachments = db.attachment().getAttachments(id); + for (EntityAttachment attachment : attachments) + db.attachment().setMessage(attachment.id, message.id); + + EntityOperation.queue(context, message, EntityOperation.ADD); + + // Delete from outbox + db.message().deleteMessage(id); // will delete operation too + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + if (message.identity != null) { + // Identity can be deleted + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel("send:" + message.identity, 1); + } + + return null; + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:move:draft"); + } + + private void onActionMoveJunk(TupleMessageEx message) { + properties.move(message.id, EntityFolder.INBOX); + } + + private void onActionDelete(TupleMessageEx message) { + if (delete) { + Bundle aargs = new Bundle(); + aargs.putString("question", context.getString(R.string.title_ask_delete)); + aargs.putLong("id", message.id); + + FragmentDialogAsk ask = new FragmentDialogAsk(); + ask.setArguments(aargs); + ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_DELETE); + ask.show(parentFragment.getFragmentManager(), "message:delete"); + } else + properties.move(message.id, EntityFolder.TRASH); + } + + private void onActionMore(TupleMessageEx message) { + boolean show_headers = properties.getValue("headers", message.id); + + PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, ibMore); + popupMenu.inflate(R.menu.menu_message); + + popupMenu.getMenu().findItem(R.id.menu_forward).setEnabled(message.content); + popupMenu.getMenu().findItem(R.id.menu_editasnew).setEnabled(message.content); + + popupMenu.getMenu().findItem(R.id.menu_unseen).setEnabled(message.uid != null && !message.folderReadOnly); + popupMenu.getMenu().findItem(R.id.menu_flag_color).setEnabled(message.uid != null && !message.folderReadOnly); + + popupMenu.getMenu().findItem(R.id.menu_copy).setEnabled(message.uid != null && !message.folderReadOnly); + popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug); + + popupMenu.getMenu().findItem(R.id.menu_junk).setEnabled(message.uid != null && !message.folderReadOnly); + popupMenu.getMenu().findItem(R.id.menu_junk).setVisible(hasJunk && !EntityFolder.JUNK.equals(message.folderType)); + + popupMenu.getMenu().findItem(R.id.menu_share).setEnabled(message.content); + popupMenu.getMenu().findItem(R.id.menu_print).setEnabled(hasWebView && message.content); + popupMenu.getMenu().findItem(R.id.menu_print).setVisible(Helper.canPrint(context)); + + popupMenu.getMenu().findItem(R.id.menu_show_headers).setChecked(show_headers); + popupMenu.getMenu().findItem(R.id.menu_show_headers).setEnabled(message.uid != null); + + popupMenu.getMenu().findItem(R.id.menu_raw).setEnabled( + message.uid != null && (message.raw == null || message.raw)); + popupMenu.getMenu().findItem(R.id.menu_raw).setTitle( + message.raw == null || !message.raw ? R.string.title_raw_download : R.string.title_raw_save); + + popupMenu.getMenu().findItem(R.id.menu_manage_keywords).setEnabled(message.uid != null && !message.folderReadOnly); + + popupMenu.getMenu().findItem(R.id.menu_decrypt).setEnabled( + message.content && message.to != null && message.to.length > 0); + + popupMenu.getMenu().findItem(R.id.menu_resync).setEnabled(message.uid != null); + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem target) { + switch (target.getItemId()) { + case R.id.menu_forward: + onMenuForward(message); + return true; + case R.id.menu_editasnew: + onMenuEditAsNew(message); + return true; + case R.id.menu_unseen: + onMenuUnseen(message); + return true; + case R.id.menu_flag_color: + onMenuColoredStar(message); + return true; + case R.id.menu_copy: + onActionMove(message, true); + return true; + case R.id.menu_delete: + // For emergencies + onMenuDelete(message); + return true; + case R.id.menu_junk: + onMenuJunk(message); + return true; + case R.id.menu_decrypt: + onActionDecrypt(message); + return true; + case R.id.menu_resync: + onMenuResync(message); + return true; + case R.id.menu_create_rule: + onMenuCreateRule(message); + return true; + case R.id.menu_manage_keywords: + onMenuManageKeywords(message); + return true; + case R.id.menu_share: + onMenuShare(message); + return true; + case R.id.menu_print: + onMenuPrint(message); + return true; + case R.id.menu_show_headers: + onMenuShowHeaders(message); + return true; + case R.id.menu_raw: + onMenuRaw(message); + return true; + default: + return false; + } + } + }); + popupMenu.show(); + } + private void loadText(TupleMessageEx message) { if (message.content) { boolean show_images = properties.getValue("images", message.id); @@ -2207,39 +2591,6 @@ public class AdapterMessage extends RecyclerView.Adapter 0); - - popupMenu.getMenu().findItem(R.id.menu_resync).setEnabled(data.message.uid != null); - - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem target) { - switch (target.getItemId()) { - case R.id.menu_forward: - onMenuForward(data.message); - return true; - case R.id.menu_editasnew: - onMenuEditAsNew(data.message); - return true; - case R.id.menu_unseen: - onMenuUnseen(data.message); - return true; - case R.id.menu_flag_color: - onMenuColoredStar(data.message); - return true; - case R.id.menu_copy: - onActionMove(data.message, true); - return true; - case R.id.menu_delete: - // For emergencies - onMenuDelete(data.message); - return true; - case R.id.menu_junk: - onMenuJunk(data.message); - return true; - case R.id.menu_decrypt: - onMenuDecrypt(data.message); - return true; - case R.id.menu_resync: - onMenuResync(data.message); - return true; - case R.id.menu_create_rule: - onMenuCreateRule(data.message); - return true; - case R.id.menu_manage_keywords: - onMenuManageKeywords(data.message); - return true; - case R.id.menu_share: - onMenuShare(data.message); - return true; - case R.id.menu_print: - onMenuPrint(data.message); - return true; - case R.id.menu_show_headers: - onMenuShowHeaders(data.message); - return true; - case R.id.menu_raw: - onMenuRaw(data.message); - return true; - default: - return false; - } - } - }); - popupMenu.show(); - } - - private void onActionDelete(final ActionData data) { - if (data.delete) { - Bundle aargs = new Bundle(); - aargs.putString("question", context.getString(R.string.title_ask_delete)); - aargs.putLong("id", data.message.id); - - FragmentDialogAsk ask = new FragmentDialogAsk(); - ask.setArguments(aargs); - ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_DELETE); - ask.show(parentFragment.getFragmentManager(), "message:delete"); - } else - properties.move(data.message.id, EntityFolder.TRASH); - } - - private void onActionMove(final TupleMessageEx message, final boolean copy) { - Bundle args = new Bundle(); - args.putString("title", context.getString(copy ? R.string.title_copy_to : R.string.title_move_to_folder)); - args.putLong("account", message.account); - args.putLongArray("disabled", new long[]{message.folder}); - args.putLong("message", message.id); - args.putBoolean("copy", copy); - args.putBoolean("similar", false); - - FragmentDialogFolder fragment = new FragmentDialogFolder(); - fragment.setArguments(args); - fragment.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_MOVE); - fragment.show(parentFragment.getFragmentManager(), "message:move"); - } - - private void onActionMoveOutbox(ActionData data) { - Bundle args = new Bundle(); - args.putLong("id", data.message.id); - - new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - long id = args.getLong("id"); - - EntityMessage message; - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - message = db.message().getMessage(id); - if (message == null) - return null; - - db.folder().setFolderError(message.folder, null); - - File source = message.getFile(context); - - // Insert into drafts - EntityFolder drafts = db.folder().getFolderByType(message.account, EntityFolder.DRAFTS); - message.id = null; - message.folder = drafts.id; - message.ui_snoozed = null; - message.error = null; - message.id = db.message().insertMessage(message); - - File target = message.getFile(context); - source.renameTo(target); - - List attachments = db.attachment().getAttachments(id); - for (EntityAttachment attachment : attachments) - db.attachment().setMessage(attachment.id, message.id); - - EntityOperation.queue(context, message, EntityOperation.ADD); - - // Delete from outbox - db.message().deleteMessage(id); // will delete operation too - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - if (message.identity != null) { - // Identity can be deleted - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel("send:" + message.identity, 1); - } - - return null; - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, args, "message:move:draft"); - } - - private void onActionArchive(ActionData data) { - properties.move(data.message.id, EntityFolder.ARCHIVE); - } - - private void onActionMoveJunk(ActionData data) { - properties.move(data.message.id, EntityFolder.INBOX); - } - - private void onActionReplyMenu(final ActionData data) { - Bundle args = new Bundle(); - args.putSerializable("message", data.message); - - new SimpleTask() { - @Override - protected EntityIdentity onExecute(Context context, Bundle args) { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - if (message.identity == null) - return null; - - DB db = DB.getInstance(context); - return db.identity().getIdentity(message.identity); - } - - @Override - protected void onExecuted(Bundle args, EntityIdentity identity) { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - - TupleMessageEx amessage = getMessage(); - if (amessage == null || !amessage.id.equals(message.id)) - return; - - String via = (identity == null ? null : MessageHelper.canonicalAddress(identity.email)); - Address[] recipients = data.message.getAllRecipients(via); - - if (recipients.length == 0 && - data.message.list_post == null && - data.message.receipt_to == null && - (answers == 0 && ActivityBilling.isPro(context))) { - onMenuReply(data, "reply"); - return; - } - - View anchor = bnvActions.findViewById(R.id.action_reply); - PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor); - popupMenu.inflate(R.menu.menu_reply); - popupMenu.getMenu().findItem(R.id.menu_reply_to_all).setVisible(recipients.length > 0); - popupMenu.getMenu().findItem(R.id.menu_reply_list).setVisible(data.message.list_post != null); - popupMenu.getMenu().findItem(R.id.menu_reply_receipt).setVisible(data.message.receipt_to != null); - popupMenu.getMenu().findItem(R.id.menu_reply_answer).setVisible(answers != 0 || !ActivityBilling.isPro(context)); - - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem target) { - switch (target.getItemId()) { - case R.id.menu_reply_to_sender: - onMenuReply(data, "reply"); - return true; - case R.id.menu_reply_to_all: - onMenuReply(data, "reply_all"); - return true; - case R.id.menu_reply_list: - onMenuReply(data, "list"); - return true; - case R.id.menu_reply_receipt: - onMenuReply(data, "receipt"); - return true; - case R.id.menu_reply_answer: - onMenuAnswer(data); - return true; - default: - return false; - } - } - }); - popupMenu.show(); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, args, "message:reply"); - } - - private void onMenuReply(final ActionData data, String action) { - Intent reply = new Intent(context, ActivityCompose.class) - .putExtra("action", action) - .putExtra("reference", data.message.id); - context.startActivity(reply); - } - - private void onMenuAnswer(final ActionData data) { - new SimpleTask>() { - @Override - protected List onExecute(Context context, Bundle args) { - return DB.getInstance(context).answer().getAnswers(false); - } - - @Override - protected void onExecuted(Bundle args, List answers) { - if (answers == null || answers.size() == 0) { - Snackbar snackbar = Snackbar.make( - parentFragment.getView(), - context.getString(R.string.title_no_answers), - Snackbar.LENGTH_LONG); - snackbar.setAction(R.string.title_fix, new View.OnClickListener() { - @Override - public void onClick(View v) { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); - lbm.sendBroadcast(new Intent(ActivityView.ACTION_EDIT_ANSWERS)); - } - }); - snackbar.show(); - } else { - View anchor = bnvActions.findViewById(R.id.action_reply); - PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor); - - int order = 0; - for (EntityAnswer answer : answers) - popupMenu.getMenu().add(Menu.NONE, answer.id.intValue(), order++, answer.name); - - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem target) { - if (!ActivityBilling.isPro(context)) { - context.startActivity(new Intent(context, ActivityBilling.class)); - return true; - } - - context.startActivity(new Intent(context, ActivityCompose.class) - .putExtra("action", "reply") - .putExtra("reference", data.message.id) - .putExtra("answer", (long) target.getItemId())); - return true; - } - }); - - popupMenu.show(); - } - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, new Bundle(), "message:answer"); - } - ItemDetailsLookup.ItemDetails getItemDetails(@NonNull MotionEvent motionEvent) { return new ItemDetailsMessage(this); } @@ -2944,12 +2950,6 @@ public class AdapterMessage extends RecyclerView.Adapter + + diff --git a/app/src/main/res/layout/include_message_body.xml b/app/src/main/res/layout/include_message_body.xml index 9e138dad09..e34f125ff3 100644 --- a/app/src/main/res/layout/include_message_body.xml +++ b/app/src/main/res/layout/include_message_body.xml @@ -28,14 +28,145 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/inHeaders" /> - + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/vSeparatorBody" /> + app:layout_constraintTop_toBottomOf="@id/vSeparatorBody" />