From 29c2ba06629d2245f6ba6e9b6ee9b574e10fdbd0 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 14 Jun 2024 10:55:59 +0300 Subject: [PATCH] [MC-1468] Custom templates present and dismiss (Custom Templates Part 4) (#332) * Implement Template Context * Implement custom template present and dismiss * Clear template context delegate on dismiss * Test evaluate custom templates * Add template present and dismiss tests --- CleverTapSDK.xcodeproj/project.pbxproj | 14 + CleverTapSDK/CTConstants.h | 1 + CleverTapSDK/CTInAppDisplayViewController.h | 21 +- CleverTapSDK/CTInAppDisplayViewController.m | 4 +- .../CTInAppNotificationDisplayDelegate.h | 36 ++ CleverTapSDK/CTInAppUtils.h | 5 +- CleverTapSDK/CTInAppUtils.m | 33 +- CleverTapSDK/CTNotificationButton.m | 3 +- CleverTapSDK/CleverTap.m | 9 +- CleverTapSDK/InApps/CTAlertViewController.m | 4 +- .../InApps/CTBaseHeaderFooterViewController.m | 4 +- CleverTapSDK/InApps/CTInAppDisplayManager.h | 5 +- CleverTapSDK/InApps/CTInAppDisplayManager.m | 109 +++++- .../InApps/CTInAppEvaluationManager.m | 4 + .../InApps/CTInAppHTMLViewController.m | 4 +- .../CTCustomTemplate-Internal.h | 1 + .../CTCustomTemplatesManager-Internal.h | 25 ++ .../CTCustomTemplatesManager.h | 3 - .../CTCustomTemplatesManager.m | 18 +- .../CTTemplateContext-Internal.h | 11 +- .../CustomTemplates/CTTemplateContext.h | 33 +- .../CustomTemplates/CTTemplateContext.m | 210 ++++++++++++ .../CustomTemplates/CTTemplatePresenter.h | 1 + .../InApps/CTInAppEvaluationManagerTest.m | 63 +++- .../CTCustomTemplatesManagerTest.m | 73 +++- .../CustomTemplates/CTTemplateContextTest.m | 311 ++++++++++++++++++ .../CustomTemplates/CTTemplatePresenterMock.h | 3 + .../CustomTemplates/CTTemplatePresenterMock.m | 2 + 28 files changed, 936 insertions(+), 74 deletions(-) create mode 100644 CleverTapSDK/CTInAppNotificationDisplayDelegate.h create mode 100644 CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager-Internal.h create mode 100644 CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 90e89a33..30cb48f4 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -371,6 +371,10 @@ 6BB778CC2BED21CE00A41628 /* CTNotificationAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB778CA2BED21CE00A41628 /* CTNotificationAction.m */; }; 6BB778CE2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB778CD2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m */; }; 6BB778D02BEE4C3400A41628 /* CTNotificationActionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB778CF2BEE4C3400A41628 /* CTNotificationActionTest.m */; }; + 6BB778D22BF267B600A41628 /* CTTemplateContextTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB778D12BF267B600A41628 /* CTTemplateContextTest.m */; }; + 6BB778D62BFD26E000A41628 /* CTInAppNotificationDisplayDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BB778D52BFD26DF00A41628 /* CTInAppNotificationDisplayDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6BB778D72BFD26E000A41628 /* CTInAppNotificationDisplayDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BB778D52BFD26DF00A41628 /* CTInAppNotificationDisplayDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 6BB778D92BFD277400A41628 /* CTCustomTemplatesManager-Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BB778D82BFD277400A41628 /* CTCustomTemplatesManager-Internal.h */; }; 6BD334EA2AF2A41F0099E33E /* CTBatchSentDelegateHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BD334E82AF2A41F0099E33E /* CTBatchSentDelegateHelper.h */; }; 6BD334EB2AF2A41F0099E33E /* CTBatchSentDelegateHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BD334E82AF2A41F0099E33E /* CTBatchSentDelegateHelper.h */; }; 6BD334EC2AF2A41F0099E33E /* CTBatchSentDelegateHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BD334E92AF2A41F0099E33E /* CTBatchSentDelegateHelper.m */; }; @@ -906,6 +910,9 @@ 6BB778CA2BED21CE00A41628 /* CTNotificationAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTNotificationAction.m; sourceTree = ""; }; 6BB778CD2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTCustomTemplateInAppDataTest.m; sourceTree = ""; }; 6BB778CF2BEE4C3400A41628 /* CTNotificationActionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTNotificationActionTest.m; sourceTree = ""; }; + 6BB778D12BF267B600A41628 /* CTTemplateContextTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTTemplateContextTest.m; sourceTree = ""; }; + 6BB778D52BFD26DF00A41628 /* CTInAppNotificationDisplayDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTInAppNotificationDisplayDelegate.h; sourceTree = ""; }; + 6BB778D82BFD277400A41628 /* CTCustomTemplatesManager-Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTCustomTemplatesManager-Internal.h"; sourceTree = ""; }; 6BD334E82AF2A41F0099E33E /* CTBatchSentDelegateHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTBatchSentDelegateHelper.h; sourceTree = ""; }; 6BD334E92AF2A41F0099E33E /* CTBatchSentDelegateHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTBatchSentDelegateHelper.m; sourceTree = ""; }; 6BD334EF2AF545C70099E33E /* CTInAppStoreTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppStoreTest.m; sourceTree = ""; }; @@ -1447,6 +1454,7 @@ 6B32A0B22B9F2E8F009ADC57 /* CTTestTemplateProducer.h */, 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */, 6BB778CD2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m */, + 6BB778D12BF267B600A41628 /* CTTemplateContextTest.m */, ); path = CustomTemplates; sourceTree = ""; @@ -1475,6 +1483,7 @@ 6B32A0A02B99033F009ADC57 /* CTCustomTemplateBuilder-Internal.h */, 6BB778C52BECEC2700A41628 /* CTCustomTemplateInAppData.h */, 6BB778C62BECEC2700A41628 /* CTCustomTemplateInAppData.m */, + 6BB778D82BFD277400A41628 /* CTCustomTemplatesManager-Internal.h */, ); path = CustomTemplates; sourceTree = ""; @@ -1730,6 +1739,7 @@ 6BA3B2DA2B03E926004E834B /* CTQueueType.h */, 6BB778C92BED21CE00A41628 /* CTNotificationAction.h */, 6BB778CA2BED21CE00A41628 /* CTNotificationAction.m */, + 6BB778D52BFD26DF00A41628 /* CTInAppNotificationDisplayDelegate.h */, ); path = CleverTapSDK; sourceTree = ""; @@ -1798,6 +1808,7 @@ 4E8B816C2AD2B2FD00714BB4 /* CleverTapInternal.h in Headers */, D014B90220E2FB4F001E0780 /* CTEventBuilder.h in Headers */, 6A6591692AC70FFE005FDE57 /* CTBatchSentDelegate.h in Headers */, + 6BB778D72BFD26E000A41628 /* CTInAppNotificationDisplayDelegate.h in Headers */, 07BF465B217F7C41002E166D /* CTInAppDisplayViewController.h in Headers */, 4E25E3D22788889F0008C888 /* CTLoginInfoProvider.h in Headers */, D0BD75AF241769E40006EE55 /* CleverTap+ProductConfig.h in Headers */, @@ -1889,6 +1900,7 @@ 6B535FB62AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */, 4EF0D5452AD84BCA0044C48F /* CTSessionManager.h in Headers */, D0213D4C207D905800FE5740 /* CleverTapSyncDelegate.h in Headers */, + 6BB778D92BFD277400A41628 /* CTCustomTemplatesManager-Internal.h in Headers */, 07B94546219EA34300D4C542 /* CTMessageMO+CoreDataProperties.h in Headers */, 48BEA4F62AFB868B00690424 /* CTInAppImagePrefetchManager.h in Headers */, 6BB727332B8F787D009CE7D0 /* CTCustomTemplatesManager.h in Headers */, @@ -1982,6 +1994,7 @@ 6BB727212B8E55CD009CE7D0 /* CTTemplateContext.h in Headers */, 4E8B816F2AD2BB8A00714BB4 /* CTDispatchQueueManager.h in Headers */, D01A0894207EC2D400423D6F /* CleverTapInstanceConfig.h in Headers */, + 6BB778D62BFD26E000A41628 /* CTInAppNotificationDisplayDelegate.h in Headers */, 4808030E292EB4FB00C06E2F /* CleverTap+PushPermission.h in Headers */, 071EB50C217F6427008F0FAB /* CTCoverImageViewController.h in Headers */, 4E8B81782AD2CB4E00714BB4 /* CTPushPrimerManager.h in Headers */, @@ -2410,6 +2423,7 @@ 6B32A0B42B9F2E8F009ADC57 /* CTTestTemplateProducer.m in Sources */, 6A2E0B9829D49D5100FCEA5F /* CTVarCacheMock.m in Sources */, 4EAF05022A495DD5009D9D61 /* CleverTapInstanceTests.m in Sources */, + 6BB778D22BF267B600A41628 /* CTTemplateContextTest.m in Sources */, 6A2E0B9329D0A5CF00FCEA5F /* CTVariablesTest.m in Sources */, D02AC2DB276044F70031C1BE /* CleverTapSDKTests.m in Sources */, 32394C2129FA264B00956058 /* CTPreferencesTest.m in Sources */, diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 4bb18691..1bf9974e 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -186,6 +186,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_INAPP_TEMPLATE_ID @"templateId" #define CLTAP_INAPP_TEMPLATE_DESCRIPTION @"templateDescription" #define CLTAP_INAPP_VARS @"vars" +#define CLTAP_INAPP_ACTIONS @"actions" #define CLTAP_INAPP_PREVIEW_TYPE @"wzrk_inapp_type" #define CLTAP_INAPP_IMAGE_INTERSTITIAL_TYPE @"image-interstitial" diff --git a/CleverTapSDK/CTInAppDisplayViewController.h b/CleverTapSDK/CTInAppDisplayViewController.h index 978fd603..986a047b 100644 --- a/CleverTapSDK/CTInAppDisplayViewController.h +++ b/CleverTapSDK/CTInAppDisplayViewController.h @@ -1,29 +1,10 @@ #import #import "CTInAppNotification.h" +#import "CTInAppNotificationDisplayDelegate.h" #if !(TARGET_OS_TV) #import "CleverTapJSInterface.h" #endif -@class CTInAppDisplayViewController; - -@protocol CTInAppNotificationDisplayDelegate -- (void)handleNotificationCTA:(NSURL*)ctaURL buttonCustomExtras:(NSDictionary *)buttonCustomExtras forNotification:(CTInAppNotification*)notification fromViewController:(CTInAppDisplayViewController*)controller withExtras:(NSDictionary*)extras; -- (void)notificationDidDismiss:(CTInAppNotification*)notification fromViewController:(CTInAppDisplayViewController*)controller; -/** - Called when in-app button is tapped for requesting push permission. - */ -- (void)handleInAppPushPrimer:(CTInAppNotification*)notification - fromViewController:(CTInAppDisplayViewController*)controller - withFallbackToSettings:(BOOL)isFallbackToSettings; - -/** - Called to notify that local in-app push primer is dismissed. - */ -- (void)inAppPushPrimerDidDismissed; -@optional -- (void)notificationDidShow:(CTInAppNotification*)notification fromViewController:(CTInAppDisplayViewController*)controller; -@end - @interface CTInAppDisplayViewController : UIViewController @property (nonatomic, weak) id delegate; diff --git a/CleverTapSDK/CTInAppDisplayViewController.m b/CleverTapSDK/CTInAppDisplayViewController.m index fccd16a4..c15a58e0 100644 --- a/CleverTapSDK/CTInAppDisplayViewController.m +++ b/CleverTapSDK/CTInAppDisplayViewController.m @@ -136,8 +136,8 @@ - (void)showFromWindow:(BOOL)animated { [self.window setHidden:NO]; void (^completionBlock)(void) = ^ { - if (self.delegate && [self.delegate respondsToSelector:@selector(notificationDidShow:fromViewController:)]) { - [self.delegate notificationDidShow:self.notification fromViewController:self]; + if (self.delegate) { + [self.delegate notificationDidShow:self.notification]; } }; diff --git a/CleverTapSDK/CTInAppNotificationDisplayDelegate.h b/CleverTapSDK/CTInAppNotificationDisplayDelegate.h new file mode 100644 index 00000000..e0346f68 --- /dev/null +++ b/CleverTapSDK/CTInAppNotificationDisplayDelegate.h @@ -0,0 +1,36 @@ +// +// CTInAppNotificationDisplayDelegate.h +// CleverTapSDK +// +// Created by Nikola Zagorchev on 21.05.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#ifndef CTInAppNotificationDisplayDelegate_h +#define CTInAppNotificationDisplayDelegate_h + +@class CTInAppDisplayViewController; + +@protocol CTInAppNotificationDisplayDelegate + +- (void)notificationDidShow:(CTInAppNotification *)notification; + +- (void)handleNotificationCTA:(NSURL *)ctaURL buttonCustomExtras:(NSDictionary *)buttonCustomExtras forNotification:(CTInAppNotification *)notification fromViewController:(CTInAppDisplayViewController *)controller withExtras:(NSDictionary *)extras; + +- (void)notificationDidDismiss:(CTInAppNotification *)notification fromViewController:(CTInAppDisplayViewController *)controller; + +/** + Called when in-app button is tapped for requesting push permission. + */ +- (void)handleInAppPushPrimer:(CTInAppNotification *)notification + fromViewController:(CTInAppDisplayViewController *)controller + withFallbackToSettings:(BOOL)isFallbackToSettings; + +/** + Called to notify that local in-app push primer is dismissed. + */ +- (void)inAppPushPrimerDidDismissed; + +@end + +#endif /* Header_h */ diff --git a/CleverTapSDK/CTInAppUtils.h b/CleverTapSDK/CTInAppUtils.h index 7eeee4b4..dc75b6ff 100644 --- a/CleverTapSDK/CTInAppUtils.h +++ b/CleverTapSDK/CTInAppUtils.h @@ -27,8 +27,9 @@ typedef NS_ENUM(NSUInteger, CTInAppActionType){ @interface CTInAppUtils : NSObject -+ (CTInAppType)inAppTypeFromString:(NSString* _Nonnull)type; -+ (CTInAppActionType)inAppActionTypeFromString:(NSString* _Nonnull)type; ++ (CTInAppType)inAppTypeFromString:(NSString *_Nonnull)type; ++ (CTInAppActionType)inAppActionTypeFromString:(NSString *_Nonnull)type; ++ (NSString * _Nonnull)inAppActionTypeString:(CTInAppActionType)type; + (NSBundle *_Nullable)bundle; + (NSString *_Nullable)getXibNameForControllerName:(NSString *_Nonnull)controllerName; diff --git a/CleverTapSDK/CTInAppUtils.m b/CleverTapSDK/CTInAppUtils.m index 408cb32a..adcbb907 100644 --- a/CleverTapSDK/CTInAppUtils.m +++ b/CleverTapSDK/CTInAppUtils.m @@ -6,8 +6,9 @@ #import "CTUIUtils.h" #endif -static NSDictionary *_inAppTypeMap; -static NSDictionary *_inAppActionTypeMap; +static NSDictionary *_inAppTypeMap; +static NSDictionary *_inAppActionTypeStringToTypeMap; +static NSDictionary *_inAppActionTypeTypeToStringMap; @implementation CTInAppUtils @@ -35,9 +36,21 @@ + (CTInAppType)inAppTypeFromString:(NSString*)type { return [_type integerValue]; } -+ (CTInAppActionType)inAppActionTypeFromString:(NSString* _Nonnull)type { - if (_inAppActionTypeMap == nil) { - _inAppActionTypeMap = @{ ++ (NSDictionary *)inAppActionTypeTypeToStringMap { + if (_inAppActionTypeTypeToStringMap == nil) { + NSDictionary *dict = [self inAppActionTypeStringToTypeMap]; + NSMutableDictionary *swapped = [NSMutableDictionary new]; + [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + swapped[value] = key; + }]; + _inAppActionTypeTypeToStringMap = [swapped copy]; + } + return _inAppActionTypeTypeToStringMap; +} + ++ (NSDictionary *)inAppActionTypeStringToTypeMap { + if (_inAppActionTypeStringToTypeMap == nil) { + _inAppActionTypeStringToTypeMap = @{ @"close": @(CTInAppActionTypeClose), @"url": @(CTInAppActionTypeOpenURL), @"kv": @(CTInAppActionTypeKeyValues), @@ -45,14 +58,20 @@ + (CTInAppActionType)inAppActionTypeFromString:(NSString* _Nonnull)type { @"rfp": @(CTInAppActionTypeRequestForPermission) }; } - - NSNumber *_type = type != nil ? _inAppActionTypeMap[type] : @(CTInAppActionTypeUnknown); + return _inAppActionTypeStringToTypeMap; +} + ++ (CTInAppActionType)inAppActionTypeFromString:(NSString* _Nonnull)type { + NSNumber *_type = type != nil ? [self inAppActionTypeStringToTypeMap][type] : @(CTInAppActionTypeUnknown); if (_type == nil) { _type = @(CTInAppActionTypeUnknown); } return [_type integerValue]; } ++ (NSString * _Nonnull)inAppActionTypeString:(CTInAppActionType)type { + return self.inAppActionTypeTypeToStringMap[@(type)]; +} + (NSBundle *)bundle { #if CLEVERTAP_NO_INAPP_SUPPORT diff --git a/CleverTapSDK/CTNotificationButton.m b/CleverTapSDK/CTNotificationButton.m index a4525f33..1c902fd8 100644 --- a/CleverTapSDK/CTNotificationButton.m +++ b/CleverTapSDK/CTNotificationButton.m @@ -1,4 +1,5 @@ #import "CTNotificationButton.h" +#import "CTConstants.h" @interface CTNotificationButton () { @@ -30,7 +31,7 @@ - (instancetype)initWithJSON:(NSDictionary *)jsonObject { self.borderColor = jsonObject[@"border"]; self.backgroundColor = jsonObject[@"bg"]; - NSDictionary *actions = jsonObject[@"actions"]; + NSDictionary *actions = jsonObject[CLTAP_INAPP_ACTIONS]; if (actions) { self.action = [[CTNotificationAction alloc] initWithJSON:actions]; if (self.action.error) { diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index ac669dc5..1a956548 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -57,7 +57,7 @@ #import "CTInAppEvaluationManager.h" #import "CTInAppTriggerManager.h" #import "CTInAppImagePrefetchManager.h" -#import "CTCustomTemplatesManager.h" +#import "CTCustomTemplatesManager-Internal.h" #endif #if !CLEVERTAP_NO_INBOX_SUPPORT @@ -523,17 +523,18 @@ - (void)initializeInAppSupport { CTInAppFCManager *inAppFCManager = [[CTInAppFCManager alloc] initWithConfig:self.config delegateManager:self.delegateManager deviceId:[_deviceInfo.deviceId copy] impressionManager:impressionManager inAppTriggerManager:triggerManager]; + CTCustomTemplatesManager *templatesManager = [[CTCustomTemplatesManager alloc] initWithConfig:self.config]; + CTInAppDisplayManager *displayManager = [[CTInAppDisplayManager alloc] initWithCleverTap:self dispatchQueueManager:self.dispatchQueueManager inAppFCManager:inAppFCManager impressionManager:impressionManager inAppStore:inAppStore - imagePrefetchManager:self.imagePrefetchManager]; + imagePrefetchManager:self.imagePrefetchManager + templatesManager:templatesManager]; CTInAppEvaluationManager *evaluationManager = [[CTInAppEvaluationManager alloc] initWithAccountId:self.config.accountId deviceId:self.deviceInfo.deviceId delegateManager:self.delegateManager impressionManager:impressionManager inAppDisplayManager:displayManager inAppStore:inAppStore inAppTriggerManager:triggerManager]; - CTCustomTemplatesManager *templatesManager = [[CTCustomTemplatesManager alloc] initWithConfig:self.config]; - self.customTemplatesManager = templatesManager; self.inAppFCManager = inAppFCManager; self.impressionManager = impressionManager; diff --git a/CleverTapSDK/InApps/CTAlertViewController.m b/CleverTapSDK/InApps/CTAlertViewController.m index 490cab1c..9f496fec 100644 --- a/CleverTapSDK/InApps/CTAlertViewController.m +++ b/CleverTapSDK/InApps/CTAlertViewController.m @@ -118,8 +118,8 @@ - (void)showFromWindow:(BOOL)animated { [self.window setHidden:NO]; void (^completionBlock)(void) = ^ { - if (self.delegate && [self.delegate respondsToSelector:@selector(notificationDidShow:fromViewController:)]) { - [self.delegate notificationDidShow:self.notification fromViewController:self]; + if (self.delegate) { + [self.delegate notificationDidShow:self.notification]; } }; diff --git a/CleverTapSDK/InApps/CTBaseHeaderFooterViewController.m b/CleverTapSDK/InApps/CTBaseHeaderFooterViewController.m index 32ce38ea..84f6f43a 100644 --- a/CleverTapSDK/InApps/CTBaseHeaderFooterViewController.m +++ b/CleverTapSDK/InApps/CTBaseHeaderFooterViewController.m @@ -381,8 +381,8 @@ - (void)showFromWindow:(BOOL)animated { [self.window setHidden:NO]; void (^completionBlock)(void) = ^ { - if (self.delegate && [self.delegate respondsToSelector:@selector(notificationDidShow:fromViewController:)]) { - [self.delegate notificationDidShow:self.notification fromViewController:self]; + if (self.delegate) { + [self.delegate notificationDidShow:self.notification]; } }; if (animated) { diff --git a/CleverTapSDK/InApps/CTInAppDisplayManager.h b/CleverTapSDK/InApps/CTInAppDisplayManager.h index e64e9c9d..9d56a80f 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.h +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.h @@ -14,6 +14,7 @@ #import "CTPushPrimerManager.h" #import "CTInAppStore.h" #import "CTInAppImagePrefetchManager.h" +#import "CTCustomTemplatesManager.h" NS_ASSUME_NONNULL_BEGIN @@ -36,11 +37,13 @@ typedef NS_ENUM(NSInteger, CleverTapInAppRenderingStatus) { inAppFCManager:(CTInAppFCManager *)inAppFCManager impressionManager:(CTImpressionManager *)impressionManager inAppStore:(CTInAppStore *)inAppStore - imagePrefetchManager:(CTInAppImagePrefetchManager *)imagePrefetchManager; + imagePrefetchManager:(CTInAppImagePrefetchManager *)imagePrefetchManager + templatesManager:(CTCustomTemplatesManager *)templatesManager; - (void)setPushPrimerManager:(CTPushPrimerManager* _Nonnull)pushPrimerManagerObj; - (void)prepareNotificationForDisplay:(NSDictionary* _Nonnull)jsonObj; - (BOOL)didHandleInAppTestFromPushNotificaton:(NSDictionary* _Nullable)notification; +- (BOOL)isTemplateRegistered:(NSDictionary *)inAppJSON; - (void)_addInAppNotificationsToQueue:(NSArray *)inappNotifs; - (void)_showNotificationIfAvailable; diff --git a/CleverTapSDK/InApps/CTInAppDisplayManager.m b/CleverTapSDK/InApps/CTInAppDisplayManager.m index cff8e510..ce1b5f86 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.m +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.m @@ -36,6 +36,8 @@ #import "CleverTap+PushPermission.h" #import "CleverTapJSInterfacePrivate.h" #import "CTInAppImagePrefetchManager.h" + +#import "CTCustomTemplatesManager-Internal.h" #endif #if !(TARGET_OS_TV) @@ -44,10 +46,14 @@ #endif static const void *const kNotificationQueueKey = &kNotificationQueueKey; +static const NSString *kInAppNotificationKey = @"inAppNotification"; // static here as we may have multiple instances handling inapps static CTInAppDisplayViewController *currentDisplayController; -static NSMutableArray *pendingNotificationControllers; +static CTInAppNotification *currentlyDisplayingNotification; +static NSMutableArray *pendingNotifications; + +static BOOL once = YES; // private class @interface ImageLoadingResult : NSObject @@ -77,6 +83,8 @@ @interface CTInAppDisplayManager() { @property (nonatomic, strong) CTInAppImagePrefetchManager *imagePrefetchManager; +@property (nonatomic, strong) CTCustomTemplatesManager *templatesManager; + @property (nonatomic, strong, readonly) NSString *imageInterstitialHtml; @end @@ -88,7 +96,7 @@ @implementation CTInAppDisplayManager + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - pendingNotificationControllers = [NSMutableArray new]; + pendingNotifications = [NSMutableArray new]; }); } @@ -97,7 +105,8 @@ - (instancetype _Nonnull)initWithCleverTap:(CleverTap * _Nonnull)instance inAppFCManager:(CTInAppFCManager *)inAppFCManager impressionManager:(CTImpressionManager *)impressionManager inAppStore:(CTInAppStore *)inAppStore - imagePrefetchManager:(CTInAppImagePrefetchManager *)imagePrefetchManager { + imagePrefetchManager:(CTInAppImagePrefetchManager *)imagePrefetchManager + templatesManager:(CTCustomTemplatesManager *)templatesManager { if ((self = [super init])) { self.dispatchQueueManager = dispatchQueueManager; self.instance = instance; @@ -105,10 +114,22 @@ - (instancetype _Nonnull)initWithCleverTap:(CleverTap * _Nonnull)instance self.inAppFCManager = inAppFCManager; self.inAppStore = inAppStore; self.imagePrefetchManager = imagePrefetchManager; + self.templatesManager = templatesManager; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onDisplayPendingNotification:) + name:[self.class pendingNotificationKey:self.config.accountId] + object:nil]; } return self; } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:[self.class pendingNotificationKey:self.config.accountId] + object:nil]; +} + - (void)setPushPrimerManager:(CTPushPrimerManager *)pushPrimerManagerObj { pushPrimerManager = pushPrimerManagerObj; } @@ -136,7 +157,8 @@ - (void)setInAppNotificationDelegate:(id )de - (void)_addInAppNotificationsToQueue:(NSArray *)inappNotifs { @try { - [self.inAppStore enqueueInApps:inappNotifs]; + NSArray *filteredInAppNotifs = [self filterNonRegisteredTemplates:inappNotifs]; + [self.inAppStore enqueueInApps:filteredInAppNotifs]; // Fire the first notification, if any [self.dispatchQueueManager runOnNotificationQueue:^{ @@ -147,6 +169,31 @@ - (void)_addInAppNotificationsToQueue:(NSArray *)inappNotifs { } } +- (NSArray *)filterNonRegisteredTemplates:(NSArray *)inappNotifs { + NSMutableArray *filteredInAppNotifs = [NSMutableArray new]; + for (NSDictionary *inAppJSON in inappNotifs) { + if ([self isTemplateRegistered:inAppJSON]) { + [filteredInAppNotifs addObject:inAppJSON]; + } + } + return filteredInAppNotifs; +} + +- (BOOL)isTemplateRegistered:(NSDictionary *)inAppJSON { + CTCustomTemplateInAppData *customTemplateData = [CTCustomTemplateInAppData createWithJSON:inAppJSON]; + + if (customTemplateData) { + if ([self.templatesManager isRegisteredTemplateWithName:customTemplateData.templateName]) { + return YES; + } else { + CleverTapLogDebug(self.config.logLevel, @"%@: Template with name: %@ is not registered and cannot be presented.", self, customTemplateData.templateName); + return NO; + } + } else { + return YES; + } +} + - (void)_showInAppNotificationIfAny { if ([CTUIUtils runningInsideAppExtension]){ CleverTapLogDebug(self.config.logLevel, @"%@: showInAppNotificationIfAny is a no-op in an app extension.", self); @@ -335,6 +382,15 @@ - (void)displayNotification:(CTInAppNotification*)notification { return; } + // if we are currently displaying a notification, cache this notification for later display + if (currentlyDisplayingNotification) { + if (self.config.accountId && notification) { + [pendingNotifications addObject:@[self.config.accountId, notification]]; + CleverTapLogDebug(self.config.logLevel, @"%@: InApp already displaying, queueing to pending InApps", self); + } + return; + } + BOOL isHTMLType = (notification.inAppType == CTInAppTypeHTML); BOOL isInternetAvailable = self.instance.deviceInfo.isOnline; if (isHTMLType && !isInternetAvailable) { @@ -390,11 +446,16 @@ - (void)displayNotification:(CTInAppNotification*)notification { case CTInAppTypeCoverImage: controller = [[CTCoverImageViewController alloc] initWithNotification:notification]; break; + case CTInAppTypeCustom: + currentlyDisplayingNotification = notification; + [self.templatesManager presentNotification:notification withDelegate:self]; + break; default: errorString = [NSString stringWithFormat:@"Unhandled notification type: %lu", (unsigned long)notification.inAppType]; break; } if (controller) { + currentlyDisplayingNotification = notification; CleverTapLogDebug(self.config.logLevel, @"%@: Will show new InApp: %@", self, notification.campaignId); controller.delegate = self; [[self class] displayInAppDisplayController:controller]; @@ -422,34 +483,48 @@ - (void)notifyNotificationDismissed:(CTInAppNotification *)notification { } } +#pragma mark - Pending Notification + +- (void)onDisplayPendingNotification:(NSNotification *)notification { + currentlyDisplayingNotification = nil; + CleverTapLogDebug(self.config.logLevel, @"%@: Displaying pending notification.", self); + [self displayNotification:notification.userInfo[kInAppNotificationKey]]; +} + #pragma mark - InAppDisplayController static + (void)displayInAppDisplayController:(CTInAppDisplayViewController*)controller { - // if we are currently displaying a notification, cache this notification for later display - if (currentDisplayController) { - [pendingNotificationControllers addObject:controller]; - return; - } - // no current notification so display currentDisplayController = controller; [controller show:YES]; } + (void)inAppDisplayControllerDidDismiss:(CTInAppDisplayViewController*)controller { - if (currentDisplayController && currentDisplayController == controller) { - currentDisplayController = nil; + if (controller) { + if (currentDisplayController && currentDisplayController == controller) { + currentDisplayController = nil; + [self checkPendingNotifications]; + } + } else { [self checkPendingNotifications]; } } // static display handling as we may have more than one instance competing to show an inapp + (void)checkPendingNotifications { - if (pendingNotificationControllers && [pendingNotificationControllers count] > 0) { - CTInAppDisplayViewController *controller = [pendingNotificationControllers objectAtIndex:0]; - [pendingNotificationControllers removeObjectAtIndex:0]; - [self displayInAppDisplayController:controller]; + if (pendingNotifications && [pendingNotifications count] > 0) { + NSArray *pendingNotification = [pendingNotifications objectAtIndex:0]; + [pendingNotifications removeObjectAtIndex:0]; + NSString *accountId = [pendingNotification objectAtIndex:0]; + CTInAppNotification *inAppNotification = [pendingNotification objectAtIndex:1]; + [[NSNotificationCenter defaultCenter] postNotificationName:[self pendingNotificationKey:accountId] object:nil userInfo:@{kInAppNotificationKey: inAppNotification}]; + } else { + currentlyDisplayingNotification = nil; } } ++ (NSString *)pendingNotificationKey:(NSString *)accountId { + return [NSString stringWithFormat:@"%@:%@:onDisplayPendingNotification", [self class], accountId]; +} + #pragma mark - CTInAppNotificationDisplayDelegate - (void)notificationDidDismiss:(CTInAppNotification*)notification fromViewController:(CTInAppDisplayViewController*)controller { @@ -459,7 +534,7 @@ - (void)notificationDidDismiss:(CTInAppNotification*)notification fromViewContro [self _showInAppNotificationIfAny]; } -- (void)notificationDidShow:(CTInAppNotification*)notification fromViewController:(CTInAppDisplayViewController*)controller { +- (void)notificationDidShow:(CTInAppNotification*)notification { CleverTapLogInternal(self.config.logLevel, @"%@: InApp did show: %@", self, notification.campaignId); [self.instance recordInAppNotificationStateEvent:NO forNotification:notification andQueryParameters:nil]; [self.inAppFCManager didShow:notification]; diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 175ed4e8..68890d93 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -157,6 +157,10 @@ - (NSMutableArray *)evaluate:(CTEventAdapter *)event withInApps:(NSArray *)inApp if (!campaignId) { continue; } + if (![self.inAppDisplayManager isTemplateRegistered:inApp]) { + continue; + } + // Match trigger NSArray *whenTriggers = inApp[CLTAP_INAPP_TRIGGERS]; BOOL matchesTrigger = [self.triggersMatcher matchEventWhenTriggers:whenTriggers event:event]; diff --git a/CleverTapSDK/InApps/CTInAppHTMLViewController.m b/CleverTapSDK/InApps/CTInAppHTMLViewController.m index 262b90de..ac8961a8 100644 --- a/CleverTapSDK/InApps/CTInAppHTMLViewController.m +++ b/CleverTapSDK/InApps/CTInAppHTMLViewController.m @@ -504,8 +504,8 @@ - (void)showFromWindow:(BOOL)animated { [self.window setHidden:NO]; void (^completionBlock)(void) = ^ { - if (self.delegate && [self.delegate respondsToSelector:@selector(notificationDidShow:fromViewController:)]) { - [self.delegate notificationDidShow:self.notification fromViewController:self]; + if (self.delegate) { + [self.delegate notificationDidShow:self.notification]; } }; if (animated) { diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate-Internal.h b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate-Internal.h index 7a775401..c191a157 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate-Internal.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate-Internal.h @@ -17,6 +17,7 @@ @property (nonatomic, strong, readonly) NSString *templateType; @property (nonatomic, strong, readonly) NSArray *arguments; +@property (nonatomic, strong, readonly) id presenter; - (instancetype)initWithTemplateName:(NSString *)templateName templateType:(NSString *)templateType diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager-Internal.h b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager-Internal.h new file mode 100644 index 00000000..c04f6807 --- /dev/null +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager-Internal.h @@ -0,0 +1,25 @@ +// +// CTCustomTemplatesManager-Internal.h +// CleverTapSDK +// +// Created by Nikola Zagorchev on 21.05.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#ifndef CTCustomTemplatesManager_Internal_h +#define CTCustomTemplatesManager_Internal_h + +#import "CTCustomTemplatesManager.h" +#import "CleverTapInstanceConfig.h" +#import "CTInAppNotification.h" +#import "CTInAppNotificationDisplayDelegate.h" + +@interface CTCustomTemplatesManager (Internal) + +- (instancetype)initWithConfig:(CleverTapInstanceConfig *)instanceConfig; + +- (void)presentNotification:(CTInAppNotification *)notification withDelegate:(id)delegate; + +@end + +#endif /* CTCustomTemplatesManager_Internal_h */ diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h index 9ba710f3..d9a632df 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h @@ -8,7 +8,6 @@ #import #import "CTTemplateProducer.h" -#import "CleverTapInstanceConfig.h" NS_ASSUME_NONNULL_BEGIN @@ -18,8 +17,6 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithConfig:(CleverTapInstanceConfig *)instanceConfig; - - (BOOL)isRegisteredTemplateWithName:(NSString *)name; - (NSDictionary*)syncPayload; diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m index 66c6e8cf..049e6f25 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m @@ -10,6 +10,8 @@ #import "CTCustomTemplate-Internal.h" #import "CTTemplateArgument.h" #import "CTConstants.h" +#import "CTInAppNotification.h" +#import "CTTemplateContext-Internal.h" @interface CTCustomTemplatesManager () @@ -32,6 +34,10 @@ + (void)registerTemplateProducer:(nonnull id)producer { [templateProducers addObject:producer]; } ++ (void)clearTemplateProducers { + [templateProducers removeAllObjects]; +} + - (instancetype)initWithConfig:(CleverTapInstanceConfig *)instanceConfig { self = [super init]; if (self) { @@ -57,8 +63,16 @@ - (BOOL)isRegisteredTemplateWithName:(nonnull NSString *)name { return self.templates[name]; } -+ (void)clearTemplateProducers { - [templateProducers removeAllObjects]; +- (void)presentNotification:(CTInAppNotification *)notification withDelegate:(id)delegate { + CTCustomTemplate *template = self.templates[notification.customTemplateInAppData.templateName]; + if (!template) { + CleverTapLogStaticDebug("%@: Template with name:%@ not registered.", self, notification.customTemplateInAppData.templateName); + return; + } + + CTTemplateContext *context = [[CTTemplateContext alloc] initWithTemplate:template andNotification:notification]; + context.delegate = delegate; + [template.presenter onPresent:context]; } - (NSDictionary*)syncPayload { diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext-Internal.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext-Internal.h index 1c2ca9ae..f152c297 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext-Internal.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext-Internal.h @@ -9,9 +9,16 @@ #ifndef CTTemplateContext_Internal_h #define CTTemplateContext_Internal_h -@interface CTTemplateContext () +#import "CTInAppNotification.h" +#import "CTCustomTemplate.h" +#import "CTTemplateContext.h" +#import "CTInAppNotificationDisplayDelegate.h" -- (instancetype)initWithTemplateName:(NSString *)templateName; +@interface CTTemplateContext (Internal) + +- (instancetype)initWithTemplate:(CTCustomTemplate *)customTemplate andNotification:(CTInAppNotification *)notification; + +- (void)setDelegate:(id)delegate; @end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h index 2b38423c..e7221240 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h @@ -20,24 +20,47 @@ NS_SWIFT_NAME(name()); - (nullable NSString *)stringNamed:(NSString *)name NS_SWIFT_NAME(string(name:)); -- (nullable NSString *)fileNamed:(NSString *)name -NS_SWIFT_NAME(file(name:)); - - (nullable NSNumber *)numberNamed:(NSString *)name NS_SWIFT_NAME(number(name:)); +- (int)charNamed:(NSString *)name +NS_SWIFT_NAME(char(name:)); + +- (int)intNamed:(NSString *)name +NS_SWIFT_NAME(int(name:)); + +- (double)doubleNamed:(NSString *)name +NS_SWIFT_NAME(double(name:)); + +- (float)floatNamed:(NSString *)name +NS_SWIFT_NAME(float(name:)); + +- (long)longNamed:(NSString *)name +NS_SWIFT_NAME(long(name:)); + +- (long long)longLongNamed:(NSString *)name +NS_SWIFT_NAME(longLong(name:)); + - (BOOL)boolNamed:(NSString *)name NS_SWIFT_NAME(boolean(name:)); - (nullable NSDictionary *)dictionaryNamed:(NSString *)name NS_SWIFT_NAME(dictionary(name:)); +- (nullable NSString *)fileNamed:(NSString *)name +NS_SWIFT_NAME(file(name:)); + +/** + * Call this method to notify the SDK the template is presented. + */ +- (void)presented; + /** * Executes the action given by the "name" key. * Records Notification Clicked event. */ -- (void)executeActionNamed:(NSString *)name -NS_SWIFT_NAME(executeAction(name:)); +- (void)triggerActionNamed:(NSString *)name +NS_SWIFT_NAME(triggerAction(name:)); /** * Call this method to notify the SDK the template is dismissed. diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.m b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.m index 9cf3b907..56096894 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.m +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.m @@ -8,8 +8,218 @@ #import "CTTemplateContext.h" #import "CTTemplateContext-Internal.h" +#import "CTTemplateArgument.h" +#import "CTCustomTemplate-Internal.h" +#import "CTNotificationAction.h" +#import "CTConstants.h" + +@interface CTTemplateContext () + +@property (nonatomic) CTCustomTemplate *template; +@property (nonatomic) CTInAppNotification *notification; +@property (nonatomic, strong) NSDictionary *argumentValues; +@property (nonatomic) id delegate; + +@end @implementation CTTemplateContext +@synthesize argumentValues = _argumentValues; + +- (instancetype)initWithTemplate:(CTCustomTemplate *)customTemplate andNotification:(CTInAppNotification *)notification { + if (self = [super init]) { + self.notification = notification; + self.template = customTemplate; + } + return self; +} + +- (NSString *)templateName { + return self.template.name; +} + +- (NSString *)stringNamed:(NSString *)name { + return self.argumentValues[name]; +} + +- (NSNumber *)numberNamed:(NSString *)name { + return self.argumentValues[name]; +} + +- (int)charNamed:(NSString *)name { + return [[self numberNamed:name] charValue]; +} + +- (int)intNamed:(NSString *)name { + return [[self numberNamed:name] intValue]; +} + +- (double)doubleNamed:(NSString *)name { + return [[self numberNamed:name] doubleValue]; +} + +- (float)floatNamed:(NSString *)name { + return [[self numberNamed:name] floatValue]; +} + +- (long)longNamed:(NSString *)name { + return [[self numberNamed:name] longValue]; +} + +- (long long)longLongNamed:(NSString *)name { + return [[self numberNamed:name] longLongValue]; +} + +- (BOOL)boolNamed:(NSString *)name { + return [self.argumentValues[name] boolValue]; +} + +- (NSDictionary *)dictionaryNamed:(NSString *)name { + NSString *namePrefix = [NSString stringWithFormat:@"%@.", name]; + NSArray *matchingKeys = [self.argumentValues.allKeys filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return [evaluatedObject hasPrefix:namePrefix]; + }]]; + + if ([matchingKeys count] == 0) { + return nil; + } + + NSDictionary *matchedDictionary = [self.argumentValues dictionaryWithValuesForKeys:matchingKeys]; + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + for (NSString *key in matchedDictionary) { + NSString *subKey = [key substringWithRange:NSMakeRange(namePrefix.length, key.length - namePrefix.length)]; + NSArray *keyParts = [subKey componentsSeparatedByString:@"."]; + id value = matchedDictionary[key]; + + // If value is an action (CTNotificationAction *) return the template name or action type + id keyValue; + if ([value isKindOfClass:[CTNotificationAction class]]) { + CTNotificationAction *action = value; + keyValue = action.customTemplateInAppData.templateName ?: [CTInAppUtils inAppActionTypeString:action.type] ?: @""; + } else { + keyValue = value; + } + + /* + a.b.c = 1 + a.b.d = 2 + a.b.e.f = 3 + [dictionaryNamed:a] -> keys = [b.c, b.d, b.e.f] + result = { + b { + c: 1, + d: 2, + e: { + f: 3 + } + } + } + */ + NSMutableDictionary *currentMap = result; + for (NSUInteger i = 0; i < keyParts.count; i++) { + NSString *keyPart = keyParts[i]; + if (i == keyParts.count - 1) { + currentMap[keyPart] = keyValue; + } else { + NSMutableDictionary *innerMap = currentMap[keyPart]; + if (!innerMap) { + innerMap = [NSMutableDictionary dictionary]; + currentMap[keyPart] = innerMap; + } + + currentMap = innerMap; + } + } + } + + return [result copy]; +} + +- (NSString *)fileNamed:(NSString *)name { + // TODO: add when implementing file handling + return self.argumentValues[name]; +} + +- (void)presented { + if (self.delegate) { + [self.delegate notificationDidShow:self.notification]; + } else { + CleverTapLogStaticDebug(@"%@: Cannot set template as presented.", [self class]) + } +} + +- (void)triggerActionNamed:(NSString *)name { + // TODO: add when implementing action handling + id action = self.argumentValues[name]; + if ([action isKindOfClass:[CTNotificationAction class]]) { + + } +} + +- (void)dismissed { + if (self.delegate) { + [self.delegate notificationDidDismiss:self.notification fromViewController:nil]; + self.delegate = nil; + } else { + CleverTapLogStaticDebug(@"%@: Cannot set template as dismissed.", [self class]) + } +} + +- (NSDictionary *)argumentValues { + if (_argumentValues) { + return _argumentValues; + } + _argumentValues = [self mergeArguments]; + return _argumentValues; +} + +- (NSDictionary *)mergeArguments { + NSMutableDictionary *merged = [NSMutableDictionary new]; + for (CTTemplateArgument *arg in self.template.arguments) { + merged[arg.name] = arg.defaultValue; + id override = [self valueForArgument:arg]; + if (override) { + merged[arg.name] = override; + } + } + + return [merged copy]; +} + +- (id)valueForArgument:(CTTemplateArgument *)arg { + NSDictionary *overrides = self.notification.customTemplateInAppData.args; + id override = overrides[arg.name]; + if (override) { + switch (arg.type) { + case CTTemplateArgumentTypeString: + if ([override isKindOfClass:[NSString class]]) { + return override; + } + break; + case CTTemplateArgumentTypeNumber: + case CTTemplateArgumentTypeBool: + if ([override isKindOfClass:[NSNumber class]]) { + return override; + } + break; + case CTTemplateArgumentTypeFile: + // TODO: add when implementing file handling + break; + case CTTemplateArgumentTypeAction: { + CTNotificationAction *action = [[CTNotificationAction alloc] initWithJSON:override[CLTAP_INAPP_ACTIONS]]; + if (action && !action.error) { + return action; + } else if (action.error) { + CleverTapLogStaticDebug(@"%@: Error creating action for argument: %@. Error: %@", [self class], arg.name, action.error); + } + break; + } + default: + break; + } + } + return nil; +} @end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h index d57a2d0a..c0bd66c1 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)onPresent:(CTTemplateContext *)context NS_SWIFT_NAME(onPresent(context:)); + - (void)onCloseClicked:(CTTemplateContext *)context NS_SWIFT_NAME(onCloseClicked(context:)); diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m index cea7a903..3ef9948c 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m @@ -8,6 +8,7 @@ #import #import +#import #import "CTInAppEvaluationManager.h" #import "CTEventAdapter.h" #import "BaseTestCase.h" @@ -21,6 +22,9 @@ #import "CTInAppEvaluationManager+Tests.h" #import "CTPreferences.h" #import "CTMultiDelegateManager+Tests.h" +#import "CTCustomTemplatesManager-Internal.h" +#import "CTInAppTemplateBuilder.h" +#import "CTTestTemplateProducer.h" @interface CTInAppDisplayManagerMock : CTInAppDisplayManager @property (nonatomic, strong) NSMutableArray *inappNotifs; @@ -36,7 +40,20 @@ - (instancetype)initWithNil { inAppFCManager:nil impressionManager:nil inAppStore:nil - imagePrefetchManager:nil]) { + imagePrefetchManager:nil + templatesManager:nil]) { + self.inappNotifs = [NSMutableArray new]; + } + return self; +} +- (instancetype)initWithTemplateManager:(CTCustomTemplatesManager *)templatesManager { + if (self = [super initWithCleverTap:nil + dispatchQueueManager:nil + inAppFCManager:nil + impressionManager:nil + inAppStore:nil + imagePrefetchManager:nil + templatesManager:templatesManager]) { self.inappNotifs = [NSMutableArray new]; } return self; @@ -610,6 +627,50 @@ - (void)testEvaluateOnAppLaunchedServerSide { XCTAssertEqualObjects((@[inApps[0]]), self.mockDisplayManager.inappNotifs); } +- (void)testEvaluateCustomInApps { + NSMutableSet *templates = [NSMutableSet set]; + id templatePresenter = OCMProtocolMock(@protocol(CTTemplatePresenter)); + CTInAppTemplateBuilder *templateBuilder = [CTInAppTemplateBuilder new]; + [templateBuilder setName:@"Template 1"]; + [templateBuilder setPresenter:templatePresenter]; + [templates addObject:[templateBuilder build]]; + + CTTestTemplateProducer *producer = [[CTTestTemplateProducer alloc] initWithTemplates:templates]; + + [CTCustomTemplatesManager registerTemplateProducer:producer]; + + CTCustomTemplatesManager *templatesManager = [[CTCustomTemplatesManager alloc] initWithConfig:self.helper.config]; + + // Initialize with the templatesManager to register the template + self.mockDisplayManager = [[CTInAppDisplayManagerMock alloc] initWithTemplateManager:templatesManager]; + self.evaluationManager.inAppDisplayManager = self.mockDisplayManager; + + NSArray *inApps = @[ + @{ + @"ti": @1, + @"templateName": @"Template 2", + @"type": @"custom-code", + @"priority": @(100), + @"whenTriggers": @[@{ + @"eventName": @"event1" + }] + }, + @{ + @"ti": @2, + @"templateName": @"Template 1", + @"type": @"custom-code", + @"priority": @(100), + @"whenTriggers": @[@{ + @"eventName": @"event1" + }] + } + ]; + + CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:@"event1" eventProperties:@{} andLocation:kCLLocationCoordinate2DInvalid]; + + XCTAssertEqualObjects([self.evaluationManager evaluate:event withInApps:inApps], (@[inApps[1]])); +} + #pragma mark Delegates Tests - (void)testDelegatesAdded { CTMultiDelegateManager *delegateManager = [[CTMultiDelegateManager alloc] init]; diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m index 0838ad04..890a846c 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m @@ -8,7 +8,8 @@ #import #import -#import "CTCustomTemplatesManager.h" +#import +#import "CTCustomTemplatesManager-Internal.h" #import "CTCustomTemplatesManager+Tests.h" #import "CTInAppTemplateBuilder.h" #import "CTAppFunctionBuilder.h" @@ -341,4 +342,74 @@ - (void)testDuplicateTemplateNameThrows { XCTAssertThrows([[CTCustomTemplatesManager alloc] initWithConfig:config]); } +- (void)testPresenterOnPresent { + NSMutableSet *templates = [NSMutableSet set]; + CTTemplatePresenterMock *templatePresenter = [CTTemplatePresenterMock new]; + CTInAppTemplateBuilder *templateBuilder = [CTInAppTemplateBuilder new]; + [templateBuilder setName:@"Template 1"]; + [templateBuilder setPresenter:templatePresenter]; + [templates addObject:[templateBuilder build]]; + + CTTemplatePresenterMock *functionPresenter = [CTTemplatePresenterMock new]; + CTInAppTemplateBuilder *functionBuilder = [CTInAppTemplateBuilder new]; + [functionBuilder setName:@"Function 1"]; + [functionBuilder setPresenter:functionPresenter]; + [templates addObject:[functionBuilder build]]; + + CTTestTemplateProducer *producer = [[CTTestTemplateProducer alloc] initWithTemplates:templates]; + + [CTCustomTemplatesManager registerTemplateProducer:producer]; + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; + CTCustomTemplatesManager *manager = [[CTCustomTemplatesManager alloc] initWithConfig:config]; + + CTInAppNotification *notificaton = [[CTInAppNotification alloc] initWithJSON:[self simpleTemplateNotificationJson]]; + id delegate = OCMProtocolMock(@protocol(CTInAppNotificationDisplayDelegate)); + + [manager presentNotification:notificaton withDelegate:delegate]; + XCTAssertEqual(1, templatePresenter.onPresentInvocationsCount); + XCTAssertEqual(@"Template 1", templatePresenter.onPresentContext.templateName); + + CTInAppNotification *functionNotificaton = [[CTInAppNotification alloc] initWithJSON:[self simpleFunctionNotificationJson]]; + [manager presentNotification:functionNotificaton withDelegate:delegate]; + XCTAssertEqual(1, functionPresenter.onPresentInvocationsCount); + XCTAssertEqual(@"Function 1", functionPresenter.onPresentContext.templateName); +} + +- (void)testPresenterOnPresentNonRegisteredTemplate { + NSMutableSet *templates = [NSMutableSet set]; + CTTemplatePresenterMock *templatePresenter = [CTTemplatePresenterMock new]; + CTInAppTemplateBuilder *templateBuilder = [CTInAppTemplateBuilder new]; + [templateBuilder setName:@"Template 1"]; + [templateBuilder setPresenter:templatePresenter]; + [templates addObject:[templateBuilder build]]; + + CTTestTemplateProducer *producer = [[CTTestTemplateProducer alloc] initWithTemplates:templates]; + + [CTCustomTemplatesManager registerTemplateProducer:producer]; + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; + CTCustomTemplatesManager *manager = [[CTCustomTemplatesManager alloc] initWithConfig:config]; + + CTInAppNotification *notificaton = [[CTInAppNotification alloc] initWithJSON:[self simpleFunctionNotificationJson]]; + id delegate = OCMProtocolMock(@protocol(CTInAppNotificationDisplayDelegate)); + + [manager presentNotification:notificaton withDelegate:delegate]; + XCTAssertEqual(0, templatePresenter.onPresentInvocationsCount); +} + +- (NSDictionary *)simpleTemplateNotificationJson { + return @{ + @"templateName": @"Template 1", + @"type": @"custom-code", + @"vars": @{} + }; +} + +- (NSDictionary *)simpleFunctionNotificationJson { + return @{ + @"templateName": @"Function 1", + @"type": @"custom-code", + @"vars": @{} + }; +} + @end diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m new file mode 100644 index 00000000..2fc644b4 --- /dev/null +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m @@ -0,0 +1,311 @@ +// +// CTTemplateContextTest.m +// CleverTapSDKTests +// +// Created by Nikola Zagorchev on 13.05.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import +#import "CTTemplateContext-Internal.h" +#import "CTInAppTemplateBuilder.h" +#import "CTAppFunctionBuilder.h" +#import "CTTemplatePresenterMock.h" +#import "CTTemplateContext-Internal.h" + +@interface CTTemplateContext (Tests) + +@property (nonatomic) id delegate; + +@end + +@interface CTTemplateContextTest : XCTestCase + +@end + +@implementation CTTemplateContextTest + +- (void)testDismissedShouldCallDelegateDismiss { + CTTemplateContext *context = self.templateContext; + id delegate = OCMProtocolMock(@protocol(CTInAppNotificationDisplayDelegate)); + [context setDelegate:delegate]; + [context dismissed]; + [[delegate verify] notificationDidDismiss:[OCMArg any] fromViewController:[OCMArg any]]; + + // should call delegate dismiss only once + [context dismissed]; + [[delegate reject] notificationDidDismiss:[OCMArg any] fromViewController:[OCMArg any]]; +} + +- (void)testPresentedShouldCallDelegateShow { + CTTemplateContext *context = self.templateContext; + id delegate = OCMProtocolMock(@protocol(CTInAppNotificationDisplayDelegate)); + [context setDelegate:delegate]; + [context presented]; + [[delegate verify] notificationDidShow:[OCMArg any]]; +} + +- (void)testDismissClearsDelegate { + CTTemplateContext *context = self.templateContext; + id delegate = OCMProtocolMock(@protocol(CTInAppNotificationDisplayDelegate)); + [context setDelegate:delegate]; + [context dismissed]; + [[delegate verify] notificationDidDismiss:[OCMArg any] fromViewController:[OCMArg any]]; + + XCTAssertNil([context delegate]); + [context presented]; + [[delegate reject] notificationDidShow:[OCMArg any]]; +} + +- (void)testTemplateName { + CTInAppNotification *notification = [[CTInAppNotification alloc] initWithJSON:self.templateNotificationJson]; + CTTemplateContext *context = [[CTTemplateContext alloc] initWithTemplate:self.template andNotification:notification]; + XCTAssertEqualObjects(TEMPLATE_NAME_NESTED, [context templateName]); +} + +- (void)testSimpleValueOverrides { + CTInAppNotification *notification = [[CTInAppNotification alloc] initWithJSON:self.simpleTemplateNotificationJson]; + CTTemplateContext *context = [[CTTemplateContext alloc] initWithTemplate:self.simpleTemplate andNotification:notification]; + XCTAssertEqual(VARS_OVERRIDE_BOOLEAN, [context boolNamed:@"a.b.c"]); + XCTAssertEqualObjects(VARS_OVERRIDE_STRING, [context stringNamed:@"a.b.d"]); + XCTAssertEqualObjects(@(VARS_OVERRIDE_LONG), [context numberNamed:@"a.b.e.f"]); + XCTAssertEqual(VARS_OVERRIDE_DOUBLE, [context doubleNamed:@"g"]); + XCTAssertEqual(VARS_OVERRIDE_BOOLEAN, [context boolNamed:@"h"]); + XCTAssertEqualObjects(VARS_DEFAULT_STRING, [context stringNamed:@"i"]); + + NSDictionary *a = [context dictionaryNamed:@"a"]; + XCTAssertTrue([a isEqualToDictionary:(@{ + @"b": @{ + @"c": @(VARS_OVERRIDE_BOOLEAN), + @"d": VARS_OVERRIDE_STRING, + @"e": @{ + @"f": @(VARS_OVERRIDE_LONG) + } + } + })]); + + NSDictionary *ab = [context dictionaryNamed:@"a.b"]; + XCTAssertTrue([ab isEqualToDictionary:(@{ + @"c": @(VARS_OVERRIDE_BOOLEAN), + @"d": VARS_OVERRIDE_STRING, + @"e": @{ + @"f": @(VARS_OVERRIDE_LONG) + } + })]); + + NSDictionary *abe = [context dictionaryNamed:@"a.b.e"]; + XCTAssertTrue([abe isEqualToDictionary:(@{ + @"f": @(VARS_OVERRIDE_LONG) + })]); + + NSDictionary *abg = [context dictionaryNamed:@"a.b.g"]; + XCTAssertNil(abg); +} + +- (void)testValueOverrides { + XCTAssertEqual(VARS_OVERRIDE_BOOLEAN, [self.templateContext boolNamed:@"boolean"]); + XCTAssertEqual(VARS_OVERRIDE_STRING, [self.templateContext stringNamed:@"string"]); + XCTAssertEqual(VARS_OVERRIDE_CHAR, [self.templateContext charNamed:@"char"]); + XCTAssertEqual(VARS_OVERRIDE_LONG, [self.templateContext longNamed:@"long"]); + XCTAssertEqual(VARS_OVERRIDE_DOUBLE, [self.templateContext doubleNamed:@"double"]); + XCTAssertEqualObjects(@(VARS_DEFAULT_INT), [self.templateContext numberNamed:@"noOverrideInt"]); + XCTAssertFalse([self.templateContext boolNamed:@"overrideWithoutDefinitionBoolean"]); + XCTAssertNil([self.templateContext numberNamed:@"nonDefinedNumber"]); +} + +- (void)testNotDefinedValues { + XCTAssertFalse([self.templateContext boolNamed:@"overrideWithoutDefinitionBoolean"]); + XCTAssertNil([self.templateContext stringNamed:@"notDefinedString"]); + XCTAssertNil([self.templateContext numberNamed:@"notDefinedNumber"]); + XCTAssertNil([self.templateContext dictionaryNamed:@"notDefinedMap"]); + XCTAssertEqual(0, [self.templateContext longNamed:@"notDefinedLong"]); + XCTAssertEqual(0, [self.templateContext charNamed:@"notDefinedChar"]); + XCTAssertEqual(0, [self.templateContext intNamed:@"notDefinedInt"]); + XCTAssertEqual(0, [self.templateContext doubleNamed:@"notDefinedDouble"]); + XCTAssertEqual(0, [self.templateContext floatNamed:@"notDefinedFloat"]); +} + +- (void)testDictionaryArguments { + NSDictionary *notificationVars = self.templateNotificationJson[@"vars"]; + + NSDictionary *map = [self.templateContext dictionaryNamed:@"map"]; + XCTAssertEqualObjects(notificationVars[@"map.int"], map[@"int"]); + XCTAssertEqualObjects(notificationVars[@"map.float"], map[@"float"]); + XCTAssertEqualObjects(@25, map[@"noOverrideInt"]); + + NSDictionary *innerMap = map[@"innerMap"]; + [self verifyInnerMap:notificationVars map:innerMap]; + XCTAssertTrue([innerMap isEqualToDictionary:[self.templateContext dictionaryNamed:@"map.innerMap"]]); + + NSDictionary *innermostMap = innerMap[@"innermostMap"]; + [self verifyInnermostMap:notificationVars map:innermostMap]; + XCTAssertTrue([innermostMap isEqualToDictionary:[self.templateContext dictionaryNamed:@"map.innerMap.innermostMap"]]); +} + +- (void)testActionsValueInDictionary { + NSDictionary *actionsMap = [self.templateContext dictionaryNamed:@"map.actions"]; + XCTAssertEqualObjects(VARS_ACTION_FUNCTION_NAME, actionsMap[@"function"]); + XCTAssertEqualObjects(@"close", actionsMap[@"close"]); +} + +- (void)verifyInnerMap:(NSDictionary *)vars map:(NSDictionary *)map { + XCTAssertEqualObjects(vars[@"map.innerMap.boolean"], map[@"boolean"]); + XCTAssertEqualObjects(vars[@"map.innerMap.string"], map[@"string"]); + XCTAssertEqualObjects(vars[@"map.innerMap.char"], map[@"char"]); + XCTAssertEqualObjects(vars[@"map.innerMap.int"], map[@"int"]); + XCTAssertEqualObjects(vars[@"map.innerMap.long"], map[@"long"]); + XCTAssertEqualObjects(vars[@"map.innerMap.double"], map[@"double"]); + XCTAssertEqualObjects(@15, map[@"noOverrideInt"]); +} + +- (void)verifyInnermostMap:(NSDictionary *)vars map:(NSDictionary *)map { + XCTAssertEqualObjects(vars[@"map.innerMap.innermostMap.int"], map[@"int"]); + XCTAssertEqualObjects(vars[@"map.innerMap.innermostMap.string"], map[@"string"]); + XCTAssertEqualObjects(vars[@"map.innerMap.innermostMap.boolean"], map[@"boolean"]); + XCTAssertEqualObjects(@YES, map[@"noOverrideBoolean"]); +} + +- (CTCustomTemplate *)simpleTemplate { + CTInAppTemplateBuilder *builder = [[CTInAppTemplateBuilder alloc] init]; + [builder setName:TEMPLATE_NAME]; + [builder addArgument:@"a.b.c" withBool:VARS_DEFAULT_BOOLEAN]; + [builder addArgument:@"a.b.d" withString:VARS_DEFAULT_STRING]; + [builder addArgument:@"a.b.e.f" withNumber:@(VARS_DEFAULT_LONG)]; + [builder addArgument:@"g" withNumber:@(VARS_DEFAULT_DOUBLE)]; + [builder addArgument:@"h" withBool:VARS_DEFAULT_BOOLEAN]; + [builder addArgument:@"i" withString:VARS_DEFAULT_STRING]; + [builder setPresenter:[CTTemplatePresenterMock new]]; + return [builder build]; +} + +- (NSDictionary *)simpleTemplateNotificationJson { + return @{ + @"templateName": TEMPLATE_NAME, + @"type": @"custom-code", + @"vars": @{ + @"a.b.c": @(VARS_OVERRIDE_BOOLEAN), + @"a.b.d": VARS_OVERRIDE_STRING, + @"a.b.e.f": @(VARS_OVERRIDE_LONG), + @"g": @(VARS_OVERRIDE_DOUBLE), + @"h": @YES + } + }; +} + +- (CTTemplateContext *)templateContext { + CTInAppNotification *notification = [[CTInAppNotification alloc] initWithJSON:self.templateNotificationJson]; + return [[CTTemplateContext alloc] initWithTemplate:self.template andNotification:notification]; +} + +- (CTCustomTemplate *)template { + CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; + [templateBuilder setName:TEMPLATE_NAME_NESTED]; + [templateBuilder addArgument:@"boolean" withBool:NO]; + [templateBuilder addArgument:@"char" withNumber:[NSNumber numberWithChar:VARS_DEFAULT_CHAR]]; + [templateBuilder addArgument:@"string" withString:VARS_DEFAULT_STRING]; + [templateBuilder addArgument:@"long" withNumber:[NSNumber numberWithLong:VARS_DEFAULT_LONG]]; + [templateBuilder addArgument:@"double" withNumber:@(VARS_DEFAULT_DOUBLE)]; + [templateBuilder addArgument:@"map.int" withNumber:@(VARS_DEFAULT_INT)]; + [templateBuilder addArgument:@"noOverrideInt" withNumber:@(VARS_DEFAULT_INT)]; + [templateBuilder addArgument:@"map.noOverrideInt" withNumber:@25]; + [templateBuilder addArgument:@"map" withDictionary:@{ + @"float": @15.6f, + @"innerMap": @{ + @"boolean": @NO, + @"string": @"Default", + @"noOverrideInt": @15 + } + }]; + [templateBuilder addArgument:@"map.innerMap" withDictionary:@{ + @"char": @10, + @"int": @1100, + @"long": @21474836472, + @"innermostMap": @{ + @"int": @1200, + @"string": @"Default", + @"boolean": @NO, + @"noOverrideBoolean": @YES + } + }]; + [templateBuilder addArgument:@"map.innerMap.double" withNumber:@12.12]; + [templateBuilder addActionArgument:@"map.actions.function"]; + [templateBuilder addActionArgument:@"map.actions.close"]; + [templateBuilder addActionArgument:@"map.actions.openUrl"]; + [templateBuilder setPresenter:[CTTemplatePresenterMock new]]; + return [templateBuilder build]; +} + +- (NSDictionary *)templateNotificationJson { + return @{ + @"templateName": TEMPLATE_NAME_NESTED, + @"type": @"custom-code", + @"vars": @{ + @"boolean": @(VARS_OVERRIDE_BOOLEAN), + @"string": VARS_OVERRIDE_STRING, + @"char": @(VARS_OVERRIDE_CHAR), + @"long": @(VARS_OVERRIDE_LONG), + @"double": @(VARS_OVERRIDE_DOUBLE), + @"overrideWithoutDefinitionBoolean": @YES, + @"map.actions.close": @{ + @"actions": @{ + @"type": @"close" + } + }, + @"map.actions.function": @{ + @"actions": @{ + @"templateName": VARS_ACTION_FUNCTION_NAME, + @"type": @"custom-code", + @"vars": @{ + @"boolean": @(VARS_ACTION_OVERRIDE_BOOLEAN), + @"string": VARS_ACTION_OVERRIDE_STRING, + @"int": @(VARS_ACTION_OVERRIDE_INT) + } + } + }, + @"map.actions.openUrl": @{ + @"actions": @{ + @"type": @"url", + @"ios": VARS_ACTION_OPEN_URL_ADDRESS + } + }, + @"map.int": @123, + @"map.float": @15.6f, + @"map.innerMap.boolean": @YES, + @"map.innerMap.string": @"String", + @"map.innerMap.char": @1, + @"map.innerMap.int": @1345, + @"map.innerMap.long": @21474836470, + @"map.innerMap.double": @3402823466385288.0, + @"map.innerMap.innermostMap.int": @1024, + @"map.innerMap.innermostMap.string": @"innerText", + @"map.innerMap.innermostMap.boolean": @YES + } + }; +} + +static NSString * const TEMPLATE_NAME = @"Template"; +static NSString * const TEMPLATE_NAME_NESTED = @"TemplateNestedArgs"; +static NSString * const FUNCTION_NAME_TOP_LEVEL = @"FunctionTopLevel"; + +static BOOL const VARS_OVERRIDE_BOOLEAN = YES; +static NSString * const VARS_OVERRIDE_STRING = @"Text"; +static char const VARS_OVERRIDE_CHAR = 10; +static long long const VARS_OVERRIDE_LONG = 21474836475; +static double const VARS_OVERRIDE_DOUBLE = 3402823466385285.0; + +static BOOL const VARS_DEFAULT_BOOLEAN = NO; +static NSString * const VARS_DEFAULT_STRING = @"Default"; +static char const VARS_DEFAULT_CHAR = 1; +static long long const VARS_DEFAULT_LONG = 5435050l; +static double const VARS_DEFAULT_DOUBLE = 12.345678; +static int const VARS_DEFAULT_INT = 35; + +static NSString * const VARS_ACTION_FUNCTION_NAME = @"function"; +static BOOL const VARS_ACTION_OVERRIDE_BOOLEAN = YES; +static NSString * const VARS_ACTION_OVERRIDE_STRING = @"Function text"; +static int const VARS_ACTION_OVERRIDE_INT = 5421; + +static NSString * const VARS_ACTION_OPEN_URL_ADDRESS = @"https://clevertap.com"; + +@end diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.h b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.h index 8b0acbfa..4d0c175c 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.h +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.h @@ -13,6 +13,9 @@ NS_ASSUME_NONNULL_BEGIN @interface CTTemplatePresenterMock : NSObject +@property (nonatomic) int onPresentInvocationsCount; +@property (nonatomic) CTTemplateContext *onPresentContext; + @end NS_ASSUME_NONNULL_END diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.m b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.m index e91af9dc..fc744f93 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.m +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplatePresenterMock.m @@ -14,6 +14,8 @@ - (void)onCloseClicked:(CTTemplateContext *)context { } - (void)onPresent:(CTTemplateContext *)context { + self.onPresentInvocationsCount++; + self.onPresentContext = context; } @end