use external editor, manage permissions

This commit is contained in:
randogoth 2025-02-22 14:29:01 +02:00
parent 05026b3fe2
commit 34580109bb
4 changed files with 117 additions and 14 deletions

View file

@ -3,6 +3,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<application
android:label="muzzlevelocity"
@ -57,5 +58,8 @@
<intent>
<action android:name="android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
</intent>
</queries>
</manifest>

View file

@ -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<NoteListScreen> {
List<File> filteredNotes = [];
String? notesDirectoryPath;
String? defaultDir;
bool _useExternalEditor = false;
@override
void initState() {
@ -45,12 +48,33 @@ class _NoteListScreenState extends State<NoteListScreen> {
});
}
Future<void> _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<void> _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<NoteListScreen> {
),
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<NoteListScreen> {
.trim();
}
Future<void> _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<NoteListScreen> {
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<NoteListScreen> {
),
),
),
PopupMenuItem<String>(
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<String>(
value: 'Select Folder',
child: ListTile(
@ -410,12 +477,34 @@ class _NoteListScreenState extends State<NoteListScreen> {
children: tagSpans))
: null,
onTap: () async {
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)),
note: File(path.join(notesDirectoryPath!, noteFile.uri.pathSegments.last)),
isDarkMode: widget.isDarkMode,
notesDirectoryPath: notesDirectoryPath,
),
@ -423,6 +512,7 @@ class _NoteListScreenState extends State<NoteListScreen> {
);
_loadNotes(); // Reload notes after returning
setState(() {}); // Force UI refresh
}
},
),
);

View file

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

View file

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