Allow folders to be renamed

This is a huge change which involves -
* Implementing Folder renames on the FS + Git level
* Implementing the concept of selecting an item in the FoldersListView,
  this feature isn't provided by default, and even now I'm not sure if
  the semantics which I've implemented are correct. I haven't
  implemented multiple folder selection for now.

Related to issue #18

Also, we're now one step closer to allowing notes to be renamed (#23)
This commit is contained in:
Vishesh Handa
2019-12-06 21:28:44 +01:00
parent 073e76bcba
commit 8c6a33456a
5 changed files with 206 additions and 10 deletions

View File

@ -144,4 +144,12 @@ class NotesFolder with ChangeNotifier {
notifyListeners();
}
void rename(String newName) {
var dir = Directory(folderPath);
var parentDirName = dirname(folderPath);
var newFullPath = p.join(parentDirName, newName);
dir.renameSync(newFullPath);
}
}

View File

@ -8,7 +8,14 @@ import 'package:gitjournal/core/notes_folder.dart';
import 'journal_listing.dart';
class FolderListingScreen extends StatelessWidget {
class FolderListingScreen extends StatefulWidget {
@override
_FolderListingScreenState createState() => _FolderListingScreenState();
}
class _FolderListingScreenState extends State<FolderListingScreen> {
NotesFolder selectedFolder;
@override
Widget build(BuildContext context) {
final container = StateContainer.of(context);
@ -16,7 +23,7 @@ class FolderListingScreen extends StatelessWidget {
var treeView = FolderTreeView(
rootFolder: appState.notesFolder,
onFolderSelected: (NotesFolder folder) {
onFolderEntered: (NotesFolder folder) {
var route = MaterialPageRoute(
builder: (context) => JournalListingScreen(
notesFolder: folder,
@ -24,12 +31,49 @@ class FolderListingScreen extends StatelessWidget {
);
Navigator.of(context).push(route);
},
onFolderSelected: (folder) {
setState(() {
selectedFolder = folder;
});
},
onFolderUnselected: () {
setState(() {
selectedFolder = null;
});
},
);
Widget action;
if (selectedFolder != null) {
action = PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem<String>(
child: const Text("Rename Folder"),
value: "Rename Folder",
)
];
},
onSelected: (String _) async {
var folderName = await showDialog(
context: context,
builder: (_) => RenameFolderDialog(selectedFolder),
);
if (folderName is String) {
final container = StateContainer.of(context);
container.renameFolder(selectedFolder, folderName);
}
},
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Folders'),
leading: GJAppBarMenuButton(),
actions: <Widget>[
if (selectedFolder != null) action,
],
),
body: treeView,
drawer: AppDrawer(),
@ -109,3 +153,67 @@ class _CreateFolderButtonState extends State<CreateFolderButton> {
);
}
}
class RenameFolderDialog extends StatefulWidget {
final NotesFolder folder;
RenameFolderDialog(this.folder);
@override
_RenameFolderDialogState createState() => _RenameFolderDialogState();
}
class _RenameFolderDialogState extends State<RenameFolderDialog> {
TextEditingController _textController;
@override
void initState() {
super.initState();
_textController = TextEditingController(text: widget.folder.name);
}
@override
Widget build(BuildContext context) {
var form = Form(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: 'Folder Name'),
validator: (value) {
if (value.isEmpty) return 'Please enter a name';
return "";
},
autofocus: true,
keyboardType: TextInputType.text,
controller: _textController,
),
],
),
);
return AlertDialog(
title: const Text("Rename Folder"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
FlatButton(
onPressed: () {
var newFolderName = _textController.text;
return Navigator.of(context).pop(newFolderName);
},
child: const Text("Rename"),
),
],
content: form,
);
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
}

View File

@ -160,6 +160,21 @@ class StateContainerState extends State<StateContainer> {
});
}
void renameFolder(NotesFolder folder, String newFolderName) async {
var oldFolderPath = folder.folderPath;
setState(() {
folder.rename(newFolderName);
// Update the git repo
noteRepo
.renameFolder(oldFolderPath, folder.folderPath)
.then((NoteRepoResult _) {
_syncNotes();
});
});
}
void addNote(Note note) {
insertNote(0, note);
}

