Skip to content

Commit dafd372

Browse files
committed
Make IAM able to re display multiple times
* Add OSInAppMessageDisplayStats class with delay and limit params for redisplay logic * Make OSMessagingController handle the multiple re displays * Save on UserDefaults quantity of displays and last display time per IAM * Remove from UserDefaults when not available from backend * Add unit test and integration test for new functionality * Fix test that weren't taking care of all logic flow
1 parent baf0a85 commit dafd372

17 files changed

+848
-34
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
7A674F1B2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A674F1A2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m */; };
8686
7A674F1C2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A674F1A2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m */; };
8787
7A674F1D2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A674F1A2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m */; };
88+
7A72EB0E23E252C200B4D50F /* OSInAppMessageDisplayStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A72EB0D23E252C200B4D50F /* OSInAppMessageDisplayStats.m */; };
89+
7A72EB0F23E252C700B4D50F /* OSInAppMessageDisplayStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A72EB0D23E252C200B4D50F /* OSInAppMessageDisplayStats.m */; };
90+
7A72EB1023E252C700B4D50F /* OSInAppMessageDisplayStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A72EB0D23E252C200B4D50F /* OSInAppMessageDisplayStats.m */; };
91+
7A72EB1223E252DD00B4D50F /* OSInAppMessageDisplayStats.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A72EB1123E252D400B4D50F /* OSInAppMessageDisplayStats.h */; };
8892
7A9173A2231971E5007848FA /* OneSignalReceiveReceiptsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A9173A1231971E5007848FA /* OneSignalReceiveReceiptsController.m */; };
8993
7AD8DDE7234BD3BE00747A8A /* OneSignalUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8DDE6234BD3BE00747A8A /* OneSignalUserDefaults.m */; };
9094
7ADE379422E8B69C00263048 /* OneSignalOutcomeEventsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE379322E8B69C00263048 /* OneSignalOutcomeEventsController.m */; };
@@ -398,6 +402,8 @@
398402
7A12EBDC23060B37005C4FA5 /* OSIndirectNotification.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSIndirectNotification.m; sourceTree = "<group>"; };
399403
7A674F182360D813001F9ACD /* OSBaseFocusTimeProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSBaseFocusTimeProcessor.h; sourceTree = "<group>"; };
400404
7A674F1A2360D82E001F9ACD /* OSBaseFocusTimeProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSBaseFocusTimeProcessor.m; sourceTree = "<group>"; };
405+
7A72EB0D23E252C200B4D50F /* OSInAppMessageDisplayStats.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSInAppMessageDisplayStats.m; sourceTree = "<group>"; };
406+
7A72EB1123E252D400B4D50F /* OSInAppMessageDisplayStats.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSInAppMessageDisplayStats.h; sourceTree = "<group>"; };
401407
7A9173A1231971E5007848FA /* OneSignalReceiveReceiptsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalReceiveReceiptsController.m; sourceTree = "<group>"; };
402408
7A9173A3231971F8007848FA /* OneSignalReceiveReceiptsController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalReceiveReceiptsController.h; sourceTree = "<group>"; };
403409
7AD8DDE6234BD3BE00747A8A /* OneSignalUserDefaults.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalUserDefaults.m; sourceTree = "<group>"; };
@@ -936,6 +942,8 @@
936942
CAB269D821B0B6F000F8A43C /* OSInAppMessageAction.m */,
937943
CAB269DD21B2038B00F8A43C /* OSInAppMessageBridgeEvent.h */,
938944
CAB269DE21B2038B00F8A43C /* OSInAppMessageBridgeEvent.m */,
945+
7A72EB1123E252D400B4D50F /* OSInAppMessageDisplayStats.h */,
946+
7A72EB0D23E252C200B4D50F /* OSInAppMessageDisplayStats.m */,
939947
);
940948
name = Model;
941949
sourceTree = "<group>";
@@ -1030,6 +1038,7 @@
10301038
9124121D1E73342200E41FD7 /* OneSignalJailbreakDetection.h in Headers */,
10311039
7AFE856F2368DDC50091D6A5 /* OSFocusCallParams.h in Headers */,
10321040
9129C6B71E89E59B009CB6A0 /* OSPermission.h in Headers */,
1041+
7A72EB1223E252DD00B4D50F /* OSInAppMessageDisplayStats.h in Headers */,
10331042
912412151E73342200E41FD7 /* OneSignalHelper.h in Headers */,
10341043
CAAEA68A21ED68A40049CF15 /* OneSignalNotificationCategoryController.h in Headers */,
10351044
7A1232A5235E179B002B6CE3 /* OSIndirectNotification.h in Headers */,
@@ -1213,6 +1222,7 @@
12131222
91F58D831E7C80DA0017D24D /* OneSignalNotificationSettingsIOS8.m in Sources */,
12141223
CAAEA68721ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */,
12151224
9D1BD9602379E7C300A064F7 /* OSOutcomeEvent.m in Sources */,
1225+
7A72EB0E23E252C200B4D50F /* OSInAppMessageDisplayStats.m in Sources */,
12161226
9124121E1E73342200E41FD7 /* OneSignalJailbreakDetection.m in Sources */,
12171227
CA08FC791FE99B13004C445F /* OneSignalRequest.m in Sources */,
12181228
912412471E73369600E41FD7 /* OneSignalHelper.m in Sources */,
@@ -1282,6 +1292,7 @@
12821292
CAAEA68821ED68A40049CF15 /* OneSignalNotificationCategoryController.m in Sources */,
12831293
9124121F1E73342200E41FD7 /* OneSignalJailbreakDetection.m in Sources */,
12841294
CA08FC7A1FE99B13004C445F /* OneSignalRequest.m in Sources */,
1295+
7A72EB0F23E252C700B4D50F /* OSInAppMessageDisplayStats.m in Sources */,
12851296
912412481E73369700E41FD7 /* OneSignalHelper.m in Sources */,
12861297
9124122F1E73342200E41FD7 /* OneSignalSelectorHelpers.m in Sources */,
12871298
9124122B1E73342200E41FD7 /* OneSignalReachability.m in Sources */,
@@ -1414,6 +1425,7 @@
14141425
9D3300F623145AF3000F0A83 /* OneSignalViewHelper.m in Sources */,
14151426
4529DEF31FA8440A00CEAB1D /* UIAlertViewOverrider.m in Sources */,
14161427
CA8E18FF2193A1A5009DA223 /* NSTimerOverrider.m in Sources */,
1428+
7A72EB1023E252C700B4D50F /* OSInAppMessageDisplayStats.m in Sources */,
14171429
CACBAA9F218A6243000ACAA5 /* OSInAppMessageView.m in Sources */,
14181430
CA4742E7218B8FF30020DC8C /* OSTriggerController.m in Sources */,
14191431
4529DEEA1FA8360C00CEAB1D /* UIApplicationOverrider.m in Sources */,

