diff --git a/example/lib/src/screens/main/map_view/components/download_progress/components/greyscale_masker.dart b/example/lib/src/screens/main/map_view/components/download_progress/components/render_object.dart similarity index 69% rename from example/lib/src/screens/main/map_view/components/download_progress/components/greyscale_masker.dart rename to example/lib/src/screens/main/map_view/components/download_progress/components/render_object.dart index b1f403b0..f135f916 100644 --- a/example/lib/src/screens/main/map_view/components/download_progress/components/greyscale_masker.dart +++ b/example/lib/src/screens/main/map_view/components/download_progress/components/render_object.dart @@ -1,16 +1,18 @@ part of '../download_progress_masker.dart'; -class GreyscaleMasker extends SingleChildRenderObjectWidget { - const GreyscaleMasker({ +class DownloadProgressMaskerRenderObject extends SingleChildRenderObjectWidget { + const DownloadProgressMaskerRenderObject({ super.key, - required super.child, + required this.isVisible, required this.latestTileCoordinates, required this.mapCamera, required this.minZoom, required this.maxZoom, required this.tileSize, + required super.child, }); + final bool isVisible; final TileCoordinates? latestTileCoordinates; final MapCamera mapCamera; final int minZoom; @@ -19,7 +21,8 @@ class GreyscaleMasker extends SingleChildRenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) => - _GreyscaleMaskerRenderer( + _DownloadProgressMaskerRenderer( + isVisible: isVisible, mapCamera: mapCamera, minZoom: minZoom, maxZoom: maxZoom, @@ -30,17 +33,20 @@ class GreyscaleMasker extends SingleChildRenderObjectWidget { void updateRenderObject( BuildContext context, // ignore: library_private_types_in_public_api - _GreyscaleMaskerRenderer renderObject, + _DownloadProgressMaskerRenderer renderObject, ) { - renderObject.mapCamera = mapCamera; + renderObject + ..mapCamera = mapCamera + ..isVisible = isVisible; if (latestTileCoordinates case final ltc?) renderObject.addTile(ltc); // We don't support changing the other properties. They should not change // during a download. } } -class _GreyscaleMaskerRenderer extends RenderProxyBox { - _GreyscaleMaskerRenderer({ +class _DownloadProgressMaskerRenderer extends RenderProxyBox { + _DownloadProgressMaskerRenderer({ + required bool isVisible, required MapCamera mapCamera, required this.minZoom, required this.maxZoom, @@ -50,7 +56,8 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { 'Unable to work with the large numbers that result from handling the ' 'difference of `maxZoom` & `minZoom`', ), - _mapCamera = mapCamera { + _mapCamera = mapCamera, + _isVisible = isVisible { // Precalculate for more efficient greyscale amount calculations later _maxSubtilesCountPerZoomLevel = Uint64List((maxZoom - minZoom) + 1); int p = 0; @@ -63,17 +70,23 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { //! PROPERTIES + bool _isVisible; + bool get isVisible => _isVisible; + set isVisible(bool value) { + if (value == isVisible) return; + _isVisible = value; + markNeedsPaint(); + } + MapCamera _mapCamera; MapCamera get mapCamera => _mapCamera; set mapCamera(MapCamera value) { if (value == mapCamera) return; _mapCamera = value; - _recompileGreyscalePathCache(); + _recompileEffectLevelPathCache(); markNeedsPaint(); } - TileCoordinates? _prevTile; - /// Minimum zoom level of the download /// /// The difference of [maxZoom] & [minZoom] must be less than 32, due to @@ -89,8 +102,14 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { /// Size of each tile in pixels final int tileSize; + /// Maximum amount of blur effect + static const double _maxBlurSigma = 10; + //! STATE + TileCoordinates? _prevTile; + Rect Function()? _mostRecentTile; + /// Maps tiles of a download to a [_TileMappingValue], which contains: /// * the number of subtiles downloaded /// * the lat/lng coordinates of the tile's top-left (North-West) & @@ -111,19 +130,21 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { /// The number of subtiles a tile at the zoom level (index) may have late final Uint64List _maxSubtilesCountPerZoomLevel; - /// Cache for a greyscale amount to the path that should be painted with that - /// greyscale level + /// Cache for effect percentages to the path that should be painted with that + /// effect percentage + /// + /// Effect percentage means both greyscale percentage and blur amount. /// - /// The key is multiplied by 1/[_greyscaleLevelsCount] to give the greyscale - /// percentage. This means there are [_greyscaleLevelsCount] levels of - /// greyscale available. Because the difference between close greyscales is - /// very difficult to percieve with the eye, this is acceptable, and improves - /// performance drastically. The ideal amount is calculated and rounded to the - /// nearest level. - final Map _greyscalePathCache = Map.unmodifiable({ - for (int i = 0; i <= _greyscaleLevelsCount; i++) i: Path(), + /// The key is multiplied by 1/[_effectLevelsCount] to give the effect + /// percentage. This means there are [_effectLevelsCount] levels of + /// effects available. Because the difference between close greyscales and + /// blurs is very difficult to percieve with the eye, this is acceptable, and + /// improves performance drastically. The ideal amount is calculated and + /// rounded to the nearest level. + final Map _effectLevelPathCache = Map.unmodifiable({ + for (int i = 0; i <= _effectLevelsCount; i++) i: Path(), }); - static const _greyscaleLevelsCount = 25; + static const _effectLevelsCount = 25; //! GREYSCALE HANDLING @@ -161,9 +182,9 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { /// Calculate the greyscale level given the number of subtiles actually /// downloaded and the possible number of subtiles /// - /// Multiply by 1/[_greyscaleLevelsCount] to pass to [_generateGreyscaleFilter] + /// Multiply by 1/[_effectLevelsCount] to pass to [_generateGreyscaleFilter] /// to generate [ColorFilter]. - int _calculateGreyscaleLevel(int subtilesCount, int maxSubtilesCount) { + int _calculateEffectLevel(int subtilesCount, int maxSubtilesCount) { assert( subtilesCount <= maxSubtilesCount, '`subtilesCount` must be less than or equal to `maxSubtilesCount`', @@ -171,8 +192,8 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { final invGreyscalePercentage = (subtilesCount + 1) / (maxSubtilesCount + 1); // +1 to count self - return _greyscaleLevelsCount - - (invGreyscalePercentage * _greyscaleLevelsCount).round(); + return _effectLevelsCount - + (invGreyscalePercentage * _effectLevelsCount).round(); } //! INPUT STREAM HANDLING @@ -214,13 +235,13 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { } /// Handles incoming tiles from the input stream, modifying the [_tileMapping] - /// and [_greyscalePathCache] as necessary + /// and [_effectLevelPathCache] as necessary /// /// Tiles are pruned from the tile mapping where the parent tile has maxed out /// the number of subtiles (ie. all this tile's neighbours within the quad of /// the parent are also downloaded), to save memory space. However, it is /// not possible to prune the path cache, so this will slowly become - /// out-of-sync and less efficient. See [_recompileGreyscalePathCache] + /// out-of-sync and less efficient. See [_recompileEffectLevelPathCache] /// for details. void addTile(TileCoordinates tile) { assert(tile.z >= minZoom, 'Incoming `tile` has zoom level below minimum'); @@ -254,10 +275,12 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { seCoord: mapCamera.crs .pointToLatLng((tile + const Point(1, 1)) * tileSize, zoom), ); + _mostRecentTile = + () => _calculateRectOfCoords(tmv.nwCoord, tmv.seCoord); } - _greyscalePathCache[ - _calculateGreyscaleLevel(tmv.subtilesCount, maxSubtilesCount)]! + _effectLevelPathCache[ + _calculateEffectLevel(tmv.subtilesCount, maxSubtilesCount)]! .addRect(_calculateRectOfCoords(tmv.nwCoord, tmv.seCoord)); late final isParentMaxedOut = _tileMapping[TileCoordinates( @@ -270,35 +293,34 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { intermediateZoomTile.z - 1 - minZoom] - 1; if (intermediateZoomTile.z != minZoom && isParentMaxedOut) { - _tileMapping.remove(intermediateZoomTile); // self - - if (intermediateZoomTile.x.isOdd) { - _tileMapping.remove( + // Remove adjacent tiles in quad + _tileMapping + ..remove(intermediateZoomTile) // self + ..remove( TileCoordinates( - intermediateZoomTile.x - 1, + intermediateZoomTile.x + + (intermediateZoomTile.x.isOdd ? -1 : 1), intermediateZoomTile.y, intermediateZoomTile.z, ), - ); - } - if (intermediateZoomTile.y.isOdd) { - _tileMapping.remove( + ) + ..remove( TileCoordinates( intermediateZoomTile.x, - intermediateZoomTile.y - 1, + intermediateZoomTile.y + + (intermediateZoomTile.y.isOdd ? -1 : 1), intermediateZoomTile.z, ), - ); - } - if (intermediateZoomTile.x.isOdd && intermediateZoomTile.y.isOdd) { - _tileMapping.remove( + ) + ..remove( TileCoordinates( - intermediateZoomTile.x - 1, - intermediateZoomTile.y - 1, + intermediateZoomTile.x + + (intermediateZoomTile.x.isOdd ? -1 : 1), + intermediateZoomTile.y + + (intermediateZoomTile.y.isOdd ? -1 : 1), intermediateZoomTile.z, ), ); - } } }, ); @@ -306,7 +328,7 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { markNeedsPaint(); } - /// Recompile the [_greyscalePathCache] ready for repainting based on the + /// Recompile the [_effectLevelPathCache] ready for repainting based on the /// single source-of-truth of the [_tileMapping] /// /// --- @@ -334,8 +356,8 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { /// /// This method does not call [markNeedsPaint], the caller should perform that /// if necessary. - void _recompileGreyscalePathCache() { - for (final path in _greyscalePathCache.values) { + void _recompileEffectLevelPathCache() { + for (final path in _effectLevelPathCache.values) { path.reset(); } @@ -343,7 +365,7 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { key: TileCoordinates(z: tileZoom), value: _TileMappingValue(:subtilesCount, :nwCoord, :seCoord), ) in _tileMapping.entries) { - _greyscalePathCache[_calculateGreyscaleLevel( + _effectLevelPathCache[_calculateEffectLevel( subtilesCount, _maxSubtilesCountPerZoomLevel[tileZoom - minZoom], )]! @@ -363,42 +385,63 @@ class _GreyscaleMaskerRenderer extends RenderProxyBox { @override void paint(PaintingContext context, Offset offset) { - // Paint the map in greyscale + if (!isVisible) return super.paint(context, offset); + + // Paint the map in full greyscale & blur context.pushColorFilter( offset, _generateGreyscaleFilter(1), - (context, offset) => context.paintChild(child!, offset), + (context, offset) => context.pushImageFilter( + offset, + ImageFilter.blur(sigmaX: _maxBlurSigma, sigmaY: _maxBlurSigma), + (context, offset) => context.paintChild(child!, offset), + ), ); - // Then paint, from colorest to greyscalist (high to low zoom level), each - // layer using the respective `Path` as a clip ('cut') + // Then paint, from lowest effect to highest effect (high to low zoom level), + // each layer using the respective `Path` as a clip int layerHandleIndex = 0; - for (int i = _greyscalePathCache.length - 1; i >= 0; i--) { - final MapEntry(key: greyscaleAmount, value: path) = - _greyscalePathCache.entries.elementAt(i); + for (int i = _effectLevelPathCache.length - 1; i >= 0; i--) { + final MapEntry(key: effectLevel, value: path) = + _effectLevelPathCache.entries.elementAt(i); - final greyscalePercentage = greyscaleAmount * 1 / _greyscaleLevelsCount; + final effectPercentage = effectLevel / _effectLevelsCount; _layerHandles.elementAt(layerHandleIndex).layer = context.pushColorFilter( offset, - _generateGreyscaleFilter(greyscalePercentage), - (context, offset) => context.pushClipPath( - needsCompositing, + _generateGreyscaleFilter(effectPercentage), + (context, offset) => context.pushImageFilter( offset, - Offset.zero & size, - path, - (context, offset) => context.paintChild(child!, offset), - clipBehavior: Clip.hardEdge, + ImageFilter.blur( + sigmaX: effectPercentage * _maxBlurSigma, + sigmaY: effectPercentage * _maxBlurSigma, + ), + (context, offset) => context.pushClipPath( + needsCompositing, + offset, + Offset.zero & size, + path, + (context, offset) => context.paintChild(child!, offset), + clipBehavior: Clip.hardEdge, + ), ), oldLayer: _layerHandles.elementAt(layerHandleIndex).layer, ); layerHandleIndex++; } + + // Paint green 50% overlay over latest tile + if (_mostRecentTile case final rect?) { + context.canvas.drawPath( + Path()..addRect(rect()), + Paint()..color = Colors.green.withAlpha(255 ~/ 2), + ); + } } } -/// See [_GreyscaleMaskerRenderer._tileMapping] for documentation +/// See [_DownloadProgressMaskerRenderer._tileMapping] for documentation /// /// Is mutable to improve performance. class _TileMappingValue { @@ -412,3 +455,17 @@ class _TileMappingValue { final LatLng nwCoord; final LatLng seCoord; } + +extension on PaintingContext { + ImageFilterLayer pushImageFilter( + Offset offset, + ImageFilter imageFilter, + PaintingContextCallback painter, { + ImageFilterLayer? oldLayer, + }) { + final ImageFilterLayer layer = (oldLayer ?? ImageFilterLayer()) + ..imageFilter = imageFilter; + pushLayer(layer, painter, offset); + return layer; + } +} diff --git a/example/lib/src/screens/main/map_view/components/download_progress/download_progress_masker.dart b/example/lib/src/screens/main/map_view/components/download_progress/download_progress_masker.dart index 676d6be2..597cc019 100644 --- a/example/lib/src/screens/main/map_view/components/download_progress/download_progress_masker.dart +++ b/example/lib/src/screens/main/map_view/components/download_progress/download_progress_masker.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:math'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -9,11 +10,12 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; import 'package:latlong2/latlong.dart' hide Path; -part 'components/greyscale_masker.dart'; +part 'components/render_object.dart'; class DownloadProgressMasker extends StatefulWidget { const DownloadProgressMasker({ super.key, + required this.isVisible, required this.tileEvents, required this.minZoom, required this.maxZoom, @@ -21,26 +23,30 @@ class DownloadProgressMasker extends StatefulWidget { required this.child, }); + final bool isVisible; final Stream? tileEvents; final int minZoom; final int maxZoom; final int tileSize; final TileLayer child; + // To reset after a download, the `key` must be changed + @override State createState() => _DownloadProgressMaskerState(); } class _DownloadProgressMaskerState extends State { @override - Widget build(BuildContext context) { - if (widget.tileEvents case final tileEvents?) { - return RepaintBoundary( + Widget build(BuildContext context) => RepaintBoundary( child: StreamBuilder( - stream: tileEvents - .where((evt) => evt is SuccessfulTileEvent) + stream: widget.tileEvents + ?.where( + (evt) => evt is SuccessfulTileEvent || evt is SkippedTileEvent, + ) .map((evt) => evt.coordinates), - builder: (context, coords) => GreyscaleMasker( + builder: (context, coords) => DownloadProgressMaskerRenderObject( + isVisible: widget.isVisible, mapCamera: MapCamera.of(context), latestTileCoordinates: coords.data == null ? null @@ -56,7 +62,4 @@ class _DownloadProgressMaskerState extends State { ), ), ); - } - return widget.child; - } } diff --git a/example/lib/src/screens/main/map_view/components/region_selection/region_shape.dart b/example/lib/src/screens/main/map_view/components/region_selection/region_shape.dart index 0e8f475c..ec094739 100644 --- a/example/lib/src/screens/main/map_view/components/region_selection/region_shape.dart +++ b/example/lib/src/screens/main/map_view/components/region_selection/region_shape.dart @@ -4,12 +4,18 @@ import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; +import '../../../../../shared/state/download_provider.dart'; import '../../../../../shared/state/general_provider.dart'; import '../../../../../shared/state/region_selection_provider.dart'; -class RegionShape extends StatelessWidget { +class RegionShape extends StatefulWidget { const RegionShape({super.key}); + @override + State createState() => _RegionShapeState(); +} + +class _RegionShapeState extends State { @override Widget build(BuildContext context) => Consumer( builder: (context, provider, _) { @@ -84,51 +90,72 @@ class RegionShape extends StatelessWidget { }, ); - Widget _renderConstructedRegion(BaseRegion region, HSLColor color) => - switch (region) { - RectangleRegion(:final bounds) => PolygonLayer( - polygons: [ - Polygon( - points: [ - bounds.northWest, - bounds.northEast, - bounds.southEast, - bounds.southWest, - ], - color: color.toColor().withAlpha(255 ~/ 2), - ), - ], - ), - CircleRegion(:final center, :final radius) => CircleLayer( - circles: [ - CircleMarker( - point: center, - radius: radius * 1000, - useRadiusInMeter: true, - color: color.toColor().withAlpha(255 ~/ 2), - ), - ], - ), - LineRegion() => PolygonLayer( - polygons: region - .toOutlines(1) - .map( - (o) => Polygon( - points: o, - color: color.toColor().withAlpha(255 ~/ 2), - ), - ) - .toList(growable: false), - ), - CustomPolygonRegion(:final outline) => PolygonLayer( - polygons: [ - Polygon( - points: outline, - color: color.toColor().withAlpha(255 ~/ 2), - ), - ], - ), - MultiRegion() => - throw UnsupportedError('Cannot support `MultiRegion`s here'), - }; + Widget _renderConstructedRegion(BaseRegion region, HSLColor color) { + final isDownloading = + context.watch().storeName != null; + + return switch (region) { + RectangleRegion(:final bounds) => PolygonLayer( + polygons: [ + Polygon( + points: [ + bounds.northWest, + bounds.northEast, + bounds.southEast, + bounds.southWest, + ], + color: isDownloading + ? Colors.transparent + : color.toColor().withAlpha(255 ~/ 2), + borderColor: isDownloading ? Colors.black : Colors.transparent, + borderStrokeWidth: 3, + ), + ], + ), + CircleRegion(:final center, :final radius) => CircleLayer( + circles: [ + CircleMarker( + point: center, + radius: radius * 1000, + useRadiusInMeter: true, + color: isDownloading + ? Colors.transparent + : color.toColor().withAlpha(255 ~/ 2), + borderColor: isDownloading ? Colors.black : Colors.transparent, + borderStrokeWidth: 3, + ), + ], + ), + LineRegion() => PolygonLayer( + polygons: region + .toOutlines(1) + .map( + (o) => Polygon( + points: o, + color: isDownloading + ? Colors.transparent + : color.toColor().withAlpha(255 ~/ 2), + borderColor: + isDownloading ? Colors.black : Colors.transparent, + borderStrokeWidth: 3, + ), + ) + .toList(growable: false), + ), + CustomPolygonRegion(:final outline) => PolygonLayer( + polygons: [ + Polygon( + points: outline, + color: isDownloading + ? Colors.transparent + : color.toColor().withAlpha(255 ~/ 2), + borderColor: isDownloading ? Colors.black : Colors.transparent, + borderStrokeWidth: 3, + ), + ], + ), + MultiRegion() => + throw UnsupportedError('Cannot support `MultiRegion`s here'), + }; + } } diff --git a/example/lib/src/screens/main/map_view/map_view.dart b/example/lib/src/screens/main/map_view/map_view.dart index 6a77633c..006cb388 100644 --- a/example/lib/src/screens/main/map_view/map_view.dart +++ b/example/lib/src/screens/main/map_view/map_view.dart @@ -1,4 +1,4 @@ -//import 'dart:async'; +import 'dart:async'; import 'dart:io'; import 'dart:math'; @@ -13,12 +13,12 @@ import 'package:provider/provider.dart'; import '../../../shared/misc/shared_preferences.dart'; import '../../../shared/misc/store_metadata_keys.dart'; -//import '../../../shared/state/download_provider.dart'; +import '../../../shared/state/download_provider.dart'; import '../../../shared/state/general_provider.dart'; import '../../../shared/state/region_selection_provider.dart'; import 'components/additional_overlay/additional_overlay.dart'; import 'components/debugging_tile_builder/debugging_tile_builder.dart'; -//import 'components/download_progress/download_progress_masker.dart'; +import 'components/download_progress/download_progress_masker.dart'; import 'components/recovery_regions/recovery_regions.dart'; import 'components/region_selection/crosshairs.dart'; import 'components/region_selection/custom_polygon_snapping_indicator.dart'; @@ -334,40 +334,40 @@ class _MapViewState extends State with TickerProviderStateMixin { ), ); - //final isDownloadProgressMaskerVisible = widget.mode == - // MapViewMode.downloadRegion && - // context.select((p) => p.isFocused); + final isDownloadProgressMaskerVisible = widget.mode == + MapViewMode.downloadRegion && + context.select((p) => p.isFocused); final map = FlutterMap( mapController: _mapController.mapController, options: mapOptions, children: [ - /*DownloadProgressMasker( - key: ObjectKey( - isDownloadProgressMaskerVisible - ? context - .select( - (p) => p.downloadableRegion, - ) - : null, + DownloadProgressMasker( + key: ValueKey( + context.select( + (p) => p.storeName != null + ? p.downloadableRegion.originalRegion + : null, + ), + ), + isVisible: isDownloadProgressMaskerVisible && + context.select( + (provider) => provider.useMaskEffect, + ), + tileEvents: + context.select?>( + (p) => p.storeName != null ? p.rawTileEventStream : null, + ), + minZoom: context.select( + (p) => + p.storeName != null ? p.downloadableRegion.minZoom : 0, + ), + maxZoom: context.select( + (p) => + p.storeName != null ? p.downloadableRegion.maxZoom : 20, ), - tileEvents: isDownloadProgressMaskerVisible - ? context.select>((p) => p.rawTileEventStream) - : null, - minZoom: isDownloadProgressMaskerVisible - ? context.select( - (p) => p.downloadableRegion.minZoom, - ) - : 0, - maxZoom: isDownloadProgressMaskerVisible - ? context.select( - (p) => p.downloadableRegion.maxZoom, - ) - : 0, child: tileLayer, - ),*/ - tileLayer, + ), if (widget.mode == MapViewMode.downloadRegion) ...[ const RegionShape(), const CustomPolygonSnappingIndicator(), diff --git a/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/components/progress/indicator_text.dart b/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/components/progress/indicator_text.dart index c2dcd8df..ffaddbf4 100644 --- a/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/components/progress/indicator_text.dart +++ b/example/lib/src/screens/main/secondary_view/contents/downloading/components/statistics/components/progress/indicator_text.dart @@ -79,26 +79,55 @@ class _ProgressIndicatorTextState extends State { return Column( children: [ - Align( - alignment: Alignment.centerRight, - child: SegmentedButton( - segments: const [ - ButtonSegment( - value: false, - icon: Icon(Icons.numbers), - tooltip: 'Show tile counts', - ), - ButtonSegment( - value: true, - icon: Icon(Icons.percent), - tooltip: 'Show percentages', + Row( + mainAxisAlignment: MainAxisAlignment.end, + spacing: 12, + children: [ + Tooltip( + message: 'Use mask effect', + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 6) + + const EdgeInsets.only(left: 6), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceDim, + borderRadius: BorderRadius.circular(99), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + const Icon(Icons.gradient), + Switch.adaptive( + value: context.select( + (provider) => provider.useMaskEffect, + ), + onChanged: (val) => context + .read() + .useMaskEffect = val, + ), + ], + ), ), - ], - selected: {_usePercentages}, - onSelectionChanged: (v) => - setState(() => _usePercentages = v.single), - showSelectedIcon: false, - ), + ), + SegmentedButton( + segments: const [ + ButtonSegment( + value: false, + icon: Icon(Icons.numbers), + tooltip: 'Show tile counts', + ), + ButtonSegment( + value: true, + icon: Icon(Icons.percent), + tooltip: 'Show percentages', + ), + ], + selected: {_usePercentages}, + onSelectionChanged: (v) => + setState(() => _usePercentages = v.single), + showSelectedIcon: false, + ), + ], ), const SizedBox(height: 8), _TextRow( diff --git a/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/tiles/unspecified_tile.dart b/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/tiles/unspecified_tile.dart index 7e9e8513..16f6907f 100644 --- a/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/tiles/unspecified_tile.dart +++ b/example/lib/src/screens/main/secondary_view/contents/home/components/stores_list/components/tiles/unspecified_tile.dart @@ -51,14 +51,14 @@ class _UnspecifiedTileState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceDim, - borderRadius: BorderRadius.circular(99), - ), - child: Tooltip( - message: 'Use as fallback only', + Tooltip( + message: 'Use as fallback only', + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceDim, + borderRadius: BorderRadius.circular(99), + ), child: Row( children: [ const Icon(Icons.last_page), diff --git a/example/lib/src/shared/state/download_provider.dart b/example/lib/src/shared/state/download_provider.dart index b23191c8..81535d64 100644 --- a/example/lib/src/shared/state/download_provider.dart +++ b/example/lib/src/shared/state/download_provider.dart @@ -64,7 +64,7 @@ class DownloadingProvider extends ChangeNotifier { }, ); - downloadStreams.tileEvents.listen((evt) { + _rawTileEventsStream!.listen((evt) { // Update stored value _latestTileEvent = evt; notifyListeners(); @@ -96,10 +96,18 @@ class DownloadingProvider extends ChangeNotifier { _isFocused = false; _isComplete = false; _storeName = null; + _downloadableRegion = null; notifyListeners(); } StateError get _notReadyError => StateError( 'Unsafe to retrieve information before a download has been assigned.', ); + + bool _useMaskEffect = true; + bool get useMaskEffect => _useMaskEffect; + set useMaskEffect(bool newState) { + _useMaskEffect = newState; + notifyListeners(); + } } diff --git a/lib/src/providers/image_provider/image_provider.dart b/lib/src/providers/image_provider/image_provider.dart index d9200554..bb2d350a 100644 --- a/lib/src/providers/image_provider/image_provider.dart +++ b/lib/src/providers/image_provider/image_provider.dart @@ -142,16 +142,21 @@ class _FMTCImageProvider extends ImageProvider<_FMTCImageProvider> { @override Future<_FMTCImageProvider> obtainKey(ImageConfiguration configuration) => - SynchronousFuture<_FMTCImageProvider>(this); + SynchronousFuture(this); @override bool operator ==(Object other) => identical(this, other) || (other is _FMTCImageProvider && - other.coords == coords && - other.provider == provider && - other.options == options); + other.coords == + coords /*&& + other.provider == provider && + other.options == options*/ + ); @override - int get hashCode => Object.hash(coords, provider, options); + int get hashCode => coords + .hashCode; /*Object.hash( + coords, /*, provider, options*/ + );*/ }