Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/ExampleSwiftUI/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import SwiftUI
import OnLaunch
import SwiftUI

struct ContentView: View {
var body: some View {
Expand Down
5 changes: 4 additions & 1 deletion Examples/ExampleSwiftUI/MainApp.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import SwiftUI
import OnLaunch
import SwiftUI

@main
struct MainApp: App {
Expand All @@ -12,6 +12,9 @@ struct MainApp: App {

// (Optional) Configure a custom base URL to your API host
// options.baseURL = "https://your-domain.com/api"

// (Optional) Configure the App Store id, required by the action "OPEN_IN_APP_STORE"
options.appStoreId = 409_201_541
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions Examples/ExampleUIKit/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
func application(
_: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options _: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}

7 changes: 4 additions & 3 deletions Examples/ExampleUIKit/SceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import UIKit
import OnLaunch
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func sceneDidBecomeActive(_ scene: UIScene) {
Expand All @@ -17,7 +16,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

// (Optional) Configure a custom base URL to your API host
// options.baseURL = "https://your-domain.com/api"

// (Optional) Configure the App Store id, required by the action 'OPEN_IN_APP_STORE'
options.appStoreId = 409_201_541
}
}
}

6 changes: 2 additions & 4 deletions Examples/ExampleUIKit/ViewController.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import UIKit
import OnLaunch
import UIKit

class ViewController: UIViewController {

@IBAction func checkForMessagesActions(_ sender: Any) {
@IBAction func checkForMessagesActions(_: Any) {
OnLaunch.check()
}
}

6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<sub>Created and maintained by <a href="https://kula.app">kula.app</a> and all the amazing <a href="https://github.com/kula-app/OnLaunch-iOS-Client/graphs/contributors">contributors</a>.</sub>
</p>

[OnLaunch](https://github.com/kula-app/OnLaunch) is a service allowing app developers to notify app users about updates, warnings and maintenance
[OnLaunch](https://github.com/kula-app/OnLaunch) is a service allowing app developers to notify app users about updates, warnings and maintenance.
Our open-source framework provides an easy-to-integrate client to communicate with the backend and display the user interface.

<p align="center">
Expand Down Expand Up @@ -135,6 +135,10 @@ The OnLaunch iOS client provides a couple of configuration options:
| `hostScene` | Scene used to host the OnLaunch client UI. Required if you use UIKit with scenes | |
| `hostViewController` | An instance of `UIViewController` used to host the OnLaunch client UI. Required if you use UIKit without scenes | |
| `theme` | Custom theme used by the OnLaunch client UI. Adapt the values to change the theme to match your preferences. To see all possible configuration values, see [`Theme.swift`](https://github.com/kula-app/OnLaunch-iOS-Client/blob/main/Sources/OnLaunch/Theme/Theme.swift) | Default values as defined in `Theme.standard` in [Theme.swift](https://github.com/kula-app/OnLaunch-iOS-Client/blob/main/Sources/OnLaunch/Theme/Theme.swift) |
| `bundleId` | Bundle identifier used by server-side rules. | Uses value set in `Bundle.main.bundleIdentifier` |
| `bundleVersion` | Version of the build that identifies an iteration of the bundle. | `CFBundleVersion` defined in `Info.plist` |
| `releaseVersion` | Release or version number of the bundle. | `CFBundleShortVersionString` defined in `Info.plist` |
| `appStoreId` | ID of the app in the App Store. If not defined, the `ActionType.openInAppStore` will throw a non-crashing assertion | |

## Contributing Guide

Expand Down
25 changes: 11 additions & 14 deletions Sources/OnLaunch/API/ApiMessageResponseDto.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import Foundation

internal struct ApiMessageResponseDto: Decodable {

internal struct Action: Decodable {

internal enum ActionType: String, Decodable {
struct ApiMessageResponseDto: Decodable {
struct Action: Decodable {
enum ActionType: String, Decodable {
case button = "BUTTON"
case dismissButton = "DISMISS"
case openInAppStore = "OPEN_IN_APP_STORE"
}

internal let actionType: ActionType
internal let title: String

let actionType: ActionType
let title: String
}

internal let id: Int
internal let blocking: Bool
internal let title: String
internal let body: String
internal let actions: [Action]

let id: Int
let blocking: Bool
let title: String
let body: String
let actions: [Action]
}
13 changes: 7 additions & 6 deletions Sources/OnLaunch/Models/Action.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
/// Structure used to define an user-interactable action
internal struct Action {

struct Action {
/// Different kinds of actions, used to implement different behaviour
internal enum Kind {

enum Kind {
/// Action is implemented as a generic button with an associated action
case button

/// Action is represented as a button which dismisses the UI
case dismissButton

/// Action is dedicated to open the current app's App Store Page
case openAppInAppStore
}

/// Kind of the action
internal let kind: Kind
let kind: Kind

/// Title of the action used to display in the UI
internal let title: String
let title: String
}
13 changes: 6 additions & 7 deletions Sources/OnLaunch/Models/Message.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import Foundation

/// Structure of a message to be displayed to the user
internal struct Message: Identifiable {

struct Message: Identifiable {
/// Unique identifier of this message.
///
/// This identifier is used to track if the message has already been presented to the user
internal let id: Int
let id: Int

/// Title of the message
internal let title: String
let title: String

/// Content of the message
internal let body: String
let body: String

/// Flag indicating if this message is blocking the user
internal let isBlocking: Bool
let isBlocking: Bool

/// Actions for the user to interact with
internal let actions: [Action]
let actions: [Action]
}
65 changes: 38 additions & 27 deletions Sources/OnLaunch/OnLaunch.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import Combine
import OSLog
import SwiftUI
import UIKit
import Combine

public class OnLaunch: NSObject {

// MARK: - Types

/// Options used to control the behaviour of OnLaunch
public class Options {

/// Base URL where the OnLaunch API is hosted at.
public var baseURL = "https://onlaunch.kula.app/api/"

Expand All @@ -31,23 +29,27 @@ public class OnLaunch: NSObject {
public var theme = Theme.standard

/// Internal flag used to indicate that the SwiftUI host system is used
internal var isSwiftUIHost = false
var isSwiftUIHost = false

/// Bundle identifier used by server-side rules.
///
/// If not defined, it will fallback to `Bundle.main.bundleIdentifier`
public var bundleId: String?

/// The version of the build that identifies an iteration of the bundle.
/// Version of the build that identifies an iteration of the bundle.
///
/// If not defined, it will fallback to the `CFBundleVersion` defined in `Info.plist`
public var bundleVersion: String?

/// The release or version number of the bundle.
/// Release or version number of the bundle.
///
/// If not defined, it will fallback to the `CFBundleShortVersionString` defined in `Info.plist`
public var releaseVersion: String?

/// ID of the app in the App Store
///
/// If not defined, the ``ActionType.openInAppStore`` will throw a non-crashing assertion
public var appStoreId: Int?
}

/// Closure used to modify the given options instance
Expand All @@ -69,7 +71,7 @@ public class OnLaunch: NSObject {

let client = try OnLaunch(options: options)
try client.configure()
self.shared = client
shared = client

if options.shouldCheckOnConfigure {
check()
Expand All @@ -94,18 +96,18 @@ public class OnLaunch: NSObject {

// MARK: - Internal Static State

internal static var shared: OnLaunch?
static var shared: OnLaunch?

// MARK: - Internal State

/// Options as defined by the library user
internal var options: Options
var options: Options

/// Instance used to store data
internal let storage: LocalStorage
let storage: LocalStorage

/// The `URLSession` used to send API requests
internal let session: URLSession
let session: URLSession

/// URL used as the reference for all API calls, e.g. `https://onlaunch.kula.app/api`
private let baseURL: URL
Expand All @@ -114,14 +116,14 @@ public class OnLaunch: NSObject {
private var messageQueue: [Message] = []

/// The message which is currently presented
internal var currentMessage = CurrentValueSubject<Message?, Never>(nil)
var currentMessage = CurrentValueSubject<Message?, Never>(nil)

/// Completion handler used by the SwiftUI implementation
private var swiftUIDismissCompletionHandler: () -> Void = { }
private var swiftUIDismissCompletionHandler: () -> Void = {}

// MARK: - Internal Initializer

internal init(options: Options, storage: LocalStorage = LocalStorage(), session: URLSession = URLSession(configuration: .ephemeral)) throws {
init(options: Options, storage: LocalStorage = LocalStorage(), session: URLSession = URLSession(configuration: .ephemeral)) throws {
self.options = options
guard let baseURL = URL(string: options.baseURL) else {
throw OnLaunchError.invalidBaseURL(options.baseURL)
Expand All @@ -135,14 +137,21 @@ public class OnLaunch: NSObject {
// MARK: - Internal Methods

/// Helper function used to setup the completion handler for SwiftUI hosted views
internal func swiftUIContainerViewFor(message: Message) -> some View {
containerViewFor(message: message, completionHandler: swiftUIDismissCompletionHandler)
func swiftUIContainerViewFor(message: Message) -> some View {
containerViewFor(
message: message,
completionHandler: swiftUIDismissCompletionHandler
)
}

/// Creates the fully configured message view for the given `message`
private func containerViewFor(message: Message, completionHandler: @escaping () -> Void) -> some View {
MessageView(message: message, completionHandler: completionHandler)
.environment(\.theme, options.theme)
MessageView(
message: message,
options: options,
completionHandler: completionHandler
)
.environment(\.theme, options.theme)
}

// MARK: - Private Methods
Expand Down Expand Up @@ -209,15 +218,17 @@ public class OnLaunch: NSObject {
body: message.body,
isBlocking: message.blocking,
actions: message.actions.compactMap { action in
let kind: Action.Kind
switch action.actionType {
case .button:
kind = .button
case .dismissButton:
kind = .dismissButton
}
return Action(kind: kind, title: action.title)
})
let kind: Action.Kind
switch action.actionType {
case .button:
kind = .button
case .dismissButton:
kind = .dismissButton
case .openInAppStore:
kind = .openAppInAppStore
}
return Action(kind: kind, title: action.title)
})
}
let filteredMessages = messages.filter { message in
// Only include messages which are blocking or have not already been presented
Expand Down
3 changes: 1 addition & 2 deletions Sources/OnLaunch/Storage/LocalStorage.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Foundation
import os.log

internal class LocalStorage {

class LocalStorage {
private let defaults: UserDefaults

init(defaults: UserDefaults = .init(suiteName: "OnLaunch")!) {
Expand Down
8 changes: 3 additions & 5 deletions Sources/OnLaunch/SwiftUI/View+OnLaunch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import SwiftUI

/// Modifier used to integrate OnLaunch with SwiftUI
public struct OnLaunchModifier: ViewModifier {

/// Environment value used to track for scene updates
@Environment(\.scenePhase) private var scenePhase

Expand Down Expand Up @@ -55,10 +54,9 @@ public struct OnLaunchModifier: ViewModifier {
}
}

extension View {

public extension View {
/// Configures OnLaunch with the given `configurationHandler` and conditionally presents the OnLaunch UI
public func configureOnLaunch(_ configurationHandler: @escaping OnLaunch.ConfigurationHandler) -> some View {
self.modifier(OnLaunchModifier(configurationHandler: configurationHandler))
func configureOnLaunch(_ configurationHandler: @escaping OnLaunch.ConfigurationHandler) -> some View {
modifier(OnLaunchModifier(configurationHandler: configurationHandler))
}
}
3 changes: 1 addition & 2 deletions Sources/OnLaunch/Theme/Environment+Theme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ private struct ThemeEnvironmentKey: EnvironmentKey {
}

extension EnvironmentValues {

internal var theme: Theme {
var theme: Theme {
get {
self[ThemeEnvironmentKey.self]
}
Expand Down
Loading