mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Restore File tree state when application is resumed (closes #150)
This commit is contained in:
@@ -354,6 +354,10 @@ public class EditorActivity extends StudioActivity
|
||||
super.onPause();
|
||||
dispatchOnPauseToEditors();
|
||||
EditorActivityActions.clear();
|
||||
|
||||
if (mFileTreeFragment != null) {
|
||||
mFileTreeFragment.saveTreeState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -365,7 +369,10 @@ public class EditorActivity extends StudioActivity
|
||||
try {
|
||||
checkForCompilerModule();
|
||||
dispatchOnResumeToEditors();
|
||||
mFileTreeFragment.listProjectFiles();
|
||||
|
||||
if (mFileTreeFragment != null) {
|
||||
mFileTreeFragment.listProjectFiles();
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to update files list", th);
|
||||
getApp().toast(R.string.msg_failed_list_files, Toaster.Type.ERROR);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/************************************************************************************
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
*
|
||||
*
|
||||
* AndroidIDE 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
|
||||
@@ -16,11 +14,14 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
*/
|
||||
package com.itsaky.androidide.fragments;
|
||||
|
||||
import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -43,6 +44,7 @@ import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.FileTreeCallable;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.Logger;
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.unnamed.b.atv.model.TreeNode;
|
||||
@@ -50,15 +52,20 @@ import com.unnamed.b.atv.view.AndroidTreeView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
|
||||
|
||||
private static final String KEY_STORED_TREE_STATE = "fileTree_state";
|
||||
private static final Logger LOG = Logger.newInstance("FileTreeFragment");
|
||||
private LayoutEditorFileTreeBinding binding;
|
||||
private AndroidTreeView mFileTreeView;
|
||||
private FileActionListener mFileActionListener;
|
||||
private AndroidProject mProject;
|
||||
private TreeNode mRoot;
|
||||
private String mTreeState;
|
||||
|
||||
public FileTreeFragment() {}
|
||||
|
||||
@@ -71,6 +78,52 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
return frag;
|
||||
}
|
||||
|
||||
public void saveTreeState() {
|
||||
if (mFileTreeView != null) {
|
||||
mTreeState = mFileTreeView.getSaveState();
|
||||
} else {
|
||||
LOG.error("Unable to save tree state. TreeView is null.");
|
||||
mTreeState = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryRestoreState() {
|
||||
tryRestoreState(mTreeState);
|
||||
}
|
||||
|
||||
private void tryRestoreState(String state) {
|
||||
if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
|
||||
|
||||
LOG.debug("Restoring tree view state:", "'" + state + "'");
|
||||
|
||||
mFileTreeView.collapseAll();
|
||||
final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
|
||||
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
|
||||
restoreNodeState(mRoot, openNodes);
|
||||
} else {
|
||||
LOG.error(
|
||||
"Unable to restore tree state",
|
||||
"treeState=" + state,
|
||||
"treeView=" + mFileTreeView);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
|
||||
final var children = root.getChildren();
|
||||
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
|
||||
final var node = children.get(i);
|
||||
if (openNodes.contains(node.getPath())) {
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
restoreNodeState(node, openNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -89,6 +142,10 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mProject = requireArguments().getParcelable("project");
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
|
||||
mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
|
||||
}
|
||||
|
||||
listProjectFiles();
|
||||
}
|
||||
|
||||
@@ -121,41 +178,48 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
expandNode(node);
|
||||
} else {
|
||||
setLoading(node);
|
||||
listNode(node);
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listNode(@NonNull TreeNode node) {
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
saveTreeState();
|
||||
outState.putString(KEY_STORED_TREE_STATE, mTreeState);
|
||||
}
|
||||
|
||||
private void listNode(@NonNull TreeNode node, Runnable whenDone) {
|
||||
|
||||
if (whenDone == null) {
|
||||
whenDone = () -> {};
|
||||
}
|
||||
|
||||
node.getChildren().clear();
|
||||
node.setExpanded(false);
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
() -> {
|
||||
getNodeFromFiles(
|
||||
node.getValue()
|
||||
.listFiles(
|
||||
/* new FileTreeCallable.HiddenFilesFilter() */ ),
|
||||
node);
|
||||
TreeNode temp = node;
|
||||
while (temp.size() == 1) {
|
||||
temp = temp.childAt(0);
|
||||
if (!temp.getValue().isDirectory()) {
|
||||
break;
|
||||
}
|
||||
getNodeFromFiles(
|
||||
temp.getValue()
|
||||
.listFiles(
|
||||
/* new FileTreeCallable.HiddenFilesFilter() */ ),
|
||||
temp);
|
||||
temp.setExpanded(true);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
__ -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
});
|
||||
|
||||
final var finalWhenDone = whenDone;
|
||||
TaskExecutor.execAsync(
|
||||
() -> {
|
||||
getNodeFromFiles(node.getValue().listFiles(), node);
|
||||
TreeNode temp = node;
|
||||
while (temp.size() == 1) {
|
||||
temp = temp.childAt(0);
|
||||
if (!temp.getValue().isDirectory()) {
|
||||
break;
|
||||
}
|
||||
getNodeFromFiles(temp.getValue().listFiles(), temp);
|
||||
temp.setExpanded(true);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
__ -> finalWhenDone.run());
|
||||
}
|
||||
|
||||
private void getNodeFromFiles(File[] files, TreeNode parent) {
|
||||
@@ -246,7 +310,11 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
tree.setDefaultNodeClickListener(FileTreeFragment.this);
|
||||
tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
|
||||
getScrollView().removeAllViews();
|
||||
getScrollView().addView(tree.getView());
|
||||
|
||||
final var view = tree.getView();
|
||||
getScrollView().addView(view);
|
||||
|
||||
view.post(this::tryRestoreState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ public class TaskExecutor {
|
||||
|
||||
private final Logger LOG = Logger.newInstance("TaskExecutor");
|
||||
|
||||
public static <R> void execAsync(Callable<R> callable, Callback<R> callback) {
|
||||
new TaskExecutor().executeAsync(callable, callback);
|
||||
}
|
||||
|
||||
public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
|
||||
@@ -26,10 +26,9 @@ import java.util.Set;
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class AndroidTreeView {
|
||||
|
||||
private static final String NODES_PATH_SEPARATOR = ";";
|
||||
|
||||
protected TreeNode mRoot;
|
||||
public static final String NODES_PATH_SEPARATOR = ";";
|
||||
private final Context mContext;
|
||||
protected TreeNode mRoot;
|
||||
private boolean applyForRoot;
|
||||
private int containerStyle = 0;
|
||||
private int nodeViewBackground = 0;
|
||||
@@ -40,10 +39,6 @@ public class AndroidTreeView {
|
||||
private boolean use2dScroll = false;
|
||||
private boolean enableAutoToggle = true;
|
||||
|
||||
public void setRoot(TreeNode mRoot) {
|
||||
this.mRoot = mRoot;
|
||||
}
|
||||
|
||||
public AndroidTreeView(Context context, TreeNode root, @DrawableRes int nodeBackground) {
|
||||
this.mRoot = root;
|
||||
this.mContext = context;
|
||||
@@ -51,6 +46,71 @@ public class AndroidTreeView {
|
||||
this.defaultViewHolder = new SimpleViewHolder(context);
|
||||
}
|
||||
|
||||
private static void expand(final View v) {
|
||||
v.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
final int targetHeight = v.getMeasuredHeight();
|
||||
|
||||
v.getLayoutParams().height = 0;
|
||||
v.setVisibility(View.VISIBLE);
|
||||
Animation a =
|
||||
new Animation() {
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
v.getLayoutParams().height =
|
||||
interpolatedTime == 1
|
||||
? LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
: (int) (targetHeight * interpolatedTime);
|
||||
v.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willChangeBounds() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 1dp/ms
|
||||
a.setDuration(
|
||||
(int)
|
||||
((targetHeight / v.getContext().getResources().getDisplayMetrics().density)
|
||||
/ 2));
|
||||
v.startAnimation(a);
|
||||
}
|
||||
|
||||
private static void collapse(final View v) {
|
||||
final int initialHeight = v.getMeasuredHeight();
|
||||
|
||||
Animation a =
|
||||
new Animation() {
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
if (interpolatedTime == 1) {
|
||||
v.setVisibility(View.GONE);
|
||||
} else {
|
||||
v.getLayoutParams().height =
|
||||
initialHeight - (int) (initialHeight * interpolatedTime);
|
||||
v.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willChangeBounds() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 1dp/ms
|
||||
a.setDuration(
|
||||
(int)
|
||||
((initialHeight / v.getContext().getResources().getDisplayMetrics().density)
|
||||
/ 2));
|
||||
v.startAnimation(a);
|
||||
}
|
||||
|
||||
public void setRoot(TreeNode mRoot) {
|
||||
this.mRoot = mRoot;
|
||||
}
|
||||
|
||||
public void setDefaultContainerStyle(int style) {
|
||||
setDefaultContainerStyle(style, false);
|
||||
}
|
||||
@@ -229,6 +289,9 @@ public class AndroidTreeView {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Selection methods
|
||||
|
||||
public void expandNode(final TreeNode node, boolean includeSubnodes) {
|
||||
node.setExpanded(true);
|
||||
final TreeNode.BaseNodeViewHolder parentViewHolder = getViewHolderForNode(node);
|
||||
@@ -261,54 +324,31 @@ public class AndroidTreeView {
|
||||
}
|
||||
|
||||
nodeView.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (n.getClickListener() != null) {
|
||||
n.getClickListener().onClick(n, n.getValue());
|
||||
} else if (nodeClickListener != null) {
|
||||
nodeClickListener.onClick(n, n.getValue());
|
||||
}
|
||||
if (enableAutoToggle) {
|
||||
toggleNode(n);
|
||||
}
|
||||
v -> {
|
||||
if (n.getClickListener() != null) {
|
||||
n.getClickListener().onClick(n, n.getValue());
|
||||
} else if (nodeClickListener != null) {
|
||||
nodeClickListener.onClick(n, n.getValue());
|
||||
}
|
||||
if (enableAutoToggle) {
|
||||
toggleNode(n);
|
||||
}
|
||||
});
|
||||
|
||||
nodeView.setOnLongClickListener(
|
||||
new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (n.getLongClickListener() != null) {
|
||||
return n.getLongClickListener().onLongClick(n, n.getValue());
|
||||
} else if (nodeLongClickListener != null) {
|
||||
return nodeLongClickListener.onLongClick(n, n.getValue());
|
||||
}
|
||||
if (enableAutoToggle) {
|
||||
toggleNode(n);
|
||||
}
|
||||
return false;
|
||||
view -> {
|
||||
if (n.getLongClickListener() != null) {
|
||||
return n.getLongClickListener().onLongClick(n, n.getValue());
|
||||
} else if (nodeLongClickListener != null) {
|
||||
return nodeLongClickListener.onLongClick(n, n.getValue());
|
||||
}
|
||||
if (enableAutoToggle) {
|
||||
toggleNode(n);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Selection methods
|
||||
|
||||
public void setSelectionModeEnabled(boolean selectionModeEnabled) {
|
||||
if (!selectionModeEnabled) {
|
||||
// TODO fix double iteration over tree
|
||||
deselectAll();
|
||||
}
|
||||
mSelectionModeEnabled = selectionModeEnabled;
|
||||
|
||||
List<TreeNode> children = mRoot.getChildren();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
TreeNode node = children.get(i);
|
||||
toggleSelectionMode(node, selectionModeEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
public <E> List<E> getSelectedValues(Class<E> clazz) {
|
||||
List<E> result = new ArrayList<>();
|
||||
List<TreeNode> selected = getSelected();
|
||||
@@ -325,6 +365,20 @@ public class AndroidTreeView {
|
||||
return mSelectionModeEnabled;
|
||||
}
|
||||
|
||||
public void setSelectionModeEnabled(boolean selectionModeEnabled) {
|
||||
if (!selectionModeEnabled) {
|
||||
// TODO fix double iteration over tree
|
||||
deselectAll();
|
||||
}
|
||||
mSelectionModeEnabled = selectionModeEnabled;
|
||||
|
||||
List<TreeNode> children = mRoot.getChildren();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
TreeNode node = children.get(i);
|
||||
toggleSelectionMode(node, selectionModeEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSelectionMode(TreeNode parent, boolean mSelectionModeEnabled) {
|
||||
toogleSelectionForNode(parent, mSelectionModeEnabled);
|
||||
if (parent.isExpanded()) {
|
||||
@@ -387,7 +441,7 @@ public class AndroidTreeView {
|
||||
private void selectNode(TreeNode parent, boolean selected, boolean skipCollapsed) {
|
||||
parent.setSelected(selected);
|
||||
toogleSelectionForNode(parent, true);
|
||||
boolean toContinue = skipCollapsed ? parent.isExpanded() : true;
|
||||
boolean toContinue = !skipCollapsed || parent.isExpanded();
|
||||
if (toContinue) {
|
||||
List<TreeNode> children = parent.getChildren();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
@@ -418,67 +472,6 @@ public class AndroidTreeView {
|
||||
return viewHolder;
|
||||
}
|
||||
|
||||
private static void expand(final View v) {
|
||||
v.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
final int targetHeight = v.getMeasuredHeight();
|
||||
|
||||
v.getLayoutParams().height = 0;
|
||||
v.setVisibility(View.VISIBLE);
|
||||
Animation a =
|
||||
new Animation() {
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
v.getLayoutParams().height =
|
||||
interpolatedTime == 1
|
||||
? LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
: (int) (targetHeight * interpolatedTime);
|
||||
v.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willChangeBounds() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 1dp/ms
|
||||
a.setDuration(
|
||||
(int)
|
||||
((targetHeight / v.getContext().getResources().getDisplayMetrics().density)
|
||||
/ 2));
|
||||
v.startAnimation(a);
|
||||
}
|
||||
|
||||
private static void collapse(final View v) {
|
||||
final int initialHeight = v.getMeasuredHeight();
|
||||
|
||||
Animation a =
|
||||
new Animation() {
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
if (interpolatedTime == 1) {
|
||||
v.setVisibility(View.GONE);
|
||||
} else {
|
||||
v.getLayoutParams().height =
|
||||
initialHeight - (int) (initialHeight * interpolatedTime);
|
||||
v.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willChangeBounds() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 1dp/ms
|
||||
a.setDuration(
|
||||
(int)
|
||||
((initialHeight / v.getContext().getResources().getDisplayMetrics().density)
|
||||
/ 2));
|
||||
v.startAnimation(a);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Add / Remove
|
||||
|
||||
|
||||
Reference in New Issue
Block a user