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:
Vishesh Handa
2020-06-10 00:47:54 +02:00
parent d6a288c085
commit 267ecb7499
9 changed files with 233 additions and 5 deletions

View File

@ -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

View File

@ -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:

View File

@ -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':

View File

@ -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,

View File

@ -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;
}

View File

@ -1,3 +1,4 @@
class Features {
static bool perFolderConfig = false;
static bool showFileSystem = false;
}

View 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),
);
}
}

View File

@ -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;

View File

@ -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,