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/android/src/main/java/com/expofp/ExpofpViewManager.kt b/android/src/main/java/com/expofp/ExpofpViewManager.kt index 4b07add..c87b2f0 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,8 @@ 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 class ExpofpViewManager : SimpleViewManager() { private var reactContext: ThemedReactContext? = null @@ -40,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") @@ -68,7 +122,11 @@ class ExpofpViewManager : SimpleViewManager() { GlobalLocationProvider.start() } if (view.state.equals(FplanViewState.Created)) { - view.load(it.getString("url") ?: "", com.expofp.fplan.models.Settings().withGlobalLocationProvider()); + val url = it.getString("url") ?: "" + val expoKey = getExpoKeyFromUrl(url) + + openMapForUrl(view, url) + triggerOfflinePlanDownload(expoKey) } } } diff --git a/ios/ExpofpViewManager.swift b/ios/ExpofpViewManager.swift index 19f2743..061e6d7 100644 --- a/ios/ExpofpViewManager.swift +++ b/ios/ExpofpViewManager.swift @@ -76,13 +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.count >= 2 ? 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)") + } else { + print("success downloading the map") + if let htmlFilePath = htmlFilePath { + print("htmlFilePath: \(htmlFilePath)") + } + } + } + } + var body: some View { VStack { fplanView.onAppear{ - if (loadedUrl !== dataStore.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 + downloadOffline(for: urlString) } }.onDisappear{ fplanView.clear()