Files
notas/lib/widgets/note_card.dart
T
Marcos 710be805ee 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.
2026-05-24 17:52:11 +02:00

154 lines
4.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:notas/data/note_body.dart';
import 'package:notas/models/note.dart';
import 'package:notas/theme/app_palette.dart';
// Small presentational widget for a note inside the grid.
// Keep this widget lightweight and layout-agnostic: it should not force
// width/height constraints (so it works inside different parent layouts
// like MasonryGridView or Draggable feedback). Visual styling only.
class NoteCard extends StatefulWidget {
const NoteCard({
super.key,
required this.note,
this.onTap,
this.isDragging = false,
this.borderColor,
});
final Note note;
final VoidCallback? onTap;
final bool isDragging;
final Color? borderColor;
@override
State<NoteCard> createState() => _NoteCardState();
}
class _NoteCardState extends State<NoteCard> {
bool _isPressed = false;
@override
Widget build(BuildContext context) {
final AppPalette palette = Theme.of(context).extension<AppPalette>()!;
final bool showGrabbing = widget.isDragging || _isPressed;
return MouseRegion(
cursor: showGrabbing
? SystemMouseCursors.grabbing
: SystemMouseCursors.grab,
child: GestureDetector(
onTapDown: widget.onTap == null
? null
: (_) {
setState(() {
_isPressed = true;
});
},
onTapUp: widget.onTap == null
? null
: (_) {
setState(() {
_isPressed = false;
});
},
onTapCancel: widget.onTap == null
? null
: () {
setState(() {
_isPressed = false;
});
},
onTap: widget.onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: palette.cardBackground,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: widget.borderColor ?? palette.textDisabled,
width: 1,
),
),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Estimate whether the body will exceed 20 lines without always
// running the expensive TextPainter layout. This heuristic counts
// newline characters and estimates wrapped lines based on an
// average characters-per-line to handle many short lines well.
final String bodyText = noteBodyToPlainText(widget.note.body);
final List<String> rawLines = bodyText.split('\n');
const int avgCharsPerLine = 40;
int estimatedLines = 0;
for (final String line in rawLines) {
estimatedLines += (line.trim().length ~/ avgCharsPerLine) + 1;
}
final bool needsPreciseMeasurement = estimatedLines > 15;
final bool isBodyTruncated;
if (needsPreciseMeasurement) {
final TextPainter textPainter = TextPainter(
text: TextSpan(
text: bodyText,
style: TextStyle(
color: palette.textSecondary,
fontSize: 14,
),
),
maxLines: 15,
textDirection: TextDirection.ltr,
)..layout(maxWidth: constraints.maxWidth);
isBodyTruncated = textPainter.didExceedMaxLines;
} else {
isBodyTruncated = false;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.note.title,
style: TextStyle(
color: palette.textPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Text(
bodyText,
style: TextStyle(
color: palette.textSecondary,
fontSize: 14,
),
maxLines: 15,
overflow: TextOverflow.clip,
),
if (isBodyTruncated) ...[
const SizedBox(height: 4),
Text(
'...',
style: TextStyle(
color: palette.textMuted,
fontSize: 18,
height: 1,
),
),
],
],
);
},
),
),
),
);
}
}