mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-07-02 04:47:01 +08:00
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:
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user