Simplify collapsible folders

pull/156/head
M66B 6 years ago
parent a3316c1aae
commit 9dd73d9d46

@ -27,11 +27,8 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
@ -48,12 +45,9 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.PopupMenu;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
@ -63,8 +57,10 @@ import java.text.Collator;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder> {
private Context context;
@ -73,11 +69,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
private boolean show_hidden;
private long account;
private int level;
private EntityFolder parent;
private boolean collapsible;
private boolean collapsible_hidden;
private IProperties properties;
private boolean subscriptions;
private boolean debug;
private int dp12;
@ -108,13 +100,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
private ImageView ivSync;
private TextView tvKeywords;
private TextView tvError;
private View vwHidden;
private View vwRipple;
private RecyclerView rvChilds;
private AdapterFolder childs;
private TwoStateOwner cowner = new TwoStateOwner(owner, "FolderChilds");
private TwoStateOwner powner = new TwoStateOwner(owner, "FolderPopup");
ViewHolder(View itemView) {
@ -138,43 +124,6 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
ivSync = itemView.findViewById(R.id.ivSync);
tvKeywords = itemView.findViewById(R.id.tvKeywords);
tvError = itemView.findViewById(R.id.tvError);
vwHidden = itemView.findViewById(R.id.vwHidden);
vwRipple = itemView.findViewById(R.id.vwRipple);
rvChilds = itemView.findViewById(R.id.rvChilds);
LinearLayoutManager llm = new LinearLayoutManager(context);
rvChilds.setLayoutManager(llm);
rvChilds.setNestedScrollingEnabled(false);
DividerItemDecoration itemDecorator = new DividerItemDecoration(context, llm.getOrientation()) {
Drawable d = context.getDrawable(R.drawable.divider);
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
for (int i = 0; i < parent.getChildCount(); i++) {
d.setBounds(0, 0, parent.getWidth(), d.getIntrinsicHeight());
canvas.save();
canvas.translate(0, parent.getChildAt(i).getTop() - d.getIntrinsicHeight());
d.draw(canvas);
canvas.restore();
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (view.findViewById(R.id.clItem).getVisibility() == View.GONE)
outRect.setEmpty();
else {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, outRect.bottom, 0, 0);
}
}
};
itemDecorator.setDrawable(context.getDrawable(R.drawable.divider));
rvChilds.addItemDecoration(itemDecorator);
childs = new AdapterFolder(context, owner, show_hidden, properties);
rvChilds.setAdapter(childs);
}
private void wire() {
@ -190,9 +139,28 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
}
private void bindTo(final TupleFolderEx folder) {
view.setVisibility(folder.hide && !show_hidden ? View.GONE : View.VISIBLE);
boolean hidden = (folder.hide && !show_hidden);
int level = 1;
TupleFolderEx parent = folder.parent_ref;
while (parent != null) {
level++;
if (parent.collapsed || (parent.hide && !show_hidden))
hidden = true;
parent = parent.parent_ref;
}
boolean childs_visible = false;
if (folder.child_refs != null)
for (TupleFolderEx child : folder.child_refs)
if (!child.hide || show_hidden) {
childs_visible = true;
break;
}
view.setVisibility(hidden ? View.GONE : View.VISIBLE);
view.setActivated(folder.tbc != null || folder.tbd != null);
vwHidden.setAlpha(folder.hide ? Helper.LOW_LIGHT : 0.0f);
view.setAlpha(folder.hide ? Helper.LOW_LIGHT : 1.0f);
if (textSize != 0)
tvName.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
@ -229,14 +197,14 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
ivReadOnly.setVisibility(folder.read_only ? View.VISIBLE : View.GONE);
boolean folder_collapsible = (show_hidden ? collapsible : collapsible_hidden);
ViewGroup.LayoutParams lp = vwLevel.getLayoutParams();
lp.width = (account < 0 || !folder_collapsible ? 1 : level) * dp12;
lp.width = (account < 0 ? 1 : level) * dp12;
vwLevel.setLayoutParams(lp);
ivExpander.setImageLevel(folder.collapsed ? 1 /* more */ : 0 /* less */);
ivExpander.setVisibility(account < 0 || !folder_collapsible ? View.GONE : (folder.childs > 0 ? View.VISIBLE : View.INVISIBLE));
ivExpander.setVisibility(account < 0
? View.GONE
: childs_visible ? View.VISIBLE : View.INVISIBLE);
ivNotify.setVisibility(folder.notify ? View.VISIBLE : View.GONE);
ivSubscribed.setVisibility(subscriptions && folder.subscribed != null && folder.subscribed ? View.VISIBLE : View.GONE);
@ -305,27 +273,6 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
tvError.setText(folder.error);
tvError.setVisibility(folder.error != null ? View.VISIBLE : View.GONE);
childs.setShowHidden(show_hidden);
cowner.recreate();
if (account > 0 && folder.childs > 0 && !folder.collapsed) {
DB db = DB.getInstance(context);
cowner.start();
rvChilds.setVisibility(View.VISIBLE);
childs.set(folder.account, folder, level + 1, properties.getChilds(folder.id));
db.folder().liveFolders(folder.account, folder.id).observe(cowner, new Observer<List<TupleFolderEx>>() {
@Override
public void onChanged(List<TupleFolderEx> folders) {
if (folders == null)
folders = new ArrayList<>();
properties.setChilds(folder.id, folders);
childs.set(account, folder, level + 1, folders);
}
});
} else {
rvChilds.setVisibility(View.GONE);
childs.set(folder.account, folder, level + 1, new ArrayList<TupleFolderEx>());
}
}
@Override
@ -341,9 +288,6 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
if (view.getId() == R.id.ivExpander)
onCollapse(folder);
else {
vwRipple.setPressed(true);
vwRipple.setPressed(false);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_MESSAGES)
@ -386,7 +330,7 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
if (folder.tbd != null)
return false;
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, vwRipple);
PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, powner, view);
popupMenu.getMenu().add(Menu.NONE, R.string.title_synchronize_now, 1, R.string.title_synchronize_now);
@ -704,12 +648,12 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
}
}
AdapterFolder(Context context, LifecycleOwner owner, boolean show_hidden, IProperties properties) {
AdapterFolder(Context context, LifecycleOwner owner, long account, boolean show_hidden) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.owner = owner;
this.account = account;
this.show_hidden = show_hidden;
this.properties = properties;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean compact = prefs.getBoolean("compact", false);
@ -728,27 +672,8 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
setHasStableIds(true);
}
public void set(final long account, EntityFolder parent, int level, @NonNull List<TupleFolderEx> folders) {
Log.i("Set account=" + account + " folders=" + folders.size());
this.account = account;
this.parent = parent;
this.level = level;
if (parent == null) {
this.collapsible = false;
this.collapsible_hidden = false;
for (TupleFolderEx folder : folders)
if (folder.childs > 0) {
this.collapsible = true;
this.collapsible_hidden = (folder.childs - folder.hidden_childs > 0);
break;
}
} else {
this.collapsible = true;
this.collapsible_hidden = true;
}
public void set(@NonNull List<TupleFolderEx> folders) {
Log.i("Set folders=" + folders.size());
final Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
@ -756,9 +681,33 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
if (folders.size() > 0)
Collections.sort(folders, folders.get(0).getComparator(context));
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, folders), false);
List<TupleFolderEx> parents = new ArrayList<>();
Map<Long, TupleFolderEx> idFolder = new HashMap<>();
Map<Long, List<TupleFolderEx>> parentChilds = new HashMap<>();
for (TupleFolderEx folder : folders) {
idFolder.put(folder.id, folder);
if (folder.parent == null)
parents.add(folder);
else {
if (!parentChilds.containsKey(folder.parent))
parentChilds.put(folder.parent, new ArrayList<TupleFolderEx>());
parentChilds.get(folder.parent).add(folder);
}
}
items = folders;
for (long pid : parentChilds.keySet()) {
TupleFolderEx parent = idFolder.get(pid);
parent.child_refs = parentChilds.get(pid);
for (TupleFolderEx child : parent.child_refs)
child.parent_ref = parent;
}
List<TupleFolderEx> hierarchical = getHierchical(parents);
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, hierarchical), false);
items = hierarchical;
diff.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
@ -784,6 +733,18 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
diff.dispatchUpdatesTo(this);
}
List<TupleFolderEx> getHierchical(List<TupleFolderEx> parents) {
List<TupleFolderEx> result = new ArrayList<>();
for (TupleFolderEx parent : parents) {
result.add(parent);
if (parent.child_refs != null)
result.addAll(getHierchical(parent.child_refs));
}
return result;
}
void setShowHidden(boolean show_hidden) {
if (this.show_hidden != show_hidden) {
this.show_hidden = show_hidden;
@ -821,7 +782,10 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
TupleFolderEx f1 = prev.get(oldItemPosition);
TupleFolderEx f2 = next.get(newItemPosition);
return f1.equals(f2);
boolean e = f1.equals(f2);
if (!e || f1.parent_ref == null || f2.parent_ref == null)
return e;
return f1.parent_ref.equals(f2.parent_ref);
}
}
@ -843,7 +807,6 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
holder.cowner.stop();
holder.powner.recreate();
}
@ -856,10 +819,4 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
holder.wire();
}
interface IProperties {
void setChilds(long parent, List<TupleFolderEx> childs);
List<TupleFolderEx> getChilds(long parent);
}
}

