Skip to content

Commit 86512b2

Browse files
committed
Sort notes and folders
1 parent 6f21b8a commit 86512b2

File tree

9 files changed

+270
-17
lines changed

9 files changed

+270
-17
lines changed

android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ android {
5353
applicationId "com.adilhanney.saber"
5454
// You can update the following values to match your application needs.
5555
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
56-
minSdkVersion 23
56+
minSdkVersion 24
5757
targetSdkVersion flutter.targetSdkVersion
5858
versionCode flutterVersionCode.toInteger()
5959
versionName flutterVersionName

lib/components/home/sort_button.dart

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
9+
class SortNotes {
10+
SortNotes._();
11+
12+
static final Map<String, void Function(List<String>, bool)> _sortFunctions = {
13+
t.home.sortNames.alphabetical: _sortNotesAlpha,
14+
t.home.sortNames.lastModified: _sortNotesLastModified,
15+
t.home.sortNames.sizeOnDisk: _sortNotesSize,
16+
};
17+
static final PlainPref<int> _sortFunctionIdx = Prefs.sortFunctionIdx;
18+
static final PlainPref<bool> _isIncreasingOrder = Prefs.isSortIncreasing;
19+
20+
static bool _isNeeded = true;
21+
static bool get isNeeded => _isNeeded;
22+
23+
static int get sortFunctionIdx => _sortFunctionIdx.value;
24+
static set sortFunctionIdx(int value) {
25+
_sortFunctionIdx.value = value;
26+
_isNeeded = true;
27+
}
28+
29+
static bool get isIncreasingOrder => _isIncreasingOrder.value;
30+
static set isIncreasingOrder(bool value) {
31+
_isIncreasingOrder.value = value;
32+
_isNeeded = true;
33+
}
34+
35+
static void _reverse(List<String> list) {
36+
final n = list.length;
37+
for (int i = 0; i < n / 2; i++) {
38+
final tmp = list[i];
39+
list[i] = list[n - i - 1];
40+
list[n - i - 1] = tmp;
41+
}
42+
}
43+
44+
static void sortNotes(List<String> filePaths, {bool forced = false}) {
45+
if (_isNeeded || forced) {
46+
_sortFunctions[_sortFunctions.keys.elementAt(sortFunctionIdx)]!
47+
.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+
final List<String> sortNames = SortNotes._sortFunctions.keys.toList();
148+
149+
return Align(
150+
alignment: Alignment.topRight,
151+
child: Container(
152+
width: 220,
153+
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)),
154+
clipBehavior: Clip.antiAlias,
155+
child: Material(
156+
child: Column(
157+
mainAxisSize: MainAxisSize.min,
158+
children: [
159+
for (int idx = 0; idx < sortNames.length; idx++)
160+
RadioListTile<int>(
161+
title: Text(sortNames[idx]),
162+
onChanged: (int? newValue) => {
163+
SortNotes.sortFunctionIdx = newValue!,
164+
setState(() {}),
165+
// Navigator.pop(context),
166+
},
167+
groupValue: SortNotes.sortFunctionIdx,
168+
value: idx,
169+
),
170+
CheckboxListTile(
171+
controlAffinity: ListTileControlAffinity.leading,
172+
title: Text(t.home.sortNames.increasing),
173+
value: SortNotes.isIncreasingOrder,
174+
onChanged: (bool? v) => {
175+
SortNotes.isIncreasingOrder = v!,
176+
setState(() {}),
177+
}),
178+
],
179+
),
180+
),
181+
),
182+
);
183+
}
184+
}

