Added unified folder types

pull/159/head
M66B 5 years ago
parent 1f36e69e04
commit 272e691478

@ -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<List<String>>() {
@Override
public void onChanged(List<String> types) {
if (types == null)
types = new ArrayList<>();
uadapter.set(types);
}
});
db.folder().liveNavigation().observe(this, new Observer<List<TupleFolderNav>>() {
@Override
public void onChanged(List<TupleFolderNav> 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));

@ -248,13 +248,8 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
if (listener == null && folder.selectable) {
if (account < 0)
tvType.setText(folder.accountName);
else {
int resid = context.getResources().getIdentifier(
"title_folder_" + folder.type.toLowerCase(),
"string",
context.getPackageName());
tvType.setText(resid > 0 ? context.getString(resid) : folder.type);
}
else
tvType.setText(Helper.localizeFolderType(context, folder.type));
tvTotal.setText(folder.total == null ? "" : nf.format(folder.total));

@ -146,6 +146,7 @@ import static android.app.Activity.RESULT_OK;
public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> {
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<AdapterMessage.ViewHold
if (viewType == ViewType.FOLDER)
tvFolder.setText(message.accountName);
else {
else if (type == null) {
String folderName = (message.folderDisplay == null
? Helper.localizeFolderName(context, message.folderName)
: message.folderDisplay);
tvFolder.setText((compact ? "" : message.accountName + "/") + folderName);
} else {
String folderName = (message.folderDisplay == null
? Helper.localizeFolderName(context, message.folderName)
: message.folderDisplay);
tvFolder.setText(message.accountName + "/" + folderName);
}
tvFolder.setVisibility(compact &&
(viewType == ViewType.FOLDER ||
@ -2886,9 +2892,11 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}
AdapterMessage(Fragment parentFragment,
ViewType viewType, boolean compact, int zoom, String sort, boolean filter_duplicates,
String type, ViewType viewType,
boolean compact, int zoom, String sort, boolean filter_duplicates,
final IProperties properties) {
this.parentFragment = parentFragment;
this.type = type;
this.viewType = viewType;
this.compact = compact;
this.zoom = zoom;

@ -0,0 +1,191 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
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.
FairEmail is distributed in the hope that it will be useful,
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
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
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<AdapterNavUnified.ViewHolder> {
private Context context;
private LifecycleOwner owner;
private LayoutInflater inflater;
private List<String> 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<String> types) {
Log.i("Set nav unified=" + types.size());
Collections.sort(types, new Comparator<String>() {
@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<String> prev = new ArrayList<>();
private List<String> next = new ArrayList<>();
DiffCallback(List<String> prev, List<String> 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();
}
}

@ -154,6 +154,15 @@ public interface DaoFolder {
" AND type <> '" + EntityFolder.USER + "'")
List<EntityFolder> 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<List<String>> liveUnifiedTypes();
@Query("SELECT * FROM folder WHERE id = :id")
EntityFolder getFolder(Long id);

@ -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<Integer, TupleMessageEx> pagedUnifiedInbox(
DataSource.Factory<Integer, TupleMessageEx> pagedUnified(
String type,
boolean threading,
String sort,
boolean filter_seen, boolean filter_unflagged, boolean filter_snoozed,

@ -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);

@ -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);

@ -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 +

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM15,9c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM19,17h-8v-1c0,-1.33 2.67,-2 4,-2s4,0.67 4,2v1z"/>
</vector>

@ -12,13 +12,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvUnified"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvAccount" />
<View
android:id="@+id/vSeparatorAccount"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvAccount" />
app:layout_constraintTop_toBottomOf="@id/rvUnified" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFolder"
@ -57,9 +65,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/title_legend_expander"
app:srcCompat="@drawable/expander"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorMenu" />
app:layout_constraintTop_toBottomOf="@id/vSeparatorMenu"
app:srcCompat="@drawable/expander" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMenuExtra"

Loading…
Cancel
Save