import 'dart:async'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:notas/models/note.dart'; import 'package:notas/platform/app_platform.dart'; // NoteEditorScreen: unified UI for creating and editing notes. // - Use `NoteEditorScreen.showDialog(context, note: existing)` to edit. // - Use `NoteEditorScreen.showDialog(context)` to create a new note. // The screen returns either a `Note` (saved) or the string `'delete'` when // the user confirmed deletion. `null` indicates the user closed without saving. class NoteEditorScreen extends StatefulWidget { const NoteEditorScreen({ super.key, required this.note, this.categoryId, this.onComplete, }); final Note? note; final String? categoryId; final ValueChanged? onComplete; @override State createState() => _NoteEditorScreenState(); static Future _showGeneralEditorDialog( BuildContext context, { Note? note, String? categoryId, }) { return showGeneralDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, transitionDuration: const Duration(milliseconds: 200), pageBuilder: (context, animation, secondaryAnimation) { return NoteEditorScreen(note: note, categoryId: categoryId); }, transitionBuilder: (context, animation, secondaryAnimation, child) { return ScaleTransition(scale: animation, child: child); }, ); } static Future showDialog( BuildContext context, { Note? note, String? categoryId, }) { if (isAndroid || isIOS) { return _showGeneralEditorDialog( context, note: note, categoryId: categoryId, ); } final OverlayState? overlayState = Overlay.of(context, rootOverlay: true); if (overlayState == null) { return _showGeneralEditorDialog( context, note: note, categoryId: categoryId, ); } final Completer completer = Completer(); late final OverlayEntry entry; entry = OverlayEntry( builder: (BuildContext overlayContext) { return NoteEditorScreen( note: note, categoryId: categoryId, onComplete: (dynamic result) { if (!completer.isCompleted) { completer.complete(result); } if (entry.mounted) { entry.remove(); } }, ); }, ); overlayState.insert(entry); return completer.future; } } class _NoteEditorScreenState extends State { late TextEditingController _titleController; late TextEditingController _bodyController; late Note _currentNote; late bool _isNewNote; bool get _isMobileLayout => isAndroid || isIOS; @override void initState() { super.initState(); _isNewNote = widget.note == null; if (_isNewNote) { final DateTime now = DateTime.now(); _currentNote = Note( title: '', body: '', createdAt: now, updatedAt: now, position: 0, categoryId: widget.categoryId, ); } else { _currentNote = widget.note!; } _titleController = TextEditingController(text: _currentNote.title); _bodyController = TextEditingController(text: _currentNote.body); } @override void dispose() { _titleController.dispose(); _bodyController.dispose(); super.dispose(); } void _complete(dynamic result) { final ValueChanged? callback = widget.onComplete; if (callback != null) { callback(result); return; } Navigator.of(context).pop(result); } void _closeWithoutSaving() { _complete(null); } void _saveNote() { final String title = _titleController.text.trim(); final String body = _bodyController.text.trim(); if (title.isEmpty && body.isEmpty) { _complete(null); return; } final Note updatedNote = _currentNote.copyWith( title: title.isEmpty ? 'Sin título' : title, body: body, updatedAt: DateTime.now(), isDirty: true, ); _complete(updatedNote); } Widget _buildDeleteConfirmationDialog({ required ValueChanged onConfirmed, }) { final bool isDeletedNote = _currentNote.isDeleted; return AlertDialog( backgroundColor: const Color(0xFF303134), title: Text( isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar nota', style: const TextStyle(color: Colors.white), ), content: Text( isDeletedNote ? 'Esta nota ya está borrada. Si la eliminas ahora, se borrará permanentemente.' : '¿Estás seguro de que deseas eliminar esta nota?', style: const TextStyle(color: Colors.white70), ), actions: [ TextButton( onPressed: () => onConfirmed(false), child: const Text( 'Cancelar', style: TextStyle(color: Colors.white70), ), ), TextButton( onPressed: () => onConfirmed(true), child: Text( isDeletedNote ? 'Eliminar permanentemente' : 'Eliminar', style: const TextStyle(color: Colors.red), ), ), ], ); } Future _showDeleteConfirmation() async { if (_isMobileLayout) { final bool? confirmed = await showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext dialogContext) { return _buildDeleteConfirmationDialog( onConfirmed: (bool confirmed) => Navigator.of(dialogContext).pop(confirmed), ); }, ); return confirmed ?? false; } final OverlayState? overlayState = Overlay.of(context, rootOverlay: true); if (overlayState == null) { final bool? confirmed = await showDialog( context: context, barrierDismissible: false, barrierColor: Colors.transparent, builder: (BuildContext dialogContext) { return _buildDeleteConfirmationDialog( onConfirmed: (bool confirmed) => Navigator.of(dialogContext).pop(confirmed), ); }, ); return confirmed ?? false; } final Completer completer = Completer(); late final OverlayEntry entry; bool didRemove = false; entry = OverlayEntry( builder: (BuildContext overlayContext) { final ValueChanged close = (bool confirmed) { if (!completer.isCompleted) { completer.complete(confirmed); } if (!didRemove && entry.mounted) { didRemove = true; entry.remove(); } }; return Material( color: Colors.transparent, child: Stack( children: [ const Positioned.fill( child: ModalBarrier( dismissible: false, color: Color.fromARGB(140, 0, 0, 0), ), ), Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: _buildDeleteConfirmationDialog(onConfirmed: close), ), ), ], ), ); }, ); overlayState.insert(entry); return completer.future; } Future _deleteNote() async { final bool confirmed = await _showDeleteConfirmation(); if (!mounted || !confirmed) { return; } _complete('delete'); } String _formatDate(DateTime date) { final DateTime now = DateTime.now(); final DateTime today = DateTime(now.year, now.month, now.day); final DateTime yesterday = today.subtract(const Duration(days: 1)); final DateTime noteDate = DateTime(date.year, date.month, date.day); if (noteDate == today) { return 'Hoy ${DateFormat('HH:mm').format(date)}'; } else if (noteDate == yesterday) { return 'Ayer ${DateFormat('HH:mm').format(date)}'; } else { return DateFormat('dd/MM/yyyy HH:mm').format(date); } } Widget _buildEditorContent({required bool isMobile}) { final double titleSpacing = isMobile ? 16.0 : 8.0; return Column( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.white12, width: 1)), ), child: Row( children: [ IconButton( onPressed: _closeWithoutSaving, icon: const Icon(Icons.close, color: Colors.white70), tooltip: 'Cerrar sin guardar', ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'Creado: ${_formatDate(_currentNote.createdAt)}', style: const TextStyle( color: Colors.white54, fontSize: 12, ), ), if (_currentNote.updatedAt != _currentNote.createdAt) Text( 'Modificado: ${_formatDate(_currentNote.updatedAt)}', style: const TextStyle( color: Colors.white54, fontSize: 12, ), ), ], ), ), ], ), ), Expanded( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: _titleController, style: const TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold, ), decoration: const InputDecoration( hintText: 'Título', hintStyle: TextStyle(color: Colors.white30), border: InputBorder.none, contentPadding: EdgeInsets.zero, ), ), SizedBox(height: titleSpacing), Expanded( child: TextField( controller: _bodyController, keyboardType: TextInputType.multiline, maxLines: null, expands: true, style: const TextStyle( color: Colors.white, fontSize: 16, height: 1.6, ), decoration: const InputDecoration( hintText: 'Escribe tu nota...', hintStyle: TextStyle(color: Colors.white30), border: InputBorder.none, contentPadding: EdgeInsets.zero, ), ), ), ], ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border(top: BorderSide(color: Colors.white12, width: 1)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (!_isNewNote) IconButton( onPressed: _deleteNote, icon: const Icon(Icons.delete_outline, color: Colors.red), tooltip: 'Eliminar nota', ) else const SizedBox(width: 48), FilledButton(onPressed: _saveNote, child: const Text('Guardar')), ], ), ), ], ); } @override Widget build(BuildContext context) { if (_isMobileLayout) { return Material( color: Colors.transparent, child: SafeArea( child: Container( color: const Color.fromARGB(255, 24, 25, 26), child: _buildEditorContent(isMobile: true), ), ), ); } return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final double maxWidth = math.min(constraints.maxWidth - 32, 600); final double maxHeight = math.min(constraints.maxHeight - 32, 720); return Stack( children: [ Positioned.fill( child: ModalBarrier( dismissible: false, color: const Color.fromARGB(54, 0, 0, 0).withValues(alpha: 0.5), ), ), Positioned.fill( child: Center( child: SizedBox( width: maxWidth, height: maxHeight, child: Material( color: const Color.fromRGBO(24, 25, 26, 1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.white24, width: 1), ), clipBehavior: Clip.antiAlias, child: _buildEditorContent(isMobile: false), ), ), ), ), ], ); }, ); } }