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)), 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: 16, vertical: 12), decoration: const BoxDecoration( border: Border( bottom: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), ), ), child: Row( children: [ Wrap( spacing: 8, runSpacing: 8, children: note.tags.map((tag) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), border: Border.all(color: const Color(0xFFE5E5E5)), ), child: Text( tag, style: const TextStyle( fontSize: 12, color: Color(0xFF666666), ), ), ); }).toList(), ), ], ), ), // 内容区域 Expanded( child: note.contentType == 'markdown' ? _buildMarkdownContent(note) : _buildPlainTextContent(note), ), // 图片区域(仅在纯文本模式下显示) if (note.contentType == 'plain_text' && note.images.isNotEmpty) Container( height: 120, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: const BoxDecoration( border: Border( top: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), ), ), child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: note.images.length, itemBuilder: (context, index) { return Container( width: 100, height: 100, margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( border: Border.all(color: const Color(0xFFE5E5E5)), ), child: Image.file( File(note.images[index]), fit: BoxFit.cover, ), ); }, ), ), // 底部操作栏 Container( decoration: const BoxDecoration( border: Border( top: BorderSide(color: Color(0xFFE5E5E5), width: 0.5), ), ), child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 创建时间和更新时间 Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '创建时间:${_formatDateTime(note.createdAt)}', style: const TextStyle( fontSize: 11, color: Color(0xFF999999), ), ), const SizedBox(height: 4), Text( '更新时间:${_formatDateTime(note.updatedAt)}', style: const TextStyle( fontSize: 11, color: Color(0xFF999999), ), ), ], ), ), Row( children: [ IconButton( icon: const Icon(Icons.edit_outlined, size: 20), color: const Color(0xFF666666), onPressed: () => _navigateToEdit(context), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), const SizedBox(width: 16), IconButton( icon: const Icon(Icons.delete_outline, size: 20), color: Colors.red, onPressed: () => _showDeleteDialog(context), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), ], ), ], ), ], ), ), ), ), ], ), ); } /// 构建 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.all(16), child: SizedBox( width: double.infinity, child: Text( note.content, textAlign: TextAlign.left, style: const TextStyle( fontSize: 16, color: Color(0xFF1A1A1A), height: 1.8, ), ), ), ); } /// 格式化日期时间 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')}'; } /// 获取标题(内容前5个字) String _getTitle(String content) { if (content.isEmpty) return '无标题'; // 移除换行符和多余空格 final trimmed = content.replaceAll('\n', ' ').trim(); if (trimmed.isEmpty) return '无标题'; // 取前5个字 if (trimmed.length <= 5) return trimmed; return '${trimmed.substring(0, 5)}...'; } /// 跳转到编辑页面 void _navigateToEdit(BuildContext context) { Navigator.pushNamed(context, '/note-form', arguments: widget.note).then((_) { context.read().loadNotes(); }); } /// 显示删除对话框 void _showDeleteDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Colors.white, elevation: 0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), title: const Text('确认删除'), content: const Text('确定要删除这条笔记吗?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消', style: TextStyle(color: Color(0xFF666666))), ), TextButton( onPressed: () async { await context.read().removeNote(widget.note.id); if (!mounted) return; Navigator.pop(context); Navigator.pop(context); ToastUtil.show(context, '已删除'); }, child: const Text('删除', style: TextStyle(color: Colors.red)), ), ], ), ); } }