| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- 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<MyApp> createState() => _MyAppState();
- }
- class _MyAppState extends State<MyApp> {
- final _photoclassifier = PhotoClassifier();
- bool _hasPermission = false;
- StreamSubscription<ClassificationEvent?>? _subscription;
- ClassificationProgress? _progress;
- List<ClassifiedImageGroup> _similarResult = [];
- List<ClassifiedImage> _peopleResult = [];
- List<ClassifiedImage> _screenshotResult = [];
- List<ClassifiedImage> _blurryResult = [];
- bool _isClassifying = false;
- String _errorMessage = '';
- @override
- void initState() {
- super.initState();
- _checkPermissions();
- }
- Future<void> _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<void> _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<void> _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();
- }
- }
|