Skip to content

Commit 8c45153

Browse files
committed
Sort notes and folders
1 parent c873848 commit 8c45153

File tree

8 files changed

+273
-16
lines changed

8 files changed

+273
-16
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import 'dart:io';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:saber/data/file_manager/file_manager.dart';
5+
import 'package:saber/data/prefs.dart';
6+
import 'package:saber/i18n/strings.g.dart';
7+
import 'package:saber/pages/editor/editor.dart';
8+
import 'package:stow_plain/stow_plain.dart';
9+
10+
class SortNotes {
11+
SortNotes._();
12+
13+
static final List<void Function(List<String>, bool)> _sortFunctions = [
14+
_sortNotesAlpha,
15+
_sortNotesLastModified,
16+
_sortNotesSize,
17+
];
18+
static final PlainStow<int> _sortFunctionIdx = stows.sortFunctionIdx;
19+
static final PlainStow<bool> _isIncreasingOrder = stows.isSortIncreasing;
20+
21+
static bool _isNeeded = true;
22+
static bool get isNeeded => _isNeeded;
23+
24+
static int get sortFunctionIdx => _sortFunctionIdx.value;
25+
static set sortFunctionIdx(int value) {
26+
_sortFunctionIdx.value = value;
27+
_isNeeded = true;
28+
}
29+
30+
static bool get isIncreasingOrder => _isIncreasingOrder.value;
31+
static set isIncreasingOrder(bool value) {
32+
_isIncreasingOrder.value = value;
33+
_isNeeded = true;
34+
}
35+
36+
static void _reverse(List<String> list) {
37+
final n = list.length;
38+
for (int i = 0; i < n / 2; i++) {
39+
final tmp = list[i];
40+
list[i] = list[n - i - 1];
41+
list[n - i - 1] = tmp;
42+
}
43+
}
44+
45+
static void sortNotes(List<String> filePaths, {bool forced = false}) {
46+
if (_isNeeded || forced) {
47+
_sortFunctions[sortFunctionIdx].call(filePaths, isIncreasingOrder);
48+
_isNeeded = false;
49+
}
50+
}
51+
52+
static void _sortNotesAlpha(List<String> filePaths, bool isIncreasing) {
53+
filePaths.sort((a, b) => a.split('/').last.compareTo(b.split('/').last));
54+
if (!isIncreasing) _reverse(filePaths);
55+
}
56+
57+
static DateTime _getDirLastModified(Directory dir) {
58+
assert(dir.existsSync());
59+
DateTime out = dir.statSync().modified;
60+
for (FileSystemEntity entity
61+
in dir.listSync(recursive: true, followLinks: false)) {
62+
if (entity is File && entity.absolute.path.endsWith(Editor.extension)) {
63+
final DateTime curFileModified = entity.lastModifiedSync();
64+
if (curFileModified.isAfter(out)) out = curFileModified;
65+
}
66+
}
67+
return out;
68+
}
69+
70+
static void _sortNotesLastModified(
71+
List<String> filePaths, bool isIncreasing) {
72+
filePaths.sort((a, b) {
73+
final Directory firstDir = Directory(FileManager.documentsDirectory + a);
74+
final Directory secondDir = Directory(FileManager.documentsDirectory + b);
75+
final DateTime firstTime = firstDir.existsSync()
76+
? _getDirLastModified(firstDir)
77+
: FileManager.lastModified(a + Editor.extension);
78+
final DateTime secondTime = secondDir.existsSync()
79+
? _getDirLastModified(secondDir)
80+
: FileManager.lastModified(b + Editor.extension);
81+
return firstTime.compareTo(secondTime);
82+
});
83+
if (!isIncreasing) _reverse(filePaths);
84+
}
85+
86+
static int _getDirSize(Directory dir) {
87+
assert(dir.existsSync());
88+
int out = 0;
89+
for (FileSystemEntity entity
90+
in dir.listSync(recursive: true, followLinks: false)) {
91+
if (entity is File && entity.absolute.path.endsWith(Editor.extension)) {
92+
final int curFileSize = entity.lengthSync();
93+
out += curFileSize;
94+
}
95+
}
96+
return out;
97+
}
98+
99+
static void _sortNotesSize(List<String> filePaths, bool isIncreasing) {
100+
filePaths.sort((a, b) {
101+
final Directory firstDir = Directory(FileManager.documentsDirectory + a);
102+
final Directory secondDir = Directory(FileManager.documentsDirectory + b);
103+
final int firstSize = firstDir.existsSync()
104+
? _getDirSize(firstDir)
105+
: FileManager.getFile('$a${Editor.extension}').statSync().size;
106+
final int secondSize = secondDir.existsSync()
107+
? _getDirSize(secondDir)
108+
: FileManager.getFile('$b${Editor.extension}').statSync().size;
109+
return firstSize.compareTo(secondSize);
110+
});
111+
if (!isIncreasing) _reverse(filePaths);
112+
}
113+
}
114+
115+
class SortButton extends StatelessWidget {
116+
const SortButton({
117+
super.key,
118+
required this.callback,
119+
});
120+
121+
final void Function() callback;
122+
123+
@override
124+
Widget build(BuildContext context) {
125+
return IconButton(
126+
icon: Icon(Icons.sort),
127+
onPressed: () async {
128+
showDialog(
129+
context: context,
130+
builder: (BuildContext context) {
131+
return _SortButtonDialog();
132+
},
133+
).then((_) => callback());
134+
},
135+
);
136+
}
137+
}
138+
139+
class _SortButtonDialog extends StatefulWidget {
140+
@override
141+
State<_SortButtonDialog> createState() => _SortButtonDialogState();
142+
}
143+
144+
class _SortButtonDialogState extends State<_SortButtonDialog> {
145+
@override
146+
Widget build(BuildContext context) {
147+
// Needs to match the order of _sortFunctions
148+
final List<String> sortNames = [
149+
t.home.sortNames.alphabetical,
150+
t.home.sortNames.lastModified,
151+
t.home.sortNames.sizeOnDisk,
152+
];
153+
154+
return Align(
155+
alignment: Alignment.topRight,
156+
child: Container(
157+
width: 220,
158+
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)),
159+
clipBehavior: Clip.antiAlias,
160+
child: Material(
161+
child: Column(
162+
mainAxisSize: MainAxisSize.min,
163+
children: [
164+
for (int idx = 0; idx < sortNames.length; idx++)
165+
RadioListTile<int>(
166+
title: Text(sortNames[idx]),
167+
onChanged: (int? newValue) => {
168+
SortNotes.sortFunctionIdx = newValue!,
169+
setState(() {}),
170+
// Navigator.pop(context),
171+
},
172+
groupValue: SortNotes.sortFunctionIdx,
173+
value: idx,
174+
),
175+
CheckboxListTile(
176+
controlAffinity: ListTileControlAffinity.leading,
177+
title: Text(t.home.sortNames.increasing),
178+
value: SortNotes.isIncreasingOrder,
179+
onChanged: (bool? v) => {
180+
SortNotes.isIncreasingOrder = v!,
181+
setState(() {}),
182+
}),
183+
],
184+
),
185+
),
186+
),
187+
);
188+
}
189+
}

