From 272e69147803d3823e909ceeac199ae084eeaea0 Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 19 Jul 2019 08:27:44 +0200 Subject: [PATCH] Added unified folder types --- .../java/eu/faircode/email/ActivityView.java | 16 ++ .../java/eu/faircode/email/AdapterFolder.java | 9 +- .../eu/faircode/email/AdapterMessage.java | 12 +- .../eu/faircode/email/AdapterNavUnified.java | 191 ++++++++++++++++++ .../java/eu/faircode/email/DaoFolder.java | 9 + .../java/eu/faircode/email/DaoMessage.java | 10 +- .../eu/faircode/email/FragmentMessages.java | 13 +- .../main/java/eu/faircode/email/Helper.java | 8 + .../eu/faircode/email/ViewModelMessages.java | 22 +- .../drawable/baseline_folder_shared_24.xml | 10 + app/src/main/res/layout/include_nav.xml | 14 +- 11 files changed, 289 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/AdapterNavUnified.java create mode 100644 app/src/main/res/drawable/baseline_folder_shared_24.xml diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index 6b4787cc6e..6aca58976c 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -85,6 +85,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB private ActionBarDrawerToggle drawerToggle; private ScrollView drawerContainer; private RecyclerView rvAccount; + private RecyclerView rvUnified; private RecyclerView rvFolder; private RecyclerView rvMenu; private ImageView ivExpander; @@ -154,6 +155,11 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB final AdapterNavAccount aadapter = new AdapterNavAccount(this, this); rvAccount.setAdapter(aadapter); + rvUnified = drawerContainer.findViewById(R.id.rvUnified); + rvUnified.setLayoutManager(new LinearLayoutManager(this)); + final AdapterNavUnified uadapter = new AdapterNavUnified(this, this); + rvUnified.setAdapter(uadapter); + rvFolder = drawerContainer.findViewById(R.id.rvFolder); rvFolder.setLayoutManager(new LinearLayoutManager(this)); final AdapterNavFolder fadapter = new AdapterNavFolder(this, this); @@ -352,6 +358,15 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB } }); + db.folder().liveUnifiedTypes().observe(this, new Observer>() { + @Override + public void onChanged(List types) { + if (types == null) + types = new ArrayList<>(); + uadapter.set(types); + } + }); + db.folder().liveNavigation().observe(this, new Observer>() { @Override public void onChanged(List folders) { @@ -946,6 +961,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB getSupportFragmentManager().popBackStack("messages", FragmentManager.POP_BACK_STACK_INCLUSIVE); Bundle args = new Bundle(); + args.putString("type", intent.getStringExtra("type")); args.putLong("account", intent.getLongExtra("account", -1)); args.putLong("folder", intent.getLongExtra("folder", -1)); diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index cb4c91a8cf..a20d2a8568 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -248,13 +248,8 @@ public class AdapterFolder extends RecyclerView.Adapter 0 ? context.getString(resid) : folder.type); - } + else + tvType.setText(Helper.localizeFolderType(context, folder.type)); tvTotal.setText(folder.total == null ? "" : nf.format(folder.total)); diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 7ea1be2e85..f151753bf7 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -146,6 +146,7 @@ import static android.app.Activity.RESULT_OK; public class AdapterMessage extends RecyclerView.Adapter { private Fragment parentFragment; + private String type; private ViewType viewType; private boolean compact; private int zoom; @@ -669,11 +670,16 @@ public class AdapterMessage extends RecyclerView.Adapter. + + Copyright 2018-2019 by Marcel Bokhorst (M66B) +*/ + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListUpdateCallback; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class AdapterNavUnified extends RecyclerView.Adapter { + private Context context; + private LifecycleOwner owner; + private LayoutInflater inflater; + + private List items = new ArrayList<>(); + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + private View view; + private ImageView ivItem; + private TextView tvItem; + private ImageView ivWarning; + + ViewHolder(View itemView) { + super(itemView); + + view = itemView.findViewById(R.id.clItem); + ivItem = itemView.findViewById(R.id.ivItem); + tvItem = itemView.findViewById(R.id.tvItem); + ivWarning = itemView.findViewById(R.id.ivWarning); + } + + private void wire() { + view.setOnClickListener(this); + } + + private void unwire() { + view.setOnClickListener(null); + } + + private void bindTo(String type) { + ivItem.setImageResource(R.drawable.baseline_folder_shared_24); + tvItem.setText(Helper.localizeFolderType(context, type)); + ivWarning.setVisibility(View.GONE); + } + + @Override + public void onClick(View v) { + int pos = getAdapterPosition(); + if (pos == RecyclerView.NO_POSITION) + return; + + String type = items.get(pos); + if (type == null) + return; + + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); + lbm.sendBroadcast( + new Intent(ActivityView.ACTION_VIEW_MESSAGES) + .putExtra("type", type)); + } + } + + AdapterNavUnified(Context context, LifecycleOwner owner) { + this.context = context; + this.owner = owner; + this.inflater = LayoutInflater.from(context); + } + + public void set(@NonNull List types) { + Log.i("Set nav unified=" + types.size()); + + Collections.sort(types, new Comparator() { + @Override + public int compare(String t1, String t2) { + int i1 = EntityFolder.FOLDER_SORT_ORDER.indexOf(t1); + int i2 = EntityFolder.FOLDER_SORT_ORDER.indexOf(t2); + return Integer.compare(i1, i2); + } + }); + + DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, types), false); + + items = types; + + diff.dispatchUpdatesTo(new ListUpdateCallback() { + @Override + public void onInserted(int position, int count) { + Log.i("Inserted @" + position + " #" + count); + } + + @Override + public void onRemoved(int position, int count) { + Log.i("Removed @" + position + " #" + count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + Log.i("Moved " + fromPosition + ">" + toPosition); + } + + @Override + public void onChanged(int position, int count, Object payload) { + Log.i("Changed @" + position + " #" + count); + } + }); + diff.dispatchUpdatesTo(this); + } + + private class DiffCallback extends DiffUtil.Callback { + private List prev = new ArrayList<>(); + private List next = new ArrayList<>(); + + DiffCallback(List prev, List next) { + this.prev.addAll(prev); + this.next.addAll(next); + } + + @Override + public int getOldListSize() { + return prev.size(); + } + + @Override + public int getNewListSize() { + return next.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + String t1 = prev.get(oldItemPosition); + String t2 = next.get(newItemPosition); + return t1.equals(t2); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return true; + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + @Override + @NonNull + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(inflater.inflate(R.layout.item_nav, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.unwire(); + String type = items.get(position); + holder.bindTo(type); + holder.wire(); + } +} diff --git a/app/src/main/java/eu/faircode/email/DaoFolder.java b/app/src/main/java/eu/faircode/email/DaoFolder.java index ddf094fd51..779b17b073 100644 --- a/app/src/main/java/eu/faircode/email/DaoFolder.java +++ b/app/src/main/java/eu/faircode/email/DaoFolder.java @@ -154,6 +154,15 @@ public interface DaoFolder { " AND type <> '" + EntityFolder.USER + "'") List getSystemFolders(long account); + @Query("SELECT folder.type" + + " FROM folder" + + " JOIN account ON account.id = folder.account" + + " WHERE folder.synchronize" + + " AND account.synchronize" + + " GROUP BY folder.type" + + " HAVING COUNT(folder.id) > 1") + LiveData> liveUnifiedTypes(); + @Query("SELECT * FROM folder WHERE id = :id") EntityFolder getFolder(Long id); diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index cbe05b5965..a70e6d99fd 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -54,7 +54,8 @@ public interface DaoMessage { ", SUM(CASE WHEN folder.type = '" + EntityFolder.DRAFTS + "' THEN 1 ELSE 0 END) AS drafts" + ", COUNT(DISTINCT CASE WHEN message.msgid IS NULL THEN message.id ELSE message.msgid END) AS visible" + ", SUM(message.size) AS totalSize" + - ", MAX(CASE WHEN :found OR folder.unified THEN message.received ELSE 0 END) AS dummy" + + ", MAX(CASE WHEN :found OR CASE WHEN type IS NULL THEN folder.unified ELSE folder.type = :type END" + + " THEN message.received ELSE 0 END) AS dummy" + " FROM message" + " JOIN account ON account.id = message.account" + " LEFT JOIN identity ON identity.id = message.identity" + @@ -63,7 +64,9 @@ public interface DaoMessage { " AND (message.ui_hide = 0 OR :debug)" + " AND (NOT :found OR ui_found = :found)" + " GROUP BY account.id, CASE WHEN message.thread IS NULL OR NOT :threading THEN message.id ELSE message.thread END" + - " HAVING (:found OR SUM(folder.unified) > 0)" + + " HAVING (:found OR" + + " CASE WHEN :type IS NULL THEN SUM(folder.unified) > 0" + + " ELSE SUM(CASE WHEN folder.type = :type THEN 1 ELSE 0 END) > 0 END)" + " AND (NOT :filter_seen OR " + unseen_unified + " > 0)" + " AND (NOT :filter_unflagged OR COUNT(message.id) - " + unflagged_unified + " > 0)" + " AND (NOT :filter_snoozed OR message.ui_snoozed IS NULL)" + @@ -78,7 +81,8 @@ public interface DaoMessage { " ELSE 0" + " END, message.received DESC") @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) - DataSource.Factory pagedUnifiedInbox( + DataSource.Factory pagedUnified( + String type, boolean threading, String sort, boolean filter_seen, boolean filter_unflagged, boolean filter_snoozed, diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index ea324394f4..956d75f365 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -177,6 +177,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. private FloatingActionButton fabSearch; private FloatingActionButton fabError; + private String type; private long account; private long folder; private boolean server; @@ -284,6 +285,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. // Get arguments Bundle args = getArguments(); + type = args.getString("type"); account = args.getLong("account", -1); folder = args.getLong("folder", -1); server = args.getBoolean("server", false); @@ -526,7 +528,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. String sort = prefs.getString("sort", "time"); boolean filter_duplicates = prefs.getBoolean("filter_duplicates", false); - adapter = new AdapterMessage(this, viewType, compact, zoom, sort, filter_duplicates, iProperties); + adapter = new AdapterMessage(this, type, viewType, compact, zoom, sort, filter_duplicates, iProperties); rvMessage.setAdapter(adapter); seekBar.setOnTouchListener(new View.OnTouchListener() { @@ -901,6 +903,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. swipeRefresh.setOnChildScrollUpCallback(new SwipeRefreshLayout.OnChildScrollUpCallback() { @Override public boolean canChildScrollUp(@NonNull SwipeRefreshLayout parent, @Nullable View child) { + if (type != null) + return true; if (viewType != AdapterMessage.ViewType.UNIFIED && viewType != AdapterMessage.ViewType.FOLDER) return true; if (!prefs.getBoolean("pull", true)) @@ -2624,7 +2628,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. // Get name String name; if (viewType == AdapterMessage.ViewType.UNIFIED) - name = getString(R.string.title_folder_unified); + if (type == null) + name = getString(R.string.title_folder_unified); + else + name = Helper.localizeFolderType(getContext(), type); else name = (folders.size() > 0 ? folders.get(0).getDisplayName(getContext()) : ""); @@ -2709,7 +2716,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. ViewModelMessages.Model vmodel = model.getModel( getContext(), getViewLifecycleOwner(), - viewType, account, folder, thread, id, query, server); + viewType, type, account, folder, thread, id, query, server); vmodel.setCallback(callback); vmodel.setObserver(getViewLifecycleOwner(), observer); diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index fc298993cc..1109d2c990 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -407,6 +407,14 @@ public class Helper { text.removeSpan(span); } + static String localizeFolderType(Context context, String type) { + int resid = context.getResources().getIdentifier( + "title_folder_" + type.toLowerCase(), + "string", + context.getPackageName()); + return (resid > 0 ? context.getString(resid) : type); + } + static String localizeFolderName(Context context, String name) { if (name != null && "INBOX".equals(name.toUpperCase())) return context.getString(R.string.title_folder_inbox); diff --git a/app/src/main/java/eu/faircode/email/ViewModelMessages.java b/app/src/main/java/eu/faircode/email/ViewModelMessages.java index d2d2ca4a48..5f1370b528 100644 --- a/app/src/main/java/eu/faircode/email/ViewModelMessages.java +++ b/app/src/main/java/eu/faircode/email/ViewModelMessages.java @@ -56,10 +56,11 @@ public class ViewModelMessages extends ViewModel { Model getModel( final Context context, final LifecycleOwner owner, final AdapterMessage.ViewType viewType, - long account, long folder, String thread, long id, + String type, long account, long folder, + String thread, long id, String query, boolean server) { - Args args = new Args(context, account, folder, thread, id, query, server); + Args args = new Args(context, type, account, folder, thread, id, query, server); Log.i("Get model=" + viewType + " " + args); dump(); @@ -84,7 +85,8 @@ public class ViewModelMessages extends ViewModel { switch (viewType) { case UNIFIED: builder = new LivePagedListBuilder<>( - db.message().pagedUnifiedInbox( + db.message().pagedUnified( + args.type, args.threading, args.sort, args.filter_seen, args.filter_unflagged, args.filter_snoozed, @@ -125,7 +127,8 @@ public class ViewModelMessages extends ViewModel { .build(); if (args.folder < 0) builder = new LivePagedListBuilder<>( - db.message().pagedUnifiedInbox( + db.message().pagedUnified( + null, args.threading, "time", false, false, false, @@ -258,6 +261,7 @@ public class ViewModelMessages extends ViewModel { private class Args { private long account; + private String type; private long folder; private String thread; private long id; @@ -272,9 +276,11 @@ public class ViewModelMessages extends ViewModel { private boolean debug; Args(Context context, - long account, long folder, String thread, long id, + String type, long account, long folder, + String thread, long id, String query, boolean server) { + this.type = type; this.account = account; this.folder = folder; this.thread = thread; @@ -295,7 +301,8 @@ public class ViewModelMessages extends ViewModel { public boolean equals(@Nullable Object obj) { if (obj instanceof Args) { Args other = (Args) obj; - return (this.account == other.account && + return (Objects.equals(this.type, other.type) && + this.account == other.account && this.folder == other.folder && Objects.equals(this.thread, other.thread) && this.id == other.id && @@ -315,7 +322,8 @@ public class ViewModelMessages extends ViewModel { @NonNull @Override public String toString() { - return "folder=" + account + ":" + folder + " thread=" + thread + ":" + id + + return "folder=" + type + ":" + account + ":" + folder + + " thread=" + thread + ":" + id + " query=" + query + ":" + server + "" + " threading=" + threading + " sort=" + sort + diff --git a/app/src/main/res/drawable/baseline_folder_shared_24.xml b/app/src/main/res/drawable/baseline_folder_shared_24.xml new file mode 100644 index 0000000000..f1bfc7b7cf --- /dev/null +++ b/app/src/main/res/drawable/baseline_folder_shared_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/include_nav.xml b/app/src/main/res/layout/include_nav.xml index f0c11c2726..571f20b8c3 100644 --- a/app/src/main/res/layout/include_nav.xml +++ b/app/src/main/res/layout/include_nav.xml @@ -12,13 +12,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toBottomOf="@id/rvUnified" /> + app:layout_constraintTop_toBottomOf="@id/vSeparatorMenu" + app:srcCompat="@drawable/expander" />