From 76d9de9b1ea2fe8eb1364e2f2e302f037099421b Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 20 Mar 2020 21:35:28 +0900 Subject: [PATCH] added stats --- lib/widgets/album/all_collection_page.dart | 21 +++- lib/widgets/debug_page.dart | 4 - lib/widgets/stats.dart | 114 +++++++++++++++++++++ pubspec.lock | 31 +++++- pubspec.yaml | 5 + 5 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 lib/widgets/stats.dart diff --git a/lib/widgets/album/all_collection_page.dart b/lib/widgets/album/all_collection_page.dart index 58625f07f..daa421853 100644 --- a/lib/widgets/album/all_collection_page.dart +++ b/lib/widgets/album/all_collection_page.dart @@ -4,6 +4,7 @@ import 'package:aves/widgets/album/search_delegate.dart'; import 'package:aves/widgets/album/thumbnail_collection.dart'; import 'package:aves/widgets/common/menu_row.dart'; import 'package:aves/widgets/debug_page.dart'; +import 'package:aves/widgets/stats.dart'; import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:provider/provider.dart'; @@ -73,6 +74,10 @@ class _AllCollectionAppBar extends SliverAppBar { ), const PopupMenuDivider(), ], + PopupMenuItem( + value: AlbumAction.stats, + child: MenuRow(text: 'Stats', icon: OMIcons.pieChart), + ), PopupMenuItem( value: AlbumAction.debug, child: MenuRow(text: 'Debug', icon: OMIcons.whatshot), @@ -90,6 +95,9 @@ class _AllCollectionAppBar extends SliverAppBar { case AlbumAction.debug: _goToDebug(context, collection); break; + case AlbumAction.stats: + _goToStats(context, collection); + break; case AlbumAction.groupByAlbum: settings.collectionGroupFactor = GroupFactor.album; collection.group(GroupFactor.album); @@ -127,6 +135,17 @@ class _AllCollectionAppBar extends SliverAppBar { ), ); } + + static Future _goToStats(BuildContext context, CollectionLens collection) { + return Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StatsPage( + collection: collection, + ), + ), + ); + } } -enum AlbumAction { debug, groupByAlbum, groupByMonth, groupByDay, sortByDate, sortBySize, sortByName } +enum AlbumAction { debug, stats, groupByAlbum, groupByMonth, groupByDay, sortByDate, sortBySize, sortByName } diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index a4f60651a..50d343be8 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -44,10 +44,6 @@ class DebugPageState extends State { body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Paths'), - Text('DCIM path: ${androidFileUtils.dcimPath}'), - Text('pictures path: ${androidFileUtils.picturesPath}'), - const Divider(), const Text('Settings'), Text('collectionGroupFactor: ${settings.collectionGroupFactor}'), Text('collectionSortFactor: ${settings.collectionSortFactor}'), diff --git a/lib/widgets/stats.dart b/lib/widgets/stats.dart new file mode 100644 index 000000000..d9005ade9 --- /dev/null +++ b/lib/widgets/stats.dart @@ -0,0 +1,114 @@ +import 'dart:math'; + +import 'package:aves/model/collection_lens.dart'; +import 'package:aves/model/image_entry.dart'; +import 'package:aves/utils/color_utils.dart'; +import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:charts_flutter/flutter.dart' as charts; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +class StatsPage extends StatelessWidget { + final CollectionLens collection; + + const StatsPage({this.collection}); + + List get entries => collection.sortedEntries; + + @override + Widget build(BuildContext context) { + final catalogued = entries.where((entry) => entry.isCatalogued); + final withGps = catalogued.where((entry) => entry.hasGps); + final located = withGps.where((entry) => entry.isLocated); + final Map byMimeTypes = groupBy(entries, (entry) => entry.mimeType).map((k, v) => MapEntry(k, v.length)); + return MediaQueryDataProvider( + child: Scaffold( + appBar: AppBar( + title: const Text('Stats'), + ), + body: ListView( + children: [ + _buildMimePie(context, 'images', Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image/')))), + _buildMimePie(context, 'videos', Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video/')))), + const Divider(), + Text('Catalogued: ${catalogued.length}'), + Text('With GPS: ${withGps.length}'), + Text('With address: ${located.length}'), + ], + ), + ), + ); + } + + Widget _buildMimePie(BuildContext context, String label, Map byMimeTypes) { + if (byMimeTypes.isEmpty) return const SizedBox.shrink(); + + final seriesData = byMimeTypes.entries.map((kv) => StringNumDatum(kv.key.replaceFirst(RegExp('.*/'), '').toUpperCase(), kv.value)).toList(); + seriesData.sort((kv1, kv2) => kv2.value.compareTo(kv1.value)); + + final series = [ + charts.Series( + id: label, + colorFn: (d, i) => charts.ColorUtil.fromDartColor(stringToColor(d.key)), + domainFn: (d, i) => d.key, + measureFn: (d, i) => d.value, + data: seriesData, + labelAccessorFn: (d, _) => '${d.key}: ${d.value}', + ), + ]; + + final size = MediaQuery.of(context).size; + final circleDim = min(size.width, size.height) / 2; + + return Row( + children: [ + Container( + width: circleDim, + height: circleDim, + child: Stack( + children: [ + charts.PieChart( + series, + defaultRenderer: charts.ArcRendererConfig( + arcWidth: 16, + ), + ), + Center( + child: Text( + '${byMimeTypes.values.fold(0, (prev, v) => prev + v)}\n$label', + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: seriesData + .map((kv) => Row( + children: [ + Icon(Icons.fiber_manual_record, color: stringToColor(kv.key)), + const SizedBox(width: 8), + Text(kv.key), + const SizedBox(width: 8), + Text('${kv.value}'), + ], + )) + .toList()), + ], + ); + } +} + +class StringNumDatum { + final String key; + final num value; + + const StringNumDatum(this.key, this.value); + + @override + String toString() { + return '[$runtimeType#$hashCode: key=$key, value=$value]'; + } +} diff --git a/pubspec.lock b/pubspec.lock index 1f950768f..01f00c6a1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,6 +50,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.2" + charts_common: + dependency: transitive + description: + name: charts_common + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.0" + charts_flutter: + dependency: "direct main" + description: + name: charts_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.0" collection: dependency: "direct main" description: @@ -90,9 +104,11 @@ packages: flushbar: dependency: "direct main" description: - name: flushbar - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: "13c55a8" + resolved-ref: "13c55a888c1693f1c8269ea30d55c614a1bfee16" + url: "https://github.com/AndreHaueisen/flushbar.git" + source: git version: "1.9.1" flutter: dependency: "direct main" @@ -188,6 +204,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.8.3" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" matcher: dependency: transitive description: @@ -416,7 +439,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" transparent_image: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index a2062356f..93da59fe7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,12 +17,17 @@ dependencies: flutter: sdk: flutter after_init: + charts_flutter: collection: draggable_scrollbar: git: url: git://github.com/deckerst/flutter-draggable-scrollbar.git event_bus: flushbar: +# flushbar-1.9.1 cannot be built with Flutter 1.15.17 + git: + url: https://github.com/AndreHaueisen/flushbar.git + ref: 13c55a8 flutter_native_timezone: flutter_staggered_grid_view: flutter_sticky_header: