diff --git a/.idea/misc.xml b/.idea/misc.xml index 4e024a8..26dc4f5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -25,5 +25,5 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index bee9e70..62306f1 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 8306744..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,7 +1,6 @@ - \ No newline at end of file diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAdapter.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAdapter.java index 24a5009..01b252a 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAdapter.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAdapter.java @@ -38,6 +38,8 @@ class ModDetailAdapter extends FragmentStatePagerAdapter { return ModDetailEventFragment.newInstance(mItemPos); case ModulePart.GRADEBOOK: return ModDetailGradebookFragment.newInstance(mItemPos); + case ModulePart.RESOURCES: + return ModDetailResourceFragment.newInstance(mItemPos); default: return null; } @@ -57,6 +59,8 @@ class ModDetailAdapter extends FragmentStatePagerAdapter { return this.mContext.getResources().getString(R.string.events); case ModulePart.GRADEBOOK: return this.mContext.getResources().getString(R.string.gradebook); + case ModulePart.RESOURCES: + return this.mContext.getResources().getString(R.string.resourcen); default: return ""; } diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailResourceAdapter.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailResourceAdapter.java new file mode 100644 index 0000000..8b5e570 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailResourceAdapter.java @@ -0,0 +1,34 @@ +package de.sebse.fuplanner.fragments.moddetails; + +import java.util.ArrayList; +import java.util.List; + +import de.sebse.fuplanner.services.KVV.types.Modules; +import de.sebse.fuplanner.services.KVV.types.Resource; +import de.sebse.fuplanner.tools.ui.treeview.TreeNode; +import de.sebse.fuplanner.tools.ui.treeview.TreeViewAdapter; +import de.sebse.fuplanner.tools.ui.treeview.TreeViewBinder; + +class ModDetailResourceAdapter extends TreeViewAdapter { + private Modules.Module mValue; + public ModDetailResourceAdapter(List viewBinders) { + super(viewBinders); + mValue = null; + } + + public void setModule(Modules.Module module) { + mValue = module; + this.setModule(); + } + + public void setModule() { + if (mValue == null || mValue.resources == null) { + return; + } + List nodes = new ArrayList<>(); + for (Resource res: mValue.resources) { + nodes.add(res.getTreeNode()); + } + refresh(nodes); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailResourceFragment.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailResourceFragment.java new file mode 100644 index 0000000..efdebb5 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailResourceFragment.java @@ -0,0 +1,132 @@ +package de.sebse.fuplanner.fragments.moddetails; + + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import java.util.Arrays; + +import de.sebse.fuplanner.MainActivity; +import de.sebse.fuplanner.R; +import de.sebse.fuplanner.services.KVV.KVV; +import de.sebse.fuplanner.services.KVV.types.Modules; +import de.sebse.fuplanner.tools.logging.Logger; +import de.sebse.fuplanner.tools.ui.treeview.DirectoryNodeBinder; +import de.sebse.fuplanner.tools.ui.treeview.FileNodeBinder; +import de.sebse.fuplanner.tools.ui.treeview.TreeNode; +import de.sebse.fuplanner.tools.ui.treeview.TreeViewAdapter; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link ModDetailResourceFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class ModDetailResourceFragment extends Fragment { + private static final String ARG_POSITION = "itemPosition"; + + private String mItemPos; + private final Logger log = new Logger(this); + private ModDetailResourceAdapter adapter; + private SwipeRefreshLayout swipeLayout; + + + public ModDetailResourceFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param itemPosition Item position in module list. + * @return A new instance of fragment ModDetailAnnounceFragment. + */ + public static ModDetailResourceFragment newInstance(String itemPosition) { + ModDetailResourceFragment fragment = new ModDetailResourceFragment(); + Bundle args = new Bundle(); + args.putString(ARG_POSITION, itemPosition); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mItemPos = getArguments().getString(ARG_POSITION); + } + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_recycler_view, container, false); + // Set the adapter + Context context = view.getContext(); + RecyclerView recyclerView = view.findViewById(R.id.list); + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + + adapter = new ModDetailResourceAdapter(Arrays.asList(new FileNodeBinder(), new DirectoryNodeBinder())); + adapter.setOnTreeNodeListener(new TreeViewAdapter.OnTreeNodeListener() { + @Override + public boolean onClick(TreeNode node, RecyclerView.ViewHolder holder) { + if (!node.isLeaf()) { + //Update and toggle the node. + onToggle(!node.isExpand(), holder); +// if (!node.isExpand()) +// adapter.collapseBrotherNode(node); + } + return false; + } + + @Override + public void onToggle(boolean isExpand, RecyclerView.ViewHolder holder) { + DirectoryNodeBinder.ViewHolder dirViewHolder = (DirectoryNodeBinder.ViewHolder) holder; + final ImageView ivArrow = dirViewHolder.getIvArrow(); + int rotateDegree = isExpand ? 90 : -90; + ivArrow.animate().rotationBy(rotateDegree) + .start(); + } + }); + + recyclerView.setAdapter(adapter); + + // Getting SwipeContainerLayout + swipeLayout = view.findViewById(R.id.swipe_container); + // Adding Listener + swipeLayout.setOnRefreshListener(() -> refresh(true)); + refresh(false); + + return view; + } + + private void refresh(boolean forceRefresh) { + if (getActivity() != null) { + KVV kvv = ((MainActivity) getActivity()).getKVV(); + kvv.getModule(mItemPos, (Modules.Module module) -> { + adapter.setModule(module); + kvv.getModuleResources(module, success1 -> { + adapter.setModule(); + swipeLayout.setRefreshing(false); + }, error -> { + swipeLayout.setRefreshing(false); + log.e(error); + }, forceRefresh); + }, error -> { + swipeLayout.setRefreshing(false); + log.e(error); + }, forceRefresh); + } + } + +} diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModulePart.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModulePart.java index 06c0fb4..17dff82 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModulePart.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModulePart.java @@ -7,7 +7,8 @@ class ModulePart { static final int ASSIGNMENT = 3; static final int EVENT = 4; static final int GRADEBOOK = 5; - private static final int[] pages = new int[]{OVERVIEW, ANNOUNCEMENT, ASSIGNMENT, EVENT, GRADEBOOK}; + static final int RESOURCES = 6; + private static final int[] pages = new int[]{OVERVIEW, ANNOUNCEMENT, ASSIGNMENT, EVENT, GRADEBOOK, RESOURCES}; static int getPageCount() { return pages.length; diff --git a/app/src/main/java/de/sebse/fuplanner/services/KVV/types/Resource.java b/app/src/main/java/de/sebse/fuplanner/services/KVV/types/Resource.java index 5365f96..fc5c93e 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/KVV/types/Resource.java +++ b/app/src/main/java/de/sebse/fuplanner/services/KVV/types/Resource.java @@ -3,6 +3,9 @@ package de.sebse.fuplanner.services.KVV.types; import java.io.Serializable; import java.util.ArrayList; +import de.sebse.fuplanner.tools.ui.treeview.Dir; +import de.sebse.fuplanner.tools.ui.treeview.TreeNode; + public abstract class Resource implements Serializable { @@ -47,9 +50,9 @@ public abstract class Resource implements Serializable { return visible; } + public abstract TreeNode getTreeNode(); + public static class File extends Resource { - - private final String type; public File(String author, String title, long modifiedDate, String url, boolean visible, String container, String type) { @@ -67,6 +70,11 @@ public abstract class Resource implements Serializable { ", type='" + type + '\'' + '}'; } + + @Override + public TreeNode getTreeNode() { + return new TreeNode<>(new de.sebse.fuplanner.tools.ui.treeview.File(title)); + } } public static class Folder extends Resource{ @@ -99,6 +107,15 @@ public abstract class Resource implements Serializable { ", childs='" + childs + '\'' + '}'; } + + @Override + public TreeNode getTreeNode() { + TreeNode dir = new TreeNode<>(new Dir(title)); + for (Resource res: childs) { + dir.addChild(res.getTreeNode()); + } + return dir; + } } } diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/Dir.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/Dir.java new file mode 100644 index 0000000..c36bd6e --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/Dir.java @@ -0,0 +1,21 @@ +package de.sebse.fuplanner.tools.ui.treeview; + + +import de.sebse.fuplanner.R; + +/** + * Created by tlh on 2016/10/1 :) + */ + +public class Dir implements LayoutItemType { + public String dirName; + + public Dir(String dirName) { + this.dirName = dirName; + } + + @Override + public int getLayoutId() { + return R.layout.item_dir; + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/DirectoryNodeBinder.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/DirectoryNodeBinder.java new file mode 100644 index 0000000..40e2f23 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/DirectoryNodeBinder.java @@ -0,0 +1,55 @@ +package de.sebse.fuplanner.tools.ui.treeview; + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import de.sebse.fuplanner.R; + +/** + * Created by tlh on 2016/10/1 :) + */ + +public class DirectoryNodeBinder extends TreeViewBinder { + @Override + public ViewHolder provideViewHolder(View itemView) { + return new ViewHolder(itemView); + } + + @Override + public void bindView(ViewHolder holder, int position, TreeNode node) { + holder.ivArrow.setRotation(0); + holder.ivArrow.setImageResource(R.drawable.ic_keyboard_arrow_right_black_18dp); + int rotateDegree = node.isExpand() ? 90 : 0; + holder.ivArrow.setRotation(rotateDegree); + Dir dirNode = (Dir) node.getContent(); + holder.tvName.setText(dirNode.dirName); + if (node.isLeaf()) + holder.ivArrow.setVisibility(View.INVISIBLE); + else holder.ivArrow.setVisibility(View.VISIBLE); + } + + @Override + public int getLayoutId() { + return R.layout.item_dir; + } + + public static class ViewHolder extends TreeViewBinder.ViewHolder { + private ImageView ivArrow; + private TextView tvName; + + public ViewHolder(View rootView) { + super(rootView); + this.ivArrow = (ImageView) rootView.findViewById(R.id.iv_arrow); + this.tvName = (TextView) rootView.findViewById(R.id.tv_name); + } + + public ImageView getIvArrow() { + return ivArrow; + } + + public TextView getTvName() { + return tvName; + } + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/File.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/File.java new file mode 100644 index 0000000..1f07396 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/File.java @@ -0,0 +1,21 @@ +package de.sebse.fuplanner.tools.ui.treeview; + + +import de.sebse.fuplanner.R; + +/** + * Created by tlh on 2016/10/1 :) + */ + +public class File implements LayoutItemType { + public String fileName; + + public File(String fileName) { + this.fileName = fileName; + } + + @Override + public int getLayoutId() { + return R.layout.item_file; + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/FileNodeBinder.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/FileNodeBinder.java new file mode 100644 index 0000000..8ca2858 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/FileNodeBinder.java @@ -0,0 +1,38 @@ +package de.sebse.fuplanner.tools.ui.treeview; + +import android.view.View; +import android.widget.TextView; + +import de.sebse.fuplanner.R; + +/** + * Created by tlh on 2016/10/1 :) + */ + +public class FileNodeBinder extends TreeViewBinder { + @Override + public ViewHolder provideViewHolder(View itemView) { + return new ViewHolder(itemView); + } + + @Override + public void bindView(ViewHolder holder, int position, TreeNode node) { + File fileNode = (File) node.getContent(); + holder.tvName.setText(fileNode.fileName); + } + + @Override + public int getLayoutId() { + return R.layout.item_file; + } + + public class ViewHolder extends TreeViewBinder.ViewHolder { + public TextView tvName; + + public ViewHolder(View rootView) { + super(rootView); + this.tvName = (TextView) rootView.findViewById(R.id.tv_name); + } + + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/LayoutItemType.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/LayoutItemType.java new file mode 100644 index 0000000..c4ea5ef --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/LayoutItemType.java @@ -0,0 +1,9 @@ +package de.sebse.fuplanner.tools.ui.treeview; + +/** + * Created by tlh on 2016/10/1 :) + */ + +public interface LayoutItemType { + int getLayoutId(); +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeNode.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeNode.java new file mode 100644 index 0000000..38f6ed0 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeNode.java @@ -0,0 +1,149 @@ +package de.sebse.fuplanner.tools.ui.treeview; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by tlh on 2016/10/1 :) + */ + +public class TreeNode implements Cloneable { + private T content; + private TreeNode parent; + private List childList; + private boolean isExpand; + private boolean isLocked; + //the tree high + private int height = UNDEFINE; + + private static final int UNDEFINE = -1; + + public TreeNode(@NonNull T content) { + this.content = content; + this.childList = new ArrayList<>(); + } + + public int getHeight() { + if (isRoot()) + height = 0; + else if (height == UNDEFINE) + height = parent.getHeight() + 1; + return height; + } + + public boolean isRoot() { + return parent == null; + } + + public boolean isLeaf() { + return childList == null || childList.isEmpty(); + } + + public void setContent(T content) { + this.content = content; + } + + public T getContent() { + return content; + } + + public List getChildList() { + return childList; + } + + public void setChildList(List childList) { + this.childList.clear(); + for (TreeNode treeNode : childList) { + addChild(treeNode); + } + } + + public TreeNode addChild(TreeNode node) { + if (childList == null) + childList = new ArrayList<>(); + childList.add(node); + node.parent = this; + return this; + } + + public boolean toggle() { + isExpand = !isExpand; + return isExpand; + } + + public void collapse() { + if (isExpand) { + isExpand = false; + } + } + + public void collapseAll() { + if (childList == null || childList.isEmpty()) { + return; + } + for (TreeNode child : this.childList) { + child.collapseAll(); + } + } + + public void expand() { + if (!isExpand) { + isExpand = true; + } + } + + public void expandAll() { + expand(); + if (childList == null || childList.isEmpty()) { + return; + } + for (TreeNode child : this.childList) { + child.expandAll(); + } + } + + public boolean isExpand() { + return isExpand; + } + + public void setParent(TreeNode parent) { + this.parent = parent; + } + + public TreeNode getParent() { + return parent; + } + + public TreeNode lock() { + isLocked = true; + return this; + } + + public TreeNode unlock() { + isLocked = false; + return this; + } + + public boolean isLocked() { + return isLocked; + } + + @Override + public String toString() { + return "TreeNode{" + + "content=" + this.content + + ", parent=" + (parent == null ? "null" : parent.getContent().toString()) + + ", childList=" + (childList == null ? "null" : childList.toString()) + + ", isExpand=" + isExpand + + '}'; + } + + @Override + protected TreeNode clone() throws CloneNotSupportedException { + TreeNode clone = new TreeNode<>(this.content); + clone.isExpand = this.isExpand; + return clone; + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeViewAdapter.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeViewAdapter.java new file mode 100644 index 0000000..0f3c130 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeViewAdapter.java @@ -0,0 +1,320 @@ +package de.sebse.fuplanner.tools.ui.treeview; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.util.DiffUtil; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Created by tlh on 2016/10/1 :) + */ +public class TreeViewAdapter extends RecyclerView.Adapter { + private static final String KEY_IS_EXPAND = "IS_EXPAND"; + private final List viewBinders; + private List displayNodes; + private int padding = 30; + private OnTreeNodeListener onTreeNodeListener; + private boolean toCollapseChild; + + public TreeViewAdapter(List viewBinders) { + this(null, viewBinders); + } + + public TreeViewAdapter(List nodes, List viewBinders) { + displayNodes = new ArrayList<>(); + if (nodes != null) + findDisplayNodes(nodes); + this.viewBinders = viewBinders; + } + + /** + * 从nodes的结点中寻找展开了的非叶结点,添加到displayNodes中。 + * + * @param nodes 基准点 + */ + private void findDisplayNodes(List nodes) { + for (TreeNode node : nodes) { + displayNodes.add(node); + if (!node.isLeaf() && node.isExpand()) + findDisplayNodes(node.getChildList()); + } + } + + @Override + public int getItemViewType(int position) { + return displayNodes.get(position).getContent().getLayoutId(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(viewType, parent, false); + if (viewBinders.size() == 1) + return viewBinders.get(0).provideViewHolder(v); + for (TreeViewBinder viewBinder : viewBinders) { + if (viewBinder.getLayoutId() == viewType) + return viewBinder.provideViewHolder(v); + } + return viewBinders.get(0).provideViewHolder(v); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) { + if (payloads != null && !payloads.isEmpty()) { + Bundle b = (Bundle) payloads.get(0); + for (String key : b.keySet()) { + switch (key) { + case KEY_IS_EXPAND: + if (onTreeNodeListener != null) + onTreeNodeListener.onToggle(b.getBoolean(key), holder); + break; + } + } + } + super.onBindViewHolder(holder, position, payloads); + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + holder.itemView.setPadding(displayNodes.get(position).getHeight() * padding, 3, 3, 3); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TreeNode selectedNode = displayNodes.get(holder.getLayoutPosition()); + // Prevent multi-click during the short interval. + try { + long lastClickTime = (long) holder.itemView.getTag(); + if (System.currentTimeMillis() - lastClickTime < 500) + return; + } catch (Exception e) { + holder.itemView.setTag(System.currentTimeMillis()); + } + holder.itemView.setTag(System.currentTimeMillis()); + + if (onTreeNodeListener != null && onTreeNodeListener.onClick(selectedNode, holder)) + return; + if (selectedNode.isLeaf()) + return; + // This TreeNode was locked to click. + if (selectedNode.isLocked()) return; + boolean isExpand = selectedNode.isExpand(); + int positionStart = displayNodes.indexOf(selectedNode) + 1; + if (!isExpand) { + notifyItemRangeInserted(positionStart, addChildNodes(selectedNode, positionStart)); + } else { + notifyItemRangeRemoved(positionStart, removeChildNodes(selectedNode, true)); + } + } + }); + for (TreeViewBinder viewBinder : viewBinders) { + if (viewBinder.getLayoutId() == displayNodes.get(position).getContent().getLayoutId()) + viewBinder.bindView(holder, position, displayNodes.get(position)); + } + } + + private int addChildNodes(TreeNode pNode, int startIndex) { + List childList = pNode.getChildList(); + int addChildCount = 0; + for (TreeNode treeNode : childList) { + displayNodes.add(startIndex + addChildCount++, treeNode); + if (treeNode.isExpand()) { + addChildCount += addChildNodes(treeNode, startIndex + addChildCount); + } + } + if (!pNode.isExpand()) + pNode.toggle(); + return addChildCount; + } + + private int removeChildNodes(TreeNode pNode) { + return removeChildNodes(pNode, true); + } + + private int removeChildNodes(TreeNode pNode, boolean shouldToggle) { + if (pNode.isLeaf()) + return 0; + List childList = pNode.getChildList(); + int removeChildCount = childList.size(); + displayNodes.removeAll(childList); + for (TreeNode child : childList) { + if (child.isExpand()) { + if (toCollapseChild) + child.toggle(); + removeChildCount += removeChildNodes(child, false); + } + } + if (shouldToggle) + pNode.toggle(); + return removeChildCount; + } + + @Override + public int getItemCount() { + return displayNodes == null ? 0 : displayNodes.size(); + } + + public void setPadding(int padding) { + this.padding = padding; + } + + public void ifCollapseChildWhileCollapseParent(boolean toCollapseChild) { + this.toCollapseChild = toCollapseChild; + } + + public void setOnTreeNodeListener(OnTreeNodeListener onTreeNodeListener) { + this.onTreeNodeListener = onTreeNodeListener; + } + + public interface OnTreeNodeListener { + /** + * called when TreeNodes were clicked. + * @return weather consume the click event. + */ + boolean onClick(TreeNode node, RecyclerView.ViewHolder holder); + + /** + * called when TreeNodes were toggle. + * @param isExpand the status of TreeNodes after being toggled. + */ + void onToggle(boolean isExpand, RecyclerView.ViewHolder holder); + } + + public void refresh(List treeNodes) { + displayNodes.clear(); + findDisplayNodes(treeNodes); + notifyDataSetChanged(); + } + + public Iterator getDisplayNodesIterator() { + return displayNodes.iterator(); + } + + private void notifyDiff(final List temp) { + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return temp.size(); + } + + @Override + public int getNewListSize() { + return displayNodes.size(); + } + + // judge if the same items + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return TreeViewAdapter.this.areItemsTheSame(temp.get(oldItemPosition), displayNodes.get(newItemPosition)); + } + + // if they are the same items, whether the contents has bean changed. + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return TreeViewAdapter.this.areContentsTheSame(temp.get(oldItemPosition), displayNodes.get(newItemPosition)); + } + + @Nullable + @Override + public Object getChangePayload(int oldItemPosition, int newItemPosition) { + return TreeViewAdapter.this.getChangePayload(temp.get(oldItemPosition), displayNodes.get(newItemPosition)); + } + }); + diffResult.dispatchUpdatesTo(this); + } + + private Object getChangePayload(TreeNode oldNode, TreeNode newNode) { + Bundle diffBundle = new Bundle(); + if (newNode.isExpand() != oldNode.isExpand()) { + diffBundle.putBoolean(KEY_IS_EXPAND, newNode.isExpand()); + } + if (diffBundle.size() == 0) + return null; + return diffBundle; + } + + // For DiffUtil, if they are the same items, whether the contents has bean changed. + private boolean areContentsTheSame(TreeNode oldNode, TreeNode newNode) { + return oldNode.getContent() != null && oldNode.getContent().equals(newNode.getContent()) + && oldNode.isExpand() == newNode.isExpand(); + } + + // judge if the same item for DiffUtil + private boolean areItemsTheSame(TreeNode oldNode, TreeNode newNode) { + return oldNode.getContent() != null && oldNode.getContent().equals(newNode.getContent()); + } + + /** + * collapse all root nodes. + */ + public void collapseAll() { + // Back up the nodes are displaying. + List temp = backupDisplayNodes(); + //find all root nodes. + List roots = new ArrayList<>(); + for (TreeNode displayNode : displayNodes) { + if (displayNode.isRoot()) + roots.add(displayNode); + } + //Close all root nodes. + for (TreeNode root : roots) { + if (root.isExpand()) + removeChildNodes(root); + } + notifyDiff(temp); + } + + @NonNull + private List backupDisplayNodes() { + List temp = new ArrayList<>(); + for (TreeNode displayNode : displayNodes) { + try { + temp.add(displayNode.clone()); + } catch (CloneNotSupportedException e) { + temp.add(displayNode); + } + } + return temp; + } + + public void collapseNode(TreeNode pNode) { + List temp = backupDisplayNodes(); + removeChildNodes(pNode); + notifyDiff(temp); + } + + public void collapseBrotherNode(TreeNode pNode) { + List temp = backupDisplayNodes(); + if (pNode.isRoot()) { + List roots = new ArrayList<>(); + for (TreeNode displayNode : displayNodes) { + if (displayNode.isRoot()) + roots.add(displayNode); + } + //Close all root nodes. + for (TreeNode root : roots) { + if (root.isExpand() && !root.equals(pNode)) + removeChildNodes(root); + } + } else { + TreeNode parent = pNode.getParent(); + if (parent == null) + return; + List childList = parent.getChildList(); + for (TreeNode node : childList) { + if (node.equals(pNode) || !node.isExpand()) + continue; + removeChildNodes(node); + } + } + notifyDiff(temp); + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeViewBinder.java b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeViewBinder.java new file mode 100644 index 0000000..522536c --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/ui/treeview/TreeViewBinder.java @@ -0,0 +1,23 @@ +package de.sebse.fuplanner.tools.ui.treeview; + +import android.support.annotation.IdRes; +import android.support.v7.widget.RecyclerView; +import android.view.View; + + +public abstract class TreeViewBinder implements LayoutItemType { + public abstract VH provideViewHolder(View itemView); + + public abstract void bindView(VH holder, int position, TreeNode node); + + public static class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(View rootView) { + super(rootView); + } + + protected T findViewById(@IdRes int id) { + return (T) itemView.findViewById(id); + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_dir.xml b/app/src/main/res/layout/item_dir.xml new file mode 100644 index 0000000..2af6a95 --- /dev/null +++ b/app/src/main/res/layout/item_dir.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_file.xml b/app/src/main/res/layout/item_file.xml new file mode 100644 index 0000000..35323aa --- /dev/null +++ b/app/src/main/res/layout/item_file.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file