View File

@ -60,6 +60,19 @@ class GitNoteRepository {
return NoteRepoResult(noteFilePath: folder.folderPath, error: false);
}
Future<NoteRepoResult> renameFolder(
String oldFullPath,
String newFullPath,
) async {
// FIXME: This is a hacky way of adding the changes, ideally we should be calling rm + add or something
await _gitRepo.add(".");
await _gitRepo.commit(
message: "Renamed folder",
);
return NoteRepoResult(noteFilePath: newFullPath, error: false);
}
Future<NoteRepoResult> removeNote(String noteFilePath) async {
var gitDir = p.join(baseDirectory, dirName);
var pathSpec = noteFilePath.replaceFirst(gitDir, "").substring(1);

View File

@ -4,30 +4,71 @@ import 'package:gitjournal/core/notes_folder.dart';
typedef void FolderSelectedCallback(NotesFolder folder);
class FolderTreeView extends StatelessWidget {
class FolderTreeView extends StatefulWidget {
final NotesFolder rootFolder;
final FolderSelectedCallback onFolderSelected;
final Function onFolderUnselected;
final FolderSelectedCallback onFolderEntered;
FolderTreeView({
@required this.rootFolder,
@required this.onFolderSelected,
@required this.onFolderUnselected,
@required this.onFolderEntered,
});
@override
_FolderTreeViewState createState() => _FolderTreeViewState();
}
class _FolderTreeViewState extends State<FolderTreeView> {
bool inSelectionMode = false;
NotesFolder selectedFolder;
@override
Widget build(BuildContext context) {
var tile = FolderTile(
folder: widget.rootFolder,
onTap: (NotesFolder folder) {
if (!inSelectionMode) {
widget.onFolderEntered(folder);
} else {
setState(() {
inSelectionMode = false;
selectedFolder = null;
});
widget.onFolderUnselected();
}
},
onLongPress: (folder) {
setState(() {
inSelectionMode = true;
selectedFolder = folder;
});
widget.onFolderSelected(folder);
},
selectedFolder: selectedFolder,
);
return ListView(
children: <Widget>[
FolderTile(rootFolder, onFolderSelected),
],
children: <Widget>[tile],
);
}
}
class FolderTile extends StatefulWidget {
final NotesFolder folder;
final FolderSelectedCallback onFolderSelected;
final FolderSelectedCallback onTap;
final FolderSelectedCallback onLongPress;
final NotesFolder selectedFolder;
FolderTile(this.folder, this.onFolderSelected);
FolderTile({
@required this.folder,
@required this.onTap,
@required this.onLongPress,
@required this.selectedFolder,
});
@override
FolderTileState createState() => FolderTileState();
@ -49,7 +90,8 @@ class FolderTileState extends State<FolderTile> {
children: <Widget>[
GestureDetector(
child: _buildFolderTile(),
onTap: () => widget.onFolderSelected(widget.folder),
onTap: () => widget.onTap(widget.folder),
onLongPress: () => widget.onLongPress(widget.folder),
),
_getChild(),
],
@ -72,6 +114,9 @@ class FolderTileState extends State<FolderTile> {
}
var subtitle = folder.numberOfNotes.toString() + " Notes";
final theme = Theme.of(context);
var selected = widget.selectedFolder == widget.folder;
return Card(
child: ListTile(
leading: Container(
@ -87,7 +132,9 @@ class FolderTileState extends State<FolderTile> {
title: Text(folderName),
subtitle: Text(subtitle),
trailing: trailling,
selected: selected,
),
color: selected ? theme.selectedRowColor : theme.cardColor,
);
}
@ -102,7 +149,12 @@ class FolderTileState extends State<FolderTile> {
var children = <FolderTile>[];
widget.folder.getFolders().forEach((folder) {
children.add(FolderTile(folder, widget.onFolderSelected));
children.add(FolderTile(
folder: folder,
onTap: widget.onTap,
onLongPress: widget.onLongPress,
selectedFolder: widget.selectedFolder,
));
});
return Container(