Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
22 changes: 20 additions & 2 deletions Sources/MessagingInApp/Config/MessagingInAppConfigBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class MessagingInAppConfigBuilder {
// configuration options for MessagingInAppConfigOptions
private let siteId: String
private let region: Region
private var colorScheme: ColorScheme = .auto

/// Initializes new `MessagingInAppConfigBuilder` with required configuration options.
/// - Parameters:
Expand All @@ -28,11 +29,18 @@ public class MessagingInAppConfigBuilder {
self.region = region
}

@discardableResult
public func setColorScheme(_ colorScheme: ColorScheme) -> MessagingInAppConfigBuilder {
self.colorScheme = colorScheme
return self
}

/// Builds and returns `MessagingInAppConfigOptions` instance from the configured properties.
public func build() -> MessagingInAppConfigOptions {
MessagingInAppConfigOptions(
siteId: siteId,
region: region
region: region,
colorScheme: colorScheme
)
}
}
Expand Down Expand Up @@ -74,6 +82,16 @@ public extension MessagingInAppConfigBuilder {
let regionStr = sdkConfig[Keys.region.rawValue] as? String ?? ""
let region = Region.getRegion(from: regionStr)

return MessagingInAppConfigBuilder(siteId: siteId, region: region).build()
let builder = MessagingInAppConfigBuilder(siteId: siteId, region: region)

if let colorSchemeStr = config["colorScheme"] as? String {
switch colorSchemeStr {
case "light": builder.setColorScheme(.light)
case "dark": builder.setColorScheme(.dark)
default: builder.setColorScheme(.auto)
}
}

return builder.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import CioInternalCommon
public struct MessagingInAppConfigOptions {
public let siteId: String
public let region: Region
public let colorScheme: ColorScheme
}
40 changes: 39 additions & 1 deletion Sources/MessagingInApp/Gist/EngineWeb/EngineWeb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ protocol EngineWebInstance: AutoMockable {
var delegate: EngineWebDelegate? { get set }
var view: UIView { get }
func cleanEngineWeb()
func updateColorScheme(_ scheme: String)
}

public class EngineWeb: NSObject, EngineWebInstance {
Expand All @@ -34,7 +35,8 @@ public class EngineWeb: NSObject, EngineWebInstance {
webView
}

private let currentConfiguration: EngineWebConfiguration
private var currentConfiguration: EngineWebConfiguration
private let colorSchemeMode: ColorScheme

public private(set) var currentRoute: String {
get { _currentRoute }
Expand All @@ -45,6 +47,7 @@ public class EngineWeb: NSObject, EngineWebInstance {
init(configuration: EngineWebConfiguration, state: InAppMessageState, message: Message) {
self.currentMessage = message
self.currentConfiguration = configuration
self.colorSchemeMode = state.colorScheme

super.init()

Expand Down Expand Up @@ -92,6 +95,30 @@ public class EngineWeb: NSObject, EngineWebInstance {
}
}

public func updateColorScheme(_ scheme: String) {
applyInterfaceStyle(scheme)

do {
let jsonData = try JSONEncoder().encode(["action": "updateColorScheme", "colorScheme": scheme])
guard let jsonString = String(data: jsonData, encoding: .utf8) else { return }
let js = "window.postMessage(\(jsonString), '*');"
webView.evaluateJavaScript(js, completionHandler: nil)
} catch {
logger.logWithModuleTag("Failed to encode color scheme update: \(error)", level: .error)
}
}

Comment thread
cursor[bot] marked this conversation as resolved.
private func applyInterfaceStyle(_ scheme: String) {
switch scheme {
case "dark":
webView.overrideUserInterfaceStyle = .dark
case "light":
webView.overrideUserInterfaceStyle = .light
default:
webView.overrideUserInterfaceStyle = .unspecified
}
}

public func cleanEngineWeb() {
_timeoutTimer?.invalidate()
_timeoutTimer = nil
Expand Down Expand Up @@ -159,6 +186,17 @@ extension EngineWeb: WKScriptMessageHandler {
// swiftlint:enable cyclomatic_complexity
extension EngineWeb: WKNavigationDelegate {
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let resolvedScheme = colorSchemeMode.resolve(with: webView.traitCollection)
applyInterfaceStyle(resolvedScheme)
currentConfiguration = EngineWebConfiguration(
siteId: currentConfiguration.siteId,
dataCenter: currentConfiguration.dataCenter,
instanceId: currentConfiguration.instanceId,
endpoint: currentConfiguration.endpoint,
messageId: currentConfiguration.messageId,
properties: currentConfiguration.properties,
colorScheme: colorSchemeMode.resolve(with: webView.traitCollection)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we reuse resolvedScheme here?

)
injectConfiguration(currentConfiguration)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ struct EngineWebConfiguration: Encodable {
let messageId: String
let livePreview: Bool = false
let properties: [String: AnyEncodable?]?
let colorScheme: String?

init(
siteId: String,
dataCenter: String,
instanceId: String,
endpoint: String,
messageId: String,
properties: [String: AnyEncodable?]?
properties: [String: AnyEncodable?]?,
colorScheme: String? = nil
) {
self.siteId = siteId
self.dataCenter = dataCenter
self.instanceId = instanceId
self.endpoint = endpoint
self.messageId = messageId
self.properties = properties
self.colorScheme = colorScheme
}
}
45 changes: 44 additions & 1 deletion Sources/MessagingInApp/Gist/Managers/MessageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ open class BaseMessageManager {
var engine: EngineWebInstance!
var gistView: GistView!
var deeplinkUtil: DeepLinkUtil
private var lastResolvedColorScheme: String?
private var colorSchemeSubscriber: InAppMessageStoreSubscriber?

// MARK: - Initializers

Expand All @@ -47,13 +49,15 @@ open class BaseMessageManager {
self.deeplinkUtil = diGraph.deepLinkUtil

// Create engine
let resolvedColorScheme = MessagingInAppImplementation.currentColorScheme.resolve(with: UITraitCollection.current)
Comment thread
BernardGatt marked this conversation as resolved.
let engineWebConfiguration = EngineWebConfiguration(
siteId: state.siteId,
dataCenter: state.dataCenter,
instanceId: message.instanceId,
endpoint: state.environment.networkSettings.engineAPI,
messageId: message.messageId,
properties: message.properties.mapValues { AnyEncodable($0) }
properties: message.properties.mapValues { AnyEncodable($0) },
colorScheme: resolvedColorScheme
)
self.engine = engineWebProvider.getEngineWebInstance(
configuration: engineWebConfiguration,
Expand All @@ -64,12 +68,51 @@ open class BaseMessageManager {

// Create GistView
self.gistView = GistView(message: currentMessage, engineView: engine.view)
self.lastResolvedColorScheme = resolvedColorScheme

// Set delegate and subscribe
engine.delegate = self

// Subscribe to runtime colorScheme changes
subscribeToColorSchemeChanges()

// Handle system trait collection changes (dark mode toggle)
gistView.onTraitCollectionChange = { [weak self] traitCollection in
self?.handleTraitCollectionChange(traitCollection)
}
}

private func subscribeToColorSchemeChanges() {
colorSchemeSubscriber = {
let subscriber = InAppMessageStoreSubscriber { [weak self] state in
guard let self else { return }
let resolved = state.colorScheme.resolve(with: UITraitCollection.current)
if resolved != self.lastResolvedColorScheme {
self.lastResolvedColorScheme = resolved
self.threadUtil.runMain {
self.engine.updateColorScheme(resolved)
}
}
Comment thread
BernardGatt marked this conversation as resolved.
}
self.inAppMessageManager.subscribe(keyPath: \.colorScheme, subscriber: subscriber)
return subscriber
}()
}

private func handleTraitCollectionChange(_ traitCollection: UITraitCollection) {
let resolved = MessagingInAppImplementation.currentColorScheme.resolve(with: traitCollection)
if resolved != lastResolvedColorScheme {
lastResolvedColorScheme = resolved
threadUtil.runMain { [weak self] in
self?.engine.updateColorScheme(resolved)
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

deinit {
if let subscriber = colorSchemeSubscriber {
inAppMessageManager.unsubscribe(subscriber: subscriber)
}
removeEngineWebView()
}

Expand Down
38 changes: 31 additions & 7 deletions Sources/MessagingInApp/Gist/Managers/ModalMessageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import UIKit
public class ModalMessageManager: BaseMessageManager {
private var modalViewManager: ModalViewManager?
var inAppMessageStoreSubscriber: InAppMessageStoreSubscriber?
private var colorSchemeSubscriber: InAppMessageStoreSubscriber?

override init(state: InAppMessageState, message: Message) {
super.init(state: state, message: message)
subscribeToInAppMessageState()
subscribeToColorSchemeChanges()
}

deinit {
Expand All @@ -22,9 +25,10 @@ public class ModalMessageManager: BaseMessageManager {
let messageState = state.modalMessageState
switch messageState {
case .displayed:
let colorScheme = MessagingInAppImplementation.currentColorScheme
threadUtil.runMain {
// Subclasses (Modal or Inline) can show differently
self.onMessageDisplayed()
self.onMessageDisplayed(colorScheme: colorScheme)
}
case .dismissed, .initial:
threadUtil.runMain {
Expand All @@ -41,7 +45,7 @@ public class ModalMessageManager: BaseMessageManager {
}

// Show the modal when the message is displayed
func onMessageDisplayed() {
func onMessageDisplayed(colorScheme: ColorScheme = .auto) {
guard isMessageLoaded else {
logger.logWithModuleTag(
"Message not loaded yet. Skipping loadModalMessage for \(currentMessage.describeForLogs).",
Expand All @@ -62,7 +66,8 @@ public class ModalMessageManager: BaseMessageManager {
modalViewManager = ModalViewManager(
gistView: gistView,
position: gistProperties.position,
overlayColor: gistProperties.overlayColor
overlayColor: gistProperties.overlayColor,
colorScheme: colorScheme
)
// Show the modal with an optional completion
modalViewManager?.showModalView { [weak self] in
Expand Down Expand Up @@ -93,11 +98,30 @@ public class ModalMessageManager: BaseMessageManager {
modalViewManager.dismissModalView(completionHandler: dismissalHandler)
}

private func subscribeToColorSchemeChanges() {
colorSchemeSubscriber = {
let subscriber = InAppMessageStoreSubscriber { [weak self] state in
guard let self else { return }
let colorScheme = MessagingInAppImplementation.currentColorScheme
self.threadUtil.runMain {
self.modalViewManager?.updateColorScheme(colorScheme)
}
}
self.inAppMessageManager.subscribe(keyPath: \.colorScheme, subscriber: subscriber)
return subscriber
}()
}

open func unsubscribeFromInAppMessageState() {
guard let subscriber = inAppMessageStoreSubscriber else { return }
logger.logWithModuleTag("Unsubscribing BaseMessageManager from InAppMessageState", level: .debug)
inAppMessageManager.unsubscribe(subscriber: subscriber)
inAppMessageStoreSubscriber = nil
if let subscriber = inAppMessageStoreSubscriber {
logger.logWithModuleTag("Unsubscribing BaseMessageManager from InAppMessageState", level: .debug)
inAppMessageManager.unsubscribe(subscriber: subscriber)
inAppMessageStoreSubscriber = nil
}
if let subscriber = colorSchemeSubscriber {
inAppMessageManager.unsubscribe(subscriber: subscriber)
colorSchemeSubscriber = nil
}
}

private func finishDismissal(messageState: ModalMessageState) {
Expand Down
34 changes: 33 additions & 1 deletion Sources/MessagingInApp/Gist/Managers/ModalViewManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ class ModalViewManager {
var viewController: GistModalViewController!
var position: MessagePosition
var overlayColor: String?
var colorScheme: ColorScheme

init(gistView: GistView, position: MessagePosition, overlayColor: String?) {
init(gistView: GistView, position: MessagePosition, overlayColor: String?, colorScheme: ColorScheme = .auto) {
self.viewController = GistModalViewController()
viewController.gistView = gistView
viewController.setup(position: position)
self.position = position
self.overlayColor = overlayColor
self.colorScheme = colorScheme
}

func showModalView(completionHandler: @escaping () -> Void) {
viewController.view.isHidden = true
window = getUIWindow()
applyColorSchemeToWindow()
window?.rootViewController = viewController
window?.isHidden = false
var finalPosition: CGFloat = 0
Expand Down Expand Up @@ -83,6 +86,35 @@ class ModalViewManager {
window = nil
}

func updateColorScheme(_ newColorScheme: ColorScheme) {
colorScheme = newColorScheme
applyColorSchemeToWindow()
}

private func applyColorSchemeToWindow() {
switch colorScheme {
case .light:
window?.overrideUserInterfaceStyle = .light
case .dark:
window?.overrideUserInterfaceStyle = .dark
case .auto:
inheritAppInterfaceStyle()
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

private func inheritAppInterfaceStyle() {
guard let appWindow = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.flatMap({ $0.windows })
.first(where: { $0 !== self.window }) else { return }

if appWindow.overrideUserInterfaceStyle != .unspecified {
window?.overrideUserInterfaceStyle = appWindow.overrideUserInterfaceStyle
} else if let rootVC = appWindow.rootViewController, rootVC.overrideUserInterfaceStyle != .unspecified {
window?.overrideUserInterfaceStyle = rootVC.overrideUserInterfaceStyle
}
}
Comment thread
cursor[bot] marked this conversation as resolved.

private func getUIWindow() -> UIWindow {
var modalWindow = UIWindow(frame: UIScreen.main.bounds)
if #available(iOS 13.0, *) {
Expand Down
8 changes: 8 additions & 0 deletions Sources/MessagingInApp/Gist/Views/GistView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public protocol GistViewLifecycleDelegate: AnyObject {
public class GistView: UIView {
public weak var delegate: GistViewDelegate?
public weak var lifecycleDelegate: GistViewLifecycleDelegate?
var onTraitCollectionChange: ((UITraitCollection) -> Void)?
var message: Message?

convenience init(message: Message, engineView: UIView) {
Expand All @@ -25,6 +26,13 @@ public class GistView: UIView {
engineView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleRightMargin]
}

override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle {
onTraitCollectionChange?(traitCollection)
}
}

override public func removeFromSuperview() {
super.removeFromSuperview()

Expand Down
Loading
Loading