chore: add build artifacts, map assets, and routing data

This commit is contained in:
Hamza-Ayed
2026-09-19 12:34:24 +03:00
parent aa2b9f131f
commit 43ad2e0ad4
327 changed files with 52421 additions and 1810 deletions
@@ -3,6 +3,8 @@ import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
private let valhallaBridge = ValhallaBridge()
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
@@ -12,5 +14,6 @@ import UIKit
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
valhallaBridge.register(with: engineBridge.applicationRegistrar.messenger())
}
}
@@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 45 KiB

@@ -0,0 +1,189 @@
import Flutter
import UIKit
#if canImport(Valhalla)
import Valhalla
import ValhallaConfigModels
#endif
/// ============================================================================
/// [ValhallaBridge] - جسر التوجيه المحلي لمحرك Valhalla على iOS
/// ============================================================================
/// English:
/// Swift bridge connecting Flutter's MethodChannel (intaleq_tactical/valhalla)
/// to the native Valhalla offline routing engine on iOS.
/// The Jordan tile extract (valhalla_tiles.tar) downloaded by
/// OfflineRoutingPackageService into <Documents>/routing/jordan is opened by
/// the C++ engine through ValhallaConfig(tileExtractTar:), and every route
/// request runs 100% on-device with zero network usage.
///
/// العربية:
/// جسر سويفت يربط قناة فلاتر بمحرك Valhalla الأصلي على iOS. يفتح المحرك ملف
/// غراف طرق الأردن المنزّل من مجلد الحزمة ويحسب المسارات محلياً بالكامل مع
/// ارتفاعات SRTM وأسماء الشوارع الحقيقية وتعليمات انعطاف عربية.
/// ============================================================================
class ValhallaBridge: NSObject {
static let channelName = "intaleq_tactical/valhalla"
/// Serial queue guarding engine lifecycle + routing calls (C++ actor is not thread-safe).
private let engineQueue = DispatchQueue(label: "com.intaleq.tactical.valhalla")
#if canImport(Valhalla)
private var engine: Valhalla?
private var engineDir: String?
#endif
func register(with messenger: FlutterBinaryMessenger) {
let channel = FlutterMethodChannel(name: ValhallaBridge.channelName, binaryMessenger: messenger)
channel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
guard let self = self else { return }
switch call.method {
case "ensureReady":
let args = call.arguments as? [String: Any]
let regionDir = args?["regionDir"] as? String
self.handleEnsureReady(regionDirPath: regionDir, result: result)
case "route":
let args = call.arguments as? [String: Any]
guard let requestJson = args?["request"] as? String else {
result(FlutterError(code: "BAD_ARGS", message: "Missing request JSON", details: nil))
return
}
self.handleRoute(requestJson: requestJson, result: result)
case "release":
self.releaseEngine()
result(true)
default:
result(FlutterMethodNotImplemented)
}
}
}
// MARK: - ensureReady
private func handleEnsureReady(regionDirPath: String?, result: @escaping FlutterResult) {
let dir = Self.resolveRegionDir(regionDirPath)
let tarPath = Self.findTar(named: "valhalla_tiles.tar", in: dir)?.path
guard let tarPath = tarPath else {
result(FlutterError(
code: "DIR_NOT_FOUND",
message: "Routing package not installed at \(dir.path) — download it from the server while online",
details: nil))
return
}
#if canImport(Valhalla)
engineQueue.async { [weak self] in
guard let self = self else { return }
// إعادة الاستخدام إن كان المحرك مبنياً على نفس الحزمة
if let engine = self.engine, self.engineDir == dir.path {
result(true)
return
}
self.releaseEngineLocked()
do {
let config = try ValhallaConfig(tileExtractTar: URL(fileURLWithPath: tarPath))
self.engine = try Valhalla(config)
self.engineDir = dir.path
NSLog("[ValhallaBridge] engine ready against \(tarPath)")
DispatchQueue.main.async { result(true) }
} catch {
NSLog("[ValhallaBridge] engine init failed: \(error)")
DispatchQueue.main.async {
result(FlutterError(code: "ENGINE_INIT_FAILED",
message: "\(error)",
details: nil))
}
}
}
#else
result(FlutterError(
code: "FALLBACK_SYNTHETIC",
message: "Valhalla SPM package is not linked into this build target",
details: nil))
#endif
}
// MARK: - route
private func handleRoute(requestJson: String, result: @escaping FlutterResult) {
#if canImport(Valhalla)
engineQueue.async { [weak self] in
guard let self = self, let engine = self.engine else {
DispatchQueue.main.async {
result(FlutterError(code: "NOT_READY",
message: "Call ensureReady before route",
details: nil))
}
return
}
// route(rawRequest:) يعيد JSON خاماً؛ أخطاء المحرك تعود داخل الجسم
// بصيغة {"code": int, "message": str} ويفكّها طرف Dart مباشرة.
let rawResponse = engine.route(rawRequest: requestJson)
DispatchQueue.main.async { result(rawResponse) }
}
#else
result(FlutterError(
code: "FALLBACK_SYNTHETIC",
message: "Valhalla SPM package is not linked into this build target",
details: nil))
#endif
}
// MARK: - release
func releaseEngine() {
#if canImport(Valhalla)
engineQueue.async { [weak self] in
self?.releaseEngineLocked()
}
#else
// no-op
#endif
}
#if canImport(Valhalla)
/// Must be called on engineQueue.
private func releaseEngineLocked() {
engine = nil
engineDir = nil
}
#endif
// MARK: - Helpers
private static func resolveRegionDir(_ regionDirPath: String?) -> URL {
if let path = regionDirPath {
return URL(fileURLWithPath: path, isDirectory: true)
}
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return docs.appendingPathComponent("routing/jordan", isDirectory: true)
}
/// بحث تكراري عن ملف الغراف داخل مجلد الحزمة (البنية قد تختلف بين الإصدارات)
private static func findTar(named name: String, in dir: URL) -> URL? {
let fm = FileManager.default
var isDir: ObjCBool = false
guard fm.fileExists(atPath: dir.path, isDirectory: &isDir), isDir.boolValue else {
return nil
}
if fm.fileExists(atPath: dir.appendingPathComponent(name).path) {
return dir.appendingPathComponent(name)
}
guard let children = try? fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: [.isDirectoryKey]) else {
return nil
}
for child in children {
let isChildDir = (try? child.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false
if isChildDir, let found = findTar(named: name, in: child) {
return found
}
}
return nil
}
}