feat: add Binance Pay webhook handler and implement payment order creation in billing service
This commit is contained in:
@@ -40,4 +40,7 @@ FIREBASE_SERVICE_ACCOUNT_PATH=/secrets/firebase-service-account.json
|
|||||||
PAYMOB_API_KEY=ZXlKaGJHY2lPaUpJVXpVeE1pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmpiR0Z6Y3lJNklrMWxjbU5vWVc1MElpd2ljSEp2Wm1sc1pWOXdheUk2T1Rjd09UWXdMQ0p1WVcxbElqb2lhVzVwZEdsaGJDSjkua0ZfRFlCU0Q2QkUtU25TS3FqSWRIbmh4NWxsUUlNQloySjlFZFFsS2NmNjJoeUpMeDRmY2NFOTZuYzVuQ25ocUdPaGJZUHdGRndfOVptU3FjR0pSdXc=
|
PAYMOB_API_KEY=ZXlKaGJHY2lPaUpJVXpVeE1pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmpiR0Z6Y3lJNklrMWxjbU5vWVc1MElpd2ljSEp2Wm1sc1pWOXdheUk2T1Rjd09UWXdMQ0p1WVcxbElqb2lhVzVwZEdsaGJDSjkua0ZfRFlCU0Q2QkUtU25TS3FqSWRIbmh4NWxsUUlNQloySjlFZFFsS2NmNjJoeUpMeDRmY2NFOTZuYzVuQ25ocUdPaGJZUHdGRndfOVptU3FjR0pSdXc=
|
||||||
PAYMOB_HMAC_SECRET=7C9A0BEFC9DC11BF4C5EE05DE61C11F9
|
PAYMOB_HMAC_SECRET=7C9A0BEFC9DC11BF4C5EE05DE61C11F9
|
||||||
PAYMOB_INTEGRATION_ID=4556055
|
PAYMOB_INTEGRATION_ID=4556055
|
||||||
PAYMOB_IFRAME_ID=837992
|
PAYMOB_IFRAME_ID=837992
|
||||||
|
|
||||||
|
BINANCE_PAY_API_KEY="المفتاح_الخاص_بك_هنا"
|
||||||
|
BINANCE_PAY_SECRET_KEY="المفتاح_السري_الخاص_بك_هنا"
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -185,6 +185,78 @@ export class BillingController {
|
|||||||
return { status: 'success' };
|
return { status: 'success' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binance Pay Webhook
|
||||||
|
* Called by Binance when a payment status changes
|
||||||
|
*/
|
||||||
|
@Post('webhooks/binance')
|
||||||
|
@ApiOperation({ summary: 'Binance Payment Webhook' })
|
||||||
|
async handleBinanceWebhook(
|
||||||
|
@Body() body: any,
|
||||||
|
@Req() req: any
|
||||||
|
) {
|
||||||
|
const timestamp = req.headers['binancepay-timestamp'] as string;
|
||||||
|
const nonce = req.headers['binancepay-nonce'] as string;
|
||||||
|
const signature = req.headers['binancepay-signature'] as string;
|
||||||
|
|
||||||
|
this.logger.log(`📥 Binance Webhook Received: Transaction ID ${body.bizId || 'UNKNOWN'}`);
|
||||||
|
|
||||||
|
if (!timestamp || !nonce || !signature) {
|
||||||
|
this.logger.error('❌ Binance Webhook: Missing required headers');
|
||||||
|
throw new BadRequestException('Missing headers');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Verify Signature
|
||||||
|
if (!this.binanceProvider.verifySignature(timestamp, nonce, signature, body)) {
|
||||||
|
this.logger.error('❌ Binance Signature Verification Failed');
|
||||||
|
throw new BadRequestException('Invalid signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Process Payment
|
||||||
|
if (body.bizStatus === 'PAY_SUCCESS') {
|
||||||
|
try {
|
||||||
|
let dataObj: any = {};
|
||||||
|
if (body.data) {
|
||||||
|
dataObj = JSON.parse(body.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tenantId = '';
|
||||||
|
let plan = 'PRO';
|
||||||
|
|
||||||
|
if (dataObj.passThroughInfo) {
|
||||||
|
const passThrough = JSON.parse(dataObj.passThroughInfo);
|
||||||
|
tenantId = passThrough.tenantId;
|
||||||
|
plan = passThrough.plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tenantId) {
|
||||||
|
this.logger.error(`❌ Binance Webhook failed: No tenantId found. Body: ${JSON.stringify(body)}`);
|
||||||
|
return { returnCode: 'FAIL', returnMessage: 'No tenantId found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const externalTxId = dataObj.merchantTradeNo || body.bizId?.toString();
|
||||||
|
|
||||||
|
await this.billingService.processSuccessfulPayment(
|
||||||
|
externalTxId,
|
||||||
|
PaymentProvider.BINANCE,
|
||||||
|
0,
|
||||||
|
{ tenantId, plan }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.log(`✅ Binance Payment Processed Successfully for Tenant ${tenantId}`);
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
this.logger.error(`❌ Failed to process Binance payment: ${err.message}`);
|
||||||
|
return { returnCode: 'FAIL', returnMessage: err.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
returnCode: 'SUCCESS',
|
||||||
|
returnMessage: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(FirebaseAuthGuard)
|
@UseGuards(FirebaseAuthGuard)
|
||||||
@Get('invoices')
|
@Get('invoices')
|
||||||
|
|||||||
@@ -1,33 +1,115 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import axios from 'axios';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BinanceProvider {
|
export class BinanceProvider {
|
||||||
private readonly logger = new Logger(BinanceProvider.name);
|
private readonly logger = new Logger(BinanceProvider.name);
|
||||||
|
private readonly apiUrl = 'https://bpay.binanceapi.com/binancepay/openapi/v2/order';
|
||||||
|
|
||||||
constructor(private configService: ConfigService) {}
|
constructor(private configService: ConfigService) {}
|
||||||
|
|
||||||
/**
|
private get apiKey(): string {
|
||||||
* Mock Binance Pay Order Creation
|
return this.configService.get<string>('BINANCE_PAY_API_KEY');
|
||||||
* Will lead to a success/fail redirect for testing
|
}
|
||||||
*/
|
|
||||||
async createOrder(tenantId: string, amount: number, plan: string) {
|
private get secretKey(): string {
|
||||||
this.logger.log(`[Mock] Creating Binance Pay order for ${tenantId} - ${plan}`);
|
return this.configService.get<string>('BINANCE_PAY_SECRET_KEY');
|
||||||
|
|
||||||
// In production, this calls https://bpay.binanceapi.com/binancepay/openapi/v2/order
|
|
||||||
// and returns a checkoutUrl.
|
|
||||||
|
|
||||||
return {
|
|
||||||
checkoutUrl: `https://map-dashbord.intaleqapp.com/api/billing/mock-binance-success?tenantId=${tenantId}&plan=${plan}`,
|
|
||||||
prepayId: `mock_${Date.now()}`
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock Webhook Signature Verification
|
* Create the Binance Pay Signature
|
||||||
*/
|
*/
|
||||||
verifySignature(payload: any, signature: string): boolean {
|
private generateSignature(timestamp: string, nonce: string, body: any): string {
|
||||||
// In production, uses HMAC-SHA512 with Binance Secret Key
|
const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(body) + '\n';
|
||||||
return true;
|
return crypto
|
||||||
|
.createHmac('sha512', this.secretKey)
|
||||||
|
.update(payload)
|
||||||
|
.digest('hex')
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Binance Pay Order
|
||||||
|
*/
|
||||||
|
async createOrder(tenantId: string, amount: number, plan: string) {
|
||||||
|
this.logger.log(`Creating Binance Pay order for ${tenantId} - ${plan}`);
|
||||||
|
|
||||||
|
if (!this.apiKey || !this.secretKey) {
|
||||||
|
this.logger.error('Binance Pay API keys are not configured');
|
||||||
|
throw new InternalServerErrorException('Payment provider is not configured properly');
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonce = crypto.randomBytes(16).toString('hex');
|
||||||
|
const timestamp = Date.now().toString();
|
||||||
|
const merchantTradeNo = `txn_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
env: {
|
||||||
|
terminalType: 'WEB',
|
||||||
|
},
|
||||||
|
merchantTradeNo: merchantTradeNo,
|
||||||
|
orderAmount: amount,
|
||||||
|
currency: 'USDT',
|
||||||
|
goods: {
|
||||||
|
goodsType: '01',
|
||||||
|
goodsCategory: 'Z000',
|
||||||
|
referenceGoodsId: plan,
|
||||||
|
goodsName: `${plan} Plan Subscription`,
|
||||||
|
goodsDetail: `Subscription to ${plan} plan for Maps SaaS`,
|
||||||
|
},
|
||||||
|
passThroughInfo: JSON.stringify({ tenantId, plan }),
|
||||||
|
returnUrl: `https://map-dashboard.intaleqapp.com/dashboard.html#billing`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const signature = this.generateSignature(timestamp, nonce, body);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(this.apiUrl, body, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'BinancePay-Timestamp': timestamp,
|
||||||
|
'BinancePay-Nonce': nonce,
|
||||||
|
'BinancePay-Certificate-SN': this.apiKey,
|
||||||
|
'BinancePay-Signature': signature,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.status === 'SUCCESS') {
|
||||||
|
return {
|
||||||
|
checkoutUrl: response.data.data.checkoutUrl,
|
||||||
|
orderId: merchantTradeNo,
|
||||||
|
prepayId: response.data.data.prepayId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.logger.error(`Binance API Error: ${JSON.stringify(response.data)}`);
|
||||||
|
throw new InternalServerErrorException('Failed to create Binance order');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
this.logger.error(`Error communicating with Binance API: ${error.message}`);
|
||||||
|
throw new InternalServerErrorException('Failed to communicate with payment provider');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify Webhook Signature
|
||||||
|
*/
|
||||||
|
verifySignature(timestamp: string, nonce: string, signature: string, payloadBody: any): boolean {
|
||||||
|
const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(payloadBody) + '\n';
|
||||||
|
const expectedSignature = crypto
|
||||||
|
.createHmac('sha512', this.secretKey)
|
||||||
|
.update(payload)
|
||||||
|
.digest('hex')
|
||||||
|
.toUpperCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return crypto.timingSafeEqual(
|
||||||
|
Buffer.from(signature || ''),
|
||||||
|
Buffer.from(expectedSignature)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.19.0+2/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.19.0+2/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[{"name":"maplibre_gl_web","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.19.0+2/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"maplibre_gl","dependencies":["maplibre_gl_web"]},{"name":"maplibre_gl_web","dependencies":[]}],"date_created":"2026-04-17 14:04:31.920711","version":"3.41.2","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[{"name":"maplibre_gl_web","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.25.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"maplibre_gl","dependencies":["maplibre_gl_web"]},{"name":"maplibre_gl_web","dependencies":[]}],"date_created":"2026-07-09 03:25:15.926071","version":"3.41.2","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
||||||
@@ -1,3 +1,35 @@
|
|||||||
|
## 2.2.0
|
||||||
|
|
||||||
|
* Added `onStyleLoaded` callback to `IntaleqMap` to handle style initialization.
|
||||||
|
* Improved overlay persistence: Markers, Polylines, Circles, and Polygons are now automatically restored after style changes (e.g., toggling Dark Mode).
|
||||||
|
* Exported MapLibre offline management primitives (`OfflineRegion`, `downloadOfflineRegion`, etc.) for advanced usage.
|
||||||
|
* Exported `MyLocationRenderMode` and `MyLocationTrackingMode` for location UI customization.
|
||||||
|
* Fixed `trackCameraPosition` logic to correctly trigger `onCameraIdle`.
|
||||||
|
|
||||||
|
## 2.1.3
|
||||||
|
|
||||||
|
* Fixed missing `dart:ui` import in `types.dart`.
|
||||||
|
* Verified compatibility with Flutter 3.22.
|
||||||
|
|
||||||
|
## 2.1.2
|
||||||
|
|
||||||
|
* Updated dependencies to latest stable versions (`http`, `lints`, `meta`).
|
||||||
|
* Fixed `onCameraMoveStarted` not being triggered correctly.
|
||||||
|
* Updated `README.md` to reflect the latest version.
|
||||||
|
|
||||||
|
## 2.1.1
|
||||||
|
|
||||||
|
* Finalized static analysis and documentation fixes for maximum pub.dev score.
|
||||||
|
* Renamed deprecated MapLibre components to latest naming conventions.
|
||||||
|
* Fixed deprecated Color member usage.
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
* Fixed static analysis warnings (unused imports and variables).
|
||||||
|
* Added comprehensive documentation for core SDK elements.
|
||||||
|
* Improved API parity with Google Maps Flutter (CameraPosition helpers).
|
||||||
|
* Added an `example/` project demonstrating SDK integration.
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
**Breaking redesign — full Google Maps Flutter API parity.**
|
**Breaking redesign — full Google Maps Flutter API parity.**
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ A **drop-in replacement for `google_maps_flutter`** backed by MapLibre GL, optim
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
intaleq_maps: ^2.0.0
|
intaleq_maps: ^2.2.0
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -160,6 +160,7 @@ IntaleqMap(
|
|||||||
| `circles` | `Set<Circle>` | `{}` | Declarative circle set |
|
| `circles` | `Set<Circle>` | `{}` | Declarative circle set |
|
||||||
| `polygons` | `Set<Polygon>` | `{}` | Declarative polygon set |
|
| `polygons` | `Set<Polygon>` | `{}` | Declarative polygon set |
|
||||||
| `onMapCreated` | `MapCreatedCallback?` | — | Fires once map is ready |
|
| `onMapCreated` | `MapCreatedCallback?` | — | Fires once map is ready |
|
||||||
|
| `onStyleLoaded` | `VoidCallback?` | — | Style fully loaded & ready for overlays |
|
||||||
| `onTap` | `ArgumentCallback<LatLng>?` | — | Map tap |
|
| `onTap` | `ArgumentCallback<LatLng>?` | — | Map tap |
|
||||||
| `onLongPress` | `ArgumentCallback<LatLng>?` | — | Map long press |
|
| `onLongPress` | `ArgumentCallback<LatLng>?` | — | Map long press |
|
||||||
| `onCameraMove` | `CameraPositionCallback?` | — | Camera movement |
|
| `onCameraMove` | `CameraPositionCallback?` | — | Camera movement |
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
|||||||
|
Extension Discovery Cache
|
||||||
|
=========================
|
||||||
|
|
||||||
|
This folder is used by `package:extension_discovery` to cache lists of
|
||||||
|
packages that contains extensions for other packages.
|
||||||
|
|
||||||
|
DO NOT USE THIS FOLDER
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
* Do not read (or rely) the contents of this folder.
|
||||||
|
* Do write to this folder.
|
||||||
|
|
||||||
|
If you're interested in the lists of extensions stored in this folder use the
|
||||||
|
API offered by package `extension_discovery` to get this information.
|
||||||
|
|
||||||
|
If this package doesn't work for your use-case, then don't try to read the
|
||||||
|
contents of this folder. It may change, and will not remain stable.
|
||||||
|
|
||||||
|
Use package `extension_discovery`
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
If you want to access information from this folder.
|
||||||
|
|
||||||
|
Feel free to delete this folder
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Files in this folder act as a cache, and the cache is discarded if the files
|
||||||
|
are older than the modification time of `.dart_tool/package_config.json`.
|
||||||
|
|
||||||
|
Hence, it should never be necessary to clear this cache manually, if you find a
|
||||||
|
need to do please file a bug.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":2,"entries":[{"package":"intaleq_maps","rootUri":"../../","packageUri":"lib/"},{"package":"intaleq_maps_example","rootUri":"../","packageUri":"lib/"}]}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
{
|
||||||
|
"configVersion": 2,
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "archive",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/archive-4.0.9",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/async-2.13.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boolean_selector",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "characters",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/characters-1.4.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/clock-1.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collection",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/collection-1.19.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fake_async",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/fake_async-1.3.3",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ffi",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/ffi-2.2.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/flutter/packages/flutter",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_test",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/flutter/packages/flutter_test",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_web_plugins",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/flutter/packages/flutter_web_plugins",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/http-1.6.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http_parser",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/http_parser-4.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "image",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/image-4.8.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intaleq_maps",
|
||||||
|
"rootUri": "../../",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_flutter_testing",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_testing",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maplibre_gl",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maplibre_gl_platform_interface",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_platform_interface-0.25.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maplibre_gl_web",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.25.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/matcher-0.12.18",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "material_color_utilities",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/material_color_utilities-0.13.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/meta-1.17.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/path-1.9.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "petitparser",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/petitparser-7.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "posix",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/posix-6.5.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sky_engine",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/flutter/bin/cache/pkg/sky_engine",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/source_span-1.10.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stack_trace",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_channel",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_api",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/test_api-0.7.9",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "typed_data",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vector_math",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/vector_math-2.2.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vm_service",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/vm_service-15.1.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/web-1.1.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xml",
|
||||||
|
"rootUri": "file:///Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/xml-6.6.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intaleq_maps_example",
|
||||||
|
"rootUri": "../",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generator": "pub",
|
||||||
|
"generatorVersion": "3.11.0",
|
||||||
|
"flutterRoot": "file:///Users/hamzaaleghwairyeen/flutter",
|
||||||
|
"flutterVersion": "3.41.2",
|
||||||
|
"pubCache": "file:///Users/hamzaaleghwairyeen/.pub-cache"
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
{
|
||||||
|
"roots": [
|
||||||
|
"intaleq_maps_example"
|
||||||
|
],
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "intaleq_maps_example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"intaleq_maps"
|
||||||
|
],
|
||||||
|
"devDependencies": [
|
||||||
|
"flutter_test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection",
|
||||||
|
"fake_async",
|
||||||
|
"flutter",
|
||||||
|
"leak_tracker_flutter_testing",
|
||||||
|
"matcher",
|
||||||
|
"meta",
|
||||||
|
"path",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"test_api",
|
||||||
|
"vector_math"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intaleq_maps",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"http",
|
||||||
|
"maplibre_gl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"characters",
|
||||||
|
"collection",
|
||||||
|
"material_color_utilities",
|
||||||
|
"meta",
|
||||||
|
"sky_engine",
|
||||||
|
"vector_math"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_channel",
|
||||||
|
"version": "2.1.4",
|
||||||
|
"dependencies": [
|
||||||
|
"async"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"version": "1.17.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collection",
|
||||||
|
"version": "1.19.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_flutter_testing",
|
||||||
|
"version": "3.0.10",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"leak_tracker",
|
||||||
|
"leak_tracker_testing",
|
||||||
|
"matcher",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vector_math",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stack_trace",
|
||||||
|
"version": "1.12.1",
|
||||||
|
"dependencies": [
|
||||||
|
"path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fake_async",
|
||||||
|
"version": "1.3.3",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"version": "1.9.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"version": "0.12.18",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"meta",
|
||||||
|
"stack_trace",
|
||||||
|
"term_glyph",
|
||||||
|
"test_api"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_api",
|
||||||
|
"version": "0.7.9",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"boolean_selector",
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"source_span",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"string_scanner",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"version": "1.6.0",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"http_parser",
|
||||||
|
"meta",
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maplibre_gl",
|
||||||
|
"version": "0.25.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"maplibre_gl_platform_interface",
|
||||||
|
"maplibre_gl_web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sky_engine",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "material_color_utilities",
|
||||||
|
"version": "0.13.0",
|
||||||
|
"dependencies": [
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "characters",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"version": "2.13.1",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_testing",
|
||||||
|
"version": "3.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"leak_tracker",
|
||||||
|
"matcher",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker",
|
||||||
|
"version": "11.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"path",
|
||||||
|
"vm_service"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"version": "1.10.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"path",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boolean_selector",
|
||||||
|
"version": "2.1.2",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span",
|
||||||
|
"string_scanner"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "http_parser",
|
||||||
|
"version": "4.1.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"source_span",
|
||||||
|
"string_scanner",
|
||||||
|
"typed_data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maplibre_gl_web",
|
||||||
|
"version": "0.25.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"flutter_web_plugins",
|
||||||
|
"image",
|
||||||
|
"maplibre_gl_platform_interface",
|
||||||
|
"meta",
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maplibre_gl_platform_interface",
|
||||||
|
"version": "0.25.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vm_service",
|
||||||
|
"version": "15.1.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "typed_data",
|
||||||
|
"version": "1.4.0",
|
||||||
|
"dependencies": [
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "image",
|
||||||
|
"version": "4.8.0",
|
||||||
|
"dependencies": [
|
||||||
|
"archive",
|
||||||
|
"meta",
|
||||||
|
"xml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_web_plugins",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "xml",
|
||||||
|
"version": "6.6.1",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"petitparser"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "archive",
|
||||||
|
"version": "4.0.9",
|
||||||
|
"dependencies": [
|
||||||
|
"path",
|
||||||
|
"posix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "petitparser",
|
||||||
|
"version": "7.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "posix",
|
||||||
|
"version": "6.5.0",
|
||||||
|
"dependencies": [
|
||||||
|
"ffi",
|
||||||
|
"meta",
|
||||||
|
"path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ffi",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"dependencies": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configVersion": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"maplibre_gl","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl-0.25.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[{"name":"maplibre_gl_web","path":"/Users/hamzaaleghwairyeen/.pub-cache/hosted/pub.dev/maplibre_gl_web-0.25.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"maplibre_gl","dependencies":["maplibre_gl_web"]},{"name":"maplibre_gl_web","dependencies":[]}],"date_created":"2026-07-09 03:25:15.960410","version":"3.41.2","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Intaleq Maps Example',
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorSchemeSeed: Colors.blue,
|
||||||
|
),
|
||||||
|
home: const MapScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapScreen extends StatefulWidget {
|
||||||
|
const MapScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MapScreen> createState() => _MapScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapScreenState extends State<MapScreen> {
|
||||||
|
IntaleqMapController? _controller;
|
||||||
|
|
||||||
|
// Example marker
|
||||||
|
final Set<Marker> _markers = {
|
||||||
|
const Marker(
|
||||||
|
markerId: MarkerId('damascus'),
|
||||||
|
position: LatLng(33.5138, 36.2765),
|
||||||
|
infoWindow: InfoWindow(
|
||||||
|
title: 'Damascus',
|
||||||
|
snippet: 'The oldest continuously inhabited city.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Intaleq Maps Demo'),
|
||||||
|
),
|
||||||
|
body: IntaleqMap(
|
||||||
|
apiKey: 'YOUR_API_KEY_HERE', // Replace with a valid key
|
||||||
|
initialCameraPosition: const CameraPosition(
|
||||||
|
target: LatLng(33.5138, 36.2765),
|
||||||
|
zoom: 12,
|
||||||
|
),
|
||||||
|
markers: _markers,
|
||||||
|
onCameraMoveStarted: () {
|
||||||
|
debugPrint('Camera movement started');
|
||||||
|
},
|
||||||
|
onMapCreated: (controller) {
|
||||||
|
setState(() {
|
||||||
|
_controller = controller;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
_controller?.animateCamera(
|
||||||
|
CameraUpdate.newLatLngZoom(const LatLng(31.9454, 35.9284), 12),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.location_city),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.9"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.8.0"
|
||||||
|
intaleq_maps:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: ".."
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "2.2.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
maplibre_gl:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: maplibre_gl
|
||||||
|
sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.25.0"
|
||||||
|
maplibre_gl_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: maplibre_gl_platform_interface
|
||||||
|
sha256: bd7de401dea24dd7e8a6f2fa736ddee7dbbee3e24a9027f0afdd619994702047
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.25.0"
|
||||||
|
maplibre_gl_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: maplibre_gl_web
|
||||||
|
sha256: af0e48bf96e8dd99f8b958a1953126971eb8a0527b9735441d4f24df3913f5a2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.25.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.18"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.17.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.2"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.9"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.1.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.9.0-0 <4.0.0"
|
||||||
|
flutter: ">=3.22.0"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: intaleq_maps_example
|
||||||
|
description: Demonstrates how to use the Intaleq Maps SDK.
|
||||||
|
publish_to: 'none'
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
flutter: ">=3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
intaleq_maps:
|
||||||
|
path: ../
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
@@ -6,12 +6,22 @@ library intaleq_maps;
|
|||||||
|
|
||||||
// Re-export core MapLibre primitives under familiar names
|
// Re-export core MapLibre primitives under familiar names
|
||||||
export 'package:maplibre_gl/maplibre_gl.dart'
|
export 'package:maplibre_gl/maplibre_gl.dart'
|
||||||
show LatLng, LatLngBounds, CameraUpdate, CameraPosition;
|
show
|
||||||
|
LatLng,
|
||||||
|
LatLngBounds,
|
||||||
|
OfflineRegion,
|
||||||
|
OfflineRegionDefinition,
|
||||||
|
downloadOfflineRegion,
|
||||||
|
getListOfRegions,
|
||||||
|
deleteOfflineRegion,
|
||||||
|
MyLocationRenderMode,
|
||||||
|
MyLocationTrackingMode;
|
||||||
|
|
||||||
// Public SDK surface
|
// Public SDK surface
|
||||||
export 'src/intaleq_map_widget.dart';
|
export 'src/intaleq_map_widget.dart';
|
||||||
export 'src/intaleq_map_controller.dart';
|
export 'src/intaleq_map_controller.dart';
|
||||||
export 'src/styles.dart';
|
export 'src/styles.dart';
|
||||||
|
export 'src/offline_service.dart';
|
||||||
export 'src/constants/colors.dart';
|
export 'src/constants/colors.dart';
|
||||||
export 'src/utils/polyline_utils.dart';
|
export 'src/utils/polyline_utils.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/services.dart' show rootBundle;
|
import 'package:flutter/services.dart' show rootBundle;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
||||||
import 'models/geometry.dart';
|
import 'models/geometry.dart';
|
||||||
import 'models/bitmap.dart';
|
import 'models/bitmap.dart';
|
||||||
|
import 'models/types.dart';
|
||||||
|
|
||||||
/// Controls a live [IntaleqMap] widget.
|
/// Controls a live [IntaleqMap] widget.
|
||||||
///
|
///
|
||||||
@@ -13,12 +15,12 @@ import 'models/bitmap.dart';
|
|||||||
/// This API mirrors [GoogleMapController] from `google_maps_flutter`.
|
/// This API mirrors [GoogleMapController] from `google_maps_flutter`.
|
||||||
class IntaleqMapController {
|
class IntaleqMapController {
|
||||||
IntaleqMapController._({
|
IntaleqMapController._({
|
||||||
required mgl.MaplibreMapController raw,
|
required mgl.MapLibreMapController raw,
|
||||||
required String apiKey,
|
required String apiKey,
|
||||||
}) : _raw = raw,
|
}) : _raw = raw,
|
||||||
_apiKey = apiKey;
|
_apiKey = apiKey;
|
||||||
|
|
||||||
final mgl.MaplibreMapController _raw;
|
final mgl.MapLibreMapController _raw;
|
||||||
final String _apiKey;
|
final String _apiKey;
|
||||||
|
|
||||||
// ── Internal object registries ─────────────────────────────
|
// ── Internal object registries ─────────────────────────────
|
||||||
@@ -39,10 +41,18 @@ class IntaleqMapController {
|
|||||||
/// Maps our [PolygonId] → live MapLibre [mgl.Fill].
|
/// Maps our [PolygonId] → live MapLibre [mgl.Fill].
|
||||||
final Map<PolygonId, mgl.Fill> _fills = {};
|
final Map<PolygonId, mgl.Fill> _fills = {};
|
||||||
|
|
||||||
|
/// A single, imperatively-managed "user location" symbol (the moving puck).
|
||||||
|
/// Kept OUT of the declarative [Marker] sets so it can be updated on every
|
||||||
|
/// GPS / animation tick without rebuilding the widget tree — see
|
||||||
|
/// [setUserMarker]. Reset to null on style reload (native symbol is
|
||||||
|
/// destroyed) so the next update re-adds it.
|
||||||
|
mgl.Symbol? _userSymbol;
|
||||||
|
bool _userSymbolBusy = false;
|
||||||
|
|
||||||
// ── Factory / init ─────────────────────────────────────────
|
// ── Factory / init ─────────────────────────────────────────
|
||||||
|
|
||||||
static Future<IntaleqMapController> create({
|
static Future<IntaleqMapController> create({
|
||||||
required mgl.MaplibreMapController raw,
|
required mgl.MapLibreMapController raw,
|
||||||
required String apiKey,
|
required String apiKey,
|
||||||
}) async {
|
}) async {
|
||||||
final ctrl = IntaleqMapController._(raw: raw, apiKey: apiKey);
|
final ctrl = IntaleqMapController._(raw: raw, apiKey: apiKey);
|
||||||
@@ -61,17 +71,60 @@ class IntaleqMapController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called by the widget when the map style has finished loading/reloading.
|
||||||
|
/// We clear internal registries because native objects (Symbols, Lines)
|
||||||
|
/// are destroyed on style reload.
|
||||||
|
Future<void> onStyleLoaded() async {
|
||||||
|
_symbols.clear();
|
||||||
|
_symbolToMarker.clear();
|
||||||
|
_lines.clear();
|
||||||
|
_lineToPolyline.clear();
|
||||||
|
_circles.clear();
|
||||||
|
_fills.clear();
|
||||||
|
_loadedImages.clear();
|
||||||
|
// The user-location symbol is destroyed with the old style; forget the
|
||||||
|
// stale handle so the next [setUserMarker] call re-creates it.
|
||||||
|
_userSymbol = null;
|
||||||
|
await _registerDefaultImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Google Maps draws every marker unconditionally, but MapLibre's symbol
|
||||||
|
/// layer defaults to `icon-allow-overlap = false`, so custom markers get
|
||||||
|
/// collision-culled against the basemap's place labels and silently vanish.
|
||||||
|
///
|
||||||
|
/// To match `GoogleMap` semantics (this SDK is a drop-in replacement) we opt
|
||||||
|
/// marker *icons* out of collision. The symbol manager is created before
|
||||||
|
/// `onStyleLoadedCallback` fires, so applying this here — before any markers
|
||||||
|
/// are (re)added on style load — makes pins reliably visible and keeps them
|
||||||
|
/// visible across dark/light style reloads.
|
||||||
|
///
|
||||||
|
/// Only the icon is forced to always draw; the optional info-window text is
|
||||||
|
/// left collision-managed so a marker whose artwork already contains a label
|
||||||
|
/// (e.g. the A/B route pins) doesn't render a duplicate text glyph on top.
|
||||||
|
Future<void> applyMarkerVisibilityDefaults() async {
|
||||||
|
try {
|
||||||
|
await _raw.setSymbolIconAllowOverlap(true);
|
||||||
|
await _raw.setSymbolIconIgnorePlacement(true);
|
||||||
|
} catch (_) {
|
||||||
|
// Symbol manager not ready yet — safe to ignore; defaults reapply on the
|
||||||
|
// next style load.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Camera (same API as GoogleMapController) ──────────────
|
// ── Camera (same API as GoogleMapController) ──────────────
|
||||||
|
|
||||||
/// Animates the camera to the given [update].
|
/// Animates the camera to the given [update].
|
||||||
Future<bool?> animateCamera(mgl.CameraUpdate update) =>
|
Future<bool?> animateCamera(CameraUpdate update) =>
|
||||||
_raw.animateCamera(update);
|
_raw.animateCamera(update.toMapLibre());
|
||||||
|
|
||||||
/// Instantly moves the camera to the given [update].
|
/// Instantly moves the camera to the given [update].
|
||||||
Future<bool?> moveCamera(mgl.CameraUpdate update) => _raw.moveCamera(update);
|
Future<bool?> moveCamera(CameraUpdate update) =>
|
||||||
|
_raw.moveCamera(update.toMapLibre());
|
||||||
|
|
||||||
/// Returns the current [mgl.CameraPosition] of the map.
|
/// Returns the current [CameraPosition] of the map.
|
||||||
mgl.CameraPosition? get cameraPosition => _raw.cameraPosition;
|
CameraPosition? get cameraPosition => _raw.cameraPosition != null
|
||||||
|
? CameraPosition.fromMapLibre(_raw.cameraPosition!)
|
||||||
|
: null;
|
||||||
|
|
||||||
/// Returns the current zoom level.
|
/// Returns the current zoom level.
|
||||||
Future<double> getZoomLevel() async => _raw.cameraPosition?.zoom ?? 14.0;
|
Future<double> getZoomLevel() async => _raw.cameraPosition?.zoom ?? 14.0;
|
||||||
@@ -91,17 +144,20 @@ class IntaleqMapController {
|
|||||||
|
|
||||||
/// Register a custom image into the map.
|
/// Register a custom image into the map.
|
||||||
/// Required before using [InlqBitmap.fromBytes] or custom style images.
|
/// Required before using [InlqBitmap.fromBytes] or custom style images.
|
||||||
Future<void> addImage(String imageId, Uint8List bytes) =>
|
Future<void> addImage(String imageId, Uint8List bytes) async {
|
||||||
_raw.addImage(imageId, bytes);
|
await _raw.addImage(imageId, bytes);
|
||||||
|
_loadedImages.add(imageId);
|
||||||
// ── Markers ────────────────────────────────────────────────
|
}
|
||||||
|
|
||||||
/// Adds a single [Marker] to the map and returns its MapLibre handle.
|
/// Adds a single [Marker] to the map and returns its MapLibre handle.
|
||||||
Future<mgl.Symbol> addMarker(Marker marker) async {
|
Future<mgl.Symbol> addMarker(Marker marker) async {
|
||||||
await _loadBitmapIfNeeded(marker.icon);
|
|
||||||
final symbol = await _raw.addSymbol(marker.toSymbolOptions());
|
final symbol = await _raw.addSymbol(marker.toSymbolOptions());
|
||||||
_symbols[marker.markerId] = symbol;
|
_symbols[marker.markerId] = symbol;
|
||||||
_symbolToMarker[symbol.id] = marker;
|
_symbolToMarker[symbol.id] = marker;
|
||||||
|
|
||||||
|
// Ensure collision defaults apply (symbol layer is created lazily by MapLibre)
|
||||||
|
await applyMarkerVisibilityDefaults();
|
||||||
|
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +177,44 @@ class IntaleqMapController {
|
|||||||
await _raw.removeSymbol(symbol);
|
await _raw.removeSymbol(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── User-location puck (imperative, high-frequency) ────────
|
||||||
|
|
||||||
|
/// Places or moves the single "user location" puck.
|
||||||
|
///
|
||||||
|
/// Unlike the declarative [markers] set, this updates one native symbol
|
||||||
|
/// in place — the correct pattern for a marker that moves on every GPS or
|
||||||
|
/// animation frame (a vehicle/heading puck). It avoids rebuilding the
|
||||||
|
/// Flutter tree and the whole-set diff, which otherwise makes a
|
||||||
|
/// continuously-moving marker flicker or disappear.
|
||||||
|
///
|
||||||
|
/// Safe to call at up to display refresh rate: overlapping calls are
|
||||||
|
/// coalesced (a call that arrives while another is still in flight is
|
||||||
|
/// dropped, and the next tick supplies fresh coordinates). The puck is
|
||||||
|
/// re-created automatically after a style reload.
|
||||||
|
Future<void> setUserMarker(Marker marker) async {
|
||||||
|
if (_userSymbolBusy) return;
|
||||||
|
_userSymbolBusy = true;
|
||||||
|
try {
|
||||||
|
await _loadBitmapIfNeeded(marker.icon);
|
||||||
|
final symbol = _userSymbol;
|
||||||
|
if (symbol == null) {
|
||||||
|
_userSymbol = await _raw.addSymbol(marker.toSymbolOptions());
|
||||||
|
} else {
|
||||||
|
await _raw.updateSymbol(symbol, marker.toSymbolOptions());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_userSymbolBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the user-location puck, if present.
|
||||||
|
Future<void> clearUserMarker() async {
|
||||||
|
final symbol = _userSymbol;
|
||||||
|
if (symbol == null) return;
|
||||||
|
_userSymbol = null;
|
||||||
|
await _raw.removeSymbol(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Polylines ──────────────────────────────────────────────
|
// ── Polylines ──────────────────────────────────────────────
|
||||||
|
|
||||||
Future<mgl.Line> addPolyline(Polyline polyline) async {
|
Future<mgl.Line> addPolyline(Polyline polyline) async {
|
||||||
@@ -295,9 +389,9 @@ class IntaleqMapController {
|
|||||||
|
|
||||||
/// Searches for places using the Intaleq Geocoding API.
|
/// Searches for places using the Intaleq Geocoding API.
|
||||||
Future<List<dynamic>> searchPlaces(String query) async {
|
Future<List<dynamic>> searchPlaces(String query) async {
|
||||||
final uri = Uri.https('map-saas.intaleq.com', '/v1/geocoding/search', {
|
final uri = Uri.https('map-saas.intaleqapp.com', '/api/geocoding/search', {
|
||||||
'q': query,
|
'q': query,
|
||||||
'key': _apiKey,
|
'api_key': _apiKey,
|
||||||
});
|
});
|
||||||
final res = await http.get(uri);
|
final res = await http.get(uri);
|
||||||
if (res.statusCode == 200) return jsonDecode(res.body) as List<dynamic>;
|
if (res.statusCode == 200) return jsonDecode(res.body) as List<dynamic>;
|
||||||
@@ -306,10 +400,10 @@ class IntaleqMapController {
|
|||||||
|
|
||||||
/// Reverse geocodes a [LatLng] to a place description.
|
/// Reverse geocodes a [LatLng] to a place description.
|
||||||
Future<Map<String, dynamic>> reverseGeocode(mgl.LatLng position) async {
|
Future<Map<String, dynamic>> reverseGeocode(mgl.LatLng position) async {
|
||||||
final uri = Uri.https('map-saas.intaleq.com', '/v1/geocoding/reverse', {
|
final uri = Uri.https('map-saas.intaleqapp.com', '/api/geocoding/reverse', {
|
||||||
'lat': position.latitude.toString(),
|
'lat': position.latitude.toString(),
|
||||||
'lng': position.longitude.toString(),
|
'lng': position.longitude.toString(),
|
||||||
'key': _apiKey,
|
'api_key': _apiKey,
|
||||||
});
|
});
|
||||||
final res = await http.get(uri);
|
final res = await http.get(uri);
|
||||||
if (res.statusCode == 200)
|
if (res.statusCode == 200)
|
||||||
@@ -320,17 +414,22 @@ class IntaleqMapController {
|
|||||||
/// Fetches a route between [origin] and [destination].
|
/// Fetches a route between [origin] and [destination].
|
||||||
///
|
///
|
||||||
/// [profile] is one of: `driving`, `cycling`, `walking`.
|
/// [profile] is one of: `driving`, `cycling`, `walking`.
|
||||||
|
/// [steps] when true, returns turn-by-turn navigation steps.
|
||||||
/// Returns the raw Intaleq Routing API response.
|
/// Returns the raw Intaleq Routing API response.
|
||||||
Future<Map<String, dynamic>> getDirections(
|
Future<Map<String, dynamic>> getDirections(
|
||||||
mgl.LatLng origin,
|
mgl.LatLng origin,
|
||||||
mgl.LatLng destination, {
|
mgl.LatLng destination, {
|
||||||
String profile = 'driving',
|
String profile = 'driving',
|
||||||
|
bool steps = true,
|
||||||
}) async {
|
}) async {
|
||||||
final uri = Uri.https('map-saas.intaleq.com', '/v1/routing/route', {
|
final uri = Uri.https('map-saas.intaleqapp.com', '/api/maps/route', {
|
||||||
'start': '${origin.longitude},${origin.latitude}',
|
'fromLat': origin.latitude.toString(),
|
||||||
'end': '${destination.longitude},${destination.latitude}',
|
'fromLng': origin.longitude.toString(),
|
||||||
|
'toLat': destination.latitude.toString(),
|
||||||
|
'toLng': destination.longitude.toString(),
|
||||||
'profile': profile,
|
'profile': profile,
|
||||||
'key': _apiKey,
|
'steps': steps.toString(),
|
||||||
|
'api_key': _apiKey,
|
||||||
});
|
});
|
||||||
final res = await http.get(uri);
|
final res = await http.get(uri);
|
||||||
if (res.statusCode == 200)
|
if (res.statusCode == 200)
|
||||||
@@ -338,6 +437,42 @@ class IntaleqMapController {
|
|||||||
throw Exception('Routing request failed: ${res.statusCode}');
|
throw Exception('Routing request failed: ${res.statusCode}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// High-level helper to fetch and draw a route on the map.
|
||||||
|
///
|
||||||
|
/// Returns the [PolylineId] of the drawn route, or null if it failed.
|
||||||
|
Future<PolylineId?> drawRoute({
|
||||||
|
required mgl.LatLng origin,
|
||||||
|
required mgl.LatLng destination,
|
||||||
|
String profile = 'driving',
|
||||||
|
Color color = const Color(0xFF2196F3),
|
||||||
|
double width = 5.0,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final data = await getDirections(origin, destination, profile: profile);
|
||||||
|
if (data['status'] == 'success' && data['data'] != null) {
|
||||||
|
final List<dynamic> points = data['data']['points'];
|
||||||
|
final List<mgl.LatLng> path = points
|
||||||
|
.map((p) => mgl.LatLng(p['lat'] as double, p['lng'] as double))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final polylineId =
|
||||||
|
PolylineId('route_${DateTime.now().millisecondsSinceEpoch}');
|
||||||
|
final polyline = Polyline(
|
||||||
|
polylineId: polylineId,
|
||||||
|
points: path,
|
||||||
|
color: color,
|
||||||
|
width: width,
|
||||||
|
);
|
||||||
|
|
||||||
|
await addPolyline(polyline);
|
||||||
|
return polylineId;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('IntaleqMapController.drawRoute error: $e');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Bitmap loader helper ───────────────────────────────────
|
// ── Bitmap loader helper ───────────────────────────────────
|
||||||
|
|
||||||
final Set<String> _loadedImages = {};
|
final Set<String> _loadedImages = {};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
||||||
import 'intaleq_map_controller.dart';
|
import 'intaleq_map_controller.dart';
|
||||||
import 'styles.dart';
|
import 'styles.dart';
|
||||||
|
import 'offline_service.dart';
|
||||||
import 'models/geometry.dart';
|
import 'models/geometry.dart';
|
||||||
import 'models/types.dart';
|
import 'models/types.dart';
|
||||||
|
|
||||||
@@ -40,9 +42,11 @@ class IntaleqMap extends StatefulWidget {
|
|||||||
this.polylines = const {},
|
this.polylines = const {},
|
||||||
this.circles = const {},
|
this.circles = const {},
|
||||||
this.polygons = const {},
|
this.polygons = const {},
|
||||||
this.onMapCreated,
|
|
||||||
this.onTap,
|
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
this.onMapCreated,
|
||||||
|
this.onStyleLoaded,
|
||||||
|
this.onTap,
|
||||||
this.onCameraMove,
|
this.onCameraMove,
|
||||||
this.onCameraMoveStarted,
|
this.onCameraMoveStarted,
|
||||||
this.onCameraIdle,
|
this.onCameraIdle,
|
||||||
@@ -54,6 +58,7 @@ class IntaleqMap extends StatefulWidget {
|
|||||||
this.scrollGesturesEnabled = true,
|
this.scrollGesturesEnabled = true,
|
||||||
this.tiltGesturesEnabled = true,
|
this.tiltGesturesEnabled = true,
|
||||||
this.zoomGesturesEnabled = true,
|
this.zoomGesturesEnabled = true,
|
||||||
|
this.autoCache = true,
|
||||||
this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
|
this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
|
||||||
this.cameraTargetBounds = CameraTargetBounds.unbounded,
|
this.cameraTargetBounds = CameraTargetBounds.unbounded,
|
||||||
});
|
});
|
||||||
@@ -64,7 +69,7 @@ class IntaleqMap extends StatefulWidget {
|
|||||||
final String apiKey;
|
final String apiKey;
|
||||||
|
|
||||||
/// Starting camera position (target + zoom).
|
/// Starting camera position (target + zoom).
|
||||||
final mgl.CameraPosition initialCameraPosition;
|
final CameraPosition initialCameraPosition;
|
||||||
|
|
||||||
// ── Map style ──────────────────────────────────────────────
|
// ── Map style ──────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -74,6 +79,9 @@ class IntaleqMap extends StatefulWidget {
|
|||||||
/// Override with a custom MapLibre style URL.
|
/// Override with a custom MapLibre style URL.
|
||||||
final String? styleUrl;
|
final String? styleUrl;
|
||||||
|
|
||||||
|
/// Automatically download tiles around the camera center when idle.
|
||||||
|
final bool autoCache;
|
||||||
|
|
||||||
// ── Overlays (declarative — mirrors GoogleMap) ─────────────
|
// ── Overlays (declarative — mirrors GoogleMap) ─────────────
|
||||||
|
|
||||||
final Set<Marker> markers;
|
final Set<Marker> markers;
|
||||||
@@ -86,6 +94,10 @@ class IntaleqMap extends StatefulWidget {
|
|||||||
/// Called once the map is ready. Use [IntaleqMapController] for all operations.
|
/// Called once the map is ready. Use [IntaleqMapController] for all operations.
|
||||||
final MapCreatedCallback? onMapCreated;
|
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 ──────────────────────────────────
|
// ── Interaction callbacks ──────────────────────────────────
|
||||||
|
|
||||||
/// Called when the user taps the map (not on a marker).
|
/// Called when the user taps the map (not on a marker).
|
||||||
@@ -128,10 +140,48 @@ class IntaleqMap extends StatefulWidget {
|
|||||||
|
|
||||||
class _IntaleqMapState extends State<IntaleqMap> {
|
class _IntaleqMapState extends State<IntaleqMap> {
|
||||||
IntaleqMapController? _controller;
|
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
|
@override
|
||||||
void didUpdateWidget(IntaleqMap oldWidget) {
|
void didUpdateWidget(IntaleqMap oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.mapType != widget.mapType ||
|
||||||
|
oldWidget.styleUrl != widget.styleUrl) {
|
||||||
|
_loadStyle();
|
||||||
|
}
|
||||||
|
|
||||||
final ctrl = _controller;
|
final ctrl = _controller;
|
||||||
if (ctrl == null) return;
|
if (ctrl == null) return;
|
||||||
|
|
||||||
@@ -144,15 +194,16 @@ class _IntaleqMapState extends State<IntaleqMap> {
|
|||||||
|
|
||||||
String get _resolvedStyleUrl {
|
String get _resolvedStyleUrl {
|
||||||
if (widget.styleUrl != null) return widget.styleUrl!;
|
if (widget.styleUrl != null) return widget.styleUrl!;
|
||||||
|
// Default to local assets for performance and offline support
|
||||||
return switch (widget.mapType) {
|
return switch (widget.mapType) {
|
||||||
IntaleqMapType.normal => IntaleqStyles.obsidian(widget.apiKey),
|
IntaleqMapType.normal => 'asset://packages/intaleq_maps/assets/style_dark.json',
|
||||||
IntaleqMapType.light => IntaleqStyles.light(widget.apiKey),
|
IntaleqMapType.light => 'asset://packages/intaleq_maps/assets/style.json',
|
||||||
IntaleqMapType.satellite => IntaleqStyles.satellite(widget.apiKey),
|
IntaleqMapType.satellite => IntaleqStyles.satellite(widget.apiKey),
|
||||||
IntaleqMapType.none => 'about:blank',
|
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.
|
// Wire up tap routing before handing the controller to the caller.
|
||||||
rawCtrl.onSymbolTapped.add(_onSymbolTapped);
|
rawCtrl.onSymbolTapped.add(_onSymbolTapped);
|
||||||
rawCtrl.onLineTapped.add(_onLineTapped);
|
rawCtrl.onLineTapped.add(_onLineTapped);
|
||||||
@@ -162,13 +213,6 @@ class _IntaleqMapState extends State<IntaleqMap> {
|
|||||||
apiKey: widget.apiKey,
|
apiKey: widget.apiKey,
|
||||||
);
|
);
|
||||||
_controller = ctrl;
|
_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);
|
widget.onMapCreated?.call(ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,25 +221,64 @@ class _IntaleqMapState extends State<IntaleqMap> {
|
|||||||
|
|
||||||
void _onLineTapped(mgl.Line line) => _controller?.onLineTapped(line);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (_isLoadingStyle) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
return mgl.MaplibreMap(
|
return mgl.MaplibreMap(
|
||||||
styleString: _resolvedStyleUrl,
|
styleString: _styleString!,
|
||||||
initialCameraPosition: widget.initialCameraPosition,
|
initialCameraPosition: widget.initialCameraPosition.toMapLibre(),
|
||||||
onMapCreated: _onMapCreated,
|
onMapCreated: _onMapCreated,
|
||||||
|
onStyleLoadedCallback: _onStyleLoaded,
|
||||||
onMapClick: widget.onTap != null
|
onMapClick: widget.onTap != null
|
||||||
? (point, latlng) => widget.onTap!(latlng)
|
? (point, latlng) => widget.onTap!(latlng)
|
||||||
: null,
|
: null,
|
||||||
onMapLongClick: widget.onLongPress != null
|
onMapLongClick: widget.onLongPress != null
|
||||||
? (point, latlng) => widget.onLongPress!(latlng)
|
? (point, latlng) => widget.onLongPress!(latlng)
|
||||||
: null,
|
: 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,
|
onCameraTrackingChanged: null,
|
||||||
myLocationEnabled: widget.myLocationEnabled,
|
myLocationEnabled: widget.myLocationEnabled,
|
||||||
myLocationRenderMode: widget.myLocationEnabled
|
myLocationRenderMode: widget.myLocationEnabled
|
||||||
? mgl.MyLocationRenderMode.NORMAL
|
? mgl.MyLocationRenderMode.normal
|
||||||
: mgl.MyLocationRenderMode.NORMAL,
|
: mgl.MyLocationRenderMode.normal,
|
||||||
myLocationTrackingMode: mgl.MyLocationTrackingMode.None,
|
myLocationTrackingMode: mgl.MyLocationTrackingMode.none,
|
||||||
compassEnabled: widget.compassEnabled,
|
compassEnabled: widget.compassEnabled,
|
||||||
rotateGesturesEnabled: widget.rotateGesturesEnabled,
|
rotateGesturesEnabled: widget.rotateGesturesEnabled,
|
||||||
scrollGesturesEnabled: widget.scrollGesturesEnabled,
|
scrollGesturesEnabled: widget.scrollGesturesEnabled,
|
||||||
@@ -208,7 +291,17 @@ class _IntaleqMapState extends State<IntaleqMap> {
|
|||||||
cameraTargetBounds: widget.cameraTargetBounds.bounds != null
|
cameraTargetBounds: widget.cameraTargetBounds.bounds != null
|
||||||
? mgl.CameraTargetBounds(widget.cameraTargetBounds.bounds!)
|
? mgl.CameraTargetBounds(widget.cameraTargetBounds.bounds!)
|
||||||
: mgl.CameraTargetBounds.unbounded,
|
: 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,
|
onCameraTrackingDismissed: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:typed_data' show Uint8List;
|
import 'dart:typed_data' show Uint8List;
|
||||||
import 'dart:ui' show Offset;
|
|
||||||
|
|
||||||
/// Defines a bitmap image for use as a [Marker] icon.
|
/// Defines a bitmap image for use as a [Marker] icon.
|
||||||
///
|
///
|
||||||
@@ -9,7 +8,6 @@ class InlqBitmap {
|
|||||||
const InlqBitmap._({
|
const InlqBitmap._({
|
||||||
required this.mapLibreImageId,
|
required this.mapLibreImageId,
|
||||||
this.size,
|
this.size,
|
||||||
this.offset,
|
|
||||||
this.bytes,
|
this.bytes,
|
||||||
this.assetName,
|
this.assetName,
|
||||||
});
|
});
|
||||||
@@ -20,8 +18,6 @@ class InlqBitmap {
|
|||||||
/// Optional size multiplier (MapLibre iconSize).
|
/// Optional size multiplier (MapLibre iconSize).
|
||||||
final double? size;
|
final double? size;
|
||||||
|
|
||||||
/// Optional pixel offset from the anchor point.
|
|
||||||
final Offset? offset;
|
|
||||||
|
|
||||||
/// Raw PNG/JPEG bytes (used with [fromBytes]).
|
/// Raw PNG/JPEG bytes (used with [fromBytes]).
|
||||||
final Uint8List? bytes;
|
final Uint8List? bytes;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:ui' show Offset, Color;
|
import 'dart:ui' show Offset, Color;
|
||||||
import 'package:flutter/foundation.dart' show VoidCallback, ValueChanged;
|
import 'package:flutter/foundation.dart' show VoidCallback, ValueChanged;
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
||||||
import '../constants/colors.dart';
|
|
||||||
import 'bitmap.dart';
|
import 'bitmap.dart';
|
||||||
import 'info_window.dart';
|
import 'info_window.dart';
|
||||||
|
|
||||||
@@ -9,46 +8,74 @@ import 'info_window.dart';
|
|||||||
// ID types (identical contract to google_maps_flutter)
|
// ID types (identical contract to google_maps_flutter)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Uniquely identifies a [Marker] on the map.
|
||||||
class MarkerId {
|
class MarkerId {
|
||||||
|
/// Creates a [MarkerId] with the given [value].
|
||||||
const MarkerId(this.value);
|
const MarkerId(this.value);
|
||||||
|
|
||||||
|
/// The unique string identifier.
|
||||||
final String value;
|
final String value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object o) => o is MarkerId && o.value == value;
|
bool operator ==(Object o) => o is MarkerId && o.value == value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => value.hashCode;
|
int get hashCode => value.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MarkerId($value)';
|
String toString() => 'MarkerId($value)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Uniquely identifies a [Polyline] on the map.
|
||||||
class PolylineId {
|
class PolylineId {
|
||||||
|
/// Creates a [PolylineId] with the given [value].
|
||||||
const PolylineId(this.value);
|
const PolylineId(this.value);
|
||||||
|
|
||||||
|
/// The unique string identifier.
|
||||||
final String value;
|
final String value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object o) => o is PolylineId && o.value == value;
|
bool operator ==(Object o) => o is PolylineId && o.value == value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => value.hashCode;
|
int get hashCode => value.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'PolylineId($value)';
|
String toString() => 'PolylineId($value)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Uniquely identifies a [Circle] on the map.
|
||||||
class CircleId {
|
class CircleId {
|
||||||
|
/// Creates a [CircleId] with the given [value].
|
||||||
const CircleId(this.value);
|
const CircleId(this.value);
|
||||||
|
|
||||||
|
/// The unique string identifier.
|
||||||
final String value;
|
final String value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object o) => o is CircleId && o.value == value;
|
bool operator ==(Object o) => o is CircleId && o.value == value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => value.hashCode;
|
int get hashCode => value.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'CircleId($value)';
|
String toString() => 'CircleId($value)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Uniquely identifies a [Polygon] on the map.
|
||||||
class PolygonId {
|
class PolygonId {
|
||||||
|
/// Creates a [PolygonId] with the given [value].
|
||||||
const PolygonId(this.value);
|
const PolygonId(this.value);
|
||||||
|
|
||||||
|
/// The unique string identifier.
|
||||||
final String value;
|
final String value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object o) => o is PolygonId && o.value == value;
|
bool operator ==(Object o) => o is PolygonId && o.value == value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => value.hashCode;
|
int get hashCode => value.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'PolygonId($value)';
|
String toString() => 'PolygonId($value)';
|
||||||
}
|
}
|
||||||
@@ -57,7 +84,9 @@ class PolygonId {
|
|||||||
// Marker (mirrors google_maps_flutter Marker)
|
// Marker (mirrors google_maps_flutter Marker)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A marker that is placed at a specific geographical location on the map.
|
||||||
class Marker {
|
class Marker {
|
||||||
|
/// Creates a [Marker].
|
||||||
const Marker({
|
const Marker({
|
||||||
required this.markerId,
|
required this.markerId,
|
||||||
required this.position,
|
required this.position,
|
||||||
@@ -76,7 +105,10 @@ class Marker {
|
|||||||
this.onDragEnd,
|
this.onDragEnd,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Unique identifier for this marker.
|
||||||
final MarkerId markerId;
|
final MarkerId markerId;
|
||||||
|
|
||||||
|
/// Geographical location of the marker.
|
||||||
final mgl.LatLng position;
|
final mgl.LatLng position;
|
||||||
|
|
||||||
/// Opacity of the marker icon, from 0.0 (transparent) to 1.0 (opaque).
|
/// Opacity of the marker icon, from 0.0 (transparent) to 1.0 (opaque).
|
||||||
@@ -165,7 +197,24 @@ class Marker {
|
|||||||
draggable: draggable,
|
draggable: draggable,
|
||||||
zIndex: zIndex.toInt(),
|
zIndex: zIndex.toInt(),
|
||||||
textField: infoWindow.title,
|
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
|
@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
|
@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)
|
// Polyline (mirrors google_maps_flutter Polyline)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A polyline is a list of segments that join a sequence of [mgl.LatLng] locations.
|
||||||
class Polyline {
|
class Polyline {
|
||||||
|
/// Creates a [Polyline].
|
||||||
const Polyline({
|
const Polyline({
|
||||||
required this.polylineId,
|
required this.polylineId,
|
||||||
required this.points,
|
required this.points,
|
||||||
this.color = const Color(0xFF0D47A1),
|
this.color = const Color(0xFF0D47A1),
|
||||||
this.width = 5,
|
this.width = 5.0,
|
||||||
this.visible = true,
|
this.visible = true,
|
||||||
this.zIndex = 0,
|
this.zIndex = 0,
|
||||||
this.geodesic = false,
|
this.geodesic = false,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Unique identifier for this polyline.
|
||||||
final PolylineId polylineId;
|
final PolylineId polylineId;
|
||||||
|
|
||||||
/// The ordered list of points that make up the polyline.
|
/// The ordered list of points that make up the polyline.
|
||||||
@@ -209,7 +285,7 @@ class Polyline {
|
|||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
/// Line stroke width in screen pixels.
|
/// Line stroke width in screen pixels.
|
||||||
final int width;
|
final double width;
|
||||||
|
|
||||||
/// Whether the polyline is visible on the map.
|
/// Whether the polyline is visible on the map.
|
||||||
final bool visible;
|
final bool visible;
|
||||||
@@ -225,7 +301,7 @@ class Polyline {
|
|||||||
Polyline copyWith({
|
Polyline copyWith({
|
||||||
List<mgl.LatLng>? points,
|
List<mgl.LatLng>? points,
|
||||||
Color? color,
|
Color? color,
|
||||||
int? width,
|
double? width,
|
||||||
bool? visible,
|
bool? visible,
|
||||||
int? zIndex,
|
int? zIndex,
|
||||||
bool? geodesic,
|
bool? geodesic,
|
||||||
@@ -243,40 +319,59 @@ class Polyline {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal: converts to MapLibre LineOptions.
|
/// Internal: converts to MapLibre LineOptions.
|
||||||
mgl.LineOptions toLineOptions() {
|
mgl.LineOptions toLineOptions() {
|
||||||
return mgl.LineOptions(
|
return mgl.LineOptions(
|
||||||
geometry: points,
|
geometry: points,
|
||||||
lineColor: _colorToHex(color),
|
lineColor: _colorToHex(color),
|
||||||
lineWidth: width.toDouble(),
|
lineWidth: width < 5.0 ? 5.0 : width,
|
||||||
lineOpacity: visible ? color.opacity : 0.0,
|
|
||||||
lineJoin: 'round',
|
lineJoin: 'round',
|
||||||
|
lineOpacity: 0.85,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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
|
@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)
|
// Circle (mirrors google_maps_flutter Circle)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A circle on the map surface.
|
||||||
class Circle {
|
class Circle {
|
||||||
|
/// Creates a [Circle].
|
||||||
const Circle({
|
const Circle({
|
||||||
required this.circleId,
|
required this.circleId,
|
||||||
required this.center,
|
required this.center,
|
||||||
required this.radius,
|
required this.radius,
|
||||||
this.fillColor = const Color(0x1A0D47A1),
|
this.fillColor = const Color(0x1A0D47A1),
|
||||||
this.strokeColor = const Color(0xFF0D47A1),
|
this.strokeColor = const Color(0xFF0D47A1),
|
||||||
this.strokeWidth = 2,
|
this.strokeWidth = 2.0,
|
||||||
this.visible = true,
|
this.visible = true,
|
||||||
this.zIndex = 0,
|
this.zIndex = 0,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Unique identifier for this circle.
|
||||||
final CircleId circleId;
|
final CircleId circleId;
|
||||||
|
|
||||||
/// Center of the circle.
|
/// Center of the circle.
|
||||||
@@ -292,7 +387,7 @@ class Circle {
|
|||||||
final Color strokeColor;
|
final Color strokeColor;
|
||||||
|
|
||||||
/// Stroke width in pixels.
|
/// Stroke width in pixels.
|
||||||
final int strokeWidth;
|
final double strokeWidth;
|
||||||
|
|
||||||
final bool visible;
|
final bool visible;
|
||||||
final int zIndex;
|
final int zIndex;
|
||||||
@@ -305,7 +400,7 @@ class Circle {
|
|||||||
double? radius,
|
double? radius,
|
||||||
Color? fillColor,
|
Color? fillColor,
|
||||||
Color? strokeColor,
|
Color? strokeColor,
|
||||||
int? strokeWidth,
|
double? strokeWidth,
|
||||||
bool? visible,
|
bool? visible,
|
||||||
int? zIndex,
|
int? zIndex,
|
||||||
VoidCallback? onTap,
|
VoidCallback? onTap,
|
||||||
@@ -332,37 +427,58 @@ class Circle {
|
|||||||
circleRadius:
|
circleRadius:
|
||||||
radius / 10, // approximate; use addCircleAccurate for precision
|
radius / 10, // approximate; use addCircleAccurate for precision
|
||||||
circleColor: _colorToHex(fillColor),
|
circleColor: _colorToHex(fillColor),
|
||||||
circleOpacity: visible ? fillColor.opacity : 0.0,
|
circleOpacity: visible ? fillColor.a : 0.0,
|
||||||
circleStrokeColor: _colorToHex(strokeColor),
|
circleStrokeColor: _colorToHex(strokeColor),
|
||||||
circleStrokeWidth: strokeWidth.toDouble(),
|
circleStrokeWidth: strokeWidth,
|
||||||
circleStrokeOpacity: visible ? strokeColor.opacity : 0.0,
|
circleStrokeOpacity: visible ? strokeColor.a : 0.0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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
|
@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)
|
// Polygon (mirrors google_maps_flutter Polygon)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A polygon on the map surface.
|
||||||
class Polygon {
|
class Polygon {
|
||||||
|
/// Creates a [Polygon].
|
||||||
const Polygon({
|
const Polygon({
|
||||||
required this.polygonId,
|
required this.polygonId,
|
||||||
required this.points,
|
required this.points,
|
||||||
this.holes = const [],
|
this.holes = const [],
|
||||||
this.fillColor = const Color(0x1ABDBDBD),
|
this.fillColor = const Color(0x1ABDBDBD),
|
||||||
this.strokeColor = const Color(0xFF0D47A1),
|
this.strokeColor = const Color(0xFF0D47A1),
|
||||||
this.strokeWidth = 2,
|
this.strokeWidth = 2.0,
|
||||||
this.visible = true,
|
this.visible = true,
|
||||||
this.zIndex = 0,
|
this.zIndex = 0,
|
||||||
this.geodesic = false,
|
this.geodesic = false,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Unique identifier for this polygon.
|
||||||
final PolygonId polygonId;
|
final PolygonId polygonId;
|
||||||
|
|
||||||
/// Outer boundary of the polygon.
|
/// Outer boundary of the polygon.
|
||||||
@@ -378,7 +494,7 @@ class Polygon {
|
|||||||
final Color strokeColor;
|
final Color strokeColor;
|
||||||
|
|
||||||
/// Outline stroke width in pixels.
|
/// Outline stroke width in pixels.
|
||||||
final int strokeWidth;
|
final double strokeWidth;
|
||||||
|
|
||||||
final bool visible;
|
final bool visible;
|
||||||
final int zIndex;
|
final int zIndex;
|
||||||
@@ -392,7 +508,7 @@ class Polygon {
|
|||||||
List<List<mgl.LatLng>>? holes,
|
List<List<mgl.LatLng>>? holes,
|
||||||
Color? fillColor,
|
Color? fillColor,
|
||||||
Color? strokeColor,
|
Color? strokeColor,
|
||||||
int? strokeWidth,
|
double? strokeWidth,
|
||||||
bool? visible,
|
bool? visible,
|
||||||
int? zIndex,
|
int? zIndex,
|
||||||
bool? geodesic,
|
bool? geodesic,
|
||||||
@@ -419,15 +535,34 @@ class Polygon {
|
|||||||
return mgl.FillOptions(
|
return mgl.FillOptions(
|
||||||
geometry: rings,
|
geometry: rings,
|
||||||
fillColor: _colorToHex(fillColor),
|
fillColor: _colorToHex(fillColor),
|
||||||
fillOpacity: visible ? fillColor.opacity : 0.0,
|
fillOpacity: visible ? fillColor.a : 0.0,
|
||||||
fillOutlineColor: _colorToHex(strokeColor),
|
fillOutlineColor: _colorToHex(strokeColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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
|
@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.green.toRadixString(16).padLeft(2, '0')}'
|
||||||
'${color.blue.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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:maplibre_gl/maplibre_gl.dart'
|
import 'dart:ui';
|
||||||
show LatLngBounds, LatLng, CameraPosition;
|
import 'package:maplibre_gl/maplibre_gl.dart' as mgl;
|
||||||
import '../intaleq_map_controller.dart';
|
import '../intaleq_map_controller.dart';
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
@@ -48,7 +48,7 @@ class MinMaxZoomPreference {
|
|||||||
class CameraTargetBounds {
|
class CameraTargetBounds {
|
||||||
const CameraTargetBounds(this.bounds);
|
const CameraTargetBounds(this.bounds);
|
||||||
|
|
||||||
final LatLngBounds? bounds;
|
final mgl.LatLngBounds? bounds;
|
||||||
|
|
||||||
/// Unbounded (default).
|
/// Unbounded (default).
|
||||||
static const CameraTargetBounds unbounded = CameraTargetBounds(null);
|
static const CameraTargetBounds unbounded = CameraTargetBounds(null);
|
||||||
@@ -61,6 +61,152 @@ class CameraTargetBounds {
|
|||||||
// Typedefs (same names as google_maps_flutter)
|
// Typedefs (same names as google_maps_flutter)
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Callback when the map is created.
|
||||||
typedef MapCreatedCallback = void Function(IntaleqMapController controller);
|
typedef MapCreatedCallback = void Function(IntaleqMapController controller);
|
||||||
|
|
||||||
|
/// Generic callback for an argument of type [T].
|
||||||
typedef ArgumentCallback<T> = void Function(T argument);
|
typedef ArgumentCallback<T> = void Function(T argument);
|
||||||
|
|
||||||
|
/// Callback for camera position changes.
|
||||||
typedef CameraPositionCallback = void Function(CameraPosition position);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
/// Service for managing offline map regions in Intaleq Maps.
|
||||||
|
///
|
||||||
|
/// This service allows you to download map tiles for a specific region
|
||||||
|
/// so they can be accessed without an internet connection.
|
||||||
|
class IntaleqOfflineService {
|
||||||
|
static final IntaleqOfflineService instance = IntaleqOfflineService._();
|
||||||
|
IntaleqOfflineService._();
|
||||||
|
|
||||||
|
bool _isDownloading = false;
|
||||||
|
LatLng? _lastDownloadedCenter;
|
||||||
|
Timer? _debounceTimer;
|
||||||
|
|
||||||
|
/// Calculate bounding box for a given center and radius in km.
|
||||||
|
LatLngBounds _calculateBounds(LatLng center, double radiusKm) {
|
||||||
|
const double earthRadius = 6371.0;
|
||||||
|
|
||||||
|
// Latitude degrees per km
|
||||||
|
double latDelta = (radiusKm / earthRadius) * (180 / math.pi);
|
||||||
|
// Longitude degrees per km at given latitude
|
||||||
|
double lngDelta = (radiusKm / earthRadius) *
|
||||||
|
(180 / math.pi) /
|
||||||
|
math.cos(center.latitude * math.pi / 180);
|
||||||
|
|
||||||
|
return LatLngBounds(
|
||||||
|
southwest:
|
||||||
|
LatLng(center.latitude - latDelta, center.longitude - lngDelta),
|
||||||
|
northeast:
|
||||||
|
LatLng(center.latitude + latDelta, center.longitude + lngDelta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads map tiles for a specified radius around a coordinate.
|
||||||
|
///
|
||||||
|
/// [center] is the midpoint of the region.
|
||||||
|
/// [styleUrl] is the style to download (e.g. from IntaleqStyles).
|
||||||
|
/// [radiusKm] defines the extent of the download.
|
||||||
|
void downloadRegion(
|
||||||
|
LatLng center, {
|
||||||
|
required String styleUrl,
|
||||||
|
double radiusKm = 5.0,
|
||||||
|
double minZoom = 6.0,
|
||||||
|
double maxZoom = 16.0,
|
||||||
|
}) {
|
||||||
|
// Debounce: Wait for user to stop moving for 2 seconds before starting download
|
||||||
|
_debounceTimer?.cancel();
|
||||||
|
_debounceTimer = Timer(const Duration(seconds: 2), () async {
|
||||||
|
if (_isDownloading) return;
|
||||||
|
|
||||||
|
// Avoid re-downloading if the user hasn't moved significantly (e.g. > 3km)
|
||||||
|
if (_lastDownloadedCenter != null) {
|
||||||
|
double distance = _calculateDistance(center, _lastDownloadedCenter!);
|
||||||
|
if (distance < 3.0) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDownloading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final bounds = _calculateBounds(center, radiusKm);
|
||||||
|
|
||||||
|
// iOS native crash guard for relative assets - only download remote styles
|
||||||
|
// because local assets are already on disk.
|
||||||
|
if (styleUrl.startsWith('asset://') || (Platform.isIOS && !styleUrl.startsWith('http'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final regionDefinition = OfflineRegionDefinition(
|
||||||
|
bounds: bounds,
|
||||||
|
mapStyleUrl: styleUrl,
|
||||||
|
minZoom: minZoom,
|
||||||
|
maxZoom: maxZoom,
|
||||||
|
);
|
||||||
|
|
||||||
|
_lastDownloadedCenter = center;
|
||||||
|
|
||||||
|
// Use maplibre_gl top-level function
|
||||||
|
await downloadOfflineRegion(
|
||||||
|
regionDefinition,
|
||||||
|
metadata: {
|
||||||
|
'name': 'Intaleq-${center.latitude}-${center.longitude}',
|
||||||
|
'downloadDate': DateTime.now().toIso8601String(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail or log in debug
|
||||||
|
print("❌ [OfflineService] Download failed: $e");
|
||||||
|
} finally {
|
||||||
|
_isDownloading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to calculate distance in km using Haversine formula.
|
||||||
|
double _calculateDistance(LatLng p1, LatLng p2) {
|
||||||
|
var p = 0.017453292519943295;
|
||||||
|
var c = math.cos;
|
||||||
|
var a = 0.5 -
|
||||||
|
c((p2.latitude - p1.latitude) * p) / 2 +
|
||||||
|
c(p1.latitude * p) *
|
||||||
|
c(p2.latitude * p) *
|
||||||
|
(1 - c((p2.longitude - p1.longitude) * p)) /
|
||||||
|
2;
|
||||||
|
return 12742 * math.asin(math.sqrt(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all offline map regions and tiles.
|
||||||
|
Future<void> clearCache() async {
|
||||||
|
try {
|
||||||
|
final List<OfflineRegion> regions = await getListOfRegions();
|
||||||
|
for (var region in regions) {
|
||||||
|
await deleteOfflineRegion(region.id);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,26 @@ class IntaleqStyles {
|
|||||||
IntaleqStyles._();
|
IntaleqStyles._();
|
||||||
|
|
||||||
/// Dark premium Obsidian style — the Intaleq default.
|
/// Dark premium Obsidian style — the Intaleq default.
|
||||||
|
///
|
||||||
|
/// This style is optimized for readability and a premium look.
|
||||||
static String obsidian(String apiKey) =>
|
static String obsidian(String apiKey) =>
|
||||||
'https://maps.intaleq.com/styles/obsidian/style.json?key=$apiKey';
|
'https://map-saas.intaleqapp.com/api/maps/style.json?theme=obsidian&api_key=$apiKey';
|
||||||
|
|
||||||
/// High-contrast light style.
|
/// High-contrast light style.
|
||||||
|
///
|
||||||
|
/// Best for daylight use and printing.
|
||||||
static String light(String apiKey) =>
|
static String light(String apiKey) =>
|
||||||
'https://maps.intaleq.com/styles/light/style.json?key=$apiKey';
|
'https://map-saas.intaleqapp.com/api/maps/style.json?theme=light&api_key=$apiKey';
|
||||||
|
|
||||||
/// Satellite imagery with road labels.
|
/// Satellite imagery with road labels.
|
||||||
|
///
|
||||||
|
/// High-resolution satellite tiles overlaid with Intaleq vector labels.
|
||||||
static String satellite(String apiKey) =>
|
static String satellite(String apiKey) =>
|
||||||
'https://maps.intaleq.com/styles/satellite/style.json?key=$apiKey';
|
'https://map-saas.intaleqapp.com/api/maps/style.json?theme=satellite&api_key=$apiKey';
|
||||||
|
|
||||||
|
/// Path to the local light style asset.
|
||||||
|
static const String localLight = 'assets/style.json';
|
||||||
|
|
||||||
|
/// Path to the local dark style asset.
|
||||||
|
static const String localDark = 'assets/style_dark.json';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,14 +104,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.0"
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.2"
|
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -140,34 +132,34 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "5.1.1"
|
||||||
maplibre_gl:
|
maplibre_gl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: maplibre_gl
|
name: maplibre_gl
|
||||||
sha256: "9dd9eebee52f42a45aaa9cdb912afa47845c37007b26a799aa482ecd368804c8"
|
sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0+2"
|
version: "0.25.0"
|
||||||
maplibre_gl_platform_interface:
|
maplibre_gl_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: maplibre_gl_platform_interface
|
name: maplibre_gl_platform_interface
|
||||||
sha256: a95fa38a3532253f32dfe181389adfe9f402773e58ac902d9c4efad3209e0903
|
sha256: bd7de401dea24dd7e8a6f2fa736ddee7dbbee3e24a9027f0afdd619994702047
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0+2"
|
version: "0.25.0"
|
||||||
maplibre_gl_web:
|
maplibre_gl_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: maplibre_gl_web
|
name: maplibre_gl_web
|
||||||
sha256: "7f1540b384f16f3c9bc8b4ebdfca96fb07f6dab5d9ef4dd0e102985dba238691"
|
sha256: af0e48bf96e8dd99f8b958a1953126971eb8a0527b9735441d4f24df3913f5a2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0+2"
|
version: "0.25.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -311,4 +303,4 @@ packages:
|
|||||||
version: "6.6.1"
|
version: "6.6.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0-0 <4.0.0"
|
dart: ">=3.9.0-0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.22.0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: intaleq_maps
|
|||||||
description: >
|
description: >
|
||||||
Premium Flutter SDK for the Intaleq Map Platform (Jordan & Syria).
|
Premium Flutter SDK for the Intaleq Map Platform (Jordan & Syria).
|
||||||
A drop-in Google Maps Flutter replacement backed by MapLibre GL.
|
A drop-in Google Maps Flutter replacement backed by MapLibre GL.
|
||||||
version: 2.0.0
|
version: 2.2.0
|
||||||
homepage: https://intaleqapp.com
|
homepage: https://intaleqapp.com
|
||||||
repository: https://github.com/Hamza-Ayed/intaleq_maps
|
repository: https://github.com/Hamza-Ayed/intaleq_maps
|
||||||
issue_tracker: https://github.com/Hamza-Ayed/intaleq_maps/issues
|
issue_tracker: https://github.com/Hamza-Ayed/intaleq_maps/issues
|
||||||
@@ -14,13 +14,16 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
maplibre_gl: ^0.19.0
|
maplibre_gl: ^0.25.0
|
||||||
http: ^1.1.0
|
http: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
lints: ^2.1.0
|
lints: ^5.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/style.json
|
||||||
|
- assets/style_dark.json
|
||||||
|
|||||||
Reference in New Issue
Block a user