Skip to content

Commit

Permalink
[MC-1470] Custom templates actions (Custom Templates Part 5) (#336)
Browse files Browse the repository at this point in the history
* Implement actions

* Add unit tests

* Do not trigger action for functions

* Remove the string type prop from InAppNotification, use the enum

* Check if presented

* Move isAction to internal header
  • Loading branch information
nzagorchev authored Jun 17, 2024
1 parent 29c2ba0 commit 3b20bf9
Show file tree
Hide file tree
Showing 38 changed files with 698 additions and 109 deletions.
10 changes: 10 additions & 0 deletions CleverTapSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@
6B535FB72AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */; };
6B535FB82AD56C60002A2663 /* CTMultiDelegateManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B535FB52AD56C60002A2663 /* CTMultiDelegateManager.m */; };
6B535FB92AD56C60002A2663 /* CTMultiDelegateManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B535FB52AD56C60002A2663 /* CTMultiDelegateManager.m */; };
6B9157B92C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B9157B82C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.m */; };
6B9157BB2C11D07200B1C907 /* CTCustomTemplateInAppData-Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B9157BA2C11D07200B1C907 /* CTCustomTemplateInAppData-Internal.h */; };
6B9DEE9F2B4D8A500097EF40 /* clevertap-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 6B9DEE9E2B4D8A500097EF40 /* clevertap-logo.png */; };
6BA3B2DB2B03E926004E834B /* CTQueueType.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BA3B2DA2B03E926004E834B /* CTQueueType.h */; };
6BA3B2DC2B03E926004E834B /* CTQueueType.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BA3B2DA2B03E926004E834B /* CTQueueType.h */; };
Expand Down Expand Up @@ -876,6 +878,9 @@
6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppTriggerManagerTest.m; sourceTree = "<group>"; };
6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTMultiDelegateManager.h; sourceTree = "<group>"; };
6B535FB52AD56C60002A2663 /* CTMultiDelegateManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTMultiDelegateManager.m; sourceTree = "<group>"; };
6B9157B72C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTInAppNotificationDisplayDelegateMock.h; sourceTree = "<group>"; };
6B9157B82C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppNotificationDisplayDelegateMock.m; sourceTree = "<group>"; };
6B9157BA2C11D07200B1C907 /* CTCustomTemplateInAppData-Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTCustomTemplateInAppData-Internal.h"; sourceTree = "<group>"; };
6B9DEE9E2B4D8A500097EF40 /* clevertap-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "clevertap-logo.png"; sourceTree = "<group>"; };
6B9DEEA02B4DF1B70097EF40 /* CTInAppImagePrefetchManager+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTInAppImagePrefetchManager+Tests.h"; sourceTree = "<group>"; };
6BA3B2DA2B03E926004E834B /* CTQueueType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTQueueType.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1455,6 +1460,8 @@
6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */,
6BB778CD2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m */,
6BB778D12BF267B600A41628 /* CTTemplateContextTest.m */,
6B9157B72C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.h */,
6B9157B82C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.m */,
);
path = CustomTemplates;
sourceTree = "<group>";
Expand Down Expand Up @@ -1484,6 +1491,7 @@
6BB778C52BECEC2700A41628 /* CTCustomTemplateInAppData.h */,
6BB778C62BECEC2700A41628 /* CTCustomTemplateInAppData.m */,
6BB778D82BFD277400A41628 /* CTCustomTemplatesManager-Internal.h */,
6B9157BA2C11D07200B1C907 /* CTCustomTemplateInAppData-Internal.h */,
);
path = CustomTemplates;
sourceTree = "<group>";
Expand Down Expand Up @@ -2009,6 +2017,7 @@
6BF5A5912ACC854800CDED20 /* CTInAppDisplayManager.h in Headers */,
6B32A0A12B99033F009ADC57 /* CTCustomTemplateBuilder-Internal.h in Headers */,
07B94547219EA34300D4C542 /* CTMessageMO.h in Headers */,
6B9157BB2C11D07200B1C907 /* CTCustomTemplateInAppData-Internal.h in Headers */,
4E25E3C2278887A70008C888 /* CTIdentityRepoFactory.h in Headers */,
071EB4FF217F6427008F0FAB /* CTHeaderViewController.h in Headers */,
071EB4D6217F6427008F0FAB /* CTInAppFCManager.h in Headers */,
Expand Down Expand Up @@ -2434,6 +2443,7 @@
6BEEC2CE2AEC49F100BD4EC5 /* CTImpressionManagerTest.m in Sources */,
6A2E4C18291E8A4A00385536 /* CleverTapInstanceConfigTests.m in Sources */,
4E2BFB9C2AD69BCA00DEB247 /* XCTestCase+XCTestCase_Tests.m in Sources */,
6B9157B92C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.m in Sources */,
6A59D20F2A3351A800531F9D /* LeanplumCTTest.m in Sources */,
32790957299CC099001FE140 /* CTUtilsTest.m in Sources */,
6A2E4C18291E8A4A00385536 /* CleverTapInstanceConfigTests.m in Sources */,
Expand Down
14 changes: 4 additions & 10 deletions CleverTapSDK/CTInAppDisplayViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,8 @@ - (void)buttonTapped:(UIButton*)button {

- (void)handleButtonClickFromIndex:(int)index {
CTNotificationButton *button = self.notification.buttons[index];
NSURL *buttonCTA = button.actionURL;
NSString *buttonText = button.text;
NSString *campaignId = self.notification.campaignId;
NSDictionary *buttonCustomExtras = button.customExtras;

if (campaignId == nil) {
campaignId = @"";
}
Expand Down Expand Up @@ -262,24 +259,21 @@ - (void)handleButtonClickFromIndex:(int)index {
return;
}

if (self.delegate && [self.delegate respondsToSelector:@selector(handleNotificationCTA:buttonCustomExtras:forNotification:fromViewController:withExtras:)]) {
[self.delegate handleNotificationCTA:buttonCTA buttonCustomExtras:buttonCustomExtras forNotification:self.notification fromViewController:self withExtras:@{CLTAP_NOTIFICATION_ID_TAG:campaignId, @"wzrk_c2a": buttonText}];
if (self.delegate && [self.delegate respondsToSelector:@selector(handleNotificationAction:forNotification:withExtras:)]) {
[self.delegate handleNotificationAction:button.action forNotification:self.notification withExtras:@{CLTAP_NOTIFICATION_ID_TAG:campaignId, @"wzrk_c2a": buttonText}];
}
}

- (void)handleImageTapGesture {
CTNotificationButton *button = self.notification.buttons[0];
NSURL *buttonCTA = button.actionURL;
NSString *buttonText = @"";
NSString *campaignId = self.notification.campaignId;
NSDictionary *buttonCustomExtras = button.customExtras;

if (campaignId == nil) {
campaignId = @"";
}

if (self.delegate && [self.delegate respondsToSelector:@selector(handleNotificationCTA:buttonCustomExtras:forNotification:fromViewController:withExtras:)]) {
[self.delegate handleNotificationCTA:buttonCTA buttonCustomExtras:buttonCustomExtras forNotification:self.notification fromViewController:self withExtras:@{CLTAP_NOTIFICATION_ID_TAG:campaignId, @"wzrk_c2a": buttonText}];
if (self.delegate && [self.delegate respondsToSelector:@selector(handleNotificationAction:forNotification:withExtras:)]) {
[self.delegate handleNotificationAction:button.action forNotification:self.notification withExtras:@{CLTAP_NOTIFICATION_ID_TAG:campaignId, @"wzrk_c2a": buttonText}];
}
}

Expand Down
1 change: 0 additions & 1 deletion CleverTapSDK/CTInAppNotification.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

@property (nonatomic, readonly) NSString *Id;
@property (nonatomic, readonly) NSString *campaignId;
@property (nonatomic, copy, readonly) NSString *type;
@property (nonatomic, readonly) CTInAppType inAppType;

@property (nonatomic, copy, readonly) NSString *html;
Expand Down
8 changes: 2 additions & 6 deletions CleverTapSDK/CTInAppNotification.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ @interface CTInAppNotification() {

@property (nonatomic, readwrite) NSString *Id;
@property (nonatomic, readwrite) NSString *campaignId;
@property (nonatomic, readwrite) NSString *type;
@property (nonatomic, readwrite) CTInAppType inAppType;

@property (nonatomic, strong, readwrite) NSURL *imageURL;
Expand Down Expand Up @@ -107,7 +106,7 @@ - (instancetype)initWithJSON:(NSDictionary *)jsonObject {
if (self.inAppType == CTInAppTypeUnknown) {
self.error = @"Unknown InApp Type";
}

NSUInteger timeToLive = [jsonObject[CLTAP_INAPP_TTL] longValue];
if (timeToLive) {
_timeToLive = timeToLive;
Expand All @@ -126,10 +125,7 @@ - (instancetype)initWithJSON:(NSDictionary *)jsonObject {
}

- (void)configureFromJSON: (NSDictionary *)jsonObject {
self.type = (NSString*) jsonObject[@"type"];
if (self.type) {
self.inAppType = [CTInAppUtils inAppTypeFromString:self.type];
}
self.inAppType = [CTInAppUtils inAppTypeFromString:jsonObject[@"type"]];
self.backgroundColor = jsonObject[@"bg"];
self.title = (NSString*) jsonObject[@"title"][@"text"];
self.titleColor = (NSString*) jsonObject[@"title"][@"color"];
Expand Down
4 changes: 3 additions & 1 deletion CleverTapSDK/CTInAppNotificationDisplayDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
#define CTInAppNotificationDisplayDelegate_h

@class CTInAppDisplayViewController;
@class CTInAppNotification;
@class CTNotificationAction;

@protocol CTInAppNotificationDisplayDelegate <NSObject>

- (void)notificationDidShow:(CTInAppNotification *)notification;

- (void)handleNotificationCTA:(NSURL *)ctaURL buttonCustomExtras:(NSDictionary *)buttonCustomExtras forNotification:(CTInAppNotification *)notification fromViewController:(CTInAppDisplayViewController *)controller withExtras:(NSDictionary *)extras;
- (void)handleNotificationAction:(CTNotificationAction *)action forNotification:(CTInAppNotification *)notification withExtras:(NSDictionary *)extras;

- (void)notificationDidDismiss:(CTInAppNotification *)notification fromViewController:(CTInAppDisplayViewController *)controller;

Expand Down
1 change: 1 addition & 0 deletions CleverTapSDK/CTInAppUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ typedef NS_ENUM(NSUInteger, CTInAppActionType){
@interface CTInAppUtils : NSObject

+ (CTInAppType)inAppTypeFromString:(NSString *_Nonnull)type;
+ (NSString * _Nonnull)inAppTypeString:(CTInAppType)type;
+ (CTInAppActionType)inAppActionTypeFromString:(NSString *_Nonnull)type;
+ (NSString * _Nonnull)inAppActionTypeString:(CTInAppActionType)type;
+ (NSBundle *_Nullable)bundle;
Expand Down
26 changes: 23 additions & 3 deletions CleverTapSDK/CTInAppUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
#endif

static NSDictionary<NSString *, NSNumber *> *_inAppTypeMap;
static NSDictionary<NSNumber *, NSString *> *_inAppTypeToStringMap;
static NSDictionary<NSString *, NSNumber *> *_inAppActionTypeStringToTypeMap;
static NSDictionary<NSNumber *, NSString *> *_inAppActionTypeTypeToStringMap;

@implementation CTInAppUtils

+ (CTInAppType)inAppTypeFromString:(NSString*)type {
+ (NSDictionary<NSString *, NSNumber *> *)inAppTypeStringToTypeMap {
if (_inAppTypeMap == nil) {
_inAppTypeMap = @{
CLTAP_INAPP_HTML_TYPE: @(CTInAppTypeHTML),
Expand All @@ -28,14 +29,33 @@ + (CTInAppType)inAppTypeFromString:(NSString*)type {
@"custom-code": @(CTInAppTypeCustom)
};
}

NSNumber *_type = type != nil ? _inAppTypeMap[type] : @(CTInAppTypeUnknown);
return _inAppTypeMap;
}

+ (NSDictionary<NSNumber *, NSString *> *)inAppTypeTypeToStringMap {
if (_inAppTypeToStringMap == nil) {
NSDictionary *dict = [self inAppTypeStringToTypeMap];
NSMutableDictionary *swapped = [NSMutableDictionary new];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
swapped[value] = key;
}];
_inAppTypeToStringMap = [swapped copy];
}
return _inAppTypeToStringMap;
}

+ (CTInAppType)inAppTypeFromString:(NSString*)type {
NSNumber *_type = type != nil ? [self inAppTypeStringToTypeMap][type] : @(CTInAppTypeUnknown);
if (_type == nil) {
_type = @(CTInAppTypeUnknown);
}
return [_type integerValue];
}

+ (NSString * _Nonnull)inAppTypeString:(CTInAppType)type {
return self.inAppTypeTypeToStringMap[@(type)];
}

+ (NSDictionary<NSNumber *, NSString *> *)inAppActionTypeTypeToStringMap {
if (_inAppActionTypeTypeToStringMap == nil) {
NSDictionary *dict = [self inAppActionTypeStringToTypeMap];
Expand Down
3 changes: 2 additions & 1 deletion CleverTapSDK/CTNotificationAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ NS_ASSUME_NONNULL_BEGIN

- (instancetype)init NS_UNAVAILABLE;
#if !CLEVERTAP_NO_INAPP_SUPPORT
- (instancetype)initWithJSON:(NSDictionary*)json;
- (instancetype)initWithJSON:(NSDictionary *)json;
- (instancetype)initWithOpenURL:(NSURL *)url;
#endif

@end
Expand Down
8 changes: 8 additions & 0 deletions CleverTapSDK/CTNotificationAction.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,12 @@ - (nonnull instancetype)initWithJSON:(nonnull NSDictionary *)json {
return self;
}

- (nonnull instancetype)initWithOpenURL:(nonnull NSURL *)url {
if (self = [super init]) {
self.type = CTInAppActionTypeOpenURL;
self.actionURL = url;
}
return self;
}

@end
100 changes: 89 additions & 11 deletions CleverTapSDK/InApps/CTInAppDisplayManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
#import "CleverTap+PushPermission.h"
#import "CleverTapJSInterfacePrivate.h"
#import "CTInAppImagePrefetchManager.h"

#import "CTCustomTemplatesManager-Internal.h"
#import "CTCustomTemplateInAppData-Internal.h"
#endif

#if !(TARGET_OS_TV)
Expand Down Expand Up @@ -169,6 +169,21 @@ - (void)_addInAppNotificationsToQueue:(NSArray *)inappNotifs {
}
}

- (void)_addInAppNotificationInFrontOfQueue:(CTInAppNotification *)inappNotif {
@try {
NSString *templateName = inappNotif.customTemplateInAppData.templateName;
if ([self.templatesManager isRegisteredTemplateWithName:templateName]) {
[self.inAppStore insertInFrontInApp:inappNotif.jsonDescription];
// Fire the first notification, if any
[self.dispatchQueueManager runOnNotificationQueue:^{
[self _showNotificationIfAvailable];
}];
}
} @catch (NSException *e) {
CleverTapLogInternal(self.config.logLevel, @"%@: InApp notification handling error: %@", self, e.debugDescription);
}
}

- (NSArray *)filterNonRegisteredTemplates:(NSArray *)inappNotifs {
NSMutableArray *filteredInAppNotifs = [NSMutableArray new];
for (NSDictionary *inAppJSON in inappNotifs) {
Expand Down Expand Up @@ -447,8 +462,12 @@ - (void)displayNotification:(CTInAppNotification*)notification {
controller = [[CTCoverImageViewController alloc] initWithNotification:notification];
break;
case CTInAppTypeCustom:
currentlyDisplayingNotification = notification;
[self.templatesManager presentNotification:notification withDelegate:self];
if ([self.templatesManager presentNotification:notification withDelegate:self]) {
currentlyDisplayingNotification = notification;
} else {
errorString = [NSString stringWithFormat:@"Cannot present custom notification with template name: %@.",
notification.customTemplateInAppData.templateName];
}
break;
default:
errorString = [NSString stringWithFormat:@"Unhandled notification type: %lu", (unsigned long)notification.inAppType];
Expand Down Expand Up @@ -546,18 +565,45 @@ - (void)notifyNotificationButtonTappedWithCustomExtras:(NSDictionary *)customExt
}
}

- (void)handleNotificationCTA:(NSURL *)ctaURL buttonCustomExtras:(NSDictionary *)buttonCustomExtras forNotification:(CTInAppNotification*)notification fromViewController:(CTInAppDisplayViewController*)controller withExtras:(NSDictionary*)extras {
CleverTapLogInternal(self.config.logLevel, @"%@: handle InApp cta: %@ button custom extras: %@ with options:%@", self, ctaURL.absoluteString, buttonCustomExtras, extras);
- (void)handleNotificationAction:(CTNotificationAction *)action forNotification:(CTInAppNotification *)notification withExtras:(NSDictionary *)extras {
CleverTapLogInternal(self.config.logLevel, @"%@: handle InApp action type:%@ with cta: %@ button custom extras: %@ with options:%@", self, [CTInAppUtils inAppActionTypeString:action.type], action.actionURL.absoluteString, action.keyValues, extras);
// record the notification clicked event
[self.instance recordInAppNotificationStateEvent:YES forNotification:notification andQueryParameters:extras];

// add the action extras so they can be passed to the dismissedWithExtras delegate
if (extras) {
notification.actionExtras = extras;
}
if (buttonCustomExtras && buttonCustomExtras.count > 0) {
CleverTapLogDebug(self.config.logLevel, @"%@: InApp: button tapped with custom extras: %@", self, buttonCustomExtras);
[self notifyNotificationButtonTappedWithCustomExtras:buttonCustomExtras];

switch (action.type) {
case CTInAppActionTypeUnknown:
CleverTapLogDebug(self.config.logLevel, @"%@: Triggered in-app action with unknown type.", self);
break;
case CTInAppActionTypeClose:
// SDK in-apps are dismissed in CTInAppDisplayViewController buttonTapped: or tappedDismiss
if (notification.inAppType == CTInAppTypeCustom) {
[self.templatesManager closeNotification:notification];
}
break;
case CTInAppActionTypeOpenURL:
[self handleCTAOpenURL:action.actionURL];
break;
case CTInAppActionTypeKeyValues:
if (action.keyValues && action.keyValues.count > 0) {
CleverTapLogDebug(self.config.logLevel, @"%@: InApp: button tapped with custom extras: %@", self, action.keyValues);
[self notifyNotificationButtonTappedWithCustomExtras:action.keyValues];
}
break;
case CTInAppActionTypeCustom:
[self triggerCustomTemplateAction:action.customTemplateInAppData forNotification:notification];
break;
case CTInAppActionTypeRequestForPermission:
// Handled in CTInAppDisplayViewController handleButtonClickFromIndex:
break;
}
else if (ctaURL) {

}

- (void)handleCTAOpenURL:(NSURL *)ctaURL {
#if !CLEVERTAP_NO_INAPP_SUPPORT
if (self.instance.urlDelegate) {
// URL DELEGATE FOUND. OPEN DEEP LINKS ONLY IF USER ALLOWS IT
Expand All @@ -574,8 +620,40 @@ - (void)handleNotificationCTA:(NSURL *)ctaURL buttonCustomExtras:(NSDictionary *
}];
}
#endif
}

- (void)triggerCustomTemplateAction:(CTCustomTemplateInAppData *)actionData forNotification:(CTInAppNotification *)notification {
if (actionData && actionData.templateName) {
if ([self.templatesManager isRegisteredTemplateWithName:actionData.templateName]) {
CTCustomTemplateInAppData *inAppData = [actionData copy];
[inAppData setIsAction:YES];
CTInAppNotification *notificationFromAction = [self createNotificationForAction:inAppData andParentNotification:notification];

if ([self.templatesManager isVisualTemplateWithName:inAppData.templateName]) {
[self _addInAppNotificationInFrontOfQueue:notificationFromAction];
} else {
[self.templatesManager presentNotification:notificationFromAction withDelegate:self];
}
} else {
CleverTapLogDebug(self.config.logLevel, @"%@: Cannot trigger non-registered template with name: %@", [self class], actionData.templateName);
}
} else {
CleverTapLogDebug(self.config.logLevel, @"%@: Cannot trigger action without template name.", [self class]);
}
[controller hide:true];
}

- (CTInAppNotification *)createNotificationForAction:(CTCustomTemplateInAppData *)inAppData andParentNotification:(CTInAppNotification *)notification {
NSMutableDictionary *json = [@{
CLTAP_INAPP_ID: notification.Id ? notification.Id : @"",
CLTAP_INAPP_EXCLUDE_GLOBAL_CAPS: @(YES),
CLTAP_INAPP_TYPE: [CTInAppUtils inAppTypeString:CTInAppTypeCustom],
CLTAP_PROP_WZRK_ID: notification.campaignId ? notification.campaignId : @"",
CLTAP_INAPP_TTL: @(notification.timeToLive)
} mutableCopy];

[json addEntriesFromDictionary:inAppData.json];

return [[CTInAppNotification alloc] initWithJSON:json];
}

- (void)handleInAppPushPrimer:(CTInAppNotification *)notification
Expand Down
Loading

0 comments on commit 3b20bf9

Please sign in to comment.