import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../theme/app_theme.dart'; import '../services/category_service.dart'; import '../database/app_usage_dao.dart'; import '../widgets/empty_state_widget.dart'; import '../widgets/error_state_widget.dart'; class CategoryManagementScreen extends ConsumerStatefulWidget { const CategoryManagementScreen({super.key}); @override ConsumerState createState() => _CategoryManagementScreenState(); } class _CategoryManagementScreenState extends ConsumerState { final CategoryService _categoryService = CategoryService(); final AppUsageDao _appUsageDao = AppUsageDao(); final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; // 可用分类列表 final List _availableCategories = ['work', 'study', 'entertainment', 'social', 'tool', 'other']; @override void dispose() { _searchController.dispose(); super.dispose(); } Future> _loadAppCategories() async { // 获取所有应用使用记录(最近使用的) // 如果数据库为空,返回空列表(Web 平台会返回空列表) final now = DateTime.now(); final startOfWeek = now.subtract(const Duration(days: 7)); final appUsages = await _appUsageDao.getAppUsages( startTime: startOfWeek, endTime: now, ); // 如果没有数据,返回空列表 if (appUsages.isEmpty) { return []; } // 获取自定义分类 final customCategories = await _categoryService.getAllCustomCategories(); // 去重并创建列表 final packageMap = {}; for (final usage in appUsages) { if (!packageMap.containsKey(usage.packageName)) { final currentCategory = customCategories[usage.packageName] ?? CategoryService.defaultCategories[usage.packageName] ?? 'other'; packageMap[usage.packageName] = AppCategoryItem( packageName: usage.packageName, appName: usage.appName, category: currentCategory, isCustom: customCategories.containsKey(usage.packageName), ); } } return packageMap.values.toList() ..sort((a, b) => a.appName.compareTo(b.appName)); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: const Text('应用分类'), ), body: Column( children: [ // 搜索框 Padding( padding: const EdgeInsets.all(16), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: '搜索应用...', prefixIcon: const Icon(Icons.search), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); setState(() { _searchQuery = ''; }); }, ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), onChanged: (value) { setState(() { _searchQuery = value; }); }, ), ), // 应用列表 Expanded( child: FutureBuilder>( future: _loadAppCategories(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return ErrorStateWidget.dataLoad( message: _getErrorMessage(snapshot.error!), onRetry: () { setState(() {}); }, ); } final items = snapshot.data ?? []; final filteredItems = _searchQuery.isEmpty ? items : items.where((item) { return item.appName.toLowerCase().contains(_searchQuery.toLowerCase()) || item.packageName.toLowerCase().contains(_searchQuery.toLowerCase()); }).toList(); if (filteredItems.isEmpty) { if (_searchQuery.isEmpty) { return EmptyStateWidget.noApps( onAction: () { setState(() {}); }, ); } else { return EmptyStateWidget.noSearchResults(query: _searchQuery); } } return ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: filteredItems.length, itemBuilder: (context, index) { final item = filteredItems[index]; return _buildAppCategoryItem(context, theme, item); }, ); }, ), ), ], ), ); } Widget _buildAppCategoryItem( BuildContext context, ThemeData theme, AppCategoryItem item, ) { return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: Container( width: 40, height: 40, decoration: BoxDecoration( color: AppTheme.getCategoryColor(item.category).withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.phone_android, color: AppTheme.getCategoryColor(item.category), ), ), title: Text( item.appName, style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, ), ), subtitle: Text( item.packageName, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.6), ), ), trailing: PopupMenuButton( icon: Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppTheme.getCategoryColor(item.category).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( AppTheme.getCategoryName(item.category), style: TextStyle( color: AppTheme.getCategoryColor(item.category), fontSize: 12, fontWeight: FontWeight.w500, ), ), ), const SizedBox(width: 8), const Icon(Icons.arrow_drop_down), ], ), onSelected: (String category) async { await _categoryService.setCategory(item.packageName, category); setState(() {}); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('已将 ${item.appName} 分类为 ${AppTheme.getCategoryName(category)}'), duration: const Duration(seconds: 2), ), ); } }, itemBuilder: (BuildContext context) { return _availableCategories.map((category) { return PopupMenuItem( value: category, child: Row( children: [ if (item.category == category) const Icon(Icons.check, size: 20, color: AppTheme.primaryColor) else const SizedBox(width: 20), const SizedBox(width: 8), Container( width: 12, height: 12, decoration: BoxDecoration( color: AppTheme.getCategoryColor(category), shape: BoxShape.circle, ), ), const SizedBox(width: 8), Text(AppTheme.getCategoryName(category)), ], ), ); }).toList(); }, ), ), ); } String _getErrorMessage(Object error) { final errorString = error.toString().toLowerCase(); if (errorString.contains('permission') || errorString.contains('权限')) { return '需要授予应用使用权限'; } else if (errorString.contains('network') || errorString.contains('网络')) { return '网络连接失败,请检查网络'; } else if (errorString.contains('database') || errorString.contains('数据库')) { return '数据库操作失败'; } return '加载失败,请稍后重试'; } } class AppCategoryItem { final String packageName; final String appName; final String category; final bool isCustom; AppCategoryItem({ required this.packageName, required this.appName, required this.category, required this.isCustom, }); }