diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ba0b42b..9da57ca 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + + + + diff --git a/lib/note_list.dart b/lib/note_list.dart index ca334e0..4a461d8 100644 --- a/lib/note_list.dart +++ b/lib/note_list.dart @@ -2,7 +2,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:file_picker/file_picker.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:open_filex/open_filex.dart'; import 'package:path/path.dart' as path; +import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'note_editor.dart'; @@ -30,6 +32,7 @@ class _NoteListScreenState extends State { List filteredNotes = []; String? notesDirectoryPath; String? defaultDir; + bool _useExternalEditor = false; @override void initState() { @@ -45,12 +48,33 @@ class _NoteListScreenState extends State { }); } + Future _requestStoragePermission() async { + if (Platform.isAndroid) { + if (await Permission.storage.request().isGranted) { + print("✅ Basic storage permission granted."); + return; + } + + if (await Permission.manageExternalStorage.request().isGranted) { + print("✅ Full file access granted."); + return; + } + + print("❌ Storage permission denied. Opening settings..."); + await openAppSettings(); // Open settings if denied + } + } + Future _initializeApp() async { + final prefs = await SharedPreferences.getInstance(); final dir = await getApplicationDocumentsDirectory(); + setState(() { - defaultDir = dir.path; // Update state with defaultDir + defaultDir = dir.path; + _useExternalEditor = prefs.getBool('use_external_editor') ?? false; }); - await _loadNotes(); // Load notes after setting defaultDir + + await _loadNotes(); } void _showDirectoryChoiceDialog(String defaultPath) { @@ -79,6 +103,7 @@ class _NoteListScreenState extends State { ), TextButton( onPressed: () async { + await _requestStoragePermission(); Navigator.pop(context); // Close the current dialog await _selectNotesDirectory(); // Allow user to pick a folder }, @@ -194,6 +219,25 @@ class _NoteListScreenState extends State { .trim(); } + Future _openExternalEditor(File noteFile) async { + if (!noteFile.existsSync()) { + print("❌ File does not exist: ${noteFile.path}"); + return; + } + + try { + if (Platform.isAndroid) { + await _requestStoragePermission(); + } + + final result = await OpenFilex.open(noteFile.path); + print("✅ OpenFilex result: ${result.type}, message: ${result.message}"); + } catch (e) { + print("❌ Failed to open external editor: $e"); + } + } + + void _deleteNote(File note) { final trashDir = Directory('${note.parent.path}/trash'); if (!trashDir.existsSync()) { @@ -326,6 +370,14 @@ class _NoteListScreenState extends State { widget.toggleTheme(!widget.isDarkMode); }); break; + case 'External Editor': + setState(() { + _useExternalEditor = !_useExternalEditor; + }); + SharedPreferences.getInstance().then((prefs) { + prefs.setBool('use_external_editor', _useExternalEditor); + }); + break; case 'Select Folder': _showDirectoryChoiceDialog( notesDirectoryPath ?? ''); // Re-trigger popup @@ -359,6 +411,21 @@ class _NoteListScreenState extends State { ), ), ), + PopupMenuItem( + value: 'External Editor', + child: ListTile( + leading: Icon( + _useExternalEditor + ? Icons.open_in_new_off + : Icons.open_in_new, + ), + title: Text( + !_useExternalEditor + ? 'Use External Editor' + : 'Use Internal Editor', + ), + ), + ), PopupMenuItem( value: 'Select Folder', child: ListTile( @@ -410,19 +477,42 @@ class _NoteListScreenState extends State { children: tagSpans)) : null, onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => NoteEditorScreen( - note: File(path.join(notesDirectoryPath!, - noteFile.uri.pathSegments.last)), - isDarkMode: widget.isDarkMode, - notesDirectoryPath: notesDirectoryPath, + if (_useExternalEditor) { + // Open with system's default editor + try { + if (Platform.isLinux) { + final result = await Process.run('which', ['xdg-open']); + if (result.stdout.toString().trim().isNotEmpty) { + await Process.run('xdg-open', [noteFile.path]); + } else { + print("❌ `xdg-open` is not available."); + } + } else if (Platform.isAndroid) { + await _requestStoragePermission(); + final result = await OpenFilex.open( + noteFile.path, + type: "text/markdown", + ); + print("✅ OpenFilex result: ${result.type}, message: ${result.message}"); + } + } catch (e) { + print("❌ Failed to open external editor: $e"); + } + } else { + // Open internally + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NoteEditorScreen( + note: File(path.join(notesDirectoryPath!, noteFile.uri.pathSegments.last)), + isDarkMode: widget.isDarkMode, + notesDirectoryPath: notesDirectoryPath, + ), ), - ), - ); - _loadNotes(); // Reload notes after returning - setState(() {}); // Force UI refresh + ); + _loadNotes(); // Reload notes after returning + setState(() {}); // Force UI refresh + } }, ), ); diff --git a/pubspec.lock b/pubspec.lock index e599e15..c89ff32 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -336,6 +336,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + open_filex: + dependency: "direct main" + description: + name: open_filex + sha256: dcb7bd3d32db8db5260253a62f1564c02c2c8df64bc0187cd213f65f827519bd + url: "https://pub.dev" + source: hosted + version: "4.6.0" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3e5f11c..a8d1a00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: permission_handler: ^11.4.0 shared_preferences: ^2.5.2 highlight: ^0.7.0 + open_filex: ^4.6.0 dev_dependencies: flutter_test: