filter grid scaling: highlight

This commit is contained in:
Thibault Deckers 2020-11-27 15:36:15 +09:00
parent 548d723223
commit d21cd23ac8
19 changed files with 319 additions and 204 deletions

View file

@ -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/filters/filters.dart';
import 'package:aves/model/image_entry.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/utils/android_file_utils.dart';
import 'package:aves/widgets/common/identity/aves_icons.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/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';

24
lib/model/highlight.dart Normal file
View file

@ -0,0 +1,24 @@
import 'dart:collection';
import 'package:flutter/foundation.dart';
class HighlightInfo extends ChangeNotifier {
final Queue<Object> _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);
}

View file

@ -18,7 +18,6 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
EntryGroupFactor groupFactor; EntryGroupFactor groupFactor;
EntrySortFactor sortFactor; EntrySortFactor sortFactor;
final AChangeNotifier filterChangeNotifier = AChangeNotifier(); final AChangeNotifier filterChangeNotifier = AChangeNotifier();
final StreamController<ImageEntry> _highlightController = StreamController.broadcast();
List<ImageEntry> _filteredEntries; List<ImageEntry> _filteredEntries;
List<StreamSubscription> _subscriptions = []; List<StreamSubscription> _subscriptions = [];
@ -70,10 +69,6 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
return _sortedEntries; return _sortedEntries;
} }
Stream<ImageEntry> get highlightStream => _highlightController.stream;
void highlight(ImageEntry entry) => _highlightController.add(entry);
bool get showHeaders { bool get showHeaders {
if (sortFactor == EntrySortFactor.size) return false; if (sortFactor == EntrySortFactor.size) return false;

View file

@ -1,22 +1,22 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/main.dart'; 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/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.dart'; import 'package:aves/model/source/enums.dart';
import 'package:aves/services/app_shortcut_service.dart'; import 'package:aves/services/app_shortcut_service.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/model/actions/collection_actions.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/collection/filter_bar.dart';
import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
import 'package:aves/widgets/collection/entry_set_action_delegate.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_subtitle.dart';
import 'package:aves/widgets/common/app_bar_title.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/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_button.dart';
import 'package:aves/widgets/search/search_delegate.dart'; import 'package:aves/widgets/search/search_delegate.dart';
import 'package:aves/widgets/stats/stats.dart'; import 'package:aves/widgets/stats/stats.dart';

View file

@ -1,16 +1,16 @@
import 'dart:async'; 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/image_entry.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/image_file_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/feedback.dart';
import 'package:aves/widgets/common/action_mixins/permission_aware.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/common/action_mixins/size_aware.dart';
import 'package:aves/widgets/dialogs/aves_dialog.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:aves/widgets/filter_grids/album_pick.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -3,12 +3,12 @@ import 'dart:math';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/enums.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/android_file_utils.dart';
import 'package:aves/utils/constants.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_album.dart';
import 'package:aves/widgets/collection/grid/header_date.dart'; import 'package:aves/widgets/collection/grid/header_date.dart';
import 'package:aves/theme/icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View file

@ -10,7 +10,7 @@ class DecoratedThumbnail extends StatelessWidget {
final double extent; final double extent;
final CollectionLens collection; final CollectionLens collection;
final ValueNotifier<bool> isScrollingNotifier; final ValueNotifier<bool> isScrollingNotifier;
final bool showOverlay; final bool selectable, highlightable;
final Object heroTag; final Object heroTag;
static final Color borderColor = Colors.grey.shade700; static final Color borderColor = Colors.grey.shade700;
@ -22,7 +22,8 @@ class DecoratedThumbnail extends StatelessWidget {
@required this.extent, @required this.extent,
this.collection, this.collection,
this.isScrollingNotifier, this.isScrollingNotifier,
this.showOverlay = true, this.selectable = true,
this.highlightable = true,
}) : heroTag = collection?.heroTag(entry), }) : heroTag = collection?.heroTag(entry),
super(key: key); super(key: key);
@ -40,29 +41,32 @@ class DecoratedThumbnail extends StatelessWidget {
isScrollingNotifier: isScrollingNotifier, isScrollingNotifier: isScrollingNotifier,
heroTag: heroTag, heroTag: heroTag,
); );
if (showOverlay) {
child = Stack( child = Stack(
children: [ fit: StackFit.passthrough,
child, children: [
Positioned( child,
bottom: 0, Positioned(
left: 0, bottom: 0,
child: ThumbnailEntryOverlay( left: 0,
entry: entry, child: ThumbnailEntryOverlay(
extent: extent, entry: entry,
), extent: extent,
), ),
),
if (selectable)
ThumbnailSelectionOverlay( ThumbnailSelectionOverlay(
entry: entry, entry: entry,
extent: extent, extent: extent,
), ),
if (highlightable)
ThumbnailHighlightOverlay( ThumbnailHighlightOverlay(
highlightedStream: collection.highlightStream.map((highlighted) => highlighted == entry), entry: entry,
extent: extent, extent: extent,
), ),
], ],
); );
}
return Container( return Container(
foregroundDecoration: BoxDecoration( foregroundDecoration: BoxDecoration(
border: Border.all( border: Border.all(

View file

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -116,13 +117,13 @@ class ThumbnailSelectionOverlay extends StatelessWidget {
} }
class ThumbnailHighlightOverlay extends StatefulWidget { class ThumbnailHighlightOverlay extends StatefulWidget {
final ImageEntry entry;
final double extent; final double extent;
final Stream<bool> highlightedStream;
const ThumbnailHighlightOverlay({ const ThumbnailHighlightOverlay({
Key key, Key key,
@required this.entry,
@required this.extent, @required this.extent,
@required this.highlightedStream,
}) : super(key: key); }) : super(key: key);
@override @override
@ -132,27 +133,25 @@ class ThumbnailHighlightOverlay extends StatefulWidget {
class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> { class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> {
final ValueNotifier<bool> _highlightedNotifier = ValueNotifier(false); final ValueNotifier<bool> _highlightedNotifier = ValueNotifier(false);
ImageEntry get entry => widget.entry;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<bool>( final highlightInfo = context.watch<HighlightInfo>();
stream: widget.highlightedStream, _highlightedNotifier.value = highlightInfo.contains(entry);
builder: (context, snapshot) { return Sweeper(
_highlightedNotifier.value = snapshot.hasData && snapshot.data; builder: (context) => Container(
return Sweeper( decoration: BoxDecoration(
builder: (context) => Container( border: Border.all(
decoration: BoxDecoration( color: Theme.of(context).accentColor,
border: Border.all( width: widget.extent * .1,
color: Theme.of(context).accentColor,
width: widget.extent * .1,
),
),
), ),
toggledNotifier: _highlightedNotifier, ),
startAngle: pi * -3 / 4, ),
centerSweep: false, toggledNotifier: _highlightedNotifier,
onSweepEnd: () => _highlightedNotifier.value = false, startAngle: pi * -3 / 4,
); centerSweep: false,
}, onSweepEnd: () => highlightInfo.remove(entry),
); );
} }
} }

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.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/image_entry.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.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/empty.dart';
import 'package:aves/widgets/collection/grid/list_section_layout.dart'; import 'package:aves/widgets/collection/grid/list_section_layout.dart';
import 'package:aves/widgets/collection/grid/list_sliver.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/collection/thumbnail/decorated.dart';
import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/behaviour/routes.dart';
import 'package:aves/widgets/common/behaviour/sloppy_scroll_physics.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/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:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
@ -35,79 +37,82 @@ class ThumbnailCollection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return HighlightInfoProvider(
child: LayoutBuilder( child: SafeArea(
builder: (context, constraints) { child: LayoutBuilder(
final viewportSize = constraints.biggest; builder: (context, constraints) {
assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.'); final viewportSize = constraints.biggest;
if (viewportSize.isEmpty) return SizedBox.shrink(); assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.');
if (viewportSize.isEmpty) return SizedBox.shrink();
final tileExtentManager = TileExtentManager( final tileExtentManager = TileExtentManager(
settingsRouteKey: context.currentRouteName, settingsRouteKey: context.currentRouteName,
columnCountMin: columnCountMin, columnCountMin: columnCountMin,
columnCountDefault: columnCountDefault, columnCountDefault: columnCountDefault,
extentMin: extentMin, extentMin: extentMin,
extentNotifier: _tileExtentNotifier, extentNotifier: _tileExtentNotifier,
spacing: 0, spacing: 0,
)..applyTileExtent(viewportSize: viewportSize); )..applyTileExtent(viewportSize: viewportSize);
final cacheExtent = tileExtentManager.getEffectiveExtentMax(viewportSize) * 2; final cacheExtent = tileExtentManager.getEffectiveExtentMax(viewportSize) * 2;
// do not replace by Provider.of<CollectionLens> // do not replace by Provider.of<CollectionLens>
// so that view updates on collection filter changes // so that view updates on collection filter changes
return Consumer<CollectionLens>( return Consumer<CollectionLens>(
builder: (context, collection, child) { builder: (context, collection, child) {
final scrollView = CollectionScrollView( final scrollView = CollectionScrollView(
scrollableKey: _scrollableKey, scrollableKey: _scrollableKey,
collection: collection,
appBar: CollectionAppBar(
appBarHeightNotifier: _appBarHeightNotifier,
collection: collection, collection: collection,
), appBar: CollectionAppBar(
appBarHeightNotifier: _appBarHeightNotifier, appBarHeightNotifier: _appBarHeightNotifier,
isScrollingNotifier: _isScrollingNotifier,
scrollController: PrimaryScrollController.of(context),
cacheExtent: cacheExtent,
);
final scaler = GridScaleGestureDetector<ImageEntry>(
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<SectionedListLayout>(context, listen: false);
return sectionedListLayout.getTileRect(entry) ?? Rect.zero;
},
onScaled: collection.highlight,
child: scrollView,
);
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
valueListenable: _tileExtentNotifier,
builder: (context, tileExtent, child) => SectionedListLayoutProvider(
collection: collection,
scrollableWidth: viewportSize.width,
tileExtent: tileExtent,
thumbnailBuilder: (entry) => GridThumbnail(
key: ValueKey(entry.contentId),
collection: collection, collection: collection,
entry: entry,
tileExtent: tileExtent,
isScrollingNotifier: _isScrollingNotifier,
), ),
child: scaler, appBarHeightNotifier: _appBarHeightNotifier,
), isScrollingNotifier: _isScrollingNotifier,
); scrollController: PrimaryScrollController.of(context),
return sectionedListLayoutProvider; cacheExtent: cacheExtent,
}, );
);
}, final scaler = GridScaleGestureDetector<ImageEntry>(
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<SectionedListLayout>(context, listen: false);
return sectionedListLayout.getTileRect(entry) ?? Rect.zero;
},
onScaled: (entry) => Provider.of<HighlightInfo>(context, listen: false).add(entry),
child: scrollView,
);
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
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;
},
);
},
),
), ),
); );
} }

