() {
@Override
protected SpannableStringBuilder onExecute(Context context, final Bundle args) {
DB db = DB.getInstance(context);
TupleMessageEx message = (TupleMessageEx) args.getSerializable("message");
String body;
try {
body = Helper.readText(message.getFile(context));
} catch (IOException ex) {
Log.e(ex);
db.message().setMessageContent(message.id, false, null, null);
db.message().setMessageSize(message.id, null);
return null;
}
Spanned html = decodeHtml(context, message, body);
SpannableStringBuilder builder = new SpannableStringBuilder(html);
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);
}
args.putBoolean("has_quotes", builder.getSpans(0, body.length(), StyledQuoteSpan.class).length > 0);
args.putBoolean("has_images", builder.getSpans(0, body.length(), ImageSpan.class).length > 0);
return builder;
}
@Override
protected void onExecuted(Bundle args, SpannableStringBuilder body) {
TupleMessageEx message = (TupleMessageEx) args.getSerializable("message");
properties.setBody(message.id, body);
TupleMessageEx amessage = getMessage();
if (amessage == null || !amessage.id.equals(message.id))
return;
boolean has_quotes = args.getBoolean("has_quotes");
boolean has_images = args.getBoolean("has_images");
boolean show_expanded = properties.getValue("expanded", message.id);
boolean show_quotes = properties.getValue("quotes", message.id);
boolean show_images = properties.getValue("images", message.id);
btnHtml.setVisibility(hasWebView && show_expanded ? View.VISIBLE : View.GONE);
ibQuotes.setVisibility(has_quotes && show_expanded && !show_quotes ? View.VISIBLE : View.GONE);
ibImages.setVisibility(has_images && show_expanded && !show_images ? View.VISIBLE : View.GONE);
tvBody.setText(body);
tvBody.setTextIsSelectable(false);
tvBody.setTextIsSelectable(true);
tvBody.setMovementMethod(new UrlHandler());
tvBody.setVisibility(show_expanded ? View.VISIBLE : View.GONE);
pbBody.setVisibility(View.GONE);
rvImage.setVisibility(adapterImage.getItemCount() > 0 ? View.VISIBLE : View.GONE);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
};
private Spanned decodeHtml(final Context context, final EntityMessage message, String body) {
final boolean show_quotes = properties.getValue("quotes", message.id);
final boolean show_images = properties.getValue("images", message.id);
String html = HtmlHelper.sanitize(context, body, show_quotes);
if (debug)
html += "" + Html.escapeHtml(html) + "
";
return HtmlHelper.fromHtml(html, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
Drawable image = HtmlHelper.decodeImage(source, context, message.id, show_images);
float width = context.getResources().getDisplayMetrics().widthPixels -
Helper.dp2pixels(context, 12); // margins
if (image.getIntrinsicWidth() > width) {
float scale = width / image.getIntrinsicWidth();
image.setBounds(0, 0,
Math.round(image.getIntrinsicWidth() * scale),
Math.round(image.getIntrinsicHeight() * scale));
}
return image;
}
}, null);
}
private class UrlHandler extends ArrowKeyMovementMethod {
@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);
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);
onOpenLink(uri);
return true;
}
}
return super.onTouchEvent(widget, buffer, event);
}
}
private void onOpenLink(final Uri uri) {
if (BuildConfig.APPLICATION_ID.equals(uri.getHost()) && "/activate/".equals(uri.getPath())) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_ACTIVATE_PRO)
.putExtra("uri", uri));
} else {
if ("cid".equals(uri.getScheme()))
return;
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
View view = LayoutInflater.from(context).inflate(R.layout.dialog_link, null);
final EditText etLink = view.findViewById(R.id.etLink);
final CheckBox cbOrganization = view.findViewById(R.id.cbOrganization);
TextView tvInsecure = view.findViewById(R.id.tvInsecure);
cbOrganization.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("show_organization", isChecked).apply();
if (isChecked) {
Bundle args = new Bundle();
args.putParcelable("uri", uri);
new SimpleTask() {
@Override
protected void onPreExecute(Bundle args) {
cbOrganization.setText("…");
}
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
Uri uri = args.getParcelable("uri");
String host = uri.getHost();
return (TextUtils.isEmpty(host) ? null : Helper.getOrganization(host));
}
@Override
protected void onExecuted(Bundle args, String organization) {
cbOrganization.setText(organization == null ? "?" : organization);
}
@Override
protected void onException(Bundle args, Throwable ex) {
cbOrganization.setText(ex.getMessage());
}
}.execute(context, owner, args, "link:domain");
} else
cbOrganization.setText(R.string.title_show_organization);
}
});
etLink.setText(uri.toString());
cbOrganization.setChecked(prefs.getBoolean("show_organization", true));
tvInsecure.setVisibility("http".equals(uri.getScheme()) ? View.VISIBLE : View.GONE);
new DialogBuilderLifecycle(context, owner)
.setView(view)
.setPositiveButton(R.string.title_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(etLink.getText().toString());
Helper.view(context, owner, uri, false);
}
})
.setNeutralButton(R.string.title_browse, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(etLink.getText().toString());
Helper.view(context, owner, uri, true);
}
})
.setNegativeButton(R.string.title_no, null)
.show();
}
}
private class ActionData {
boolean hasJunk;
boolean delete;
TupleMessageEx message;
}
@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:
onActionMove(data);
return true;
case R.id.action_archive:
onActionArchive(data);
return true;
case R.id.action_reply:
onActionReplyMenu(data);
return true;
default:
return false;
}
}
private void onMenuForward(final ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask() {
@Override
protected Boolean onExecute(Context context, Bundle args) {
long id = args.getLong("id");
List attachments = DB.getInstance(context).attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (!attachment.available)
return false;
return true;
}
@Override
protected void onExecuted(Bundle args, Boolean available) {
final Intent forward = new Intent(context, ActivityCompose.class)
.putExtra("action", "forward")
.putExtra("reference", data.message.id);
if (available)
context.startActivity(forward);
else
new DialogBuilderLifecycle(context, owner)
.setMessage(R.string.title_attachment_unavailable)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
context.startActivity(forward);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:forward");
}
private void onMenuUnseen(final 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");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityOperation.queue(context, db, message, EntityOperation.SEEN, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onExecuted(Bundle args, Void ignored) {
properties.setValue("expanded", data.message.id, false);
notifyDataSetChanged();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:unseen");
}
private void onMenuSnooze(final ActionData data) {
DialogDuration.show(context, owner, R.string.title_snooze,
new DialogDuration.IDialogDuration() {
@Override
public void onDurationSelected(long duration, long time) {
if (!Helper.isPro(context)) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO));
return;
}
Bundle args = new Bundle();
args.putLong("id", data.message.id);
args.putLong("wakeup", duration == 0 ? -1 : time);
new SimpleTask() {
@Override
protected Long onExecute(Context context, Bundle args) {
long id = args.getLong("id");
Long wakeup = args.getLong("wakeup");
if (wakeup < 0)
wakeup = null;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message != null) {
List messages = db.message().getMessageByThread(
message.account, message.thread, threading ? null : id, message.folder);
for (EntityMessage threaded : messages) {
db.message().setMessageSnoozed(threaded.id, wakeup);
EntityMessage.snooze(context, threaded.id, wakeup);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return wakeup;
}
@Override
protected void onExecuted(Bundle args, Long wakeup) {
if (wakeup != null)
properties.finish();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:snooze");
}
@Override
public void onDismiss() {
}
});
}
private void onMenuCopy(final ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask>() {
@Override
protected List onExecute(Context context, Bundle args) {
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(args.getLong("id"));
if (message == null)
return null;
List folders = db.folder().getFolders(message.account);
if (folders == null)
return null;
EntityFolder.sort(context, folders, true);
return folders;
}
@Override
protected void onExecuted(final Bundle args, List folders) {
if (folders == null)
return;
View anchor = bnvActions.findViewById(R.id.action_more);
PopupMenu popupMenu = new PopupMenu(context, anchor);
int order = 0;
for (EntityFolder folder : folders)
popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, folder.getDisplayName(context));
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem target) {
args.putLong("target", target.getItemId());
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
long target = args.getLong("target");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityOperation.queue(context, db, message, EntityOperation.COPY, target);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:copy");
return true;
}
});
popupMenu.show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:copy:list");
}
private void onMenuDelete(final 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");
DB db = DB.getInstance(context);
db.message().deleteMessage(id);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:delete");
}
private void onMenuJunk(final ActionData data) {
String who = MessageHelper.formatAddresses(data.message.from);
new DialogBuilderLifecycle(context, owner)
.setMessage(context.getString(R.string.title_ask_spam_who, who))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
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");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityFolder junk = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
EntityOperation.queue(context, db, message, EntityOperation.MOVE, junk.id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:spam");
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void onMenuDecrypt(ActionData data) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_DECRYPT)
.putExtra("id", data.message.id));
}
private void onMenuResync(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");
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);
EntityOperation.sync(context, message.folder, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:share");
}
private void onMenuCreateRule(ActionData data) {
Intent rule = new Intent(ActivityView.ACTION_EDIT_RULE);
rule.putExtra("account", data.message.account);
rule.putExtra("folder", data.message.folder);
if (data.message.from != null && data.message.from.length > 0)
rule.putExtra("sender", ((InternetAddress) data.message.from[0]).getAddress());
if (!TextUtils.isEmpty(data.message.subject))
rule.putExtra("subject", data.message.subject);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(rule);
}
private void onMenuManageKeywords(ActionData data) {
if (!Helper.isPro(context)) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO));
return;
}
Bundle args = new Bundle();
args.putSerializable("message", data.message);
new SimpleTask() {
@Override
protected EntityFolder onExecute(Context context, Bundle args) {
EntityMessage message = (EntityMessage) args.getSerializable("message");
return DB.getInstance(context).folder().getFolder(message.folder);
}
@Override
protected void onExecuted(final Bundle args, EntityFolder folder) {
EntityMessage message = (EntityMessage) args.getSerializable("message");
List keywords = Arrays.asList(message.keywords);
final List items = new ArrayList<>(keywords);
for (String keyword : folder.keywords)
if (!items.contains(keyword))
items.add(keyword);
Collections.sort(items);
final boolean selected[] = new boolean[items.size()];
final boolean dirty[] = new boolean[items.size()];
for (int i = 0; i < selected.length; i++) {
selected[i] = keywords.contains(items.get(i));
dirty[i] = false;
}
new DialogBuilderLifecycle(context, owner)
.setTitle(R.string.title_manage_keywords)
.setMultiChoiceItems(items.toArray(new String[0]), selected, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
dirty[which] = true;
}
})
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
args.putStringArray("keywords", items.toArray(new String[0]));
args.putBooleanArray("selected", selected);
args.putBooleanArray("dirty", dirty);
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) {
EntityMessage message = (EntityMessage) args.getSerializable("message");
String[] keywords = args.getStringArray("keywords");
boolean[] selected = args.getBooleanArray("selected");
boolean[] dirty = args.getBooleanArray("dirty");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
for (int i = 0; i < selected.length; i++)
if (dirty[i])
EntityOperation.queue(context, db, message, EntityOperation.KEYWORD, keywords[i], selected[i]);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:keywords:managa");
}
})
.setNeutralButton(R.string.title_add, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
View view = LayoutInflater.from(context).inflate(R.layout.dialog_keyword, null);
final EditText etKeyword = view.findViewById(R.id.etKeyword);
etKeyword.setText(null);
new DialogBuilderLifecycle(context, owner)
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String keyword = Helper.sanitizeKeyword(etKeyword.getText().toString());
if (!TextUtils.isEmpty(keyword)) {
args.putString("keyword", keyword);
new SimpleTask() {
@Override
protected Void onExecute(Context context, Bundle args) {
EntityMessage message = (EntityMessage) args.getSerializable("message");
String keyword = args.getString("keyword");
DB db = DB.getInstance(context);
EntityOperation.queue(context, db, message, EntityOperation.KEYWORD, keyword, true);
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:keyword:add");
}
}
}).show();
}
})
.show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:keywords");
}
private void onMenuShare(ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask() {
@Override
protected String[] onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
String from = null;
if (message.from != null && message.from.length > 0)
from = ((InternetAddress) message.from[0]).getAddress();
return new String[]{
from,
message.subject,
HtmlHelper.getText(Helper.readText(message.getFile(context)))
};
}
@Override
protected void onExecuted(Bundle args, String[] text) {
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]);
share.putExtra(Intent.EXTRA_TEXT, text[2]);
PackageManager pm = context.getPackageManager();
if (share.resolveActivity(pm) == null)
Toast.makeText(context, R.string.title_no_viewer, Toast.LENGTH_LONG).show();
else
context.startActivity(share);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:share");
}
private void onMenuPrint(final ActionData data) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("print_html_confirmed", false)) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_PRINT)
.putExtra("id", data.message.id));
return;
}
final View dview = LayoutInflater.from(context).inflate(R.layout.dialog_ask_again, null);
final TextView tvMessage = dview.findViewById(R.id.tvMessage);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
tvMessage.setText(context.getText(R.string.title_ask_show_html));
new DialogBuilderLifecycle(context, owner)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (cbNotAgain.isChecked())
prefs.edit().putBoolean("print_html_confirmed", true).apply();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_PRINT)
.putExtra("id", data.message.id));
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void onMenuShowHeaders(ActionData data) {
boolean show_headers = !properties.getValue("headers", data.message.id);
properties.setValue("headers", data.message.id, show_headers);
if (show_headers && data.message.headers == null) {
grpHeaders.setVisibility(View.VISIBLE);
if (suitable)
pbHeaders.setVisibility(View.VISIBLE);
else
tvNoInternetHeaders.setVisibility(View.VISIBLE);
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");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityOperation.queue(context, db, message, EntityOperation.HEADERS);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:headers");
} else
notifyDataSetChanged();
}
private void onMenuRaw(ActionData data) {
if (data.message.raw == null) {
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");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityOperation.queue(context, db, 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(context, owner, ex);
}
}.execute(context, owner, args, "message:raw");
} else {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_STORE_RAW)
.putExtra("id", data.message.id));
}
}
private void onActionMore(final ActionData data) {
boolean show_headers = properties.getValue("headers", data.message.id);
View anchor = bnvActions.findViewById(R.id.action_more);
PopupMenu popupMenu = new PopupMenu(context, anchor);
popupMenu.inflate(R.menu.menu_message);
popupMenu.getMenu().findItem(R.id.menu_forward).setEnabled(data.message.content);
popupMenu.getMenu().findItem(R.id.menu_unseen).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_copy).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug);
popupMenu.getMenu().findItem(R.id.menu_junk).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_junk).setVisible(
data.hasJunk && !EntityFolder.JUNK.equals(data.message.folderType));
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));
popupMenu.getMenu().findItem(R.id.menu_show_headers).setChecked(show_headers);
popupMenu.getMenu().findItem(R.id.menu_show_headers).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_raw).setVisible(show_headers);
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);
popupMenu.getMenu().findItem(R.id.menu_manage_keywords).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_decrypt).setEnabled(
data.message.content && data.message.to != null && data.message.to.length > 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);
return true;
case R.id.menu_unseen:
onMenuUnseen(data);
return true;
case R.id.menu_snooze:
onMenuSnooze(data);
return true;
case R.id.menu_copy:
onMenuCopy(data);
return true;
case R.id.menu_delete:
// For emergencies
onMenuDelete(data);
return true;
case R.id.menu_junk:
onMenuJunk(data);
return true;
case R.id.menu_decrypt:
onMenuDecrypt(data);
return true;
case R.id.menu_resync:
onMenuResync(data);
return true;
case R.id.menu_create_rule:
onMenuCreateRule(data);
return true;
case R.id.menu_manage_keywords:
onMenuManageKeywords(data);
return true;
case R.id.menu_share:
onMenuShare(data);
return true;
case R.id.menu_print:
onMenuPrint(data);
return true;
case R.id.menu_show_headers:
onMenuShowHeaders(data);
return true;
case R.id.menu_raw:
onMenuRaw(data);
return true;
default:
return false;
}
}
});
popupMenu.show();
}
private void onActionDelete(final ActionData data) {
if (data.delete) {
// No trash or is trash
new DialogBuilderLifecycle(context, owner)
.setMessage(R.string.title_ask_delete)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
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");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
EntityFolder folder = db.folder().getFolder(message.folder);
if (EntityFolder.OUTBOX.equals(folder.type)) {
db.message().deleteMessage(id);
db.folder().setFolderError(message.folder, null);
db.identity().setIdentityError(message.identity, null);
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel("send", message.identity.intValue());
} else
EntityOperation.queue(context, db, message, EntityOperation.DELETE);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:delete");
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
} else
properties.move(data.message.id, EntityFolder.TRASH, true);
}
private void onActionMove(ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask>() {
@Override
protected List onExecute(Context context, Bundle args) {
EntityMessage message;
List folders = null;
DB db = DB.getInstance(context);
try {
db.beginTransaction();
message = db.message().getMessage(args.getLong("id"));
if (message == null)
return null;
EntityFolder folder = db.folder().getFolder(message.folder);
if (EntityFolder.OUTBOX.equals(folder.type)) {
long id = message.id;
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.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, db, message, EntityOperation.ADD);
// Delete from outbox
db.message().deleteMessage(id);
} else
folders = db.folder().getFolders(message.account);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (folders == null)
return null;
List targets = new ArrayList<>();
for (EntityFolder folder : folders)
if (!folder.hide &&
!folder.id.equals(message.folder) &&
!EntityFolder.ARCHIVE.equals(folder.type) &&
!EntityFolder.TRASH.equals(folder.type) &&
!EntityFolder.JUNK.equals(folder.type))
targets.add(folder);
EntityFolder.sort(context, targets, true);
return targets;
}
@Override
protected void onExecuted(final Bundle args, List folders) {
if (folders == null)
return;
View anchor = bnvActions.findViewById(R.id.action_move);
PopupMenu popupMenu = new PopupMenu(context, anchor);
int order = 0;
for (EntityFolder folder : folders)
popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, folder.getDisplayName(context));
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem target) {
args.putLong("target", target.getItemId());
new SimpleTask() {
@Override
protected String onExecute(Context context, Bundle args) {
long target = args.getLong("target");
return DB.getInstance(context).folder().getFolder(target).name;
}
@Override
protected void onExecuted(Bundle args, String folderName) {
long id = args.getLong("id");
properties.move(id, folderName, false);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:move");
return true;
}
});
popupMenu.show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:move:list");
}
private void onActionArchive(ActionData data) {
properties.move(data.message.id, EntityFolder.ARCHIVE, true);
}
private void onActionReplyMenu(final ActionData data) {
View anchor = bnvActions.findViewById(R.id.action_reply);
PopupMenu popupMenu = new PopupMenu(context, anchor);
popupMenu.inflate(R.menu.menu_reply);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem target) {
switch (target.getItemId()) {
case R.id.menu_reply_to_sender:
onMenuReply(data, false);
return true;
case R.id.menu_reply_to_all:
onMenuReply(data, true);
return true;
case R.id.menu_reply_template:
onMenuAnswer(data);
return true;
default:
return false;
}
}
});
popupMenu.show();
}
private void onMenuReply(final ActionData data, final boolean all) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask() {
@Override
protected Boolean onExecute(Context context, Bundle args) {
long id = args.getLong("id");
List attachments = DB.getInstance(context).attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (!attachment.available && attachment.isInline())
return false;
return true;
}
@Override
protected void onExecuted(Bundle args, Boolean available) {
final Intent reply = new Intent(context, ActivityCompose.class)
.putExtra("action", all ? "reply_all" : "reply")
.putExtra("reference", data.message.id);
if (available)
context.startActivity(reply);
else
new DialogBuilderLifecycle(context, owner)
.setMessage(R.string.title_image_unavailable)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
context.startActivity(reply);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:reply");
}
private void onMenuAnswer(final ActionData data) {
new SimpleTask>() {
@Override
protected List onExecute(Context context, Bundle args) {
return DB.getInstance(context).answer().getAnswers();
}
@Override
protected void onExecuted(Bundle args, List answers) {
if (answers == null || answers.size() == 0) {
Snackbar snackbar = Snackbar.make(
view,
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 {
final Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
Collections.sort(answers, new Comparator() {
@Override
public int compare(EntityAnswer a1, EntityAnswer a2) {
return collator.compare(a1.name, a2.name);
}
});
View anchor = bnvActions.findViewById(R.id.action_reply);
PopupMenu popupMenu = new PopupMenu(context, 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 (Helper.isPro(context))
context.startActivity(new Intent(context, ActivityCompose.class)
.putExtra("action", "reply")
.putExtra("reference", data.message.id)
.putExtra("answer", (long) target.getItemId()));
else {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO));
}
return true;
}
});
popupMenu.show();
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, new Bundle(), "message:answer");
}
ItemDetailsLookup.ItemDetails getItemDetails(@NonNull MotionEvent motionEvent) {
return new ItemDetailsMessage(this);
}
Long getKey() {
return getKeyAtPosition(getAdapterPosition());
}
}
AdapterMessage(Context context, LifecycleOwner owner,
ViewType viewType, boolean compact, int zoom, String sort, boolean duplicates, IProperties properties) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.context = context;
this.owner = owner;
this.inflater = LayoutInflater.from(context);
this.viewType = viewType;
this.compact = compact;
this.name_email = prefs.getBoolean("name_email", !compact);
this.subject_italic = prefs.getBoolean("subject_italic", true);
this.monospaced = prefs.getBoolean("monospaced", false);
this.zoom = zoom;
this.sort = sort;
this.duplicates = duplicates;
this.suitable = Helper.getNetworkState(context).isSuitable();
this.properties = properties;
this.date = prefs.getBoolean("date", true);
this.threading = prefs.getBoolean("threading", true);
this.contacts = Helper.hasPermission(context, Manifest.permission.READ_CONTACTS);
this.search = (context.getPackageManager().getComponentEnabledSetting(
new ComponentName(context, ActivitySearch.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
this.avatars = (prefs.getBoolean("avatars", true) ||
prefs.getBoolean("identicons", false));
this.flags = prefs.getBoolean("flags", true);
this.preview = prefs.getBoolean("preview", false);
this.autohtml = prefs.getBoolean("autohtml", false);
this.autoimages = prefs.getBoolean("autoimages", false);
this.debug = prefs.getBoolean("debug", false);
this.textSize = Helper.getTextSize(context, zoom);
this.colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary);
this.colorAccent = Helper.resolveColor(context, R.attr.colorAccent);
this.colorWarning = Helper.resolveColor(context, R.attr.colorWarning);
this.textColorSecondary = Helper.resolveColor(context, android.R.attr.textColorSecondary);
this.colorUnread = Helper.resolveColor(context, R.attr.colorUnread);
this.hasWebView = Helper.hasWebView(context);
}
void submitList(PagedList list) {
differ.submitList(list);
}
PagedList getCurrentList() {
return differ.getCurrentList();
}
void setCompact(boolean compact) {
if (this.compact != compact) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.compact = compact;
this.name_email = prefs.getBoolean("name_email", !compact);
notifyDataSetChanged();
}
}
void setZoom(int zoom) {
if (this.zoom != zoom) {
this.zoom = zoom;
textSize = Helper.getTextSize(context, zoom);
notifyDataSetChanged();
}
}
int getZoom() {
return this.zoom;
}
void setSort(String sort) {
if (!sort.equals(this.sort)) {
this.sort = sort;
notifyDataSetChanged();
// Needed to redraw item decorators / add/remove size
}
}
String getSort() {
return this.sort;
}
void setDuplicates(boolean duplicates) {
if (this.duplicates != duplicates) {
this.duplicates = duplicates;
notifyDataSetChanged();
}
}
void checkInternet() {
boolean suitable = Helper.getNetworkState(context).isSuitable();
if (this.suitable != suitable) {
this.suitable = suitable;
notifyDataSetChanged();
}
}
@Override
public int getItemViewType(int position) {
return (compact ? R.layout.item_message_compact : R.layout.item_message_normal);
}
@Override
public int getItemCount() {
return differ.getItemCount();
}
private static final DiffUtil.ItemCallback DIFF_CALLBACK =
new DiffUtil.ItemCallback() {
@Override
public boolean areItemsTheSame(
@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
return prev.id.equals(next.id);
}
@Override
public boolean areContentsTheSame(
@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
return prev.uiEquals(next);
}
};
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(viewType, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
TupleMessageEx message = differ.getItem(position);
if (message == null)
holder.clear();
else {
holder.bindTo(message);
holder.wire();
}
}
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
holder.clearExpanded();
}
void setSelectionTracker(SelectionTracker selectionTracker) {
this.selectionTracker = selectionTracker;
}
int getPositionForKey(long key) {
PagedList messages = getCurrentList();
if (messages != null)
for (int i = 0; i < messages.size(); i++) {
TupleMessageEx message = messages.get(i);
if (message != null && message.id.equals(key)) {
Log.i("Position=" + i + " @Key=" + key);
return i;
}
}
Log.i("Position=" + RecyclerView.NO_POSITION + " @Key=" + key);
return RecyclerView.NO_POSITION;
}
TupleMessageEx getItemAtPosition(int pos) {
PagedList list = getCurrentList();
if (list != null && pos < list.size()) {
TupleMessageEx message = list.get(pos);
Long key = (message == null ? null : message.id);
Log.i("Item=" + key + " @Position=" + pos);
return message;
} else {
Log.i("Item=" + null + " @Position=" + pos);
return null;
}
}
TupleMessageEx getItemForKey(long key) {
PagedList messages = getCurrentList();
if (messages != null)
for (int i = 0; i < messages.size(); i++) {
TupleMessageEx message = messages.get(i);
if (message != null && message.id.equals(key)) {
Log.i("Item=" + message.id + " @Key=" + key);
return message;
}
}
Log.i("Item=" + null + " @Key" + key);
return null;
}
Long getKeyAtPosition(int pos) {
TupleMessageEx message = getItemAtPosition(pos);
Long key = (message == null ? null : message.id);
Log.i("Key=" + key + " @Position=" + pos);
return key;
}
interface IProperties {
void setValue(String name, long id, boolean enabled);
boolean getValue(String name, long id);
void setBody(long id, Spanned body);
Spanned getBody(long id);
void setHtml(long id, String html);
String getHtml(long id);
void setAttchments(long id, List attachments);
List getAttachments(long id);
void scrollTo(int pos, int dy);
void move(long id, String target, boolean type);
void finish();
}
}