lib/data/prefs.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ class Stows {
122122
final printPageIndicators =
123123
PlainStow('printPageIndicators', false, volatile: !_isOnMainIsolate);
124124

125+
final sortFunctionIdx =
126+
PlainStow('sortFunctionIdx', 0, volatile: !_isOnMainIsolate);
127+
final isSortIncreasing =
128+
PlainStow('isSortIncreasing', true, volatile: !_isOnMainIsolate);
129+
125130
final maxImageSize =
126131
PlainStow<double>('maxImageSize', 1000, volatile: !_isOnMainIsolate);
127132

lib/i18n/en.i18n.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ home:
4949
multipleRenamedTo: "The following notes will be renamed:"
5050
numberRenamedTo: $n notes will be renamed to avoid conflicts
5151
deleteNote: Delete note
52+
sortNames:
53+
alphabetical: Alphabetical
54+
lastModified: Last Modified
55+
sizeOnDisk: Size
56+
increasing: Increasing
5257
renameFolder:
5358
renameFolder: Rename folder
5459
folderName: Folder name

lib/i18n/it.i18n.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ home:
4848
multipleRenamedTo: "Le note seguenti verranno rinominate:"
4949
numberRenamedTo: $n le note verranno rinominate per evitare conflitti
5050
deleteNote: Elimina nota
51+
sortNames:
52+
alphabetical: Alfabetico
53+
lastModified: Ultima Modifica
54+
sizeOnDisk: Dimensioni
55+
increasing: Crescente
5156
renameFolder:
5257
renameFolder: Rinomina cartella
5358
folderName: Nome cartella

lib/i18n/strings_en.g.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class TranslationsHomeEn {
7878
late final TranslationsHomeRenameNoteEn renameNote = TranslationsHomeRenameNoteEn.internal(_root);
7979
late final TranslationsHomeMoveNoteEn moveNote = TranslationsHomeMoveNoteEn.internal(_root);
8080
String get deleteNote => 'Delete note';
81+
late final TranslationsHomeSortNamesEn sortNames = TranslationsHomeSortNamesEn.internal(_root);
8182
late final TranslationsHomeRenameFolderEn renameFolder = TranslationsHomeRenameFolderEn.internal(_root);
8283
late final TranslationsHomeDeleteFolderEn deleteFolder = TranslationsHomeDeleteFolderEn.internal(_root);
8384
}
@@ -314,6 +315,19 @@ class TranslationsHomeMoveNoteEn {
314315
String numberRenamedTo({required Object n}) => '${n} notes will be renamed to avoid conflicts';
315316
}
316317

318+
// Path: home.sortNames
319+
class TranslationsHomeSortNamesEn {
320+
TranslationsHomeSortNamesEn.internal(this._root);
321+
322+
final Translations _root; // ignore: unused_field
323+
324+
// Translations
325+
String get alphabetical => 'Alphabetical';
326+
String get lastModified => 'Last Modified';
327+
String get sizeOnDisk => 'Size';
328+
String get increasing => 'Increasing';
329+
}
330+
317331
// Path: home.renameFolder
318332
class TranslationsHomeRenameFolderEn {
319333
TranslationsHomeRenameFolderEn.internal(this._root);

lib/i18n/strings_it.g.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class _TranslationsHomeIt extends TranslationsHomeEn {
7575
@override late final _TranslationsHomeRenameNoteIt renameNote = _TranslationsHomeRenameNoteIt._(_root);
7676
@override late final _TranslationsHomeMoveNoteIt moveNote = _TranslationsHomeMoveNoteIt._(_root);
7777
@override String get deleteNote => 'Elimina nota';
78+
@override late final _TranslationsHomeSortNamesIt sortNames = _TranslationsHomeSortNamesIt._(_root);
7879
@override late final _TranslationsHomeRenameFolderIt renameFolder = _TranslationsHomeRenameFolderIt._(_root);
7980
@override late final _TranslationsHomeDeleteFolderIt deleteFolder = _TranslationsHomeDeleteFolderIt._(_root);
8081
@override String get noPreviewAvailable => 'Nessuna anteprima disponibile';
@@ -312,6 +313,19 @@ class _TranslationsHomeMoveNoteIt extends TranslationsHomeMoveNoteEn {
312313
@override String numberRenamedTo({required Object n}) => '${n} le note verranno rinominate per evitare conflitti';
313314
}
314315

316+
// Path: home.sortNames
317+
class _TranslationsHomeSortNamesIt extends TranslationsHomeSortNamesEn {
318+
_TranslationsHomeSortNamesIt._(TranslationsIt root) : this._root = root, super.internal(root);
319+
320+
final TranslationsIt _root; // ignore: unused_field
321+
322+
// Translations
323+
@override String get alphabetical => 'Alfabetico';
324+
@override String get lastModified => 'Ultima Modifica';
325+
@override String get sizeOnDisk => 'Dimensioni';
326+
@override String get increasing => 'Crescente';
327+
}
328+
315329
// Path: home.renameFolder
316330
class _TranslationsHomeRenameFolderIt extends TranslationsHomeRenameFolderEn {
317331
_TranslationsHomeRenameFolderIt._(TranslationsIt root) : this._root = root, super.internal(root);

lib/pages/home/browse.dart

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:saber/components/home/move_note_button.dart';
1010
import 'package:saber/components/home/new_note_button.dart';
1111
import 'package:saber/components/home/no_files.dart';
1212
import 'package:saber/components/home/rename_note_button.dart';
13+
import 'package:saber/components/home/sort_button.dart';
1314
import 'package:saber/components/home/syncing_button.dart';
1415
import 'package:saber/data/file_manager/file_manager.dart';
1516
import 'package:saber/data/routes.dart';
@@ -30,6 +31,8 @@ class BrowsePage extends StatefulWidget {
3031

3132
class _BrowsePageState extends State<BrowsePage> {
3233
DirectoryChildren? children;
34+
final List<String> files = [];
35+
final List<String> folders = [];
3336

3437
final List<String?> pathHistory = [];
3538
String? path;
@@ -41,6 +44,7 @@ class _BrowsePageState extends State<BrowsePage> {
4144
path = widget.initialPath;
4245

4346
findChildrenOfPath();
47+
4448
fileWriteSubscription =
4549
FileManager.fileWriteStream.stream.listen(fileWriteListener);
4650
selectedFiles.addListener(_setState);
@@ -73,6 +77,16 @@ class _BrowsePageState extends State<BrowsePage> {
7377
}
7478

7579
children = await FileManager.getChildrenOfDirectory(path ?? '/');
80+
files.clear();
81+
for (String filePath in children?.files ?? const []) {
82+
files.add("${path ?? ""}/$filePath");
83+
}
84+
folders.clear();
85+
for (String directoryPath in children?.directories ?? const []) {
86+
folders.add("${path ?? ""}/$directoryPath");
87+
}
88+
SortNotes.sortNotes(files, forced: true);
89+
SortNotes.sortNotes(folders, forced: true);
7690

7791
if (mounted) setState(() {});
7892
}
@@ -135,8 +149,18 @@ class _BrowsePageState extends State<BrowsePage> {
135149
titlePadding: EdgeInsetsDirectional.only(
136150
start: cupertino ? 0 : 16, bottom: 16),
137151
),
138-
actions: const [
139-
SyncingButton(),
152+
actions: [
153+
const SyncingButton(),
154+
SortButton(
155+
callback: () => {
156+
if (SortNotes.isNeeded)
157+
{
158+
SortNotes.sortNotes(files, forced: true),
159+
SortNotes.sortNotes(folders, forced: true),
160+
setState(() {}),
161+
}
162+
},
163+
),
140164
],
141165
),
142166
),
@@ -164,10 +188,7 @@ class _BrowsePageState extends State<BrowsePage> {
164188
await FileManager.deleteDirectory(folderPath);
165189
findChildrenOfPath();
166190
},
167-
folders: [
168-
for (String directoryPath in children?.directories ?? const [])
169-
directoryPath,
170-
],
191+
folders: folders.map((e) => e.split('/').last).toList(),
171192
),
172193
if (children == null) ...[
173194
// loading
@@ -185,10 +206,7 @@ class _BrowsePageState extends State<BrowsePage> {
185206
),
186207
sliver: MasonryFiles(
187208
crossAxisCount: crossAxisCount,
188-
files: [
189-
for (String filePath in children?.files ?? const [])
190-
"${path ?? ""}/$filePath",
191-
],
209+
files: files,
192210
selectedFiles: selectedFiles,
193211
),
194212
),

0 commit comments

Comments
 (0)