696 lines
19 KiB
Dart
696 lines
19 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../providers/folder_provider.dart';
|
|
import 'custom_button.dart';
|
|
|
|
/// 文件夹选择器弹窗 - 用于选择保存图片的目标文件夹
|
|
/// 支持文件夹列表显示、新建文件夹、最近使用排序等功能
|
|
class FolderSelectorDialog extends ConsumerStatefulWidget {
|
|
/// 文件夹选择回调
|
|
final Function(String folderId) onFolderSelected;
|
|
|
|
/// 当前选中的文件夹ID
|
|
final String? currentFolderId;
|
|
|
|
const FolderSelectorDialog({
|
|
Key? key,
|
|
required this.onFolderSelected,
|
|
this.currentFolderId,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<FolderSelectorDialog> createState() => _FolderSelectorDialogState();
|
|
}
|
|
|
|
class _FolderSelectorDialogState extends ConsumerState<FolderSelectorDialog> {
|
|
/// 搜索控制器
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
/// 搜索关键词
|
|
String searchKeyword = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// 加载文件夹列表
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
ref.read(folderProvider.notifier).loadFolders();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final colorScheme = theme.colorScheme;
|
|
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Container(
|
|
width: 480,
|
|
height: 600,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16),
|
|
color: theme.scaffoldBackgroundColor,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// 标题栏
|
|
_buildHeader(colorScheme),
|
|
|
|
// 搜索框
|
|
_buildSearchBar(colorScheme),
|
|
|
|
// 文件夹列表
|
|
Expanded(
|
|
child: _buildFolderList(colorScheme),
|
|
),
|
|
|
|
// 底部操作栏
|
|
_buildFooter(colorScheme),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建标题栏
|
|
Widget _buildHeader(ColorScheme colorScheme) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surface,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(16),
|
|
topRight: Radius.circular(16),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.folder,
|
|
color: colorScheme.primary,
|
|
size: 28,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'选择文件夹',
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
IconButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
icon: const Icon(Icons.close),
|
|
tooltip: '关闭',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建搜索框
|
|
Widget _buildSearchBar(ColorScheme colorScheme) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
child: TextField(
|
|
controller: _searchController,
|
|
decoration: InputDecoration(
|
|
hintText: '搜索文件夹...',
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: searchKeyword.isNotEmpty
|
|
? IconButton(
|
|
onPressed: () {
|
|
_searchController.clear();
|
|
setState(() {
|
|
searchKeyword = '';
|
|
});
|
|
},
|
|
icon: const Icon(Icons.clear),
|
|
)
|
|
: null,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 12,
|
|
),
|
|
),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
searchKeyword = value;
|
|
});
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建文件夹列表
|
|
Widget _buildFolderList(ColorScheme colorScheme) {
|
|
final folderState = ref.watch(folderProvider);
|
|
|
|
if (folderState.isLoading) {
|
|
return const Center(
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
}
|
|
|
|
if (folderState.error != null) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.error_outline,
|
|
size: 64,
|
|
color: colorScheme.error,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'加载文件夹失败',
|
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
color: colorScheme.error,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
CustomButton(
|
|
text: '重试',
|
|
onPressed: () {
|
|
ref.read(folderProvider.notifier).loadFolders();
|
|
},
|
|
buttonType: ButtonType.outlined,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
final folders = folderState.folders;
|
|
|
|
if (folders.isEmpty) {
|
|
return _buildEmptyState(colorScheme);
|
|
}
|
|
|
|
// 过滤文件夹
|
|
final filteredFolders = searchKeyword.isEmpty
|
|
? folders
|
|
: folders.where((folder) =>
|
|
folder.name.toLowerCase().contains(searchKeyword.toLowerCase())).toList();
|
|
|
|
if (filteredFolders.isEmpty) {
|
|
return _buildNoSearchResults(colorScheme);
|
|
}
|
|
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
itemCount: filteredFolders.length,
|
|
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
|
itemBuilder: (context, index) {
|
|
final folder = filteredFolders[index];
|
|
final isSelected = folder.id == widget.currentFolderId;
|
|
|
|
return _buildFolderItem(folder, isSelected, colorScheme);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 构建空状态
|
|
Widget _buildEmptyState(ColorScheme colorScheme) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.folder_outlined,
|
|
size: 64,
|
|
color: colorScheme.outline,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'还没有文件夹',
|
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
color: colorScheme.outline,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'创建第一个文件夹来开始整理你的灵感',
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: colorScheme.outline.withOpacity(0.7),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
CustomButton(
|
|
text: '创建文件夹',
|
|
onPressed: _showCreateFolderDialog,
|
|
prefixIcon: Icons.add,
|
|
buttonType: ButtonType.primary,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建无搜索结果状态
|
|
Widget _buildNoSearchResults(ColorScheme colorScheme) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.search_off,
|
|
size: 64,
|
|
color: colorScheme.outline,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'未找到匹配的文件夹',
|
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
color: colorScheme.outline,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'尝试使用其他关键词搜索',
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: colorScheme.outline.withOpacity(0.7),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建文件夹项
|
|
Widget _buildFolderItem(
|
|
dynamic folder,
|
|
bool isSelected,
|
|
ColorScheme colorScheme,
|
|
) {
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(12),
|
|
onTap: () {
|
|
widget.onFolderSelected(folder.id);
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? colorScheme.primary
|
|
: colorScheme.outline.withOpacity(0.2),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
color: isSelected
|
|
? colorScheme.primary.withOpacity(0.1)
|
|
: colorScheme.surface,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
// 文件夹图标
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? colorScheme.primary.withOpacity(0.2)
|
|
: colorScheme.primaryContainer.withOpacity(0.5),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
_getFolderIcon(folder.icon),
|
|
color: isSelected
|
|
? colorScheme.primary
|
|
: colorScheme.primary,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
|
|
// 文件夹信息
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
folder.name,
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: isSelected
|
|
? colorScheme.primary
|
|
: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${folder.imageCount} 张图片',
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: colorScheme.onSurface.withOpacity(0.6),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 选择标记
|
|
if (isSelected)
|
|
Icon(
|
|
Icons.check_circle,
|
|
color: colorScheme.primary,
|
|
size: 24,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建底部操作栏
|
|
Widget _buildFooter(ColorScheme colorScheme) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surface,
|
|
borderRadius: const BorderRadius.only(
|
|
bottomLeft: Radius.circular(16),
|
|
bottomRight: Radius.circular(16),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
// 创建新文件夹按钮
|
|
Expanded(
|
|
child: CustomButton(
|
|
text: '新建文件夹',
|
|
onPressed: _showCreateFolderDialog,
|
|
prefixIcon: Icons.add,
|
|
buttonType: ButtonType.outlined,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
|
|
// 取消按钮
|
|
CustomButton(
|
|
text: '取消',
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
buttonType: ButtonType.text,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 显示创建文件夹对话框
|
|
void _showCreateFolderDialog() {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => CreateFolderDialog(
|
|
onFolderCreated: (folderId) {
|
|
Navigator.of(context).pop(); // 关闭创建对话框
|
|
widget.onFolderSelected(folderId);
|
|
Navigator.of(context).pop(); // 关闭选择器对话框
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 获取文件夹图标
|
|
IconData _getFolderIcon(String? iconCode) {
|
|
// 默认文件夹图标
|
|
if (iconCode == null || iconCode.isEmpty) {
|
|
return Icons.folder;
|
|
}
|
|
|
|
// 尝试解析Material Icons名称
|
|
try {
|
|
// 这里需要根据实际的图标存储方式来实现
|
|
// 暂时返回默认图标
|
|
return Icons.folder;
|
|
} catch (e) {
|
|
return Icons.folder;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 创建文件夹对话框
|
|
class CreateFolderDialog extends ConsumerStatefulWidget {
|
|
/// 文件夹创建成功回调
|
|
final Function(String folderId) onFolderCreated;
|
|
|
|
const CreateFolderDialog({
|
|
Key? key,
|
|
required this.onFolderCreated,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<CreateFolderDialog> createState() => _CreateFolderDialogState();
|
|
}
|
|
|
|
class _CreateFolderDialogState extends ConsumerState<CreateFolderDialog> {
|
|
/// 文件夹名称控制器
|
|
final TextEditingController _nameController = TextEditingController();
|
|
|
|
/// 选中的图标
|
|
String selectedIcon = 'folder';
|
|
|
|
/// 表单键
|
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
|
|
|
/// 是否正在创建
|
|
bool isCreating = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final colorScheme = theme.colorScheme;
|
|
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Container(
|
|
width: 400,
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16),
|
|
color: theme.scaffoldBackgroundColor,
|
|
),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 标题
|
|
Text(
|
|
'新建文件夹',
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// 文件夹名称输入
|
|
TextFormField(
|
|
controller: _nameController,
|
|
decoration: const InputDecoration(
|
|
labelText: '文件夹名称',
|
|
hintText: '输入文件夹名称',
|
|
border: OutlineInputBorder(),
|
|
prefixIcon: Icon(Icons.folder),
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return '请输入文件夹名称';
|
|
}
|
|
if (value.trim().length > 20) {
|
|
return '文件夹名称不能超过20个字符';
|
|
}
|
|
return null;
|
|
},
|
|
textInputAction: TextInputAction.done,
|
|
onFieldSubmitted: (_) => _createFolder(),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// 图标选择
|
|
Text(
|
|
'选择图标',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 12),
|
|
_buildIconSelector(colorScheme),
|
|
const SizedBox(height: 24),
|
|
|
|
// 操作按钮
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
// 取消按钮
|
|
CustomButton(
|
|
text: '取消',
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
buttonType: ButtonType.text,
|
|
),
|
|
const SizedBox(width: 16),
|
|
|
|
// 创建按钮
|
|
CustomButton(
|
|
text: '创建',
|
|
onPressed: isCreating ? null : _createFolder,
|
|
buttonType: ButtonType.primary,
|
|
isLoading: isCreating,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 构建图标选择器
|
|
Widget _buildIconSelector(ColorScheme colorScheme) {
|
|
final icons = [
|
|
'folder',
|
|
'bookmark',
|
|
'favorite',
|
|
'star',
|
|
'work',
|
|
'home',
|
|
'travel_explore',
|
|
'restaurant',
|
|
'shopping_bag',
|
|
'sports_esports',
|
|
];
|
|
|
|
return Wrap(
|
|
spacing: 12,
|
|
runSpacing: 12,
|
|
children: icons.map((icon) {
|
|
final isSelected = selectedIcon == icon;
|
|
return GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
selectedIcon = icon;
|
|
});
|
|
},
|
|
child: Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? colorScheme.primary.withOpacity(0.2)
|
|
: colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? colorScheme.primary
|
|
: colorScheme.outline.withOpacity(0.2),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Icon(
|
|
_getIconData(icon),
|
|
color: isSelected
|
|
? colorScheme.primary
|
|
: colorScheme.onSurface,
|
|
size: 24,
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
/// 创建文件夹
|
|
Future<void> _createFolder() async {
|
|
if (!_formKey.currentState!.validate()) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
isCreating = true;
|
|
});
|
|
|
|
try {
|
|
final folderId = await ref.read(folderProvider.notifier).createFolder(
|
|
name: _nameController.text.trim(),
|
|
icon: selectedIcon,
|
|
);
|
|
|
|
if (folderId != null) {
|
|
widget.onFolderCreated(folderId);
|
|
} else {
|
|
_showErrorSnackBar('创建文件夹失败');
|
|
}
|
|
} catch (e) {
|
|
_showErrorSnackBar('创建文件夹失败:${e.toString()}');
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
isCreating = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 显示错误提示
|
|
void _showErrorSnackBar(String message) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(message),
|
|
backgroundColor: Theme.of(context).colorScheme.error,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 获取图标数据
|
|
IconData _getIconData(String iconName) {
|
|
switch (iconName) {
|
|
case 'folder':
|
|
return Icons.folder;
|
|
case 'bookmark':
|
|
return Icons.bookmark;
|
|
case 'favorite':
|
|
return Icons.favorite;
|
|
case 'star':
|
|
return Icons.star;
|
|
case 'work':
|
|
return Icons.work;
|
|
case 'home':
|
|
return Icons.home;
|
|
case 'travel_explore':
|
|
return Icons.travel_explore;
|
|
case 'restaurant':
|
|
return Icons.restaurant;
|
|
case 'shopping_bag':
|
|
return Icons.shopping_bag;
|
|
case 'sports_esports':
|
|
return Icons.sports_esports;
|
|
default:
|
|
return Icons.folder;
|
|
}
|
|
}
|
|
} |