Files
app/lib/presentation/widgets/file_breadcrumb.dart
T
gongyun b02daf1448 主要合入内容:
- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、Ctrl/Cmd+F搜索、Esc 关闭搜索、鼠标右滑返回上级。
- 移动端同步状态页、同步统计卡片、同步任务/累计统计事件、同步页布局更新。
- Android 相册同步默认路径改为 DCIM/Camera,下载路径改用 external_path。
- Rust sync-core 更新:Android 日志依赖、AlbumUpload/AlbumDownload、MirrorWcf 统计修复、Linux FUSE镜像挂载与读写同步支持。

- 依赖更新:external_path、fl_chart、pdfrx 升级,并修了 pdfrx 新版本的deprecated API。
- 新增文件包括:sort_options.dart、sync_page_android.dart、sync_stats_card.dart、platform/fuse.rs、sync_engine/fuse.rs。
2026-06-04 07:11:43 +08:00

146 lines
4.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../core/utils/file_utils.dart';
/// 面包屑导航组件
class FileBreadcrumb extends StatefulWidget {
final String currentPath;
final void Function(String path) onPathTap;
const FileBreadcrumb({
super.key,
required this.currentPath,
required this.onPathTap,
});
@override
State<FileBreadcrumb> createState() => _FileBreadcrumbState();
}
class _FileBreadcrumbState extends State<FileBreadcrumb> {
final _controller = ScrollController();
@override
void didUpdateWidget(covariant FileBreadcrumb oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.currentPath != widget.currentPath) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_controller.hasClients) {
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
});
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final pathParts = widget.currentPath.split('/');
pathParts.removeWhere((part) => part.isEmpty);
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: theme.scaffoldBackgroundColor,
border: Border(
top: BorderSide(
color: theme.dividerColor.withValues(alpha: 0.5),
width: 1,
),
),
),
child: SingleChildScrollView(
controller: _controller,
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildBreadcrumbItem(
context,
name: '首页',
path: '/',
icon: LucideIcons.home,
primaryColor: colorScheme.primary,
onTap: () => widget.onPathTap('/'),
),
for (int i = 0; i < pathParts.length; i++) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
LucideIcons.chevronRight,
size: 16,
color: theme.hintColor.withValues(alpha: 0.5),
),
),
_buildBreadcrumbItem(
context,
name: _decodePathSegment(pathParts[i]),
path: '/${pathParts.sublist(0, i + 1).join('/')}',
icon: null,
primaryColor: colorScheme.primary,
onTap: () => widget.onPathTap(
'/${pathParts.sublist(0, i + 1).join('/')}',
),
),
],
],
),
),
);
}
String _decodePathSegment(String value) {
return FileUtils.safeDecodePathSegment(value);
}
Widget _buildBreadcrumbItem(
BuildContext context, {
required String name,
required String path,
required IconData? icon,
required Color primaryColor,
required VoidCallback onTap,
}) {
return Container(
height: 36,
decoration: BoxDecoration(
color: primaryColor.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(18),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(18),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
child: Row(
children: [
if (icon != null) Icon(icon, size: 16, color: primaryColor),
if (icon != null) const SizedBox(width: 5),
Text(
name,
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
),
),
),
);
}
}