import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:provider/provider.dart'; import '../../providers/app_provider.dart'; import '../../models/data_models.dart'; import '../../utils/toast_util.dart'; /// 笔记详情页 - 极简主义设计 class NoteDetailPage extends StatefulWidget { final Note note; const NoteDetailPage({super.key, required this.note}); @override State createState() => _NoteDetailPageState(); } class _NoteDetailPageState extends State { @override Widget build(BuildContext context) { // 从 Provider 获取最新的笔记数据 final note = context.watch().notes.firstWhere( (n) => n.id == widget.note.id, orElse: () => widget.note, ); return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text( _getTitle(note.content), overflow: TextOverflow.ellipsis, maxLines: 1, ), actions: [ // 格式指示器 - 纯文本标记 if (note.contentType == 'markdown') Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(2), ), child: const Text( 'MD', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFF666666), ), ), ) else Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(2), ), child: const Text( 'TXT', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFF666666), ), ), ), IconButton( icon: const Icon(Icons.edit_outlined), onPressed: () => _navigateToEdit(context), ), const SizedBox(width: 8), ], ), body: Column( children: [ // 标签区域 if (note.tags.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), decoration: const BoxDecoration( border: Border( bottom: BorderSide(color: Color(0xFFE8E8E8), width: 0.5), ), ), child: Row( children: [ Wrap( spacing: 10, runSpacing: 10, children: note.tags.map((tag) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), ), child: Text( tag, style: const TextStyle( fontSize: 13, color: Color(0xFF666666), ), ), ); }).toList(), ), ], ), ), // 内容区域 Expanded( child: note.contentType == 'markdown' ? _buildMarkdownContent(note) : _buildPlainTextContent(note), ), // 图片区域(仅在纯文本模式下显示) if (note.contentType == 'plain_text' && note.images.isNotEmpty) Container( padding: const EdgeInsets.fromLTRB(20, 20, 20, 24), decoration: const BoxDecoration( border: Border( top: BorderSide(color: Color(0xFFE8E8E8), width: 0.5), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 图片标题 Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), ), child: const Icon( Icons.image_outlined, size: 18, color: Color(0xFF666666), ), ), const SizedBox(width: 12), Text( '图片 (${note.images.length})', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), ), ), ], ), const SizedBox(height: 16), // 图片列表 SizedBox( height: 110, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: note.images.length, itemBuilder: (context, index) { return GestureDetector( onTap: () => _showImagePreview(context, note.images, index), child: Container( width: 110, height: 110, margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), ), clipBehavior: Clip.antiAlias, child: Image.file( File(note.images[index]), fit: BoxFit.cover, ), ), ); }, ), ), ], ), ), // 底部操作栏 Container( decoration: const BoxDecoration( border: Border( top: BorderSide(color: Color(0xFFE8E8E8), width: 0.5), ), ), child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row( children: [ // 时间信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( '创建 ${_formatDateTime(note.createdAt)}', style: const TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), const SizedBox(height: 4), Text( '更新 ${_formatDateTime(note.updatedAt)}', style: const TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), ], ), ), // 操作按钮 Row( children: [ _buildActionButton( icon: Icons.edit_outlined, color: const Color(0xFF666666), onTap: () => _navigateToEdit(context), ), const SizedBox(width: 12), _buildActionButton( icon: Icons.delete_outline, color: Colors.red, onTap: () => _showDeleteDialog(context), ), ], ), ], ), ), ), ), ], ), ); } /// 构建 Markdown 内容 Widget _buildMarkdownContent(Note note) { return Markdown( data: note.content, styleSheet: MarkdownStyleSheet( h1: const TextStyle( fontSize: 24, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4, ), h2: const TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4, ), h3: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A), height: 1.4, ), p: const TextStyle( fontSize: 16, color: Color(0xFF1A1A1A), height: 1.8, ), code: const TextStyle( fontSize: 14, color: Color(0xFF1A1A1A), backgroundColor: Color(0xFFF5F5F5), ), codeblockDecoration: BoxDecoration( color: const Color(0xFFF5F5F5), border: Border.all(color: const Color(0xFFE5E5E5)), ), blockquote: const TextStyle( fontSize: 16, color: Color(0xFF666666), fontStyle: FontStyle.italic, ), blockquoteDecoration: BoxDecoration( border: Border( left: BorderSide(color: const Color(0xFF999999), width: 4), ), ), listBullet: const TextStyle( fontSize: 16, color: Color(0xFF1A1A1A), ), a: const TextStyle( fontSize: 16, color: Color(0xFF1A1A1A), decoration: TextDecoration.underline, ), ), padding: const EdgeInsets.all(16), ); } /// 构建纯文本内容 Widget _buildPlainTextContent(Note note) { return SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: SizedBox( width: double.infinity, child: SelectableText( note.content, textAlign: TextAlign.left, style: const TextStyle( fontSize: 15, color: Color(0xFF1A1A1A), height: 1.9, letterSpacing: 0.2, ), ), ), ); } /// 格式化日期时间 String _formatDateTime(DateTime dateTime) { return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; } /// 获取标题(内容第一行,去除换行) String _getTitle(String content) { if (content.isEmpty) return '无标题'; // 移除换行符和多余空格 final trimmed = content.replaceAll('\n', ' ').trim(); if (trimmed.isEmpty) return '无标题'; return trimmed; } /// 跳转到编辑页面 void _navigateToEdit(BuildContext context) { // 从 Provider 获取最新的笔记数据,确保图片等字段是最新的 final currentNote = context.read().notes.firstWhere( (n) => n.id == widget.note.id, orElse: () => widget.note, ); Navigator.pushNamed(context, '/note-form', arguments: currentNote).then((_) async { // 返回时刷新笔记列表 await context.read().loadNotes(); }); } /// 显示图片预览 void _showImagePreview(BuildContext context, List images, int initialIndex) { showDialog( context: context, barrierDismissible: true, builder: (context) => GestureDetector( onTap: () => Navigator.pop(context), child: Container( color: Colors.black.withOpacity(0.9), child: Center( child: InteractiveViewer( panEnabled: true, boundaryMargin: const EdgeInsets.all(20), minScale: 0.5, maxScale: 4, child: Image.file( File(images[initialIndex]), fit: BoxFit.contain, ), ), ), ), ), ); } /// 构建操作按钮 Widget _buildActionButton({ required IconData icon, required Color color, required VoidCallback onTap, }) { return Container( width: 40, height: 40, decoration: BoxDecoration( color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5), ), child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(10), child: Icon( icon, color: color, size: 20, ), ), ), ); } /// 显示删除对话框 void _showDeleteDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: const Text( '确认删除', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), content: const Text( '确定要删除这条笔记吗?删除后可在回收站恢复。', style: TextStyle( fontSize: 14, color: Color(0xFF666666), height: 1.5, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( foregroundColor: const Color(0xFF666666), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('取消'), ), ElevatedButton( onPressed: () async { await context.read().removeNote(widget.note.id); // 刷新笔记列表 await context.read().loadNotes(); if (!mounted) return; Navigator.pop(context); Navigator.pop(context); ToastUtil.show(context, '已删除'); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), child: const Text('删除'), ), ], actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), ); } }