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 bodyTask = new SimpleTask() { - @Override - protected SpannableStringBuilder onExecute(final Context context, final Bundle args) throws IOException { - final TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - final boolean show_images = args.getBoolean("show_images"); - boolean show_quotes = args.getBoolean("show_quotes"); - int zoom = args.getInt("zoom"); + private void onActionReplyMenu(TupleMessageEx message) { + Bundle args = new Bundle(); + args.putSerializable("message", message); - if (message == null || !message.content) - return null; + new SimpleTask() { + @Override + protected EntityIdentity onExecute(Context context, Bundle args) { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + if (message.identity == null) + return null; - File file = message.getFile(context); - if (!file.exists()) - return null; + DB db = DB.getInstance(context); + return db.identity().getIdentity(message.identity); + } - String body = Helper.readText(file); + @Override + protected void onExecuted(Bundle args, EntityIdentity identity) { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - if (!show_quotes) { - Document document = Jsoup.parse(body); - for (Element quote : document.select("blockquote")) - quote.html("…"); - body = document.html(); - } + TupleMessageEx amessage = getMessage(); + if (amessage == null || !amessage.id.equals(message.id)) + return; - String html = HtmlHelper.sanitize(context, body, show_images); - if (debug) - html += "
" + Html.escapeHtml(html) + "
"; + String via = (identity == null ? null : MessageHelper.canonicalAddress(identity.email)); + Address[] recipients = message.getAllRecipients(via); - Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() { - @Override - public Drawable getDrawable(String source) { - return HtmlHelper.decodeImage(context, message.id, source, show_images, tvBody); + if (recipients.length == 0 && + message.list_post == null && + message.receipt_to == null && + (answers == 0 && ActivityBilling.isPro(context))) { + onMenuReply(message, "reply"); + return; } - }, null); - SpannableStringBuilder builder = new SpannableStringBuilder(spanned); - QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class); - for (QuoteSpan quoteSpan : quoteSpans) { - builder.setSpan( - new StyledQuoteSpan(colorPrimary), - builder.getSpanStart(quoteSpan), - builder.getSpanEnd(quoteSpan), - builder.getSpanFlags(quoteSpan)); - builder.removeSpan(quoteSpan); - } + 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)); - if (!show_quotes) { - final int px = Helper.dp2pixels(context, 24 + (zoom) * 8); + 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(); + } - StyledQuoteSpan[] squotes = builder.getSpans(0, builder.length(), StyledQuoteSpan.class); - for (StyledQuoteSpan squote : squotes) - builder.setSpan(new DynamicDrawableSpan() { - @Override - public Drawable getDrawable() { - Drawable d = context.getDrawable(R.drawable.baseline_format_quote_24); - d.setTint(colorAccent); - d.setBounds(0, 0, px, px); - return d; - } - }, - builder.getSpanStart(squote), - builder.getSpanEnd(squote), - builder.getSpanFlags(squote)); + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); } + }.execute(context, owner, args, "message:reply"); + } - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean inline = prefs.getBoolean("inline_images", false); + private void onMenuReply(TupleMessageEx message, String action) { + Intent reply = new Intent(context, ActivityCompose.class) + .putExtra("action", action) + .putExtra("reference", message.id); + context.startActivity(reply); + } - boolean has_images; - ImageSpan[] spans = builder.getSpans(0, body.length(), ImageSpan.class); - if (inline) { - has_images = false; - for (ImageSpan span : spans) { - String source = span.getSource(); - if (source == null || !source.startsWith("cid:")) { - has_images = true; - break; - } - } - } else - has_images = spans.length > 0; + private void onMenuAnswer(TupleMessageEx message) { + new SimpleTask>() { + @Override + protected List onExecute(Context context, Bundle args) { + return DB.getInstance(context).answer().getAnswers(false); + } - args.putBoolean("has_images", has_images); + @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); - return builder; - } + int order = 0; + for (EntityAnswer answer : answers) + popupMenu.getMenu().add(Menu.NONE, answer.id.intValue(), order++, answer.name); - @Override - protected void onExecuted(Bundle args, SpannableStringBuilder body) { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - properties.setBody(message.id, body); + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem target) { + if (!ActivityBilling.isPro(context)) { + context.startActivity(new Intent(context, ActivityBilling.class)); + return true; + } - TupleMessageEx amessage = getMessage(); - if (amessage == null || !amessage.id.equals(message.id)) - return; + context.startActivity(new Intent(context, ActivityCompose.class) + .putExtra("action", "reply") + .putExtra("reference", message.id) + .putExtra("answer", (long) target.getItemId())); + return true; + } + }); - boolean show_expanded = properties.getValue("expanded", message.id); - if (!show_expanded) - return; + popupMenu.show(); + } + } - boolean has_images = args.getBoolean("has_images"); - boolean show_images = properties.getValue("images", message.id); + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, new Bundle(), "message:answer"); + } - ibFull.setVisibility(hasWebView ? View.VISIBLE : View.GONE); - ibImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE); - - tvBody.setText(body); - tvBody.setTextIsSelectable(false); - tvBody.setTextIsSelectable(true); - tvBody.setMovementMethod(new TouchHandler(message)); - - pbBody.setVisibility(View.GONE); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }; - - private class TouchHandler extends ArrowKeyMovementMethod { - private TupleMessageEx message; - - TouchHandler(TupleMessageEx message) { - this.message = message; - } - - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - boolean show_images = properties.getValue("images", message.id); - if (!show_images) { - ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class); - if (image.length > 0 && image[0].getSource() != null) { - HtmlHelper.AnnotatedSource a = new HtmlHelper.AnnotatedSource(image[0].getSource()); - Uri uri = Uri.parse(a.getSource()); - if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) { - onOpenLink(uri, null); - return true; - } - } - } - - URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); - if (link.length > 0) { - String url = link[0].getURL(); - Uri uri = Uri.parse(url); - if (uri.getScheme() == null) - uri = Uri.parse("https://" + url); - - int start = buffer.getSpanStart(link[0]); - int end = buffer.getSpanEnd(link[0]); - String title = (start < 0 || end < 0 || end <= start - ? null : buffer.subSequence(start, end).toString()); - if (url.equals(title)) - title = null; - - onOpenLink(uri, title); - return true; - } - - ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class); - if (image.length > 0) { - String source = image[0].getSource(); - if (source != null) { - onOpenImage(message.id, source); - return true; - } - } - - DynamicDrawableSpan[] ddss = buffer.getSpans(off, off, DynamicDrawableSpan.class); - if (ddss.length > 0) { - properties.setValue("quotes", message.id, true); - loadText(message); - } - } - - return super.onTouchEvent(widget, buffer, event); - } - } - - private void onOpenLink(final Uri uri, String title) { - Log.i("Opening uri=" + uri); - - if (BuildConfig.APPLICATION_ID.equals(uri.getHost()) && "/activate/".equals(uri.getPath())) { - try { - if (ActivityBilling.activatePro(context, uri)) - ToastEx.makeText(context, R.string.title_pro_valid, Toast.LENGTH_LONG).show(); - else - ToastEx.makeText(context, R.string.title_pro_invalid, Toast.LENGTH_LONG).show(); - } catch (NoSuchAlgorithmException ex) { - Log.e(ex); - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - } else { - if ("cid".equals(uri.getScheme())) - return; - - Bundle args = new Bundle(); - args.putParcelable("uri", uri); - args.putString("title", title); - - FragmentDialogLink fragment = new FragmentDialogLink(); - fragment.setArguments(args); - fragment.show(parentFragment.getFragmentManager(), "open:link"); - } + private void onActionArchive(TupleMessageEx message) { + properties.move(message.id, EntityFolder.ARCHIVE); } - private void onOpenImage(long id, String source) { - Log.i("Viewing image source=" + source); - + private void onActionMove(TupleMessageEx message, final boolean copy) { Bundle args = new Bundle(); - args.putLong("id", id); - args.putString("source", source); + 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); - FragmentDialogImage fragment = new FragmentDialogImage(); + FragmentDialogFolder fragment = new FragmentDialogFolder(); fragment.setArguments(args); - fragment.show(parentFragment.getFragmentManager(), "view:image"); - } - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - ActionData data = (ActionData) bnvActions.getTag(); - if (data == null) - return false; - - switch (item.getItemId()) { - case R.id.action_more: - onActionMore(data); - return true; - case R.id.action_delete: - onActionDelete(data); - return true; - case R.id.action_move: - if (EntityFolder.OUTBOX.equals(data.message.folderType)) - onActionMoveOutbox(data); - else - onActionMove(data.message, false); - return true; - case R.id.action_archive: - if (EntityFolder.JUNK.equals(data.message.folderType)) - onActionMoveJunk(data); - else - onActionArchive(data); - return true; - case R.id.action_reply: - onActionReplyMenu(data); - return true; - default: - return false; - } - } - - private void onMenuForward(final TupleMessageEx message) { - Intent forward = new Intent(context, ActivityCompose.class) - .putExtra("action", "forward") - .putExtra("reference", message.id); - context.startActivity(forward); - } - - private void onMenuEditAsNew(final TupleMessageEx message) { - Intent asnew = new Intent(context, ActivityCompose.class) - .putExtra("action", "editasnew") - .putExtra("reference", message.id); - context.startActivity(asnew); + fragment.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_MOVE); + fragment.show(parentFragment.getFragmentManager(), "message:move"); } - private void onMenuUnseen(final TupleMessageEx message) { + private void onActionMoveOutbox(TupleMessageEx message) { Bundle args = new Bundle(); args.putLong("id", message.id); @@ -2263,678 +2176,771 @@ public class AdapterMessage extends RecyclerView.Adapter 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(); } - return null; - } + if (message.identity != null) { + // Identity can be deleted + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel("send:" + message.identity, 1); + } - @Override - protected void onExecuted(Bundle args, Void ignored) { - properties.setValue("expanded", message.id, false); - notifyDataSetChanged(); + return null; } @Override protected void onException(Bundle args, Throwable ex) { Helper.unexpectedError(parentFragment.getFragmentManager(), ex); } - }.execute(context, owner, args, "message:unseen"); + }.execute(context, owner, args, "message:move:draft"); } - private void onMenuColoredStar(final TupleMessageEx message) { - int color = (message.color == null ? Color.TRANSPARENT : message.color); + private void onActionMoveJunk(TupleMessageEx message) { + properties.move(message.id, EntityFolder.INBOX); + } - Bundle args = new Bundle(); - args.putLong("id", message.id); + 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); - FragmentDialogColor fragment = new FragmentDialogColor(); - fragment.initialize(R.string.title_flag_color, color, args, context); - fragment.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_COLOR); - fragment.show(parentFragment.getFragmentManager(), "message:color"); + 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 onMenuDelete(final TupleMessageEx message) { - Bundle args = new Bundle(); - args.putLong("id", message.id); + private void onActionMore(TupleMessageEx message) { + boolean show_headers = properties.getValue("headers", message.id); - new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - long id = args.getLong("id"); + PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, ibMore); + popupMenu.inflate(R.menu.menu_message); - DB db = DB.getInstance(context); - db.message().deleteMessage(id); - return null; - } + popupMenu.getMenu().findItem(R.id.menu_forward).setEnabled(message.content); + popupMenu.getMenu().findItem(R.id.menu_editasnew).setEnabled(message.content); - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, args, "message:delete"); - } + 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); - private void onMenuJunk(final TupleMessageEx message) { - String who = MessageHelper.formatAddresses(message.from); + popupMenu.getMenu().findItem(R.id.menu_copy).setEnabled(message.uid != null && !message.folderReadOnly); + popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug); - Bundle aargs = new Bundle(); - aargs.putString("question", context.getString(R.string.title_ask_spam_who, who)); - aargs.putLong("id", message.id); + 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)); - FragmentDialogAsk ask = new FragmentDialogAsk(); - ask.setArguments(aargs); - ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_JUNK); - ask.show(parentFragment.getFragmentManager(), "message:junk"); - } + 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)); - private void onMenuDecrypt(TupleMessageEx message) { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); - lbm.sendBroadcast( - new Intent(FragmentMessages.ACTION_DECRYPT) - .putExtra("id", message.id)); - } + popupMenu.getMenu().findItem(R.id.menu_show_headers).setChecked(show_headers); + popupMenu.getMenu().findItem(R.id.menu_show_headers).setEnabled(message.uid != null); - private void onMenuResync(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"); - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - EntityMessage message = db.message().getMessage(id); - if (message == null || message.uid == null) - return null; - db.message().deleteMessage(id); + 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); - EntityOperation.sync(context, message.folder, true); + popupMenu.getMenu().findItem(R.id.menu_manage_keywords).setEnabled(message.uid != null && !message.folderReadOnly); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + popupMenu.getMenu().findItem(R.id.menu_decrypt).setEnabled( + message.content && message.to != null && message.to.length > 0); - return null; - } + popupMenu.getMenu().findItem(R.id.menu_resync).setEnabled(message.uid != null); + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + 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; + } } - }.execute(context, owner, args, "message:share"); + }); + popupMenu.show(); } - private void onMenuCreateRule(TupleMessageEx message) { - Intent rule = new Intent(ActivityView.ACTION_EDIT_RULE); - rule.putExtra("account", message.account); - rule.putExtra("folder", message.folder); - if (message.from != null && message.from.length > 0) - rule.putExtra("sender", ((InternetAddress) message.from[0]).getAddress()); - if (message.to != null && message.to.length > 0) - rule.putExtra("recipient", ((InternetAddress) message.to[0]).getAddress()); - if (!TextUtils.isEmpty(message.subject)) - rule.putExtra("subject", message.subject); + private void loadText(TupleMessageEx message) { + if (message.content) { + boolean show_images = properties.getValue("images", message.id); + boolean show_quotes = (properties.getValue("quotes", message.id) || !collapse_quotes); - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); - lbm.sendBroadcast(rule); + Bundle args = new Bundle(); + args.putSerializable("message", message); + args.putBoolean("show_images", show_images); + args.putBoolean("show_quotes", show_quotes); + args.putInt("zoom", zoom); + bodyTask.setCount(false).execute(context, owner, args, "message:body"); + } } - private void onMenuManageKeywords(TupleMessageEx message) { - Bundle args = new Bundle(); - args.putLong("id", message.id); - args.putStringArray("keywords", message.keywords); + private SimpleTask bodyTask = new SimpleTask() { + @Override + protected SpannableStringBuilder onExecute(final Context context, final Bundle args) throws IOException { + final TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + final boolean show_images = args.getBoolean("show_images"); + boolean show_quotes = args.getBoolean("show_quotes"); + int zoom = args.getInt("zoom"); - new SimpleTask() { - @Override - protected EntityFolder onExecute(Context context, Bundle args) { - long id = args.getLong("id"); + if (message == null || !message.content) + return null; - DB db = DB.getInstance(context); - EntityMessage message = db.message().getMessage(id); - if (message == null) - return null; + File file = message.getFile(context); + if (!file.exists()) + return null; - return db.folder().getFolder(message.folder); + String body = Helper.readText(file); + + if (!show_quotes) { + Document document = Jsoup.parse(body); + for (Element quote : document.select("blockquote")) + quote.html("…"); + body = document.html(); } - @Override - protected void onExecuted(final Bundle args, EntityFolder folder) { - if (folder == null) - return; + String html = HtmlHelper.sanitize(context, body, show_images); + if (debug) + html += "
" + Html.escapeHtml(html) + "
"; - args.putStringArray("fkeywords", folder.keywords); + Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() { + @Override + public Drawable getDrawable(String source) { + return HtmlHelper.decodeImage(context, message.id, source, show_images, tvBody); + } + }, null); - FragmentKeywordManage fragment = new FragmentKeywordManage(); - fragment.setArguments(args); - fragment.show(parentFragment.getFragmentManager(), "keyword:manage"); + SpannableStringBuilder builder = new SpannableStringBuilder(spanned); + QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class); + for (QuoteSpan quoteSpan : quoteSpans) { + builder.setSpan( + new StyledQuoteSpan(colorPrimary), + builder.getSpanStart(quoteSpan), + builder.getSpanEnd(quoteSpan), + builder.getSpanFlags(quoteSpan)); + builder.removeSpan(quoteSpan); } - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, args, "message:keywords"); - } + if (!show_quotes) { + final int px = Helper.dp2pixels(context, 24 + (zoom) * 8); - private void onMenuShare(TupleMessageEx message) { - Bundle args = new Bundle(); - args.putLong("id", message.id); + StyledQuoteSpan[] squotes = builder.getSpans(0, builder.length(), StyledQuoteSpan.class); + for (StyledQuoteSpan squote : squotes) + builder.setSpan(new DynamicDrawableSpan() { + @Override + public Drawable getDrawable() { + Drawable d = context.getDrawable(R.drawable.baseline_format_quote_24); + d.setTint(colorAccent); + d.setBounds(0, 0, px, px); + return d; + } + }, + builder.getSpanStart(squote), + builder.getSpanEnd(squote), + builder.getSpanFlags(squote)); + } - new SimpleTask() { - @Override - protected String[] onExecute(Context context, Bundle args) throws Throwable { - long id = args.getLong("id"); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean inline = prefs.getBoolean("inline_images", false); - DB db = DB.getInstance(context); - EntityMessage message = db.message().getMessage(id); - if (message == null || !message.content) - return null; + boolean has_images; + ImageSpan[] spans = builder.getSpans(0, body.length(), ImageSpan.class); + if (inline) { + has_images = false; + for (ImageSpan span : spans) { + String source = span.getSource(); + if (source == null || !source.startsWith("cid:")) { + has_images = true; + break; + } + } + } else + has_images = spans.length > 0; - File file = message.getFile(context); - if (!file.exists()) - return null; + args.putBoolean("has_images", has_images); - String from = null; - if (message.from != null && message.from.length > 0) - from = ((InternetAddress) message.from[0]).getAddress(); + return builder; + } - String html = HtmlHelper.getText(Helper.readText(file)); + @Override + protected void onExecuted(Bundle args, SpannableStringBuilder body) { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + properties.setBody(message.id, body); - return new String[]{from, message.subject, html}; - } + TupleMessageEx amessage = getMessage(); + if (amessage == null || !amessage.id.equals(message.id)) + return; - @Override - protected void onExecuted(Bundle args, String[] text) { - if (text == null) - return; + boolean show_expanded = properties.getValue("expanded", message.id); + if (!show_expanded) + return; - Intent share = new Intent(); - share.setAction(Intent.ACTION_SEND); - share.setType("text/plain"); - if (!TextUtils.isEmpty(text[0])) - share.putExtra(Intent.EXTRA_EMAIL, new String[]{text[0]}); - if (!TextUtils.isEmpty(text[1])) - share.putExtra(Intent.EXTRA_SUBJECT, text[1]); - if (!TextUtils.isEmpty(text[2])) - share.putExtra(Intent.EXTRA_TEXT, text[2]); + boolean has_images = args.getBoolean("has_images"); + boolean show_images = properties.getValue("images", message.id); - PackageManager pm = context.getPackageManager(); - if (share.resolveActivity(pm) == null) - Snackbar.make(parentFragment.getView(), - R.string.title_no_viewer, Snackbar.LENGTH_LONG).show(); - else - context.startActivity(share); - } + ibFull.setVisibility(hasWebView ? View.VISIBLE : View.GONE); + ibImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE); - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, args, "message:share"); - } + tvBody.setText(body); + tvBody.setTextIsSelectable(false); + tvBody.setTextIsSelectable(true); + tvBody.setMovementMethod(new TouchHandler(message)); - private void onMenuPrint(TupleMessageEx message) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (prefs.getBoolean("print_html_confirmed", false)) { - Bundle args = new Bundle(); - args.putLong("id", message.id); - Intent data = new Intent(); - data.putExtra("args", args); - parentFragment.onActivityResult(FragmentMessages.REQUEST_PRINT, RESULT_OK, data); - return; + pbBody.setVisibility(View.GONE); } - Bundle aargs = new Bundle(); - aargs.putString("question", context.getString(R.string.title_ask_show_html)); - aargs.putString("notagain", "print_html_confirmed"); - aargs.putLong("id", message.id); - - FragmentDialogAsk ask = new FragmentDialogAsk(); - ask.setArguments(aargs); - ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_PRINT); - ask.show(parentFragment.getFragmentManager(), "message:print"); - } + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }; - private void onMenuShowHeaders(TupleMessageEx message) { - boolean show_headers = !properties.getValue("headers", message.id); - properties.setValue("headers", message.id, show_headers); - if (show_headers && message.headers == null) { - grpHeaders.setVisibility(View.VISIBLE); - if (suitable) - pbHeaders.setVisibility(View.VISIBLE); - else - tvNoInternetHeaders.setVisibility(View.VISIBLE); + private class TouchHandler extends ArrowKeyMovementMethod { + private TupleMessageEx message; - Bundle args = new Bundle(); - args.putLong("id", message.id); + TouchHandler(TupleMessageEx message) { + this.message = message; + } - new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - Long id = args.getLong("id"); + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + int x = (int) event.getX(); + int y = (int) event.getY(); - DB db = DB.getInstance(context); - try { - db.beginTransaction(); + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); - EntityMessage message = db.message().getMessage(id); - if (message == null) - return null; + x += widget.getScrollX(); + y += widget.getScrollY(); - EntityOperation.queue(context, message, EntityOperation.HEADERS); + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); + boolean show_images = properties.getValue("images", message.id); + if (!show_images) { + ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class); + if (image.length > 0 && image[0].getSource() != null) { + HtmlHelper.AnnotatedSource a = new HtmlHelper.AnnotatedSource(image[0].getSource()); + Uri uri = Uri.parse(a.getSource()); + if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) { + onOpenLink(uri, null); + return true; + } } - return null; - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); } - }.execute(context, owner, args, "message:headers"); - } else - notifyDataSetChanged(); - } - - private void onMenuRaw(TupleMessageEx message) { - if (message.raw == null) { - Bundle args = new Bundle(); - args.putLong("id", message.id); - - new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - Long id = args.getLong("id"); - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - EntityMessage message = db.message().getMessage(id); - if (message == null) - return null; + URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); + if (link.length > 0) { + String url = link[0].getURL(); + Uri uri = Uri.parse(url); + if (uri.getScheme() == null) + uri = Uri.parse("https://" + url); - EntityOperation.queue(context, message, EntityOperation.RAW); + int start = buffer.getSpanStart(link[0]); + int end = buffer.getSpanEnd(link[0]); + String title = (start < 0 || end < 0 || end <= start + ? null : buffer.subSequence(start, end).toString()); + if (url.equals(title)) + title = null; - db.message().setMessageRaw(message.id, false); + onOpenLink(uri, title); + return true; + } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); + ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class); + if (image.length > 0) { + String source = image[0].getSource(); + if (source != null) { + onOpenImage(message.id, source); + return true; } - return null; } - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + DynamicDrawableSpan[] ddss = buffer.getSpans(off, off, DynamicDrawableSpan.class); + if (ddss.length > 0) { + properties.setValue("quotes", message.id, true); + loadText(message); } - }.execute(context, owner, args, "message:raw"); + } + + return super.onTouchEvent(widget, buffer, event); + } + } + + private void onOpenLink(final Uri uri, String title) { + Log.i("Opening uri=" + uri); + + if (BuildConfig.APPLICATION_ID.equals(uri.getHost()) && "/activate/".equals(uri.getPath())) { + try { + if (ActivityBilling.activatePro(context, uri)) + ToastEx.makeText(context, R.string.title_pro_valid, Toast.LENGTH_LONG).show(); + else + ToastEx.makeText(context, R.string.title_pro_invalid, Toast.LENGTH_LONG).show(); + } catch (NoSuchAlgorithmException ex) { + Log.e(ex); + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } } else { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); - lbm.sendBroadcast( - new Intent(FragmentMessages.ACTION_STORE_RAW) - .putExtra("id", message.id)); + if ("cid".equals(uri.getScheme())) + return; + + Bundle args = new Bundle(); + args.putParcelable("uri", uri); + args.putString("title", title); + + FragmentDialogLink fragment = new FragmentDialogLink(); + fragment.setArguments(args); + fragment.show(parentFragment.getFragmentManager(), "open:link"); } } - private void onActionMore(final ActionData data) { - boolean show_headers = properties.getValue("headers", data.message.id); + private void onOpenImage(long id, String source) { + Log.i("Viewing image source=" + source); - View anchor = bnvActions.findViewById(R.id.action_more); - PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor); - popupMenu.inflate(R.menu.menu_message); + Bundle args = new Bundle(); + args.putLong("id", id); + args.putString("source", source); - popupMenu.getMenu().findItem(R.id.menu_forward).setEnabled(data.message.content); - popupMenu.getMenu().findItem(R.id.menu_editasnew).setEnabled(data.message.content); + FragmentDialogImage fragment = new FragmentDialogImage(); + fragment.setArguments(args); + fragment.show(parentFragment.getFragmentManager(), "view:image"); + } - popupMenu.getMenu().findItem(R.id.menu_unseen).setEnabled(data.message.uid != null && !data.message.folderReadOnly); - popupMenu.getMenu().findItem(R.id.menu_flag_color).setEnabled(data.message.uid != null && !data.message.folderReadOnly); + private void onMenuForward(final TupleMessageEx message) { + Intent forward = new Intent(context, ActivityCompose.class) + .putExtra("action", "forward") + .putExtra("reference", message.id); + context.startActivity(forward); + } - popupMenu.getMenu().findItem(R.id.menu_copy).setEnabled(data.message.uid != null && !data.message.folderReadOnly); - popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug); + private void onMenuEditAsNew(final TupleMessageEx message) { + Intent asnew = new Intent(context, ActivityCompose.class) + .putExtra("action", "editasnew") + .putExtra("reference", message.id); + context.startActivity(asnew); + } - popupMenu.getMenu().findItem(R.id.menu_junk).setEnabled(data.message.uid != null && !data.message.folderReadOnly); - popupMenu.getMenu().findItem(R.id.menu_junk).setVisible( - data.hasJunk && !EntityFolder.JUNK.equals(data.message.folderType)); + private void onMenuUnseen(final TupleMessageEx message) { + Bundle args = new Bundle(); + args.putLong("id", message.id); - popupMenu.getMenu().findItem(R.id.menu_share).setEnabled(data.message.content); - popupMenu.getMenu().findItem(R.id.menu_print).setEnabled(hasWebView && data.message.content); - popupMenu.getMenu().findItem(R.id.menu_print).setVisible(Helper.canPrint(context)); + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + long id = args.getLong("id"); - popupMenu.getMenu().findItem(R.id.menu_show_headers).setChecked(show_headers); - popupMenu.getMenu().findItem(R.id.menu_show_headers).setEnabled(data.message.uid != null); + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - popupMenu.getMenu().findItem(R.id.menu_raw).setEnabled( - data.message.uid != null && (data.message.raw == null || data.message.raw)); - popupMenu.getMenu().findItem(R.id.menu_raw).setTitle( - data.message.raw == null || !data.message.raw ? R.string.title_raw_download : R.string.title_raw_save); + EntityMessage message = db.message().getMessage(id); + if (message == null) + return null; - popupMenu.getMenu().findItem(R.id.menu_manage_keywords).setEnabled(data.message.uid != null && !data.message.folderReadOnly); + EntityOperation.queue(context, message, EntityOperation.SEEN, false); - popupMenu.getMenu().findItem(R.id.menu_decrypt).setEnabled( - data.message.content && data.message.to != null && data.message.to.length > 0); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - popupMenu.getMenu().findItem(R.id.menu_resync).setEnabled(data.message.uid != null); + return 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; - } + protected void onExecuted(Bundle args, Void ignored) { + properties.setValue("expanded", message.id, false); + notifyDataSetChanged(); } - }); - popupMenu.show(); + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:unseen"); } - 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); + private void onMenuColoredStar(final TupleMessageEx message) { + int color = (message.color == null ? Color.TRANSPARENT : message.color); - 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); + Bundle args = new Bundle(); + args.putLong("id", message.id); + + FragmentDialogColor fragment = new FragmentDialogColor(); + fragment.initialize(R.string.title_flag_color, color, args, context); + fragment.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_COLOR); + fragment.show(parentFragment.getFragmentManager(), "message:color"); } - private void onActionMove(final TupleMessageEx message, final boolean copy) { + private void onMenuDelete(final TupleMessageEx message) { 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); + args.putLong("id", message.id); - FragmentDialogFolder fragment = new FragmentDialogFolder(); - fragment.setArguments(args); - fragment.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_MOVE); - fragment.show(parentFragment.getFragmentManager(), "message:move"); + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + long id = args.getLong("id"); + + DB db = DB.getInstance(context); + db.message().deleteMessage(id); + return null; + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:delete"); + } + + private void onMenuJunk(final TupleMessageEx message) { + String who = MessageHelper.formatAddresses(message.from); + + Bundle aargs = new Bundle(); + aargs.putString("question", context.getString(R.string.title_ask_spam_who, who)); + aargs.putLong("id", message.id); + + FragmentDialogAsk ask = new FragmentDialogAsk(); + ask.setArguments(aargs); + ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_JUNK); + ask.show(parentFragment.getFragmentManager(), "message:junk"); } - private void onActionMoveOutbox(ActionData data) { + private void onMenuResync(TupleMessageEx message) { Bundle args = new Bundle(); - args.putLong("id", data.message.id); + 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) + EntityMessage message = db.message().getMessage(id); + if (message == null || message.uid == null) return null; + db.message().deleteMessage(id); - db.folder().setFolderError(message.folder, null); + EntityOperation.sync(context, message.folder, true); - File source = message.getFile(context); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - // 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); + return null; + } - File target = message.getFile(context); - source.renameTo(target); + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:share"); + } - List attachments = db.attachment().getAttachments(id); - for (EntityAttachment attachment : attachments) - db.attachment().setMessage(attachment.id, message.id); + private void onMenuCreateRule(TupleMessageEx message) { + Intent rule = new Intent(ActivityView.ACTION_EDIT_RULE); + rule.putExtra("account", message.account); + rule.putExtra("folder", message.folder); + if (message.from != null && message.from.length > 0) + rule.putExtra("sender", ((InternetAddress) message.from[0]).getAddress()); + if (message.to != null && message.to.length > 0) + rule.putExtra("recipient", ((InternetAddress) message.to[0]).getAddress()); + if (!TextUtils.isEmpty(message.subject)) + rule.putExtra("subject", message.subject); - EntityOperation.queue(context, message, EntityOperation.ADD); + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); + lbm.sendBroadcast(rule); + } + + private void onMenuManageKeywords(TupleMessageEx message) { + Bundle args = new Bundle(); + args.putLong("id", message.id); + args.putStringArray("keywords", message.keywords); + + new SimpleTask() { + @Override + protected EntityFolder onExecute(Context context, Bundle args) { + long id = args.getLong("id"); + + DB db = DB.getInstance(context); + EntityMessage message = db.message().getMessage(id); + if (message == null) + return null; - // Delete from outbox - db.message().deleteMessage(id); // will delete operation too + return db.folder().getFolder(message.folder); + } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + @Override + protected void onExecuted(final Bundle args, EntityFolder folder) { + if (folder == null) + return; - if (message.identity != null) { - // Identity can be deleted - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel("send:" + message.identity, 1); - } + args.putStringArray("fkeywords", folder.keywords); - return null; + FragmentKeywordManage fragment = new FragmentKeywordManage(); + fragment.setArguments(args); + fragment.show(parentFragment.getFragmentManager(), "keyword:manage"); } @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); + }.execute(context, owner, args, "message:keywords"); } - private void onActionReplyMenu(final ActionData data) { + private void onMenuShare(TupleMessageEx message) { Bundle args = new Bundle(); - args.putSerializable("message", data.message); + args.putLong("id", message.id); - new SimpleTask() { + new SimpleTask() { @Override - protected EntityIdentity onExecute(Context context, Bundle args) { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - if (message.identity == null) - return null; + protected String[] onExecute(Context context, Bundle args) throws Throwable { + long id = args.getLong("id"); DB db = DB.getInstance(context); - return db.identity().getIdentity(message.identity); - } + EntityMessage message = db.message().getMessage(id); + if (message == null || !message.content) + return null; - @Override - protected void onExecuted(Bundle args, EntityIdentity identity) { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + File file = message.getFile(context); + if (!file.exists()) + return null; - TupleMessageEx amessage = getMessage(); - if (amessage == null || !amessage.id.equals(message.id)) - return; + String from = null; + if (message.from != null && message.from.length > 0) + from = ((InternetAddress) message.from[0]).getAddress(); - String via = (identity == null ? null : MessageHelper.canonicalAddress(identity.email)); - Address[] recipients = data.message.getAllRecipients(via); + String html = HtmlHelper.getText(Helper.readText(file)); - if (recipients.length == 0 && - data.message.list_post == null && - data.message.receipt_to == null && - (answers == 0 && ActivityBilling.isPro(context))) { - onMenuReply(data, "reply"); + return new String[]{from, message.subject, html}; + } + + @Override + protected void onExecuted(Bundle args, String[] text) { + if (text == null) 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)); + Intent share = new Intent(); + share.setAction(Intent.ACTION_SEND); + share.setType("text/plain"); + if (!TextUtils.isEmpty(text[0])) + share.putExtra(Intent.EXTRA_EMAIL, new String[]{text[0]}); + if (!TextUtils.isEmpty(text[1])) + share.putExtra(Intent.EXTRA_SUBJECT, text[1]); + if (!TextUtils.isEmpty(text[2])) + share.putExtra(Intent.EXTRA_TEXT, text[2]); - 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(); + PackageManager pm = context.getPackageManager(); + if (share.resolveActivity(pm) == null) + Snackbar.make(parentFragment.getView(), + R.string.title_no_viewer, Snackbar.LENGTH_LONG).show(); + else + context.startActivity(share); } @Override protected void onException(Bundle args, Throwable ex) { Helper.unexpectedError(parentFragment.getFragmentManager(), ex); } - }.execute(context, owner, args, "message:reply"); + }.execute(context, owner, args, "message:share"); } - 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 onMenuPrint(TupleMessageEx message) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + if (prefs.getBoolean("print_html_confirmed", false)) { + Bundle args = new Bundle(); + args.putLong("id", message.id); + Intent data = new Intent(); + data.putExtra("args", args); + parentFragment.onActivityResult(FragmentMessages.REQUEST_PRINT, RESULT_OK, data); + return; + } + + Bundle aargs = new Bundle(); + aargs.putString("question", context.getString(R.string.title_ask_show_html)); + aargs.putString("notagain", "print_html_confirmed"); + aargs.putLong("id", message.id); + + FragmentDialogAsk ask = new FragmentDialogAsk(); + ask.setArguments(aargs); + ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_PRINT); + ask.show(parentFragment.getFragmentManager(), "message:print"); } - private void onMenuAnswer(final ActionData data) { - new SimpleTask>() { - @Override - protected List onExecute(Context context, Bundle args) { - return DB.getInstance(context).answer().getAnswers(false); - } + private void onMenuShowHeaders(TupleMessageEx message) { + boolean show_headers = !properties.getValue("headers", message.id); + properties.setValue("headers", message.id, show_headers); + if (show_headers && message.headers == null) { + grpHeaders.setVisibility(View.VISIBLE); + if (suitable) + pbHeaders.setVisibility(View.VISIBLE); + else + tvNoInternetHeaders.setVisibility(View.VISIBLE); - @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); + Bundle args = new Bundle(); + args.putLong("id", message.id); - int order = 0; - for (EntityAnswer answer : answers) - popupMenu.getMenu().add(Menu.NONE, answer.id.intValue(), order++, answer.name); + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + Long id = args.getLong("id"); - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem target) { - if (!ActivityBilling.isPro(context)) { - context.startActivity(new Intent(context, ActivityBilling.class)); - return true; - } + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - context.startActivity(new Intent(context, ActivityCompose.class) - .putExtra("action", "reply") - .putExtra("reference", data.message.id) - .putExtra("answer", (long) target.getItemId())); - return true; - } - }); + EntityMessage message = db.message().getMessage(id); + if (message == null) + return null; - popupMenu.show(); + EntityOperation.queue(context, message, EntityOperation.HEADERS); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return null; } - } - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }.execute(context, owner, new Bundle(), "message:answer"); + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:headers"); + } else + notifyDataSetChanged(); + } + + private void onMenuRaw(TupleMessageEx message) { + if (message.raw == null) { + Bundle args = new Bundle(); + args.putLong("id", message.id); + + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + Long id = args.getLong("id"); + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + EntityMessage message = db.message().getMessage(id); + if (message == null) + return null; + + EntityOperation.queue(context, message, EntityOperation.RAW); + + db.message().setMessageRaw(message.id, false); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return null; + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:raw"); + } else { + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); + lbm.sendBroadcast( + new Intent(FragmentMessages.ACTION_STORE_RAW) + .putExtra("id", message.id)); + } } ItemDetailsLookup.ItemDetails getItemDetails(@NonNull MotionEvent motionEvent) { @@ -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" />