feat: add Binance Pay webhook handler and implement payment order creation in billing service

This commit is contained in:
Hamza-Ayed
2026-07-14 21:46:48 +03:00
parent cc62019609
commit 790bfcefc8
29 changed files with 6366 additions and 122 deletions
@@ -1,5 +1,4 @@
import 'dart:typed_data' show Uint8List;
import 'dart:ui' show Offset;
/// Defines a bitmap image for use as a [Marker] icon.
///
@@ -9,7 +8,6 @@ class InlqBitmap {
const InlqBitmap._({
required this.mapLibreImageId,
this.size,
this.offset,
this.bytes,
this.assetName,
});
@@ -20,8 +18,6 @@ class InlqBitmap {
/// Optional size multiplier (MapLibre iconSize).
final double? size;
/// Optional pixel offset from the anchor point.
final Offset? offset;
/// Raw PNG/JPEG bytes (used with [fromBytes]).
final Uint8List? bytes;
+195 -26
View File
@@ -1,7 +1,6 @@
import 'dart:ui' show Offset, Color;
import 'package:flutter/foundation.dart' show VoidCallback, ValueChanged;
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
import '../constants/colors.dart';
import 'bitmap.dart';
import 'info_window.dart';
@@ -9,46 +8,74 @@ import 'info_window.dart';
// ID types (identical contract to google_maps_flutter)
// ─────────────────────────────────────────────────────────────
/// Uniquely identifies a [Marker] on the map.
class MarkerId {
/// Creates a [MarkerId] with the given [value].
const MarkerId(this.value);
/// The unique string identifier.
final String value;
@override
bool operator ==(Object o) => o is MarkerId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'MarkerId($value)';
}
/// Uniquely identifies a [Polyline] on the map.
class PolylineId {
/// Creates a [PolylineId] with the given [value].
const PolylineId(this.value);
/// The unique string identifier.
final String value;
@override
bool operator ==(Object o) => o is PolylineId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'PolylineId($value)';
}
/// Uniquely identifies a [Circle] on the map.
class CircleId {
/// Creates a [CircleId] with the given [value].
const CircleId(this.value);
/// The unique string identifier.
final String value;
@override
bool operator ==(Object o) => o is CircleId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'CircleId($value)';
}
/// Uniquely identifies a [Polygon] on the map.
class PolygonId {
/// Creates a [PolygonId] with the given [value].
const PolygonId(this.value);
/// The unique string identifier.
final String value;
@override
bool operator ==(Object o) => o is PolygonId && o.value == value;
@override
int get hashCode => value.hashCode;
@override
String toString() => 'PolygonId($value)';
}
@@ -57,7 +84,9 @@ class PolygonId {
// Marker (mirrors google_maps_flutter Marker)
// ─────────────────────────────────────────────────────────────
/// A marker that is placed at a specific geographical location on the map.
class Marker {
/// Creates a [Marker].
const Marker({
required this.markerId,
required this.position,
@@ -76,7 +105,10 @@ class Marker {
this.onDragEnd,
});
/// Unique identifier for this marker.
final MarkerId markerId;
/// Geographical location of the marker.
final mgl.LatLng position;
/// Opacity of the marker icon, from 0.0 (transparent) to 1.0 (opaque).
@@ -165,7 +197,24 @@ class Marker {
draggable: draggable,
zIndex: zIndex.toInt(),
textField: infoWindow.title,
iconOffset: icon.offset,
textAnchor: 'bottom',
textOffset: const Offset(0, -3.0),
textSize: 12.0,
textColor: (infoWindow.snippet != null &&
(infoWindow.snippet == 'start' ||
infoWindow.snippet == 'end' ||
infoWindow.snippet!.startsWith('stop_'))) ? '#FFFFFF' : '#000000',
textHaloColor: infoWindow.snippet == 'start' ? '#4CAF50'
: (infoWindow.snippet == 'end' ? '#F44336'
: (infoWindow.snippet == 'stop_0' ? '#FF9800' // Orange
: (infoWindow.snippet == 'stop_1' ? '#9C27B0' // Purple
: '#FFFFFF'))),
textHaloWidth: 3.0,
// The bundled Intaleq styles serve glyphs from a host that only carries
// Noto Sans. Without an explicit font, the annotation layer falls back to
// "Open Sans Regular, Arial Unicode MS Regular"; that glyph request 404s
// and MapLibre then drops the entire symbol — icon included — on iOS.
fontNames: infoWindow.title != null ? const ['Noto Sans Regular'] : null,
);
}
@@ -179,27 +228,54 @@ class Marker {
}
@override
bool operator ==(Object o) => o is Marker && o.markerId == markerId;
bool operator ==(Object o) =>
o is Marker &&
o.markerId == markerId &&
_latLngEquals(o.position, position) &&
o.alpha == alpha &&
o.anchor == anchor &&
o.draggable == draggable &&
o.flat == flat &&
o.icon == icon &&
o.infoWindow == infoWindow &&
o.rotation == rotation &&
o.visible == visible &&
o.zIndex == zIndex;
@override
int get hashCode => markerId.hashCode;
int get hashCode => Object.hash(
markerId,
_latLngHash(position),
alpha,
anchor,
draggable,
flat,
icon,
infoWindow,
rotation,
visible,
zIndex,
);
}
// ─────────────────────────────────────────────────────────────
// Polyline (mirrors google_maps_flutter Polyline)
// ─────────────────────────────────────────────────────────────
/// A polyline is a list of segments that join a sequence of [mgl.LatLng] locations.
class Polyline {
/// Creates a [Polyline].
const Polyline({
required this.polylineId,
required this.points,
this.color = const Color(0xFF0D47A1),
this.width = 5,
this.width = 5.0,
this.visible = true,
this.zIndex = 0,
this.geodesic = false,
this.onTap,
});
/// Unique identifier for this polyline.
final PolylineId polylineId;
/// The ordered list of points that make up the polyline.
@@ -209,7 +285,7 @@ class Polyline {
final Color color;
/// Line stroke width in screen pixels.
final int width;
final double width;
/// Whether the polyline is visible on the map.
final bool visible;
@@ -225,7 +301,7 @@ class Polyline {
Polyline copyWith({
List<mgl.LatLng>? points,
Color? color,
int? width,
double? width,
bool? visible,
int? zIndex,
bool? geodesic,
@@ -243,40 +319,59 @@ class Polyline {
);
}
/// Internal: converts to MapLibre LineOptions.
/// Internal: converts to MapLibre LineOptions.
mgl.LineOptions toLineOptions() {
return mgl.LineOptions(
geometry: points,
lineColor: _colorToHex(color),
lineWidth: width.toDouble(),
lineOpacity: visible ? color.opacity : 0.0,
lineWidth: width < 5.0 ? 5.0 : width,
lineJoin: 'round',
lineOpacity: 0.85,
);
}
@override
bool operator ==(Object o) => o is Polyline && o.polylineId == polylineId;
bool operator ==(Object o) =>
o is Polyline &&
o.polylineId == polylineId &&
o.color == color &&
o.width == width &&
o.visible == visible &&
o.zIndex == zIndex &&
o.geodesic == geodesic &&
_latLngListEquals(o.points, points);
@override
int get hashCode => polylineId.hashCode;
int get hashCode => Object.hash(
polylineId,
color,
width,
visible,
zIndex,
geodesic,
Object.hashAll(points.map(_latLngHash)),
);
}
// ─────────────────────────────────────────────────────────────
// Circle (mirrors google_maps_flutter Circle)
// ─────────────────────────────────────────────────────────────
/// A circle on the map surface.
class Circle {
/// Creates a [Circle].
const Circle({
required this.circleId,
required this.center,
required this.radius,
this.fillColor = const Color(0x1A0D47A1),
this.strokeColor = const Color(0xFF0D47A1),
this.strokeWidth = 2,
this.strokeWidth = 2.0,
this.visible = true,
this.zIndex = 0,
this.onTap,
});
/// Unique identifier for this circle.
final CircleId circleId;
/// Center of the circle.
@@ -292,7 +387,7 @@ class Circle {
final Color strokeColor;
/// Stroke width in pixels.
final int strokeWidth;
final double strokeWidth;
final bool visible;
final int zIndex;
@@ -305,7 +400,7 @@ class Circle {
double? radius,
Color? fillColor,
Color? strokeColor,
int? strokeWidth,
double? strokeWidth,
bool? visible,
int? zIndex,
VoidCallback? onTap,
@@ -332,37 +427,58 @@ class Circle {
circleRadius:
radius / 10, // approximate; use addCircleAccurate for precision
circleColor: _colorToHex(fillColor),
circleOpacity: visible ? fillColor.opacity : 0.0,
circleOpacity: visible ? fillColor.a : 0.0,
circleStrokeColor: _colorToHex(strokeColor),
circleStrokeWidth: strokeWidth.toDouble(),
circleStrokeOpacity: visible ? strokeColor.opacity : 0.0,
circleStrokeWidth: strokeWidth,
circleStrokeOpacity: visible ? strokeColor.a : 0.0,
);
}
@override
bool operator ==(Object o) => o is Circle && o.circleId == circleId;
bool operator ==(Object o) =>
o is Circle &&
o.circleId == circleId &&
_latLngEquals(o.center, center) &&
o.radius == radius &&
o.fillColor == fillColor &&
o.strokeColor == strokeColor &&
o.strokeWidth == strokeWidth &&
o.visible == visible &&
o.zIndex == zIndex;
@override
int get hashCode => circleId.hashCode;
int get hashCode => Object.hash(
circleId,
_latLngHash(center),
radius,
fillColor,
strokeColor,
strokeWidth,
visible,
zIndex,
);
}
// ─────────────────────────────────────────────────────────────
// Polygon (mirrors google_maps_flutter Polygon)
// ─────────────────────────────────────────────────────────────
/// A polygon on the map surface.
class Polygon {
/// Creates a [Polygon].
const Polygon({
required this.polygonId,
required this.points,
this.holes = const [],
this.fillColor = const Color(0x1ABDBDBD),
this.strokeColor = const Color(0xFF0D47A1),
this.strokeWidth = 2,
this.strokeWidth = 2.0,
this.visible = true,
this.zIndex = 0,
this.geodesic = false,
this.onTap,
});
/// Unique identifier for this polygon.
final PolygonId polygonId;
/// Outer boundary of the polygon.
@@ -378,7 +494,7 @@ class Polygon {
final Color strokeColor;
/// Outline stroke width in pixels.
final int strokeWidth;
final double strokeWidth;
final bool visible;
final int zIndex;
@@ -392,7 +508,7 @@ class Polygon {
List<List<mgl.LatLng>>? holes,
Color? fillColor,
Color? strokeColor,
int? strokeWidth,
double? strokeWidth,
bool? visible,
int? zIndex,
bool? geodesic,
@@ -419,15 +535,34 @@ class Polygon {
return mgl.FillOptions(
geometry: rings,
fillColor: _colorToHex(fillColor),
fillOpacity: visible ? fillColor.opacity : 0.0,
fillOpacity: visible ? fillColor.a : 0.0,
fillOutlineColor: _colorToHex(strokeColor),
);
}
@override
bool operator ==(Object o) => o is Polygon && o.polygonId == polygonId;
bool operator ==(Object o) =>
o is Polygon &&
o.polygonId == polygonId &&
o.fillColor == fillColor &&
o.strokeColor == strokeColor &&
o.strokeWidth == strokeWidth &&
o.visible == visible &&
o.zIndex == zIndex &&
o.geodesic == geodesic &&
_latLngListEquals(o.points, points) &&
_latLngListListEquals(o.holes, holes);
@override
int get hashCode => polygonId.hashCode;
int get hashCode => Object.hash(
polygonId,
fillColor,
strokeColor,
strokeWidth,
visible,
zIndex,
geodesic,
Object.hashAll(points.map(_latLngHash)),
);
}
// ─────────────────────────────────────────────────────────────
@@ -440,3 +575,37 @@ String _colorToHex(Color color) {
'${color.green.toRadixString(16).padLeft(2, '0')}'
'${color.blue.toRadixString(16).padLeft(2, '0')}';
}
// ─────────────────────────────────────────────────────────────
// LatLng value equality
//
// mgl.LatLng does not override == / hashCode, so two coordinates
// with identical lat/lng are NOT equal by default (identity only).
// Geometry classes (Marker/Polyline/Circle/Polygon) need real
// content equality so that diffMarkers/diffPolylines/etc. can tell
// apart "same shape, unchanged" from "same id, moved/redrawn" —
// otherwise in-place updates are silently skipped.
// ─────────────────────────────────────────────────────────────
bool _latLngEquals(mgl.LatLng a, mgl.LatLng b) =>
identical(a, b) || (a.latitude == b.latitude && a.longitude == b.longitude);
int _latLngHash(mgl.LatLng p) => Object.hash(p.latitude, p.longitude);
bool _latLngListEquals(List<mgl.LatLng> a, List<mgl.LatLng> b) {
if (identical(a, b)) return true;
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++) {
if (!_latLngEquals(a[i], b[i])) return false;
}
return true;
}
bool _latLngListListEquals(List<List<mgl.LatLng>> a, List<List<mgl.LatLng>> b) {
if (identical(a, b)) return true;
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++) {
if (!_latLngListEquals(a[i], b[i])) return false;
}
return true;
}
+149 -3
View File
@@ -1,5 +1,5 @@
import 'package:maplibre_gl/maplibre_gl.dart'
show LatLngBounds, LatLng, CameraPosition;
import 'dart:ui';
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
import '../intaleq_map_controller.dart';
// ─────────────────────────────────────────────────────────────
@@ -48,7 +48,7 @@ class MinMaxZoomPreference {
class CameraTargetBounds {
const CameraTargetBounds(this.bounds);
final LatLngBounds? bounds;
final mgl.LatLngBounds? bounds;
/// Unbounded (default).
static const CameraTargetBounds unbounded = CameraTargetBounds(null);
@@ -61,6 +61,152 @@ class CameraTargetBounds {
// Typedefs (same names as google_maps_flutter)
// ─────────────────────────────────────────────────────────────
/// Callback when the map is created.
typedef MapCreatedCallback = void Function(IntaleqMapController controller);
/// Generic callback for an argument of type [T].
typedef ArgumentCallback<T> = void Function(T argument);
/// Callback for camera position changes.
typedef CameraPositionCallback = void Function(CameraPosition position);
/// Defines a particular camera position.
///
/// This class mirrors `CameraPosition` from `google_maps_flutter`.
class CameraPosition {
/// Creates an immutable representation of the [CameraPosition].
const CameraPosition({
required this.target,
this.bearing = 0.0,
this.tilt = 0.0,
this.zoom = 0.0,
});
/// The location that the camera is pointing at.
final mgl.LatLng target;
/// The direction that the camera is facing in degrees clockwise from north.
final double bearing;
/// The angle, in degrees, of the camera angle from the perpendicular to the map's surface.
final double tilt;
/// The zoom level of the camera.
final double zoom;
/// Serializes this [CameraPosition] to a JSON-compatible map.
Map<String, dynamic> toMap() {
return {
'target': [target.latitude, target.longitude],
'bearing': bearing,
'tilt': tilt,
'zoom': zoom,
};
}
/// Deserializes a [CameraPosition] from a map.
static CameraPosition fromMap(Map<String, dynamic> json) {
final targetList = json['target'] as List<dynamic>;
return CameraPosition(
target: mgl.LatLng(targetList[0] as double, targetList[1] as double),
bearing: (json['bearing'] as num).toDouble(),
tilt: (json['tilt'] as num).toDouble(),
zoom: (json['zoom'] as num).toDouble(),
);
}
/// Internal: Converts to MapLibre's [mgl.CameraPosition].
mgl.CameraPosition toMapLibre() {
return mgl.CameraPosition(
target: target,
bearing: bearing,
tilt: tilt,
zoom: zoom,
);
}
/// Internal: Creates from MapLibre's [mgl.CameraPosition].
static CameraPosition fromMapLibre(mgl.CameraPosition pos) {
return CameraPosition(
target: pos.target,
bearing: pos.bearing,
tilt: pos.tilt,
zoom: pos.zoom,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! CameraPosition) return false;
return target == other.target &&
bearing == other.bearing &&
tilt == other.tilt &&
zoom == other.zoom;
}
@override
int get hashCode => Object.hash(target, bearing, tilt, zoom);
@override
String toString() =>
'CameraPosition(target: $target, zoom: $zoom, bearing: $bearing, tilt: $tilt)';
}
/// Defines a camera move.
///
/// This class mirrors `CameraUpdate` from `google_maps_flutter` but
/// works with our custom [CameraPosition].
class CameraUpdate {
CameraUpdate._(this._raw);
final mgl.CameraUpdate _raw;
/// Returns a [CameraUpdate] that moves the camera to the specified [position].
static CameraUpdate newCameraPosition(CameraPosition position) =>
CameraUpdate._(mgl.CameraUpdate.newCameraPosition(position.toMapLibre()));
/// Returns a [CameraUpdate] that moves the camera to the specified [latLng].
static CameraUpdate newLatLng(mgl.LatLng latLng) =>
CameraUpdate._(mgl.CameraUpdate.newLatLng(latLng));
/// Returns a [CameraUpdate] that moves the camera to the specified [bounds].
static CameraUpdate newLatLngBounds(
mgl.LatLngBounds bounds, {
double left = 0,
double top = 0,
double right = 0,
double bottom = 0,
}) =>
CameraUpdate._(mgl.CameraUpdate.newLatLngBounds(
bounds,
left: left,
top: top,
right: right,
bottom: bottom,
));
/// Returns a [CameraUpdate] that moves the camera to the specified [latLng] and [zoom].
static CameraUpdate newLatLngZoom(mgl.LatLng latLng, double zoom) =>
CameraUpdate._(mgl.CameraUpdate.newLatLngZoom(latLng, zoom));
/// Returns a [CameraUpdate] that scrolls the camera by the specified pixels.
static CameraUpdate scrollBy(double dx, double dy) =>
CameraUpdate._(mgl.CameraUpdate.scrollBy(dx, dy));
/// Returns a [CameraUpdate] that zooms the camera by the specified amount.
static CameraUpdate zoomBy(double amount, [Offset? focus]) =>
CameraUpdate._(mgl.CameraUpdate.zoomBy(amount, focus));
/// Returns a [CameraUpdate] that zooms in the camera.
static CameraUpdate zoomIn() => CameraUpdate._(mgl.CameraUpdate.zoomIn());
/// Returns a [CameraUpdate] that zooms out the camera.
static CameraUpdate zoomOut() => CameraUpdate._(mgl.CameraUpdate.zoomOut());
/// Returns a [CameraUpdate] that zooms the camera to the specified [zoom] level.
static CameraUpdate zoomTo(double zoom) =>
CameraUpdate._(mgl.CameraUpdate.zoomTo(zoom));
/// Internal: Converts to MapLibre's [mgl.CameraUpdate].
mgl.CameraUpdate toMapLibre() => _raw;
}