Skip to content

feat: Sort notes and folders in recent and browse pages #1408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ android {
applicationId "com.adilhanney.saber"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion 23
minSdkVersion 24
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
188 changes: 188 additions & 0 deletions lib/components/home/sort_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:saber/data/file_manager/file_manager.dart';
import 'package:saber/data/prefs.dart';
import 'package:saber/i18n/strings.g.dart';
import 'package:saber/pages/editor/editor.dart';

class SortNotes {
SortNotes._();

static final List<void Function(List<String>, bool)> _sortFunctions = [
_sortNotesAlpha,
_sortNotesLastModified,
_sortNotesSize,
];
static final PlainPref<int> _sortFunctionIdx = Prefs.sortFunctionIdx;
static final PlainPref<bool> _isIncreasingOrder = Prefs.isSortIncreasing;

static bool _isNeeded = true;
static bool get isNeeded => _isNeeded;

static int get sortFunctionIdx => _sortFunctionIdx.value;
static set sortFunctionIdx(int value) {
_sortFunctionIdx.value = value;
_isNeeded = true;
}

static bool get isIncreasingOrder => _isIncreasingOrder.value;
static set isIncreasingOrder(bool value) {
_isIncreasingOrder.value = value;
_isNeeded = true;
}

static void _reverse(List<String> list) {
final n = list.length;
for (int i = 0; i < n / 2; i++) {
final tmp = list[i];
list[i] = list[n - i - 1];
list[n - i - 1] = tmp;
}
}

static void sortNotes(List<String> filePaths, {bool forced = false}) {
if (_isNeeded || forced) {
_sortFunctions[sortFunctionIdx].call(filePaths, isIncreasingOrder);
_isNeeded = false;
}
}

static void _sortNotesAlpha(List<String> filePaths, bool isIncreasing) {
filePaths.sort((a, b) => a.split('/').last.compareTo(b.split('/').last));
if (!isIncreasing) _reverse(filePaths);
}

static DateTime _getDirLastModified(Directory dir) {
assert(dir.existsSync());
DateTime out = dir.statSync().modified;
for (FileSystemEntity entity
in dir.listSync(recursive: true, followLinks: false)) {
if (entity is File && entity.absolute.path.endsWith(Editor.extension)) {
final DateTime curFileModified = entity.lastModifiedSync();
if (curFileModified.isAfter(out)) out = curFileModified;
}
}
return out;
}

static void _sortNotesLastModified(
List<String> filePaths, bool isIncreasing) {
filePaths.sort((a, b) {
final Directory firstDir = Directory(FileManager.documentsDirectory + a);
final Directory secondDir = Directory(FileManager.documentsDirectory + b);
final DateTime firstTime = firstDir.existsSync()
? _getDirLastModified(firstDir)
: FileManager.lastModified(a + Editor.extension);
final DateTime secondTime = secondDir.existsSync()
? _getDirLastModified(secondDir)
: FileManager.lastModified(b + Editor.extension);
return firstTime.compareTo(secondTime);
});
if (!isIncreasing) _reverse(filePaths);
}

static int _getDirSize(Directory dir) {
assert(dir.existsSync());
int out = 0;
for (FileSystemEntity entity
in dir.listSync(recursive: true, followLinks: false)) {
if (entity is File && entity.absolute.path.endsWith(Editor.extension)) {
final int curFileSize = entity.lengthSync();
out += curFileSize;
}
}
return out;
}

static void _sortNotesSize(List<String> filePaths, bool isIncreasing) {
filePaths.sort((a, b) {
final Directory firstDir = Directory(FileManager.documentsDirectory + a);
final Directory secondDir = Directory(FileManager.documentsDirectory + b);
final int firstSize = firstDir.existsSync()
? _getDirSize(firstDir)
: FileManager.getFile('$a${Editor.extension}').statSync().size;
final int secondSize = secondDir.existsSync()
? _getDirSize(secondDir)
: FileManager.getFile('$b${Editor.extension}').statSync().size;
return firstSize.compareTo(secondSize);
});
if (!isIncreasing) _reverse(filePaths);
}
}

class SortButton extends StatelessWidget {
const SortButton({
super.key,
required this.callback,
});

final void Function() callback;

@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.sort),
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return _SortButtonDialog();
},
).then((_) => callback());
},
);
}
}

class _SortButtonDialog extends StatefulWidget {
@override
State<_SortButtonDialog> createState() => _SortButtonDialogState();
}

