app icons

This commit is contained in:
randogoth 2025-02-22 19:11:10 +02:00
parent be43333da6
commit 4fcd6db6fd
30 changed files with 331 additions and 216 deletions

View file

@ -3,8 +3,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart';
import 'package:flutter_svg/svg.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path/path.dart' as path;
import 'package:shared_preferences/shared_preferences.dart';
import 'themes/day.dart';
import 'themes/night.dart';
import 'themes/markdown.dart';
@ -25,57 +26,102 @@ class NoteEditorScreen extends StatefulWidget {
}
class _NoteEditorScreenState extends State<NoteEditorScreen> {
bool isLoading = true;
bool _isPreviewMode = false; // Default: Editing mode
late CodeController _controller;
double _fontSize = 16.0; // Default font size
late String _initialContent;
bool _isPreviewMode = false;
bool _isLoading = true;
bool _autoSaveEnabled = false; // Controlled by user toggle
double _fontSize = 16.0;
@override
void initState() {
super.initState();
_loadPreferences();
_loadNoteContent();
}
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_autoSaveEnabled = prefs.getBool('auto_save_enabled') ?? false;
});
}
Future<void> _setAutoSaveEnabled(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('auto_save_enabled', value);
setState(() {
_autoSaveEnabled = value;
});
}
Future<void> _loadNoteContent() async {
String content = "";
try {
content = await widget.note.readAsString();
_initialContent = await widget.note.readAsString();
} catch (e) {
print("❌ Failed to read file: ${widget.note.path}");
_initialContent = "";
}
setState(() {
_controller = CodeController(
text: content,
text: _initialContent,
language: markdown,
);
_controller.addListener(_saveNote);
_controller.popupController.enabled = false;
isLoading = false;
_controller.addListener(_onTextChanged);
_isLoading = false;
});
}
void _onTextChanged() {
if (_autoSaveEnabled && _controller.text != _initialContent) {
_saveNote();
}
}
Future<void> _saveNote() async {
try {
final notesDirectory = widget.notesDirectoryPath ??
widget.note.parent.path; // Use correct directory
if (_controller.text == _initialContent) return; // No change, skip saving
// Get the full path of the original file, preserving subfolder structure
final relativeNotePath = path.relative(widget.note.path, from: notesDirectory);
final newFilePath = path.join(notesDirectory, relativeNotePath); // Preserve subfolder
await widget.note.writeAsString(_controller.text);
_initialContent = _controller.text; // Update initial state
print("📝 Attempting to save note at: $newFilePath");
final newFile = File(newFilePath);
await newFile.writeAsString(_controller.text);
print("✅ Successfully saved note at: $newFilePath");
} catch (e, stackTrace) {
print("✅ Note saved successfully");
} catch (e) {
print("❌ Error saving note: ${widget.note.path}");
print("⚠️ Exception: $e");
print("🛠 Stack trace: $stackTrace");
}
}
Future<bool> _onWillPop() async {
if (_controller.text == _initialContent) return true; // No changes, exit
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Save Changes?"),
content: Text("You have unsaved changes. Save before exiting?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(true), // Discard and exit
child: Text("Discard"),
),
TextButton(
onPressed: () async {
await _saveNote();
Navigator.of(context).pop(true); // Save and exit
},
child: Text("Save"),
),
TextButton(
onPressed: () => Navigator.of(context).pop(false), // Stay on page
child: Text("Cancel"),
),
],
),
) ??
false;
}
void _updateFontSize(double newSize) {
setState(() {
_fontSize = newSize;
@ -84,138 +130,153 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
@override
void dispose() {
_controller.removeListener(_saveNote);
_controller.removeListener(_onTextChanged);
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.note.uri.pathSegments.last.replaceAll('.md', '')),
actions: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: PopupMenuButton<String>(
icon: Icon(Icons.radio_button_checked, color: Colors.orange),
onSelected: (String value) {
if (value == 'Toggle Preview') {
setState(() {
_isPreviewMode = !_isPreviewMode;
});
}
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
title: Text(widget.note.uri.pathSegments.last.replaceAll('.md', '')),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () async {
if (await _onWillPop()) {
Navigator.pop(context);
}
},
),
actions: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: PopupMenuButton<String>(
icon: SvgPicture.asset(
"assets/blaster.svg",
width: 30,
color: Colors.orange,
),
onSelected: (String value) {
switch (value) {
case 'Save':
_saveNote();
break;
case 'Toggle Autosave':
_setAutoSaveEnabled(!_autoSaveEnabled);
break;
case 'Toggle Preview':
setState(() {
_isPreviewMode = !_isPreviewMode;
});
break;
}
},
itemBuilder: (BuildContext context) => [
PopupMenuItem<String>(
value: 'Save',
child: ListTile(
leading: Icon(Icons.save),
title: Text("Save"),
),
),
PopupMenuItem<String>(
value: 'Toggle Autosave',
child: ListTile(
leading: Icon(_autoSaveEnabled
? Icons.toggle_on
: Icons.toggle_off),
title: Text(_autoSaveEnabled
? "Disable Autosave"
: "Enable Autosave"),
),
),
PopupMenuItem<String>(
value: 'Toggle Preview',
child: ListTile(
leading:
Icon(_isPreviewMode ? Icons.edit : Icons.preview),
title:
Text(_isPreviewMode ? "Edit Mode" : "Preview Mode"),
),
),
],
),
),
],
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: _isPreviewMode
? Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: MarkdownBody(
builders: {
'latex': LatexElementBuilder(
textStyle: TextStyle(
color: widget.isDarkMode
? Colors.white
: Colors.black)),
},
itemBuilder: (BuildContext context) => [
PopupMenuItem<String>(
value: 'Toggle Preview',
child: ListTile(
leading: Icon(_isPreviewMode ? Icons.edit : Icons.preview),
title: Text(_isPreviewMode ? 'Edit Mode' : 'Preview Mode'),
),
),
PopupMenuItem<String>(
enabled: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Font Size'),
StatefulBuilder(
builder: (context, setState) {
return Slider(
value: _fontSize,
min: 12.0,
max: 24.0,
divisions: 6,
label: _fontSize.toString(),
onChanged: (newSize) {
setState(() {
_fontSize = newSize;
});
_updateFontSize(newSize);
},
);
},
),
],
),
),
],
extensionSet: md.ExtensionSet(
[LatexBlockSyntax()],
[LatexInlineSyntax()],
),
data: _controller.text,
selectable: true,
styleSheet: MarkdownStyleSheet(
p: TextStyle(
fontSize: _fontSize,
color: widget.isDarkMode
? Colors.white
: Colors.black),
h1: TextStyle(
fontSize: _fontSize + 8,
color: widget.isDarkMode
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold),
h2: TextStyle(
fontSize: _fontSize + 6,
color: widget.isDarkMode
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold),
h3: TextStyle(
fontSize: _fontSize + 4,
color: widget.isDarkMode
? Colors.white
: Colors.black,
fontWeight: FontWeight.bold),
code: TextStyle(
fontSize: _fontSize,
backgroundColor: Colors.transparent),
codeblockDecoration: BoxDecoration(
color: Colors.transparent),
),
),
),
],
),
body: isLoading
? Center(
child:
CircularProgressIndicator()) // Show loader while initializing
: _isPreviewMode
? Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: MarkdownBody(
builders: {
'latex': LatexElementBuilder(
textStyle: TextStyle(
color: widget.isDarkMode
? Colors.white
: Colors.black)),
},
extensionSet: md.ExtensionSet(
[LatexBlockSyntax()],
[LatexInlineSyntax()],
),
data: _controller
.text, // Plain Markdown without syntax highlighting
selectable: true, // Allows text selection
styleSheet: MarkdownStyleSheet(
p: TextStyle(
fontSize: _fontSize,
color: widget.isDarkMode
? Colors.white
: Colors.black),
h1: TextStyle(
fontSize: _fontSize + 8,
color:
widget.isDarkMode ? Colors.white : Colors.black,
fontWeight: FontWeight.bold),
h2: TextStyle(
fontSize: _fontSize + 6,
color:
widget.isDarkMode ? Colors.white : Colors.black,
fontWeight: FontWeight.bold),
h3: TextStyle(
fontSize: _fontSize + 4,
color:
widget.isDarkMode ? Colors.white : Colors.black,
fontWeight: FontWeight.bold),
code: TextStyle(
fontSize: _fontSize,
backgroundColor: Colors
.transparent), // Disables syntax highlighting
codeblockDecoration: BoxDecoration(
color: Colors
.transparent), // Removes background color from code blocks
),
),
),
)
: Padding(
padding: const EdgeInsets.all(16.0),
child: CodeTheme(
data: CodeThemeData(
styles: widget.isDarkMode ? nightTheme : dayTheme),
child: SingleChildScrollView(
child: WrappedCodeField(
wrap: true,
controller: _controller,
textStyle: TextStyle(
fontSize: _fontSize,
),
),
),
),
)
: Padding(
padding: const EdgeInsets.all(16.0),
child: CodeTheme(
data: CodeThemeData(
styles: widget.isDarkMode ? nightTheme : dayTheme),
child: SingleChildScrollView(
child: WrappedCodeField(
wrap: true,
controller: _controller,
textStyle: TextStyle(
fontSize: _fontSize,
),
),
),
),
),
),
);
}
}