feat: update notes view & edit ui

This commit is contained in:
2026-04-21 09:52:38 +03:00
parent 08159faec2
commit 36a74b39e1
3 changed files with 294 additions and 219 deletions

View File

@@ -42,6 +42,7 @@ class NoteDetailView extends StatelessWidget {
),
),
floatingActionButton: FloatingActionButton(
heroTag: null,
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
@@ -51,70 +52,79 @@ class NoteDetailView extends StatelessWidget {
},
child: const Icon(Icons.edit),
),
body: note.content != null && note.content!.isNotEmpty
? Directionality(
textDirection: contentDir,
child: Markdown(
data: note.content!,
padding: const EdgeInsets.all(16),
selectable: true,
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
h1: theme.textTheme.headlineMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h2: theme.textTheme.headlineSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h3: theme.textTheme.titleLarge?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h4: theme.textTheme.titleMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
listBullet: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
code: TextStyle(
color: textColor,
backgroundColor: textColor.withAlpha(30),
fontFamily: 'monospace',
),
codeblockDecoration: BoxDecoration(
color: textColor.withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
blockquote: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(180),
fontStyle: FontStyle.italic,
),
blockquoteDecoration: BoxDecoration(
border: Border(
left: BorderSide(
color: textColor.withAlpha(100),
width: 4,
body: Hero(
tag: 'note-${note.id}',
child: Material(
color: bgColor,
child: note.content != null && note.content!.isNotEmpty
? Directionality(
textDirection: contentDir,
child: Markdown(
data: note.content!,
padding: const EdgeInsets.all(16),
selectable: true,
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
h1: theme.textTheme.headlineMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h2: theme.textTheme.headlineSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h3: theme.textTheme.titleLarge?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h4: theme.textTheme.titleMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
listBullet: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
code: TextStyle(
color: textColor,
backgroundColor: textColor.withAlpha(30),
fontFamily: 'monospace',
),
codeblockDecoration: BoxDecoration(
color: textColor.withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
blockquote: theme.textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(180),
fontStyle: FontStyle.italic,
),
blockquoteDecoration: BoxDecoration(
border: Border(
left: BorderSide(
color: textColor.withAlpha(100),
width: 4,
),
),
),
a: TextStyle(
color: textColor,
decoration: TextDecoration.underline,
),
strong: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
em: TextStyle(
color: textColor,
fontStyle: FontStyle.italic,
),
),
),
a: TextStyle(
color: textColor,
decoration: TextDecoration.underline,
),
strong: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
em: TextStyle(color: textColor, fontStyle: FontStyle.italic),
),
),
)
: null,
)
: null,
),
),
);
}
}

View File

@@ -104,10 +104,25 @@ class _NoteFormViewState extends State<NoteFormView> {
}
}
Color get _bgColor {
if (_selectedColor != null && _selectedColor!.isNotEmpty) {
final hex = _selectedColor!.replaceFirst('#', '');
final value = int.tryParse('FF$hex', radix: 16);
if (value != null) return Color(value);
}
return Theme.of(context).colorScheme.surfaceContainerHighest;
}
@override
Widget build(BuildContext context) {
final bgColor = _bgColor;
final textColor = _contrastColor(bgColor);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
backgroundColor: bgColor,
foregroundColor: textColor,
title: Text(_isEditing ? m.notesWall.editNote : m.notesWall.newNote),
),
floatingActionButton: FloatingActionButton(
@@ -120,76 +135,92 @@ class _NoteFormViewState extends State<NoteFormView> {
)
: const Icon(Icons.check),
),
body: ListView(
padding: const EdgeInsets.all(16),
body: Column(
children: [
TextField(
controller: _titleController,
decoration: InputDecoration(
labelText: m.notesWall.title,
border: const OutlineInputBorder(),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(16, 16, 16, 0),
child: TextField(
controller: _titleController,
decoration: InputDecoration(
hintText: m.notesWall.title,
hintStyle: TextStyle(color: textColor.withAlpha(100)),
border: InputBorder.none,
),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
autofocus: !_isEditing,
textCapitalization: TextCapitalization.sentences,
textInputAction: TextInputAction.next,
textDirection: _titleDir,
),
autofocus: !_isEditing,
textCapitalization: TextCapitalization.sentences,
textInputAction: TextInputAction.next,
textDirection: _titleDir,
),
const SizedBox(height: 16),
TextField(
controller: _contentController,
decoration: InputDecoration(
labelText: m.notesWall.content,
border: const OutlineInputBorder(),
alignLabelWithHint: true,
),
textCapitalization: TextCapitalization.sentences,
maxLines: 10,
minLines: 4,
textDirection: _contentDir,
),
const SizedBox(height: 16),
Text(
m.notesWall.color,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _colorOptions.map((hex) {
final color = hex != null
? Color(
int.parse('FF${hex.replaceFirst('#', '')}', radix: 16),
)
: Theme.of(context).colorScheme.surfaceContainerHighest;
final isSelected = _selectedColor == hex;
return GestureDetector(
onTap: () => setState(() => _selectedColor = hex),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(
color: Theme.of(context).colorScheme.primary,
width: 3,
)
: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: isSelected
? Icon(
Icons.check,
size: 18,
color: _contrastColor(color),
)
: null,
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 0),
child: TextField(
controller: _contentController,
decoration: InputDecoration(
hintText: m.notesWall.content,
hintStyle: TextStyle(color: textColor.withAlpha(100)),
border: InputBorder.none,
),
);
}).toList(),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: textColor.withAlpha(230),
),
textCapitalization: TextCapitalization.sentences,
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
textDirection: _contentDir,
),
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsetsDirectional.fromSTEB(16, 8, 80, 48),
child: Row(
children: _colorOptions.map((hex) {
final color = hex != null
? Color(
int.parse('FF${hex.replaceFirst('#', '')}', radix: 16),
)
: Theme.of(context).colorScheme.surfaceContainerHighest;
final isSelected = _selectedColor == hex;
return Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: GestureDetector(
onTap: () => setState(() => _selectedColor = hex),
child: CustomPaint(
foregroundPainter: hex == null
? _DiagonalLinePainter(
color: textColor.withAlpha(120),
)
: null,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(color: textColor, width: 3)
: Border.all(color: textColor.withAlpha(60)),
),
child: isSelected
? Icon(
Icons.check,
size: 18,
color: _contrastColor(color),
)
: null,
),
),
),
);
}).toList(),
),
),
],
),
@@ -200,3 +231,34 @@ class _NoteFormViewState extends State<NoteFormView> {
return bg.computeLuminance() > 0.5 ? Colors.black87 : Colors.white;
}
}
class _DiagonalLinePainter extends CustomPainter {
final Color color;
_DiagonalLinePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = 2
..style = PaintingStyle.stroke;
final center = size.center(Offset.zero);
final radius = size.width / 2;
canvas.clipRRect(
RRect.fromRectAndRadius(
Rect.fromCircle(center: center, radius: radius),
Radius.circular(radius),
),
);
canvas.drawLine(
Offset(size.width * 0.15, size.height * 0.85),
Offset(size.width * 0.85, size.height * 0.15),
paint,
);
}
@override
bool shouldRepaint(_DiagonalLinePainter oldDelegate) =>
color != oldDelegate.color;
}

