731 lines
24 KiB
Dart
731 lines
24 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
import '../../../data/models/share_model.dart';
|
|
import '../../../services/share_service.dart';
|
|
import '../../../core/utils/file_type_utils.dart';
|
|
import '../../widgets/toast_helper.dart';
|
|
|
|
class SharesPage extends StatefulWidget {
|
|
const SharesPage({super.key});
|
|
|
|
@override
|
|
State<SharesPage> createState() => _SharesPageState();
|
|
}
|
|
|
|
class _SharesPageState extends State<SharesPage> {
|
|
List<ShareModel> _shares = [];
|
|
bool _isLoading = false;
|
|
bool _hasMore = true;
|
|
String? _errorMessage;
|
|
String? _nextPageToken;
|
|
late ScrollController _scrollController;
|
|
static const _sharesPageListSize = 20;
|
|
|
|
String _searchQuery = '';
|
|
final TextEditingController _searchController = TextEditingController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_scrollController = ScrollController()..addListener(_onScroll);
|
|
_loadShares();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollController.dispose();
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onScroll() {
|
|
if (_scrollController.position.pixels >=
|
|
_scrollController.position.maxScrollExtent - 200) {
|
|
_loadMoreShares();
|
|
}
|
|
}
|
|
|
|
Future<bool> _loadShares({bool isLoadMore = false}) async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
_errorMessage = null;
|
|
});
|
|
|
|
try {
|
|
final response = await ShareService().listShares(
|
|
pageSize: _sharesPageListSize,
|
|
nextPageToken: isLoadMore ? _nextPageToken : null,
|
|
);
|
|
|
|
final List<dynamic> sharesData =
|
|
response['shares'] as List<dynamic>? ?? [];
|
|
final pagination = response['pagination'] as Map<String, dynamic>? ?? {};
|
|
final newShares = sharesData
|
|
.map((s) => ShareModel.fromJson(s as Map<String, dynamic>))
|
|
.toList();
|
|
|
|
setState(() {
|
|
_isLoading = false;
|
|
if (isLoadMore) {
|
|
_shares.addAll(newShares);
|
|
} else {
|
|
_shares = newShares;
|
|
}
|
|
_nextPageToken = pagination['next_token'] as String?;
|
|
_hasMore = _nextPageToken != null;
|
|
});
|
|
return true;
|
|
} catch (e) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
_errorMessage = e.toString();
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> _loadMoreShares() async {
|
|
if (!_hasMore || _isLoading) return;
|
|
await _loadShares(isLoadMore: true);
|
|
}
|
|
|
|
Future<void> _refreshShares() async {
|
|
final success = await _loadShares(isLoadMore: false);
|
|
if (mounted) {
|
|
if (success) {
|
|
ToastHelper.success('刷新成功');
|
|
} else {
|
|
ToastHelper.failure('刷新失败');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _deleteShare(ShareModel share) async {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (dialogContext) => AlertDialog(
|
|
title: const Text('删除分享'),
|
|
content: Text('确定删除分享 "${share.name}" 吗?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(dialogContext).pop(false),
|
|
child: const Text('取消'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () => Navigator.of(dialogContext).pop(true),
|
|
style: FilledButton.styleFrom(backgroundColor: colorScheme.error),
|
|
child: const Text('删除'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true) {
|
|
setState(() => _isLoading = true);
|
|
try {
|
|
await ShareService().deleteShare(id: share.id);
|
|
setState(() => _shares.remove(share));
|
|
if (mounted) ToastHelper.success('删除成功');
|
|
} catch (e) {
|
|
setState(() => _isLoading = false);
|
|
if (mounted) ToastHelper.failure('删除失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _editShare(ShareModel share) async {
|
|
final parts = share.url.split('/');
|
|
if (parts.length < 5) {
|
|
if (mounted) ToastHelper.error('分享链接格式错误');
|
|
return;
|
|
}
|
|
final shareId = parts[4];
|
|
|
|
final expireDaysController = TextEditingController(text: '7');
|
|
final downloadsController = TextEditingController();
|
|
|
|
final edited = await showDialog<bool>(
|
|
context: context,
|
|
builder: (dialogContext) => AlertDialog(
|
|
title: Row(
|
|
children: [
|
|
const Text('编辑分享'),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: const Icon(LucideIcons.copy),
|
|
onPressed: () {
|
|
Clipboard.setData(ClipboardData(text: share.url));
|
|
Navigator.of(dialogContext).pop();
|
|
ToastHelper.success('分享链接已复制');
|
|
},
|
|
tooltip: '复制分享链接',
|
|
),
|
|
],
|
|
),
|
|
content: SizedBox(
|
|
width: 400,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextField(
|
|
decoration: InputDecoration(
|
|
labelText: '文件名',
|
|
prefixIcon: const Icon(LucideIcons.fileText),
|
|
suffixIcon: IconButton(
|
|
icon: const Icon(LucideIcons.copy, size: 18),
|
|
onPressed: () {
|
|
Clipboard.setData(ClipboardData(text: share.name));
|
|
ToastHelper.success('文件名已复制');
|
|
},
|
|
tooltip: '复制文件名',
|
|
style: IconButton.styleFrom(
|
|
padding: EdgeInsets.zero,
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
),
|
|
),
|
|
controller: TextEditingController(text: share.name),
|
|
readOnly: true,
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: expireDaysController,
|
|
decoration: const InputDecoration(
|
|
labelText: '有效期(天)',
|
|
hintText: '留空则永久有效',
|
|
prefixIcon: Icon(LucideIcons.timer),
|
|
suffixText: '天',
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: downloadsController,
|
|
decoration: const InputDecoration(
|
|
labelText: '下载次数限制',
|
|
hintText: '留空则不限制',
|
|
prefixIcon: Icon(LucideIcons.download),
|
|
suffixText: '次',
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(dialogContext).pop(false),
|
|
child: const Text('取消'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () => Navigator.of(dialogContext).pop(true),
|
|
child: const Text('保存'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (edited == true) {
|
|
final expireDaysText = expireDaysController.text.trim();
|
|
final expireDays =
|
|
expireDaysText.isEmpty ? null : int.tryParse(expireDaysText);
|
|
final downloadsText = downloadsController.text.trim();
|
|
final downloads =
|
|
downloadsText.isEmpty ? null : int.tryParse(downloadsText);
|
|
final expireSeconds = expireDays != null ? expireDays * 24 * 60 * 60 : null;
|
|
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
final shareInfo = await ShareService().getShareInfo(
|
|
id: shareId,
|
|
password: share.password,
|
|
ownerExtended: true,
|
|
);
|
|
if (shareInfo.sourceUri == null) {
|
|
setState(() => _isLoading = false);
|
|
if (mounted) ToastHelper.error('无法获取文件信息');
|
|
return;
|
|
}
|
|
|
|
final uri = '${shareInfo.sourceUri}/${share.name}';
|
|
final newUrl = await ShareService().editShare(
|
|
id: shareId,
|
|
uri: uri,
|
|
isPrivate: share.isPrivate,
|
|
password: share.password,
|
|
shareView: share.shareView,
|
|
downloads: downloads,
|
|
expire: expireSeconds,
|
|
);
|
|
|
|
setState(() => _isLoading = false);
|
|
if (mounted) await _loadShares();
|
|
if (mounted) {
|
|
ToastHelper.success('修改成功');
|
|
showDialog(
|
|
context: context,
|
|
builder: (dialogContext) => AlertDialog(
|
|
title: const Text('分享链接'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(newUrl, style: const TextStyle(fontSize: 12)),
|
|
const SizedBox(height: 16),
|
|
FilledButton.icon(
|
|
icon: const Icon(LucideIcons.copy, size: 16),
|
|
label: const Text('复制到剪贴板'),
|
|
onPressed: () {
|
|
Clipboard.setData(ClipboardData(text: newUrl));
|
|
Navigator.of(dialogContext).pop();
|
|
ToastHelper.success('已复制到剪贴板');
|
|
},
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
|
child: const Text('关闭'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
setState(() => _isLoading = false);
|
|
if (mounted) ToastHelper.failure('修改失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('我的分享'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(LucideIcons.refreshCw),
|
|
onPressed: _refreshShares,
|
|
tooltip: '刷新',
|
|
),
|
|
],
|
|
),
|
|
body: Column(
|
|
children: [
|
|
_buildSearchBar(),
|
|
Expanded(child: _buildBody()),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSearchBar() {
|
|
final theme = Theme.of(context);
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
controller: _searchController,
|
|
decoration: InputDecoration(
|
|
hintText: '搜索分享内容...',
|
|
prefixIcon: const Icon(LucideIcons.search, size: 20),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
filled: true,
|
|
fillColor: theme.colorScheme.surfaceContainerHighest
|
|
.withValues(alpha: 0.5),
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 8),
|
|
isDense: true,
|
|
),
|
|
onChanged: (value) {
|
|
_searchQuery = value.toLowerCase();
|
|
setState(() {});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBody() {
|
|
final filteredShares = _searchQuery.isEmpty
|
|
? _shares
|
|
: _shares
|
|
.where((s) =>
|
|
s.name.toLowerCase().contains(_searchQuery) ||
|
|
s.url.toLowerCase().contains(_searchQuery))
|
|
.toList();
|
|
|
|
if (_isLoading && _shares.isEmpty) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () => _loadShares(isLoadMore: false),
|
|
child: _errorMessage != null
|
|
? _buildErrorState()
|
|
: filteredShares.isEmpty
|
|
? (_searchQuery.isEmpty ? _buildEmptyState() : _buildNoSearchResult())
|
|
: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final isDesktop = constraints.maxWidth >= 800;
|
|
return isDesktop
|
|
? _buildDesktopLayout(filteredShares)
|
|
: _buildMobileLayout(filteredShares);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// ─── 桌面端布局 ───
|
|
|
|
Widget _buildDesktopLayout(List<ShareModel> shares) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return SingleChildScrollView(
|
|
controller: _scrollController,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
child: Card(
|
|
margin: EdgeInsets.zero,
|
|
clipBehavior: Clip.antiAlias,
|
|
child: DataTable(
|
|
headingRowColor:
|
|
WidgetStateProperty.all(colorScheme.surfaceContainerHighest),
|
|
columnSpacing: 24,
|
|
columns: const [
|
|
DataColumn(label: Text('文件名', style: TextStyle(fontWeight: FontWeight.bold))),
|
|
DataColumn(label: Text('类型', style: TextStyle(fontWeight: FontWeight.bold))),
|
|
DataColumn(label: Text('浏览/下载', style: TextStyle(fontWeight: FontWeight.bold))),
|
|
DataColumn(label: Text('状态', style: TextStyle(fontWeight: FontWeight.bold))),
|
|
DataColumn(label: Text('创建时间', style: TextStyle(fontWeight: FontWeight.bold))),
|
|
DataColumn(label: Text('操作', style: TextStyle(fontWeight: FontWeight.bold))),
|
|
],
|
|
rows: shares.map((share) {
|
|
return DataRow(
|
|
cells: [
|
|
DataCell(
|
|
ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 300),
|
|
child: Row(
|
|
children: [
|
|
Icon(_getShareIcon(share), size: 18, color: _getIconColor(share, colorScheme)),
|
|
const SizedBox(width: 12),
|
|
Expanded(child: Text(share.name, overflow: TextOverflow.ellipsis)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
DataCell(Text(share.isFolder ? '文件夹' : '文件')),
|
|
DataCell(Text('${share.visited} / ${share.downloaded ?? 0}')),
|
|
DataCell(_buildStatusBadge(share)),
|
|
DataCell(Text(_formatDate(share.createdAt), style: const TextStyle(fontSize: 12))),
|
|
DataCell(
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_buildActionButton(
|
|
icon: LucideIcons.pencil,
|
|
tooltip: '编辑',
|
|
onPressed: () => _editShare(share),
|
|
),
|
|
_buildActionButton(
|
|
icon: LucideIcons.copy,
|
|
tooltip: '复制链接',
|
|
onPressed: () {
|
|
Clipboard.setData(ClipboardData(text: share.url));
|
|
if (mounted) ToastHelper.success('链接已复制');
|
|
},
|
|
),
|
|
_buildActionButton(
|
|
icon: LucideIcons.trash2,
|
|
tooltip: '删除',
|
|
color: colorScheme.error,
|
|
onPressed: () => _deleteShare(share),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ─── 移动端布局 ───
|
|
|
|
Widget _buildMobileLayout(List<ShareModel> shares) {
|
|
final theme = Theme.of(context);
|
|
return ListView.builder(
|
|
controller: _scrollController,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
itemCount: shares.length + (_isLoading ? 1 : 0),
|
|
itemBuilder: (context, index) {
|
|
if (index >= shares.length) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
final share = shares[index];
|
|
final iconColor = _getIconColor(share, theme.colorScheme);
|
|
return InkWell(
|
|
onTap: () => _editShare(share),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: iconColor.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(_getShareIcon(share), size: 18, color: iconColor),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
share.name,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Row(
|
|
children: [
|
|
_buildStatusBadge(share),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'浏览 ${share.visited} · 下载 ${share.downloaded ?? 0}',
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.hintColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(LucideIcons.moreVertical, size: 18),
|
|
onPressed: () => _showMobileActionMenu(share),
|
|
style: IconButton.styleFrom(
|
|
padding: const EdgeInsets.all(8),
|
|
minimumSize: const Size(36, 36),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// ─── 状态徽章 ───
|
|
|
|
Widget _buildStatusBadge(ShareModel share) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
final isExpired = share.expired;
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: isExpired
|
|
? colorScheme.error.withValues(alpha: 0.1)
|
|
: Colors.green.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Text(
|
|
isExpired ? '已过期' : '正常',
|
|
style: TextStyle(
|
|
color: isExpired ? colorScheme.error : Colors.green,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ─── 桌面端操作按钮 ───
|
|
|
|
Widget _buildActionButton({
|
|
required IconData icon,
|
|
required String tooltip,
|
|
Color? color,
|
|
required VoidCallback onPressed,
|
|
}) {
|
|
return IconButton(
|
|
icon: Icon(icon, size: 18, color: color),
|
|
onPressed: onPressed,
|
|
tooltip: tooltip,
|
|
style: IconButton.styleFrom(
|
|
padding: const EdgeInsets.all(4),
|
|
minimumSize: const Size(32, 32),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ─── 图标与颜色 ───
|
|
|
|
IconData _getShareIcon(ShareModel share) {
|
|
if (share.isFolder) return LucideIcons.folder;
|
|
final name = share.name;
|
|
if (FileTypeUtils.isImage(name)) return LucideIcons.image;
|
|
if (FileTypeUtils.isPdf(name)) return LucideIcons.fileText;
|
|
if (FileTypeUtils.isVideo(name)) return LucideIcons.video;
|
|
if (FileTypeUtils.isAudio(name)) return LucideIcons.music;
|
|
if (FileTypeUtils.isMarkdown(name)) return LucideIcons.fileText;
|
|
if (FileTypeUtils.isTextCode(name)) return LucideIcons.code;
|
|
return LucideIcons.file;
|
|
}
|
|
|
|
Color _getIconColor(ShareModel share, ColorScheme colorScheme) {
|
|
if (share.isFolder) return Colors.amber.shade700;
|
|
final name = share.name;
|
|
if (FileTypeUtils.isImage(name)) return Colors.purple.shade600;
|
|
if (FileTypeUtils.isPdf(name)) return Colors.red.shade600;
|
|
if (FileTypeUtils.isVideo(name)) return Colors.orange.shade600;
|
|
if (FileTypeUtils.isAudio(name)) return Colors.blue.shade600;
|
|
if (FileTypeUtils.isMarkdown(name)) return Colors.teal.shade600;
|
|
if (FileTypeUtils.isTextCode(name)) return Colors.cyan.shade700;
|
|
return colorScheme.onSurfaceVariant;
|
|
}
|
|
|
|
String _formatDate(DateTime date) {
|
|
final now = DateTime.now();
|
|
final diff = now.difference(date);
|
|
if (diff.inDays == 0) return '今天';
|
|
if (diff.inDays == 1) return '昨天';
|
|
if (diff.inDays < 7) return '${diff.inDays} 天前';
|
|
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
// ─── 移动端菜单 ───
|
|
|
|
void _showMobileActionMenu(ShareModel share) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
showModalBottomSheet(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
|
|
builder: (context) => SafeArea(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ListTile(
|
|
leading: const Icon(LucideIcons.pencil),
|
|
title: const Text('修改分享设置'),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
_editShare(share);
|
|
},
|
|
),
|
|
ListTile(
|
|
leading: const Icon(LucideIcons.copy),
|
|
title: const Text('复制链接'),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Clipboard.setData(ClipboardData(text: share.url));
|
|
if (mounted) ToastHelper.success('链接已复制');
|
|
},
|
|
),
|
|
ListTile(
|
|
leading: Icon(LucideIcons.trash2, color: colorScheme.error),
|
|
title: Text('取消分享', style: TextStyle(color: colorScheme.error)),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
_deleteShare(share);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ─── 空状态 / 错误状态 ───
|
|
|
|
Widget _buildEmptyState() {
|
|
final theme = Theme.of(context);
|
|
return CustomScrollView(
|
|
slivers: [
|
|
SliverFillRemaining(
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.share2, size: 48, color: theme.colorScheme.outline),
|
|
const SizedBox(height: 16),
|
|
Text('还没有分享过文件', style: TextStyle(color: theme.hintColor)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildNoSearchResult() {
|
|
final theme = Theme.of(context);
|
|
return CustomScrollView(
|
|
slivers: [
|
|
SliverFillRemaining(
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.searchX, size: 48, color: theme.colorScheme.outline),
|
|
const SizedBox(height: 16),
|
|
Text('没有找到 "$_searchQuery"', style: TextStyle(color: theme.hintColor)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorState() {
|
|
final theme = Theme.of(context);
|
|
return CustomScrollView(
|
|
slivers: [
|
|
SliverFillRemaining(
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.alertCircle, size: 48, color: theme.colorScheme.error),
|
|
const SizedBox(height: 16),
|
|
Text('加载失败', style: TextStyle(color: theme.hintColor)),
|
|
const SizedBox(height: 8),
|
|
Text(_errorMessage ?? '未知错误',
|
|
style: TextStyle(fontSize: 12, color: theme.hintColor)),
|
|
const SizedBox(height: 24),
|
|
FilledButton.icon(
|
|
icon: const Icon(LucideIcons.refreshCw, size: 18),
|
|
label: const Text('重试'),
|
|
onPressed: _refreshShares,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|