From 73cbf1a7479186d6b828e7a1bd2e95cbe48479bd Mon Sep 17 00:00:00 2001 From: Arystan Date: Wed, 17 Sep 2025 17:16:40 +0500 Subject: [PATCH 1/5] feat: map preloading --- .../main/java/com/expofp/ExpofpViewManager.kt | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/expofp/ExpofpViewManager.kt b/android/src/main/java/com/expofp/ExpofpViewManager.kt index 4b07add..2edd084 100644 --- a/android/src/main/java/com/expofp/ExpofpViewManager.kt +++ b/android/src/main/java/com/expofp/ExpofpViewManager.kt @@ -1,20 +1,11 @@ package com.expofp -import android.Manifest -import android.app.Activity -import android.app.AlertDialog import android.app.Application -import android.os.Build import android.util.Log import android.view.View -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.app.ActivityCompat import com.expofp.common.GlobalLocationProvider import com.expofp.crowdconnected.CrowdConnectedProvider import com.expofp.crowdconnected.Mode -import com.expofp.crowdconnected.Settings -// import com.expofp.crowdconnectedbackground.CrowdConnectedBackgroundProvider import com.expofp.fplan.FplanView import com.expofp.fplan.models.FplanViewState import com.facebook.react.bridge.ReadableMap @@ -22,6 +13,9 @@ import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.annotations.ReactProp import com.expofp.R +import com.expofp.fplan.contracts.DownloadOfflinePlanCallback +import com.expofp.fplan.models.OfflinePlanInfo +import android.content.res.AssetManager class ExpofpViewManager : SimpleViewManager() { private var reactContext: ThemedReactContext? = null @@ -68,7 +62,54 @@ class ExpofpViewManager : SimpleViewManager() { GlobalLocationProvider.start() } if (view.state.equals(FplanViewState.Created)) { - view.load(it.getString("url") ?: "", com.expofp.fplan.models.Settings().withGlobalLocationProvider()); + val info = ExpofpModule.downloadedOfflinePlanInfo + val url = it.getString("url") ?: "" + val expoKey = url.substringAfter("https://").substringBefore(".expofp.com") + + val offlinePlanManager = FplanView.getOfflinePlanManager(reactContext) + val latestOfflinePlan = offlinePlanManager.allOfflinePlansFromCache + .filter { offlinePlanInfo -> offlinePlanInfo.expoKey == expoKey } + .maxByOrNull { offlinePlanInfo -> offlinePlanInfo.version } + if (latestOfflinePlan != null) { + Log.d("ExpofpModule", latestOfflinePlan.expoKey) + view.openOfflinePlan(latestOfflinePlan, "", com.expofp.fplan.models.Settings().withGlobalLocationProvider()) + } else { + val ctx = this.reactContext + if (ctx != null) { + val am = ctx.assets + val cachePlanExists = try { + am.open("${expoKey}.zip").close() + true + } catch (e: Exception) { + false + } + + if (cachePlanExists) { + try { + Log.d("ExpofpModule", "openZipFromAssets: ${'$'}candidate") + view.openZipFromAssets("${expoKey}.zip", "", com.expofp.fplan.models.Settings().withGlobalLocationProvider(), ctx) + } catch (e: Exception) { + Log.d("ExpofpModule", "failed to open asset zip, loading url: ${'$'}url") + view.load(url, com.expofp.fplan.models.Settings().withGlobalLocationProvider()) + } + } else { + Log.d("ExpofpModule", "asset zip not found, loading url: ${'$'}url") + view.load(url, com.expofp.fplan.models.Settings().withGlobalLocationProvider()) + } + } else { + view.load(url, com.expofp.fplan.models.Settings().withGlobalLocationProvider()) + } + } + + offlinePlanManager.downloadOfflinePlanToCache(expoKey, object : DownloadOfflinePlanCallback { + override fun onCompleted(offlinePlanInfo: OfflinePlanInfo) { + Log.d("ExpofpModule", "downloaded offline plan: ${'$'}{offlinePlanInfo.expoKey} v${'$'}{offlinePlanInfo.version}") + } + + override fun onError(message: String) { + Log.e("ExpofpModule", "offline plan download failed: ${'$'}message") + } + }) } } } From abec5aad83350ab17b40c22aa5c087e8e431dce3 Mon Sep 17 00:00:00 2001 From: Arystan Date: Thu, 18 Sep 2025 17:38:35 +0500 Subject: [PATCH 2/5] feat(ios): implement ios and update android lib versions --- android/build.gradle | 8 ++++---- ios/ExpofpViewManager.swift | 32 +++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 56101a2..d39349f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -91,11 +91,11 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'com.expofp:common:4.10.0' - implementation 'com.expofp:fplan:4.10.0' + implementation 'com.expofp:common:4.11.0' + implementation 'com.expofp:fplan:4.11.0' - implementation 'com.expofp:crowdconnected:4.10.0' - // implementation 'com.expofp:crowdconnectedbackground:4.10.0' + implementation 'com.expofp:crowdconnected:4.11.0' + // implementation 'com.expofp:crowdconnectedbackground:4.11.0' implementation 'net.crowdconnected.android.core:android-core:2.0.2' implementation 'net.crowdconnected.android.ips:android-ips:2.0.2' implementation 'net.crowdconnected.android.geo:android-geo:2.0.2' diff --git a/ios/ExpofpViewManager.swift b/ios/ExpofpViewManager.swift index 19f2743..7caf243 100644 --- a/ios/ExpofpViewManager.swift +++ b/ios/ExpofpViewManager.swift @@ -81,8 +81,38 @@ struct ExpoFP: View { { fplanView.onAppear{ if (loadedUrl !== dataStore.url) { - fplanView.load(dataStore.url as String, useGlobalLocationProvider: true) + let expoKey = (URL(string: dataStore.url as String)?.host ?? "").components(separatedBy: ".").first ?? "" + print("expoKey: \(expoKey)") + + if let cachePath = SharedFplanView.getFilePathFromCache() { + let pathComponents = cachePath.absoluteString.components(separatedBy: "/") + let cachedExpoKey = pathComponents[pathComponents.count - 2] + print("cachePath: \(cachePath.absoluteString)") + print("cachedExpoKey: \(cachedExpoKey)") + if cachedExpoKey == expoKey { + print("loading from cache") + fplanView.openFile(htmlFilePathUrl: cachePath, params: nil, settings: ExpoFpFplan.Settings(useGlobalLocationProvider: true)) + } + } else if let path = Bundle.main.path(forResource: expoKey, ofType: "zip", inDirectory: "maps") { + print("loading from preloaded map path: \(path)") + fplanView.openZip(path, params: nil, useGlobalLocationProvider: true) + } else { + print("loading from url") + fplanView.load(dataStore.url as String, useGlobalLocationProvider: true) + } loadedUrl = dataStore.url + print("downlpading the map") + fplanView.downloadZipToCache(dataStore.url as String) { htmlFilePath, error in + if let error = error { + print("error doenaloding the map") + print(error) + } else { + print("success downloading the map") + if let htmlFilePath = htmlFilePath { + print("htmlFilePath: \(htmlFilePath)") + } + } + } } }.onDisappear{ fplanView.clear() From bf0d715c36e12a4ae7d61a9258545b90398ef27e Mon Sep 17 00:00:00 2001 From: Arystan Date: Fri, 19 Sep 2025 14:34:52 +0500 Subject: [PATCH 3/5] refactor(android): expofp --- .../main/java/com/expofp/ExpofpViewManager.kt | 113 ++++++++++-------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/android/src/main/java/com/expofp/ExpofpViewManager.kt b/android/src/main/java/com/expofp/ExpofpViewManager.kt index 2edd084..c87b2f0 100644 --- a/android/src/main/java/com/expofp/ExpofpViewManager.kt +++ b/android/src/main/java/com/expofp/ExpofpViewManager.kt @@ -15,7 +15,6 @@ import com.facebook.react.uimanager.annotations.ReactProp import com.expofp.R import com.expofp.fplan.contracts.DownloadOfflinePlanCallback import com.expofp.fplan.models.OfflinePlanInfo -import android.content.res.AssetManager class ExpofpViewManager : SimpleViewManager() { private var reactContext: ThemedReactContext? = null @@ -34,6 +33,67 @@ class ExpofpViewManager : SimpleViewManager() { super.onDropViewInstance(view) } + private fun getExpoKeyFromUrl(url: String): String { + return url.substringAfter("https://").substringBefore(".expofp.com") + } + + private fun openMapForUrl(view: FplanView, url: String) { + val expoKey = getExpoKeyFromUrl(url) + val settings = com.expofp.fplan.models.Settings().withGlobalLocationProvider() + + val offlinePlanManager = FplanView.getOfflinePlanManager(reactContext) + val latestOfflinePlan = offlinePlanManager.allOfflinePlansFromCache + .filter { offlinePlanInfo -> offlinePlanInfo.expoKey == expoKey } + .maxByOrNull { offlinePlanInfo -> offlinePlanInfo.version } + + if (latestOfflinePlan != null) { + Log.d("ExpofpModule", latestOfflinePlan.expoKey) + view.openOfflinePlan(latestOfflinePlan, "", settings) + return + } + + val ctx = this.reactContext ?: run { + view.load(url, settings) + return + } + + val am = ctx.assets + val cachePlanExists = try { + am.open("${expoKey}.zip").close() + true + } catch (e: Exception) { + false + } + + if (cachePlanExists) { + try { + Log.d("ExpofpModule", "openZipFromAssets: ${expoKey}.zip") + view.openZipFromAssets("${expoKey}.zip", "", settings, ctx) + return + } catch (e: Exception) { + Log.d("ExpofpModule", "failed to open asset zip, loading url: $url") + view.load(url, settings) + return + } + } + + Log.d("ExpofpModule", "asset zip not found, loading url: $url") + view.load(url, settings) + } + + private fun triggerOfflinePlanDownload(expoKey: String) { + val offlinePlanManager = FplanView.getOfflinePlanManager(reactContext) + offlinePlanManager.downloadOfflinePlanToCache(expoKey, object : DownloadOfflinePlanCallback { + override fun onCompleted(offlinePlanInfo: OfflinePlanInfo) { + Log.d("ExpofpModule", "downloaded offline plan: ${offlinePlanInfo.expoKey} v${offlinePlanInfo.version}") + } + + override fun onError(message: String) { + Log.e("ExpofpModule", "offline plan download failed: $message") + } + }) + } + @ReactProp(name = "settings") fun setSettings(view: FplanView, settingsMap: ReadableMap?) { println("setSettings: $settingsMap") @@ -62,54 +122,11 @@ class ExpofpViewManager : SimpleViewManager() { GlobalLocationProvider.start() } if (view.state.equals(FplanViewState.Created)) { - val info = ExpofpModule.downloadedOfflinePlanInfo val url = it.getString("url") ?: "" - val expoKey = url.substringAfter("https://").substringBefore(".expofp.com") - - val offlinePlanManager = FplanView.getOfflinePlanManager(reactContext) - val latestOfflinePlan = offlinePlanManager.allOfflinePlansFromCache - .filter { offlinePlanInfo -> offlinePlanInfo.expoKey == expoKey } - .maxByOrNull { offlinePlanInfo -> offlinePlanInfo.version } - if (latestOfflinePlan != null) { - Log.d("ExpofpModule", latestOfflinePlan.expoKey) - view.openOfflinePlan(latestOfflinePlan, "", com.expofp.fplan.models.Settings().withGlobalLocationProvider()) - } else { - val ctx = this.reactContext - if (ctx != null) { - val am = ctx.assets - val cachePlanExists = try { - am.open("${expoKey}.zip").close() - true - } catch (e: Exception) { - false - } - - if (cachePlanExists) { - try { - Log.d("ExpofpModule", "openZipFromAssets: ${'$'}candidate") - view.openZipFromAssets("${expoKey}.zip", "", com.expofp.fplan.models.Settings().withGlobalLocationProvider(), ctx) - } catch (e: Exception) { - Log.d("ExpofpModule", "failed to open asset zip, loading url: ${'$'}url") - view.load(url, com.expofp.fplan.models.Settings().withGlobalLocationProvider()) - } - } else { - Log.d("ExpofpModule", "asset zip not found, loading url: ${'$'}url") - view.load(url, com.expofp.fplan.models.Settings().withGlobalLocationProvider()) - } - } else { - view.load(url, com.expofp.fplan.models.Settings().withGlobalLocationProvider()) - } - } - - offlinePlanManager.downloadOfflinePlanToCache(expoKey, object : DownloadOfflinePlanCallback { - override fun onCompleted(offlinePlanInfo: OfflinePlanInfo) { - Log.d("ExpofpModule", "downloaded offline plan: ${'$'}{offlinePlanInfo.expoKey} v${'$'}{offlinePlanInfo.version}") - } - - override fun onError(message: String) { - Log.e("ExpofpModule", "offline plan download failed: ${'$'}message") - } - }) + val expoKey = getExpoKeyFromUrl(url) + + openMapForUrl(view, url) + triggerOfflinePlanDownload(expoKey) } } } From f8e845e9503635575ab7a1f9bfd82912bbc247cc Mon Sep 17 00:00:00 2001 From: Arystan Date: Fri, 19 Sep 2025 15:02:34 +0500 Subject: [PATCH 4/5] refactor(android): expofp --- ios/ExpofpViewManager.swift | 84 +++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/ios/ExpofpViewManager.swift b/ios/ExpofpViewManager.swift index 7caf243..d755835 100644 --- a/ios/ExpofpViewManager.swift +++ b/ios/ExpofpViewManager.swift @@ -76,43 +76,63 @@ struct ExpoFP: View { @State private var loadedUrl: NSString? = nil + private var defaultSettings: ExpoFpFplan.Settings { .init(useGlobalLocationProvider: true) } + + private func expoKey(from urlString: String) -> String { + return (URL(string: urlString)?.host ?? "").components(separatedBy: ".").first ?? "" + } + + private func openMap(for urlString: String) { + let key = expoKey(from: urlString) + print("expoKey: \(key)") + + if let cachePath = SharedFplanView.getFilePathFromCache() { + let pathComponents = cachePath.absoluteString.components(separatedBy: "/") + let cachedExpoKey = pathComponents[pathComponents.count - 2] + print("cachePath: \(cachePath.absoluteString)") + print("cachedExpoKey: \(cachedExpoKey)") + if cachedExpoKey == key { + print("loading from cache") + fplanView.openFile(htmlFilePathUrl: cachePath, params: nil, settings: defaultSettings) + return + } else { + print("cache key mismatch, expected: \(key), found: \(cachedExpoKey)") + } + } + + if let path = Bundle.main.path(forResource: key, ofType: "zip", inDirectory: "maps") { + print("loading from preloaded map path: \(path)") + fplanView.openZip(path, params: nil, useGlobalLocationProvider: true) + return + } + + print("loading from url") + fplanView.load(urlString, useGlobalLocationProvider: true) + } + + private func downloadOffline(for urlString: String) { + print("downloading the map") + fplanView.downloadZipToCache(urlString) { htmlFilePath, error in + if let error = error { + print("error downloading the map: \(error.localizedDescription)") + } else { + print("success downloading the map") + if let htmlFilePath = htmlFilePath { + print("htmlFilePath: \(htmlFilePath)") + } + } + } + } + var body: some View { VStack { fplanView.onAppear{ - if (loadedUrl !== dataStore.url) { - let expoKey = (URL(string: dataStore.url as String)?.host ?? "").components(separatedBy: ".").first ?? "" - print("expoKey: \(expoKey)") - - if let cachePath = SharedFplanView.getFilePathFromCache() { - let pathComponents = cachePath.absoluteString.components(separatedBy: "/") - let cachedExpoKey = pathComponents[pathComponents.count - 2] - print("cachePath: \(cachePath.absoluteString)") - print("cachedExpoKey: \(cachedExpoKey)") - if cachedExpoKey == expoKey { - print("loading from cache") - fplanView.openFile(htmlFilePathUrl: cachePath, params: nil, settings: ExpoFpFplan.Settings(useGlobalLocationProvider: true)) - } - } else if let path = Bundle.main.path(forResource: expoKey, ofType: "zip", inDirectory: "maps") { - print("loading from preloaded map path: \(path)") - fplanView.openZip(path, params: nil, useGlobalLocationProvider: true) - } else { - print("loading from url") - fplanView.load(dataStore.url as String, useGlobalLocationProvider: true) - } + if loadedUrl != dataStore.url { + let urlString = dataStore.url as String + openMap(for: urlString) loadedUrl = dataStore.url - print("downlpading the map") - fplanView.downloadZipToCache(dataStore.url as String) { htmlFilePath, error in - if let error = error { - print("error doenaloding the map") - print(error) - } else { - print("success downloading the map") - if let htmlFilePath = htmlFilePath { - print("htmlFilePath: \(htmlFilePath)") - } - } - } + downloadOffline(for: urlString) } }.onDisappear{ fplanView.clear() From 94f1effd82d051880c7ef87a451a646fc86fc50b Mon Sep 17 00:00:00 2001 From: Arystan Date: Fri, 19 Sep 2025 15:08:03 +0500 Subject: [PATCH 5/5] fix(ios): array element access --- ios/ExpofpViewManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/ExpofpViewManager.swift b/ios/ExpofpViewManager.swift index d755835..061e6d7 100644 --- a/ios/ExpofpViewManager.swift +++ b/ios/ExpofpViewManager.swift @@ -88,7 +88,7 @@ struct ExpoFP: View { if let cachePath = SharedFplanView.getFilePathFromCache() { let pathComponents = cachePath.absoluteString.components(separatedBy: "/") - let cachedExpoKey = pathComponents[pathComponents.count - 2] + let cachedExpoKey = pathComponents.count >= 2 ? pathComponents[pathComponents.count - 2] : "" print("cachePath: \(cachePath.absoluteString)") print("cachedExpoKey: \(cachedExpoKey)") if cachedExpoKey == key { @@ -114,7 +114,7 @@ struct ExpoFP: View { print("downloading the map") fplanView.downloadZipToCache(urlString) { htmlFilePath, error in if let error = error { - print("error downloading the map: \(error.localizedDescription)") + print("error downloading the map: \(error)") } else { print("success downloading the map") if let htmlFilePath = htmlFilePath {