chore: initialize Guangya Flutter project
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../models/cloud_file.dart';
|
||||
|
||||
class BreadcrumbBar extends StatelessWidget {
|
||||
final List<CloudFile> path;
|
||||
final ValueChanged<int> onNavigate;
|
||||
|
||||
const BreadcrumbBar({
|
||||
super.key,
|
||||
required this.path,
|
||||
required this.onNavigate,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
|
||||
final items = <Widget>[
|
||||
// Home button
|
||||
GestureDetector(
|
||||
onTap: () => onNavigate(-1),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: path.isEmpty ? cs.primary.withAlpha(15) : null,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'云盘',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: path.isEmpty ? FontWeight.w600 : FontWeight.normal,
|
||||
color: path.isEmpty ? cs.primary : cs.mutedForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
for (int i = 0; i < path.length; i++) {
|
||||
final isLast = i == path.length - 1;
|
||||
items.add(
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
size: 16,
|
||||
color: cs.mutedForeground.withAlpha(100),
|
||||
),
|
||||
);
|
||||
items.add(
|
||||
GestureDetector(
|
||||
onTap: isLast ? null : () => onNavigate(i),
|
||||
child: MouseRegion(
|
||||
cursor: isLast ? SystemMouseCursors.basic : SystemMouseCursors.click,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isLast ? cs.primary.withAlpha(15) : null,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
path[i].name,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: isLast ? FontWeight.w600 : FontWeight.normal,
|
||||
color: isLast ? cs.primary : cs.mutedForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(children: items),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
Future<bool> showConfirmDialog(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String content,
|
||||
String confirmText = '确认',
|
||||
String cancelText = '取消',
|
||||
}) async {
|
||||
final result = await showShadDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => ShadDialog(
|
||||
title: Text(title),
|
||||
description: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(content),
|
||||
),
|
||||
actions: [
|
||||
ShadButton.outline(
|
||||
child: Text(cancelText),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
ShadButton(
|
||||
child: Text(confirmText),
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../models/cloud_file.dart';
|
||||
|
||||
class FileIcon extends StatelessWidget {
|
||||
final CloudFile file;
|
||||
final double size;
|
||||
|
||||
const FileIcon({
|
||||
super.key,
|
||||
required this.file,
|
||||
this.size = 28,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (file.isDirectory) {
|
||||
return Icon(
|
||||
LucideIcons.folder,
|
||||
size: size,
|
||||
color: const Color(0xFFF59E0B),
|
||||
);
|
||||
}
|
||||
|
||||
final iconData = _getIconForFileType(file.fileType);
|
||||
final color = _getColorForFileType(file.fileType);
|
||||
|
||||
return Icon(
|
||||
iconData,
|
||||
size: size,
|
||||
color: color,
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getIconForFileType(int fileType) {
|
||||
switch (fileType) {
|
||||
case 1:
|
||||
return LucideIcons.image;
|
||||
case 2:
|
||||
return LucideIcons.film;
|
||||
case 3:
|
||||
return LucideIcons.music;
|
||||
case 4:
|
||||
return LucideIcons.fileText;
|
||||
case 5:
|
||||
case 9:
|
||||
return LucideIcons.archive;
|
||||
default:
|
||||
return LucideIcons.file;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getColorForFileType(int fileType) {
|
||||
switch (fileType) {
|
||||
case 1:
|
||||
return const Color(0xFF10B981);
|
||||
case 2:
|
||||
return const Color(0xFF3B82F6);
|
||||
case 3:
|
||||
return const Color(0xFFF59E0B);
|
||||
case 4:
|
||||
return const Color(0xFF8B5CF6);
|
||||
case 5:
|
||||
case 9:
|
||||
return const Color(0xFF6B7280);
|
||||
default:
|
||||
return const Color(0xFF6B7280);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../models/cloud_file.dart';
|
||||
import 'file_icon.dart';
|
||||
|
||||
/// A single file row in the file list with context menu support.
|
||||
class FileListTile extends StatelessWidget {
|
||||
final CloudFile file;
|
||||
final bool isSelected;
|
||||
final VoidCallback? onSelect;
|
||||
final VoidCallback? onOpen;
|
||||
final VoidCallback? onRename;
|
||||
final VoidCallback? onCopy;
|
||||
final VoidCallback? onCut;
|
||||
final VoidCallback? onDownload;
|
||||
final VoidCallback? onShare;
|
||||
final VoidCallback? onDelete;
|
||||
|
||||
const FileListTile({
|
||||
super.key,
|
||||
required this.file,
|
||||
this.isSelected = false,
|
||||
this.onSelect,
|
||||
this.onOpen,
|
||||
this.onRename,
|
||||
this.onCopy,
|
||||
this.onCut,
|
||||
this.onDownload,
|
||||
this.onShare,
|
||||
this.onDelete,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
|
||||
return ShadContextMenuRegion(
|
||||
items: [
|
||||
ShadContextMenuItem.inset(
|
||||
leading: const Icon(LucideIcons.folderOpen, size: 16),
|
||||
trailing: const Icon(LucideIcons.chevronRight),
|
||||
onPressed: onOpen,
|
||||
child: const Text('打开'),
|
||||
),
|
||||
const ShadContextMenuItem.inset(
|
||||
enabled: false,
|
||||
child: Text('重命名'),
|
||||
),
|
||||
ShadContextMenuItem.inset(
|
||||
leading: const Icon(LucideIcons.copy, size: 16),
|
||||
trailing: const Icon(LucideIcons.chevronRight),
|
||||
onPressed: onCopy,
|
||||
child: const Text('复制'),
|
||||
),
|
||||
ShadContextMenuItem.inset(
|
||||
leading: const Icon(LucideIcons.scissors, size: 16),
|
||||
trailing: const Icon(LucideIcons.chevronRight),
|
||||
onPressed: onCut,
|
||||
child: const Text('剪切'),
|
||||
),
|
||||
const Divider(height: 8),
|
||||
ShadContextMenuItem.inset(
|
||||
leading: const Icon(LucideIcons.download, size: 16),
|
||||
trailing: const Icon(LucideIcons.chevronRight),
|
||||
onPressed: onDownload,
|
||||
child: const Text('下载'),
|
||||
),
|
||||
ShadContextMenuItem.inset(
|
||||
leading: const Icon(LucideIcons.share2, size: 16),
|
||||
trailing: const Icon(LucideIcons.chevronRight),
|
||||
onPressed: onShare,
|
||||
child: const Text('分享'),
|
||||
),
|
||||
const Divider(height: 8),
|
||||
ShadContextMenuItem.inset(
|
||||
leading: Icon(LucideIcons.trash2, size: 16, color: theme.colorScheme.destructive),
|
||||
trailing: Icon(LucideIcons.chevronRight, color: theme.colorScheme.destructive),
|
||||
onPressed: onDelete,
|
||||
child: Text(
|
||||
'删除',
|
||||
style: TextStyle(color: theme.colorScheme.destructive),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: GestureDetector(
|
||||
onTap: () => onSelect?.call(),
|
||||
onDoubleTap: onOpen,
|
||||
child: Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? (isDark
|
||||
? theme.colorScheme.primary.withAlpha(30)
|
||||
: theme.colorScheme.primary.withAlpha(15))
|
||||
: (isDark
|
||||
? Colors.white.withAlpha(3)
|
||||
: Colors.black.withAlpha(2)),
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: isDark
|
||||
? Colors.white.withAlpha(10)
|
||||
: Colors.black.withAlpha(8),
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Selection indicator
|
||||
if (isSelected)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Icon(
|
||||
LucideIcons.checkCircle,
|
||||
size: 18,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
|
||||
// File icon
|
||||
SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: FileIcon(file: file),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// File name
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
file.name,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (file.isDirectory && file.subFileCount != null)
|
||||
Text(
|
||||
'${file.subFileCount} 个项目',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// File size
|
||||
if (!file.isDirectory)
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
file.formattedSize,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Modified date
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
file.modifiedAt,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../providers/file_provider.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
import '../models/cloud_file.dart';
|
||||
|
||||
class SidePanel extends ConsumerWidget {
|
||||
const SidePanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return Container(
|
||||
width: 280,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.card,
|
||||
border: Border(left: BorderSide(color: theme.colorScheme.border)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(LucideIcons.info, size: 18, color: theme.colorScheme.foreground),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'详情',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const ShadSeparator.horizontal(),
|
||||
Expanded(child: _buildContent(context, ref)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, WidgetRef ref) {
|
||||
final fileState = ref.watch(fileProvider);
|
||||
final selected = fileState.files
|
||||
.where((f) => fileState.selectedIDs.contains(f.id))
|
||||
.toList();
|
||||
|
||||
if (selected.isEmpty) {
|
||||
return _buildDefaultContent(context, ref);
|
||||
}
|
||||
if (selected.length == 1) {
|
||||
return _buildSingleFileDetail(context, selected.first, ref);
|
||||
}
|
||||
return _buildMultiSelectDetail(context, selected, ref);
|
||||
}
|
||||
|
||||
Widget _buildDefaultContent(BuildContext context, WidgetRef ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
final fileState = ref.watch(fileProvider);
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_sectionTitle(context, '账号信息'),
|
||||
const SizedBox(height: 12),
|
||||
_infoRow(context, '用户名', auth.userName),
|
||||
_infoRow(context, '会员等级', auth.memberLevel),
|
||||
_infoRow(context, '存储空间', auth.capacityText),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
if (fileState.section == WorkspaceSection.recycle) ...[
|
||||
_sectionTitle(context, '回收站'),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ShadButton.outline(
|
||||
onPressed: () {
|
||||
showShadDialog(
|
||||
context: context,
|
||||
builder: (context) => ShadDialog(
|
||||
title: const Text('清空回收站'),
|
||||
description: const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text('确定要永久删除回收站中的所有文件吗?此操作不可恢复。'),
|
||||
),
|
||||
actions: [
|
||||
ShadButton.outline(
|
||||
child: const Text('取消'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
ShadButton(
|
||||
child: const Text('清空'),
|
||||
onPressed: () {
|
||||
ref.read(fileProvider.notifier).clearRecycleBin();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
leading: const Icon(LucideIcons.trash2, size: 16),
|
||||
child: const Text('清空回收站'),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSingleFileDetail(
|
||||
BuildContext context, CloudFile file, WidgetRef ref) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
file.isDirectory ? LucideIcons.folder : LucideIcons.file,
|
||||
size: 64,
|
||||
color: file.isDirectory
|
||||
? const Color(0xFFF59E0B)
|
||||
: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
file.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_sectionTitle(context, '文件信息'),
|
||||
const SizedBox(height: 12),
|
||||
_infoRow(context, '类型', file.typeName),
|
||||
if (!file.isDirectory) _infoRow(context, '大小', file.formattedSize),
|
||||
if (file.modifiedAt.isNotEmpty)
|
||||
_infoRow(context, '修改时间', file.modifiedAt),
|
||||
const SizedBox(height: 24),
|
||||
_sectionTitle(context, '操作'),
|
||||
const SizedBox(height: 12),
|
||||
_buildActionButtons(context, file, ref),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMultiSelectDetail(
|
||||
BuildContext context, List<CloudFile> files, WidgetRef ref) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.checkCircle,
|
||||
size: 48,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'已选择 ${files.length} 个项目',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_sectionTitle(context, '批量操作'),
|
||||
const SizedBox(height: 12),
|
||||
_buildActionButtons(context, files.first, ref),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtons(
|
||||
BuildContext context, CloudFile file, WidgetRef ref) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final fp = ref.read(fileProvider.notifier);
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
if (file.isDirectory)
|
||||
_actionChip(
|
||||
context,
|
||||
icon: LucideIcons.folderOpen,
|
||||
label: '打开',
|
||||
onTap: () => fp.navigateToFolder(file),
|
||||
),
|
||||
_actionChip(
|
||||
context,
|
||||
icon: LucideIcons.copy,
|
||||
label: '复制',
|
||||
onTap: () => fp.copyToClipboard([file]),
|
||||
),
|
||||
_actionChip(
|
||||
context,
|
||||
icon: LucideIcons.scissors,
|
||||
label: '剪切',
|
||||
onTap: () => fp.cutToClipboard([file]),
|
||||
),
|
||||
_actionChip(
|
||||
context,
|
||||
icon: LucideIcons.download,
|
||||
label: '下载',
|
||||
onTap: () => fp.downloadFile(file),
|
||||
),
|
||||
_actionChip(
|
||||
context,
|
||||
icon: LucideIcons.share2,
|
||||
label: '分享',
|
||||
onTap: () {},
|
||||
),
|
||||
_actionChip(
|
||||
context,
|
||||
icon: LucideIcons.trash2,
|
||||
label: '删除',
|
||||
color: theme.colorScheme.destructive,
|
||||
onTap: () => fp.deleteFiles([file]),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionChip(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String label,
|
||||
Color? color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ShadButton.outline(
|
||||
onPressed: onTap,
|
||||
leading: Icon(icon, size: 14, color: color),
|
||||
child: Text(label, style: TextStyle(fontSize: 12, color: color)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sectionTitle(BuildContext context, String title) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.mutedForeground,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoRow(BuildContext context, String label, String value) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 13, color: theme.colorScheme.mutedForeground),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(fontSize: 13, color: theme.colorScheme.foreground),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../providers/file_provider.dart';
|
||||
|
||||
class SortMenu extends StatelessWidget {
|
||||
final FileSort currentSort;
|
||||
final SortDirection currentDirection;
|
||||
final ValueChanged<FileSort> onSortChanged;
|
||||
|
||||
const SortMenu({
|
||||
super.key,
|
||||
required this.currentSort,
|
||||
required this.currentDirection,
|
||||
required this.onSortChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
|
||||
return ShadButton.ghost(
|
||||
onPressed: () => _showSortMenu(context),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.sort_rounded, size: 16, color: cs.mutedForeground),
|
||||
const SizedBox(width: 4),
|
||||
Text('排序', style: TextStyle(fontSize: 13, color: cs.mutedForeground)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSortMenu(BuildContext context) {
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) => Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('排序方式',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: cs.foreground)),
|
||||
const Spacer(),
|
||||
Text('当前: ${currentSort.title}',
|
||||
style: TextStyle(fontSize: 12, color: cs.mutedForeground)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const ShadSeparator.horizontal(),
|
||||
for (final sort in FileSort.values)
|
||||
ListTile(
|
||||
dense: true,
|
||||
leading: currentSort == sort
|
||||
? Icon(Icons.check_rounded, size: 16, color: cs.primary)
|
||||
: const SizedBox(width: 16),
|
||||
title: Text(
|
||||
sort.title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: currentSort == sort ? FontWeight.w600 : FontWeight.normal,
|
||||
color: currentSort == sort ? cs.primary : cs.foreground,
|
||||
),
|
||||
),
|
||||
trailing: currentSort == sort
|
||||
? Icon(
|
||||
currentDirection == SortDirection.ascending
|
||||
? Icons.arrow_upward_rounded
|
||||
: Icons.arrow_downward_rounded,
|
||||
size: 14,
|
||||
color: cs.primary,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
Navigator.of(ctx).pop();
|
||||
onSortChanged(sort);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user