@ -56,18 +56,15 @@ public interface DaoFolder {
", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content" +
", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" +
", (SELECT COUNT(operation.id) FROM operation WHERE operation.folder = folder.id AND operation.state = 'executing') AS executing" +
", (SELECT COUNT(child.id) FROM folder child WHERE child.parent = folder.id) AS childs" +
", (SELECT COUNT(child.id) FROM folder child WHERE child.parent = folder.id AND child.hide) AS hidden_childs" +
" FROM folder" +
" LEFT JOIN account ON account.id = folder.account" +
" LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +
" WHERE CASE WHEN :account IS NULL" +
" THEN folder.unified AND account.synchronize" +
" ELSE folder.account = :account AND account.synchronize" +
" AND CASE WHEN :parent IS NULL THEN folder.parent IS NULL ELSE folder.parent = :parent END" +
" END" +
" GROUP BY folder.id")
LiveData<List<TupleFolderEx>> liveFolders(Long account, Long parent);
LiveData<List<TupleFolderEx>> liveFolders(Long account);
@Query("SELECT folder.*" +
", account.`order` AS accountOrder, account.name AS accountName, account.color AS accountColor, account.state AS accountState" +
@ -75,8 +72,6 @@ public interface DaoFolder {
", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content" +
", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" +
", (SELECT COUNT(operation.id) FROM operation WHERE operation.folder = folder.id AND operation.state = 'executing') AS executing" +
", (SELECT COUNT(child.id) FROM folder child WHERE child.parent = folder.id) AS childs" +
", (SELECT COUNT(child.id) FROM folder child WHERE child.parent = folder.id AND child.hide) AS hidden_childs" +
" FROM folder" +
" JOIN account ON account.id = folder.account" +
" LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +
@ -116,8 +111,6 @@ public interface DaoFolder {
", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content" +
", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" +
", (SELECT COUNT(operation.id) FROM operation WHERE operation.folder = folder.id AND operation.state = 'executing') AS executing" +
", (SELECT COUNT(child.id) FROM folder child WHERE child.parent = folder.id) AS childs" +
", (SELECT COUNT(child.id) FROM folder child WHERE child.parent = folder.id AND child.hide) AS hidden_childs" +
" FROM folder" +
" LEFT JOIN account ON account.id = folder.account" +
" LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +

@ -50,10 +50,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FragmentFolders extends FragmentBase {
private ViewGroup view;
@ -73,8 +70,6 @@ public class FragmentFolders extends FragmentBase {
private String searching = null;
private AdapterFolder adapter;
private Map<Long, List<TupleFolderEx>> parentChilds = new HashMap<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -149,18 +144,7 @@ public class FragmentFolders extends FragmentBase {
itemDecorator.setDrawable(getContext().getDrawable(R.drawable.divider));
rvFolder.addItemDecoration(itemDecorator);
adapter = new AdapterFolder(getContext(), getViewLifecycleOwner(), show_hidden, new AdapterFolder.IProperties() {
@Override
public void setChilds(long parent, List<TupleFolderEx> childs) {
parentChilds.put(parent, childs);
}
@Override
public List<TupleFolderEx> getChilds(long parent) {
List<TupleFolderEx> childs = parentChilds.get(parent);
return (childs == null ? new ArrayList<TupleFolderEx>() : childs);
}
});
adapter = new AdapterFolder(getContext(), getViewLifecycleOwner(), account, show_hidden);
rvFolder.setAdapter(adapter);
fab.setOnClickListener(new View.OnClickListener() {
@ -234,14 +218,6 @@ public class FragmentFolders extends FragmentBase {
public void onSaveInstanceState(Bundle outState) {
outState.putString("fair:searching", searching);
outState.putLongArray("fair:parents", Helper.toLongArray(parentChilds.keySet()));
for (Long parent : parentChilds.keySet()) {
List<TupleFolderEx> childs = parentChilds.get(parent);
outState.putInt("fair:childs:" + parent + ":count", childs.size());
for (int i = 0; i < childs.size(); i++)
outState.putSerializable("fair:childs:" + parent + ":" + i, childs.get(i));
}
super.onSaveInstanceState(outState);
}
@ -249,18 +225,9 @@ public class FragmentFolders extends FragmentBase {
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
if (savedInstanceState != null)
searching = savedInstanceState.getString("fair:searching");
for (long parent : savedInstanceState.getLongArray("fair:parents")) {
int count = savedInstanceState.getInt("fair:childs:" + parent + ":count");
List<TupleFolderEx> childs = new ArrayList<>(count);
for (int i = 0; i < count; i++)
childs.add((TupleFolderEx) savedInstanceState.getSerializable("fair:childs:" + parent + ":" + i));
parentChilds.put(parent, childs);
}
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
grpHintActions.setVisibility(prefs.getBoolean("folder_actions", false) ? View.GONE : View.VISIBLE);
grpHintSync.setVisibility(prefs.getBoolean("folder_sync", false) ? View.GONE : View.VISIBLE);
@ -302,7 +269,7 @@ public class FragmentFolders extends FragmentBase {
});
// Observe folders
db.folder().liveFolders(account < 0 ? null : account, null).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
db.folder().liveFolders(account < 0 ? null : account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
@Override
public void onChanged(@Nullable List<TupleFolderEx> folders) {
if (folders == null) {
@ -310,7 +277,7 @@ public class FragmentFolders extends FragmentBase {
return;
}
adapter.set(account, null, 0, folders);
adapter.set(folders);
pbWait.setVisibility(View.GONE);
grpReady.setVisibility(View.VISIBLE);
@ -437,7 +404,6 @@ public class FragmentFolders extends FragmentBase {
private void onMenuShowHidden() {
show_hidden = !show_hidden;
parentChilds.clear();
getActivity().invalidateOptionsMenu();
adapter.setShowHidden(show_hidden);
}

@ -24,9 +24,12 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import androidx.room.Ignore;
import java.io.Serializable;
import java.text.Collator;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@ -39,8 +42,12 @@ public class TupleFolderEx extends EntityFolder implements Serializable {
public int content;
public int unseen;
public int executing;
public int childs;
public int hidden_childs;
@Ignore
TupleFolderEx parent_ref;
@Ignore
List<TupleFolderEx> child_refs;
@Override
public boolean equals(Object obj) {
@ -53,9 +60,7 @@ public class TupleFolderEx extends EntityFolder implements Serializable {
this.messages == other.messages &&
this.content == other.content &&
this.unseen == other.unseen &&
this.executing == other.executing &&
this.childs == other.childs &&
this.hidden_childs == other.hidden_childs);
this.executing == other.executing);
} else
return false;
}

@ -8,7 +8,8 @@
android:id="@+id/clItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/activatableItemBackground">
android:background="?attr/activatableItemBackground"
android:foreground="?attr/selectableItemBackground">
<View
android:id="@+id/vwColor"
@ -216,34 +217,5 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvError" />
<View
android:id="@+id/vwHidden"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
android:background="?android:colorBackground"
app:layout_constraintBottom_toBottomOf="@id/paddingBottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/vwRipple"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="@id/paddingBottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvChilds"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/paddingBottom" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Loading…
Cancel
Save