import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../providers/icon_library_provider.dart'; import '../../../shared/constants/breakpoints.dart'; import '../../../shared/theme/app_theme.dart'; import '../../../shared/utils/user_error_formatter.dart'; import '../../../shared/widgets/adaptive_modal.dart'; import '../../../shared/widgets/app_dialog.dart'; import '../../../shared/widgets/app_loading_ring.dart'; import '../widgets/settings_card.dart'; class IconLibrarySettingsTab extends ConsumerWidget { const IconLibrarySettingsTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return const SettingsTabScroll( children: [ SettingsCard( children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: IconLibrarySection(), ), ], ), ], ); } } class IconLibrarySection extends ConsumerWidget { const IconLibrarySection({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final tokens = context.appTokens; final state = ref.watch(iconLibraryProvider); return state.when( data: (data) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '配置图标库 JSON URL,添加/编辑服务器时可从中选择图标', style: theme.textTheme.bodySmall?.copyWith( color: tokens.mutedForeground, ), ), const SizedBox(height: 12), if (data.entries.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Text( '尚未配置图标库', style: theme.textTheme.bodySmall?.copyWith( color: tokens.mutedForeground, ), ), ) else ...data.entries.map((entry) => IconLibraryRow(entry: entry)), const SizedBox(height: 8), Row( children: [ FilledButton.tonalIcon( onPressed: () => _showAddDialog(context, ref), icon: const Icon(Icons.add, size: 16), label: const Text('添加图标库'), ), const SizedBox(width: 8), if (data.entries.isNotEmpty) TextButton.icon( onPressed: () => ref.read(iconLibraryProvider.notifier).refreshAll(), icon: const Icon(Icons.refresh, size: 16), label: const Text('刷新全部'), ), const Spacer(), Text( '共 ${data.totalIcons} 个图标', style: theme.textTheme.bodySmall?.copyWith( color: tokens.mutedForeground, ), ), ], ), ], ), loading: () => const SizedBox.shrink(), error: (e, _) => settingsLoadError(e), ); } void _showAddDialog(BuildContext context, WidgetRef ref) { final controller = TextEditingController(); void submit(BuildContext ctx) { final url = controller.text.trim(); if (url.isEmpty) return; ref.read(iconLibraryProvider.notifier).addUrl(url); Navigator.pop(ctx); } showAdaptiveModal( context: context, maxWidth: 420, compactMaxHeightFactor: 0.9, builder: (ctx) { final theme = Theme.of(ctx); final isCompact = Breakpoints.isCompact(MediaQuery.sizeOf(ctx).width); return SingleChildScrollView( padding: EdgeInsets.fromLTRB(24, isCompact ? 16 : 24, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('添加图标库', style: theme.textTheme.titleLarge), const SizedBox(height: 8), Text( '输入图标库 JSON URL', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), TextField( controller: controller, autofocus: true, keyboardType: TextInputType.url, textInputAction: TextInputAction.done, autocorrect: false, enableSuggestions: false, onSubmitted: (_) => submit(ctx), decoration: const InputDecoration( hintText: 'https://example.com/icons.json', isDense: true, ), ), const SizedBox(height: 20), if (isCompact) ...[ SizedBox( height: 48, child: FilledButton( onPressed: () => submit(ctx), child: const Text('添加'), ), ), const SizedBox(height: 8), SizedBox( height: 44, child: OutlinedButton( onPressed: () => Navigator.pop(ctx), child: const Text('取消'), ), ), ] else Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('取消'), ), const SizedBox(width: 8), FilledButton( onPressed: () => submit(ctx), child: const Text('添加'), ), ], ), ], ), ); }, ).whenComplete(controller.dispose); } } class IconLibraryRow extends ConsumerWidget { final IconLibraryEntryState entry; const IconLibraryRow({super.key, required this.entry}); @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final tokens = context.appTokens; final notifier = ref.read(iconLibraryProvider.notifier); final lib = entry.library; final title = lib?.name.isNotEmpty == true ? lib!.name : entry.url; final (statusText, statusColor) = switch (entry.status) { IconLibraryStatus.loading => ('加载中…', tokens.mutedForeground), IconLibraryStatus.loaded => ( '${entry.iconCount} 个图标', tokens.mutedForeground, ), IconLibraryStatus.error => ( '加载失败:${formatUserError(entry.error, fallback: "未知错误")}', theme.colorScheme.error, ), IconLibraryStatus.idle => ('未加载', tokens.mutedForeground), }; return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium, ), const SizedBox(height: 2), if (lib?.name.isNotEmpty == true) Padding( padding: const EdgeInsets.only(bottom: 2), child: Text( entry.url, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.labelSmall?.copyWith( color: tokens.mutedForeground, ), ), ), Text( statusText, maxLines: 2, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: statusColor, ), ), ], ), ), if (entry.status == IconLibraryStatus.loading) const Padding( padding: EdgeInsets.symmetric(horizontal: 6), child: AppLoadingRing(size: 14), ) else IconButton( tooltip: '刷新', icon: const Icon(Icons.refresh, size: 18), onPressed: () => notifier.refresh(entry.url), ), IconButton( tooltip: '移除', icon: const Icon(Icons.delete_outline, size: 18), onPressed: () => _confirmRemove(context, notifier, entry.url), ), ], ), ); } Future _confirmRemove( BuildContext context, IconLibrarySettingsNotifier notifier, String url, ) async { final confirmed = await showAppConfirmDialog( context, title: '移除图标库', body: Text('确定移除「$url」?已选用此库图标的服务器将回退为首字母显示。'), confirmLabel: '移除', destructive: true, ); if (confirmed) await notifier.removeUrl(url); } }