import 'dart:async'; import 'package:flutter/material.dart'; import 'package:photo_classifier/photo_classifier.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:photo_classifier/models.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { final _photoclassifier = PhotoClassifier(); bool _hasPermission = false; StreamSubscription? _subscription; ClassificationProgress? _progress; List _similarResult = []; List _peopleResult = []; List _screenshotResult = []; List _blurryResult = []; bool _isClassifying = false; String _errorMessage = ''; @override void initState() { super.initState(); _checkPermissions(); } Future _checkPermissions() async { final status = await Permission.photos.status; setState(() { _hasPermission = status.isGranted; if (!status.isGranted) { _errorMessage = '需要相册权限才能进行照片分类,请点击上方按钮申请权限'; } }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('照片分类插件示例'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (!_hasPermission) ElevatedButton( onPressed: _requestPermission, child: const Text('请求照片库权限'), ), if (!_hasPermission) Padding( padding: const EdgeInsets.only(top: 16.0), child: ElevatedButton( onPressed: () => openAppSettings(), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, ), child: const Text('打开系统设置'), ), ), if (_hasPermission && !_isClassifying) ElevatedButton( onPressed: _startClassificationWithStream, child: const Text('开始分类'), ), if (_isClassifying) ElevatedButton( onPressed: _cancelClassification, child: const Text('取消分类'), ), if (_progress != null) ...[ const SizedBox(height: 20), Text('进度: ${(_progress!.rate * 100).toStringAsFixed(1)}%', style: const TextStyle(fontSize: 18)), const SizedBox(height: 10), LinearProgressIndicator(value: _progress!.rate), Text('已处理: ${_progress!.currentBatch}/${_progress!.totalBatches}'), ], if (_errorMessage.isNotEmpty) ...[ const SizedBox(height: 20), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.withOpacity(0.5)), ), child: Column( children: [ const Icon(Icons.error_outline, color: Colors.red, size: 28), const SizedBox(height: 8), Text( _errorMessage, style: const TextStyle(color: Colors.red), textAlign: TextAlign.center, ), ], ), ), ], if (_progress != null) ...[ const SizedBox(height: 20), const Text('分类结果:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), _buildResultSummary(), ], if (_progress?.isCompleted == true) ElevatedButton( onPressed: _resetAll, style: ElevatedButton.styleFrom( backgroundColor: Colors.blueGrey, ), child: const Text('重置'), ), ], ), ), ), ); } Widget _buildResultSummary() { if (_similarResult.isEmpty && _peopleResult.isEmpty && _screenshotResult.isEmpty && _blurryResult.isEmpty) return const Text('无结果'); return Expanded( child: ListView( children: [ _buildResultItem('相似图片组', _similarResult.length, isGroup: true), _buildResultItem('人物照片', _peopleResult.length), _buildResultItem('屏幕截图', _screenshotResult.length), _buildResultItem('模糊图片', _blurryResult.length), ], ), ); } Widget _buildResultItem(String title, dynamic items, {bool isGroup = false}) { if (items == null) return const SizedBox.shrink(); final count = isGroup ? (items as List).length : (items as List).length; final itemsText = isGroup ? '$count 组' : '$count 张'; return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('$title: $itemsText', style: const TextStyle(fontWeight: FontWeight.bold)), // 这里可以添加图片预览 ], ), ); } Future _requestPermission() async { try { // 使用permission_handler请求权限 final status = await Permission.photos.request(); final granted = status.isGranted; setState(() { _hasPermission = granted; if (!granted) { _errorMessage = '需要相册权限才能进行照片分类'; } else { _errorMessage = ''; } }); // 如果被拒绝且不能再次请求,提示用户前往设置 if (status.isPermanentlyDenied || status.isDenied) { setState(() { _errorMessage = '权限被拒绝,请点击下方按钮前往设置手动开启相册权限'; }); } } catch (e) { setState(() { _errorMessage = '请求权限失败: $e'; }); } } Future _startClassificationWithStream() async { setState(() { _isClassifying = true; _errorMessage = ''; _progress = null; _similarResult = []; _peopleResult = []; _screenshotResult = []; _blurryResult = []; }); try { // 先配置分类器 await _photoclassifier.configureClassifier( batchSize: 200, maxConcurrentProcessing: 4, similarityThreshold: 0.75, ); // 设置流监听 _subscription = _photoclassifier .startClassificationStream() .listen( (event) { if (event == null) return; setState(() { _progress = event.progress; print("批次: ${event.progress?.currentBatch}/${event.progress?.totalBatches}, 用时: ${event.progress?.batchDuration}"); print("${event.result?.screenshotImages?.length}"); var result = event.result; if (result != null) { _similarResult.addAll(result.similarGroups?.map((group) => group).toList() ?? []); _peopleResult.addAll(result.peopleImages?.map((image) => image).toList() ?? []); _screenshotResult.addAll(result.screenshotImages?.map((image) => image).toList() ?? []); _blurryResult.addAll(result.blurryImages?.map((image) => image).toList() ?? []); } if (event.progress?.isCompleted == true) { _isClassifying = false; _subscription?.cancel(); // 取消订阅 _subscription = null; } }); }, onError: (error) { setState(() { _errorMessage = '分类过程中出错: $error'; _isClassifying = false; }); }, onDone: () { if (_progress?.isCompleted != true) { setState(() { _errorMessage = '分类过程意外结束'; _isClassifying = false; }); } }, ); } catch (e) { setState(() { _errorMessage = '启动分类失败: $e'; _isClassifying = false; }); } } void _cancelClassification() { _subscription?.cancel(); _subscription = null; setState(() { _isClassifying = false; }); _photoclassifier.resetClassifier(); } void _resetAll() { setState(() { _progress = null; _similarResult = []; _peopleResult = []; _screenshotResult = []; _blurryResult = []; _errorMessage = ''; _isClassifying = false; }); // _photoclassifier.resetClassifier(); } @override void dispose() { _subscription?.cancel(); super.dispose(); } }