mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-07-03 05:59:37 +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();
|
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';
|
import 'journal_listing.dart';
|
||||||
|
|
||||||
class FolderListingScreen extends StatelessWidget {
|
class FolderListingScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_FolderListingScreenState createState() => _FolderListingScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FolderListingScreenState extends State<FolderListingScreen> {
|
||||||
|
NotesFolder selectedFolder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final container = StateContainer.of(context);
|
final container = StateContainer.of(context);
|
||||||
@ -16,7 +23,7 @@ class FolderListingScreen extends StatelessWidget {
|
|||||||
|
|
||||||
var treeView = FolderTreeView(
|
var treeView = FolderTreeView(
|
||||||
rootFolder: appState.notesFolder,
|
rootFolder: appState.notesFolder,
|
||||||
onFolderSelected: (NotesFolder folder) {
|
onFolderEntered: (NotesFolder folder) {
|
||||||
var route = MaterialPageRoute(
|
var route = MaterialPageRoute(
|
||||||
builder: (context) => JournalListingScreen(
|
builder: (context) => JournalListingScreen(
|
||||||
notesFolder: folder,
|
notesFolder: folder,
|
||||||
@ -24,12 +31,49 @@ class FolderListingScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
Navigator.of(context).push(route);
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Folders'),
|
title: const Text('Folders'),
|
||||||
leading: GJAppBarMenuButton(),
|
leading: GJAppBarMenuButton(),
|
||||||
|
actions: <Widget>[
|
||||||
|
if (selectedFolder != null) action,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: treeView,
|
body: treeView,
|
||||||
drawer: AppDrawer(),
|
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) {
|
void addNote(Note note) {
|
||||||
insertNote(0, note);
|
insertNote(0, note);
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,19 @@ class GitNoteRepository {
|
|||||||
return NoteRepoResult(noteFilePath: folder.folderPath, error: false);
|
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 {
|
Future<NoteRepoResult> removeNote(String noteFilePath) async {
|
||||||
var gitDir = p.join(baseDirectory, dirName);
|
var gitDir = p.join(baseDirectory, dirName);
|
||||||
var pathSpec = noteFilePath.replaceFirst(gitDir, "").substring(1);
|
var pathSpec = noteFilePath.replaceFirst(gitDir, "").substring(1);
|
||||||
|
@ -4,30 +4,71 @@ import 'package:gitjournal/core/notes_folder.dart';
|
|||||||
|
|
||||||
typedef void FolderSelectedCallback(NotesFolder folder);
|
typedef void FolderSelectedCallback(NotesFolder folder);
|
||||||
|
|
||||||
class FolderTreeView extends StatelessWidget {
|
class FolderTreeView extends StatefulWidget {
|
||||||
final NotesFolder rootFolder;
|
final NotesFolder rootFolder;
|
||||||
|
|
||||||
final FolderSelectedCallback onFolderSelected;
|
final FolderSelectedCallback onFolderSelected;
|
||||||
|
final Function onFolderUnselected;
|
||||||
|
final FolderSelectedCallback onFolderEntered;
|
||||||
|
|
||||||
FolderTreeView({
|
FolderTreeView({
|
||||||
@required this.rootFolder,
|
@required this.rootFolder,
|
||||||
@required this.onFolderSelected,
|
@required this.onFolderSelected,
|
||||||
|
@required this.onFolderUnselected,
|
||||||
|
@required this.onFolderEntered,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FolderTreeViewState createState() => _FolderTreeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FolderTreeViewState extends State<FolderTreeView> {
|
||||||
|
bool inSelectionMode = false;
|
||||||
|
NotesFolder selectedFolder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[tile],
|
||||||
FolderTile(rootFolder, onFolderSelected),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FolderTile extends StatefulWidget {
|
class FolderTile extends StatefulWidget {
|
||||||
final NotesFolder folder;
|
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
|
@override
|
||||||
FolderTileState createState() => FolderTileState();
|
FolderTileState createState() => FolderTileState();
|
||||||
@ -49,7 +90,8 @@ class FolderTileState extends State<FolderTile> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: _buildFolderTile(),
|
child: _buildFolderTile(),
|
||||||
onTap: () => widget.onFolderSelected(widget.folder),
|
onTap: () => widget.onTap(widget.folder),
|
||||||
|
onLongPress: () => widget.onLongPress(widget.folder),
|
||||||
),
|
),
|
||||||
_getChild(),
|
_getChild(),
|
||||||
],
|
],
|
||||||
@ -72,6 +114,9 @@ class FolderTileState extends State<FolderTile> {
|
|||||||
}
|
}
|
||||||
var subtitle = folder.numberOfNotes.toString() + " Notes";
|
var subtitle = folder.numberOfNotes.toString() + " Notes";
|
||||||
|
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
var selected = widget.selectedFolder == widget.folder;
|
||||||
return Card(
|
return Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Container(
|
leading: Container(
|
||||||
@ -87,7 +132,9 @@ class FolderTileState extends State<FolderTile> {
|
|||||||
title: Text(folderName),
|
title: Text(folderName),
|
||||||
subtitle: Text(subtitle),
|
subtitle: Text(subtitle),
|
||||||
trailing: trailling,
|
trailing: trailling,
|
||||||
|
selected: selected,
|
||||||
),
|
),
|
||||||
|
color: selected ? theme.selectedRowColor : theme.cardColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +149,12 @@ class FolderTileState extends State<FolderTile> {
|
|||||||
|
|
||||||
var children = <FolderTile>[];
|
var children = <FolderTile>[];
|
||||||
widget.folder.getFolders().forEach((folder) {
|
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(
|
return Container(
|
||||||
|
Reference in New Issue
Block a user