class _SortButtonDialogState extends State<_SortButtonDialog> {
@override
Widget build(BuildContext context) {
// Needs to match the order of _sortFunctions
final List<String> sortNames = [
t.home.sortNames.alphabetical,
t.home.sortNames.lastModified,
t.home.sortNames.sizeOnDisk,
];

return Align(
alignment: Alignment.topRight,
child: Container(
width: 220,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)),
clipBehavior: Clip.antiAlias,
child: Material(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int idx = 0; idx < sortNames.length; idx++)
RadioListTile<int>(
title: Text(sortNames[idx]),
onChanged: (int? newValue) => {
SortNotes.sortFunctionIdx = newValue!,
setState(() {}),
// Navigator.pop(context),
},
groupValue: SortNotes.sortFunctionIdx,
value: idx,
),
CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
title: Text(t.home.sortNames.increasing),
value: SortNotes.isIncreasingOrder,
onChanged: (bool? v) => {
SortNotes.isIncreasingOrder = v!,
setState(() {}),
}),
],
),
),
),
);
}
}
6 changes: 6 additions & 0 deletions lib/data/prefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ abstract class Prefs {
static late final PlainPref<bool> hideHomeBackgrounds;
static late final PlainPref<bool> printPageIndicators;

static late final PlainPref<int> sortFunctionIdx;
static late final PlainPref<bool> isSortIncreasing;

static late final PlainPref<double> maxImageSize;

static late final PlainPref<bool> autoClearWhiteboardOnExit;
Expand Down Expand Up @@ -212,6 +215,9 @@ abstract class Prefs {
hideHomeBackgrounds = PlainPref('hideHomeBackgrounds', false);
printPageIndicators = PlainPref('printPageIndicators', false);

sortFunctionIdx = PlainPref('sortFunctionIdx', 0);
isSortIncreasing = PlainPref('isSortIncreasing', true);

maxImageSize = PlainPref('maxImageSize', 1000);

autoClearWhiteboardOnExit = PlainPref('autoClearWhiteboardOnExit', false);
Expand Down
5 changes: 5 additions & 0 deletions lib/i18n/en.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ home:
multipleRenamedTo: "The following notes will be renamed:"
numberRenamedTo: $n notes will be renamed to avoid conflicts
deleteNote: Delete note
sortNames:
alphabetical: Alphabetical
lastModified: Last Modified
sizeOnDisk: Size
increasing: Increasing
renameFolder:
renameFolder: Rename folder
folderName: Folder name
Expand Down
5 changes: 5 additions & 0 deletions lib/i18n/it.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ home:
multipleRenamedTo: "Le note seguenti verranno rinominate:"
numberRenamedTo: $n le note verranno rinominate per evitare conflitti
deleteNote: Elimina nota
sortNames:
alphabetical: Alfabetico
lastModified: Ultima Modifica
sizeOnDisk: Dimensioni
increasing: Crescente
renameFolder:
renameFolder: Rinomina cartella
folderName: Nome cartella
Expand Down
14 changes: 14 additions & 0 deletions lib/i18n/strings_en.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class TranslationsHomeEn {
late final TranslationsHomeRenameNoteEn renameNote = TranslationsHomeRenameNoteEn.internal(_root);
late final TranslationsHomeMoveNoteEn moveNote = TranslationsHomeMoveNoteEn.internal(_root);
String get deleteNote => 'Delete note';
late final TranslationsHomeSortNamesEn sortNames = TranslationsHomeSortNamesEn.internal(_root);
late final TranslationsHomeRenameFolderEn renameFolder = TranslationsHomeRenameFolderEn.internal(_root);
late final TranslationsHomeDeleteFolderEn deleteFolder = TranslationsHomeDeleteFolderEn.internal(_root);
}
Expand Down Expand Up @@ -309,6 +310,19 @@ class TranslationsHomeMoveNoteEn {
String numberRenamedTo({required Object n}) => '${n} notes will be renamed to avoid conflicts';
}

// Path: home.sortNames
class TranslationsHomeSortNamesEn {
TranslationsHomeSortNamesEn.internal(this._root);

final Translations _root; // ignore: unused_field

// Translations
String get alphabetical => 'Alphabetical';
String get lastModified => 'Last Modified';
String get sizeOnDisk => 'Size';
String get increasing => 'Increasing';
}

// Path: home.renameFolder
class TranslationsHomeRenameFolderEn {
TranslationsHomeRenameFolderEn.internal(this._root);
Expand Down
14 changes: 14 additions & 0 deletions lib/i18n/strings_it.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class _TranslationsHomeIt extends TranslationsHomeEn {
@override late final _TranslationsHomeRenameNoteIt renameNote = _TranslationsHomeRenameNoteIt._(_root);
@override late final _TranslationsHomeMoveNoteIt moveNote = _TranslationsHomeMoveNoteIt._(_root);
@override String get deleteNote => 'Elimina nota';
@override late final _TranslationsHomeSortNamesIt sortNames = _TranslationsHomeSortNamesIt._(_root);
@override late final _TranslationsHomeRenameFolderIt renameFolder = _TranslationsHomeRenameFolderIt._(_root);
@override late final _TranslationsHomeDeleteFolderIt deleteFolder = _TranslationsHomeDeleteFolderIt._(_root);
}
Expand Down Expand Up @@ -306,6 +307,19 @@ class _TranslationsHomeMoveNoteIt extends TranslationsHomeMoveNoteEn {
@override String numberRenamedTo({required Object n}) => '${n} le note verranno rinominate per evitare conflitti';
}

// Path: home.sortNames
class _TranslationsHomeSortNamesIt extends TranslationsHomeSortNamesEn {
_TranslationsHomeSortNamesIt._(TranslationsIt root) : this._root = root, super.internal(root);

final TranslationsIt _root; // ignore: unused_field

// Translations
@override String get alphabetical => 'Alfabetico';
@override String get lastModified => 'Ultima Modifica';
@override String get sizeOnDisk => 'Dimensioni';
@override String get increasing => 'Crescente';
}

// Path: home.renameFolder
class _TranslationsHomeRenameFolderIt extends TranslationsHomeRenameFolderEn {
_TranslationsHomeRenameFolderIt._(TranslationsIt root) : this._root = root, super.internal(root);
Expand Down
38 changes: 28 additions & 10 deletions lib/pages/home/browse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:saber/components/home/move_note_button.dart';
import 'package:saber/components/home/new_note_button.dart';
import 'package:saber/components/home/no_files.dart';
import 'package:saber/components/home/rename_note_button.dart';
import 'package:saber/components/home/sort_button.dart';
import 'package:saber/components/home/syncing_button.dart';
import 'package:saber/data/file_manager/file_manager.dart';
import 'package:saber/data/routes.dart';
Expand All @@ -30,6 +31,8 @@ class BrowsePage extends StatefulWidget {

class _BrowsePageState extends State<BrowsePage> {
DirectoryChildren? children;
final List<String> files = [];
final List<String> folders = [];

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

findChildrenOfPath();

fileWriteSubscription =
FileManager.fileWriteStream.stream.listen(fileWriteListener);
selectedFiles.addListener(_setState);
Expand Down Expand Up @@ -73,6 +77,16 @@ class _BrowsePageState extends State<BrowsePage> {
}

children = await FileManager.getChildrenOfDirectory(path ?? '/');
files.clear();
for (String filePath in children?.files ?? const []) {
files.add("${path ?? ""}/$filePath");
}
folders.clear();
for (String directoryPath in children?.directories ?? const []) {
folders.add("${path ?? ""}/$directoryPath");
}
SortNotes.sortNotes(files, forced: true);
SortNotes.sortNotes(folders, forced: true);

if (mounted) setState(() {});
}
Expand Down Expand Up @@ -135,8 +149,18 @@ class _BrowsePageState extends State<BrowsePage> {
titlePadding: EdgeInsetsDirectional.only(
start: cupertino ? 0 : 16, bottom: 16),
),
actions: const [
SyncingButton(),
actions: [
const SyncingButton(),
SortButton(
callback: () => {
if (SortNotes.isNeeded)
{
SortNotes.sortNotes(files, forced: true),
SortNotes.sortNotes(folders, forced: true),
setState(() {}),
}
},
),
],
),
),
Expand Down Expand Up @@ -164,10 +188,7 @@ class _BrowsePageState extends State<BrowsePage> {
await FileManager.deleteDirectory(folderPath);
findChildrenOfPath();
},
folders: [
for (String directoryPath in children?.directories ?? const [])
directoryPath,
],
folders: folders.map((e) => e.split('/').last).toList(),
),
if (children == null) ...[
// loading
Expand All @@ -185,10 +206,7 @@ class _BrowsePageState extends State<BrowsePage> {
),
sliver: MasonryFiles(
crossAxisCount: crossAxisCount,
files: [
for (String filePath in children?.files ?? const [])
"${path ?? ""}/$filePath",
],
files: files,
selectedFiles: selectedFiles,
),
),
Expand Down
Loading