info: custom marker on map
This commit is contained in:
parent
49637ede95
commit
96422e3340
5 changed files with 263 additions and 43 deletions
|
@ -146,7 +146,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
if (widget.background != null)
|
||||
if (hasBackground)
|
||||
ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: widget.background,
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
|||
import 'package:aves/widgets/fullscreen/info/maps/common.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/google_map.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/leaflet_map.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/marker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LocationSection extends StatefulWidget {
|
||||
|
@ -34,6 +35,9 @@ class LocationSection extends StatefulWidget {
|
|||
class _LocationSectionState extends State<LocationSection> {
|
||||
String _loadedUri;
|
||||
|
||||
static const extent = 48.0;
|
||||
static const pointerSize = Size(8.0, 6.0);
|
||||
|
||||
CollectionLens get collection => widget.collection;
|
||||
|
||||
ImageEntry get entry => widget.entry;
|
||||
|
@ -85,6 +89,14 @@ class _LocationSectionState extends State<LocationSection> {
|
|||
if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place));
|
||||
}
|
||||
|
||||
Widget buildMarker(BuildContext context) {
|
||||
return ImageMarker(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
pointerSize: pointerSize,
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -96,16 +108,19 @@ class _LocationSectionState extends State<LocationSection> {
|
|||
},
|
||||
child: settings.infoMapStyle.isGoogleMaps
|
||||
? EntryGoogleMap(
|
||||
markerId: entry.uri ?? entry.path,
|
||||
latLng: entry.latLng,
|
||||
geoUri: entry.geoUri,
|
||||
initialZoom: settings.infoMapZoom,
|
||||
markerId: entry.uri ?? entry.path,
|
||||
markerBuilder: buildMarker,
|
||||
)
|
||||
: EntryLeafletMap(
|
||||
latLng: entry.latLng,
|
||||
geoUri: entry.geoUri,
|
||||
initialZoom: settings.infoMapZoom,
|
||||
style: settings.infoMapStyle,
|
||||
markerSize: Size(extent, extent + pointerSize.height),
|
||||
markerBuilder: buildMarker,
|
||||
),
|
||||
),
|
||||
if (entry.hasGps)
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/common.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/maps/marker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class EntryGoogleMap extends StatefulWidget {
|
||||
final String markerId;
|
||||
final LatLng latLng;
|
||||
final String geoUri;
|
||||
final double initialZoom;
|
||||
final String markerId;
|
||||
final WidgetBuilder markerBuilder;
|
||||
|
||||
EntryGoogleMap({
|
||||
Key key,
|
||||
this.markerId,
|
||||
Tuple2<double, double> latLng,
|
||||
this.geoUri,
|
||||
this.initialZoom,
|
||||
this.markerId,
|
||||
this.markerBuilder,
|
||||
}) : latLng = LatLng(latLng.item1, latLng.item2),
|
||||
super(key: key);
|
||||
|
||||
|
@ -26,6 +32,13 @@ class EntryGoogleMap extends StatefulWidget {
|
|||
|
||||
class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveClientMixin {
|
||||
GoogleMapController _controller;
|
||||
Completer<Uint8List> _markerLoaderCompleter;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_markerLoaderCompleter = Completer<Uint8List>();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(EntryGoogleMap oldWidget) {
|
||||
|
@ -33,6 +46,9 @@ class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveC
|
|||
if (widget.latLng != oldWidget.latLng && _controller != null) {
|
||||
_controller.moveCamera(CameraUpdate.newLatLng(widget.latLng));
|
||||
}
|
||||
if (widget.markerId != oldWidget.markerId) {
|
||||
_markerLoaderCompleter = Completer<Uint8List>();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -46,6 +62,11 @@ class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveC
|
|||
super.build(context);
|
||||
return Stack(
|
||||
children: [
|
||||
MarkerGeneratorWidget(
|
||||
key: Key(widget.markerId),
|
||||
markers: [widget.markerBuilder(context)],
|
||||
onComplete: (bitmaps) => _markerLoaderCompleter.complete(bitmaps.first),
|
||||
),
|
||||
MapDecorator(
|
||||
child: _buildMap(),
|
||||
),
|
||||
|
@ -58,7 +79,18 @@ class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveC
|
|||
}
|
||||
|
||||
Widget _buildMap() {
|
||||
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
|
||||
return FutureBuilder<Uint8List>(
|
||||
future: _markerLoaderCompleter.future,
|
||||
builder: (context, snapshot) {
|
||||
final markers = <Marker>{};
|
||||
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done) {
|
||||
final markerBytes = snapshot.data;
|
||||
markers.add(Marker(
|
||||
markerId: MarkerId(widget.markerId),
|
||||
icon: BitmapDescriptor.fromBytes(markerBytes),
|
||||
position: widget.latLng,
|
||||
));
|
||||
}
|
||||
return GoogleMap(
|
||||
// GoogleMap init perf issue: https://github.com/flutter/flutter/issues/28493
|
||||
initialCameraPosition: CameraPosition(
|
||||
|
@ -78,14 +110,9 @@ class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveC
|
|||
tiltGesturesEnabled: false,
|
||||
myLocationEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: MarkerId(widget.markerId),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
|
||||
position: widget.latLng,
|
||||
)
|
||||
},
|
||||
markers: markers,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _zoomBy(double amount) {
|
||||
|
|
|
@ -15,6 +15,8 @@ class EntryLeafletMap extends StatefulWidget {
|
|||
final String geoUri;
|
||||
final double initialZoom;
|
||||
final EntryMapStyle style;
|
||||
final Size markerSize;
|
||||
final WidgetBuilder markerBuilder;
|
||||
|
||||
EntryLeafletMap({
|
||||
Key key,
|
||||
|
@ -22,6 +24,8 @@ class EntryLeafletMap extends StatefulWidget {
|
|||
this.geoUri,
|
||||
this.initialZoom,
|
||||
this.style,
|
||||
this.markerBuilder,
|
||||
this.markerSize,
|
||||
}) : latLng = LatLng(latLng.item1, latLng.item2),
|
||||
super(key: key);
|
||||
|
||||
|
@ -32,8 +36,6 @@ class EntryLeafletMap extends StatefulWidget {
|
|||
class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||
final MapController _mapController = MapController();
|
||||
|
||||
static const markerSize = 40.0;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(EntryLeafletMap oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
@ -80,16 +82,10 @@ class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliv
|
|||
options: MarkerLayerOptions(
|
||||
markers: [
|
||||
Marker(
|
||||
width: markerSize,
|
||||
height: markerSize,
|
||||
width: widget.markerSize.width,
|
||||
height: widget.markerSize.height,
|
||||
point: widget.latLng,
|
||||
builder: (ctx) {
|
||||
return Icon(
|
||||
Icons.place,
|
||||
size: markerSize,
|
||||
color: Theme.of(context).accentColor,
|
||||
);
|
||||
},
|
||||
builder: widget.markerBuilder,
|
||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||
),
|
||||
],
|
||||
|
|
182
lib/widgets/fullscreen/info/maps/marker.dart
Normal file
182
lib/widgets/fullscreen/info/maps/marker.dart
Normal file
|
@ -0,0 +1,182 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/collection/thumbnail/raster.dart';
|
||||
import 'package:aves/widgets/collection/thumbnail/vector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class ImageMarker extends StatelessWidget {
|
||||
final ImageEntry entry;
|
||||
final double extent;
|
||||
final Size pointerSize;
|
||||
|
||||
static const double outerBorderRadiusDim = 8;
|
||||
static const double outerBorderWidth = 1.5;
|
||||
static const double innerBorderWidth = 2;
|
||||
static const outerBorderColor = Colors.white30;
|
||||
static final innerBorderColor = Colors.grey[900];
|
||||
|
||||
const ImageMarker({
|
||||
@required this.entry,
|
||||
@required this.extent,
|
||||
this.pointerSize = Size.zero,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final thumbnail = entry.isSvg
|
||||
? ThumbnailVectorImage(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
)
|
||||
: ThumbnailRasterImage(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
);
|
||||
|
||||
final outerBorderRadius = BorderRadius.circular(outerBorderRadiusDim);
|
||||
final innerBorderRadius = BorderRadius.circular(outerBorderRadiusDim - outerBorderWidth);
|
||||
|
||||
return CustomPaint(
|
||||
foregroundPainter: MarkerPointerPainter(
|
||||
color: innerBorderColor,
|
||||
outlineColor: outerBorderColor,
|
||||
outlineWidth: outerBorderWidth,
|
||||
size: pointerSize,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: pointerSize.height),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: outerBorderColor,
|
||||
width: outerBorderWidth,
|
||||
),
|
||||
borderRadius: outerBorderRadius,
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: innerBorderColor,
|
||||
width: innerBorderWidth,
|
||||
),
|
||||
borderRadius: innerBorderRadius,
|
||||
),
|
||||
position: DecorationPosition.foreground,
|
||||
child: ClipRRect(
|
||||
borderRadius: innerBorderRadius,
|
||||
child: thumbnail,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MarkerPointerPainter extends CustomPainter {
|
||||
final Color color, outlineColor;
|
||||
final double outlineWidth;
|
||||
final Size size;
|
||||
|
||||
const MarkerPointerPainter({
|
||||
this.color,
|
||||
this.outlineColor,
|
||||
this.outlineWidth,
|
||||
this.size,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final pointerWidth = this.size.width;
|
||||
final pointerHeight = this.size.height;
|
||||
|
||||
final bottomCenter = Offset(size.width / 2, size.height);
|
||||
final topLeft = bottomCenter + Offset(-pointerWidth / 2, -pointerHeight);
|
||||
final topRight = bottomCenter + Offset(pointerWidth / 2, -pointerHeight);
|
||||
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(bottomCenter.dx, bottomCenter.dy)
|
||||
..lineTo(topRight.dx, topRight.dy)
|
||||
..lineTo(topLeft.dx, topLeft.dy)
|
||||
..close(),
|
||||
Paint()..color = outlineColor);
|
||||
|
||||
canvas.translate(0, -outlineWidth.ceilToDouble());
|
||||
canvas.drawPath(
|
||||
Path()
|
||||
..moveTo(bottomCenter.dx, bottomCenter.dy)
|
||||
..lineTo(topRight.dx, topRight.dy)
|
||||
..lineTo(topLeft.dx, topLeft.dy)
|
||||
..close(),
|
||||
Paint()..color = color);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
// generate bitmap from widget, for Google Maps
|
||||
class MarkerGeneratorWidget extends StatefulWidget {
|
||||
final List<Widget> markers;
|
||||
final Duration delay;
|
||||
final Function(List<Uint8List> bitmaps) onComplete;
|
||||
|
||||
const MarkerGeneratorWidget({
|
||||
Key key,
|
||||
@required this.markers,
|
||||
this.delay = Duration.zero,
|
||||
@required this.onComplete,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MarkerGeneratorWidgetState createState() => _MarkerGeneratorWidgetState();
|
||||
}
|
||||
|
||||
class _MarkerGeneratorWidgetState extends State<MarkerGeneratorWidget> {
|
||||
final _globalKeys = <GlobalKey>[];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (widget.delay > Duration.zero) {
|
||||
await Future.delayed(widget.delay);
|
||||
}
|
||||
widget.onComplete(await _getBitmaps(context));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Transform.translate(
|
||||
offset: Offset(MediaQuery.of(context).size.width, 0),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Stack(
|
||||
children: widget.markers.map((i) {
|
||||
final key = GlobalKey();
|
||||
_globalKeys.add(key);
|
||||
return RepaintBoundary(
|
||||
key: key,
|
||||
child: i,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Uint8List>> _getBitmaps(BuildContext context) async {
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
return Future.wait(_globalKeys.map((key) async {
|
||||
RenderRepaintBoundary boundary = key.currentContext.findRenderObject();
|
||||
final image = await boundary.toImage(pixelRatio: pixelRatio);
|
||||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
return byteData.buffer.asUint8List();
|
||||
}));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue