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,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
import 'intaleq_map_controller.dart';
import 'styles.dart';
import 'offline_service.dart';
import 'models/geometry.dart';
import 'models/types.dart';
@@ -40,9 +42,11 @@ class IntaleqMap extends StatefulWidget {
this.polylines = const {},
this.circles = const {},
this.polygons = const {},
this.onMapCreated,
this.onTap,
this.onLongPress,
this.onMapCreated,
this.onStyleLoaded,
this.onTap,
this.onCameraMove,
this.onCameraMoveStarted,
this.onCameraIdle,
@@ -54,6 +58,7 @@ class IntaleqMap extends StatefulWidget {
this.scrollGesturesEnabled = true,
this.tiltGesturesEnabled = true,
this.zoomGesturesEnabled = true,
this.autoCache = true,
this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
this.cameraTargetBounds = CameraTargetBounds.unbounded,
});
@@ -64,7 +69,7 @@ class IntaleqMap extends StatefulWidget {
final String apiKey;
/// Starting camera position (target + zoom).
final mgl.CameraPosition initialCameraPosition;
final CameraPosition initialCameraPosition;
// ── Map style ──────────────────────────────────────────────
@@ -74,6 +79,9 @@ class IntaleqMap extends StatefulWidget {
/// Override with a custom MapLibre style URL.
final String? styleUrl;
/// Automatically download tiles around the camera center when idle.
final bool autoCache;
// ── Overlays (declarative — mirrors GoogleMap) ─────────────
final Set<Marker> markers;
@@ -86,6 +94,10 @@ class IntaleqMap extends StatefulWidget {
/// Called once the map is ready. Use [IntaleqMapController] for all operations.
final MapCreatedCallback? onMapCreated;
/// Note: If this callback returns a Future, the widget will await it before
/// adding declarative markers and polylines.
final Function()? onStyleLoaded;
// ── Interaction callbacks ──────────────────────────────────
/// Called when the user taps the map (not on a marker).
@@ -128,10 +140,48 @@ class IntaleqMap extends StatefulWidget {
class _IntaleqMapState extends State<IntaleqMap> {
IntaleqMapController? _controller;
bool _isCameraMoving = false;
String? _styleString;
bool _isLoadingStyle = true;
@override
void initState() {
super.initState();
_loadStyle();
}
Future<void> _loadStyle() async {
try {
final url = _resolvedStyleUrl;
print("🔥 [IntaleqMap] Resolving style: $url");
if (url.startsWith('asset://')) {
final assetPath = url.replaceFirst('asset://', '');
_styleString = await rootBundle.loadString(assetPath);
print("🔥 [IntaleqMap] Style loaded from asset string: ${assetPath.split('/').last}");
} else {
_styleString = url;
print("🔥 [IntaleqMap] Using remote style URL: $url");
}
} catch (e) {
print("❌ [IntaleqMap] Failed to load style: $e");
_styleString = 'about:blank';
} finally {
if (mounted) {
setState(() => _isLoadingStyle = false);
}
}
}
@override
void didUpdateWidget(IntaleqMap oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.mapType != widget.mapType ||
oldWidget.styleUrl != widget.styleUrl) {
_loadStyle();
}
final ctrl = _controller;
if (ctrl == null) return;
@@ -144,15 +194,16 @@ class _IntaleqMapState extends State<IntaleqMap> {
String get _resolvedStyleUrl {
if (widget.styleUrl != null) return widget.styleUrl!;
// Default to local assets for performance and offline support
return switch (widget.mapType) {
IntaleqMapType.normal => IntaleqStyles.obsidian(widget.apiKey),
IntaleqMapType.light => IntaleqStyles.light(widget.apiKey),
IntaleqMapType.normal => 'asset://packages/intaleq_maps/assets/style_dark.json',
IntaleqMapType.light => 'asset://packages/intaleq_maps/assets/style.json',
IntaleqMapType.satellite => IntaleqStyles.satellite(widget.apiKey),
IntaleqMapType.none => 'about:blank',
};
}
Future<void> _onMapCreated(mgl.MaplibreMapController rawCtrl) async {
Future<void> _onMapCreated(mgl.MapLibreMapController rawCtrl) async {
// Wire up tap routing before handing the controller to the caller.
rawCtrl.onSymbolTapped.add(_onSymbolTapped);
rawCtrl.onLineTapped.add(_onLineTapped);
@@ -162,13 +213,6 @@ class _IntaleqMapState extends State<IntaleqMap> {
apiKey: widget.apiKey,
);
_controller = ctrl;
// Render the initial overlay sets.
for (final m in widget.markers) await ctrl.addMarker(m);
for (final p in widget.polylines) await ctrl.addPolyline(p);
for (final c in widget.circles) await ctrl.addCircle(c);
for (final g in widget.polygons) await ctrl.addPolygon(g);
widget.onMapCreated?.call(ctrl);
}
@@ -177,25 +221,64 @@ class _IntaleqMapState extends State<IntaleqMap> {
void _onLineTapped(mgl.Line line) => _controller?.onLineTapped(line);
Future<void> _onStyleLoaded() async {
final ctrl = _controller;
if (ctrl == null) return;
await ctrl.onStyleLoaded();
final callbackResult = widget.onStyleLoaded?.call();
if (callbackResult is Future) {
await callbackResult;
}
// Re-render everything from the current declarative sets.
// This ensures overlays persist across style changes (Dark/Light mode)
// and during certain zoom/camera events that trigger style reloads.
for (final m in widget.markers) await ctrl.addMarker(m);
for (final p in widget.polylines) await ctrl.addPolyline(p);
for (final c in widget.circles) await ctrl.addCircle(c);
for (final g in widget.polygons) await ctrl.addPolygon(g);
// Apply defaults AFTER markers have initialized the symbol layer
await ctrl.applyMarkerVisibilityDefaults();
}
@override
Widget build(BuildContext context) {
if (_isLoadingStyle) {
return const Center(child: CircularProgressIndicator());
}
return mgl.MaplibreMap(
styleString: _resolvedStyleUrl,
initialCameraPosition: widget.initialCameraPosition,
styleString: _styleString!,
initialCameraPosition: widget.initialCameraPosition.toMapLibre(),
onMapCreated: _onMapCreated,
onStyleLoadedCallback: _onStyleLoaded,
onMapClick: widget.onTap != null
? (point, latlng) => widget.onTap!(latlng)
: null,
onMapLongClick: widget.onLongPress != null
? (point, latlng) => widget.onLongPress!(latlng)
: null,
onCameraIdle: widget.onCameraIdle,
onCameraIdle: () {
_isCameraMoving = false;
if (widget.autoCache) {
final pos = _controller?.cameraPosition;
if (pos != null) {
IntaleqOfflineService.instance.downloadRegion(
pos.target,
styleUrl: _resolvedStyleUrl,
);
}
}
widget.onCameraIdle?.call();
},
onCameraTrackingChanged: null,
myLocationEnabled: widget.myLocationEnabled,
myLocationRenderMode: widget.myLocationEnabled
? mgl.MyLocationRenderMode.NORMAL
: mgl.MyLocationRenderMode.NORMAL,
myLocationTrackingMode: mgl.MyLocationTrackingMode.None,
? mgl.MyLocationRenderMode.normal
: mgl.MyLocationRenderMode.normal,
myLocationTrackingMode: mgl.MyLocationTrackingMode.none,
compassEnabled: widget.compassEnabled,
rotateGesturesEnabled: widget.rotateGesturesEnabled,
scrollGesturesEnabled: widget.scrollGesturesEnabled,
@@ -208,7 +291,17 @@ class _IntaleqMapState extends State<IntaleqMap> {
cameraTargetBounds: widget.cameraTargetBounds.bounds != null
? mgl.CameraTargetBounds(widget.cameraTargetBounds.bounds!)
: mgl.CameraTargetBounds.unbounded,
trackCameraPosition: widget.onCameraMove != null,
trackCameraPosition:
widget.onCameraMove != null ||
widget.onCameraMoveStarted != null ||
widget.onCameraIdle != null,
onCameraMove: (pos) {
if (!_isCameraMoving && widget.onCameraMoveStarted != null) {
_isCameraMoving = true;
widget.onCameraMoveStarted!();
}
widget.onCameraMove?.call(CameraPosition.fromMapLibre(pos));
},
onCameraTrackingDismissed: null,
);
}