mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-30 19:36:25 +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
|
empty: Please enter a name
|
||||||
tags:
|
tags:
|
||||||
title: Tags
|
title: Tags
|
||||||
|
filesystem:
|
||||||
|
ignoredFile:
|
||||||
|
title: File Ignored
|
||||||
|
ok: Ok
|
||||||
|
rename: Rename
|
||||||
|
rename:
|
||||||
|
decoration: File Name
|
||||||
|
title: Rename
|
||||||
widgets:
|
widgets:
|
||||||
rename:
|
rename:
|
||||||
yes: Rename
|
yes: Rename
|
||||||
@ -102,3 +110,6 @@ widgets:
|
|||||||
other: "{} Notes link to this Note"
|
other: "{} Notes link to this Note"
|
||||||
|
|
||||||
rootFolder: Root Folder
|
rootFolder: Root Folder
|
||||||
|
ignoredFiles:
|
||||||
|
dot: Starts with a .
|
||||||
|
ext: Doesn't end with .md or .txt
|
||||||
|
@ -17,6 +17,7 @@ enum Event {
|
|||||||
NoteUndoDeleted,
|
NoteUndoDeleted,
|
||||||
NoteRenamed,
|
NoteRenamed,
|
||||||
NoteMoved,
|
NoteMoved,
|
||||||
|
FileRenamed,
|
||||||
FolderAdded,
|
FolderAdded,
|
||||||
FolderDeleted,
|
FolderDeleted,
|
||||||
FolderRenamed,
|
FolderRenamed,
|
||||||
@ -39,6 +40,9 @@ String _eventToString(Event e) {
|
|||||||
case Event.NoteMoved:
|
case Event.NoteMoved:
|
||||||
return "note_moved";
|
return "note_moved";
|
||||||
|
|
||||||
|
case Event.FileRenamed:
|
||||||
|
return "file_renamed";
|
||||||
|
|
||||||
case Event.FolderAdded:
|
case Event.FolderAdded:
|
||||||
return "folder_added";
|
return "folder_added";
|
||||||
case Event.FolderDeleted:
|
case Event.FolderDeleted:
|
||||||
|
@ -5,6 +5,7 @@ import 'package:device_info/device_info.dart';
|
|||||||
import 'package:firebase_analytics/observer.dart';
|
import 'package:firebase_analytics/observer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gitjournal/analytics.dart';
|
import 'package:gitjournal/analytics.dart';
|
||||||
|
import 'package:gitjournal/screens/filesystem_screen.dart';
|
||||||
import 'package:gitjournal/screens/folder_listing.dart';
|
import 'package:gitjournal/screens/folder_listing.dart';
|
||||||
import 'package:gitjournal/screens/tag_listing.dart';
|
import 'package:gitjournal/screens/tag_listing.dart';
|
||||||
import 'package:gitjournal/screens/note_editor.dart';
|
import 'package:gitjournal/screens/note_editor.dart';
|
||||||
@ -306,7 +307,7 @@ class _JournalAppState extends State<JournalApp> {
|
|||||||
//debugShowMaterialGrid: true,
|
//debugShowMaterialGrid: true,
|
||||||
onGenerateRoute: (settings) {
|
onGenerateRoute: (settings) {
|
||||||
var route = settings.name;
|
var route = settings.name;
|
||||||
if (route == '/folders' || route == '/tags') {
|
if (route == '/folders' || route == '/tags' || route == '/filesystem') {
|
||||||
return PageRouteBuilder(
|
return PageRouteBuilder(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (_, __, ___) => _screenForRoute(route, stateContainer),
|
pageBuilder: (_, __, ___) => _screenForRoute(route, stateContainer),
|
||||||
@ -333,6 +334,8 @@ class _JournalAppState extends State<JournalApp> {
|
|||||||
return HomeScreen();
|
return HomeScreen();
|
||||||
case '/folders':
|
case '/folders':
|
||||||
return FolderListingScreen();
|
return FolderListingScreen();
|
||||||
|
case '/filesystem':
|
||||||
|
return FileSystemScreen();
|
||||||
case '/tags':
|
case '/tags':
|
||||||
return TagListingScreen();
|
return TagListingScreen();
|
||||||
case '/settings':
|
case '/settings':
|
||||||
|
@ -102,6 +102,21 @@ class GitNoteRepository {
|
|||||||
return NoteRepoResult(noteFilePath: newFullPath, error: false);
|
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(
|
Future<NoteRepoResult> moveNote(
|
||||||
String oldFullPath,
|
String oldFullPath,
|
||||||
String newFullPath,
|
String newFullPath,
|
||||||
|
@ -7,11 +7,23 @@ import 'package:gitjournal/utils/logger.dart';
|
|||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'note.dart';
|
import 'note.dart';
|
||||||
import 'notes_folder.dart';
|
import 'notes_folder.dart';
|
||||||
import 'notes_folder_notifier.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 {
|
class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
||||||
final NotesFolderFS _parent;
|
final NotesFolderFS _parent;
|
||||||
String _folderPath;
|
String _folderPath;
|
||||||
@ -19,6 +31,7 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
|||||||
|
|
||||||
List<Note> _notes = [];
|
List<Note> _notes = [];
|
||||||
List<NotesFolderFS> _folders = [];
|
List<NotesFolderFS> _folders = [];
|
||||||
|
List<IgnoredFile> _ignoredFiles = [];
|
||||||
|
|
||||||
Map<String, dynamic> _entityMap = {};
|
Map<String, dynamic> _entityMap = {};
|
||||||
NotesFolderConfig _config;
|
NotesFolderConfig _config;
|
||||||
@ -119,6 +132,8 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
|||||||
@override
|
@override
|
||||||
List<NotesFolder> get subFolders => subFoldersFS;
|
List<NotesFolder> get subFolders => subFoldersFS;
|
||||||
|
|
||||||
|
List<IgnoredFile> get ignoredFiles => _ignoredFiles;
|
||||||
|
|
||||||
List<NotesFolderFS> get subFoldersFS {
|
List<NotesFolderFS> get subFoldersFS {
|
||||||
// FIXME: This is really not ideal
|
// FIXME: This is really not ideal
|
||||||
_folders.sort((NotesFolderFS a, NotesFolderFS b) =>
|
_folders.sort((NotesFolderFS a, NotesFolderFS b) =>
|
||||||
@ -172,6 +187,8 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
|||||||
_config = await NotesFolderConfig.fromFS(this);
|
_config = await NotesFolderConfig.fromFS(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ignoredFiles = <IgnoredFile>[];
|
||||||
|
|
||||||
final dir = Directory(folderPath);
|
final dir = Directory(folderPath);
|
||||||
var lister = dir.list(recursive: false, followLinks: false);
|
var lister = dir.list(recursive: false, followLinks: false);
|
||||||
await for (var fsEntity in lister) {
|
await for (var fsEntity in lister) {
|
||||||
@ -208,9 +225,15 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
|||||||
|
|
||||||
var note = Note(this, fsEntity.path);
|
var note = Note(this, fsEntity.path);
|
||||||
if (note.fileName.startsWith('.')) {
|
if (note.fileName.startsWith('.')) {
|
||||||
|
var ignoredFile = IgnoredFile(
|
||||||
|
filePath: fsEntity.path,
|
||||||
|
reason: tr("ignoredFiles.dot"),
|
||||||
|
);
|
||||||
|
_ignoredFiles.add(ignoredFile);
|
||||||
|
|
||||||
Log.v("Ignoring file", props: {
|
Log.v("Ignoring file", props: {
|
||||||
"path": fsEntity.path,
|
"path": ignoredFile.filePath,
|
||||||
"reason": "Starts with a .",
|
"reason": ignoredFile.reason,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -218,9 +241,15 @@ class NotesFolderFS with NotesFolderNotifier implements NotesFolder {
|
|||||||
var isMarkdownFile = noteFilePath.endsWith('.md');
|
var isMarkdownFile = noteFilePath.endsWith('.md');
|
||||||
var isTxtFile = noteFilePath.endsWith('.txt');
|
var isTxtFile = noteFilePath.endsWith('.txt');
|
||||||
if (!isMarkdownFile && !isTxtFile) {
|
if (!isMarkdownFile && !isTxtFile) {
|
||||||
|
var ignoredFile = IgnoredFile(
|
||||||
|
filePath: fsEntity.path,
|
||||||
|
reason: tr("ignoredFiles.ext"),
|
||||||
|
);
|
||||||
|
_ignoredFiles.add(ignoredFile);
|
||||||
|
|
||||||
Log.v("Ignoring file", props: {
|
Log.v("Ignoring file", props: {
|
||||||
"path": fsEntity.path,
|
"path": ignoredFile.filePath,
|
||||||
"reason": "Doesn't end with .md or .txt",
|
"reason": ignoredFile.reason,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
class Features {
|
class Features {
|
||||||
static bool perFolderConfig = false;
|
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 {
|
void moveNote(Note note, NotesFolderFS destFolder) async {
|
||||||
if (destFolder.folderPath == note.parent.folderPath) {
|
if (destFolder.folderPath == note.parent.folderPath) {
|
||||||
return;
|
return;
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:gitjournal/features.dart';
|
||||||
import 'package:gitjournal/settings.dart';
|
import 'package:gitjournal/settings.dart';
|
||||||
import 'package:gitjournal/utils/logger.dart';
|
import 'package:gitjournal/utils/logger.dart';
|
||||||
import 'package:launch_review/launch_review.dart';
|
import 'package:launch_review/launch_review.dart';
|
||||||
@ -93,6 +94,15 @@ class AppDrawer extends StatelessWidget {
|
|||||||
onTap: () => _navTopLevel(context, '/folders'),
|
onTap: () => _navTopLevel(context, '/folders'),
|
||||||
selected: currentRoute == "/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(
|
_buildDrawerTile(
|
||||||
context,
|
context,
|
||||||
icon: FontAwesomeIcons.tag,
|
icon: FontAwesomeIcons.tag,
|
||||||
|
Reference in New Issue
Block a user