Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit 34e0841

Browse files
committed
Restore File tree state when application is resumed (closes #150)
1 parent 25272b3 commit 34e0841

File tree

4 files changed

+216
-144
lines changed

4 files changed

+216
-144
lines changed

app/src/main/java/com/itsaky/androidide/EditorActivity.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ protected void onPause() {
354354
super.onPause();
355355
dispatchOnPauseToEditors();
356356
EditorActivityActions.clear();
357+
358+
if (mFileTreeFragment != null) {
359+
mFileTreeFragment.saveTreeState();
360+
}
357361
}
358362

359363
@Override
@@ -365,7 +369,10 @@ protected void onResume() {
365369
try {
366370
checkForCompilerModule();
367371
dispatchOnResumeToEditors();
368-
mFileTreeFragment.listProjectFiles();
372+
373+
if (mFileTreeFragment != null) {
374+
mFileTreeFragment.listProjectFiles();
375+
}
369376
} catch (Throwable th) {
370377
LOG.error("Failed to update files list", th);
371378
getApp().toast(R.string.msg_failed_list_files, Toaster.Type.ERROR);

app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java

Lines changed: 102 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
/************************************************************************************
1+
/*
22
* This file is part of AndroidIDE.
33
*
4-
*
5-
*
64
* AndroidIDE is free software: you can redistribute it and/or modify
75
* it under the terms of the GNU General Public License as published by
86
* the Free Software Foundation, either version 3 of the License, or
@@ -16,11 +14,14 @@
1614
* You should have received a copy of the GNU General Public License
1715
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
1816
*
19-
**************************************************************************************/
17+
*/
2018
package com.itsaky.androidide.fragments;
2119

20+
import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR;
21+
2222
import android.content.Context;
2323
import android.os.Bundle;
24+
import android.text.TextUtils;
2425
import android.view.LayoutInflater;
2526
import android.view.View;
2627
import android.view.ViewGroup;
@@ -43,22 +44,28 @@
4344
import com.itsaky.androidide.tasks.TaskExecutor;
4445
import com.itsaky.androidide.tasks.callables.FileTreeCallable;
4546
import com.itsaky.androidide.utils.Environment;
47+
import com.itsaky.androidide.utils.Logger;
4648
import com.itsaky.androidide.views.editor.CodeEditorView;
4749
import com.itsaky.toaster.Toaster;
4850
import com.unnamed.b.atv.model.TreeNode;
4951
import com.unnamed.b.atv.view.AndroidTreeView;
5052

5153
import java.io.File;
5254
import java.util.Arrays;
55+
import java.util.HashSet;
56+
import java.util.Set;
5357

5458
public class FileTreeFragment extends BottomSheetDialogFragment
5559
implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
5660

61+
private static final String KEY_STORED_TREE_STATE = "fileTree_state";
62+
private static final Logger LOG = Logger.newInstance("FileTreeFragment");
5763
private LayoutEditorFileTreeBinding binding;
5864
private AndroidTreeView mFileTreeView;
5965
private FileActionListener mFileActionListener;
6066
private AndroidProject mProject;
6167
private TreeNode mRoot;
68+
private String mTreeState;
6269

6370
public FileTreeFragment() {}
6471

@@ -71,6 +78,52 @@ public static FileTreeFragment newInstance(AndroidProject project) {
7178
return frag;
7279
}
7380

81+
public void saveTreeState() {
82+
if (mFileTreeView != null) {
83+
mTreeState = mFileTreeView.getSaveState();
84+
} else {
85+
LOG.error("Unable to save tree state. TreeView is null.");
86+
mTreeState = null;
87+
}
88+
}
89+
90+
private void tryRestoreState() {
91+
tryRestoreState(mTreeState);
92+
}
93+
94+
private void tryRestoreState(String state) {
95+
if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
96+
97+
LOG.debug("Restoring tree view state:", "'" + state + "'");
98+
99+
mFileTreeView.collapseAll();
100+
final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
101+
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
102+
restoreNodeState(mRoot, openNodes);
103+
} else {
104+
LOG.error(
105+
"Unable to restore tree state",
106+
"treeState=" + state,
107+
"treeView=" + mFileTreeView);
108+
}
109+
}
110+
111+
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
112+
final var children = root.getChildren();
113+
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
114+
final var node = children.get(i);
115+
if (openNodes.contains(node.getPath())) {
116+
listNode(
117+
node,
118+
() -> {
119+
updateChevron(node);
120+
expandNode(node);
121+
restoreNodeState(node, openNodes);
122+
});
123+
}
124+
}
125+
}
126+
74127
@Override
75128
public void onAttach(@NonNull Context context) {
76129
super.onAttach(context);
@@ -89,6 +142,10 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
89142
super.onViewCreated(view, savedInstanceState);
90143
mProject = requireArguments().getParcelable("project");
91144

145+
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
146+
mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
147+
}
148+
92149
listProjectFiles();
93150
}
94151