View file

@ -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<HighlightInfo>(
create: (context) => HighlightInfo(),
child: child,
);
}
}

View file

@ -1,7 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';

View file

@ -13,6 +13,7 @@ import 'package:aves/widgets/collection/thumbnail/raster.dart';
import 'package:aves/widgets/collection/thumbnail/vector.dart'; import 'package:aves/widgets/collection/thumbnail/vector.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.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/filter_grid_page.dart';
import 'package:aves/widgets/filter_grids/common/overlay.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -21,7 +22,7 @@ class DecoratedFilterChip extends StatelessWidget {
final CollectionFilter filter; final CollectionFilter filter;
final ImageEntry entry; final ImageEntry entry;
final double extent; final double extent;
final bool pinned; final bool pinned, highlightable;
final FilterCallback onTap; final FilterCallback onTap;
final OffsetFilterCallback onLongPress; final OffsetFilterCallback onLongPress;
@ -32,6 +33,7 @@ class DecoratedFilterChip extends StatelessWidget {
@required this.entry, @required this.entry,
@required this.extent, @required this.extent,
this.pinned = false, this.pinned = false,
this.highlightable = true,
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
}) : super(key: key); }) : super(key: key);
@ -49,18 +51,34 @@ class DecoratedFilterChip extends StatelessWidget {
entry: entry, entry: entry,
extent: extent, extent: extent,
); );
final borderRadius = min<double>(AvesFilterChip.defaultRadius, extent / 4); final radius = min<double>(AvesFilterChip.defaultRadius, extent / 4);
final titlePadding = min<double>(6.0, extent / 16); final titlePadding = min<double>(6.0, extent / 16);
return AvesFilterChip( final borderRadius = BorderRadius.all(Radius.circular(radius));
Widget child = AvesFilterChip(
filter: filter, filter: filter,
showGenericIcon: false, showGenericIcon: false,
background: backgroundImage, background: backgroundImage,
details: _buildDetails(filter), details: _buildDetails(filter),
borderRadius: BorderRadius.all(Radius.circular(borderRadius)), borderRadius: borderRadius,
padding: titlePadding, padding: titlePadding,
onTap: onTap, onTap: onTap,
onLongPress: onLongPress, onLongPress: onLongPress,
); );
child = Stack(
fit: StackFit.passthrough,
children: [
child,
if (highlightable)
ChipHighlightOverlay(
filter: filter,
extent: extent,
borderRadius: borderRadius,
),
],
);
return child;
} }
Widget _buildDetails(CollectionFilter filter) { Widget _buildDetails(CollectionFilter filter) {

View file

@ -1,6 +1,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_source.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/behaviour/routes.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.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/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/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/scaling.dart'; import 'package:aves/widgets/common/scaling.dart';
import 'package:aves/widgets/common/tile_extent_manager.dart'; import 'package:aves/widgets/common/tile_extent_manager.dart';
@ -59,75 +61,76 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: Scaffold( child: Scaffold(
body: DoubleBackPopScope( body: DoubleBackPopScope(
child: SafeArea( child: HighlightInfoProvider(
child: LayoutBuilder( child: SafeArea(
builder: (context, constraints) { child: LayoutBuilder(
final viewportSize = constraints.biggest; builder: (context, constraints) {
assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.'); final viewportSize = constraints.biggest;
if (viewportSize.isEmpty) return SizedBox.shrink(); assert(viewportSize.isFinite, 'Cannot layout collection with unbounded constraints.');
if (viewportSize.isEmpty) return SizedBox.shrink();
final tileExtentManager = TileExtentManager( final tileExtentManager = TileExtentManager(
settingsRouteKey: settingsRouteKey ?? context.currentRouteName, settingsRouteKey: settingsRouteKey ?? context.currentRouteName,
columnCountMin: 2, columnCountMin: 2,
columnCountDefault: 2, columnCountDefault: 2,
extentMin: 60, extentMin: 60,
extentNotifier: _tileExtentNotifier, extentNotifier: _tileExtentNotifier,
spacing: spacing, spacing: spacing,
)..applyTileExtent(viewportSize: viewportSize); )..applyTileExtent(viewportSize: viewportSize);
return ValueListenableBuilder<double>( return ValueListenableBuilder<double>(
valueListenable: _tileExtentNotifier, valueListenable: _tileExtentNotifier,
builder: (context, tileExtent, child) { builder: (context, tileExtent, child) {
final columnCount = tileExtentManager.getEffectiveColumnCountForExtent(viewportSize, tileExtent); final columnCount = tileExtentManager.getEffectiveColumnCountForExtent(viewportSize, tileExtent);
return ValueListenableBuilder<String>( return ValueListenableBuilder<String>(
valueListenable: queryNotifier, valueListenable: queryNotifier,
builder: (context, query, child) { builder: (context, query, child) {
final allFilters = filterEntries.keys; final allFilters = filterEntries.keys;
final visibleFilters = (applyQuery != null ? applyQuery(allFilters, query) : allFilters).toList(); final visibleFilters = (applyQuery != null ? applyQuery(allFilters, query) : allFilters).toList();
final scrollView = AnimationLimiter( final scrollView = AnimationLimiter(
child: _buildDraggableScrollView(_buildScrollView(context, columnCount, visibleFilters)), child: _buildDraggableScrollView(_buildScrollView(context, columnCount, visibleFilters)),
); );
return GridScaleGestureDetector<FilterGridItem>( return GridScaleGestureDetector<FilterGridItem>(
tileExtentManager: tileExtentManager, tileExtentManager: tileExtentManager,
scrollableKey: _scrollableKey, scrollableKey: _scrollableKey,
appBarHeightNotifier: _appBarHeightNotifier, appBarHeightNotifier: _appBarHeightNotifier,
viewportSize: viewportSize, viewportSize: viewportSize,
showScaledGrid: true, showScaledGrid: true,
scaledBuilder: (item, extent) { scaledBuilder: (item, extent) {
final filter = item.filter; final filter = item.filter;
return SizedBox( return SizedBox(
width: extent, width: extent,
height: extent, height: extent,
child: DecoratedFilterChip( child: DecoratedFilterChip(
source: source, source: source,
filter: filter, filter: filter,
entry: item.entry, entry: item.entry,
extent: extent, extent: extent,
pinned: settings.pinnedFilters.contains(filter), pinned: settings.pinnedFilters.contains(filter),
), highlightable: false,
); ),
}, );
getScaledItemTileRect: (context, item) { },
final index = visibleFilters.indexOf(item.filter); getScaledItemTileRect: (context, item) {
final column = index % columnCount; final index = visibleFilters.indexOf(item.filter);
final row = (index / columnCount).floor(); final column = index % columnCount;
final left = tileExtent * column + spacing * (column - 1); final row = (index / columnCount).floor();
final top = tileExtent * row + spacing * (row - 1); final left = tileExtent * column + spacing * (column - 1);
return Rect.fromLTWH(left, top, tileExtent, tileExtent); final top = tileExtent * row + spacing * (row - 1);
}, return Rect.fromLTWH(left, top, tileExtent, tileExtent);
onScaled: (item) { },
// TODO TLAD highlight scaled item onScaled: (item) => Provider.of<HighlightInfo>(context, listen: false).add(item.filter),
}, child: scrollView,
child: scrollView, );
); },
}, );
); },
}, );
); },
}, ),
), ),
), ),
), ),

View file

@ -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<ChipHighlightOverlay> {
final ValueNotifier<bool> _highlightedNotifier = ValueNotifier(false);
CollectionFilter get filter => widget.filter;
@override
Widget build(BuildContext context) {
final highlightInfo = context.watch<HighlightInfo>();
_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),
);
}
}

View file

@ -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/main.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/theme/icons.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/db.dart';
import 'package:aves/widgets/fullscreen/debug/metadata.dart'; import 'package:aves/widgets/fullscreen/debug/metadata.dart';
import 'package:aves/widgets/fullscreen/info/common.dart'; import 'package:aves/widgets/fullscreen/info/common.dart';

View file

@ -1,11 +1,11 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/theme/durations.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_dialog.dart';
import 'package:aves/widgets/dialogs/aves_selection_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/info/location_section.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -1,13 +1,13 @@
import 'dart:collection'; import 'dart:collection';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/services/metadata_service.dart';
import 'package:aves/ref/brand_colors.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/color_utils.dart';
import 'package:aves/utils/constants.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/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/common.dart';
import 'package:aves/widgets/fullscreen/info/metadata/metadata_thumbnail.dart'; import 'package:aves/widgets/fullscreen/info/metadata/metadata_thumbnail.dart';
import 'package:aves/widgets/fullscreen/info/metadata/xmp_tile.dart'; import 'package:aves/widgets/fullscreen/info/metadata/xmp_tile.dart';

View file

@ -5,10 +5,10 @@ import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/settings/coordinate_format.dart'; import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/metadata_service.dart'; import 'package:aves/services/metadata_service.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/theme/icons.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:aves/widgets/fullscreen/overlay/common.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -8,12 +8,12 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.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/color_utils.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/utils/mime_utils.dart'; import 'package:aves/utils/mime_utils.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/collection/empty.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/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/stats/filter_table.dart'; import 'package:aves/widgets/stats/filter_table.dart';
import 'package:charts_flutter/flutter.dart' as charts; import 'package:charts_flutter/flutter.dart' as charts;