iOS_SDK/OneSignalSDK/Source/OSInAppMessage.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#import <Foundation/Foundation.h>
2929
#import "OSInAppMessagingDefines.h"
30+
#import "OSInAppMessageDisplayStats.h"
3031
#import "OSJSONHandling.h"
3132
#import "OneSignal.h"
3233
#import "OSTrigger.h"
@@ -40,13 +41,21 @@ NS_ASSUME_NONNULL_BEGIN
4041
@property (strong, nonatomic, nonnull) NSArray<NSArray <OSTrigger *> *> *triggers;
4142

4243
@property (nonatomic) OSInAppMessageDisplayPosition position;
44+
@property (nonatomic) OSInAppMessageDisplayStats *displayStats;
4345
@property (nonatomic) BOOL actionTaken;
4446
@property (nonatomic) BOOL isPreview;
47+
@property (nonatomic) BOOL isTriggerChanged;
4548
@property (nonatomic) NSNumber *height;
4649

4750
- (BOOL)isBanner;
4851
- (BOOL)takeActionAsUnique;
4952

53+
- (NSSet<NSString *> *)getClickedClickIds;
54+
- (BOOL)isClickAvailable:(NSString *)clickId;
55+
56+
- (void)clearClickIds;
57+
- (void)addClickId:(NSString *)clickId;
58+
5059
@end
5160

5261
NS_ASSUME_NONNULL_END

iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,23 @@
2929
#import "OneSignalHelper.h"
3030
#import "OneSignalCommonDefines.h"
3131

32+
@interface OSInAppMessage ()
33+
34+
@property (strong, nonatomic, nonnull) NSMutableSet <NSString *> *clickedClickIds;
35+
36+
@end
37+
3238
@implementation OSInAppMessage
3339

