Files
MookNote/lib/widgets/note_plus_list_item.dart
2026-06-26 05:53:40 +08:00

162 lines
4.4 KiB
Dart

import 'package:flutter/material.dart';
import '../../models/note_plus_models.dart';
/// AppFlowy 风格的文件树列表项
///
/// 匹配 AppFlowy ViewSectionItem 的样式:
/// - 固定高度 26px
/// - 16px 图标 + 文字
/// - 12px 字号
/// - hover 时显示操作按钮
class NotePlusTreeItem extends StatefulWidget {
final NotePlusDocument doc;
final bool isSelected;
final VoidCallback onTap;
final VoidCallback? onRename;
final VoidCallback? onDelete;
const NotePlusTreeItem({
super.key,
required this.doc,
required this.isSelected,
required this.onTap,
this.onRename,
this.onDelete,
});
@override
State<NotePlusTreeItem> createState() => _NotePlusTreeItemState();
}
class _NotePlusTreeItemState extends State<NotePlusTreeItem> {
bool _isHovering = false;
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final isSelected = widget.isSelected;
final showActions = _isHovering || isSelected;
return MouseRegion(
onEnter: (_) => setState(() => _isHovering = true),
onExit: (_) => setState(() => _isHovering = false),
child: GestureDetector(
onTap: widget.onTap,
child: Container(
height: 26,
padding: const EdgeInsets.only(left: 22, right: 6),
decoration: BoxDecoration(
color: isSelected
? colors.primary.withValues(alpha: 0.08)
: _isHovering
? colors.onSurface.withValues(alpha: 0.04)
: null,
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
// 文档图标
SizedBox(
width: 16,
height: 16,
child: Icon(
_getBlockIcon(widget.doc),
size: 14,
color: isSelected
? colors.primary
: colors.onSurface.withValues(alpha: 0.4),
),
),
const SizedBox(width: 2),
// 文档标题
Expanded(
child: Text(
widget.doc.title.isEmpty ? '无标题' : widget.doc.title,
style: TextStyle(
fontSize: 12,
fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400,
color: isSelected
? colors.primary
: colors.onSurface.withValues(alpha: 0.75),
),
overflow: TextOverflow.clip,
maxLines: 1,
),
),
// hover 操作按钮
if (showActions)
_buildActions(colors),
],
),
),
),
);
}
Widget _buildActions(ColorScheme colors) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.onRename != null)
_ActionButton(
icon: Icons.edit_outlined,
onTap: widget.onRename!,
colors: colors,
),
if (widget.onDelete != null)
_ActionButton(
icon: Icons.delete_outline,
onTap: widget.onDelete!,
colors: colors,
),
],
);
}
IconData _getBlockIcon(NotePlusDocument doc) {
// 根据第一个块的类型显示图标
if (doc.blocks.isEmpty) return Icons.description_outlined;
final firstType = doc.blocks.first.type;
switch (firstType) {
case NoteBlockType.heading1:
case NoteBlockType.heading2:
case NoteBlockType.heading3:
return Icons.title;
case NoteBlockType.checklist:
return Icons.check_box_outlined;
case NoteBlockType.codeBlock:
return Icons.code;
case NoteBlockType.quote:
return Icons.format_quote;
default:
return Icons.description_outlined;
}
}
}
class _ActionButton extends StatelessWidget {
final IconData icon;
final VoidCallback onTap;
final ColorScheme colors;
const _ActionButton({
required this.icon,
required this.onTap,
required this.colors,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 20,
height: 20,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(4),
child: Icon(icon, size: 14,
color: colors.onSurface.withValues(alpha: 0.4)),
),
);
}
}