From d21cd23ac81af51de731795e936ef2577b26dcb6 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 27 Nov 2020 15:36:15 +0900 Subject: [PATCH] filter grid scaling: highlight --- lib/model/filters/album.dart | 4 +- lib/model/highlight.dart | 24 +++ lib/model/source/collection_lens.dart | 5 - lib/widgets/collection/app_bar.dart | 12 +- .../collection/entry_set_action_delegate.dart | 4 +- .../collection/grid/header_generic.dart | 4 +- .../collection/thumbnail/decorated.dart | 38 +++-- lib/widgets/collection/thumbnail/overlay.dart | 39 +++-- .../collection/thumbnail_collection.dart | 145 +++++++++--------- .../providers/highlight_info_provider.dart | 17 ++ lib/widgets/dialogs/create_album_dialog.dart | 2 +- .../common/decorated_filter_chip.dart | 26 +++- .../filter_grids/common/filter_grid_page.dart | 131 ++++++++-------- lib/widgets/filter_grids/common/overlay.dart | 50 ++++++ .../fullscreen/fullscreen_debug_page.dart | 4 +- lib/widgets/fullscreen/info/maps/common.dart | 6 +- .../info/metadata/metadata_section.dart | 6 +- lib/widgets/fullscreen/overlay/bottom.dart | 4 +- lib/widgets/stats/stats.dart | 2 +- 19 files changed, 319 insertions(+), 204 deletions(-) create mode 100644 lib/model/highlight.dart create mode 100644 lib/widgets/common/providers/highlight_info_provider.dart create mode 100644 lib/widgets/filter_grids/common/overlay.dart diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart index 68e0b8e3d..b5062e80e 100644 --- a/lib/model/filters/album.dart +++ b/lib/model/filters/album.dart @@ -1,9 +1,9 @@ +import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/common/identity/aves_icons.dart'; -import 'package:aves/theme/icons.dart'; -import 'package:aves/image_providers/app_icon_image_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:palette_generator/palette_generator.dart'; diff --git a/lib/model/highlight.dart b/lib/model/highlight.dart new file mode 100644 index 000000000..ca620a153 --- /dev/null +++ b/lib/model/highlight.dart @@ -0,0 +1,24 @@ +import 'dart:collection'; + +import 'package:flutter/foundation.dart'; + +class HighlightInfo extends ChangeNotifier { + final Queue _items = Queue(); + + void add(Object item) { + if (_items.contains(item)) return; + + _items.addFirst(item); + while (_items.length > 5) { + _items.removeLast(); + } + notifyListeners(); + } + + void remove(Object item) { + _items.removeWhere((element) => element == item); + notifyListeners(); + } + + bool contains(Object item) => _items.contains(item); +} diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart index f70a7dd4b..9b54aa18f 100644 --- a/lib/model/source/collection_lens.dart +++ b/lib/model/source/collection_lens.dart @@ -18,7 +18,6 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel EntryGroupFactor groupFactor; EntrySortFactor sortFactor; final AChangeNotifier filterChangeNotifier = AChangeNotifier(); - final StreamController _highlightController = StreamController.broadcast(); List _filteredEntries; List _subscriptions = []; @@ -70,10 +69,6 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel return _sortedEntries; } - Stream get highlightStream => _highlightController.stream; - - void highlight(ImageEntry entry) => _highlightController.add(entry); - bool get showHeaders { if (sortFactor == EntrySortFactor.size) return false; diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 2aaef5061..ff4601a4c 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -1,22 +1,22 @@ import 'dart:async'; import 'package:aves/main.dart'; +import 'package:aves/model/actions/collection_actions.dart'; +import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; import 'package:aves/services/app_shortcut_service.dart'; import 'package:aves/theme/durations.dart'; -import 'package:aves/model/actions/collection_actions.dart'; -import 'package:aves/widgets/collection/filter_bar.dart'; -import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart'; +import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/collection/entry_set_action_delegate.dart'; +import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/common/app_bar_subtitle.dart'; import 'package:aves/widgets/common/app_bar_title.dart'; -import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; -import 'package:aves/model/actions/entry_actions.dart'; -import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/basic/menu_row.dart'; +import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart'; +import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/search/search_button.dart'; import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/stats/stats.dart'; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 33fc671a5..9ecf90e61 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -1,16 +1,16 @@ import 'dart:async'; +import 'package:aves/model/actions/collection_actions.dart'; +import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/image_file_service.dart'; -import 'package:aves/model/actions/collection_actions.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/action_mixins/permission_aware.dart'; import 'package:aves/widgets/common/action_mixins/size_aware.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; -import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/widgets/filter_grids/album_pick.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/collection/grid/header_generic.dart b/lib/widgets/collection/grid/header_generic.dart index 6588e746a..2396cf2c3 100644 --- a/lib/widgets/collection/grid/header_generic.dart +++ b/lib/widgets/collection/grid/header_generic.dart @@ -3,12 +3,12 @@ import 'dart:math'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/collection/grid/header_album.dart'; import 'package:aves/widgets/collection/grid/header_date.dart'; -import 'package:aves/theme/icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/collection/thumbnail/decorated.dart b/lib/widgets/collection/thumbnail/decorated.dart index 2f2ac1676..71dd0377c 100644 --- a/lib/widgets/collection/thumbnail/decorated.dart +++ b/lib/widgets/collection/thumbnail/decorated.dart @@ -10,7 +10,7 @@ class DecoratedThumbnail extends StatelessWidget { final double extent; final CollectionLens collection; final ValueNotifier isScrollingNotifier; - final bool showOverlay; + final bool selectable, highlightable; final Object heroTag; static final Color borderColor = Colors.grey.shade700; @@ -22,7 +22,8 @@ class DecoratedThumbnail extends StatelessWidget { @required this.extent, this.collection, this.isScrollingNotifier, - this.showOverlay = true, + this.selectable = true, + this.highlightable = true, }) : heroTag = collection?.heroTag(entry), super(key: key); @@ -40,29 +41,32 @@ class DecoratedThumbnail extends StatelessWidget { isScrollingNotifier: isScrollingNotifier, heroTag: heroTag, ); - if (showOverlay) { - child = Stack( - children: [ - child, - Positioned( - bottom: 0, - left: 0, - child: ThumbnailEntryOverlay( - entry: entry, - extent: extent, - ), + + child = Stack( + fit: StackFit.passthrough, + children: [ + child, + Positioned( + bottom: 0, + left: 0, + child: ThumbnailEntryOverlay( + entry: entry, + extent: extent, ), + ), + if (selectable) ThumbnailSelectionOverlay( entry: entry, extent: extent, ), + if (highlightable) ThumbnailHighlightOverlay( - highlightedStream: collection.highlightStream.map((highlighted) => highlighted == entry), + entry: entry, extent: extent, ), - ], - ); - } + ], + ); + return Container( foregroundDecoration: BoxDecoration( border: Border.all( diff --git a/lib/widgets/collection/thumbnail/overlay.dart b/lib/widgets/collection/thumbnail/overlay.dart index 8dba26cbf..6e9179bd3 100644 --- a/lib/widgets/collection/thumbnail/overlay.dart +++ b/lib/widgets/collection/thumbnail/overlay.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:aves/model/highlight.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; @@ -116,13 +117,13 @@ class ThumbnailSelectionOverlay extends StatelessWidget { } class ThumbnailHighlightOverlay extends StatefulWidget { + final ImageEntry entry; final double extent; - final Stream highlightedStream; const ThumbnailHighlightOverlay({ Key key, + @required this.entry, @required this.extent, - @required this.highlightedStream, }) : super(key: key); @override @@ -132,27 +133,25 @@ class ThumbnailHighlightOverlay extends StatefulWidget { class _ThumbnailHighlightOverlayState extends State { final ValueNotifier _highlightedNotifier = ValueNotifier(false); + ImageEntry get entry => widget.entry; + @override Widget build(BuildContext context) { - return StreamBuilder( - stream: widget.highlightedStream, - builder: (context, snapshot) { - _highlightedNotifier.value = snapshot.hasData && snapshot.data; - return Sweeper( - builder: (context) => Container( - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).accentColor, - width: widget.extent * .1, - ), - ), + final highlightInfo = context.watch(); + _highlightedNotifier.value = highlightInfo.contains(entry); + return Sweeper( + builder: (context) => Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).accentColor, + width: widget.extent * .1, ), - toggledNotifier: _highlightedNotifier, - startAngle: pi * -3 / 4, - centerSweep: false, - onSweepEnd: () => _highlightedNotifier.value = false, - ); - }, + ), + ), + toggledNotifier: _highlightedNotifier, + startAngle: pi * -3 / 4, + centerSweep: false, + onSweepEnd: () => highlightInfo.remove(entry), ); } } diff --git a/lib/widgets/collection/thumbnail_collection.dart b/lib/widgets/collection/thumbnail_collection.dart index be2943041..d1c1fa12d 100644 --- a/lib/widgets/collection/thumbnail_collection.dart +++ b/lib/widgets/collection/thumbnail_collection.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; +import 'package:aves/model/highlight.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; @@ -12,12 +13,13 @@ import 'package:aves/widgets/collection/app_bar.dart'; import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/collection/grid/list_section_layout.dart'; import 'package:aves/widgets/collection/grid/list_sliver.dart'; -import 'package:aves/widgets/common/scaling.dart'; -import 'package:aves/widgets/common/tile_extent_manager.dart'; import 'package:aves/widgets/collection/thumbnail/decorated.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.dart'; import 'package:aves/widgets/common/identity/scroll_thumb.dart'; +import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; +import 'package:aves/widgets/common/scaling.dart'; +import 'package:aves/widgets/common/tile_extent_manager.dart'; import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -35,79 +37,82 @@ class ThumbnailCollection extends StatelessWidget { @override Widget build(BuildContext context) { - return SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - final viewportSize = constraints.biggest; - assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.'); - if (viewportSize.isEmpty) return SizedBox.shrink(); + return HighlightInfoProvider( + child: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + final viewportSize = constraints.biggest; + assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.'); + if (viewportSize.isEmpty) return SizedBox.shrink(); - final tileExtentManager = TileExtentManager( - settingsRouteKey: context.currentRouteName, - columnCountMin: columnCountMin, - columnCountDefault: columnCountDefault, - extentMin: extentMin, - extentNotifier: _tileExtentNotifier, - spacing: 0, - )..applyTileExtent(viewportSize: viewportSize); - final cacheExtent = tileExtentManager.getEffectiveExtentMax(viewportSize) * 2; + final tileExtentManager = TileExtentManager( + settingsRouteKey: context.currentRouteName, + columnCountMin: columnCountMin, + columnCountDefault: columnCountDefault, + extentMin: extentMin, + extentNotifier: _tileExtentNotifier, + spacing: 0, + )..applyTileExtent(viewportSize: viewportSize); + final cacheExtent = tileExtentManager.getEffectiveExtentMax(viewportSize) * 2; - // do not replace by Provider.of - // so that view updates on collection filter changes - return Consumer( - builder: (context, collection, child) { - final scrollView = CollectionScrollView( - scrollableKey: _scrollableKey, - collection: collection, - appBar: CollectionAppBar( - appBarHeightNotifier: _appBarHeightNotifier, + // do not replace by Provider.of + // so that view updates on collection filter changes + return Consumer( + builder: (context, collection, child) { + final scrollView = CollectionScrollView( + scrollableKey: _scrollableKey, collection: collection, - ), - appBarHeightNotifier: _appBarHeightNotifier, - isScrollingNotifier: _isScrollingNotifier, - scrollController: PrimaryScrollController.of(context), - cacheExtent: cacheExtent, - ); - - final scaler = GridScaleGestureDetector( - tileExtentManager: tileExtentManager, - scrollableKey: _scrollableKey, - appBarHeightNotifier: _appBarHeightNotifier, - viewportSize: viewportSize, - showScaledGrid: true, - scaledBuilder: (entry, extent) => DecoratedThumbnail( - entry: entry, - extent: extent, - showOverlay: false, - ), - getScaledItemTileRect: (context, entry) { - final sectionedListLayout = Provider.of(context, listen: false); - return sectionedListLayout.getTileRect(entry) ?? Rect.zero; - }, - onScaled: collection.highlight, - child: scrollView, - ); - - final sectionedListLayoutProvider = ValueListenableBuilder( - valueListenable: _tileExtentNotifier, - builder: (context, tileExtent, child) => SectionedListLayoutProvider( - collection: collection, - scrollableWidth: viewportSize.width, - tileExtent: tileExtent, - thumbnailBuilder: (entry) => GridThumbnail( - key: ValueKey(entry.contentId), + appBar: CollectionAppBar( + appBarHeightNotifier: _appBarHeightNotifier, collection: collection, - entry: entry, - tileExtent: tileExtent, - isScrollingNotifier: _isScrollingNotifier, ), - child: scaler, - ), - ); - return sectionedListLayoutProvider; - }, - ); - }, + appBarHeightNotifier: _appBarHeightNotifier, + isScrollingNotifier: _isScrollingNotifier, + scrollController: PrimaryScrollController.of(context), + cacheExtent: cacheExtent, + ); + + final scaler = GridScaleGestureDetector( + tileExtentManager: tileExtentManager, + scrollableKey: _scrollableKey, + appBarHeightNotifier: _appBarHeightNotifier, + viewportSize: viewportSize, + showScaledGrid: true, + scaledBuilder: (entry, extent) => DecoratedThumbnail( + entry: entry, + extent: extent, + selectable: false, + highlightable: false, + ), + getScaledItemTileRect: (context, entry) { + final sectionedListLayout = Provider.of(context, listen: false); + return sectionedListLayout.getTileRect(entry) ?? Rect.zero; + }, + onScaled: (entry) => Provider.of(context, listen: false).add(entry), + child: scrollView, + ); + + final sectionedListLayoutProvider = ValueListenableBuilder( + valueListenable: _tileExtentNotifier, + builder: (context, tileExtent, child) => SectionedListLayoutProvider( + collection: collection, + scrollableWidth: viewportSize.width, + tileExtent: tileExtent, + thumbnailBuilder: (entry) => GridThumbnail( + key: ValueKey(entry.contentId), + collection: collection, + entry: entry, + tileExtent: tileExtent, + isScrollingNotifier: _isScrollingNotifier, + ), + child: scaler, + ), + ); + return sectionedListLayoutProvider; + }, + ); + }, + ), ), ); } diff --git a/lib/widgets/common/providers/highlight_info_provider.dart b/lib/widgets/common/providers/highlight_info_provider.dart new file mode 100644 index 000000000..8b09c1695 --- /dev/null +++ b/lib/widgets/common/providers/highlight_info_provider.dart @@ -0,0 +1,17 @@ +import 'package:aves/model/highlight.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +class HighlightInfoProvider extends StatelessWidget { + final Widget child; + + const HighlightInfoProvider({@required this.child}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => HighlightInfo(), + child: child, + ); + } +} diff --git a/lib/widgets/dialogs/create_album_dialog.dart b/lib/widgets/dialogs/create_album_dialog.dart index 101d35d4e..0b746a5c3 100644 --- a/lib/widgets/dialogs/create_album_dialog.dart +++ b/lib/widgets/dialogs/create_album_dialog.dart @@ -1,7 +1,7 @@ import 'dart:io'; -import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/utils/android_file_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:path/path.dart'; diff --git a/lib/widgets/filter_grids/common/decorated_filter_chip.dart b/lib/widgets/filter_grids/common/decorated_filter_chip.dart index f971fb46b..385b4a37b 100644 --- a/lib/widgets/filter_grids/common/decorated_filter_chip.dart +++ b/lib/widgets/filter_grids/common/decorated_filter_chip.dart @@ -13,6 +13,7 @@ import 'package:aves/widgets/collection/thumbnail/raster.dart'; import 'package:aves/widgets/collection/thumbnail/vector.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart'; +import 'package:aves/widgets/filter_grids/common/overlay.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/material.dart'; @@ -21,7 +22,7 @@ class DecoratedFilterChip extends StatelessWidget { final CollectionFilter filter; final ImageEntry entry; final double extent; - final bool pinned; + final bool pinned, highlightable; final FilterCallback onTap; final OffsetFilterCallback onLongPress; @@ -32,6 +33,7 @@ class DecoratedFilterChip extends StatelessWidget { @required this.entry, @required this.extent, this.pinned = false, + this.highlightable = true, this.onTap, this.onLongPress, }) : super(key: key); @@ -49,18 +51,34 @@ class DecoratedFilterChip extends StatelessWidget { entry: entry, extent: extent, ); - final borderRadius = min(AvesFilterChip.defaultRadius, extent / 4); + final radius = min(AvesFilterChip.defaultRadius, extent / 4); final titlePadding = min(6.0, extent / 16); - return AvesFilterChip( + final borderRadius = BorderRadius.all(Radius.circular(radius)); + Widget child = AvesFilterChip( filter: filter, showGenericIcon: false, background: backgroundImage, details: _buildDetails(filter), - borderRadius: BorderRadius.all(Radius.circular(borderRadius)), + borderRadius: borderRadius, padding: titlePadding, onTap: onTap, onLongPress: onLongPress, ); + + child = Stack( + fit: StackFit.passthrough, + children: [ + child, + if (highlightable) + ChipHighlightOverlay( + filter: filter, + extent: extent, + borderRadius: borderRadius, + ), + ], + ); + + return child; } Widget _buildDetails(CollectionFilter filter) { diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index b9536b4db..c6a3a7862 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/highlight.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; @@ -9,6 +10,7 @@ import 'package:aves/widgets/common/behaviour/double_back_pop.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/scroll_thumb.dart'; +import 'package:aves/widgets/common/providers/highlight_info_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/scaling.dart'; import 'package:aves/widgets/common/tile_extent_manager.dart'; @@ -59,75 +61,76 @@ class FilterGridPage extends StatelessWidget { return MediaQueryDataProvider( child: Scaffold( body: DoubleBackPopScope( - child: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - final viewportSize = constraints.biggest; - assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.'); - if (viewportSize.isEmpty) return SizedBox.shrink(); + child: HighlightInfoProvider( + child: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + final viewportSize = constraints.biggest; + assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.'); + if (viewportSize.isEmpty) return SizedBox.shrink(); - final tileExtentManager = TileExtentManager( - settingsRouteKey: settingsRouteKey ?? context.currentRouteName, - columnCountMin: 2, - columnCountDefault: 2, - extentMin: 60, - extentNotifier: _tileExtentNotifier, - spacing: spacing, - )..applyTileExtent(viewportSize: viewportSize); + final tileExtentManager = TileExtentManager( + settingsRouteKey: settingsRouteKey ?? context.currentRouteName, + columnCountMin: 2, + columnCountDefault: 2, + extentMin: 60, + extentNotifier: _tileExtentNotifier, + spacing: spacing, + )..applyTileExtent(viewportSize: viewportSize); - return ValueListenableBuilder( - valueListenable: _tileExtentNotifier, - builder: (context, tileExtent, child) { - final columnCount = tileExtentManager.getEffectiveColumnCountForExtent(viewportSize, tileExtent); + return ValueListenableBuilder( + valueListenable: _tileExtentNotifier, + builder: (context, tileExtent, child) { + final columnCount = tileExtentManager.getEffectiveColumnCountForExtent(viewportSize, tileExtent); - return ValueListenableBuilder( - valueListenable: queryNotifier, - builder: (context, query, child) { - final allFilters = filterEntries.keys; - final visibleFilters = (applyQuery != null ? applyQuery(allFilters, query) : allFilters).toList(); + return ValueListenableBuilder( + valueListenable: queryNotifier, + builder: (context, query, child) { + final allFilters = filterEntries.keys; + final visibleFilters = (applyQuery != null ? applyQuery(allFilters, query) : allFilters).toList(); - final scrollView = AnimationLimiter( - child: _buildDraggableScrollView(_buildScrollView(context, columnCount, visibleFilters)), - ); + final scrollView = AnimationLimiter( + child: _buildDraggableScrollView(_buildScrollView(context, columnCount, visibleFilters)), + ); - return GridScaleGestureDetector( - tileExtentManager: tileExtentManager, - scrollableKey: _scrollableKey, - appBarHeightNotifier: _appBarHeightNotifier, - viewportSize: viewportSize, - showScaledGrid: true, - scaledBuilder: (item, extent) { - final filter = item.filter; - return SizedBox( - width: extent, - height: extent, - child: DecoratedFilterChip( - source: source, - filter: filter, - entry: item.entry, - extent: extent, - pinned: settings.pinnedFilters.contains(filter), - ), - ); - }, - getScaledItemTileRect: (context, item) { - final index = visibleFilters.indexOf(item.filter); - final column = index % columnCount; - final row = (index / columnCount).floor(); - final left = tileExtent * column + spacing * (column - 1); - final top = tileExtent * row + spacing * (row - 1); - return Rect.fromLTWH(left, top, tileExtent, tileExtent); - }, - onScaled: (item) { - // TODO TLAD highlight scaled item - }, - child: scrollView, - ); - }, - ); - }, - ); - }, + return GridScaleGestureDetector( + tileExtentManager: tileExtentManager, + scrollableKey: _scrollableKey, + appBarHeightNotifier: _appBarHeightNotifier, + viewportSize: viewportSize, + showScaledGrid: true, + scaledBuilder: (item, extent) { + final filter = item.filter; + return SizedBox( + width: extent, + height: extent, + child: DecoratedFilterChip( + source: source, + filter: filter, + entry: item.entry, + extent: extent, + pinned: settings.pinnedFilters.contains(filter), + highlightable: false, + ), + ); + }, + getScaledItemTileRect: (context, item) { + final index = visibleFilters.indexOf(item.filter); + final column = index % columnCount; + final row = (index / columnCount).floor(); + final left = tileExtent * column + spacing * (column - 1); + final top = tileExtent * row + spacing * (row - 1); + return Rect.fromLTWH(left, top, tileExtent, tileExtent); + }, + onScaled: (item) => Provider.of(context, listen: false).add(item.filter), + child: scrollView, + ); + }, + ); + }, + ); + }, + ), ), ), ), diff --git a/lib/widgets/filter_grids/common/overlay.dart b/lib/widgets/filter_grids/common/overlay.dart new file mode 100644 index 000000000..18148fac1 --- /dev/null +++ b/lib/widgets/filter_grids/common/overlay.dart @@ -0,0 +1,50 @@ +import 'dart:math'; + +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/highlight.dart'; +import 'package:aves/widgets/common/fx/sweeper.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class ChipHighlightOverlay extends StatefulWidget { + final CollectionFilter filter; + final double extent; + final BorderRadius borderRadius; + + const ChipHighlightOverlay({ + Key key, + @required this.filter, + @required this.extent, + @required this.borderRadius, + }) : super(key: key); + + @override + _ChipHighlightOverlayState createState() => _ChipHighlightOverlayState(); +} + +class _ChipHighlightOverlayState extends State { + final ValueNotifier _highlightedNotifier = ValueNotifier(false); + + CollectionFilter get filter => widget.filter; + + @override + Widget build(BuildContext context) { + final highlightInfo = context.watch(); + _highlightedNotifier.value = highlightInfo.contains(filter); + return Sweeper( + builder: (context) => Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).accentColor, + width: widget.extent * .1, + ), + borderRadius: widget.borderRadius, + ), + ), + toggledNotifier: _highlightedNotifier, + startAngle: pi * -3 / 4, + centerSweep: false, + onSweepEnd: () => highlightInfo.remove(filter), + ); + } +} diff --git a/lib/widgets/fullscreen/fullscreen_debug_page.dart b/lib/widgets/fullscreen/fullscreen_debug_page.dart index 50e55e24e..3c796a253 100644 --- a/lib/widgets/fullscreen/fullscreen_debug_page.dart +++ b/lib/widgets/fullscreen/fullscreen_debug_page.dart @@ -1,8 +1,8 @@ +import 'package:aves/image_providers/thumbnail_provider.dart'; +import 'package:aves/image_providers/uri_picture_provider.dart'; import 'package:aves/main.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/theme/icons.dart'; -import 'package:aves/image_providers/thumbnail_provider.dart'; -import 'package:aves/image_providers/uri_picture_provider.dart'; import 'package:aves/widgets/fullscreen/debug/db.dart'; import 'package:aves/widgets/fullscreen/debug/metadata.dart'; import 'package:aves/widgets/fullscreen/info/common.dart'; diff --git a/lib/widgets/fullscreen/info/maps/common.dart b/lib/widgets/fullscreen/info/maps/common.dart index 97e057672..452d6ef00 100644 --- a/lib/widgets/fullscreen/info/maps/common.dart +++ b/lib/widgets/fullscreen/info/maps/common.dart @@ -1,11 +1,11 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/android_app_service.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/fx/blurred.dart'; +import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; -import 'package:aves/widgets/common/fx/borders.dart'; -import 'package:aves/widgets/common/fx/blurred.dart'; -import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/fullscreen/info/location_section.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/fullscreen/info/metadata/metadata_section.dart b/lib/widgets/fullscreen/info/metadata/metadata_section.dart index 5084c5088..438fb3807 100644 --- a/lib/widgets/fullscreen/info/metadata/metadata_section.dart +++ b/lib/widgets/fullscreen/info/metadata/metadata_section.dart @@ -1,13 +1,13 @@ import 'dart:collection'; import 'package:aves/model/image_entry.dart'; -import 'package:aves/services/metadata_service.dart'; import 'package:aves/ref/brand_colors.dart'; +import 'package:aves/services/metadata_service.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/theme/icons.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; -import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/fullscreen/info/common.dart'; import 'package:aves/widgets/fullscreen/info/metadata/metadata_thumbnail.dart'; import 'package:aves/widgets/fullscreen/info/metadata/xmp_tile.dart'; diff --git a/lib/widgets/fullscreen/overlay/bottom.dart b/lib/widgets/fullscreen/overlay/bottom.dart index 1144ff0b7..b2d08980f 100644 --- a/lib/widgets/fullscreen/overlay/bottom.dart +++ b/lib/widgets/fullscreen/overlay/bottom.dart @@ -5,10 +5,10 @@ import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/settings/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/metadata_service.dart'; -import 'package:aves/utils/constants.dart'; import 'package:aves/theme/durations.dart'; -import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/theme/icons.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/stats/stats.dart b/lib/widgets/stats/stats.dart index 4b343fbca..96abd7807 100644 --- a/lib/widgets/stats/stats.dart +++ b/lib/widgets/stats/stats.dart @@ -8,12 +8,12 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/theme/icons.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/mime_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/empty.dart'; -import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/stats/filter_table.dart'; import 'package:charts_flutter/flutter.dart' as charts;