diff --git a/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/EmojiSearchLauncher.kt b/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/EmojiSearchLauncher.kt index ae7be5731e..93eebdcd2a 100644 --- a/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/EmojiSearchLauncher.kt +++ b/samples/emoji-search/ios-shared/src/commonMain/kotlin/com/example/redwood/emojisearch/ios/EmojiSearchLauncher.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.flowOf import platform.Foundation.NSLog +import platform.Foundation.NSOperationQueue import platform.Foundation.NSURLSession class EmojiSearchLauncher( @@ -40,7 +41,7 @@ class EmojiSearchLauncher( private val manifestUrl = "http://localhost:8080/manifest.zipline.json" @Suppress("unused") // Invoked in Swift. - fun createTreehouseApp(): TreehouseApp { + fun createTreehouseApp(listener: EmojiSearchEventListener): TreehouseApp { val ziplineHttpClient = nsurlSession.asZiplineHttpClient() val treehouseAppFactory = TreehouseAppFactory( @@ -49,10 +50,16 @@ class EmojiSearchLauncher( eventListener = object : EventListener() { override fun codeLoadFailed(app: TreehouseApp<*>, manifestUrl: String?, exception: Exception, startValue: Any?) { NSLog("Treehouse: codeLoadFailed: $exception") + NSOperationQueue.mainQueue.addOperationWithBlock { + listener.codeLoadFailed() + } } override fun codeLoadSuccess(app: TreehouseApp<*>, manifestUrl: String?, manifest: ZiplineManifest, zipline: Zipline, startValue: Any?) { NSLog("Treehouse: codeLoadSuccess") + NSOperationQueue.mainQueue.addOperationWithBlock { + listener.codeLoadSuccess() + } } }, ) @@ -73,3 +80,8 @@ class EmojiSearchLauncher( return treehouseApp } } + +interface EmojiSearchEventListener { + fun codeLoadFailed() + fun codeLoadSuccess() +} diff --git a/samples/emoji-search/ios-uikit/EmojiSearchApp.xcodeproj/project.pbxproj b/samples/emoji-search/ios-uikit/EmojiSearchApp.xcodeproj/project.pbxproj index d512487459..33ac7962f2 100644 --- a/samples/emoji-search/ios-uikit/EmojiSearchApp.xcodeproj/project.pbxproj +++ b/samples/emoji-search/ios-uikit/EmojiSearchApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -16,6 +16,7 @@ AACC042628AC72AC00721342 /* RemoteImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AACC042528AC72AC00721342 /* RemoteImageLoader.swift */; }; C2F7CE6628BEAB6C00A66A69 /* ImageBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7CE6528BEAB6C00A66A69 /* ImageBinding.swift */; }; C2F7FBA228BEB54200A66A69 /* TextInputBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F7FBA128BEB54200A66A69 /* TextInputBinding.swift */; }; + CB5D6B092AC5326B004BF9CB /* SnackBar in Frameworks */ = {isa = PBXBuildFile; productRef = CB5D6B082AC5326B004BF9CB /* SnackBar */; }; CB85C0B725AFE61A007A2CC7 /* EmojiSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB85C0B625AFE61A007A2CC7 /* EmojiSearchViewController.swift */; }; CB9F76562810A8A8008CF457 /* IosHostApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9F76552810A8A8008CF457 /* IosHostApi.swift */; }; /* End PBXBuildFile section */ @@ -42,6 +43,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CB5D6B092AC5326B004BF9CB /* SnackBar in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -101,6 +103,9 @@ dependencies = ( ); name = EmojiSearchApp; + packageProductDependencies = ( + CB5D6B082AC5326B004BF9CB /* SnackBar */, + ); productName = EmojiSearchApp; productReference = 635661D121F12B7E00DD7240 /* EmojiSearchApp.app */; productType = "com.apple.product-type.application"; @@ -130,6 +135,9 @@ Base, ); mainGroup = 635661C821F12B7D00DD7240; + packageReferences = ( + CB5D6B072AC5326B004BF9CB /* XCRemoteSwiftPackageReference "SnackBar" */, + ); productRefGroup = 635661D221F12B7E00DD7240 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -415,6 +423,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + CB5D6B072AC5326B004BF9CB /* XCRemoteSwiftPackageReference "SnackBar" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ahmedAlmasri/SnackBar.swift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.1.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + CB5D6B082AC5326B004BF9CB /* SnackBar */ = { + isa = XCSwiftPackageProductDependency; + package = CB5D6B072AC5326B004BF9CB /* XCRemoteSwiftPackageReference "SnackBar" */; + productName = SnackBar; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 635661C921F12B7D00DD7240 /* Project object */; } diff --git a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift index 7b65108511..68f278cbe2 100644 --- a/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift +++ b/samples/emoji-search/ios-uikit/EmojiSearchApp/EmojiSearchViewController.swift @@ -17,12 +17,14 @@ import Foundation import UIKit import EmojiSearchKt +import SnackBar -class EmojiSearchViewController : UIViewController { - +class EmojiSearchViewController : UIViewController, EmojiSearchEventListener { // MARK: - Private Properties private let urlSession: URLSession = .init(configuration: .default) + private var success = true + private var snackBar: SnackBarPresentable? = nil // MARK: - UIViewController @@ -34,7 +36,7 @@ class EmojiSearchViewController : UIViewController { override func loadView() { let emojiSearchLauncher = EmojiSearchLauncher(nsurlSession: urlSession, hostApi: IosHostApi()) - let treehouseApp = emojiSearchLauncher.createTreehouseApp() + let treehouseApp = emojiSearchLauncher.createTreehouseApp(listener: self) let widgetSystem = EmojiSearchWidgetSystem(treehouseApp: treehouseApp) let treehouseView = TreehouseUIView(widgetSystem: widgetSystem) let content = treehouseApp.createContent( @@ -44,6 +46,29 @@ class EmojiSearchViewController : UIViewController { ExposedKt.bindWhenReady(content: content, view: treehouseView) view = treehouseView.view } + + func codeLoadFailed() { + if (success) { + // Only show the Snackbar on the first transition from success. + success = false + let snackBar = SnackBar.make(in: view, message: "Unable to load guest code from server", duration: SnackBar.Duration.infinite) + .setAction(with: "Dismiss", action: { self.maybeDismissSnackBar() }) + snackBar.show() + self.snackBar = snackBar + } + } + + func codeLoadSuccess() { + success = true + maybeDismissSnackBar() + } + + private func maybeDismissSnackBar() { + if let snackBar = snackBar { + snackBar.dismiss() + self.snackBar = nil + } + } } class EmojiSearchContent : TreehouseContentSource {