@@ -121,41 +178,48 @@ public void onClick(TreeNode node, Object p2) {
121178
expandNode(node);
122179
} else {
123180
setLoading(node);
124-
listNode(node);
181+
listNode(
182+
node,
183+
() -> {
184+
updateChevron(node);
185+
expandNode(node);
186+
});
125187
}
126188
}
127189
}
128190

129-
private void listNode(@NonNull TreeNode node) {
191+
@Override
192+
public void onSaveInstanceState(@NonNull Bundle outState) {
193+
super.onSaveInstanceState(outState);
194+
saveTreeState();
195+
outState.putString(KEY_STORED_TREE_STATE, mTreeState);
196+
}
197+
198+
private void listNode(@NonNull TreeNode node, Runnable whenDone) {
199+
200+
if (whenDone == null) {
201+
whenDone = () -> {};
202+
}
203+
130204
node.getChildren().clear();
131205
node.setExpanded(false);
132-
new TaskExecutor()
133-
.executeAsync(
134-
() -> {
135-
getNodeFromFiles(
136-
node.getValue()
137-
.listFiles(
138-
/* new FileTreeCallable.HiddenFilesFilter() */ ),
139-
node);
140-
TreeNode temp = node;
141-
while (temp.size() == 1) {
142-
temp = temp.childAt(0);
143-
if (!temp.getValue().isDirectory()) {
144-
break;
145-
}
146-
getNodeFromFiles(
147-
temp.getValue()
148-
.listFiles(
149-
/* new FileTreeCallable.HiddenFilesFilter() */ ),
150-
temp);
151-
temp.setExpanded(true);
152-
}
153-
return null;
154-
},
155-
__ -> {
156-
updateChevron(node);
157-
expandNode(node);
158-
});
206+
207+
final var finalWhenDone = whenDone;
208+
TaskExecutor.execAsync(
209+
() -> {
210+
getNodeFromFiles(node.getValue().listFiles(), node);
211+
TreeNode temp = node;
212+
while (temp.size() == 1) {
213+
temp = temp.childAt(0);
214+
if (!temp.getValue().isDirectory()) {
215+
break;
216+
}
217+
getNodeFromFiles(temp.getValue().listFiles(), temp);
218+
temp.setExpanded(true);
219+
}
220+
return null;
221+
},
222+
__ -> finalWhenDone.run());
159223
}
160224

161225
private void getNodeFromFiles(File[] files, TreeNode parent) {
@@ -246,7 +310,11 @@ public void listProjectFiles() {
246310
tree.setDefaultNodeClickListener(FileTreeFragment.this);
247311
tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
248312
getScrollView().removeAllViews();
249-
getScrollView().addView(tree.getView());
313+
314+
final var view = tree.getView();
315+
getScrollView().addView(view);
316+
317+
view.post(this::tryRestoreState);
250318
}
251319
});
252320
}

app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public class TaskExecutor {
2929

3030
private final Logger LOG = Logger.newInstance("TaskExecutor");
3131

32+
public static <R> void execAsync(Callable<R> callable, Callback<R> callback) {
33+
new TaskExecutor().executeAsync(callable, callback);
34+
}
35+
3236
public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
3337
CompletableFuture.supplyAsync(
3438
() -> {

0 commit comments

Comments
 (0)