Files
FairEmail/app/src/main/java/eu/faircode/email/AdapterMessage.java

3357 lines
147 KiB
Java
Raw Normal View History

2018-08-02 13:33:06 +00:00
package eu.faircode.email;
/*
2018-08-14 05:53:24 +00:00
This file is part of FairEmail.
2018-08-02 13:33:06 +00:00
2018-08-14 05:53:24 +00:00
FairEmail is free software: you can redistribute it and/or modify
2018-08-02 13:33:06 +00:00
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
2018-10-29 10:46:49 +00:00
FairEmail is distributed in the hope that it will be useful,
2018-08-02 13:33:06 +00:00
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
2018-10-29 10:46:49 +00:00
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
2018-08-02 13:33:06 +00:00
2018-12-31 08:04:33 +00:00
Copyright 2018-2019 by Marcel Bokhorst (M66B)
2018-08-02 13:33:06 +00:00
*/
2018-09-08 14:57:03 +00:00
import android.Manifest;
2019-01-27 11:17:45 +00:00
import android.annotation.SuppressLint;
2019-03-07 12:29:03 +00:00
import android.annotation.TargetApi;
2019-04-19 08:31:00 +02:00
import android.app.Dialog;
2019-03-07 12:29:03 +00:00
import android.app.Notification;
import android.app.NotificationChannel;
2018-11-19 18:10:55 +01:00
import android.app.NotificationManager;
2019-01-27 13:50:21 +00:00
import android.content.ComponentName;
2018-09-08 14:57:03 +00:00
import android.content.ContentResolver;
2018-08-02 13:33:06 +00:00
import android.content.Context;
2018-09-19 10:00:58 +00:00
import android.content.DialogInterface;
2018-08-02 13:33:06 +00:00
import android.content.Intent;
import android.content.SharedPreferences;
2018-09-08 14:57:03 +00:00
import android.content.pm.PackageManager;
2018-12-10 19:27:04 +01:00
import android.content.res.ColorStateList;
2018-10-15 10:05:42 +00:00
import android.database.Cursor;
2018-09-12 16:54:48 +00:00
import android.graphics.Color;
2019-01-12 14:08:00 +00:00
import android.graphics.Rect;
2018-08-02 13:33:06 +00:00
import android.graphics.Typeface;
2018-09-08 14:57:03 +00:00
import android.graphics.drawable.Drawable;
import android.net.Uri;
2019-03-07 12:29:03 +00:00
import android.os.Build;
2018-09-01 06:27:13 +00:00
import android.os.Bundle;
2018-09-08 14:57:03 +00:00
import android.provider.ContactsContract;
2019-03-07 12:29:03 +00:00
import android.provider.Settings;
2018-10-15 10:05:42 +00:00
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
2018-12-20 21:12:28 +01:00
import android.text.SpannableStringBuilder;
2018-10-15 10:05:42 +00:00
import android.text.Spanned;
import android.text.TextUtils;
2019-02-09 10:41:28 +00:00
import android.text.method.ArrowKeyMovementMethod;
2019-05-02 13:29:05 +02:00
import android.text.style.DynamicDrawableSpan;
2018-10-15 10:05:42 +00:00
import android.text.style.ImageSpan;
2018-12-20 21:12:28 +01:00
import android.text.style.QuoteSpan;
2018-12-27 17:35:26 +00:00
import android.text.style.StyleSpan;
2018-10-15 10:05:42 +00:00
import android.text.style.URLSpan;
2018-12-30 16:17:22 +00:00
import android.util.TypedValue;
2018-08-02 13:33:06 +00:00
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
2018-10-15 10:05:42 +00:00
import android.view.MotionEvent;
2019-01-12 14:08:00 +00:00
import android.view.TouchDelegate;
2018-08-02 13:33:06 +00:00
import android.view.View;
import android.view.ViewGroup;
2019-02-03 09:38:48 +00:00
import android.webkit.DownloadListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
2018-10-15 10:05:42 +00:00
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
2018-10-15 10:05:42 +00:00
import android.widget.EditText;
2018-12-21 15:19:07 +01:00
import android.widget.ImageButton;
import android.widget.ImageView;
2018-08-02 13:33:06 +00:00
import android.widget.TextView;
2019-01-03 20:31:45 +01:00
import android.widget.Toast;
2019-05-02 09:02:39 +02:00
import android.widget.ToggleButton;
2018-08-02 13:33:06 +00:00
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Group;
2019-04-19 08:31:00 +02:00
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
2019-04-19 08:31:00 +02:00
import androidx.lifecycle.OnLifecycleEvent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.paging.AsyncPagedListDiffer;
import androidx.paging.PagedList;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.selection.ItemDetailsLookup;
import androidx.recyclerview.selection.SelectionTracker;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
2019-04-19 08:31:00 +02:00
import com.github.chrisbanes.photoview.PhotoView;
2018-10-15 10:05:42 +00:00
import com.google.android.material.bottomnavigation.BottomNavigationView;
2018-10-24 10:34:02 +00:00
import com.google.android.material.snackbar.Snackbar;
2018-10-15 10:05:42 +00:00
2019-02-03 09:38:48 +00:00
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
2018-10-15 10:05:42 +00:00
import java.io.File;
2019-03-14 07:45:13 +00:00
import java.io.FileNotFoundException;
2018-10-15 10:05:42 +00:00
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
2019-03-10 08:02:17 +00:00
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
2018-10-15 10:05:42 +00:00
import java.util.ArrayList;
2018-11-26 12:42:06 +01:00
import java.util.Arrays;
2018-10-15 10:05:42 +00:00
import java.util.Collections;
import java.util.Date;
import java.util.List;
2018-10-15 10:05:42 +00:00
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
2018-12-03 08:21:02 +01:00
public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> {
2018-08-02 13:33:06 +00:00
private Context context;
2018-12-24 13:41:49 +00:00
private LayoutInflater inflater;
private LifecycleOwner owner;
2018-08-06 13:36:55 +00:00
private ViewType viewType;
2019-01-15 17:39:12 +00:00
private boolean compact;
2018-12-30 16:33:52 +00:00
private int zoom;
2019-01-21 18:12:22 +00:00
private String sort;
2019-04-28 15:16:26 +02:00
private boolean filter_duplicates;
2019-03-16 13:12:31 +00:00
private boolean suitable;
private IProperties properties;
private int colorPrimary;
private int colorAccent;
private int colorWarning;
private int textColorSecondary;
private int colorUnread;
private boolean dark;
private boolean hasWebView;
private boolean contacts;
2019-01-27 13:50:21 +00:00
private boolean search;
private float textSize;
private boolean date;
private boolean threading;
private boolean avatars;
private boolean name_email;
private boolean subject_italic;
2019-03-02 15:54:38 +00:00
private boolean flags;
2018-11-04 12:42:41 +00:00
private boolean preview;
private boolean monospaced;
private boolean autohtml;
private boolean autoimages;
private boolean authentication;
2018-08-06 16:22:01 +00:00
private boolean debug;
2018-09-08 17:04:10 +00:00
2019-05-04 10:24:42 +02:00
private boolean gotop = false;
private AsyncPagedListDiffer<TupleMessageEx> differ;
2018-10-24 09:09:07 +00:00
private SelectionTracker<Long> selectionTracker = null;
2018-08-02 13:33:06 +00:00
2018-08-26 12:17:09 +00:00
enum ViewType {UNIFIED, FOLDER, THREAD, SEARCH}
2018-08-02 13:33:06 +00:00
private NumberFormat NF = NumberFormat.getNumberInstance();
private DateFormat TF;
private DateFormat DTF = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG, SimpleDateFormat.LONG);
2018-12-03 08:21:02 +01:00
2019-04-17 13:37:14 +02:00
private static final List<String> PARANOID_QUERY = Collections.unmodifiableList(Arrays.asList(
"utm_source",
"utm_medium",
"utm_campaign",
"utm_term",
"utm_content"
));
2018-10-15 10:05:42 +00:00
public class ViewHolder extends RecyclerView.ViewHolder implements
2019-05-02 09:02:39 +02:00
View.OnClickListener, View.OnLongClickListener,
CompoundButton.OnCheckedChangeListener,
BottomNavigationView.OnNavigationItemSelectedListener {
2019-03-15 11:53:22 +00:00
private View view;
2018-10-15 10:05:42 +00:00
private View vwColor;
private View vwStatus;
2018-10-15 10:05:42 +00:00
private ImageView ivExpander;
private ImageView ivFlagged;
private ImageView ivAvatar;
private TextView tvFrom;
private TextView tvSize;
2018-10-15 10:05:42 +00:00
private TextView tvTime;
2019-01-01 15:26:30 +01:00
private ImageView ivDraft;
2019-01-07 15:05:24 +00:00
private ImageView ivSnoozed;
2019-01-19 16:14:40 +00:00
private ImageView ivBrowsed;
2018-11-24 19:14:28 +01:00
private ImageView ivAnswered;
private ImageView ivReceipt;
2018-10-15 10:05:42 +00:00
private ImageView ivAttachments;
private TextView tvSubject;
private TextView tvFolder;
private TextView tvCount;
private ImageView ivThread;
2018-11-04 16:16:45 +00:00
private TextView tvPreview;
2018-10-15 10:05:42 +00:00
private TextView tvError;
2018-12-27 12:31:02 +00:00
private ContentLoadingProgressBar pbLoading;
2019-03-08 10:27:33 +00:00
private View vwRipple;
2018-10-15 10:05:42 +00:00
2018-11-04 16:16:45 +00:00
private ImageView ivExpanderAddress;
2019-03-09 10:02:17 +00:00
2019-01-27 13:50:21 +00:00
private ImageView ivSearchContact;
2019-03-07 12:29:03 +00:00
private ImageView ivNotifyContact;
2018-11-04 16:16:45 +00:00
private ImageView ivAddContact;
2019-03-09 10:02:17 +00:00
private TextView tvFromExTitle;
private TextView tvToTitle;
private TextView tvReplyToTitle;
private TextView tvCcTitle;
private TextView tvBccTitle;
private TextView tvIdentityTitle;
private TextView tvTimeExTitle;
private TextView tvSizeExTitle;
private TextView tvFromEx;
2018-10-15 10:05:42 +00:00
private TextView tvTo;
private TextView tvReplyTo;
private TextView tvCc;
private TextView tvBcc;
private TextView tvIdentity;
2018-11-29 19:42:40 +01:00
private TextView tvTimeEx;
private TextView tvSizeEx;
2019-03-09 10:02:17 +00:00
2018-10-15 10:05:42 +00:00
private TextView tvSubjectEx;
2019-01-29 20:15:24 +00:00
private TextView tvFlags;
2018-11-25 18:29:11 +01:00
private TextView tvKeywords;
2018-10-15 10:05:42 +00:00
private TextView tvHeaders;
2018-12-27 12:31:02 +00:00
private ContentLoadingProgressBar pbHeaders;
private TextView tvNoInternetHeaders;
2018-10-15 10:05:42 +00:00
private RecyclerView rvAttachment;
private CheckBox cbInline;
2019-01-05 12:00:36 +00:00
private Button btnDownloadAttachments;
2019-01-05 14:56:19 +00:00
private Button btnSaveAttachments;
private TextView tvNoInternetAttachments;
2018-10-15 10:05:42 +00:00
private BottomNavigationView bnvActions;
2018-10-18 07:56:31 +00:00
2019-05-02 09:02:39 +02:00
private ToggleButton tbHtml;
2018-12-21 15:19:07 +01:00
private ImageButton ibImages;
private ImageButton ibFull;
2018-10-15 10:05:42 +00:00
private TextView tvBody;
2019-02-03 09:38:48 +00:00
private View vwBody;
2018-12-27 12:31:02 +00:00
private ContentLoadingProgressBar pbBody;
private TextView tvNoInternetBody;
2018-10-15 10:05:42 +00:00
2019-01-06 09:23:57 +00:00
private RecyclerView rvImage;
2018-10-15 10:05:42 +00:00
private Group grpHeaders;
private Group grpAttachments;
private Group grpExpanded;
2019-01-06 09:23:57 +00:00
private AdapterAttachment adapterAttachment;
private AdapterImage adapterImage;
2019-05-03 18:59:27 +02:00
private TwoStateOwner cowner = new TwoStateOwner(owner, "MessageAttachments");
private TwoStateOwner powner = new TwoStateOwner(owner, "MessagePopup");
2018-12-05 11:50:07 +01:00
2019-01-12 14:08:00 +00:00
ViewHolder(final View itemView) {
2018-08-02 13:33:06 +00:00
super(itemView);
2019-03-15 11:53:22 +00:00
view = itemView.findViewById(R.id.clItem);
2018-09-12 16:54:48 +00:00
vwColor = itemView.findViewById(R.id.vwColor);
vwStatus = itemView.findViewById(R.id.vwStatus);
2018-10-15 10:05:42 +00:00
ivExpander = itemView.findViewById(R.id.ivExpander);
2018-09-07 15:12:43 +00:00
ivFlagged = itemView.findViewById(R.id.ivFlagged);
ivAvatar = itemView.findViewById(R.id.ivAvatar);
2018-08-03 07:39:43 +00:00
tvFrom = itemView.findViewById(R.id.tvFrom);
tvSize = itemView.findViewById(R.id.tvSize);
2018-08-02 13:33:06 +00:00
tvTime = itemView.findViewById(R.id.tvTime);
2019-01-01 15:26:30 +01:00
ivDraft = itemView.findViewById(R.id.ivDraft);
2019-01-07 15:05:24 +00:00
ivSnoozed = itemView.findViewById(R.id.ivSnoozed);
2019-01-19 16:14:40 +00:00
ivBrowsed = itemView.findViewById(R.id.ivBrowsed);
2018-11-24 19:14:28 +01:00
ivAnswered = itemView.findViewById(R.id.ivAnswered);
ivReceipt = itemView.findViewById(R.id.ivReceipt);
ivAttachments = itemView.findViewById(R.id.ivAttachments);
2018-08-02 13:33:06 +00:00
tvSubject = itemView.findViewById(R.id.tvSubject);
2018-11-04 12:42:41 +00:00
tvPreview = itemView.findViewById(R.id.tvPreview);
2018-08-22 16:37:10 +00:00
tvFolder = itemView.findViewById(R.id.tvFolder);
2018-08-02 13:33:06 +00:00
tvCount = itemView.findViewById(R.id.tvCount);
2018-09-04 07:56:30 +00:00
ivThread = itemView.findViewById(R.id.ivThread);
tvError = itemView.findViewById(R.id.tvError);
2018-08-07 06:38:00 +00:00
pbLoading = itemView.findViewById(R.id.pbLoading);
2019-03-08 10:27:33 +00:00
vwRipple = itemView.findViewById(R.id.vwRipple);
2018-10-15 10:05:42 +00:00
2018-11-04 16:16:45 +00:00
ivExpanderAddress = itemView.findViewById(R.id.ivExpanderAddress);
2019-03-09 10:02:17 +00:00
2019-01-27 13:50:21 +00:00
ivSearchContact = itemView.findViewById(R.id.ivSearchContact);
2019-03-07 12:29:03 +00:00
ivNotifyContact = itemView.findViewById(R.id.ivNotifyContact);
ivAddContact = itemView.findViewById(R.id.ivAddContact);
2019-03-09 10:02:17 +00:00
tvFromExTitle = itemView.findViewById(R.id.tvFromExTitle);
tvToTitle = itemView.findViewById(R.id.tvToTitle);
tvReplyToTitle = itemView.findViewById(R.id.tvReplyToTitle);
tvCcTitle = itemView.findViewById(R.id.tvCcTitle);
tvBccTitle = itemView.findViewById(R.id.tvBccTitle);
tvIdentityTitle = itemView.findViewById(R.id.tvIdentityTitle);
tvTimeExTitle = itemView.findViewById(R.id.tvTimeExTitle);
tvSizeExTitle = itemView.findViewById(R.id.tvSizeExTitle);
tvFromEx = itemView.findViewById(R.id.tvFromEx);
2018-10-15 10:05:42 +00:00
tvTo = itemView.findViewById(R.id.tvTo);
tvReplyTo = itemView.findViewById(R.id.tvReplyTo);
tvCc = itemView.findViewById(R.id.tvCc);
tvBcc = itemView.findViewById(R.id.tvBcc);
tvIdentity = itemView.findViewById(R.id.tvIdentity);
2018-11-29 19:42:40 +01:00
tvTimeEx = itemView.findViewById(R.id.tvTimeEx);
tvSizeEx = itemView.findViewById(R.id.tvSizeEx);
2019-03-09 10:02:17 +00:00
2018-10-15 10:05:42 +00:00
tvSubjectEx = itemView.findViewById(R.id.tvSubjectEx);
2019-01-29 20:15:24 +00:00
tvFlags = itemView.findViewById(R.id.tvFlags);
2018-11-25 18:29:11 +01:00
tvKeywords = itemView.findViewById(R.id.tvKeywords);
2018-10-15 10:05:42 +00:00
tvHeaders = itemView.findViewById(R.id.tvHeaders);
pbHeaders = itemView.findViewById(R.id.pbHeaders);
tvNoInternetHeaders = itemView.findViewById(R.id.tvNoInternetHeaders);
2018-10-15 10:05:42 +00:00
rvAttachment = itemView.findViewById(R.id.rvAttachment);
rvAttachment.setHasFixedSize(false);
LinearLayoutManager llm = new LinearLayoutManager(context);
rvAttachment.setLayoutManager(llm);
rvAttachment.setItemAnimator(null);
2019-01-06 09:23:57 +00:00
adapterAttachment = new AdapterAttachment(context, owner, true);
rvAttachment.setAdapter(adapterAttachment);
2018-10-15 10:05:42 +00:00
cbInline = itemView.findViewById(R.id.cbInline);
2019-01-05 12:00:36 +00:00
btnDownloadAttachments = itemView.findViewById(R.id.btnDownloadAttachments);
2019-01-05 14:56:19 +00:00
btnSaveAttachments = itemView.findViewById(R.id.btnSaveAttachments);
tvNoInternetAttachments = itemView.findViewById(R.id.tvNoInternetAttachments);
bnvActions = itemView.findViewById(R.id.bnvActions);
2019-05-02 09:02:39 +02:00
tbHtml = itemView.findViewById(R.id.tbHtml);
2018-12-21 15:19:07 +01:00
ibImages = itemView.findViewById(R.id.ibImages);
ibFull = itemView.findViewById(R.id.ibFull);
tvBody = itemView.findViewById(R.id.tvBody);
2019-02-03 09:38:48 +00:00
vwBody = itemView.findViewById(R.id.vwBody);
pbBody = itemView.findViewById(R.id.pbBody);
tvNoInternetBody = itemView.findViewById(R.id.tvNoInternetBody);
2019-01-06 09:23:57 +00:00
rvImage = itemView.findViewById(R.id.rvImage);
rvImage.setHasFixedSize(false);
StaggeredGridLayoutManager sglm =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
rvImage.setLayoutManager(sglm);
adapterImage = new AdapterImage(context, owner);
rvImage.setAdapter(adapterImage);
2018-10-15 10:05:42 +00:00
grpHeaders = itemView.findViewById(R.id.grpHeaders);
grpAttachments = itemView.findViewById(R.id.grpAttachments);
grpExpanded = itemView.findViewById(R.id.grpExpanded);
2018-08-02 13:33:06 +00:00
}
2019-01-20 15:22:21 +00:00
Rect getItemRect() {
return new Rect(
super.itemView.getLeft(),
super.itemView.getBottom() - vwColor.getHeight(),
super.itemView.getRight(),
super.itemView.getBottom());
}
2018-08-02 13:33:06 +00:00
private void wire() {
2019-01-20 11:21:09 +00:00
final View touch = (viewType == ViewType.THREAD && threading ? ivExpander : vwColor);
touch.setOnClickListener(this);
2019-03-15 11:53:22 +00:00
view.post(new Runnable() {
2019-01-20 11:21:09 +00:00
@Override
public void run() {
Rect rect = new Rect(
2019-03-15 11:53:22 +00:00
view.getLeft(),
2019-01-24 07:10:51 +00:00
vwColor.getTop(),
2019-03-15 11:53:22 +00:00
view.getRight(),
2019-01-24 07:10:51 +00:00
vwColor.getBottom());
2019-03-15 11:53:22 +00:00
view.setTouchDelegate(new TouchDelegate(rect, touch));
2019-01-20 11:21:09 +00:00
}
});
2019-01-15 16:50:32 +00:00
2019-01-07 15:05:24 +00:00
ivSnoozed.setOnClickListener(this);
2018-12-04 17:08:36 +01:00
ivFlagged.setOnClickListener(this);
2019-01-15 16:50:32 +00:00
2018-11-04 16:16:45 +00:00
ivExpanderAddress.setOnClickListener(this);
2019-01-27 13:50:21 +00:00
ivSearchContact.setOnClickListener(this);
2019-03-07 12:29:03 +00:00
ivNotifyContact.setOnClickListener(this);
ivNotifyContact.setOnLongClickListener(this);
2018-10-15 10:05:42 +00:00
ivAddContact.setOnClickListener(this);
2019-01-15 16:50:32 +00:00
2019-01-05 12:00:36 +00:00
btnDownloadAttachments.setOnClickListener(this);
2019-01-05 14:56:19 +00:00
btnSaveAttachments.setOnClickListener(this);
2019-01-15 16:50:32 +00:00
2019-05-02 09:02:39 +02:00
tbHtml.setOnCheckedChangeListener(this);
2018-12-21 15:19:07 +01:00
ibImages.setOnClickListener(this);
ibFull.setOnClickListener(this);
bnvActions.setOnNavigationItemSelectedListener(this);
2018-08-02 13:33:06 +00:00
}
private void unwire() {
2019-01-15 10:57:08 +00:00
if (viewType == ViewType.THREAD) {
vwColor.setOnClickListener(null);
2019-01-12 14:08:00 +00:00
ivExpander.setOnClickListener(null);
2019-01-15 10:57:08 +00:00
} else
2019-03-15 11:53:22 +00:00
view.setOnClickListener(null);
2019-01-07 15:05:24 +00:00
ivSnoozed.setOnClickListener(null);
2018-12-04 17:08:36 +01:00
ivFlagged.setOnClickListener(null);
2018-11-04 16:16:45 +00:00
ivExpanderAddress.setOnClickListener(null);
2019-01-27 13:50:21 +00:00
ivSearchContact.setOnClickListener(null);
2019-03-07 12:29:03 +00:00
ivNotifyContact.setOnClickListener(null);
ivNotifyContact.setOnLongClickListener(null);
2018-10-15 10:05:42 +00:00
ivAddContact.setOnClickListener(null);
2019-01-05 12:00:36 +00:00
btnDownloadAttachments.setOnClickListener(null);
2019-01-05 14:56:19 +00:00
btnSaveAttachments.setOnClickListener(null);
2019-05-02 09:02:39 +02:00
tbHtml.setOnCheckedChangeListener(null);
2018-12-21 15:19:07 +01:00
ibImages.setOnClickListener(null);
ibFull.setOnClickListener(null);
bnvActions.setOnNavigationItemSelectedListener(null);
2018-08-02 13:33:06 +00:00
}
2018-08-07 06:38:00 +00:00
private void clear() {
2018-10-15 10:05:42 +00:00
vwColor.setVisibility(View.GONE);
vwStatus.setVisibility(View.GONE);
2018-10-15 10:05:42 +00:00
ivExpander.setVisibility(View.GONE);
2018-09-07 15:12:43 +00:00
ivFlagged.setVisibility(View.GONE);
2018-09-08 17:39:35 +00:00
ivAvatar.setVisibility(View.GONE);
2018-08-07 06:38:00 +00:00
tvFrom.setText(null);
tvSize.setText(null);
2018-08-07 06:38:00 +00:00
tvTime.setText(null);
2019-01-01 15:26:30 +01:00
ivDraft.setVisibility(View.GONE);
2019-01-07 15:05:24 +00:00
ivSnoozed.setVisibility(View.GONE);
2019-01-19 16:14:40 +00:00
ivBrowsed.setVisibility(View.GONE);
2018-11-24 19:14:28 +01:00
ivAnswered.setVisibility(View.GONE);
ivReceipt.setVisibility(View.GONE);
2018-08-07 06:38:00 +00:00
ivAttachments.setVisibility(View.GONE);
2018-09-07 15:12:43 +00:00
tvSubject.setText(null);
2018-08-23 06:57:24 +00:00
tvFolder.setText(null);
2018-08-07 06:38:00 +00:00
tvCount.setText(null);
2018-09-04 07:56:30 +00:00
ivThread.setVisibility(View.GONE);
2018-11-04 16:16:45 +00:00
tvPreview.setVisibility(View.GONE);
2018-09-08 17:39:35 +00:00
tvError.setVisibility(View.GONE);
pbLoading.setVisibility(View.VISIBLE);
2018-11-04 16:16:45 +00:00
2019-03-09 10:02:17 +00:00
clearExpanded();
2018-08-07 06:38:00 +00:00
}
2019-01-27 11:17:45 +00:00
@SuppressLint("WrongConstant")
2019-03-04 10:18:42 +00:00
private void bindTo(final TupleMessageEx message) {
pbLoading.setVisibility(View.GONE);
if (viewType == ViewType.THREAD)
2019-04-28 15:16:26 +02:00
view.setVisibility(!filter_duplicates || !message.duplicate ? View.VISIBLE : View.GONE);
// Text size
2019-01-20 11:21:09 +00:00
if (textSize != 0) {
tvFrom.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
2019-03-07 21:20:52 +00:00
tvSubject.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize * 0.9f);
2019-04-17 09:38:24 +02:00
tvPreview.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize * 0.9f);
tvBody.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
2019-03-08 09:15:40 +00:00
int px = Math.round(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_PX, textSize * (compact ? 1.5f : 3.0f),
context.getResources().getDisplayMetrics()));
if (compact && tvFrom.getMinHeight() != px)
tvFrom.setMinimumHeight(px);
ViewGroup.LayoutParams lparams = ivAvatar.getLayoutParams();
if (lparams.height != px) {
lparams.width = px;
lparams.height = px;
ivAvatar.requestLayout();
}
2019-01-20 11:21:09 +00:00
}
// Selected / disabled
2019-03-15 11:53:22 +00:00
view.setActivated(selectionTracker != null && selectionTracker.isSelected(message.id));
view.setAlpha(
2019-02-10 16:33:31 +00:00
message.uid == null &&
2019-03-20 07:23:15 +00:00
!(EntityFolder.OUTBOX.equals(message.folderType) && !message.ui_seen)
2019-02-10 16:33:31 +00:00
? Helper.LOW_LIGHT : 1.0f);
2019-01-04 18:37:56 +00:00
// Duplicate
2018-11-27 07:27:32 +01:00
if (viewType == ViewType.THREAD) {
2019-03-07 13:26:36 +00:00
boolean dim = (message.duplicate || EntityFolder.TRASH.equals(message.folderType));
ivFlagged.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivAvatar.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvFrom.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvSize.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvTime.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivDraft.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivSnoozed.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivBrowsed.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivAnswered.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivReceipt.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
2019-03-07 13:26:36 +00:00
ivAttachments.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvSubject.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvFolder.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvCount.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
ivThread.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvPreview.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
tvError.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
2018-11-27 07:27:32 +01:00
}
// Unseen
int typeface = (message.unseen > 0 ? Typeface.BOLD : Typeface.NORMAL);
tvFrom.setTypeface(null, typeface);
tvSize.setTypeface(null, typeface);
tvTime.setTypeface(null, typeface);
2019-03-09 07:36:45 +00:00
tvSubject.setTypeface(null, typeface | (subject_italic ? Typeface.ITALIC : 0));
tvCount.setTypeface(null, typeface);
2018-12-24 18:16:01 +00:00
int colorUnseen = (message.unseen > 0 ? colorUnread : textColorSecondary);
tvFrom.setTextColor(colorUnseen);
tvSize.setTextColor(colorUnseen);
tvTime.setTextColor(colorUnseen);
2018-09-08 14:57:03 +00:00
// Account color
2018-09-12 16:54:48 +00:00
vwColor.setBackgroundColor(message.accountColor == null ? Color.TRANSPARENT : message.accountColor);
2018-12-17 08:55:02 +01:00
vwColor.setVisibility(View.VISIBLE);
2018-09-12 16:54:48 +00:00
vwStatus.setBackgroundColor(
Boolean.FALSE.equals(message.dkim) ||
Boolean.FALSE.equals(message.spf) ||
Boolean.FALSE.equals(message.dmarc)
2019-04-17 08:58:24 +02:00
? colorAccent : Color.TRANSPARENT);
vwStatus.setVisibility(authentication ? View.VISIBLE : View.GONE);
// Expander
boolean expanded = (viewType == ViewType.THREAD && properties.getValue("expanded", message.id));
2019-04-28 10:44:07 +02:00
ivExpander.setImageLevel(expanded ? 0 /* less */ : 1 /* more */);
if (viewType == ViewType.THREAD && threading)
2018-11-13 10:22:41 +01:00
ivExpander.setVisibility(EntityFolder.DRAFTS.equals(message.folderType) ? View.INVISIBLE : View.VISIBLE);
else
ivExpander.setVisibility(View.GONE);
2018-10-15 10:05:42 +00:00
// Line 1
2019-04-05 08:57:50 +02:00
Long size = ("size".equals(sort) ? message.totalSize : message.size);
tvSize.setText(size == null ? null : Helper.humanReadableByteCount(size, true));
tvSize.setVisibility(size == null || (message.content && !"size".equals(sort)) ? View.GONE : View.VISIBLE);
tvTime.setText(date && "time".equals(sort)
? TF.format(message.received)
: Helper.getRelativeTimeSpanString(context, message.received));
2018-08-07 06:38:00 +00:00
// Line 2
tvSubject.setText(message.subject);
// Line 3
2019-01-01 15:26:30 +01:00
ivDraft.setVisibility(message.drafts > 0 ? View.VISIBLE : View.GONE);
2019-01-07 15:05:24 +00:00
ivSnoozed.setVisibility(message.ui_snoozed == null ? View.GONE : View.VISIBLE);
2019-01-19 16:14:40 +00:00
ivBrowsed.setVisibility(message.ui_browsed ? View.VISIBLE : View.GONE);
2018-11-24 19:14:28 +01:00
ivAnswered.setVisibility(message.ui_answered ? View.VISIBLE : View.GONE);
ivReceipt.setVisibility(message.receipt_request != null && message.receipt_request ? View.VISIBLE : View.GONE);
2018-08-23 06:57:24 +00:00
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
2018-08-07 06:38:00 +00:00
2019-01-20 10:18:42 +00:00
if (viewType == ViewType.FOLDER)
tvFolder.setText(message.accountName);
else {
String folderName = (message.folderDisplay == null
2018-09-19 17:47:03 +00:00
? Helper.localizeFolderName(context, message.folderName)
: message.folderDisplay);
2019-01-20 10:18:42 +00:00
tvFolder.setText((compact ? "" : message.accountName + "/") + folderName);
}
tvFolder.setVisibility(compact &&
2019-03-13 09:52:26 +00:00
(viewType == ViewType.FOLDER ||
(viewType == ViewType.UNIFIED && EntityFolder.INBOX.equals(message.folderType)))
? View.GONE : View.VISIBLE);
2018-08-23 06:57:24 +00:00
if (viewType == ViewType.THREAD || !threading) {
2018-08-23 06:57:24 +00:00
tvCount.setVisibility(View.GONE);
2018-09-04 07:56:30 +00:00
ivThread.setVisibility(View.GONE);
} else {
tvCount.setText(NF.format(message.visible));
2018-09-04 07:56:30 +00:00
ivThread.setVisibility(View.VISIBLE);
2018-08-09 05:37:52 +00:00
}
2018-08-07 06:38:00 +00:00
// Starred
bindFlagged(message);
// Message text preview
tvPreview.setTypeface(monospaced ? Typeface.MONOSPACE : Typeface.DEFAULT, Typeface.ITALIC);
tvPreview.setText(message.preview);
tvPreview.setVisibility(preview && !TextUtils.isEmpty(message.preview) ? View.VISIBLE : View.GONE);
// Error / warning
2019-01-17 10:49:18 +00:00
String error = message.error;
if (message.warning != null)
if (error == null)
error = message.warning;
else
error += " " + message.warning;
if (debug) {
2019-01-17 10:49:18 +00:00
String text = "error=" + error +
"\nuid=" + message.uid + " id=" + message.id + " " + DTF.format(new Date(message.received)) +
2018-12-05 11:50:07 +01:00
"\n" + (message.ui_hide ? "HIDDEN " : "") +
2019-01-05 14:08:07 +00:00
"seen=" + message.seen + "/" + message.ui_seen +
" unseen=" + message.unseen +
2019-01-10 18:55:40 +00:00
" ignored=" + message.ui_ignored +
2018-12-05 11:50:07 +01:00
" found=" + message.ui_found +
2019-01-05 14:08:07 +00:00
"\nmsgid=" + message.msgid +
"\nthread=" + message.thread +
"\nsender=" + message.sender;
2018-12-05 11:50:07 +01:00
tvError.setText(text);
tvError.setVisibility(View.VISIBLE);
2018-10-15 10:05:42 +00:00
} else {
2019-01-17 10:49:18 +00:00
tvError.setText(error);
tvError.setVisibility(error == null ? View.GONE : View.VISIBLE);
}
2018-08-11 09:18:49 +00:00
// Contact info
boolean outgoing = (viewType != ViewType.THREAD && EntityFolder.isOutgoing(message.folderType));
Address[] addresses = (outgoing ? message.to : message.from);
ContactInfo info = ContactInfo.get(context, addresses, true);
if (info == null) {
Bundle aargs = new Bundle();
aargs.putLong("id", message.id);
aargs.putSerializable("addresses", addresses);
2019-01-10 17:39:52 +00:00
new SimpleTask<ContactInfo>() {
@Override
protected void onPreExecute(Bundle args) {
Address[] addresses = (Address[]) args.getSerializable("addresses");
ivAvatar.setVisibility(avatars ? View.INVISIBLE : View.GONE);
tvFrom.setText(MessageHelper.formatAddresses(addresses, !compact, false));
}
2018-12-05 11:50:07 +01:00
@Override
protected ContactInfo onExecute(Context context, Bundle args) {
Address[] addresses = (Address[]) args.getSerializable("addresses");
return ContactInfo.get(context, addresses, false);
2018-12-05 11:50:07 +01:00
}
@Override
protected void onExecuted(Bundle args, ContactInfo info) {
2019-02-01 09:29:26 +00:00
long id = args.getLong("id");
TupleMessageEx amessage = getMessage();
if (amessage == null || !amessage.id.equals(id))
return;
bindContactInfo(info, message);
}
2018-12-05 11:50:07 +01:00
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, aargs, "message:avatar");
2018-12-22 08:48:23 +01:00
} else
bindContactInfo(info, message);
2018-08-07 06:38:00 +00:00
2019-02-04 11:45:38 +00:00
if (message.avatar != null) {
2019-02-05 11:36:20 +00:00
if (autohtml && hasWebView)
2019-02-04 11:45:38 +00:00
properties.setValue("html", message.id, true);
if (autoimages)
properties.setValue("images", message.id, true);
}
if (viewType == ViewType.THREAD) {
boolean show_expanded = properties.getValue("expanded", message.id);
if (show_expanded)
bindExpanded(message);
else {
2019-02-04 12:17:42 +00:00
clearExpanded();
2019-02-04 11:45:38 +00:00
properties.setBody(message.id, null);
properties.setHtml(message.id, null);
}
2019-02-04 12:17:42 +00:00
} else
clearExpanded();
2019-01-30 07:50:20 +00:00
}
private void clearExpanded() {
2019-04-13 08:02:01 +02:00
cowner.stop();
grpHeaders.setVisibility(View.GONE);
grpAttachments.setVisibility(View.GONE);
grpExpanded.setVisibility(View.GONE);
ivSearchContact.setVisibility(View.GONE);
2019-03-07 12:29:03 +00:00
ivNotifyContact.setVisibility(View.GONE);
ivAddContact.setVisibility(View.GONE);
2019-03-09 10:02:17 +00:00
tvFromExTitle.setVisibility(View.GONE);
tvToTitle.setVisibility(View.GONE);
tvReplyToTitle.setVisibility(View.GONE);
tvCcTitle.setVisibility(View.GONE);
tvBccTitle.setVisibility(View.GONE);
tvIdentityTitle.setVisibility(View.GONE);
tvTimeExTitle.setVisibility(View.GONE);
tvSizeExTitle.setVisibility(View.GONE);
tvFromEx.setVisibility(View.GONE);
tvTo.setVisibility(View.GONE);
tvReplyTo.setVisibility(View.GONE);
tvCc.setVisibility(View.GONE);
tvBcc.setVisibility(View.GONE);
tvIdentity.setVisibility(View.GONE);
tvTimeEx.setVisibility(View.GONE);
tvSizeEx.setVisibility(View.GONE);
tvSubjectEx.setVisibility(View.GONE);
tvFlags.setVisibility(View.GONE);
tvKeywords.setVisibility(View.GONE);
2019-03-09 10:02:17 +00:00
pbHeaders.setVisibility(View.GONE);
tvNoInternetHeaders.setVisibility(View.GONE);
cbInline.setVisibility(View.GONE);
btnDownloadAttachments.setVisibility(View.GONE);
btnSaveAttachments.setVisibility(View.GONE);
tvNoInternetAttachments.setVisibility(View.GONE);
2019-05-02 09:02:39 +02:00
tbHtml.setVisibility(View.GONE);
ibImages.setVisibility(View.GONE);
ibFull.setVisibility(View.GONE);
tvBody.setVisibility(View.GONE);
vwBody.setVisibility(View.GONE);
pbBody.setVisibility(View.GONE);
tvNoInternetBody.setVisibility(View.GONE);
rvImage.setVisibility(View.GONE);
}
2019-02-04 11:45:38 +00:00
private void bindFlagged(TupleMessageEx message) {
int flagged = (message.count - message.unflagged);
ivFlagged.setImageResource(flagged > 0 ? R.drawable.baseline_star_24 : R.drawable.baseline_star_border_24);
ivFlagged.setImageTintList(ColorStateList.valueOf(flagged > 0 ? colorAccent : textColorSecondary));
2019-03-02 15:54:38 +00:00
ivFlagged.setVisibility(flags ? (message.uid == null ? View.INVISIBLE : View.VISIBLE) : View.GONE);
2019-02-04 11:45:38 +00:00
}
private void bindContactInfo(ContactInfo info, TupleMessageEx message) {
2019-04-25 21:20:20 +02:00
if (info.hasPhoto())
ivAvatar.setImageBitmap(info.getPhotoBitmap());
else
ivAvatar.setImageResource(R.drawable.baseline_person_24);
ivAvatar.setVisibility(avatars ? View.VISIBLE : View.GONE);
tvFrom.setText(info.getDisplayName(name_email));
}
private void bindExpanded(final TupleMessageEx message) {
DB db = DB.getInstance(context);
boolean show_addresses = !properties.getValue("addresses", message.id);
boolean show_headers = properties.getValue("headers", message.id);
boolean show_html = properties.getValue("html", message.id);
grpExpanded.setVisibility(View.VISIBLE);
2019-03-09 10:02:17 +00:00
boolean hasFrom = (message.from != null && message.from.length > 0);
boolean hasChannel = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
ivSearchContact.setVisibility(show_addresses && search && BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
2019-03-09 10:02:17 +00:00
ivNotifyContact.setVisibility(show_addresses && hasChannel && hasFrom ? View.VISIBLE : View.GONE);
ivAddContact.setVisibility(show_addresses && contacts && hasFrom ? View.VISIBLE : View.GONE);
grpHeaders.setVisibility(show_headers ? View.VISIBLE : View.GONE);
if (show_headers && message.headers == null) {
2019-03-16 13:12:31 +00:00
pbHeaders.setVisibility(suitable ? View.VISIBLE : View.GONE);
tvNoInternetHeaders.setVisibility(suitable ? View.GONE : View.VISIBLE);
2019-02-04 12:17:42 +00:00
} else {
pbHeaders.setVisibility(View.GONE);
tvNoInternetHeaders.setVisibility(View.GONE);
}
grpAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
bnvActions.setTag(null);
for (int i = 0; i < bnvActions.getMenu().size(); i++)
bnvActions.getMenu().getItem(i).setVisible(false);
2019-05-02 09:02:39 +02:00
tbHtml.setChecked(show_html);
tbHtml.setVisibility(hasWebView ? View.INVISIBLE : View.GONE);
ibImages.setVisibility(!show_html ? View.INVISIBLE : View.GONE);
ibFull.setVisibility(show_html ? View.INVISIBLE : View.GONE);
tvBody.setTypeface(monospaced ? Typeface.MONOSPACE : Typeface.DEFAULT);
tvBody.setVisibility(!show_html ? View.INVISIBLE : View.GONE);
vwBody.setVisibility(show_html ? View.INVISIBLE : View.GONE);
// Addresses
2019-04-28 10:44:07 +02:00
ivExpanderAddress.setImageLevel(show_addresses ? 0 /* less */ : 1 /* more */);
2019-03-09 10:02:17 +00:00
String from = MessageHelper.formatAddresses(message.from);
String to = MessageHelper.formatAddresses(message.to);
String replyto = MessageHelper.formatAddresses(message.reply);
String cc = MessageHelper.formatAddresses(message.cc);
String bcc = MessageHelper.formatAddresses(message.bcc);
boolean self = false;
InternetAddress via = null;
if (message.identityEmail != null)
try {
via = new InternetAddress(message.identityEmail, message.identityName);
if (message.to != null) {
String v = Helper.canonicalAddress(via.getAddress());
for (Address t : message.to) {
if (v.equals(Helper.canonicalAddress(((InternetAddress) t).getAddress()))) {
self = true;
break;
}
}
}
} catch (UnsupportedEncodingException ignored) {
}
2019-03-09 10:02:17 +00:00
tvFromExTitle.setVisibility(show_addresses && !TextUtils.isEmpty(from) ? View.VISIBLE : View.GONE);
tvFromEx.setVisibility(show_addresses && !TextUtils.isEmpty(from) ? View.VISIBLE : View.GONE);
tvFromEx.setText(from);
tvToTitle.setVisibility(show_addresses && !TextUtils.isEmpty(to) ? View.VISIBLE : View.GONE);
tvTo.setVisibility(show_addresses && !TextUtils.isEmpty(to) ? View.VISIBLE : View.GONE);
tvTo.setText(to);
tvToTitle.setTextColor(self ? textColorSecondary : colorWarning);
tvTo.setTextColor(self ? textColorSecondary : colorWarning);
2019-03-09 10:02:17 +00:00
tvReplyToTitle.setVisibility(show_addresses && !TextUtils.isEmpty(replyto) ? View.VISIBLE : View.GONE);
tvReplyTo.setVisibility(show_addresses && !TextUtils.isEmpty(replyto) ? View.VISIBLE : View.GONE);
tvReplyTo.setText(replyto);
tvCcTitle.setVisibility(show_addresses && !TextUtils.isEmpty(cc) ? View.VISIBLE : View.GONE);
tvCc.setVisibility(show_addresses && !TextUtils.isEmpty(cc) ? View.VISIBLE : View.GONE);
tvCc.setText(cc);
tvBccTitle.setVisibility(show_addresses && !TextUtils.isEmpty(bcc) ? View.VISIBLE : View.GONE);
tvBcc.setVisibility(show_addresses && !TextUtils.isEmpty(bcc) ? View.VISIBLE : View.GONE);
tvBcc.setText(bcc);
2019-03-31 08:35:31 +02:00
tvIdentityTitle.setVisibility(show_addresses && via != null ? View.VISIBLE : View.GONE);
tvIdentity.setVisibility(show_addresses && via != null ? View.VISIBLE : View.GONE);
2019-03-10 08:53:59 +00:00
tvIdentity.setText(via == null ? null : MessageHelper.formatAddresses(new Address[]{via}));
2019-03-09 10:02:17 +00:00
tvTimeExTitle.setVisibility(show_addresses ? View.VISIBLE : View.GONE);
tvTimeEx.setVisibility(show_addresses ? View.VISIBLE : View.GONE);
tvTimeEx.setText(DTF.format(message.received));
if (!message.duplicate)
tvSizeEx.setAlpha(message.content ? 1.0f : Helper.LOW_LIGHT);
2019-03-09 10:02:17 +00:00
tvSizeExTitle.setVisibility(!show_addresses || message.size == null ? View.GONE : View.VISIBLE);
tvSizeEx.setVisibility(!show_addresses || message.size == null ? View.GONE : View.VISIBLE);
tvSizeEx.setText(message.size == null ? null : Helper.humanReadableByteCount(message.size, true));
2019-03-09 10:02:17 +00:00
tvSubjectEx.setVisibility(show_addresses ? View.VISIBLE : View.GONE);
tvSubjectEx.setText(message.subject);
2019-03-10 07:29:44 +00:00
tvSubjectEx.setTypeface(null, subject_italic ? Typeface.ITALIC : Typeface.NORMAL);
// Flags
2019-03-09 10:02:17 +00:00
tvFlags.setVisibility(show_addresses && debug ? View.VISIBLE : View.GONE);
tvFlags.setText(message.flags);
// Keywords
2019-03-09 10:02:17 +00:00
tvKeywords.setVisibility(show_addresses && message.keywords.length > 0 ? View.VISIBLE : View.GONE);
tvKeywords.setText(TextUtils.join(" ", message.keywords));
// Headers
if (show_headers && message.headers != null) {
SpannableStringBuilder ssb = new SpannableStringBuilder(message.headers);
int index = 0;
for (String line : message.headers.split("\n")) {
if (line.length() > 0 && !Character.isWhitespace(line.charAt(0))) {
int colon = line.indexOf(':');
if (colon > 0)
ssb.setSpan(new StyleSpan(Typeface.BOLD), index, index + colon, 0);
}
index += line.length() + 1;
}
tvHeaders.setText(ssb);
} else
tvHeaders.setText(null);
// Attachments
2019-04-04 14:47:56 +02:00
bindAttachments(message, properties.getAttachments(message.id));
2019-04-13 08:02:01 +02:00
cowner.recreate();
cowner.start();
2019-03-17 20:31:24 +00:00
db.attachment().liveAttachments(message.id).observe(cowner, new Observer<List<EntityAttachment>>() {
@Override
public void onChanged(@Nullable List<EntityAttachment> attachments) {
2019-02-04 12:17:42 +00:00
bindAttachments(message, attachments);
}
2019-03-17 20:31:24 +00:00
});
// Setup actions
Bundle sargs = new Bundle();
sargs.putLong("id", message.id);
sargs.putLong("account", message.account);
new SimpleTask<List<EntityFolder>>() {
@Override
protected List<EntityFolder> onExecute(Context context, Bundle args) {
long account = args.getLong("account");
return DB.getInstance(context).folder().getSystemFolders(account);
}
@Override
protected void onExecuted(Bundle args, List<EntityFolder> folders) {
long id = args.getLong("id");
TupleMessageEx amessage = getMessage();
if (amessage == null || !amessage.id.equals(id))
return;
boolean hasArchive = false;
boolean hasTrash = false;
boolean hasJunk = false;
if (folders != null)
for (EntityFolder folder : folders) {
if (EntityFolder.ARCHIVE.equals(folder.type))
hasArchive = true;
else if (EntityFolder.TRASH.equals(folder.type))
hasTrash = true;
else if (EntityFolder.JUNK.equals(folder.type))
hasJunk = true;
}
boolean inOutbox = EntityFolder.OUTBOX.equals(message.folderType);
boolean inArchive = EntityFolder.ARCHIVE.equals(message.folderType);
boolean inTrash = EntityFolder.TRASH.equals(message.folderType);
ActionData data = new ActionData();
data.hasJunk = hasJunk;
data.delete = (inTrash || !hasTrash || inOutbox);
data.message = message;
bnvActions.setTag(data);
bnvActions.getMenu().findItem(R.id.action_more).setVisible(!inOutbox);
bnvActions.getMenu().findItem(R.id.action_delete).setVisible(debug ||
(inTrash && message.msgid != null) ||
(!inTrash && hasTrash && message.uid != null) ||
(inOutbox && (!TextUtils.isEmpty(message.error) || !message.identitySynchronize)));
bnvActions.getMenu().findItem(R.id.action_delete).setTitle(inTrash ? R.string.title_delete : R.string.title_trash);
bnvActions.getMenu().findItem(R.id.action_move).setVisible(
message.uid != null || (inOutbox && (message.ui_snoozed != null || message.error != null)));
bnvActions.getMenu().findItem(R.id.action_move).setTitle(
inOutbox && (message.ui_snoozed != null || message.error != null)
? R.string.title_folder_drafts : R.string.title_move);
bnvActions.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inArchive && hasArchive);
bnvActions.getMenu().findItem(R.id.action_reply).setEnabled(message.content);
bnvActions.getMenu().findItem(R.id.action_reply).setVisible(!inOutbox);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, sargs, "message:actions");
// Message text
2019-03-16 13:12:31 +00:00
pbBody.setVisibility(suitable || message.content ? View.VISIBLE : View.GONE);
tvNoInternetBody.setVisibility(suitable || message.content ? View.GONE : View.VISIBLE);
2019-05-04 11:16:33 +02:00
if (show_html)
onShowHtmlConfirmed(message);
else {
Spanned body = properties.getBody(message.id);
tvBody.setText(body);
tvBody.setMovementMethod(null);
2019-02-10 14:13:23 +00:00
2019-05-04 11:16:33 +02:00
showText(message);
}
}
2019-02-04 12:17:42 +00:00
private void bindAttachments(final TupleMessageEx message, @Nullable List<EntityAttachment> attachments) {
if (attachments == null)
attachments = new ArrayList<>();
2019-04-04 14:47:56 +02:00
properties.setAttchments(message.id, attachments);
2019-02-04 12:17:42 +00:00
boolean show_inline = properties.getValue("inline", message.id);
Log.i("Show inline=" + show_inline);
2019-02-17 12:05:58 +00:00
boolean has_inline = false;
2019-02-04 12:17:42 +00:00
boolean download = false;
boolean save = (attachments.size() > 1);
boolean downloading = false;
List<EntityAttachment> a = new ArrayList<>();
for (EntityAttachment attachment : attachments) {
2019-02-17 12:05:58 +00:00
boolean inline = (TextUtils.isEmpty(attachment.name) ||
(attachment.isInline() && attachment.type.startsWith("image/")));
if (inline)
has_inline = true;
2019-02-04 12:17:42 +00:00
if (attachment.progress == null && !attachment.available)
download = true;
if (!attachment.available)
save = false;
if (attachment.progress != null)
downloading = true;
2019-02-17 12:05:58 +00:00
if (show_inline || !inline)
2019-02-04 12:17:42 +00:00
a.add(attachment);
}
adapterAttachment.set(a);
cbInline.setOnCheckedChangeListener(null);
cbInline.setChecked(show_inline);
2019-02-17 12:05:58 +00:00
cbInline.setVisibility(has_inline ? View.VISIBLE : View.GONE);
2019-03-16 13:12:31 +00:00
btnDownloadAttachments.setVisibility(download && suitable ? View.VISIBLE : View.GONE);
2019-02-04 12:17:42 +00:00
btnSaveAttachments.setVisibility(save ? View.VISIBLE : View.GONE);
2019-03-16 13:12:31 +00:00
tvNoInternetAttachments.setVisibility(downloading && !suitable ? View.VISIBLE : View.GONE);
2019-02-04 12:17:42 +00:00
cbInline.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
properties.setValue("inline", message.id, isChecked);
2019-03-17 20:31:24 +00:00
cowner.restart();
DB.getInstance(context).attachment().liveAttachments(message.id).observe(cowner, new Observer<List<EntityAttachment>>() {
@Override
public void onChanged(@Nullable List<EntityAttachment> attachments) {
bindAttachments(message, attachments);
}
});
2019-02-04 12:17:42 +00:00
}
});
List<EntityAttachment> images = new ArrayList<>();
for (EntityAttachment attachment : attachments)
2019-04-19 08:31:00 +02:00
if (!attachment.isInline() && attachment.type.startsWith("image/"))
2019-02-04 12:17:42 +00:00
images.add(attachment);
adapterImage.set(images);
boolean show_html = properties.getValue("html", message.id);
2019-05-04 11:16:33 +02:00
if (show_html)
onShowHtmlConfirmed(message);
else
showText(message);
2019-02-04 12:17:42 +00:00
}
2019-02-01 09:29:26 +00:00
private TupleMessageEx getMessage() {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
2019-02-01 09:29:26 +00:00
return null;
return differ.getItem(pos);
}
2018-10-15 10:05:42 +00:00
2019-02-01 09:29:26 +00:00
@Override
public void onClick(View view) {
TupleMessageEx message = getMessage();
2019-01-26 10:09:40 +00:00
if (message == null)
return;
2018-08-02 13:33:06 +00:00
2019-01-07 15:05:24 +00:00
if (view.getId() == R.id.ivSnoozed)
onShowSnoozed(message);
else if (view.getId() == R.id.ivFlagged)
2018-12-04 17:08:36 +01:00
onToggleFlag(message);
2019-01-27 13:50:21 +00:00
else if (view.getId() == R.id.ivSearchContact)
onSearchContact(message);
2019-03-07 12:29:03 +00:00
else if (view.getId() == R.id.ivNotifyContact)
onNotifyContact(message);
2018-12-04 17:08:36 +01:00
else if (view.getId() == R.id.ivAddContact)
2018-10-15 10:05:42 +00:00
onAddContact(message);
else if (viewType == ViewType.THREAD) {
2018-11-04 16:16:45 +00:00
if (view.getId() == R.id.ivExpanderAddress)
2019-02-01 09:29:26 +00:00
onToggleAddresses(message);
2019-01-05 12:00:36 +00:00
else if (view.getId() == R.id.btnDownloadAttachments)
onDownloadAttachments(message);
2019-01-05 14:56:19 +00:00
else if (view.getId() == R.id.btnSaveAttachments)
onSaveAttachments(message);
2018-12-21 15:19:07 +01:00
else if (view.getId() == R.id.ibImages)
2018-10-15 10:05:42 +00:00
onShowImages(message);
else if (view.getId() == R.id.ibFull)
onShowFull(message);
2018-10-15 10:05:42 +00:00
else
2019-02-01 09:29:26 +00:00
onToggleMessage(message);
2018-10-15 10:05:42 +00:00
} else {
2019-03-08 10:27:33 +00:00
vwRipple.setPressed(true);
vwRipple.setPressed(false);
2019-02-05 10:28:38 +00:00
if (EntityFolder.DRAFTS.equals(message.folderType) && message.visible == 1)
2018-10-15 10:05:42 +00:00
context.startActivity(
new Intent(context, ActivityCompose.class)
.putExtra("action", "edit")
.putExtra("id", message.id));
else {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_THREAD)
2018-10-17 09:15:44 +00:00
.putExtra("account", message.account)
.putExtra("thread", message.thread)
2019-02-05 08:15:05 +00:00
.putExtra("id", message.id)
.putExtra("found", viewType == ViewType.SEARCH));
2018-10-15 10:05:42 +00:00
}
}
}
@Override
public boolean onLongClick(View v) {
TupleMessageEx message = getMessage();
if (message == null)
return false;
onNotifyContactDelete(message);
return true;
}
2019-05-02 09:02:39 +02:00
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
TupleMessageEx message = getMessage();
if (message == null)
return;
if (isChecked)
onShowHtml(message);
else
onHideHtml(message);
}
2019-01-07 17:50:23 +00:00
private void onShowSnoozed(TupleMessageEx message) {
if (message.ui_snoozed != null) {
DateFormat df = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT);
DateFormat day = new SimpleDateFormat("E");
Toast.makeText(context,
day.format(message.ui_snoozed) + " " + df.format(message.ui_snoozed),
Toast.LENGTH_LONG).show();
}
2019-01-07 17:50:23 +00:00
}
private void onToggleFlag(TupleMessageEx message) {
2019-03-30 08:05:51 +00:00
int flagged = (message.count - message.unflagged);
Log.i("Set message id=" + message.id + " flagged=" + flagged);
2019-01-30 07:50:20 +00:00
2019-01-07 17:50:23 +00:00
Bundle args = new Bundle();
args.putLong("id", message.id);
2019-03-30 08:05:51 +00:00
args.putBoolean("flagged", flagged == 0);
2019-01-07 17:50:23 +00:00
args.putBoolean("thread", viewType != ViewType.THREAD);
2019-01-30 07:50:20 +00:00
message.unflagged = message.ui_flagged ? message.count : 0;
message.ui_flagged = !message.ui_flagged;
bindFlagged(message);
2019-01-07 17:50:23 +00:00
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean flagged = args.getBoolean("flagged");
boolean thread = args.getBoolean("thread");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
List<EntityMessage> messages = db.message().getMessageByThread(
2019-03-30 08:05:51 +00:00
message.account, message.thread, threading && thread ? null : id, null);
2019-01-07 17:50:23 +00:00
for (EntityMessage threaded : messages)
EntityOperation.queue(context, db, threaded, EntityOperation.FLAG, flagged);
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:flag");
}
2019-01-27 13:50:21 +00:00
private void onSearchContact(TupleMessageEx message) {
2019-01-27 14:38:53 +00:00
Bundle args = new Bundle();
args.putLong("id", message.id);
new SimpleTask<Address[]>() {
@Override
protected Address[] 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;
EntityFolder folder = db.folder().getFolder(message.folder);
boolean outgoing;
if (message.identity == null || message.from == null || message.from.length == 0)
outgoing = EntityFolder.isOutgoing(folder.type);
else {
String from = ((InternetAddress) message.from[0]).getAddress();
EntityIdentity identity = db.identity().getIdentity(message.identity);
outgoing = Helper.canonicalAddress(identity.email).equals(Helper.canonicalAddress(from));
}
return (outgoing
? message.to
: (message.reply == null || message.reply.length == 0 ? message.from : message.reply));
}
@Override
protected void onExecuted(Bundle args, Address[] addresses) {
if (addresses != null && addresses.length > 0) {
Intent search = new Intent(context, ActivityView.class);
search.putExtra(Intent.EXTRA_PROCESS_TEXT, ((InternetAddress) addresses[0]).getAddress());
context.startActivity(search);
}
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:search");
2019-01-27 13:50:21 +00:00
}
2019-03-07 12:29:03 +00:00
@TargetApi(Build.VERSION_CODES.O)
private void onNotifyContact(TupleMessageEx message) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
InternetAddress from = (InternetAddress) message.from[0];
2019-03-17 18:07:53 +00:00
String channelId = "notification." + from.getAddress().toLowerCase();
2019-03-07 12:29:03 +00:00
NotificationChannel channel = new NotificationChannel(
2019-03-17 18:07:53 +00:00
channelId, from.getAddress(),
2019-03-07 12:29:03 +00:00
NotificationManager.IMPORTANCE_HIGH);
2019-03-17 18:06:51 +00:00
channel.setGroup("contacts");
2019-03-07 12:29:03 +00:00
channel.setDescription(from.getPersonal());
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(channel);
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName())
2019-03-17 18:07:53 +00:00
.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
2019-03-07 12:29:03 +00:00
context.startActivity(intent);
}
@TargetApi(Build.VERSION_CODES.O)
private void onNotifyContactDelete(TupleMessageEx message) {
2019-04-28 16:05:54 +02:00
if (message.from != null && message.from.length > 0) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
2019-04-28 16:05:54 +02:00
InternetAddress from = (InternetAddress) message.from[0];
String channelName = "notification." + from.getAddress().toLowerCase();
nm.deleteNotificationChannel(channelName);
}
}
2018-11-13 10:22:41 +01:00
private void onAddContact(TupleMessageEx message) {
2018-10-15 10:05:42 +00:00
for (Address address : message.from) {
InternetAddress ia = (InternetAddress) address;
String name = ia.getPersonal();
String email = ia.getAddress();
// https://developer.android.com/training/contacts-provider/modify-data
Intent edit = new Intent();
if (!TextUtils.isEmpty(name))
edit.putExtra(ContactsContract.Intents.Insert.NAME, name);
if (!TextUtils.isEmpty(email))
edit.putExtra(ContactsContract.Intents.Insert.EMAIL, email);
2019-02-22 16:59:23 +01:00
ContentResolver resolver = context.getContentResolver();
try (Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Photo.CONTACT_ID,
ContactsContract.Contacts.LOOKUP_KEY
},
ContactsContract.CommonDataKinds.Email.ADDRESS + " = ?",
new String[]{email}, null)) {
2018-12-24 10:51:32 +00:00
if (cursor != null && cursor.moveToNext()) {
2018-10-15 10:05:42 +00:00
int colContactId = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.CONTACT_ID);
int colLookupKey = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
long contactId = cursor.getLong(colContactId);
String lookupKey = cursor.getString(colLookupKey);
Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
edit.setAction(Intent.ACTION_EDIT);
edit.setDataAndType(lookupUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE);
} else {
edit.setAction(Intent.ACTION_INSERT);
edit.setType(ContactsContract.Contacts.CONTENT_TYPE);
}
}
2019-01-23 19:32:37 +00:00
PackageManager pm = context.getPackageManager();
if (edit.resolveActivity(pm) == null)
Toast.makeText(context, R.string.title_no_contacts, Toast.LENGTH_LONG).show();
else
context.startActivity(edit);
2018-10-15 10:05:42 +00:00
}
}
2019-02-01 09:29:26 +00:00
private void onToggleMessage(TupleMessageEx message) {
2018-11-13 10:22:41 +01:00
if (EntityFolder.DRAFTS.equals(message.folderType))
context.startActivity(
new Intent(context, ActivityCompose.class)
.putExtra("action", "edit")
.putExtra("id", message.id));
else {
2018-12-21 08:32:26 +01:00
boolean expanded = !properties.getValue("expanded", message.id);
properties.setValue("expanded", message.id, expanded);
2018-12-22 09:06:33 +01:00
2019-04-28 10:44:07 +02:00
ivExpander.setImageLevel(expanded ? 0 /* less*/ : 1 /* more */);
2019-04-17 09:38:24 +02:00
2019-04-17 09:19:56 +02:00
if (expanded) {
bindExpanded(message);
2019-04-27 10:40:06 +02:00
properties.scrollTo(getAdapterPosition());
2019-04-17 09:19:56 +02:00
} else
clearExpanded();
2018-11-13 10:22:41 +01:00
}
2018-11-04 16:16:45 +00:00
}
2019-02-01 09:29:26 +00:00
private void onToggleAddresses(TupleMessageEx message) {
2018-12-21 08:32:26 +01:00
boolean addresses = !properties.getValue("addresses", message.id);
properties.setValue("addresses", message.id, addresses);
2019-04-17 09:19:56 +02:00
bindExpanded(message);
2018-11-04 16:16:45 +00:00
}
2019-01-05 14:56:19 +00:00
private void onDownloadAttachments(final TupleMessageEx message) {
Bundle args = new Bundle();
args.putLong("id", message.id);
new SimpleTask<Void>() {
@Override
2019-03-02 08:48:02 +00:00
protected Void onExecute(Context context, Bundle args) {
2019-01-05 14:56:19 +00:00
long id = args.getLong("id");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage msg = db.message().getMessage(id);
2019-03-02 08:48:02 +00:00
if (msg != null)
2019-01-05 14:56:19 +00:00
for (EntityAttachment attachment : db.attachment().getAttachments(message.id))
if (attachment.progress == null && !attachment.available) {
db.attachment().setProgress(attachment.id, 0);
EntityOperation.queue(context, db, msg, EntityOperation.ATTACHMENT, attachment.sequence);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:attachment:download");
2019-01-05 14:56:19 +00:00
}
private void onSaveAttachments(TupleMessageEx message) {
2019-01-05 12:00:36 +00:00
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_STORE_ATTACHMENTS)
.putExtra("id", message.id));
}
2018-11-13 10:22:41 +01:00
private void onShowHtml(final TupleMessageEx message) {
2019-01-30 08:47:54 +00:00
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2019-05-02 09:02:39 +02:00
if (properties.getValue("confirmed", message.id) ||
prefs.getBoolean("show_html_confirmed", false)) {
onShowHtmlConfirmed(message);
2019-01-30 08:47:54 +00:00
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) {
2019-05-02 09:02:39 +02:00
properties.setValue("confirmed", message.id, true);
2019-01-30 08:47:54 +00:00
if (cbNotAgain.isChecked())
prefs.edit().putBoolean("show_html_confirmed", true).apply();
onShowHtmlConfirmed(message);
}
})
2019-05-02 09:02:39 +02:00
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
tbHtml.setChecked(false);
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
tbHtml.setChecked(false);
}
})
2019-01-30 08:47:54 +00:00
.show();
}
2019-03-13 16:58:20 +00:00
@SuppressLint("ClickableViewAccessibility")
private void onShowHtmlConfirmed(final TupleMessageEx message) {
2019-02-03 09:38:48 +00:00
properties.setValue("html", message.id, true);
2019-03-28 07:14:45 +00:00
boolean show_images = properties.getValue("images", message.id);
2019-05-02 09:02:39 +02:00
tbHtml.setVisibility(View.VISIBLE);
ibImages.setVisibility(show_images ? View.GONE : View.INVISIBLE);
ibFull.setVisibility(View.INVISIBLE);
rvImage.setVisibility(adapterImage.getItemCount() > 0 ? View.INVISIBLE : View.GONE);
2019-02-03 09:38:48 +00:00
// For performance reasons the WebView is created when needed only
2019-02-03 09:38:48 +00:00
if (!(vwBody instanceof WebView)) {
WebView webView = new WebView(context) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
2019-04-27 10:40:06 +02:00
setMeasuredDimension(
getMeasuredWidth(),
Math.max(tvBody.getMinHeight(), getMeasuredHeight()));
}
2019-02-03 09:38:48 +00:00
};
2019-03-28 07:28:48 +00:00
setupWebView(webView);
2019-04-27 11:01:21 +02:00
2019-02-03 09:38:48 +00:00
webView.setId(vwBody.getId());
2019-02-03 15:42:51 +00:00
webView.setVisibility(vwBody.getVisibility());
2019-02-03 09:38:48 +00:00
2019-03-15 11:53:22 +00:00
ConstraintLayout cl = (ConstraintLayout) view;
2019-02-03 09:38:48 +00:00
cl.removeView(vwBody);
cl.addView(webView, vwBody.getLayoutParams());
vwBody = webView;
}
final WebView webView = (WebView) vwBody;
2019-04-29 19:31:19 +02:00
webView.setBackgroundColor(Color.TRANSPARENT);
2019-05-02 09:57:35 +02:00
WebSettings settings = webView.getSettings();
settings.setDefaultFontSize(Math.round(textSize));
settings.setDefaultFixedFontSize(Math.round(textSize));
settings.setLoadsImagesAutomatically(show_images);
2019-03-11 16:28:05 +00:00
2019-02-03 15:42:51 +00:00
String html = properties.getHtml(message.id);
if (TextUtils.isEmpty(html)) {
2019-04-27 11:01:21 +02:00
webView.loadUrl("about:blank");
2019-05-04 11:16:33 +02:00
if (!message.content)
return;
2019-02-03 15:42:51 +00:00
Bundle args = new Bundle();
args.putLong("id", message.id);
2019-02-03 09:38:48 +00:00
new SimpleTask<OriginalMessage>() {
2019-02-03 15:42:51 +00:00
@Override
protected OriginalMessage onExecute(Context context, Bundle args) throws IOException {
2019-02-03 15:42:51 +00:00
long id = args.getLong("id");
2019-04-06 11:05:33 +02:00
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message == null)
throw new FileNotFoundException();
OriginalMessage original = new OriginalMessage();
2019-04-06 11:05:33 +02:00
original.html = Helper.readText(message.getFile(context));
original.html = HtmlHelper.getHtmlEmbedded(context, id, original.html);
original.html = HtmlHelper.removeTracking(context, original.html);
Document doc = Jsoup.parse(original.html);
2019-04-12 18:04:37 +02:00
original.has_images = (doc.select("img").size() > 0);
return original;
2019-02-03 15:42:51 +00:00
}
2019-02-03 09:38:48 +00:00
2019-02-03 15:42:51 +00:00
@Override
protected void onExecuted(Bundle args, OriginalMessage original) {
2019-02-06 07:22:46 +00:00
long id = args.getLong("id");
properties.setHtml(id, original.html);
if (!original.has_images)
properties.setValue("images", id, true);
2019-02-06 07:22:46 +00:00
TupleMessageEx amessage = getMessage();
if (amessage == null || !amessage.id.equals(id))
return;
boolean expanded = properties.getValue("expanded", id);
if (!expanded)
return;
boolean show_images = properties.getValue("images", id);
ibFull.setVisibility(View.VISIBLE);
ibImages.setVisibility(show_images ? View.GONE : View.VISIBLE);
2019-05-03 10:53:34 +02:00
webView.loadDataWithBaseURL("about:blank", themeHtml(original.html), "text/html", "UTF-8", null);
2019-02-03 09:38:48 +00:00
2019-02-03 15:42:51 +00:00
pbBody.setVisibility(View.GONE);
tvBody.setVisibility(View.GONE);
webView.setVisibility(View.VISIBLE);
2019-02-03 15:42:51 +00:00
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:webview");
} else {
ibFull.setVisibility(View.VISIBLE);
ibImages.setVisibility(show_images ? View.GONE : View.VISIBLE);
2019-05-03 10:53:34 +02:00
webView.loadDataWithBaseURL("about:blank", themeHtml(html), "text/html", "UTF-8", null);
2019-02-03 15:42:51 +00:00
pbBody.setVisibility(View.GONE);
tvBody.setVisibility(View.GONE);
2019-02-03 15:42:51 +00:00
webView.setVisibility(View.VISIBLE);
}
2019-05-02 09:02:39 +02:00
}
private void onHideHtml(TupleMessageEx message) {
properties.setValue("html", message.id, false);
ibImages.setVisibility(View.INVISIBLE);
ibFull.setVisibility(View.GONE);
2019-05-02 09:02:39 +02:00
tvBody.setVisibility(View.INVISIBLE);
vwBody.setVisibility(View.GONE);
2019-05-04 11:16:33 +02:00
showText(message);
2018-11-11 11:24:34 +00:00
}
private class OriginalMessage {
String html;
boolean has_images;
}
2019-04-29 19:31:19 +02:00
private String themeHtml(String html) {
if (dark) {
String color = String.format("#%06X", (textColorSecondary & 0xFFFFFF));
return "<style type=\"text/css\">" +
"* { background: black !important; color: " + color + " !important }" +
"</style>" + html;
} else
return html;
}
private void onShowFull(TupleMessageEx message) {
WebView webView = new WebView(context);
setupWebView(webView);
boolean show_images = properties.getValue("images", message.id);
2019-04-29 21:02:54 +02:00
WebSettings settings = webView.getSettings();
settings.setDefaultFontSize(Math.round(textSize));
settings.setDefaultFixedFontSize(Math.round(textSize));
2019-04-29 21:02:54 +02:00
settings.setLoadsImagesAutomatically(show_images);
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
String html = properties.getHtml(message.id);
2019-05-03 10:53:34 +02:00
webView.loadDataWithBaseURL("about:blank", html, "text/html", "UTF-8", null);
final Dialog dialog = new Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
dialog.setContentView(webView);
owner.getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
dialog.show();
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroyed() {
dialog.dismiss();
}
});
}
private void setupWebView(WebView webView) {
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.i("Open url=" + url);
Uri uri = Uri.parse(url);
if ("cid".equals(uri.getScheme()) || "data".equals(uri.getScheme()))
return false;
onOpenLink(uri);
return true;
}
});
webView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(
String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Log.i("Download url=" + url + " mime type=" + mimetype);
Uri uri = Uri.parse(url);
Helper.view(context, owner, uri, true);
}
});
webView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
WebView.HitTestResult result = ((WebView) view).getHitTestResult();
if (result.getType() == WebView.HitTestResult.IMAGE_TYPE ||
result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
Log.i("Long press url=" + result.getExtra());
Uri uri = Uri.parse(result.getExtra());
Helper.view(context, owner, uri, true);
return true;
}
return false;
}
});
WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
settings.setAllowFileAccess(false);
if (monospaced)
settings.setStandardFontFamily("monospace");
}
2018-11-13 10:22:41 +01:00
private void onShowImages(final TupleMessageEx message) {
2019-01-30 08:47:54 +00:00
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("show_images_confirmed", false)) {
onShowImagesConfirmed(message);
2019-01-30 08:47:54 +00:00
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_image));
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("show_images_confirmed", true).apply();
onShowImagesConfirmed(message);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
2018-10-15 10:05:42 +00:00
private void onShowImagesConfirmed(final TupleMessageEx message) {
2018-12-21 08:32:26 +01:00
properties.setValue("images", message.id, true);
2019-05-04 11:16:33 +02:00
ibImages.setVisibility(View.GONE);
2019-03-28 07:14:45 +00:00
boolean show_html = properties.getValue("html", message.id);
if (show_html)
onShowHtmlConfirmed(message);
2019-05-04 11:16:33 +02:00
else
showText(message);
// Download inline images
2019-05-04 11:16:33 +02:00
Bundle args = new Bundle();
args.putSerializable("message", message);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
TupleMessageEx message = (TupleMessageEx) args.getSerializable("message");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
for (EntityAttachment attachment : attachments)
if (!attachment.available && !TextUtils.isEmpty(attachment.cid))
EntityOperation.queue(context, db, message, EntityOperation.ATTACHMENT, attachment.sequence);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "show:images");
2018-10-15 10:05:42 +00:00
}
2019-05-04 11:16:33 +02:00
private void showText(TupleMessageEx message) {
if (message.content) {
boolean show_images = properties.getValue("images", message.id);
boolean show_quotes = properties.getValue("quotes", message.id);
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.execute(context, owner, args, "message:body");
}
}
2018-12-21 18:21:35 +01:00
private SimpleTask<SpannableStringBuilder> bodyTask = new SimpleTask<SpannableStringBuilder>() {
2018-12-21 08:49:31 +01:00
@Override
2019-05-04 11:16:33 +02:00
protected SpannableStringBuilder onExecute(final Context context, final Bundle args) throws IOException {
2018-12-24 20:09:47 +00:00
DB db = DB.getInstance(context);
2019-05-03 13:08:20 +02:00
final TupleMessageEx message = (TupleMessageEx) args.getSerializable("message");
final boolean show_images = args.getBoolean("show_images");
2019-05-02 13:29:05 +02:00
boolean show_quotes = args.getBoolean("show_quotes");
2019-05-03 08:42:44 +02:00
int zoom = args.getInt("zoom");
2019-02-06 18:40:12 +00:00
2019-05-04 11:16:33 +02:00
String body = Helper.readText(message.getFile(context));
2018-12-20 21:12:28 +01:00
if (!show_quotes) {
Document document = Jsoup.parse(body);
for (Element quote : document.select("blockquote"))
quote.html("&#8230;");
body = document.html();
}
2019-05-03 13:08:20 +02:00
String html = HtmlHelper.sanitize(context, body);
if (debug)
html += "<pre>" + Html.escapeHtml(html) + "</pre>";
2018-12-20 21:12:28 +01:00
2019-05-03 13:08:20 +02:00
Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
2019-05-03 17:20:24 +02:00
Drawable image = HtmlHelper.decodeImage(source, message.id, show_images, tvBody);
2019-05-03 17:27:29 +02:00
float width = tvBody.getWidth();
2019-05-03 17:20:24 +02:00
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;
2019-05-03 13:08:20 +02:00
}
}, null);
SpannableStringBuilder builder = new SpannableStringBuilder(spanned);
2019-01-08 12:29:05 +00:00
QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class);
for (QuoteSpan quoteSpan : quoteSpans) {
2018-12-20 21:12:28 +01:00
builder.setSpan(
2019-01-08 12:29:05 +00:00
new StyledQuoteSpan(colorPrimary),
builder.getSpanStart(quoteSpan),
builder.getSpanEnd(quoteSpan),
builder.getSpanFlags(quoteSpan));
builder.removeSpan(quoteSpan);
2018-12-20 21:12:28 +01:00
}
2019-05-02 13:29:05 +02:00
if (!show_quotes) {
2019-05-03 08:42:44 +02:00
final int px = Helper.dp2pixels(context, 24 + (zoom) * 8);
2019-05-03 09:41:50 +02:00
2019-05-02 13:29:05 +02:00
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);
2019-05-03 08:42:44 +02:00
d.setBounds(0, 0, px, px);
2019-05-02 13:29:05 +02:00
return d;
}
},
builder.getSpanStart(squote),
builder.getSpanEnd(squote),
builder.getSpanFlags(squote));
}
2019-02-01 09:02:30 +00:00
args.putBoolean("has_images", builder.getSpans(0, body.length(), ImageSpan.class).length > 0);
2018-12-20 21:12:28 +01:00
return builder;
}
2018-10-15 10:05:42 +00:00
@Override
2018-12-31 07:03:48 +00:00
protected void onExecuted(Bundle args, SpannableStringBuilder body) {
2019-02-01 09:02:30 +00:00
TupleMessageEx message = (TupleMessageEx) args.getSerializable("message");
properties.setBody(message.id, body);
2019-02-01 09:29:26 +00:00
TupleMessageEx amessage = getMessage();
if (amessage == null || !amessage.id.equals(message.id))
return;
boolean show_expanded = properties.getValue("expanded", message.id);
if (!show_expanded)
return;
2019-02-01 09:02:30 +00:00
boolean has_images = args.getBoolean("has_images");
2018-12-21 08:32:26 +01:00
boolean show_images = properties.getValue("images", message.id);
2018-10-15 10:05:42 +00:00
2019-05-02 09:02:39 +02:00
tbHtml.setVisibility(hasWebView ? View.VISIBLE : View.GONE);
ibImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE);
2018-10-15 10:05:42 +00:00
tvBody.setText(body);
2019-02-09 10:41:28 +00:00
tvBody.setTextIsSelectable(false);
tvBody.setTextIsSelectable(true);
2019-05-02 13:29:05 +02:00
tvBody.setMovementMethod(new TouchHandler(message));
tvBody.setVisibility(View.VISIBLE);
pbBody.setVisibility(View.GONE);
2019-03-27 07:49:33 +00:00
rvImage.setVisibility(adapterImage.getItemCount() > 0 ? View.VISIBLE : View.GONE);
2018-09-23 04:28:13 +00:00
}
2018-10-15 10:05:42 +00:00
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-10-15 10:05:42 +00:00
}
};
2019-04-19 08:31:00 +02:00
private class TouchHandler extends ArrowKeyMovementMethod {
2019-05-02 13:29:05 +02:00
private TupleMessageEx message;
2019-04-19 08:31:00 +02:00
2019-05-02 13:29:05 +02:00
TouchHandler(TupleMessageEx message) {
this.message = message;
2019-04-19 08:31:00 +02:00
}
2019-02-17 19:08:09 +00:00
@Override
2018-10-15 10:05:42 +00:00
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
2019-02-17 19:08:09 +00:00
if (event.getAction() == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
2018-10-15 10:05:42 +00:00
2019-02-17 19:08:09 +00:00
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
2018-10-15 10:05:42 +00:00
2019-02-17 19:08:09 +00:00
x += widget.getScrollX();
y += widget.getScrollY();
2018-10-15 10:05:42 +00:00
2019-02-17 19:08:09 +00:00
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
2018-10-15 10:05:42 +00:00
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) {
Uri uri = Uri.parse(image[0].getSource());
if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
onOpenLink(uri);
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);
onOpenLink(uri);
return true;
}
2019-04-19 08:31:00 +02:00
2019-04-24 18:46:46 +02:00
ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class);
if (image.length > 0 && image[0].getSource() != null) {
onOpenImage(image[0].getDrawable());
return true;
2019-02-17 19:08:09 +00:00
}
2019-05-02 13:29:05 +02:00
DynamicDrawableSpan[] ddss = buffer.getSpans(off, off, DynamicDrawableSpan.class);
if (ddss.length > 0) {
properties.setValue("quotes", message.id, true);
2019-05-04 11:16:33 +02:00
showText(message);
2019-05-02 13:29:05 +02:00
}
2018-10-15 10:05:42 +00:00
}
2018-09-19 10:00:58 +00:00
2019-02-17 19:08:09 +00:00
return super.onTouchEvent(widget, buffer, event);
2018-10-15 10:05:42 +00:00
}
}
2018-09-19 10:00:58 +00:00
2019-03-09 15:46:49 +00:00
private void onOpenLink(final Uri uri) {
2019-05-03 10:37:14 +02:00
Log.i("Opening uri=" + uri);
2019-02-03 18:38:56 +00:00
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 {
2019-03-27 06:52:36 +00:00
if ("cid".equals(uri.getScheme()))
return;
2019-03-09 15:46:49 +00:00
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2019-04-17 13:37:14 +02:00
boolean paranoid = prefs.getBoolean("paranoid", true);
final Uri _uri;
2019-04-22 20:08:34 +02:00
if (paranoid && !uri.isOpaque()) {
2019-04-17 13:37:14 +02:00
// https://en.wikipedia.org/wiki/UTM_parameters
Uri.Builder builder = new Uri.Builder();
String scheme = uri.getScheme();
if (!TextUtils.isEmpty(scheme))
builder.scheme(scheme);
2019-04-18 07:48:03 +02:00
String authority = uri.getEncodedAuthority();
2019-04-17 13:37:14 +02:00
if (!TextUtils.isEmpty(authority))
2019-04-18 07:48:03 +02:00
builder.encodedAuthority(authority);
2019-04-17 13:37:14 +02:00
2019-04-18 07:48:03 +02:00
String path = uri.getEncodedPath();
2019-04-17 13:37:14 +02:00
if (!TextUtils.isEmpty(path))
2019-04-18 07:48:03 +02:00
builder.encodedPath(path);
2019-04-17 13:37:14 +02:00
2019-04-18 07:48:03 +02:00
for (String key : uri.getQueryParameterNames())
2019-04-17 13:37:14 +02:00
if (!PARANOID_QUERY.contains(key.toLowerCase()))
2019-04-18 07:48:03 +02:00
for (String value : uri.getQueryParameters(key))
builder.appendQueryParameter(key, value);
2019-04-17 13:37:14 +02:00
2019-04-18 07:48:03 +02:00
String fragment = uri.getEncodedFragment();
2019-04-17 13:37:14 +02:00
if (!TextUtils.isEmpty(fragment))
2019-04-18 07:48:03 +02:00
builder.encodedFragment(fragment);
2019-04-17 13:37:14 +02:00
_uri = builder.build();
2019-04-18 07:48:03 +02:00
Log.i("Source uri=" + uri);
Log.i("Target uri=" + _uri);
2019-04-17 13:37:14 +02:00
} else
_uri = uri;
2019-03-09 15:46:49 +00:00
2019-05-01 14:44:31 +02:00
View view = LayoutInflater.from(context).inflate(R.layout.dialog_open_link, null);
2019-02-03 18:38:56 +00:00
final EditText etLink = view.findViewById(R.id.etLink);
2019-03-03 16:07:09 +00:00
TextView tvInsecure = view.findViewById(R.id.tvInsecure);
final TextView tvOwner = view.findViewById(R.id.tvOwner);
Group grpOwner = view.findViewById(R.id.grpOwner);
2019-03-03 16:07:09 +00:00
etLink.setText(_uri.toString());
tvInsecure.setVisibility("http".equals(_uri.getScheme()) ? View.VISIBLE : View.GONE);
grpOwner.setVisibility(paranoid ? View.VISIBLE : View.GONE);
2019-03-09 15:46:49 +00:00
if (paranoid) {
Bundle args = new Bundle();
args.putParcelable("uri", _uri);
2019-03-09 15:46:49 +00:00
new SimpleTask<String>() {
@Override
protected void onPreExecute(Bundle args) {
tvOwner.setText("");
}
2019-03-09 15:46:49 +00:00
@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) {
tvOwner.setText(organization == null ? "?" : organization);
}
@Override
protected void onException(Bundle args, Throwable ex) {
tvOwner.setText(ex.getMessage());
}
}.execute(context, owner, args, "link:domain");
}
2019-03-09 15:46:49 +00:00
2019-03-03 16:07:09 +00:00
2019-02-03 18:38:56 +00:00
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();
}
}
2019-04-24 18:46:46 +02:00
private void onOpenImage(Drawable drawable) {
2019-04-19 08:31:00 +02:00
PhotoView pv = new PhotoView(context);
pv.setImageDrawable(drawable);
final Dialog dialog = new Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
dialog.setContentView(pv);
owner.getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
dialog.show();
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroyed() {
dialog.dismiss();
}
});
}
2018-10-15 10:05:42 +00:00
private class ActionData {
2018-10-21 14:53:39 +00:00
boolean hasJunk;
2018-10-15 10:05:42 +00:00
boolean delete;
TupleMessageEx message;
}
2018-09-19 10:00:58 +00:00
2018-10-15 10:05:42 +00:00
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
ActionData data = (ActionData) bnvActions.getTag();
2018-10-16 09:32:57 +00:00
if (data == null)
return false;
2018-10-15 10:05:42 +00:00
switch (item.getItemId()) {
case R.id.action_more:
2019-01-07 17:50:23 +00:00
onActionMore(data);
2018-10-15 10:05:42 +00:00
return true;
case R.id.action_delete:
2019-01-07 17:50:23 +00:00
onActionDelete(data);
2018-10-15 10:05:42 +00:00
return true;
case R.id.action_move:
2019-01-07 17:50:23 +00:00
onActionMove(data);
2018-10-15 10:05:42 +00:00
return true;
case R.id.action_archive:
2019-01-07 17:50:23 +00:00
onActionArchive(data);
2018-10-15 10:05:42 +00:00
return true;
case R.id.action_reply:
2019-03-02 08:48:02 +00:00
onActionReplyMenu(data);
2018-10-15 10:05:42 +00:00
return true;
default:
return false;
}
}
2018-09-19 10:00:58 +00:00
private void onMenuForward(final ActionData data) {
2018-10-15 10:05:42 +00:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<Boolean>() {
@Override
2018-12-31 07:03:48 +00:00
protected Boolean onExecute(Context context, Bundle args) {
2018-10-15 10:05:42 +00:00
long id = args.getLong("id");
List<EntityAttachment> attachments = DB.getInstance(context).attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (!attachment.available)
return false;
return true;
}
@Override
2018-12-31 07:03:48 +00:00
protected void onExecuted(Bundle args, Boolean available) {
2018-10-15 10:05:42 +00:00
final Intent forward = new Intent(context, ActivityCompose.class)
.putExtra("action", "forward")
.putExtra("reference", data.message.id);
2018-10-15 10:05:42 +00:00
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);
}
2018-09-19 10:00:58 +00:00
})
.setNegativeButton(android.R.string.cancel, null)
.show();
2018-10-15 10:05:42 +00:00
}
2018-09-19 10:00:58 +00:00
2018-10-15 10:05:42 +00:00
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-10-15 10:05:42 +00:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:forward");
2018-10-15 10:05:42 +00:00
}
2018-09-19 10:00:58 +00:00
2019-01-07 17:50:23 +00:00
private void onMenuUnseen(final ActionData data) {
2018-10-15 10:05:42 +00:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<Void>() {
@Override
2018-12-31 07:03:48 +00:00
protected Void onExecute(Context context, Bundle args) {
2018-10-15 10:05:42 +00:00
long id = args.getLong("id");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
2019-01-02 19:38:32 +01:00
if (message == null)
return null;
2019-01-04 18:37:56 +00:00
EntityOperation.queue(context, db, message, EntityOperation.SEEN, false);
2018-10-15 10:05:42 +00:00
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
2018-10-15 10:05:42 +00:00
return null;
}
@Override
2018-12-31 07:03:48 +00:00
protected void onExecuted(Bundle args, Void ignored) {
2018-12-21 08:32:26 +01:00
properties.setValue("expanded", data.message.id, false);
2018-10-15 10:05:42 +00:00
notifyDataSetChanged();
}
2018-12-01 10:47:08 +01:00
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-12-01 10:47:08 +01:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:unseen");
2018-10-15 10:05:42 +00:00
}
private void onMenuSnooze(final ActionData data) {
DialogDuration.show(context, owner, R.string.title_snooze,
new DialogDuration.IDialogDuration() {
2019-01-25 09:01:09 +00:00
@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;
}
2019-01-25 09:01:09 +00:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
args.putLong("wakeup", duration == 0 ? -1 : time);
2019-01-25 09:01:09 +00:00
new SimpleTask<Long>() {
2019-01-25 09:01:09 +00:00
@Override
protected Long onExecute(Context context, Bundle args) {
2019-01-25 09:01:09 +00:00
long id = args.getLong("id");
Long wakeup = args.getLong("wakeup");
if (wakeup < 0)
wakeup = null;
2019-01-25 09:01:09 +00:00
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message != null) {
List<EntityMessage> 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);
}
}
2019-01-25 09:01:09 +00:00
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return wakeup;
}
@Override
protected void onExecuted(Bundle args, Long wakeup) {
if (wakeup != null)
properties.finish();
2019-01-25 09:01:09 +00:00
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:snooze");
2019-01-25 09:01:09 +00:00
}
@Override
public void onDismiss() {
}
});
2019-01-25 09:01:09 +00:00
}
2019-03-02 14:32:19 +00:00
private void onMenuCopy(final ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<List<EntityFolder>>() {
@Override
protected List<EntityFolder> onExecute(Context context, Bundle args) {
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(args.getLong("id"));
if (message == null)
return null;
List<EntityFolder> folders = db.folder().getFolders(message.account);
if (folders == null)
return null;
2019-03-04 07:48:36 +00:00
EntityFolder.sort(context, folders, true);
2019-03-02 14:32:19 +00:00
return folders;
}
@Override
protected void onExecuted(final Bundle args, List<EntityFolder> folders) {
if (folders == null)
return;
View anchor = bnvActions.findViewById(R.id.action_more);
2019-05-03 18:59:27 +02:00
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
2019-03-02 14:32:19 +00:00
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<Void>() {
@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");
}
2019-01-25 09:01:09 +00:00
private void onMenuDelete(final ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<Void>() {
@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<Void>() {
@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();
}
2019-03-11 16:02:40 +00:00
private void onMenuDecrypt(ActionData data) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_DECRYPT)
.putExtra("id", data.message.id));
}
2019-04-04 17:54:05 +02:00
private void onMenuResync(ActionData data) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<Void>() {
@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);
2019-04-11 17:47:58 +02:00
EntityOperation.sync(context, message.folder, false);
2019-04-04 17:54:05 +02:00
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");
}
2019-03-01 12:51:57 +00:00
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());
2019-05-02 13:34:42 +02:00
if (data.message.to != null && data.message.to.length > 0)
rule.putExtra("recipient", ((InternetAddress) data.message.to[0]).getAddress());
2019-03-01 12:51:57 +00:00
if (!TextUtils.isEmpty(data.message.subject))
rule.putExtra("subject", data.message.subject);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(rule);
}
2019-03-11 16:02:40 +00:00
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<EntityFolder>() {
@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<String> keywords = Arrays.asList(message.keywords);
final List<String> 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<Void>() {
@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<Void>() {
@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");
}
2019-01-07 17:50:23 +00:00
private void onMenuShare(ActionData data) {
2019-01-03 20:31:45 +01:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
2019-01-07 20:40:14 +00:00
new SimpleTask<String[]>() {
2019-01-03 20:31:45 +01:00
@Override
2019-01-07 20:40:14 +00:00
protected String[] onExecute(Context context, Bundle args) throws Throwable {
2019-01-03 20:31:45 +01:00
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
2019-01-07 20:40:14 +00:00
String from = null;
if (message.from != null && message.from.length > 0)
from = ((InternetAddress) message.from[0]).getAddress();
2019-01-03 20:31:45 +01:00
2019-01-07 20:40:14 +00:00
return new String[]{
from,
message.subject,
2019-03-14 07:45:13 +00:00
HtmlHelper.getText(Helper.readText(message.getFile(context)))
2019-01-07 20:40:14 +00:00
};
2019-01-03 20:31:45 +01:00
}
@Override
2019-01-07 20:40:14 +00:00
protected void onExecuted(Bundle args, String[] text) {
2019-01-03 20:31:45 +01:00
Intent share = new Intent();
share.setAction(Intent.ACTION_SEND);
share.setType("text/plain");
2019-01-07 20:40:14 +00:00
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]);
2019-01-03 20:31:45 +01:00
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) {
2019-01-04 09:48:09 +01:00
Helper.unexpectedError(context, owner, ex);
2019-01-03 20:31:45 +01:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:share");
2019-01-03 20:31:45 +01:00
}
2019-03-05 11:18:22 +00:00
private void onMenuPrint(final ActionData data) {
2019-03-05 21:20:47 +00:00
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("print_html_confirmed", false)) {
2019-04-06 11:05:33 +02:00
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_PRINT)
.putExtra("id", data.message.id));
2019-03-05 21:20:47 +00:00
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();
2019-04-06 11:05:33 +02:00
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_PRINT)
.putExtra("id", data.message.id));
2019-03-05 21:20:47 +00:00
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
2019-01-07 17:50:23 +00:00
private void onMenuShowHeaders(ActionData data) {
2018-12-21 08:32:26 +01:00
boolean show_headers = !properties.getValue("headers", data.message.id);
properties.setValue("headers", data.message.id, show_headers);
2018-11-27 07:15:58 +01:00
if (show_headers && data.message.headers == null) {
2018-10-17 18:29:24 +00:00
grpHeaders.setVisibility(View.VISIBLE);
2019-03-16 13:12:31 +00:00
if (suitable)
pbHeaders.setVisibility(View.VISIBLE);
else
tvNoInternetHeaders.setVisibility(View.VISIBLE);
2018-10-16 10:56:05 +00:00
2018-10-17 18:29:24 +00:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
2018-10-16 10:56:05 +00:00
2018-10-17 18:29:24 +00:00
new SimpleTask<Void>() {
@Override
2018-12-31 07:03:48 +00:00
protected Void onExecute(Context context, Bundle args) {
2018-10-17 18:29:24 +00:00
Long id = args.getLong("id");
2019-01-02 19:38:32 +01:00
2018-10-17 18:29:24 +00:00
DB db = DB.getInstance(context);
2019-01-02 19:38:32 +01:00
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
2019-01-04 18:37:56 +00:00
EntityOperation.queue(context, db, message, EntityOperation.HEADERS);
2019-01-02 19:38:32 +01:00
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
2018-10-17 18:29:24 +00:00
return null;
}
2018-10-16 10:56:05 +00:00
2018-10-17 18:29:24 +00:00
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-10-17 18:29:24 +00:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:headers");
2018-10-17 18:29:24 +00:00
} else
notifyDataSetChanged();
}
2018-10-16 10:56:05 +00:00
private void onMenuRaw(ActionData data) {
if (data.message.raw == null) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<Void>() {
@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));
}
}
2019-01-07 17:50:23 +00:00
private void onActionMore(final ActionData data) {
2018-12-21 08:32:26 +01:00
boolean show_headers = properties.getValue("headers", data.message.id);
2018-10-16 10:56:05 +00:00
2018-10-17 18:29:24 +00:00
View anchor = bnvActions.findViewById(R.id.action_more);
2019-05-03 18:59:27 +02:00
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
2018-10-17 18:29:24 +00:00
popupMenu.inflate(R.menu.menu_message);
2018-10-21 14:53:39 +00:00
popupMenu.getMenu().findItem(R.id.menu_forward).setEnabled(data.message.content);
2018-11-13 20:05:09 +01:00
popupMenu.getMenu().findItem(R.id.menu_unseen).setEnabled(data.message.uid != null);
2019-01-04 18:37:56 +00:00
2019-03-02 14:32:19 +00:00
popupMenu.getMenu().findItem(R.id.menu_copy).setEnabled(data.message.uid != null);
popupMenu.getMenu().findItem(R.id.menu_delete).setVisible(debug);
2019-01-25 09:01:09 +00:00
popupMenu.getMenu().findItem(R.id.menu_junk).setEnabled(data.message.uid != null);
2019-01-28 16:59:31 +00:00
popupMenu.getMenu().findItem(R.id.menu_junk).setVisible(
data.hasJunk && !EntityFolder.JUNK.equals(data.message.folderType));
2019-01-25 09:01:09 +00:00
2019-02-08 08:43:16 +00:00
popupMenu.getMenu().findItem(R.id.menu_share).setEnabled(data.message.content);
2019-03-10 09:21:18 +00:00
popupMenu.getMenu().findItem(R.id.menu_print).setEnabled(hasWebView && data.message.content);
2019-04-05 14:12:56 +02:00
popupMenu.getMenu().findItem(R.id.menu_print).setVisible(Helper.canPrint(context));
2019-01-03 20:31:45 +01:00
2018-10-28 12:49:13 +00:00
popupMenu.getMenu().findItem(R.id.menu_show_headers).setChecked(show_headers);
2019-01-04 18:37:56 +00:00
popupMenu.getMenu().findItem(R.id.menu_show_headers).setEnabled(data.message.uid != null);
2018-10-21 14:53:39 +00:00
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);
2019-01-04 18:37:56 +00:00
popupMenu.getMenu().findItem(R.id.menu_manage_keywords).setEnabled(data.message.uid != null);
2018-11-26 12:42:06 +01:00
2018-12-14 08:53:02 +01:00
popupMenu.getMenu().findItem(R.id.menu_decrypt).setEnabled(
data.message.content && data.message.to != null && data.message.to.length > 0);
2018-10-16 10:56:05 +00:00
2019-04-04 17:54:05 +02:00
popupMenu.getMenu().findItem(R.id.menu_resync).setEnabled(data.message.uid != null);
2018-10-17 18:29:24 +00:00
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem target) {
switch (target.getItemId()) {
case R.id.menu_forward:
onMenuForward(data);
2018-10-17 18:29:24 +00:00
return true;
2018-10-28 12:49:13 +00:00
case R.id.menu_unseen:
2019-01-07 17:50:23 +00:00
onMenuUnseen(data);
2018-10-17 18:29:24 +00:00
return true;
case R.id.menu_snooze:
onMenuSnooze(data);
2019-01-25 09:01:09 +00:00
return true;
2019-03-02 14:32:19 +00:00
case R.id.menu_copy:
onMenuCopy(data);
return true;
2019-01-25 09:01:09 +00:00
case R.id.menu_delete:
// For emergencies
onMenuDelete(data);
return true;
case R.id.menu_junk:
onMenuJunk(data);
return true;
2019-03-11 16:02:40 +00:00
case R.id.menu_decrypt:
onMenuDecrypt(data);
return true;
2019-04-04 17:54:05 +02:00
case R.id.menu_resync:
onMenuResync(data);
return true;
2019-03-01 12:51:57 +00:00
case R.id.menu_create_rule:
onMenuCreateRule(data);
return true;
2019-03-11 16:02:40 +00:00
case R.id.menu_manage_keywords:
onMenuManageKeywords(data);
return true;
2019-01-03 20:31:45 +01:00
case R.id.menu_share:
2019-01-07 17:50:23 +00:00
onMenuShare(data);
2019-01-03 20:31:45 +01:00
return true;
2019-03-05 11:18:22 +00:00
case R.id.menu_print:
onMenuPrint(data);
return true;
2018-10-28 12:49:13 +00:00
case R.id.menu_show_headers:
2019-01-07 17:50:23 +00:00
onMenuShowHeaders(data);
2018-10-17 18:29:24 +00:00
return true;
case R.id.menu_raw:
onMenuRaw(data);
return true;
2018-10-17 18:29:24 +00:00
default:
return false;
}
}
});
popupMenu.show();
2018-10-16 10:56:05 +00:00
}
2019-01-07 17:50:23 +00:00
private void onActionDelete(final ActionData data) {
2018-10-15 10:05:42 +00:00
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() {
2018-09-19 10:00:58 +00:00
@Override
2018-10-15 10:05:42 +00:00
public void onClick(DialogInterface dialog, int which) {
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<Void>() {
@Override
2018-12-31 07:03:48 +00:00
protected Void onExecute(Context context, Bundle args) {
2018-10-15 10:05:42 +00:00
long id = args.getLong("id");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
2019-01-02 19:38:32 +01:00
if (message == null)
return null;
2019-01-07 17:50:23 +00:00
EntityFolder folder = db.folder().getFolder(message.folder);
2019-02-03 13:50:05 +00:00
if (EntityFolder.OUTBOX.equals(folder.type)) {
2018-10-15 10:05:42 +00:00
db.message().deleteMessage(id);
2019-01-05 07:41:04 +00:00
db.folder().setFolderError(message.folder, null);
db.identity().setIdentityError(message.identity, null);
2018-12-09 18:49:52 +01:00
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
2018-12-08 19:03:24 +01:00
nm.cancel("send", message.identity.intValue());
2018-12-03 15:46:24 +01:00
} else
2019-01-04 18:37:56 +00:00
EntityOperation.queue(context, db, message, EntityOperation.DELETE);
2018-10-15 10:05:42 +00:00
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-10-15 10:05:42 +00:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:delete");
2018-09-19 10:00:58 +00:00
}
2018-10-15 10:05:42 +00:00
})
.setNegativeButton(android.R.string.cancel, null)
.show();
2018-11-20 09:44:13 +01:00
} else
properties.move(data.message.id, EntityFolder.TRASH, true);
2018-10-15 10:05:42 +00:00
}
2019-01-07 17:50:23 +00:00
private void onActionMove(ActionData data) {
2018-10-15 10:05:42 +00:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
new SimpleTask<List<EntityFolder>>() {
@Override
2018-12-31 07:03:48 +00:00
protected List<EntityFolder> onExecute(Context context, Bundle args) {
2019-01-02 19:38:32 +01:00
EntityMessage message;
List<EntityFolder> folders = null;
2019-01-02 19:38:32 +01:00
2018-10-15 10:05:42 +00:00
DB db = DB.getInstance(context);
2019-01-02 19:38:32 +01:00
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;
2019-03-14 07:45:13 +00:00
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);
2019-03-14 07:45:13 +00:00
File target = message.getFile(context);
source.renameTo(target);
2018-11-24 18:38:34 +01:00
List<EntityAttachment> 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);
2019-01-02 19:38:32 +01:00
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
2018-11-24 18:38:34 +01:00
if (folders == null)
return null;
2018-10-28 13:35:12 +00:00
List<EntityFolder> targets = new ArrayList<>();
2018-11-24 18:38:34 +01:00
for (EntityFolder folder : folders)
2019-04-25 19:14:31 +02:00
if (!folder.isHidden(context) &&
2018-12-04 17:08:36 +01:00
!folder.id.equals(message.folder) &&
!EntityFolder.ARCHIVE.equals(folder.type) &&
!EntityFolder.TRASH.equals(folder.type) &&
!EntityFolder.JUNK.equals(folder.type))
2018-11-24 18:38:34 +01:00
targets.add(folder);
2018-10-15 10:05:42 +00:00
2019-03-04 07:48:36 +00:00
EntityFolder.sort(context, targets, true);
2018-10-15 10:05:42 +00:00
2018-10-28 13:35:12 +00:00
return targets;
2018-09-01 06:27:13 +00:00
}
2018-10-15 10:05:42 +00:00
@Override
2018-12-31 07:03:48 +00:00
protected void onExecuted(final Bundle args, List<EntityFolder> folders) {
if (folders == null)
return;
2018-10-15 10:05:42 +00:00
View anchor = bnvActions.findViewById(R.id.action_move);
2019-05-03 18:59:27 +02:00
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
2018-10-15 10:05:42 +00:00
int order = 0;
2018-12-01 10:17:49 +01:00
for (EntityFolder folder : folders)
popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, folder.getDisplayName(context));
2018-10-15 10:05:42 +00:00
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem target) {
args.putLong("target", target.getItemId());
2018-11-20 09:44:13 +01:00
new SimpleTask<String>() {
2018-10-15 10:05:42 +00:00
@Override
2018-12-31 07:03:48 +00:00
protected String onExecute(Context context, Bundle args) {
2018-10-15 10:05:42 +00:00
long target = args.getLong("target");
2018-11-20 09:44:13 +01:00
return DB.getInstance(context).folder().getFolder(target).name;
}
2018-10-15 10:05:42 +00:00
2018-11-20 09:44:13 +01:00
@Override
2018-12-31 07:03:48 +00:00
protected void onExecuted(Bundle args, String folderName) {
2018-11-20 09:44:13 +01:00
long id = args.getLong("id");
properties.move(id, folderName, false);
2018-10-15 10:05:42 +00:00
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-10-15 10:05:42 +00:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:move");
2018-10-15 10:05:42 +00:00
return true;
}
});
popupMenu.show();
}
2018-12-01 10:47:08 +01:00
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
2018-12-01 10:47:08 +01:00
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:move:list");
2018-10-15 10:05:42 +00:00
}
2019-01-07 17:50:23 +00:00
private void onActionArchive(ActionData data) {
2018-11-20 09:44:13 +01:00
properties.move(data.message.id, EntityFolder.ARCHIVE, true);
2018-10-15 10:05:42 +00:00
}
2019-03-02 08:48:02 +00:00
private void onActionReplyMenu(final ActionData data) {
View anchor = bnvActions.findViewById(R.id.action_reply);
2019-05-03 18:59:27 +02:00
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
2019-03-02 08:48:02 +00:00
popupMenu.inflate(R.menu.menu_reply);
2019-04-23 11:47:56 +02:00
popupMenu.getMenu().findItem(R.id.menu_reply_list).setVisible(data.message.list_post != null);
2019-04-18 19:13:38 +02:00
popupMenu.getMenu().findItem(R.id.menu_reply_receipt).setVisible(data.message.receipt_to != null);
2019-03-02 08:48:02 +00:00
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem target) {
switch (target.getItemId()) {
case R.id.menu_reply_to_sender:
2019-04-18 19:13:38 +02:00
onMenuReply(data, "reply");
2019-03-02 08:48:02 +00:00
return true;
case R.id.menu_reply_to_all:
2019-04-18 19:13:38 +02:00
onMenuReply(data, "reply_all");
return true;
2019-04-23 11:47:56 +02:00
case R.id.menu_reply_list:
onMenuReply(data, "list");
return true;
2019-04-18 19:13:38 +02:00
case R.id.menu_reply_receipt:
onMenuReply(data, "receipt");
2019-03-02 08:48:02 +00:00
return true;
2019-04-27 16:20:14 +02:00
case R.id.menu_reply_answer:
2019-03-02 08:48:02 +00:00
onMenuAnswer(data);
return true;
default:
return false;
}
}
});
popupMenu.show();
}
2019-04-18 19:13:38 +02:00
private void onMenuReply(final ActionData data, String action) {
2018-12-14 12:04:50 +01:00
Bundle args = new Bundle();
args.putLong("id", data.message.id);
2019-04-18 19:13:38 +02:00
args.putString("action", action);
2018-12-14 12:04:50 +01:00
new SimpleTask<Boolean>() {
@Override
2018-12-31 07:03:48 +00:00
protected Boolean onExecute(Context context, Bundle args) {
2018-12-14 12:04:50 +01:00
long id = args.getLong("id");
List<EntityAttachment> attachments = DB.getInstance(context).attachment().getAttachments(id);
for (EntityAttachment attachment : attachments)
if (!attachment.available && attachment.isInline())
2018-12-14 12:04:50 +01:00
return false;
return true;
}
@Override
2018-12-31 07:03:48 +00:00
protected void onExecuted(Bundle args, Boolean available) {
2018-12-14 12:04:50 +01:00
final Intent reply = new Intent(context, ActivityCompose.class)
2019-04-18 19:13:38 +02:00
.putExtra("action", args.getString("action"))
2018-12-14 12:04:50 +01:00
.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);
}
2019-01-07 10:17:27 +00:00
}.execute(context, owner, args, "message:reply");
}
2018-10-24 09:09:07 +00:00
2019-03-02 08:48:02 +00:00
private void onMenuAnswer(final ActionData data) {
new SimpleTask<List<EntityAnswer>>() {
@Override
protected List<EntityAnswer> onExecute(Context context, Bundle args) {
2019-04-20 10:35:30 +02:00
return DB.getInstance(context).answer().getAnswers(false);
2019-03-02 08:48:02 +00:00
}
@Override
protected void onExecuted(Bundle args, List<EntityAnswer> answers) {
if (answers == null || answers.size() == 0) {
Snackbar snackbar = Snackbar.make(
2019-03-15 11:53:22 +00:00
view,
2019-03-02 08:48:02 +00:00
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);
2019-05-03 18:59:27 +02:00
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, anchor);
2019-03-02 08:48:02 +00:00
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");
}
2018-10-24 09:09:07 +00:00
ItemDetailsLookup.ItemDetails<Long> getItemDetails(@NonNull MotionEvent motionEvent) {
2018-12-03 13:18:23 +01:00
return new ItemDetailsMessage(this);
}
Long getKey() {
return getKeyAtPosition(getAdapterPosition());
2018-10-24 09:09:07 +00:00
}
2018-08-02 13:33:06 +00:00
}
2019-01-18 08:28:07 +00:00
AdapterMessage(Context context, LifecycleOwner owner,
2019-05-04 10:24:42 +02:00
ViewType viewType, boolean compact, int zoom, String sort, boolean filter_duplicates, final IProperties properties) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.TF = Helper.getTimeInstance(context, SimpleDateFormat.SHORT);
2018-08-02 13:33:06 +00:00
this.context = context;
this.owner = owner;
2018-12-24 13:41:49 +00:00
this.inflater = LayoutInflater.from(context);
2018-08-02 13:33:06 +00:00
this.viewType = viewType;
2019-01-15 17:39:12 +00:00
this.compact = compact;
2018-12-30 16:33:52 +00:00
this.zoom = zoom;
2019-01-21 18:12:22 +00:00
this.sort = sort;
2019-04-28 15:16:26 +02:00
this.filter_duplicates = filter_duplicates;
2019-03-16 13:12:31 +00:00
this.suitable = Helper.getNetworkState(context).isSuitable();
this.properties = properties;
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);
TypedValue tv = new TypedValue();
context.getTheme().resolveAttribute(R.attr.themeName, tv, true);
this.dark = !"light".equals(tv.string);
this.hasWebView = Helper.hasWebView(context);
2019-02-07 09:02:40 +00:00
this.contacts = Helper.hasPermission(context, Manifest.permission.READ_CONTACTS);
2019-01-27 13:50:21 +00:00
this.search = (context.getPackageManager().getComponentEnabledSetting(
new ComponentName(context, ActivitySearch.class)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
this.textSize = Helper.getTextSize(context, zoom);
this.date = prefs.getBoolean("date", true);
this.threading = prefs.getBoolean("threading", true);
2019-01-26 09:58:37 +00:00
this.avatars = (prefs.getBoolean("avatars", true) ||
prefs.getBoolean("identicons", false));
this.name_email = prefs.getBoolean("name_email", !compact);
this.subject_italic = prefs.getBoolean("subject_italic", true);
2019-03-02 15:54:38 +00:00
this.flags = prefs.getBoolean("flags", true);
2018-11-04 12:42:41 +00:00
this.preview = prefs.getBoolean("preview", false);
this.monospaced = prefs.getBoolean("monospaced", false);
this.autohtml = (this.hasWebView && this.contacts && prefs.getBoolean("autohtml", false));
this.autoimages = (this.contacts && prefs.getBoolean("autoimages", false));
this.authentication = prefs.getBoolean("authentication", false);
this.debug = prefs.getBoolean("debug", false);
2019-05-04 10:24:42 +02:00
this.differ = new AsyncPagedListDiffer<>(this, DIFF_CALLBACK);
this.differ.addPagedListListener(new AsyncPagedListDiffer.PagedListListener<TupleMessageEx>() {
@Override
public void onCurrentListChanged(@Nullable PagedList<TupleMessageEx> previousList, @Nullable PagedList<TupleMessageEx> currentList) {
if (gotop) {
gotop = false;
properties.scrollTo(0);
}
}
});
2018-08-02 13:33:06 +00:00
}
2019-05-04 10:24:42 +02:00
void submitList(PagedList<TupleMessageEx> list, boolean gotop) {
this.gotop = gotop;
2019-01-29 11:38:38 +00:00
differ.submitList(list);
2019-01-20 13:27:35 +00:00
}
2018-12-03 08:21:02 +01:00
PagedList<TupleMessageEx> getCurrentList() {
return differ.getCurrentList();
}
2019-01-15 17:39:12 +00:00
void setCompact(boolean compact) {
if (this.compact != compact) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
2019-01-15 17:39:12 +00:00
this.compact = compact;
this.name_email = prefs.getBoolean("name_email", !compact);
2019-01-15 17:39:12 +00:00
notifyDataSetChanged();
}
}
2018-12-13 19:26:27 +01:00
void setZoom(int zoom) {
2018-12-30 16:33:52 +00:00
if (this.zoom != zoom) {
this.zoom = zoom;
textSize = Helper.getTextSize(context, zoom);
notifyDataSetChanged();
}
2018-12-13 19:26:27 +01:00
}
2019-01-21 18:12:22 +00:00
2019-04-04 10:11:24 +02:00
int getZoom() {
return this.zoom;
}
2019-01-21 18:12:22 +00:00
void setSort(String sort) {
if (!sort.equals(this.sort)) {
this.sort = sort;
2019-04-11 12:50:09 +02:00
notifyDataSetChanged();
// Needed to redraw item decorators / add/remove size
2019-01-21 18:12:22 +00:00
}
}
2018-12-13 19:26:27 +01:00
2019-04-04 10:11:24 +02:00
String getSort() {
return this.sort;
}
2019-04-28 15:16:26 +02:00
void setFilterDuplicates(boolean filter_duplicates) {
if (this.filter_duplicates != filter_duplicates) {
this.filter_duplicates = filter_duplicates;
notifyDataSetChanged();
}
}
void checkInternet() {
2019-03-16 13:12:31 +00:00
boolean suitable = Helper.getNetworkState(context).isSuitable();
if (this.suitable != suitable) {
this.suitable = suitable;
notifyDataSetChanged();
}
}
2019-01-15 17:39:12 +00:00
@Override
public int getItemViewType(int position) {
return (compact ? R.layout.item_message_compact : R.layout.item_message_normal);
}
2018-12-03 08:21:02 +01:00
@Override
public int getItemCount() {
return differ.getItemCount();
}
2018-08-12 16:14:20 +00:00
private static final DiffUtil.ItemCallback<TupleMessageEx> DIFF_CALLBACK =
2018-08-07 06:38:00 +00:00
new DiffUtil.ItemCallback<TupleMessageEx>() {
@Override
public boolean areItemsTheSame(
@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
return prev.id.equals(next.id);
}
2018-08-02 13:33:06 +00:00
2018-08-07 06:38:00 +00:00
@Override
public boolean areContentsTheSame(
@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
2018-12-04 18:42:45 +01:00
return prev.uiEquals(next);
2018-08-07 06:38:00 +00:00
}
};
2018-08-02 13:33:06 +00:00
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
2019-01-15 17:39:12 +00:00
return new ViewHolder(inflater.inflate(viewType, parent, false));
2018-08-02 13:33:06 +00:00
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
2018-12-03 08:21:02 +01:00
TupleMessageEx message = differ.getItem(position);
2018-08-07 06:38:00 +00:00
if (message == null)
holder.clear();
else {
2019-03-04 10:18:42 +00:00
holder.bindTo(message);
2018-08-07 06:38:00 +00:00
holder.wire();
2018-08-02 13:33:06 +00:00
}
}
2019-04-11 16:54:00 +02:00
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
2019-04-13 08:02:01 +02:00
holder.cowner.stop();
2019-05-03 18:59:27 +02:00
holder.powner.recreate();
2019-04-11 16:54:00 +02:00
}
2018-10-24 09:09:07 +00:00
void setSelectionTracker(SelectionTracker<Long> selectionTracker) {
this.selectionTracker = selectionTracker;
}
2018-12-03 13:18:23 +01:00
int getPositionForKey(long key) {
PagedList<TupleMessageEx> 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)) {
2018-12-24 12:27:45 +00:00
Log.i("Position=" + i + " @Key=" + key);
2018-12-03 13:18:23 +01:00
return i;
}
}
2018-12-24 12:27:45 +00:00
Log.i("Position=" + RecyclerView.NO_POSITION + " @Key=" + key);
2018-12-03 13:18:23 +01:00
return RecyclerView.NO_POSITION;
}
TupleMessageEx getItemAtPosition(int pos) {
PagedList<TupleMessageEx> list = getCurrentList();
if (list != null && pos < list.size()) {
TupleMessageEx message = list.get(pos);
Long key = (message == null ? null : message.id);
2018-12-24 12:27:45 +00:00
Log.i("Item=" + key + " @Position=" + pos);
2018-12-03 13:18:23 +01:00
return message;
} else {
2018-12-24 12:27:45 +00:00
Log.i("Item=" + null + " @Position=" + pos);
2018-12-03 13:18:23 +01:00
return null;
}
}
TupleMessageEx getItemForKey(long key) {
PagedList<TupleMessageEx> 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)) {
2018-12-24 12:27:45 +00:00
Log.i("Item=" + message.id + " @Key=" + key);
2018-12-03 13:18:23 +01:00
return message;
}
}
2019-04-11 19:37:34 +02:00
Log.i("Item=" + null + " @Key=" + key);
2018-12-03 13:18:23 +01:00
return null;
}
Long getKeyAtPosition(int pos) {
TupleMessageEx message = getItemAtPosition(pos);
Long key = (message == null ? null : message.id);
2018-12-24 12:27:45 +00:00
Log.i("Key=" + key + " @Position=" + pos);
2018-12-03 13:18:23 +01:00
return key;
}
interface IProperties {
2018-12-21 08:32:26 +01:00
void setValue(String name, long id, boolean enabled);
2018-12-21 08:32:26 +01:00
boolean getValue(String name, long id);
2018-11-20 09:44:13 +01:00
2018-12-22 08:48:23 +01:00
void setBody(long id, Spanned body);
Spanned getBody(long id);
2019-02-03 15:42:51 +00:00
void setHtml(long id, String html);
String getHtml(long id);
2019-04-04 14:47:56 +02:00
void setAttchments(long id, List<EntityAttachment> attachments);
List<EntityAttachment> getAttachments(long id);
2019-04-27 10:40:06 +02:00
void scrollTo(int pos);
void scrollBy(int dx, int dy);
2018-12-22 09:06:33 +01:00
2018-11-20 09:44:13 +01:00
void move(long id, String target, boolean type);
void finish();
}
2018-08-02 13:33:06 +00:00
}