import 'dart:io'; 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:shared_preferences/shared_preferences.dart'; import 'package:share_plus/share_plus.dart'; import 'themes/day.dart'; import 'themes/night.dart'; import 'themes/markdown.dart'; import 'codefield.dart'; class NoteEditorScreen extends StatefulWidget { final File note; final String? notesDirectoryPath; final bool isDarkMode; final NoteMode initialMode; final bool scrollToEnd; const NoteEditorScreen({ super.key, required this.note, required this.isDarkMode, required this.notesDirectoryPath, this.initialMode = NoteMode.view, this.scrollToEnd = false, }); @override State createState() => _NoteEditorScreenState(); } enum NoteMode { view, edit, preview } class _NoteEditorScreenState extends State { late CodeController _controller; late String _initialContent; late NoteMode _mode; final FocusNode _editorFocusNode = FocusNode(); final ScrollController _scrollController = ScrollController(); bool _isLoading = true; bool _autoSaveEnabled = false; double _fontSize = 16.0; @override void initState() { super.initState(); _mode = widget.initialMode; _loadPreferences(); _loadNoteContent(); } Future _loadPreferences() async { final prefs = await SharedPreferences.getInstance(); setState(() { _autoSaveEnabled = prefs.getBool('auto_save_enabled') ?? false; _fontSize = prefs.getDouble('font_size') ?? 16.0; }); } Future _setAutoSaveEnabled(bool value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('auto_save_enabled', value); setState(() { _autoSaveEnabled = value; }); } Future _loadNoteContent() async { try { _initialContent = await widget.note.readAsString(); } catch (e) { debugPrint("❌ Failed to read file: ${widget.note.path}"); _initialContent = ""; } setState(() { _controller = CodeController( text: _initialContent, language: markdown, ); _controller.addListener(_onTextChanged); _isLoading = false; }); WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.initialMode == NoteMode.edit) { _controller.selection = TextSelection.collapsed(offset: _controller.text.length); _editorFocusNode.requestFocus(); } else if (widget.scrollToEnd) { // Two frames: first renders content, second has a stable maxScrollExtent. WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom()); } }); } void _scrollToBottom() { if (_scrollController.hasClients) { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } } void _onTextChanged() { if (_mode == NoteMode.edit && _autoSaveEnabled && _controller.text != _initialContent) { _saveNote(); } } Future _saveNote() async { try { if (_controller.text == _initialContent) return; // No change, skip saving await widget.note.writeAsString(_controller.text); _initialContent = _controller.text; // Update initial state debugPrint("✅ Note saved successfully"); } catch (e) { debugPrint("❌ Error saving note: ${widget.note.path}"); } } void _shareNote() { if (_controller.text.isNotEmpty) { SharePlus.instance.share(ShareParams( text: _controller.text, subject: widget.note.uri.pathSegments.last, )); } } Future _onWillPop() async { if (_mode != NoteMode.edit || _controller.text == _initialContent) return true; 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(); if (context.mounted) Navigator.of(context).pop(true); }, child: Text("Save"), ), TextButton( onPressed: () => Navigator.of(context).pop(false), // Stay on page child: Text("Cancel"), ), ], ), ) ?? false; } void _updateFontSize(double newSize) { setState(() { _fontSize = newSize; }); SharedPreferences.getInstance().then((prefs) => prefs.setDouble('font_size', newSize)); } @override void dispose() { _controller.removeListener(_onTextChanged); _controller.dispose(); _editorFocusNode.dispose(); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { if (didPop) return; final bool shouldPop = await _onWillPop(); if (shouldPop && context.mounted) Navigator.of(context).pop(); }, 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()) { if (context.mounted) Navigator.pop(context); } }, ), actions: [ Padding( padding: EdgeInsets.symmetric(horizontal: 15), child: PopupMenuButton( icon: SvgPicture.asset( "assets/blaster.svg", width: 30, colorFilter: const ColorFilter.mode(Colors.orange, BlendMode.srcIn), ), onSelected: (String value) { switch (value) { case 'Share': // ✅ New case for sharing _shareNote(); break; case 'Save': _saveNote(); break; case 'Toggle Autosave': _setAutoSaveEnabled(!_autoSaveEnabled); break; case 'Mode View': setState(() { _mode = NoteMode.view; }); break; case 'Mode Edit': setState(() { _mode = NoteMode.edit; }); WidgetsBinding.instance.addPostFrameCallback((_) { _controller.selection = TextSelection.collapsed( offset: _controller.text.length); _editorFocusNode.requestFocus(); _scrollToBottom(); }); break; case 'Mode Preview': setState(() { _mode = NoteMode.preview; }); break; } }, itemBuilder: (BuildContext context) => [ PopupMenuItem( // ✅ Share button value: 'Share', child: ListTile( leading: Icon(Icons.share_outlined), title: Text("Share"), ), ), PopupMenuDivider(), PopupMenuItem( value: 'Save', enabled: _mode == NoteMode.edit, child: ListTile( leading: Icon(Icons.save_outlined), title: Text("Save"), ), ), PopupMenuItem( value: 'Toggle Autosave', enabled: _mode == NoteMode.edit, child: ListTile( leading: Icon(_autoSaveEnabled ? Icons.alarm_off : Icons.alarm_on), title: Text(_autoSaveEnabled ? "Disable Autosave" : "Enable Autosave"), ), ), PopupMenuDivider(), PopupMenuItem( 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); }, ); }, ), ], ), ), PopupMenuDivider(), PopupMenuItem( value: 'Mode View', child: ListTile( leading: Icon(_mode == NoteMode.view ? Icons.check : Icons.book_outlined), title: Text("View Mode"), ), ), PopupMenuItem( value: 'Mode Edit', child: ListTile( leading: Icon(_mode == NoteMode.edit ? Icons.check : Icons.edit_note_outlined), title: Text("Edit Mode"), ), ), PopupMenuItem( value: 'Mode Preview', child: ListTile( leading: Icon(_mode == NoteMode.preview ? Icons.check : Icons.visibility_outlined), title: Text("Preview Mode"), ), ), ], ), ), ], ), body: _isLoading ? Center(child: CircularProgressIndicator()) : _mode == NoteMode.preview ? 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, 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), ), ), ), ) : Padding( padding: const EdgeInsets.all(16.0), child: CodeTheme( data: CodeThemeData( styles: widget.isDarkMode ? nightTheme : dayTheme), child: SingleChildScrollView( controller: _scrollController, child: WrappedCodeField( wrap: true, controller: _controller, readOnly: _mode == NoteMode.view, focusNode: _editorFocusNode, textStyle: TextStyle( fontSize: _fontSize, ), ), ), ), ), ), ); } }