View File

@@ -74,104 +74,107 @@ class NoteTile extends StatelessWidget {
final titleDir = detectTextDirection(note.title);
final contentDir = detectTextDirection(note.content);
return Container(
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Directionality(
textDirection: titleDir,
child: Text(
note.title,
style: theme.textTheme.titleSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
return Hero(
tag: 'note-${note.id}',
child: Container(
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Directionality(
textDirection: titleDir,
child: Text(
note.title,
style: theme.textTheme.titleSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
_NoteMenuButton(
note: note,
controller: controller,
color: textColor,
),
],
),
if (note.content != null && note.content!.isNotEmpty) ...[
const SizedBox(height: 6),
Expanded(
child: ShaderMask(
shaderCallback: (bounds) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.white, Colors.white, Colors.transparent],
stops: const [0.0, 0.7, 1.0],
).createShader(bounds),
blendMode: BlendMode.dstIn,
child: Directionality(
textDirection: contentDir,
child: Markdown(
data: note.content!,
shrinkWrap: false,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodySmall?.copyWith(
color: textColor.withAlpha(200),
),
h1: theme.textTheme.titleMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h2: theme.textTheme.titleSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h3: theme.textTheme.bodyMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
listBullet: theme.textTheme.bodySmall?.copyWith(
color: textColor.withAlpha(200),
),
strong: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
em: TextStyle(
color: textColor.withAlpha(200),
fontStyle: FontStyle.italic,
),
code: TextStyle(
color: textColor,
backgroundColor: textColor.withAlpha(30),
fontFamily: 'monospace',
fontSize: 12,
),
blockquote: theme.textTheme.bodySmall?.copyWith(
color: textColor.withAlpha(180),
fontStyle: FontStyle.italic,
),
a: TextStyle(
color: textColor,
decoration: TextDecoration.underline,
),
),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
_NoteMenuButton(
note: note,
controller: controller,
color: textColor,
),
],
),
if (note.content != null && note.content!.isNotEmpty) ...[
const SizedBox(height: 6),
Expanded(
child: ShaderMask(
shaderCallback: (bounds) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.white, Colors.white, Colors.transparent],
stops: const [0.0, 0.7, 1.0],
).createShader(bounds),
blendMode: BlendMode.dstIn,
child: Directionality(
textDirection: contentDir,
child: Markdown(
data: note.content!,
shrinkWrap: false,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith(
p: theme.textTheme.bodySmall?.copyWith(
color: textColor.withAlpha(200),
),
h1: theme.textTheme.titleMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h2: theme.textTheme.titleSmall?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
h3: theme.textTheme.bodyMedium?.copyWith(
color: textColor,
fontWeight: FontWeight.bold,
),
listBullet: theme.textTheme.bodySmall?.copyWith(
color: textColor.withAlpha(200),
),
strong: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
em: TextStyle(
color: textColor.withAlpha(200),
fontStyle: FontStyle.italic,
),
code: TextStyle(
color: textColor,
backgroundColor: textColor.withAlpha(30),
fontFamily: 'monospace',
fontSize: 12,
),
blockquote: theme.textTheme.bodySmall?.copyWith(
color: textColor.withAlpha(180),
fontStyle: FontStyle.italic,
),
a: TextStyle(
color: textColor,
decoration: TextDecoration.underline,
),
),
),
),
),
),
],
],
),
),
);
}