new changes maps
This commit is contained in:
242
lib/services/maps/map_marker_icons.dart
Normal file
242
lib/services/maps/map_marker_icons.dart
Normal file
@@ -0,0 +1,242 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A marker bitmap plus a stable identity for it.
|
||||
///
|
||||
/// MapLibre registers images on the style by name, so every distinct bitmap
|
||||
/// needs a key that is equal whenever the pixels are equal.
|
||||
@immutable
|
||||
class MarkerIcon {
|
||||
const MarkerIcon(this.key, this.bytes);
|
||||
|
||||
final String key;
|
||||
final Uint8List bytes;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => other is MarkerIcon && other.key == key;
|
||||
|
||||
@override
|
||||
int get hashCode => key.hashCode;
|
||||
}
|
||||
|
||||
/// Marker bitmaps drawn at runtime with a canvas.
|
||||
///
|
||||
/// MapLibre has no equivalent of Google's `BitmapDescriptor.defaultMarker*`,
|
||||
/// so the pins the app used to get for free are drawn here instead. Everything
|
||||
/// is generated in-process — no new image assets, no network fetch.
|
||||
class MapMarkerIcons {
|
||||
const MapMarkerIcons._();
|
||||
|
||||
static final Map<String, MarkerIcon> _cache = {};
|
||||
|
||||
/// Classic teardrop pin, anchored at its tip.
|
||||
static Future<MarkerIcon> pin(Color color) =>
|
||||
_memo('pin_${color.toARGB32()}', () => _drawPin(color));
|
||||
|
||||
/// Filled circle with a white ring — used for the rider's own position.
|
||||
static Future<MarkerIcon> dot(Color color) =>
|
||||
_memo('dot_${color.toARGB32()}', () => _drawDot(color));
|
||||
|
||||
/// Numbered step marker for the multi-stop route screen.
|
||||
static Future<MarkerIcon> numberedCircle(
|
||||
int number, {
|
||||
required Color primaryColor,
|
||||
bool isSkipped = false,
|
||||
bool isNext = false,
|
||||
}) => _memo(
|
||||
'step_${number}_${primaryColor.toARGB32()}_${isSkipped}_$isNext',
|
||||
() => _drawNumberedCircle(
|
||||
number,
|
||||
primaryColor: primaryColor,
|
||||
isSkipped: isSkipped,
|
||||
isNext: isNext,
|
||||
),
|
||||
);
|
||||
|
||||
static Future<MarkerIcon> _memo(
|
||||
String key,
|
||||
Future<Uint8List> Function() build,
|
||||
) async {
|
||||
final cached = _cache[key];
|
||||
if (cached != null) return cached;
|
||||
return _cache[key] = MarkerIcon(key, await build());
|
||||
}
|
||||
|
||||
static Future<Uint8List> _toBytes(
|
||||
ui.PictureRecorder recorder,
|
||||
int width,
|
||||
int height,
|
||||
) async {
|
||||
final image = await recorder.endRecording().toImage(width, height);
|
||||
final data = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
image.dispose();
|
||||
return data!.buffer.asUint8List();
|
||||
}
|
||||
|
||||
static Future<Uint8List> _drawPin(Color color) async {
|
||||
const double width = 72;
|
||||
const double height = 96;
|
||||
const double radius = 27;
|
||||
const center = Offset(width / 2, radius + 4);
|
||||
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
|
||||
final body = Paint()
|
||||
..color = color
|
||||
..isAntiAlias = true;
|
||||
final outline = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4
|
||||
..isAntiAlias = true;
|
||||
|
||||
// Tail: a curve from the circle's lower flanks down to the anchor point.
|
||||
final tail = Path()
|
||||
..moveTo(center.dx - radius + 4, center.dy + radius - 9)
|
||||
..quadraticBezierTo(
|
||||
center.dx - 6,
|
||||
height - 14,
|
||||
center.dx,
|
||||
height - 4,
|
||||
)
|
||||
..quadraticBezierTo(
|
||||
center.dx + 6,
|
||||
height - 14,
|
||||
center.dx + radius - 4,
|
||||
center.dy + radius - 9,
|
||||
)
|
||||
..close();
|
||||
|
||||
canvas.drawPath(tail, body);
|
||||
canvas.drawCircle(center, radius, body);
|
||||
canvas.drawCircle(center, radius, outline);
|
||||
canvas.drawCircle(center, 9, Paint()..color = Colors.white);
|
||||
|
||||
return _toBytes(recorder, width.toInt(), height.toInt());
|
||||
}
|
||||
|
||||
static Future<Uint8List> _drawDot(Color color) async {
|
||||
const double size = 48;
|
||||
const center = Offset(size / 2, size / 2);
|
||||
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
20,
|
||||
Paint()
|
||||
..color = color.withValues(alpha: 0.25)
|
||||
..isAntiAlias = true,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
12,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..isAntiAlias = true,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
9,
|
||||
Paint()
|
||||
..color = color
|
||||
..isAntiAlias = true,
|
||||
);
|
||||
|
||||
return _toBytes(recorder, size.toInt(), size.toInt());
|
||||
}
|
||||
|
||||
/// Preserves the exact look of the previous Google Maps step marker.
|
||||
static Future<Uint8List> _drawNumberedCircle(
|
||||
int number, {
|
||||
required Color primaryColor,
|
||||
required bool isSkipped,
|
||||
required bool isNext,
|
||||
}) async {
|
||||
const double size = 70;
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
const center = Offset(size / 2, size / 2);
|
||||
|
||||
final paint = Paint()..color = isSkipped ? Colors.orange : primaryColor;
|
||||
canvas.drawCircle(center, 15, paint);
|
||||
|
||||
final border = Paint()
|
||||
..color = isSkipped ? Colors.orange.shade900 : Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = isSkipped ? 4 : 3;
|
||||
canvas.drawCircle(center, 15, border);
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: number.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 19,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(
|
||||
center.dx - textPainter.width / 2,
|
||||
center.dy - textPainter.height / 2,
|
||||
),
|
||||
);
|
||||
|
||||
if (isNext) {
|
||||
final signPaint = Paint()
|
||||
..color = Colors.green
|
||||
..style = PaintingStyle.fill;
|
||||
final signRect = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: const Offset(size / 2, 8), width: 45, height: 30),
|
||||
const Radius.circular(4),
|
||||
);
|
||||
canvas.drawRRect(signRect, signPaint);
|
||||
|
||||
final signBorder = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
canvas.drawRRect(signRect, signBorder);
|
||||
|
||||
final nextTextPainter = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'NEXT',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
nextTextPainter.layout();
|
||||
nextTextPainter.paint(
|
||||
canvas,
|
||||
Offset(size / 2 - nextTextPainter.width / 2, 4),
|
||||
);
|
||||
|
||||
final arrowPath = Path()
|
||||
..moveTo(size / 2, 18)
|
||||
..lineTo(size / 2 - 4, 24)
|
||||
..lineTo(size / 2 + 4, 24)
|
||||
..close();
|
||||
canvas.drawPath(
|
||||
arrowPath,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
}
|
||||
|
||||
return _toBytes(recorder, size.toInt(), size.toInt());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user