diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index bd541ede..e3fc4a70 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/app/build.gradle b/app/build.gradle index 6987d054..f8ca61cd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,10 +45,13 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) + // TODO: AndroidX + def support_version = "28.0.0-alpha1" def constraintlayout_version = "1.1.2" // 2.0.0-alpha1 def lifecycle_version = "1.1.1" def room_version = "1.1.1" + def paging_version = "1.0.0" def javamail_version = "1.6.0" def jsoup_version = "1.11.3" @@ -64,6 +67,8 @@ dependencies { annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" annotationProcessor "android.arch.persistence.room:compiler:$room_version" + implementation "android.arch.paging:runtime:$paging_version" + // https://javaee.github.io/javamail/ implementation "com.sun.mail:android-mail:$javamail_version" implementation "com.sun.mail:android-activation:$javamail_version" diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 2d3b9fe2..8b3da1f4 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -19,6 +19,7 @@ package eu.faircode.email; Copyright 2018 by Marcel Bokhorst (M66B) */ +import android.arch.paging.PagedListAdapter; import android.content.Context; import android.content.Intent; import android.graphics.Typeface; @@ -26,7 +27,6 @@ import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.util.DiffUtil; -import android.support.v7.util.ListUpdateCallback; import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; import android.util.Log; @@ -34,24 +34,18 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.ProgressBar; import android.widget.TextView; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class AdapterMessage extends RecyclerView.Adapter { +public class AdapterMessage extends PagedListAdapter { private Context context; private ViewType viewType; private boolean debug; private ExecutorService executor = Executors.newCachedThreadPool(); - private List all = new ArrayList<>(); - private List filtered = new ArrayList<>(); - enum ViewType {FOLDER, THREAD} public class ViewHolder extends RecyclerView.ViewHolder @@ -62,6 +56,7 @@ public class AdapterMessage extends RecyclerView.Adapter 1 ? View.VISIBLE : View.GONE); + } else + tvCount.setText(Helper.localizeFolderName(context, message.folderName)); + + ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE); + + boolean unseen = (message.thread == null && !outbox ? message.unseen > 0 : !message.seen); + + int typeface = (unseen ? Typeface.BOLD : Typeface.NORMAL); + tvFrom.setTypeface(null, typeface); + tvTime.setTypeface(null, typeface); + tvSubject.setTypeface(null, typeface); + tvCount.setTypeface(null, typeface); + + tvFrom.setTextColor(Helper.resolveColor(context, unseen ? R.attr.colorUnread : android.R.attr.textColorSecondary)); + tvTime.setTextColor(Helper.resolveColor(context, unseen ? R.attr.colorUnread : android.R.attr.textColorSecondary)); + } + @Override public void onClick(View view) { int pos = getAdapterPosition(); if (pos == RecyclerView.NO_POSITION) return; - final TupleMessageEx message = filtered.get(pos); + final TupleMessageEx message = getItem(pos); executor.submit(new Runnable() { @Override @@ -120,102 +162,26 @@ public class AdapterMessage extends RecyclerView.Adapter messages) { - Log.i(Helper.TAG, "Set messages=" + messages.size()); - - Collections.sort(messages, new Comparator() { - @Override - public int compare(TupleMessageEx m1, TupleMessageEx m2) { - if (EntityFolder.isOutgoing(m1.folderType)) - return -Long.compare(m1.received, m2.received); - else - return -Long.compare( - m1.sent == null ? 0 : m1.sent, - m2.sent == null ? 0 : m2.sent); - } - }); - - all.clear(); - all.addAll(messages); - - DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all)); - - filtered.clear(); - filtered.addAll(all); - - diff.dispatchUpdatesTo(new ListUpdateCallback() { - @Override - public void onInserted(int position, int count) { - Log.i(Helper.TAG, "Inserted @" + position + " #" + count); - } - - @Override - public void onRemoved(int position, int count) { - Log.i(Helper.TAG, "Removed @" + position + " #" + count); - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition); - } - - @Override - public void onChanged(int position, int count, Object payload) { - Log.i(Helper.TAG, "Changed @" + position + " #" + count); - } - }); - diff.dispatchUpdatesTo(AdapterMessage.this); } - private class MessageDiffCallback extends DiffUtil.Callback { - private List prev; - private List next; - - MessageDiffCallback(List prev, List next) { - this.prev = prev; - this.next = next; - } - - @Override - public int getOldListSize() { - return prev.size(); - } - - @Override - public int getNewListSize() { - return next.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - TupleMessageEx m1 = prev.get(oldItemPosition); - TupleMessageEx m2 = next.get(newItemPosition); - return m1.id.equals(m2.id); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - TupleMessageEx m1 = prev.get(oldItemPosition); - TupleMessageEx m2 = next.get(newItemPosition); - return m1.equals(m2); - } - } - - @Override - public long getItemId(int position) { - return filtered.get(position).id; - } + public static final DiffUtil.ItemCallback DIFF_CALLBACK = + new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame( + @NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) { + return prev.id.equals(next.id); + } - @Override - public int getItemCount() { - return filtered.size(); - } + @Override + public boolean areContentsTheSame( + @NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) { + return prev.equals(next); + } + }; @Override @NonNull @@ -227,39 +193,12 @@ public class AdapterMessage extends RecyclerView.Adapter 1 ? View.VISIBLE : View.GONE); - } else - holder.tvCount.setText(Helper.localizeFolderName(context, message.folderName)); - - holder.ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE); - - boolean unseen = (message.thread == null && !outbox ? message.unseen > 0 : !message.seen); - - int visibility = (unseen ? Typeface.BOLD : Typeface.NORMAL); - holder.tvFrom.setTypeface(null, visibility); - holder.tvTime.setTypeface(null, visibility); - holder.tvSubject.setTypeface(null, visibility); - holder.tvCount.setTypeface(null, visibility); - - holder.tvFrom.setTextColor(Helper.resolveColor(context, unseen ? R.attr.colorUnread : android.R.attr.textColorSecondary)); - holder.tvTime.setTextColor(Helper.resolveColor(context, unseen ? R.attr.colorUnread : android.R.attr.textColorSecondary)); - - holder.wire(); } } diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index 8bf28534..8be1ee23 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -20,6 +20,7 @@ package eu.faircode.email; */ import android.arch.lifecycle.LiveData; +import android.arch.paging.DataSource; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; @@ -39,9 +40,10 @@ public interface DaoMessage { " WHERE folder.type = '" + EntityFolder.TYPE_INBOX + "'" + " AND (NOT ui_hide OR :debug)" + " AND received IN (SELECT MAX(m.received) FROM message m WHERE m.folder = message.folder" + - " GROUP BY CASE WHEN m.thread IS NULL THEN m.id ELSE m.thread END)") + " GROUP BY CASE WHEN m.thread IS NULL THEN m.id ELSE m.thread END)" + + " ORDER BY received DESC") // in theory the message id and thread could be the same - LiveData> liveUnifiedInbox(boolean debug); + DataSource.Factory pagedUnifiedInbox(boolean debug); @Query("SELECT message.*, folder.name as folderName, folder.type as folderType" + ", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" + @@ -52,8 +54,13 @@ public interface DaoMessage { " WHERE folder.id = :folder" + " AND (NOT ui_hide OR :debug)" + " AND received IN (SELECT MAX(m.received) FROM message m WHERE m.folder = message.folder" + - " GROUP BY CASE WHEN m.thread IS NULL THEN m.id ELSE m.thread END)") - LiveData> liveMessages(long folder, boolean debug); + " GROUP BY CASE WHEN m.thread IS NULL THEN m.id ELSE m.thread END)" + + " ORDER BY CASE WHEN folder.type IN " + + "('" + EntityFolder.TYPE_OUTBOX + "'" + + ", '" + EntityFolder.TYPE_DRAFTS + "'" + + ", '" + EntityFolder.TYPE_SENT + "')" + + " THEN sent ELSE received END DESC") + DataSource.Factory pagedFolder(long folder, boolean debug); @Query("SELECT message.*, folder.name as folderName, folder.type as folderType" + ", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" + @@ -62,8 +69,9 @@ public interface DaoMessage { " FROM message" + " JOIN folder ON folder.id = message.folder" + " JOIN message m1 ON m1.id = :msgid AND m1.account = message.account AND m1.thread = message.thread" + - " WHERE NOT message.ui_hide OR :debug") - LiveData> liveThread(long msgid, boolean debug); + " WHERE NOT message.ui_hide OR :debug" + + " ORDER BY received DESC") + DataSource.Factory pagedThread(long msgid, boolean debug); @Query("SELECT * FROM message WHERE id = :id") EntityMessage getMessage(long id); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index cfb391c1..3e7314df 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -19,7 +19,10 @@ package eu.faircode.email; Copyright 2018 by Marcel Bokhorst (M66B) */ +import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; +import android.arch.paging.LivePagedListBuilder; +import android.arch.paging.PagedList; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -40,8 +43,6 @@ import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; -import java.util.List; - public class FragmentMessages extends FragmentEx { private RecyclerView rvMessage; private TextView tvNoEmail; @@ -93,51 +94,51 @@ public class FragmentMessages extends FragmentEx { pbWait.setVisibility(View.VISIBLE); fab.setVisibility(View.GONE); - DB db = DB.getInstance(getContext()); - // Observe folder/messages + DB db = DB.getInstance(getContext()); + LiveData> messages; boolean debug = PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("debug", false); if (thread < 0) if (folder < 0) { setSubtitle(R.string.title_folder_unified); - db.message().liveUnifiedInbox(debug).observe(this, messagesObserver); + messages = new LivePagedListBuilder<>(db.message().pagedUnifiedInbox(debug), 20).build(); } else { - DB.getInstance(getContext()).folder().liveFolderEx(folder).observe(this, new Observer() { + db.folder().liveFolderEx(folder).observe(this, new Observer() { @Override public void onChanged(@Nullable TupleFolderEx folder) { setSubtitle(folder == null ? null : Helper.localizeFolderName(getContext(), folder.name)); } }); - db.message().liveMessages(folder, debug).observe(this, messagesObserver); + messages = new LivePagedListBuilder<>(db.message().pagedFolder(folder, debug), 20).build(); } else { setSubtitle(R.string.title_folder_thread); - db.message().liveThread(thread, debug).observe(this, messagesObserver); + messages = new LivePagedListBuilder<>(db.message().pagedThread(thread, debug), 20).build(); } + messages.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable PagedList messages) { + adapter.submitList(messages); + + pbWait.setVisibility(View.GONE); + grpReady.setVisibility(View.VISIBLE); + + if (messages.size() == 0) { + tvNoEmail.setVisibility(View.VISIBLE); + rvMessage.setVisibility(View.GONE); + } else { + tvNoEmail.setVisibility(View.GONE); + rvMessage.setVisibility(View.VISIBLE); + } + } + }); + getLoaderManager().restartLoader(ActivityView.LOADER_MESSAGES_INIT, new Bundle(), initLoaderCallbacks).forceLoad(); return view; } - Observer> messagesObserver = new Observer>() { - @Override - public void onChanged(@Nullable List messages) { - adapter.set(messages); - - pbWait.setVisibility(View.GONE); - grpReady.setVisibility(View.VISIBLE); - - if (messages.size() == 0) { - tvNoEmail.setVisibility(View.VISIBLE); - rvMessage.setVisibility(View.GONE); - } else { - tvNoEmail.setVisibility(View.GONE); - rvMessage.setVisibility(View.VISIBLE); - } - } - }; - private static class InitLoader extends AsyncTaskLoader { InitLoader(@NonNull Context context) { super(context); diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index 1a0f2667..e90506d7 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -65,6 +65,18 @@ app:layout_constraintBottom_toBottomOf="@id/tvSubject" app:layout_constraintEnd_toEndOf="parent" /> + +