feat: add Binance Pay webhook handler and implement payment order creation in billing service
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user