diff --git a/app/lib/widget/map_browser.dart b/app/lib/widget/map_browser.dart index 4b169717..64c2986e 100644 --- a/app/lib/widget/map_browser.dart +++ b/app/lib/widget/map_browser.dart @@ -23,6 +23,7 @@ import 'package:nc_photos/exception_event.dart'; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/stream_extension.dart'; +import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/theme/dimension.dart'; import 'package:nc_photos/widget/collection_browser.dart'; diff --git a/app/lib/widget/map_browser/type.dart b/app/lib/widget/map_browser/type.dart index 58d607e5..3cccb040 100644 --- a/app/lib/widget/map_browser/type.dart +++ b/app/lib/widget/map_browser/type.dart @@ -14,16 +14,8 @@ class _DataPoint extends DataPoint { final int fileId; } -class _GoogleMarkerBuilder { - _GoogleMarkerBuilder(this.context); - - Future build(List dataPoints) { - return _getClusterBitmap( - _getMarkerSize(dataPoints.length), - text: _getMarkerCountString(dataPoints.length), - color: _getMarkerColor(dataPoints.length), - ); - } +class _MarkerBuilder { + _MarkerBuilder(this.context); String _getMarkerCountString(int count) { switch (count) { @@ -40,7 +32,7 @@ class _GoogleMarkerBuilder { } } - Color _getMarkerColor(int count) { + double _getMarkerRatio(int count) { const step = 1 / 4; final double r; switch (count) { @@ -55,43 +47,77 @@ class _GoogleMarkerBuilder { default: r = (count / 10) * step; } - if (Theme.of(context).brightness == Brightness.light) { - return HSLColor.fromAHSL( - 1, - _colorHsl.hue, - r * .7 + .3, - (_colorHsl.lightness - (.1 - r * .1)).clamp(0, 1), - ).toColor(); - } else { - return HSLColor.fromAHSL( - 1, - _colorHsl.hue, - r * .6 + .4, - (_colorHsl.lightness - (.1 - r * .1)).clamp(0, 1), - ).toColor(); - } + return r; } - int _getMarkerSize(int count) { - const step = 1 / 4; - final double r; - switch (count) { - case >= 10000: - r = 1; - case >= 1000: - r = (count ~/ 1000) / 10 * step + step * 3; - case >= 100: - r = (count ~/ 100) / 10 * step + step * 2; - case >= 10: - r = (count ~/ 10) / 10 * step + step; - default: - r = (count / 10) * step; - } - return (r * 85).toInt() + 85; + final BuildContext context; + + late final _minColorHsl = + HSLColor.fromColor(Theme.of(context).colorScheme.primary) + .withSaturation( + Theme.of(context).brightness == Brightness.light ? .9 : .7) + .withLightness( + Theme.of(context).brightness == Brightness.light ? .4 : .3); + late final _maxColorHsl = + HSLColor.fromColor(Theme.of(context).colorScheme.primary) + .withSaturation( + Theme.of(context).brightness == Brightness.light ? .9 : .7) + .withLightness( + Theme.of(context).brightness == Brightness.light ? .3 : .2); +} + +class _OsmMarkerBuilder extends _MarkerBuilder { + _OsmMarkerBuilder(super.context); + + Widget build(List dataPoints) { + final text = _getMarkerCountString(dataPoints.length); + return _OsmMarker( + size: _getMarkerSize(dataPoints.length), + text: text, + textSize: _getMarkerTextSize(text, dataPoints.length), + color: _getMarkerColor(dataPoints.length), + ); + } + + double _getMarkerSize(int count) { + final r = _getMarkerRatio(count); + return (r * 28).toInt() + 28; + } + + double _getMarkerTextSize(String text, int count) { + final r = _getMarkerRatio(count); + return (r * 3) + 9 - ((text.length / 6) * 1); + } + + Color _getMarkerColor(int count) { + final r = _getMarkerRatio(count); + return HSLColor.lerp(_minColorHsl, _maxColorHsl, r)!.toColor(); + } +} + +class _GoogleMarkerBuilder extends _MarkerBuilder { + _GoogleMarkerBuilder(super.context); + + Future build(List dataPoints) { + return _getClusterBitmap( + _getMarkerSize(dataPoints.length), + text: _getMarkerCountString(dataPoints.length), + color: _getMarkerColor(dataPoints.length), + ); + } + + double _getMarkerSize(int count) { + final r = _getMarkerRatio(count); + return (r * 75).toInt() + 100; + } + + Color _getMarkerColor(int count) { + final r = _getMarkerRatio(count); + return HSLColor.lerp(_minColorHsl, _maxColorHsl, r)!.toColor(); } Future _getClusterBitmap( - int size, { + double size, { String? text, required Color color, }) async { @@ -99,9 +125,7 @@ class _GoogleMarkerBuilder { final Canvas canvas = Canvas(pictureRecorder); final fillPaint = Paint()..color = color; final outlinePaint = Paint() - ..color = Theme.of(context).brightness == Brightness.light - ? Colors.black.withOpacity(.28) - : Colors.white.withOpacity(.6) + ..color = Colors.white.withOpacity(.75) ..strokeWidth = size / 28 ..style = PaintingStyle.stroke; const shadowPadding = 6.0; @@ -126,7 +150,7 @@ class _GoogleMarkerBuilder { text: text, style: TextStyle( fontSize: size / 3 - ((text.length / 6) * (size * 0.1)), - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: Colors.white.withOpacity(.75), fontWeight: FontWeight.normal, ), ); @@ -139,15 +163,11 @@ class _GoogleMarkerBuilder { ), ); } - final img = await pictureRecorder.endRecording().toImage(size, size); + final img = + await pictureRecorder.endRecording().toImage(size.ceil(), size.ceil()); final data = await img.toByteData(format: ImageByteFormat.png) as ByteData; return BitmapDescriptor.fromBytes(data.buffer.asUint8List()); } - - final BuildContext context; - - late final _colorHsl = - HSLColor.fromColor(Theme.of(context).colorScheme.primaryContainer); } enum _DateRangeType { diff --git a/app/lib/widget/map_browser/view.dart b/app/lib/widget/map_browser/view.dart index 10854903..6e69d57a 100644 --- a/app/lib/widget/map_browser/view.dart +++ b/app/lib/widget/map_browser/view.dart @@ -26,42 +26,44 @@ class _MapViewState extends State<_MapView> { builder: (context, state) { final prevPosition = context.read().mapBrowserPrevPositionValue; - return InteractiveMap( - providerHint: GpsMapProvider.google, - initialPosition: prevPosition ?? const MapCoord(0, 0), - initialZoom: prevPosition == null ? 2.5 : 10, - dataPoints: state.data, - onClusterTap: (dataPoints) { - final c = Collection( - name: "", - contentProvider: CollectionAdHocProvider( - account: context.bloc.account, - fileIds: dataPoints - .cast<_DataPoint>() - .map((e) => e.fileId) - .toList(), - ), - ); - Navigator.of(context).pushNamed( - CollectionBrowser.routeName, - arguments: CollectionBrowserArguments(c), - ); - }, - googleClusterBuilder: (context, dataPoints) => - _GoogleMarkerBuilder(context).build(dataPoints), - osmClusterBuilder: (context, dataPoints) => _OsmMarker( - count: dataPoints.length, + return ValueStreamBuilder( + stream: context.bloc.prefController.gpsMapProvider, + builder: (context, gpsMapProvider) => InteractiveMap( + providerHint: gpsMapProvider.requireData, + initialPosition: prevPosition ?? const MapCoord(0, 0), + initialZoom: prevPosition == null ? 2.5 : 10, + dataPoints: state.data, + onClusterTap: (dataPoints) { + final c = Collection( + name: "", + contentProvider: CollectionAdHocProvider( + account: context.bloc.account, + fileIds: dataPoints + .cast<_DataPoint>() + .map((e) => e.fileId) + .toList(), + ), + ); + Navigator.of(context).pushNamed( + CollectionBrowser.routeName, + arguments: CollectionBrowserArguments(c), + ); + }, + googleClusterBuilder: (context, dataPoints) => + _GoogleMarkerBuilder(context).build(dataPoints), + osmClusterBuilder: (context, dataPoints) => + _OsmMarkerBuilder(context).build(dataPoints), + contentPadding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top, + bottom: MediaQuery.of(context).padding.bottom, + ), + onMapCreated: (controller) { + _controller = controller; + if (state.initialPoint != null) { + controller.setPosition(state.initialPoint!); + } + }, ), - contentPadding: EdgeInsets.only( - top: MediaQuery.of(context).padding.top, - bottom: MediaQuery.of(context).padding.bottom, - ), - onMapCreated: (controller) { - _controller = controller; - if (state.initialPoint != null) { - controller.setPosition(state.initialPoint!); - } - }, ); }, ), @@ -73,26 +75,50 @@ class _MapViewState extends State<_MapView> { class _OsmMarker extends StatelessWidget { const _OsmMarker({ - required this.count, + required this.size, + required this.text, + required this.textSize, + required this.color, }); @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Theme.of(context).colorScheme.primary, - ), - child: Center( - child: Text( - count.toString(), - style: const TextStyle(color: Colors.white), + return Center( + child: Container( + width: size, + height: size, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(size / 2), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(.3), + blurRadius: 2, + offset: const Offset(1, 1), + ), + ], + border: Border.all( + color: Colors.white.withOpacity(.75), + width: 1.5, + ), + color: color, + ), + child: Center( + child: Text( + text, + style: TextStyle( + fontSize: textSize, + color: Colors.white.withOpacity(.75), + ), + ), ), ), ); } - final int count; + final double size; + final String text; + final double textSize; + final Color color; } class _PanelContainer extends StatefulWidget { diff --git a/np_gps_map/lib/src/interactive_map/osm.dart b/np_gps_map/lib/src/interactive_map/osm.dart index b7b0ca43..6ad2ebc8 100644 --- a/np_gps_map/lib/src/interactive_map/osm.dart +++ b/np_gps_map/lib/src/interactive_map/osm.dart @@ -73,6 +73,12 @@ class _OsmInteractiveMapState extends State { context, markers.cast<_OsmDataPoint>().map((e) => e.original).toList(), ), + // need to be large enough to contain markers of all size + size: const Size.square(120), + // disable all tap handlers from package + zoomToBoundsOnClick: false, + centerMarkerOnClick: false, + spiderfyCluster: false, ), ), Padding(