mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-29 10:17:16 +08:00
Implement a very basic file system view
This will show all the files and not just the "notes". This way the user can easily figure out why a file isn't included in the list of notes. It's currently disabled by default as it clearly needs a lot more work.
This commit is contained in:
@ -84,6 +84,14 @@ screens:
|
||||
empty: Please enter a name
|
||||
tags:
|
||||
title: Tags
|
||||
filesystem:
|
||||
ignoredFile:
|
||||
title: File Ignored
|
||||
ok: Ok
|
||||
rename: Rename
|
||||
rename:
|
||||
decoration: File Name
|
||||
title: Rename
|
||||
widgets:
|
||||
rename:
|
||||
yes: Rename
|
||||
@ -102,3 +110,6 @@ widgets:
|
||||
other: "{} Notes link to this Note"
|
||||
|
||||
rootFolder: Root Folder
|
||||
ignoredFiles:
|
||||
dot: Starts with a .
|
||||
ext: Doesn't end with .md or .txt
|
||||
|
@ -17,6 +17,7 @@ enum Event {
|
||||
NoteUndoDeleted,
|
||||
NoteRenamed,
|
||||
NoteMoved,
|
||||
FileRenamed,
|
||||
FolderAdded,
|
||||
FolderDeleted,
|
||||
FolderRenamed,
|
||||
@ -39,6 +40,9 @@ String _eventToString(Event e) {
|
||||
case Event.NoteMoved:
|
||||
return "note_moved";
|
||||
|
||||
case Event.FileRenamed:
|
||||
return "file_renamed";
|
||||
|
||||
case Event.FolderAdded:
|
||||
return "folder_added";
|
||||
case Event.FolderDeleted:
|
||||
|
@ -5,6 +5,7 @@ import 'package:device_info/device_info.dart';
|
||||
import 'package:firebase_analytics/observer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gitjournal/analytics.dart';
|
||||
import 'package:gitjournal/screens/filesystem_screen.dart';
|
||||
import 'package:gitjournal/screens/folder_listing.dart';
|
||||
import 'package:gitjournal/screens/tag_listing.dart';
|
||||
import 'package:gitjournal/screens/note_editor.dart';
|
||||
@ -306,7 +307,7 @@ class _JournalAppState extends State<JournalApp> {
|
||||
//debugShowMaterialGrid: true,
|
||||
onGenerateRoute: (settings) {
|
||||
var route = settings.name;
|
||||
if (route == '/folders' || route == '/tags') {
|
||||
if (route == '/folders' || route == '/tags' || route == '/filesystem') {
|
||||
return PageRouteBuilder(
|
||||
settings: settings,
|
||||
pageBuilder: (_, __, ___) => _screenForRoute(route, stateContainer),
|
||||
@ -333,6 +334,8 @@ class _JournalAppState extends State<JournalApp> {
|
||||
return HomeScreen();
|
||||
case '/folders':
|
||||
return FolderListingScreen();
|
||||
case '/filesystem':
|
||||
return FileSystemScreen();
|
||||
case '/tags':
|
||||
return TagListingScreen();
|
||||
case '/settings':
|
||||
|
@ -102,6 +102,21 @@ class GitNoteRepository {
|
||||
return NoteRepoResult(noteFilePath: newFullPath, error: false);
|
||||
}
|
||||
|
||||
Future<NoteRepoResult> renameFile(
|
||||
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 File",
|
||||
authorEmail: Settings.instance.gitAuthorEmail,
|
||||
authorName: Settings.instance.gitAuthor,
|
||||
);
|
||||
|
||||
return NoteRepoResult(noteFilePath: newFullPath, error: false);
|
||||
}
|
||||
|
||||
Future<NoteRepoResult> moveNote(
|
||||
String oldFullPath,
|
||||
String newFullPath,
|
||||
|
@ -7,11 +7,23 @@ import 'package:gitjournal/utils/logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path/path.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'note.dart';
|
||||
import 'notes_folder.dart';
|
||||
import 'notes_folder_notifier.dart';
|
||||
|
||||
class IgnoredFile {
|
||||
String filePath;
|
||||
String reason;
|
||||
|
||||
IgnoredFile({@required this.filePath, @required this.reason});
|
||||
|
||||
String get fileName {
|
||||
return p.basename(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||
final NotesFolderFS _parent;
|
||||
String _folderPath;
|
||||
@ -19,6 +31,7 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||
|
||||
List<Note> _notes = [];
|
||||
List<NotesFolderFS> _folders = [];
|
||||
List<IgnoredFile> _ignoredFiles = [];
|
||||
|
||||
Map<String, dynamic> _entityMap = {};
|
||||
NotesFolderConfig _config;
|
||||
@ -119,6 +132,8 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||
@override
|
||||
List<NotesFolder> get subFolders => subFoldersFS;
|
||||
|
||||
List<IgnoredFile> get ignoredFiles => _ignoredFiles;
|
||||
|
||||
List<NotesFolderFS> get subFoldersFS {
|
||||
// FIXME: This is really not ideal
|
||||
_folders.sort((NotesFolderFS a, NotesFolderFS b) =>
|
||||
@ -172,6 +187,8 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||
_config = await NotesFolderConfig.fromFS(this);
|
||||
}
|
||||
|
||||
_ignoredFiles = <IgnoredFile>[];
|
||||
|
||||
final dir = Directory(folderPath);
|
||||
var lister = dir.list(recursive: false, followLinks: false);
|
||||
await for (var fsEntity in lister) {
|
||||
@ -208,9 +225,15 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||
|
||||
var note = Note(this, fsEntity.path);
|
||||
if (note.fileName.startsWith('.')) {
|
||||
var ignoredFile = IgnoredFile(
|
||||
filePath: fsEntity.path,
|
||||
reason: tr("ignoredFiles.dot"),
|
||||
);
|
||||
_ignoredFiles.add(ignoredFile);
|
||||
|
||||
Log.v("Ignoring file", props: {
|
||||
"path": fsEntity.path,
|
||||
"reason": "Starts with a .",
|
||||
"path": ignoredFile.filePath,
|
||||
"reason": ignoredFile.reason,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@ -218,9 +241,15 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||
var isMarkdownFile = noteFilePath.endsWith('.md');
|
||||
var isTxtFile = noteFilePath.endsWith('.txt');
|
||||
if (!isMarkdownFile && !isTxtFile) {
|
||||
var ignoredFile = IgnoredFile(
|
||||
filePath: fsEntity.path,
|
||||
reason: tr("ignoredFiles.ext"),
|
||||
);
|
||||
_ignoredFiles.add(ignoredFile);
|
||||
|
||||
Log.v("Ignoring file", props: {
|
||||
"path": fsEntity.path,
|
||||
"reason": "Doesn't end with .md or .txt",
|
||||
"path": ignoredFile.filePath,
|
||||
"reason": ignoredFile.reason,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
class Features {
|
||||
static bool perFolderConfig = false;
|
||||
static bool showFileSystem = false;
|
||||
}
|
||||
|
137
lib/screens/filesystem_screen.dart
Normal file
137
lib/screens/filesystem_screen.dart
Normal file
@ -0,0 +1,137 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:function_types/function_types.dart';
|
||||
import 'package:gitjournal/state_container.dart';
|
||||
import 'package:gitjournal/widgets/rename_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:gitjournal/core/note.dart';
|
||||
import 'package:gitjournal/core/notes_folder_fs.dart';
|
||||
import 'package:gitjournal/widgets/app_drawer.dart';
|
||||
|
||||
class FileSystemScreen extends StatefulWidget {
|
||||
@override
|
||||
_FileSystemScreenState createState() => _FileSystemScreenState();
|
||||
}
|
||||
|
||||
class _FileSystemScreenState extends State<FileSystemScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final rootFolder = Provider.of<NotesFolderFS>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(rootFolder.publicName),
|
||||
),
|
||||
body: Scrollbar(
|
||||
child: FileSystemView(
|
||||
rootFolder,
|
||||
onFolderSelected: _onFolderSelected,
|
||||
onNoteSelected: _onNoteSelected,
|
||||
onIgnoredFileSelected: _onIgnoredFileSelected,
|
||||
),
|
||||
),
|
||||
drawer: AppDrawer(),
|
||||
);
|
||||
}
|
||||
|
||||
void _onFolderSelected(NotesFolderFS folder) {}
|
||||
void _onNoteSelected(Note note) {}
|
||||
|
||||
void _onIgnoredFileSelected(IgnoredFile ignoredFile) async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(tr("screens.filesystem.ignoredFile.title")),
|
||||
content: Text(ignoredFile.reason),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(false);
|
||||
_renameFile(ignoredFile.filePath);
|
||||
},
|
||||
child: Text(tr('screens.filesystem.ignoredFile.rename')),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(tr('screens.filesystem.ignoredFile.ok')),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _renameFile(String oldPath) async {
|
||||
var newFileName = await showDialog(
|
||||
context: context,
|
||||
builder: (_) => RenameDialog(
|
||||
oldPath: oldPath,
|
||||
inputDecoration: tr("screens.filesystem.rename.decoration"),
|
||||
dialogTitle: tr("screens.filesystem.rename.title"),
|
||||
),
|
||||
);
|
||||
if (newFileName is String) {
|
||||
var container = Provider.of<StateContainer>(context, listen: false);
|
||||
container.renameFile(oldPath, newFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FileSystemView extends StatelessWidget {
|
||||
final NotesFolderFS folder;
|
||||
|
||||
final Func1<NotesFolderFS, void> onFolderSelected;
|
||||
final Func1<Note, void> onNoteSelected;
|
||||
final Func1<IgnoredFile, void> onIgnoredFileSelected;
|
||||
|
||||
FileSystemView(
|
||||
this.folder, {
|
||||
@required this.onFolderSelected,
|
||||
@required this.onNoteSelected,
|
||||
@required this.onIgnoredFileSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
children: <Widget>[
|
||||
for (var folder in folder.subFolders)
|
||||
_buildFolderTile(folder.fsFolder as NotesFolderFS),
|
||||
for (var note in folder.notes) _buildNoteTile(note),
|
||||
for (var ignoredFile in folder.ignoredFiles)
|
||||
_buildIgnoredFileTile(ignoredFile),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFolderTile(NotesFolderFS folder) {
|
||||
return ListTile(
|
||||
leading: Icon(Icons.folder),
|
||||
title: Text(folder.name),
|
||||
dense: true,
|
||||
onTap: () => onFolderSelected(folder),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNoteTile(Note note) {
|
||||
return ListTile(
|
||||
leading: Icon(Icons.note),
|
||||
title: Text(note.fileName),
|
||||
dense: true,
|
||||
onTap: () => onNoteSelected(note),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIgnoredFileTile(IgnoredFile ignoredFile) {
|
||||
// FIXME: Paint with Ignored colours
|
||||
return ListTile(
|
||||
leading: Icon(Icons.broken_image),
|
||||
title: Text(ignoredFile.fileName),
|
||||
dense: true,
|
||||
enabled: true,
|
||||
onTap: () => onIgnoredFileSelected(ignoredFile),
|
||||
);
|
||||
}
|
||||
}
|
@ -218,6 +218,24 @@ class StateContainer with ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
void renameFile(String oldPath, String newFileName) async {
|
||||
logEvent(Event.NoteRenamed);
|
||||
|
||||
return _opLock.synchronized(() async {
|
||||
Log.d("Got renameNote lock");
|
||||
|
||||
var newPath = p.join(p.dirname(oldPath), newFileName);
|
||||
await File(oldPath).rename(newPath);
|
||||
notifyListeners();
|
||||
|
||||
_gitRepo.renameFile(oldPath, newPath).then((NoteRepoResult _) {
|
||||
_syncNotes();
|
||||
appState.numChanges += 1;
|
||||
notifyListeners();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void moveNote(Note note, NotesFolderFS destFolder) async {
|
||||
if (destFolder.folderPath == note.parent.folderPath) {
|
||||
return;
|
||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:gitjournal/features.dart';
|
||||
import 'package:gitjournal/settings.dart';
|
||||
import 'package:gitjournal/utils/logger.dart';
|
||||
import 'package:launch_review/launch_review.dart';
|
||||
@ -93,6 +94,15 @@ class AppDrawer extends StatelessWidget {
|
||||
onTap: () => _navTopLevel(context, '/folders'),
|
||||
selected: currentRoute == "/folders",
|
||||
),
|
||||
if (Features.showFileSystem)
|
||||
_buildDrawerTile(
|
||||
context,
|
||||
icon: FontAwesomeIcons.solidFolderOpen,
|
||||
isFontAwesome: true,
|
||||
title: "File System",
|
||||
onTap: () => _navTopLevel(context, '/filesystem'),
|
||||
selected: currentRoute == "/filesystem",
|
||||
),
|
||||
_buildDrawerTile(
|
||||
context,
|
||||
icon: FontAwesomeIcons.tag,
|
||||
|
Reference in New Issue
Block a user