Skip to content

Commit e8b4bbb

Browse files
authored
feat: SQDSDKS-7416 -Add RoktConfig support for selectPlacements in Flutter SDK (#52)
* feat: SQDSDKS-7416 -Add RoktConfig support for selectPlacements in Flutter SDK - Introduce `RoktConfig`, `CacheConfig`, and `ColorMode` classes in Dart. - Update `selectPlacements` method to accept and pass `RoktConfig`. - Implement native handling for `RoktConfig` in iOS and Android, including parsing and applying color mode and cache settings. * feat: Add fontFilePathMap support to selectPlacements in Flutter SDK Signed-off-by: Thomson Thomas <thomson.thomas@rokt.com> * Update the params in selectPlacements Make fontFilePathMap nullable optional Signed-off-by: Thomson Thomas <thomson.thomas@rokt.com> --------- Signed-off-by: Thomson Thomas <thomson.thomas@rokt.com>
1 parent 4266640 commit e8b4bbb

3 files changed

Lines changed: 179 additions & 8 deletions

File tree

android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.mparticle.mparticle_flutter_sdk
22

3+
import android.content.Context
4+
import android.graphics.Typeface
35
import androidx.annotation.NonNull
46

57
import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -8,8 +10,8 @@ import io.flutter.plugin.common.MethodChannel
810
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
911
import io.flutter.plugin.common.MethodChannel.Result
1012

11-
import com.mparticle.identity.AliasRequest;
12-
import com.mparticle.identity.IdentityApi;
13+
import com.mparticle.identity.AliasRequest
14+
import com.mparticle.identity.IdentityApi
1315
import com.mparticle.identity.IdentityApiRequest
1416
import com.mparticle.identity.IdentityApiResult
1517
import com.mparticle.identity.IdentityHttpResponse
@@ -22,6 +24,8 @@ import com.mparticle.commerce.*
2224
import com.mparticle.consent.CCPAConsent
2325
import com.mparticle.consent.ConsentState
2426
import com.mparticle.consent.GDPRConsent
27+
import com.mparticle.rokt.CacheConfig
28+
import com.mparticle.rokt.RoktConfig
2529
import com.mparticle.rokt.RoktEmbeddedView
2630

2731
import org.json.JSONObject
@@ -38,11 +42,15 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler {
3842
private lateinit var channel: MethodChannel
3943
private val TAG = "MparticleFlutterSdkPlugin"
4044
private lateinit var layoutFactory: RoktLayoutFactory
45+
private var flutterAssets: FlutterPlugin.FlutterAssets? = null
46+
private var applicationContext: Context? = null
4147

4248
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
4349
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mparticle_flutter_sdk")
4450
channel.setMethodCallHandler(this)
4551
layoutFactory = RoktLayoutFactory(flutterPluginBinding.binaryMessenger)
52+
flutterAssets = flutterPluginBinding.flutterAssets
53+
applicationContext = flutterPluginBinding.applicationContext
4654
flutterPluginBinding.platformViewRegistry.registerViewFactory(
4755
VIEW_TYPE,
4856
layoutFactory,
@@ -688,6 +696,20 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler {
688696
val placementId: String? = call.argument("placementId")
689697
val attributes: Map<String, Any?>? = call.argument("attributes")
690698
val placeHolders: MutableMap<String, WeakReference<RoktEmbeddedView>> = mutableMapOf()
699+
val configMap = call.argument<HashMap<String, Any>>("config")
700+
val config = configMap?.let { buildRoktConfig(it) }
701+
val customFonts = call.argument<HashMap<String, String>>("fontFilePathMap")
702+
.orEmpty()
703+
.mapNotNull { (key, fontPath) ->
704+
applicationContext?.assets?.let { assets ->
705+
flutterAssets?.getAssetFilePathByName(fontPath)?.let { assetPath ->
706+
runCatching {
707+
key to WeakReference(Typeface.createFromAsset(assets, assetPath))
708+
}.getOrNull()
709+
}
710+
}
711+
}
712+
.toMap()
691713

692714
call.argument<HashMap<Int, String>>("placeholders")?.entries?.forEach { entry ->
693715
layoutFactory.nativeViews[entry.key]?.let { view ->
@@ -706,14 +728,35 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler {
706728
}
707729

708730
MParticle.getInstance()?.let { instance ->
709-
instance.Rokt().selectPlacements(placementId, stringAttributes, null, placeHolders.takeIf { it.isNotEmpty() }, null, null)
731+
instance.Rokt().selectPlacements(placementId, stringAttributes, null, placeHolders.takeIf { it.isNotEmpty() }, customFonts, config)
710732
result.success(true)
711733
} ?: result.error(TAG, "No mParticle instance exists", null)
712734
} catch (e: Exception) {
713735
result.error(TAG, e.localizedMessage, null)
714736
}
715737
}
716738

739+
private fun buildRoktConfig(configMap: Map<String, Any>): RoktConfig {
740+
val builder = RoktConfig.Builder()
741+
(configMap["colorMode"] as? String)?.let {
742+
builder.colorMode(it.toColorMode())
743+
}
744+
(configMap["cacheConfig"] as? Map<String, Any>)?.let { cacheConfig ->
745+
val cacheDurationInSeconds = cacheConfig["cacheDurationInSeconds"] as? Int ?: 0
746+
val cacheAttributes = cacheConfig["cacheAttributes"] as? Map<String, String> ?: null
747+
builder.cacheConfig(CacheConfig(cacheDurationInSeconds.toLong(), cacheAttributes))
748+
}
749+
750+
return builder.build()
751+
}
752+
753+
private fun String.toColorMode(): RoktConfig.ColorMode =
754+
when (this) {
755+
"dark" -> RoktConfig.ColorMode.DARK
756+
"light" -> RoktConfig.ColorMode.LIGHT
757+
else -> RoktConfig.ColorMode.SYSTEM
758+
}
759+
717760
private fun ConvertIdentityHttpResponseToString(response: IdentityHttpResponse?): String {
718761
val map = mutableMapOf<String, Any?>()
719762

ios/Classes/SwiftMparticleFlutterSdkPlugin.swift

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin {
77
fileprivate static let VIEW_CALL_DELEGATE = "rokt_sdk.rokt.com/rokt_layout"
88
let roktLayoutFactory: RoktLayoutFactory
99
let channel: FlutterMethodChannel
10+
let registrar: FlutterPluginRegistrar
1011

11-
init(messenger: FlutterBinaryMessenger) {
12+
init(messenger: FlutterBinaryMessenger, registrar: FlutterPluginRegistrar) {
1213
self.roktLayoutFactory = RoktLayoutFactory(messenger: messenger)
14+
self.registrar = registrar
1315
self.channel = FlutterMethodChannel(name: "mparticle_flutter_sdk", binaryMessenger: messenger)
1416
}
1517

1618
public static func register(with registrar: FlutterPluginRegistrar) {
17-
let instance = SwiftMparticleFlutterSdkPlugin(messenger: registrar.messenger())
19+
let instance = SwiftMparticleFlutterSdkPlugin(messenger: registrar.messenger(), registrar: registrar)
1820
registrar.addMethodCallDelegate(instance, channel: instance.channel)
1921
registrar.register(instance.roktLayoutFactory, withId: SwiftMparticleFlutterSdkPlugin.VIEW_CALL_DELEGATE)
2022
}
@@ -502,7 +504,7 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin {
502504
MParticle._setWrapperSdk_internal(MPWrapperSdk.flutter, version: "")
503505
case "roktSelectPlacements":
504506
if let callArguments = call.arguments as? [String: Any],
505-
let placementId = callArguments["placementId"] as? String {
507+
let placementId = callArguments["placementId"] as? String {
506508
let attributes = callArguments["attributes"] as? [String: String] ?? [:]
507509

508510
var placeholders: [String: MPRoktEmbeddedView] = [:]
@@ -525,8 +527,17 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin {
525527
}
526528
}
527529
}
530+
531+
var roktConfig: MPRoktConfig?
532+
if let configMap = callArguments["config"] as? [String: Any] {
533+
roktConfig = buildRoktConfig(configMap: configMap)
534+
}
535+
let fontFilePathMap = callArguments["fontFilePathMap"] as? Dictionary<String, String>
536+
if let typefaces = fontFilePathMap {
537+
registerPartnerFonts(typefaces)
538+
}
528539

529-
MParticle.sharedInstance().rokt.selectPlacements(placementId, attributes: attributes, placements: placeholders, config: nil, callbacks: callback)
540+
MParticle.sharedInstance().rokt.selectPlacements(placementId, attributes: attributes, placements: placeholders, config: roktConfig, callbacks: callback)
530541
result(true)
531542
} else {
532543
print("Incorrect argument for \(call.method) iOS method")
@@ -536,6 +547,51 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin {
536547
print("mParticle flutter SDK for iOS does not support \(call.method)")
537548
}
538549
}
550+
551+
private func registerPartnerFonts(_ typefaces: Dictionary<String, String>) {
552+
let bundle = Bundle.main
553+
for (_, fileName) in typefaces {
554+
let fontKey = registrar.lookupKey(forAsset: fileName)
555+
let path = bundle.path(forResource: fontKey, ofType: nil)
556+
var errorRef: Unmanaged<CFError>? = nil
557+
guard let filePath = path, path?.isEmpty == false else {
558+
continue
559+
}
560+
let fontUrl = NSURL(fileURLWithPath: filePath)
561+
CTFontManagerRegisterFontsForURL(fontUrl, .process, &errorRef)
562+
}
563+
}
564+
}
565+
566+
private func buildRoktConfig(configMap: [String: Any]) -> MPRoktConfig? {
567+
let config = MPRoktConfig()
568+
var isConfigEmpty = true
569+
570+
if let colorModeString = configMap["colorMode"] as? String {
571+
if #available(iOS 12.0, *) {
572+
isConfigEmpty = false
573+
switch colorModeString {
574+
case "dark":
575+
if #available(iOS 13.0, *) {
576+
config.colorMode = .dark
577+
}
578+
case "light":
579+
config.colorMode = .light
580+
default: // "system"
581+
config.colorMode = .system
582+
}
583+
}
584+
}
585+
586+
if let cacheConfigMap = configMap["cacheConfig"] as? [String: Any] {
587+
isConfigEmpty = false
588+
let cacheDuration = cacheConfigMap["cacheDurationInSeconds"] as? NSNumber ?? 0
589+
let cacheAttributes = cacheConfigMap["cacheAttributes"] as? [String: String]
590+
config.cacheAttributes = cacheAttributes
591+
config.cacheDuration = cacheDuration
592+
}
593+
594+
return isConfigEmpty ? nil : config
539595
}
540596

541597
private func asStringForStringKey(jsonDictionary: [String : Any]) -> String {

lib/mparticle_flutter_sdk.dart

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ class Rokt {
300300
static const MethodChannel _channel =
301301
const MethodChannel('mparticle_flutter_sdk');
302302

303-
/// Selects placements with a [placementId] and optional [attributes].
303+
/// Selects placements with a [placementId], optional [attributes], optional [roktConfig], and optional [fontFilePathMap].
304304
///
305305
/// This method calls the Rokt selectPlacements API on each platform:
306306
/// - Web: mParticle.Rokt.selectPlacements()
@@ -309,15 +309,87 @@ class Rokt {
309309
Future<void> selectPlacements({
310310
required String placementId,
311311
Map<String, dynamic>? attributes,
312+
RoktConfig? roktConfig,
313+
Map<String, String>? fontFilePathMap,
312314
}) async {
313315
var params = {
314316
'placementId': placementId,
315317
'attributes': attributes,
318+
'config': _roktConfigToMap(config: roktConfig),
319+
'fontFilePathMap': fontFilePathMap,
316320
};
317321

318322
if (MparticleFlutterSdk._placeholders.isNotEmpty) {
319323
params['placeholders'] = MparticleFlutterSdk._placeholders;
320324
}
321325
return await _channel.invokeMethod('roktSelectPlacements', params);
322326
}
327+
328+
Map<String, dynamic>? _roktConfigToMap({required RoktConfig? config}) {
329+
if (config == null) {
330+
return null;
331+
}
332+
return {
333+
'colorMode': config.colorMode.name,
334+
'cacheConfig': config.cacheConfig != null
335+
? {
336+
'cacheDurationInSeconds': config.cacheConfig?.cacheDurationInSeconds,
337+
'cacheAttributes': config.cacheConfig?.cacheAttributes
338+
}
339+
: null
340+
};
341+
}
342+
}
343+
344+
/// Cache configuration for the Rokt SDK
345+
///
346+
/// - Attributes
347+
/// - [int] cacheDurationInSeconds: duration in seconds for which the Rokt SDK should cache the experience. Default is 90 minutes
348+
/// - [Map<String, String>]? cacheAttributes: optional attributes to be used as cache key. If null, all the attributes will be used as the cache key
349+
@immutable
350+
class CacheConfig {
351+
/// Duration in seconds for which the Rokt SDK should cache the experience
352+
final int cacheDurationInSeconds;
353+
/// Optional attributes to be used as cache key
354+
final Map<String, String>? cacheAttributes;
355+
356+
/// Constructor
357+
///
358+
/// - Parameters
359+
/// - [int] cacheDurationInSeconds: duration in seconds for which the Rokt SDK should cache the experience. Default is 90 minutes
360+
/// - [Map<String, String>]? cacheAttributes: optional attributes to be used as cache key. If null, all the attributes will be used as the cache key
361+
const CacheConfig(
362+
{this.cacheDurationInSeconds = 0, this.cacheAttributes = null});
363+
}
364+
365+
/// Configuration settings for the Rokt SDK <br>
366+
///
367+
/// - Attributes
368+
/// - [ColorMode]? colorMode: preferred device color mode configuration
369+
/// - [CacheConfig]? cacheConfig: cache configuration for the Rokt SDK
370+
@immutable
371+
class RoktConfig {
372+
/// The device color mode your application is using
373+
final ColorMode colorMode;
374+
/// The cache configuration for the Rokt SDK
375+
final CacheConfig? cacheConfig;
376+
377+
/// Constructor
378+
///
379+
/// - Parameters
380+
/// - [ColorMode]? colorMode: preferred device color mode configuration
381+
/// - [CacheConfig]? cacheConfig: cache configuration for the Rokt SDK
382+
const RoktConfig({this.colorMode = ColorMode.system, this.cacheConfig = null});
383+
}
384+
385+
/// Enum representing device color modes
386+
enum ColorMode {
387+
/// Request Light mode configuration
388+
light,
389+
390+
/// Request Dark mode configuration
391+
dark,
392+
393+
/// Request System's current configuration
394+
system
323395
}

0 commit comments

Comments
 (0)