lib/data/prefs.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ abstract class Prefs {
9393
static late final PlainPref<bool> hideHomeBackgrounds;
9494
static late final PlainPref<bool> printPageIndicators;
9595

96+
static late final PlainPref<int> sortFunctionIdx;
97+
static late final PlainPref<bool> isSortIncreasing;
98+
9699
static late final PlainPref<double> maxImageSize;
97100

98101
static late final PlainPref<bool> autoClearWhiteboardOnExit;
@@ -200,6 +203,9 @@ abstract class Prefs {
200203
hideHomeBackgrounds = PlainPref('hideHomeBackgrounds', false);
201204
printPageIndicators = PlainPref('printPageIndicators', false);
202205

206+
sortFunctionIdx = PlainPref('sortFunctionIdx', 0);
207+
isSortIncreasing = PlainPref('isSortIncreasing', true);
208+
203209
maxImageSize = PlainPref('maxImageSize', 1000);
204210

205211
autoClearWhiteboardOnExit = PlainPref('autoClearWhiteboardOnExit', false);

lib/i18n/strings.i18n.yaml

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

lib/i18n/strings_en.g.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class TranslationsHomeEn {
7575
late final TranslationsHomeRenameNoteEn renameNote = TranslationsHomeRenameNoteEn.internal(_root);
7676
late final TranslationsHomeMoveNoteEn moveNote = TranslationsHomeMoveNoteEn.internal(_root);
7777
String get deleteNote => 'Delete note';
78+
late final TranslationsHomeSortNamesEn sortNames = TranslationsHomeSortNamesEn.internal(_root);
7879
late final TranslationsHomeRenameFolderEn renameFolder = TranslationsHomeRenameFolderEn.internal(_root);
7980
late final TranslationsHomeDeleteFolderEn deleteFolder = TranslationsHomeDeleteFolderEn.internal(_root);
8081
}
@@ -309,6 +310,19 @@ class TranslationsHomeMoveNoteEn {
309310
String numberRenamedTo({required Object n}) => '${n} notes will be renamed to avoid conflicts';
310311
}
311312

313+
// Path: home.sortNames
314+
class TranslationsHomeSortNamesEn {
315+
TranslationsHomeSortNamesEn.internal(this._root);
316+
317+
final Translations _root; // ignore: unused_field
318+
319+
// Translations
320+
String get alphabetical => 'Alphabetical';
321+
String get lastModified => 'Last Modified';
322+
String get sizeOnDisk => 'Size';
323+
String get increasing => 'Increasing';
324+
}
325+
312326
// Path: home.renameFolder
313327
class TranslationsHomeRenameFolderEn {
314328
TranslationsHomeRenameFolderEn.internal(this._root);

lib/i18n/strings_it.g.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class _TranslationsHomeIt extends TranslationsHomeEn {
7272
@override late final _TranslationsHomeRenameNoteIt renameNote = _TranslationsHomeRenameNoteIt._(_root);
7373
@override late final _TranslationsHomeMoveNoteIt moveNote = _TranslationsHomeMoveNoteIt._(_root);
7474
@override String get deleteNote => 'Elimina nota';
75+
@override late final _TranslationsHomeSortNamesIt sortNames = _TranslationsHomeSortNamesIt._(_root);
7576
@override late final _TranslationsHomeRenameFolderIt renameFolder = _TranslationsHomeRenameFolderIt._(_root);
7677
@override late final _TranslationsHomeDeleteFolderIt deleteFolder = _TranslationsHomeDeleteFolderIt._(_root);
7778
}
@@ -306,6 +307,19 @@ class _TranslationsHomeMoveNoteIt extends TranslationsHomeMoveNoteEn {
306307
@override String numberRenamedTo({required Object n}) => '${n} le note verranno rinominate per evitare conflitti';
307308
}
308309

310+
// Path: home.sortNames
311+
class _TranslationsHomeSortNamesIt extends TranslationsHomeSortNamesEn {
312+
_TranslationsHomeSortNamesIt._(TranslationsIt root) : this._root = root, super.internal(root);
313+
314+
final TranslationsIt _root; // ignore: unused_field
315+
316+
// Translations
317+
@override String get alphabetical => 'Alfabetico';
318+
@override String get lastModified => 'Ultima Modifica';
319+
@override String get sizeOnDisk => 'Dimensioni';
320+
@override String get increasing => 'Crescente';
321+
}
322+
309323
// Path: home.renameFolder
310324
class _TranslationsHomeRenameFolderIt extends TranslationsHomeRenameFolderEn {
311325
_TranslationsHomeRenameFolderIt._(TranslationsIt root) : this._root = root, super.internal(root);

lib/i18n/strings_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/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)