feat: integrate Flutter Quill for rich text editing and update note handling

- Added Flutter Quill package for rich text editing capabilities.
- Refactored note body handling to support Quill's Document format.
- Updated note editor screen to use QuillEditor and QuillController.
- Enhanced note search functionality to convert note body to plain text.
- Modified note card display to show plain text from note body.
- Updated localization support for Quill in the app.
- Registered necessary plugins for URL launching and file selection on all platforms.
- Updated app icons and other assets for consistency across platforms.
- Updated pubspec.yaml and pubspec.lock to include new dependencies and versions.
This commit is contained in:
2026-05-24 17:52:11 +02:00
parent d849c25ed6
commit 710be805ee
27 changed files with 451 additions and 63 deletions
+3 -2
View File
@@ -5,6 +5,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:notas/data/note_body.dart';
import 'package:notas/data/note_repository.dart';
import 'package:notas/models/note.dart';
import 'package:notas/screens/note_editor_screen.dart';
@@ -290,7 +291,7 @@ class _HomeScreenState extends State<HomeScreen> {
.where(
(Note note) =>
note.title.toLowerCase().contains(query) ||
note.body.toLowerCase().contains(query),
noteBodyToPlainText(note.body).toLowerCase().contains(query),
)
.toList();
}
@@ -713,7 +714,7 @@ class _DraggableNote extends StatelessWidget {
),
const SizedBox(height: 8),
Text(
note.body,
noteBodyToPlainText(note.body),
style: TextStyle(color: palette.textSecondary, fontSize: 14),
maxLines: 20,
overflow: TextOverflow.clip,
+89 -33
View File
@@ -2,8 +2,10 @@ import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:intl/intl.dart';
import 'package:notas/data/note_body.dart';
import 'package:notas/models/category.dart';
import 'package:notas/models/note.dart';
import 'package:notas/platform/app_platform.dart';
@@ -110,7 +112,9 @@ class NoteEditorScreen extends StatefulWidget {
class _NoteEditorScreenState extends State<NoteEditorScreen> {
late TextEditingController _titleController;
late TextEditingController _bodyController;
late QuillController _bodyController;
late FocusNode _bodyFocusNode;
late ScrollController _bodyScrollController;
late Note _currentNote;
late bool _isNewNote;
String? _selectedCategoryId;
@@ -147,13 +151,20 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
_selectedCategoryId = _currentNote.categoryId ?? widget.categoryId;
_titleController = TextEditingController(text: _currentNote.title);
_bodyController = TextEditingController(text: _currentNote.body);
_bodyController = QuillController(
document: noteBodyToDocument(_currentNote.body),
selection: const TextSelection.collapsed(offset: 0),
);
_bodyFocusNode = FocusNode();
_bodyScrollController = ScrollController();
}
@override
void dispose() {
_closeCategoryMenu();
_titleController.dispose();
_bodyFocusNode.dispose();
_bodyScrollController.dispose();
_bodyController.dispose();
super.dispose();
}
@@ -182,17 +193,17 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
void _saveNote() {
final String title = _titleController.text.trim();
final String body = _bodyController.text.trim();
final String bodyPlainText = _bodyController.document.toPlainText().trim();
final bool categoryChanged = _selectedCategoryId != _currentNote.categoryId;
if (title.isEmpty && body.isEmpty && !categoryChanged) {
if (title.isEmpty && bodyPlainText.isEmpty && !categoryChanged) {
_complete(null);
return;
}
final Note updatedNote = _currentNote.copyWith(
title: title.isEmpty ? 'Sin título' : title,
body: body,
body: noteDocumentToStorageJson(_bodyController.document),
categoryId: _selectedCategoryId,
updatedAt: DateTime.now(),
isDirty: true,
@@ -646,21 +657,20 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
),
SizedBox(height: titleSpacing),
Expanded(
child: TextField(
controller: _bodyController,
keyboardType: TextInputType.multiline,
maxLines: null,
expands: true,
style: TextStyle(
color: palette.textPrimary,
fontSize: 16,
height: 1.6,
),
decoration: InputDecoration(
hintText: 'Escribe tu nota...',
hintStyle: TextStyle(color: palette.textHint),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: QuillEditor.basic(
controller: _bodyController,
focusNode: _bodyFocusNode,
scrollController: _bodyScrollController,
config: QuillEditorConfig(
scrollable: true,
padding: EdgeInsets.zero,
autoFocus: false,
expands: true,
placeholder: 'Escribe tu nota...',
keyboardAppearance: Theme.of(context).brightness,
),
),
),
),
@@ -673,21 +683,67 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
decoration: BoxDecoration(
border: Border(top: BorderSide(color: palette.border, width: 1)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!_isNewNote)
IconButton(
onPressed: _deleteNote,
icon: Icon(
Icons.delete_outline,
color: palette.destructiveAccent,
QuillSimpleToolbar(
controller: _bodyController,
config: const QuillSimpleToolbarConfig(
color: Colors.transparent,
showBoldButton: true,
showItalicButton: true,
showUnderLineButton: true,
showStrikeThrough: false,
showInlineCode: false,
showColorButton: false,
showBackgroundColorButton: false,
showClearFormat: false,
showAlignmentButtons: false,
showHeaderStyle: false,
showListNumbers: true,
showListBullets: true,
showListCheck: true,
showCodeBlock: false,
showQuote: false,
showIndent: false,
showLink: false,
showUndo: false,
showRedo: false,
showDividers: false,
showFontFamily: false,
showFontSize: false,
showDirection: false,
showSearchButton: false,
showSubscript: false,
showSuperscript: false,
multiRowsDisplay: false,
showClipboardCut: false,
showClipboardCopy: false,
showClipboardPaste: false,
axis: Axis.horizontal,
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!_isNewNote)
IconButton(
onPressed: _deleteNote,
icon: Icon(
Icons.delete_outline,
color: palette.destructiveAccent,
),
tooltip: 'Eliminar nota',
)
else
const SizedBox(width: 48),
FilledButton(
onPressed: _saveNote,
child: const Text('Guardar'),
),
tooltip: 'Eliminar nota',
)
else
const SizedBox(width: 48),
FilledButton(onPressed: _saveNote, child: const Text('Guardar')),
],
),
],
),
),