Skip to content

Commit

Permalink
feat: allow renaming folders
Browse files Browse the repository at this point in the history
Closes #428
  • Loading branch information
adil192 committed Aug 28, 2023
1 parent 4df9781 commit b1b8c45
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 4 deletions.
17 changes: 15 additions & 2 deletions lib/components/home/grid_folders.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flutter/cupertino.dart' show CupertinoDialogAction, CupertinoIcons;
import 'package:flutter/cupertino.dart' show CupertinoIcons;
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:saber/components/home/delete_folder_button.dart';
import 'package:saber/components/home/new_folder_dialog.dart';
import 'package:saber/components/theming/adaptive_alert_dialog.dart';
import 'package:saber/components/home/rename_folder_button.dart';
import 'package:saber/components/theming/adaptive_icon.dart';
import 'package:saber/data/extensions/list_extensions.dart';
import 'package:saber/i18n/strings.g.dart';
Expand All @@ -15,6 +15,7 @@ class GridFolders extends StatelessWidget {
required this.onTap,
required this.crossAxisCount,
required this.createFolder,
required this.renameFolder,
required this.isFolderEmpty,
required this.deleteFolder,
required this.doesFolderExist,
Expand All @@ -27,6 +28,7 @@ class GridFolders extends StatelessWidget {

final void Function(String) createFolder;
final bool Function(String) doesFolderExist;
final Future<void> Function(String oldName, String newName) renameFolder;
final Future<bool> Function(String) isFolderEmpty;
final Future<void> Function(String) deleteFolder;

Expand Down Expand Up @@ -54,6 +56,7 @@ class GridFolders extends StatelessWidget {
folderName: folderName,
createFolder: createFolder,
doesFolderExist: doesFolderExist,
renameFolder: renameFolder,
isFolderEmpty: isFolderEmpty,
deleteFolder: deleteFolder,
onTap: onTap,
Expand All @@ -71,6 +74,7 @@ class _GridFolder extends StatefulWidget {
required this.folderName,
required this.createFolder,
required this.doesFolderExist,
required this.renameFolder,
required this.isFolderEmpty,
required this.deleteFolder,
required this.onTap,
Expand All @@ -80,6 +84,7 @@ class _GridFolder extends StatefulWidget {
final String? folderName;
final void Function(String) createFolder;
final bool Function(String) doesFolderExist;
final Future<void> Function(String oldName, String newName) renameFolder;
final Future<bool> Function(String) isFolderEmpty;
final Future<void> Function(String) deleteFolder;
final Function(String) onTap;
Expand Down Expand Up @@ -188,6 +193,14 @@ class _GridFolderState extends State<_GridFolder> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
RenameFolderButton(
folderName: widget.folderName!,
doesFolderExist: widget.doesFolderExist,
renameFolder: (String folderName) async {
await widget.renameFolder(widget.folderName!, folderName);
expanded.value = false;
},
),
DeleteFolderButton(
folderName: widget.folderName!,
deleteFolder: (String folderName) async {
Expand Down
5 changes: 5 additions & 0 deletions lib/components/home/move_note_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ class _MoveNoteDialogState extends State<_MoveNoteDialog> {
doesFolderExist: (String folderName) {
return currentFolderChildren?.directories.contains(folderName) ?? false;
},
renameFolder: (String oldName, String newName) async {
final oldPath = '$currentFolder$oldName';
await FileManager.renameDirectory(oldPath, newName);
findChildrenOfCurrentFolder();
},
isFolderEmpty: (String folderName) async {
final folderPath = '$currentFolder$folderName';
final children = await FileManager.getChildrenOfDirectory(folderPath);
Expand Down
118 changes: 118 additions & 0 deletions lib/components/home/rename_folder_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:saber/components/theming/adaptive_alert_dialog.dart';
import 'package:saber/components/theming/adaptive_text_field.dart';
import 'package:saber/i18n/strings.g.dart';

class RenameFolderButton extends StatelessWidget {
const RenameFolderButton({
super.key,
required this.folderName,
required this.doesFolderExist,
required this.renameFolder,
});

final String folderName;
final bool Function(String) doesFolderExist;
final Future<void> Function(String newName) renameFolder;

@override
Widget build(BuildContext context) {
return IconButton(
padding: EdgeInsets.zero,
tooltip: t.home.renameFolder.renameFolder,
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return _RenameFolderDialog(
folderName: folderName,
doesFolderExist: doesFolderExist,
renameFolder: renameFolder,
);
},
);
},
icon: const Icon(Icons.edit_square),
);
}
}

class _RenameFolderDialog extends StatefulWidget {
const _RenameFolderDialog({
// ignore: unused_element
super.key,
required this.folderName,
required this.doesFolderExist,
required this.renameFolder,
});

final String folderName;
final bool Function(String) doesFolderExist;
final Future<void> Function(String newName) renameFolder;

@override
State<_RenameFolderDialog> createState() => _RenameFolderDialogState();
}
class _RenameFolderDialogState extends State<_RenameFolderDialog> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();

String? validateFolderName(String? folderName) {
if (folderName == null || folderName.isEmpty) {
return t.home.renameFolder.folderNameEmpty;
}
if (folderName.contains('/') || folderName.contains('\\')) {
return t.home.renameFolder.folderNameContainsSlash;
}
if (folderName != widget.folderName && widget.doesFolderExist(folderName)) {
return t.home.renameFolder.folderNameExists;
}
return null;
}

@override
void initState() {
super.initState();
_controller.text = widget.folderName;
}

@override
Widget build(BuildContext context) {
return AdaptiveAlertDialog(
title: Text(t.home.renameFolder.renameFolder),
content: Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: AdaptiveTextField(
controller: _controller,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
focusOrder: const NumericFocusOrder(1),
placeholder: t.home.renameFolder.folderName,
prefixIcon: const Icon(Icons.edit_square),
validator: validateFolderName,
),
),
actions: [
CupertinoDialogAction(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(t.editor.newerFileFormat.cancel),
),
CupertinoDialogAction(
onPressed: () async {
if (!_formKey.currentState!.validate()) return;
if (_controller.text != widget.folderName) {
await widget.renameFolder(_controller.text);
if (!mounted) return;
}
Navigator.of(context).pop();
},
child: Text(t.home.renameFolder.rename),
),
],
);
}
}
26 changes: 26 additions & 0 deletions lib/data/file_manager/file_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,32 @@ class FileManager {
broadcastFileWrite(FileOperationType.delete, filePath);
}

static Future renameDirectory(String directoryPath, String newName) async {
directoryPath = _sanitisePath(directoryPath);

final documentsDirectory = await FileManager.documentsDirectory;

final Directory directory = Directory(documentsDirectory + directoryPath);
if (!directory.existsSync()) return;

/// recursively find children of [directory] for [_renameReferences]
final List<String> children = [];
await for (final entity in directory.list(recursive: true)) {
if (entity is File) {
children.add(entity.path.substring(directory.path.length));
}
}

final String newPath = directoryPath.substring(0, directoryPath.lastIndexOf('/') + 1) + newName;
await directory.rename(documentsDirectory + newPath);

for (final child in children) {
_renameReferences(directoryPath + child, newPath + child);
broadcastFileWrite(FileOperationType.delete, directoryPath + child);
broadcastFileWrite(FileOperationType.write, newPath + child);
}
}

static Future deleteDirectory(String directoryPath, [bool recursive = true]) async {
directoryPath = _sanitisePath(directoryPath);

Expand Down
20 changes: 18 additions & 2 deletions lib/i18n/strings.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 15
/// Strings: 3495 (233 per locale)
/// Strings: 3501 (233 per locale)
///
/// Built on 2023-08-28 at 22:34 UTC
/// Built on 2023-08-28 at 22:43 UTC

// coverage:ignore-file
// ignore_for_file: type=lint
Expand Down Expand Up @@ -185,6 +185,7 @@ class _StringsHomeEn {
late final _StringsHomeRenameNoteEn renameNote = _StringsHomeRenameNoteEn._(_root);
late final _StringsHomeMoveNoteEn moveNote = _StringsHomeMoveNoteEn._(_root);
String get deleteNote => 'Delete note';
late final _StringsHomeRenameFolderEn renameFolder = _StringsHomeRenameFolderEn._(_root);
late final _StringsHomeDeleteFolderEn deleteFolder = _StringsHomeDeleteFolderEn._(_root);
}

Expand Down Expand Up @@ -394,6 +395,21 @@ class _StringsHomeMoveNoteEn {
String renamedTo({required Object newName}) => 'Note will be renamed to ${newName}';
}

// Path: home.renameFolder
class _StringsHomeRenameFolderEn {
_StringsHomeRenameFolderEn._(this._root);

final _StringsEn _root; // ignore: unused_field

// Translations
String get renameFolder => 'Rename folder';
String get folderName => 'Folder name';
String get rename => 'Rename';
String get folderNameEmpty => 'Folder name can\'t be empty';
String get folderNameContainsSlash => 'Folder name can\'t contain a slash';
String get folderNameExists => 'A folder with this name already exists';
}

// Path: home.deleteFolder
class _StringsHomeDeleteFolderEn {
_StringsHomeDeleteFolderEn._(this._root);
Expand Down
7 changes: 7 additions & 0 deletions lib/i18n/strings.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ home:
move: Move
renamedTo: Note will be renamed to $newName
deleteNote: Delete note
renameFolder:
renameFolder: Rename folder
folderName: Folder name
rename: Rename
folderNameEmpty: Folder name can't be empty
folderNameContainsSlash: Folder name can't contain a slash
folderNameExists: A folder with this name already exists
deleteFolder:
deleteFolder: Delete folder
deleteName: Delete $f
Expand Down
5 changes: 5 additions & 0 deletions lib/pages/home/browse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ class _BrowsePageState extends State<BrowsePage> {
doesFolderExist: (String folderName) {
return children?.directories.contains(folderName) ?? false;
},
renameFolder: (String oldName, String newName) async {
final oldPath = '${path ?? ''}/$oldName';
await FileManager.renameDirectory(oldPath, newName);
findChildrenOfPath();
},
isFolderEmpty: (String folderName) async {
final folderPath = '${path ?? ''}/$folderName';
final children = await FileManager.getChildrenOfDirectory(folderPath);
Expand Down

1 comment on commit b1b8c45

@adil192
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.