40+
- (instancetype)init {
41+
if (self = [super init]) {
42+
self.clickedClickIds = [[NSMutableSet alloc] init];
43+
self.isTriggerChanged = false;
44+
}
45+
46+
return self;
47+
}
48+
3449
- (BOOL)isBanner {
3550
return self.position == OSInAppMessageDisplayPositionTop || self.position == OSInAppMessageDisplayPositionBottom;
3651
}
@@ -41,6 +56,22 @@ - (BOOL)takeActionAsUnique {
4156
return self.actionTaken = true;
4257
}
4358

59+
- (BOOL)isClickAvailable:(NSString *)clickId {
60+
return ![_clickedClickIds containsObject:clickId];
61+
}
62+
63+
- (void)clearClickIds {
64+
_clickedClickIds = [[NSMutableSet alloc] init];
65+
}
66+
67+
- (void)addClickId:(NSString *)clickId {
68+
[_clickedClickIds addObject:clickId];
69+
}
70+
71+
- (NSSet<NSString *> *)getClickedClickIds {
72+
return _clickedClickIds;
73+
}
74+
4475
+ (instancetype)instanceWithData:(NSData *)data {
4576
NSError *error;
4677
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
@@ -66,6 +97,11 @@ + (instancetype)instanceWithJson:(NSDictionary * _Nonnull)json {
6697
else
6798
return nil;
6899

100+
if (json[@"redisplay"] && [json[@"redisplay"] isKindOfClass:[NSDictionary class]])
101+
message.displayStats = [OSInAppMessageDisplayStats instanceWithJson:json[@"redisplay"]];
102+
else
103+
message.displayStats = [[OSInAppMessageDisplayStats alloc] init];
104+
69105
if (json[@"triggers"] && [json[@"triggers"] isKindOfClass:[NSArray class]]) {
70106
let triggers = [NSMutableArray new];
71107

@@ -120,8 +156,34 @@ -(NSDictionary *)jsonRepresentation {
120156

121157
json[@"triggers"] = triggers;
122158

159+
if ([_displayStats isRedisplayEnabled]) {
160+
json[@"redisplay"] = [_displayStats jsonRepresentation];
161+
}
162+
123163
return json;
124164
}
125165

166+
- (NSString *)description {
167+
return [NSString stringWithFormat:@"OSInAppMessage: \nmessageId: %@ \ntriggers: %@ \ndisplayStats: %@", self.messageId, self.triggers, self.displayStats];
168+
}
169+
170+
- (BOOL)isEqual:(id)object {
171+
if (self == object) {
172+
return YES;
173+
}
174+
175+
if (![object isKindOfClass:[OSInAppMessage class]]) {
176+
return NO;
177+
}
178+
179+
OSInAppMessage *iam = object;
180+
181+
return [self.messageId isEqualToString:iam.messageId];
182+
}
183+
184+
- (NSUInteger)hash {
185+
return [self.messageId hash];
186+
}
187+
126188
@end
127189

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Modified MIT License
3+
*
4+
* Copyright 2020 OneSignal
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* 1. The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* 2. All copies of substantial portions of the Software may only be used in connection
17+
* with services provided by OneSignal.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
#import "OneSignal.h"
29+
#import "OSJSONHandling.h"
30+
31+
@interface OSInAppMessageDisplayStats : NSObject <OSJSONDecodable, OSJSONEncodable>
32+
33+
//Last IAM display time in seconds
34+
@property (nonatomic, readwrite) double lastDisplayTime;
35+
//Delay between displays in seconds
36+
@property (nonatomic, readwrite) double displayDelay;
37+
//Current quantity of displays
38+
@property (nonatomic, readwrite) NSInteger displayQuantity;
39+
//Quantity of displays limit
40+
@property (nonatomic, readwrite) NSInteger displayLimit;
41+
42+
@property (nonatomic) BOOL isRedisplayEnabled;
43+
44+
- (BOOL)isDelayTimeSatisfied:(NSTimeInterval)date;
45+
- (BOOL)shouldDisplayAgain;
46+
47+
- (void)incrementDisplayQuantity;
48+
49+
@end
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Modified MIT License
3+
*
4+
* Copyright 2020 OneSignal
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* 1. The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* 2. All copies of substantial portions of the Software may only be used in connection
17+
* with services provided by OneSignal.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
#import "OSInAppMessageDisplayStats.h"
29+
#import "OneSignalHelper.h"
30+
31+
@interface OSInAppMessageDisplayStats ()
32+
@property (nonatomic, readwrite) BOOL redisplayEnabled;
33+
@end
34+
35+
@implementation OSInAppMessageDisplayStats
36+
37+
- (instancetype)init
38+
{
39+
self = [super init];
40+
if (self) {
41+
self.displayLimit = NSIntegerMax;
42+
self.displayDelay = 0;
43+
self.displayQuantity = 0;
44+
self.lastDisplayTime = -1;
45+
self.redisplayEnabled = false;
46+
}
47+
return self;
48+
}
49+
50+
+ (instancetype)instanceWithDisplayQuantity:(NSInteger)displayQuantity lastDisplayTime:(NSTimeInterval)lastDisplayTime {
51+
let displayStats = [OSInAppMessageDisplayStats new];
52+
53+
displayStats.displayQuantity = displayQuantity;
54+
displayStats.lastDisplayTime = lastDisplayTime;
55+
56+
return displayStats;
57+
}
58+
59+
+ (instancetype _Nullable)instanceWithData:(NSData * _Nonnull)data {
60+
NSError *error;
61+
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
62+
63+
if (error || !json) {
64+
[OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"Unable to decode in-app display state message JSON: %@", error.description ?: @"No Data"]];
65+
return nil;
66+
}
67+
68+
return [self instanceWithJson:json];
69+
}
70+
71+
+ (instancetype)instanceWithJson:(NSDictionary * _Nonnull)json {
72+
let displayStats = [OSInAppMessageDisplayStats new];
73+
74+
if (json[@"limit"] && [json[@"limit"] isKindOfClass:[NSNumber class]])
75+
displayStats.displayLimit = [json[@"limit"] integerValue];
76+
else
77+
displayStats.displayLimit = NSIntegerMax;
78+
79+
if (json[@"delay"] && [json[@"delay"] isKindOfClass:[NSNumber class]])
80+
displayStats.displayDelay = [json[@"delay"] doubleValue];
81+
else
82+
displayStats.displayDelay = [@0 doubleValue];
83+
84+
displayStats.displayQuantity = 0;
85+
displayStats.lastDisplayTime = [@-1 doubleValue];
86+
displayStats.redisplayEnabled = YES;
87+
88+
return displayStats;
89+
}
90+
91+
- (BOOL)isDelayTimeSatisfied:(NSTimeInterval)date {
92+
if (_lastDisplayTime < 0) {
93+
_lastDisplayTime = date;
94+
return true;
95+
}
96+
//Calculate gap between display times
97+
let diffInSeconds = date - _lastDisplayTime;
98+
return diffInSeconds >= _displayDelay;
99+
}
100+
101+
- (BOOL)shouldDisplayAgain {
102+
return _displayQuantity < _displayLimit;
103+
}
104+
105+
- (void)incrementDisplayQuantity {
106+
_displayQuantity++;
107+
}
108+
109+
- (BOOL)isRedisplayEnabled {
110+
return _redisplayEnabled;
111+
}
112+
113+
- (NSString *)description {
114+
return [NSString stringWithFormat:@"OSInAppMessageDisplayStats: redisplayEnabled: %@ \nlastDisplayTime: %f \ndisplayDelay: %f \ndisplayQuantity: %ld \ndisplayLimit: %ld", self.redisplayEnabled ? @"YES" : @"NO", self.lastDisplayTime, self.displayDelay, (long)self.displayQuantity, (long)self.displayLimit];
115+
}
116+
117+
- (NSDictionary * _Nonnull)jsonRepresentation {
118+
let json = [NSMutableDictionary new];
119+
120+
json[@"limit"] = @(self.displayLimit);
121+
json[@"delay"] = @(self.displayDelay);
122+
123+
return json;
124+
}
125+
126+
@end

iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ typedef NS_ENUM(NSUInteger, OSTriggerOperatorType) {
7373
#define OS_IAM_SEEN_SET_KEY @"OS_IAM_SEEN_SET"
7474
#define OS_IAM_CLICKED_SET_KEY @"OS_IAM_CLICKED_SET"
7575
#define OS_IAM_IMPRESSIONED_SET_KEY @"OS_IAM_IMPRESSIONED_SET"
76+
#define OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY @"OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY"
7677

7778
// Dynamic trigger kind types
7879
#define OS_DYNAMIC_TRIGGER_KIND_CUSTOM @"custom"

0 commit comments

Comments
 (0)