diff --git a/CHANGELOG.md b/CHANGELOG.md index e56ddc04..cd3c3336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +### [Version 5.2.0](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.2.0) (August 16, 2023) + +#### Added +- Adds support for encryption of PII data wiz. Email, Identity, Name and Phone. + Please refer to [Encryption.md](/docs/Encryption.md) file to read more on how to + enable/disable encryption. +- Adds support for custom KV pairs common to all inbox messages in AppInbox. +- Adds sample SwiftUIStarter app to support CleverTap iOS SDK for SwiftUI, added steps to track screen views in SwiftUI. Refer to [SwiftUI doc](/docs/SwiftUI.md) for more details. + ### [Version 5.1.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.1.2) (July 28, 2023) #### Fixed diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 9d321a22..0f8cc4ff 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -162,6 +162,8 @@ 32394C2729FA278C00956058 /* CTUriHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C2629FA278C00956058 /* CTUriHelperTest.m */; }; 32790957299CC099001FE140 /* CTUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790956299CC099001FE140 /* CTUtilsTest.m */; }; 32790959299F4B29001FE140 /* CTDeviceInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */; }; + 4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */ = {isa = PBXBuildFile; fileRef = 480395192A7ABAD200C4D254 /* CTAES.m */; }; + 4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */ = {isa = PBXBuildFile; fileRef = 4803951A2A7ABAD200C4D254 /* CTAES.h */; }; 4808030E292EB4FB00C06E2F /* CleverTap+PushPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */; }; 48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030F292EB50D00C06E2F /* CTLocalInApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48080312292EB50D00C06E2F /* CTLocalInApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 48080310292EB50D00C06E2F /* CTLocalInApp.m */; }; @@ -613,6 +615,8 @@ 32790956299CC099001FE140 /* CTUtilsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTUtilsTest.m; sourceTree = ""; }; 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTDeviceInfoTest.m; sourceTree = ""; }; 37D3A7E5589257CFA37243C3 /* libPods-shared-CleverTapSDKUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-shared-CleverTapSDKUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 480395192A7ABAD200C4D254 /* CTAES.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTAES.m; sourceTree = ""; }; + 4803951A2A7ABAD200C4D254 /* CTAES.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTAES.h; sourceTree = ""; }; 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CleverTap+PushPermission.h"; sourceTree = ""; }; 4808030F292EB50D00C06E2F /* CTLocalInApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTLocalInApp.h; sourceTree = ""; }; 48080310292EB50D00C06E2F /* CTLocalInApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTLocalInApp.m; sourceTree = ""; }; @@ -1222,6 +1226,8 @@ D0C7BBBF207D82C0001345EF /* CleverTapSDK */ = { isa = PBXGroup; children = ( + 4803951A2A7ABAD200C4D254 /* CTAES.h */, + 480395192A7ABAD200C4D254 /* CTAES.m */, 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */, 4E838C4429A0C94B00ED0875 /* CleverTap+CTVar.h */, 4E64855A287440BA00C2F409 /* AmazonRootCA1.cer */, @@ -1518,6 +1524,7 @@ 07B94550219EA39000D4C542 /* CleverTap+Inbox.h in Headers */, 071EB4F9217F6427008F0FAB /* CTNotificationButton.h in Headers */, 48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */, + 4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */, D0213D4D207D905800FE5740 /* CleverTapTrackedViewController.h in Headers */, 4E25E3C5278887A70008C888 /* CTFlexibleIdentityRepo.h in Headers */, 071EB512217F6427008F0FAB /* CTInAppHTMLViewController.h in Headers */, @@ -2019,6 +2026,7 @@ 07B9454D219EA34300D4C542 /* Inbox.xcdatamodeld in Sources */, D0213D51207D905800FE5740 /* CleverTapUTMDetail.m in Sources */, D0596EFB208A6A9000A80753 /* CTSwizzle.m in Sources */, + 4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */, D032F3A92093EC9700F98D74 /* CTValidationResult.m in Sources */, 071EB501217F6427008F0FAB /* CTFooterViewController.m in Sources */, D0A84ADD209136D500191B1F /* CTLogger.m in Sources */, diff --git a/CleverTapSDK/CTAES.h b/CleverTapSDK/CTAES.h new file mode 100644 index 00000000..32c5cbef --- /dev/null +++ b/CleverTapSDK/CTAES.h @@ -0,0 +1,18 @@ +#import +#import "CleverTap.h" + +@interface CTAES : NSObject + +/** + * Returns AES128 encrypted string using the crypto framework. + */ +- (NSString *)getEncryptedString:(NSString *)identifier; + +/** + * Returns AES128 decrypted string using the crypto framework. + */ +- (NSString *)getDecryptedString:(NSString *)identifier; + +- (instancetype)initWithAccountID:(NSString *)accountID encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel isDefaultInstance:(BOOL)isDefaultInstance; + +@end diff --git a/CleverTapSDK/CTAES.m b/CleverTapSDK/CTAES.m new file mode 100644 index 00000000..5fec8326 --- /dev/null +++ b/CleverTapSDK/CTAES.m @@ -0,0 +1,178 @@ +#import +#import "CTAES.h" +#import "CTConstants.h" +#import "CTPreferences.h" +#import "CTUtils.h" + +NSString *const kENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY"; +NSString *const kCRYPT_KEY_PREFIX = @"Lq3fz"; +NSString *const kCRYPT_KEY_SUFFIX = @"bLti2"; +NSString *const kCacheGUIDS = @"CachedGUIDS"; + +@interface CTAES () {} +@property (nonatomic, strong) NSString *accountID; +@property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; +@property (nonatomic, assign) BOOL isDefaultInstance; +@end + +@implementation CTAES + +- (instancetype)initWithAccountID:(NSString *)accountID + encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel + isDefaultInstance:(BOOL)isDefaultInstance { + if (self = [super init]) { + _accountID = accountID; + _isDefaultInstance = isDefaultInstance; + [self updateEncryptionLevel:encryptionLevel]; + } + return self; +} + +- (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { + _encryptionLevel = encryptionLevel; + long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0]; + if (lastEncryptionLevel != _encryptionLevel) { + CleverTapLogStaticInternal(@"CleverTap Encryption level changed for account: %@ to: %d", _accountID, _encryptionLevel); + [self updatePreferencesValues]; + if (!_isDefaultInstance) { + // For Default instance, we are updating this after updating Local DB values on App Launch. + [CTPreferences putInt:_encryptionLevel forKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID]]; + } + } +} + +- (void)updatePreferencesValues { + NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[CTUtils getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + if (cachedGUIDS) { + NSMutableDictionary *newCache = [NSMutableDictionary new]; + if (_encryptionLevel == CleverTapEncryptionNone) { + [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { + NSString *key = [self getCachedKey:cachedKey]; + NSString *identifier = [self getCachedIdentifier:cachedKey]; + NSString *decryptedString = [self getDecryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString]; + newCache[cacheKey] = value; + }]; + } else if (_encryptionLevel == CleverTapEncryptionMedium) { + [cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) { + NSString *key = [self getCachedKey:cachedKey]; + NSString *identifier = [self getCachedIdentifier:cachedKey]; + NSString *encryptedString = [self getEncryptedString:identifier]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedString]; + newCache[cacheKey] = value; + }]; + } + [CTPreferences putObject:newCache forKey:[CTUtils getKeyWithSuffix:kCacheGUIDS accountID:_accountID]]; + } +} + +- (NSString *)getEncryptedString:(NSString *)identifier { + NSString *encryptedString = identifier; + if (_encryptionLevel == CleverTapEncryptionMedium) { + @try { + NSData *dataValue = [identifier dataUsingEncoding:NSUTF8StringEncoding]; + NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt]; + if (encryptedData) { + encryptedString = [encryptedData base64EncodedStringWithOptions:kNilOptions]; + } + } @catch (NSException *e) { + CleverTapLogStaticInternal(@"Error: %@ while encrypting the string: %@", e.debugDescription, identifier); + return identifier; + } + } + return encryptedString; +} + +- (NSString *)getDecryptedString:(NSString *)identifier { + NSString *decryptedString = identifier; + @try { + NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions]; + NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt]; + if (decryptedData && decryptedData.length > 0) { + NSString *utf8EncodedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; + if (utf8EncodedString) { + decryptedString = utf8EncodedString; + } + } + } @catch (NSException *e) { + CleverTapLogStaticInternal(@"Error: %@ while decrypting the string: %@", e.debugDescription, identifier); + return identifier; + } + return decryptedString; +} + +- (NSData *)convertData:(NSData *)data + withOperation:(CCOperation)operation { + NSData *outputData = [self AES128WithOperation:operation + key:[self generateKeyPassword] + identifier:CLTAP_ENCRYPTION_IV + data:data]; + return outputData; +} + +- (NSData *)AES128WithOperation:(CCOperation)operation + key:(NSString *)key + identifier:(NSString *)identifier + data:(NSData *)data { + // Note: The key will be 0's but we intentionally are keeping it this way to maintain + // compatibility. The correct code is: + // char keyPtr[[key length] + 1]; + char keyCString[kCCKeySizeAES128 + 1]; + memset(keyCString, 0, sizeof(keyCString)); + [key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding]; + + char identifierCString[kCCBlockSizeAES128 + 1]; + memset(identifierCString, 0, sizeof(identifierCString)); + [identifier getCString:identifierCString + maxLength:sizeof(identifierCString) + encoding:NSUTF8StringEncoding]; + + size_t outputAvailableSize = [data length] + kCCBlockSizeAES128; + void *output = malloc(outputAvailableSize); + + size_t outputMovedSize = 0; + CCCryptorStatus cryptStatus = CCCrypt(operation, + kCCAlgorithmAES128, + kCCOptionPKCS7Padding, + keyCString, + kCCBlockSizeAES128, + identifierCString, + [data bytes], + [data length], + output, + outputAvailableSize, + &outputMovedSize); + + if (cryptStatus != kCCSuccess) { + CleverTapLogStaticInternal(@"Failed to encode/deocde the string with error code: %d", cryptStatus); + free(output); + return nil; + } + + return [NSData dataWithBytesNoCopy:output length:outputMovedSize]; +} + +- (NSString *)getCachedKey:(NSString *)value { + if ([value rangeOfString:@"_"].length > 0) { + NSUInteger index = [value rangeOfString:@"_"].location; + return [value substringToIndex:index]; + } else { + return nil; + } +} + +- (NSString *)getCachedIdentifier:(NSString *)value { + if ([value rangeOfString:@"_"].length > 0) { + NSUInteger index = [value rangeOfString:@"_"].location; + return [value substringFromIndex:index+1]; + } else { + return nil; + } +} + +- (NSString *)generateKeyPassword { + NSString *keyPassword = [NSString stringWithFormat:@"%@%@%@",kCRYPT_KEY_PREFIX, _accountID, kCRYPT_KEY_SUFFIX]; + return keyPassword; +} + +@end diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 51a702d3..1383b7fa 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -152,4 +152,8 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_DEFINE_VARS_URL @"/defineVars" +#define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" +#define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" +#define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"userEmail", @"userPhone", @"userName"]); + diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 69b73fce..4dcac0b4 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -7,12 +7,16 @@ #import "CleverTapInstanceConfig.h" #import "CleverTapInstanceConfigPrivate.h" #import "CTLoginInfoProvider.h" +#import "CTAES.h" +#import "CTPreferences.h" +#import "CTUtils.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; static const double kProfilePersistenceIntervalSeconds = 30.f; NSString* const kWR_KEY_EVENTS = @"local_events_cache"; NSString* const kLocalCacheLastSync = @"local_cache_last_sync"; NSString* const kLocalCacheExpiry = @"local_cache_expiry"; +NSString *const CT_ENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY"; @interface CTLocalDataStore() { NSMutableDictionary *localProfileUpdateExpiryStore; @@ -23,6 +27,7 @@ @interface CTLocalDataStore() { @property (nonatomic, strong) CleverTapInstanceConfig *config; @property (nonatomic, strong) CTDeviceInfo *deviceInfo; +@property (nonatomic, strong) NSArray *piiKeys; @end @@ -36,6 +41,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); lastProfilePersistenceTime = 0; + _piiKeys = CLTAP_ENCRYPTION_PII_DATA; [self runOnBackgroundQueue:^{ @synchronized (self->localProfileForSession) { self->localProfileForSession = [self _inflateLocalProfile]; @@ -624,7 +630,8 @@ - (NSMutableDictionary *)_inflateLocalProfile { if (!_profile) { _profile = [NSMutableDictionary dictionary]; } - return _profile; + NSMutableDictionary *updatedProfile = [self decryptPIIDataIfEncrypted:_profile]; + return updatedProfile; } - (void)persistLocalProfileIfRequired { @@ -656,7 +663,8 @@ - (void)_persistLocalProfileAsync { self->lastProfilePersistenceTime = @([[[NSDate alloc] init] timeIntervalSince1970]); } - [CTPreferences archiveObject:_profile forFileName:[self profileFileName]]; + NSMutableDictionary *updatedProfile = [self cryptValuesIfNeeded:_profile]; + [CTPreferences archiveObject:updatedProfile forFileName:[self profileFileName]]; }]; } @@ -807,4 +815,42 @@ - (NSDictionary*)generateBaseProfile { return profile; } +- (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profile { + long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:CT_ENCRYPTION_KEY accountID:self.config.accountId] withResetValue:0]; + [CTPreferences putInt:self.config.encryptionLevel forKey:[CTUtils getKeyWithSuffix:CT_ENCRYPTION_KEY accountID:self.config.accountId]]; + if (lastEncryptionLevel == CleverTapEncryptionMedium && self.config.aesCrypt) { + // Always store the local profile data in decrypted values. + NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; + for (NSString *key in profile) { + if ([_piiKeys containsObject:key]) { + NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; + NSString *decryptedString = [self.config.aesCrypt getDecryptedString:value]; + updatedProfile[key] = decryptedString; + } else { + updatedProfile[key] = profile[key]; + } + } + return updatedProfile; + } + + return profile; +} + +- (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile { + if (self.config.encryptionLevel == CleverTapEncryptionMedium && self.config.aesCrypt) { + NSMutableDictionary *updatedProfile = [NSMutableDictionary new]; + for (NSString *key in profile) { + if ([_piiKeys containsObject:key]) { + NSString *value = [NSString stringWithFormat:@"%@",profile[key]]; + updatedProfile[key] = [self.config.aesCrypt getEncryptedString:value]; + } else { + updatedProfile[key] = profile[key]; + } + } + return updatedProfile; + } + + return profile; +} + @end diff --git a/CleverTapSDK/CTLoginInfoProvider.m b/CleverTapSDK/CTLoginInfoProvider.m index 78b8d217..04aec108 100644 --- a/CleverTapSDK/CTLoginInfoProvider.m +++ b/CleverTapSDK/CTLoginInfoProvider.m @@ -10,6 +10,7 @@ #import "CTPreferences.h" #import "CTConstants.h" #import "CleverTapInstanceConfigPrivate.h" +#import "CTAES.h" NSString *const kCachedGUIDS = @"CachedGUIDS"; NSString *const kCachedIdentities = @"CachedIdentities"; @@ -36,7 +37,12 @@ - (void)cacheGUID:(NSString *)guid forKey:(NSString *)key andIdentifier:(NSStrin NSDictionary *cache = [self getCachedGUIDs]; if (!cache) cache = @{}; NSMutableDictionary *newCache = [NSMutableDictionary dictionaryWithDictionary:cache]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, identifier]; + + NSString *encryptedIdentifier = identifier; + if (self.config.aesCrypt) { + encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + } + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; newCache[cacheKey] = guid; [self setCachedGUIDs:newCache]; } @@ -71,7 +77,11 @@ - (NSString *)getGUIDforKey:(NSString *)key andIdentifier:(NSString *)identifier if (!key || !identifier) return nil; NSDictionary *cache = [self getCachedGUIDs]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, identifier]; + NSString *encryptedIdentifier = identifier; + if (self.config.aesCrypt) { + encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; + } + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier]; if (!cache) return nil; else return cache[cacheKey]; } diff --git a/CleverTapSDK/CTPlistInfo.h b/CleverTapSDK/CTPlistInfo.h index 2b0691be..dad3fb00 100644 --- a/CleverTapSDK/CTPlistInfo.h +++ b/CleverTapSDK/CTPlistInfo.h @@ -1,4 +1,5 @@ #import +#import "CleverTap.h" @interface CTPlistInfo : NSObject @@ -12,6 +13,7 @@ @property (nonatomic, assign, readonly) BOOL useCustomCleverTapId; @property (nonatomic, assign, readonly) BOOL beta; @property (nonatomic, assign, readonly) BOOL disableIDFV; +@property (nonatomic, readonly) CleverTapEncryptionLevel encryptionLevel; + (instancetype _Nullable)sharedInstance; - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token region:(NSString * _Nullable)region; diff --git a/CleverTapSDK/CTPlistInfo.m b/CleverTapSDK/CTPlistInfo.m index 3564d03a..17bf2f96 100644 --- a/CleverTapSDK/CTPlistInfo.m +++ b/CleverTapSDK/CTPlistInfo.m @@ -90,6 +90,9 @@ - (instancetype)init { // Fetch IDFV Flag from INFO.PLIST NSString *disableIDFV = [CTPlistInfo getMetaDataForAttribute:CLTAP_DISABLE_IDFV_LABEL]; _disableIDFV = (disableIDFV && [disableIDFV isEqualToString:@"1"]); + + NSString *encryptionLevel = [CTPlistInfo getMetaDataForAttribute:CLTAP_ENCRYPTION_LEVEL]; + [self setEncryption:encryptionLevel]; } return self; } @@ -112,4 +115,16 @@ - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSStri _proxyDomain = proxyDomain; _spikyProxyDomain = spikyProxyDomain; } + +- (void)setEncryption:(NSString *)encryptionLevel { + if (encryptionLevel && [encryptionLevel isEqualToString:@"0"]) { + _encryptionLevel = CleverTapEncryptionNone; + } else if (encryptionLevel && [encryptionLevel isEqualToString:@"1"]) { + _encryptionLevel = CleverTapEncryptionMedium; + } else { + _encryptionLevel = CleverTapEncryptionNone; + CleverTapLogStaticInternal(@"Supported encryption levels are only 0 and 1. Setting it to 0 by default"); + } +} + @end diff --git a/CleverTapSDK/CTUtils.h b/CleverTapSDK/CTUtils.h index 126ec336..d7923d2b 100644 --- a/CleverTapSDK/CTUtils.h +++ b/CleverTapSDK/CTUtils.h @@ -7,4 +7,5 @@ + (double)toTwoPlaces:(double)x; + (BOOL)isNullOrEmpty:(id)obj; + (NSString *)jsonObjectToString:(id)object; ++ (NSString *)getKeyWithSuffix:(NSString *)suffix accountID:(NSString *)accountID; @end diff --git a/CleverTapSDK/CTUtils.m b/CleverTapSDK/CTUtils.m index 590de0da..5c0505a4 100644 --- a/CleverTapSDK/CTUtils.m +++ b/CleverTapSDK/CTUtils.m @@ -85,4 +85,9 @@ + (NSString *)jsonObjectToString:(id)object { } } ++ (NSString *)getKeyWithSuffix:(NSString *)suffix + accountID:(NSString *)accountID { + return [NSString stringWithFormat:@"%@:%@", accountID, suffix]; +} + @end diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index 4f00cab7..818aeffb 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -54,6 +54,11 @@ typedef NS_ENUM(int, CTSignedCallEvent) { SIGNED_CALL_END_EVENT }; +typedef NS_ENUM(int, CleverTapEncryptionLevel) { + CleverTapEncryptionNone = 0, + CleverTapEncryptionMedium = 1 +}; + @interface CleverTap : NSObject #pragma mark - Properties diff --git a/CleverTapSDK/CleverTapBuildInfo.h b/CleverTapSDK/CleverTapBuildInfo.h index 5abb90be..3e8fd9b5 100644 --- a/CleverTapSDK/CleverTapBuildInfo.h +++ b/CleverTapSDK/CleverTapBuildInfo.h @@ -1 +1 @@ -#define WR_SDK_REVISION @"50102" +#define WR_SDK_REVISION @"50200" diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index 02559fb6..fbb6c420 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -1,5 +1,6 @@ #import #import "CleverTap.h" +@class CTAES; @interface CleverTapInstanceConfig : NSObject @@ -16,6 +17,8 @@ @property (nonatomic, assign) BOOL disableIDFV; @property (nonatomic, assign) CleverTapLogLevel logLevel; @property (nonatomic, strong, nullable) NSArray *identityKeys; +@property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; +@property (nonatomic, strong, nullable) CTAES *aesCrypt; - (instancetype _Nonnull) init __unavailable; @@ -35,4 +38,20 @@ accountToken:(NSString* _Nonnull)accountToken proxyDomain:(NSString* _Nonnull)proxyDomain spikyProxyDomain:(NSString* _Nonnull)spikyProxyDomain; + +/*! + @method + + @abstract + Set the encryption level + + @discussion + Set the encryption level using CleverTapEncryptionLevel enum values (or the corresponding int values). + + CleverTapEncryptionNone - turns off all encryption. + CleverTapEncryptionMedium - turns encryption on for PII data + + @param encryptionLevel the encryption level to set + */ +- (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel; @end diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index 72bac34e..05e565cc 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -2,6 +2,7 @@ #import "CleverTapInstanceConfigPrivate.h" #import "CTPlistInfo.h" #import "CTConstants.h" +#import "CTAES.h" @implementation CleverTapInstanceConfig @@ -25,6 +26,8 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeBool: _isCreatedPostAppLaunched forKey:@"isCreatedPostAppLaunched"]; [coder encodeBool: _beta forKey:@"beta"]; [coder encodeBool: _wv_init forKey:@"wv_init"]; + [coder encodeBool: _encryptionLevel forKey:@"encryptionLevel"]; + [coder encodeBool: _aesCrypt forKey:@"aesCrypt"]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { @@ -47,6 +50,8 @@ - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { _isCreatedPostAppLaunched = [coder decodeBoolForKey:@"isCreatedPostAppLaunched"]; _beta = [coder decodeBoolForKey:@"beta"]; _wv_init = [coder decodeBoolForKey:@"wv_init"]; + _encryptionLevel = [coder decodeIntForKey:@"encryptionLevel"]; + _aesCrypt = [coder decodeObjectForKey:@"aesCrypt"]; } return self; } @@ -176,6 +181,8 @@ - (instancetype)copyWithZone:(NSZone*)zone { copy.disableIDFV = self.disableIDFV; copy.identityKeys = self.identityKeys; copy.beta = self.beta; + copy.encryptionLevel = self.encryptionLevel; + copy.aesCrypt = self.aesCrypt; return copy; } @@ -197,6 +204,10 @@ - (void) setupPlistData:(BOOL)isDefault { _enablePersonalization = YES; _logLevel = 0; _beta = plist.beta; + _encryptionLevel = isDefault ? plist.encryptionLevel : CleverTapEncryptionNone; + if (isDefault) { + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel isDefaultInstance:isDefault]; + } } - (void) checkIfAvailableAccountId:(NSString *)accountId @@ -209,4 +220,13 @@ - (void) checkIfAvailableAccountId:(NSString *)accountId CleverTapLogStaticInfo("CleverTap accountToken is empty"); } } + +- (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel { + if (!_isDefaultInstance) { + _encryptionLevel = encryptionLevel; + _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel isDefaultInstance:_isDefaultInstance]; + } else { + CleverTapLogStaticInfo("CleverTap Encryption level for default instance can't be updated from setEncryptionLevel method"); + } +} @end diff --git a/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m b/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m index 5bbe8387..a5fcb4f0 100755 --- a/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m +++ b/CleverTapSDK/Inbox/models/CleverTapInboxMessage.m @@ -21,9 +21,9 @@ - (instancetype)initWithJSON:(NSDictionary *)json { if (campaignId) { _campaignId = campaignId; } - NSDictionary *customData = json[@"kv"]; + NSArray *customData = json[@"msg"][@"custom_kv"]; if (customData) { - _customData = customData; + _customData = [self getMessageCustomKV:customData]; } NSArray *tags = json[@"msg"][@"tags"]; if (tags) { @@ -110,4 +110,22 @@ - (NSString *)relativeDateStringForDate:(NSDate *)date } } +- (NSDictionary *)getMessageCustomKV:(NSArray *)data { + NSMutableDictionary *customKV = [NSMutableDictionary new]; + for (NSUInteger i = 0; i < [data count]; ++i) { + NSDictionary *kv = data[i]; + if ([kv objectForKey:@"key"]) { + NSString *key = kv[@"key"]; + if ([kv objectForKey:@"value"]) { + NSDictionary *value = kv[@"value"]; + if ([value objectForKey:@"text"]) { + NSString *text = value[@"text"]; + customKV[key] = text; + } + } + } + } + return customKV; +} + @end diff --git a/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj b/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj index 952e65e6..e05d003d 100644 --- a/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj +++ b/ObjCStarter/ObjCStarter.xcodeproj/project.pbxproj @@ -893,6 +893,7 @@ CODE_SIGN_ENTITLEMENTS = ObjCStarter/ObjCStarter.entitlements; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = A5J34NR598; + GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = ObjCStarter/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -914,6 +915,7 @@ CODE_SIGN_ENTITLEMENTS = ObjCStarter/ObjCStarter.entitlements; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = A5J34NR598; + GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = ObjCStarter/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ObjCStarter/ObjCStarter/Info.plist b/ObjCStarter/ObjCStarter/Info.plist index e50b15fd..3414cdae 100644 --- a/ObjCStarter/ObjCStarter/Info.plist +++ b/ObjCStarter/ObjCStarter/Info.plist @@ -45,5 +45,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + CleverTapEncryptionLevel + 1 diff --git a/README.md b/README.md index 1c560a99..48fa2bd3 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ CleverTap iOS SDK supports creating remote config variables, refer [Remote Confi * A [demo application](/ObjCStarter) showing the integration of our SDK in Objective-C language. * A [demo application](/SwiftStarter) showing the integration of our SDK in Swift language. * A [demo application](/SPMStarter) showing the installation of our SDK via Swift Package Manager. +* A [demo application](/SwiftUIStarter) showing the installation of our SDK in Swift UI Application. ## 🆕 Change Log diff --git a/SwiftUIStarter/NotificationService/Info.plist b/SwiftUIStarter/NotificationService/Info.plist new file mode 100644 index 00000000..c837dcdc --- /dev/null +++ b/SwiftUIStarter/NotificationService/Info.plist @@ -0,0 +1,17 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + CleverTapAccountID + W9R-486-4W5Z + CleverTapToken + 6b4-2c0 + + diff --git a/SwiftUIStarter/NotificationService/NotificationService.swift b/SwiftUIStarter/NotificationService/NotificationService.swift new file mode 100644 index 00000000..18a039ef --- /dev/null +++ b/SwiftUIStarter/NotificationService/NotificationService.swift @@ -0,0 +1,18 @@ +import CTNotificationService +import CleverTapSDK + +class NotificationService: CTNotificationServiceExtension { + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + CleverTap.setDebugLevel(2) + // While running the Application add CleverTap Account ID and Account token in your .plist file + CleverTap.sharedInstance()?.recordEvent("testEventFromNotificationService") + let profile: Dictionary = [ + "Identity": 63344 as AnyObject, + "Email": "test63344@gmail.com" as AnyObject] + CleverTap.sharedInstance()?.profilePush(profile) + // call to record the Notification viewed + CleverTap.sharedInstance()?.recordNotificationViewedEvent(withData: request.content.userInfo) + super.didReceive(request, withContentHandler: contentHandler) + } +} diff --git a/SwiftUIStarter/Podfile b/SwiftUIStarter/Podfile new file mode 100644 index 00000000..7a195d0a --- /dev/null +++ b/SwiftUIStarter/Podfile @@ -0,0 +1,14 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '13.0' + +target 'SwiftUIStarter' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + pod 'CleverTap-iOS-SDK', '5.0.1' + + target 'NotificationService' do + pod 'CTNotificationService' + end + +end diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ceb36edf --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.pbxproj @@ -0,0 +1,647 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 480395022A6C75D900C4D254 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 480395012A6C75D900C4D254 /* NotificationService.swift */; }; + 480395062A6C75D900C4D254 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 480394FF2A6C75D900C4D254 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 489C5FFC2A6C5CCE00440747 /* CTWebViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489C5FFB2A6C5CCE00440747 /* CTWebViewRepresentable.swift */; }; + 489C5FFE2A6C5E1A00440747 /* sampleHTMLCode.html in Resources */ = {isa = PBXBuildFile; fileRef = 489C5FFD2A6C5E1A00440747 /* sampleHTMLCode.html */; }; + 489C60002A6C616F00440747 /* CTCallbackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489C5FFF2A6C616F00440747 /* CTCallbackCoordinator.swift */; }; + 489C60022A6C699300440747 /* CTViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 489C60012A6C699300440747 /* CTViewModifier.swift */; }; + 48E0B3422A66FE5F009C4312 /* SwiftUIStarterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3412A66FE5F009C4312 /* SwiftUIStarterApp.swift */; }; + 48E0B3442A66FE5F009C4312 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3432A66FE5F009C4312 /* ContentView.swift */; }; + 48E0B3462A66FE61009C4312 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48E0B3452A66FE61009C4312 /* Assets.xcassets */; }; + 48E0B3492A66FE61009C4312 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48E0B3482A66FE61009C4312 /* Preview Assets.xcassets */; }; + 48E0B3522A6708B5009C4312 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3512A6708B5009C4312 /* HomeScreen.swift */; }; + 48E0B3542A67166C009C4312 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3532A67166C009C4312 /* AppDelegate.swift */; }; + 48E0B35A2A69C5B5009C4312 /* CTAppInboxRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E0B3592A69C5B5009C4312 /* CTAppInboxRepresentable.swift */; }; + 48E0B35E2A69D47F009C4312 /* Pods_SwiftUIStarter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 334E3ED58090A5AEEF23410D /* Pods_SwiftUIStarter.framework */; }; + A983E8E0003C52EE342CBDB9 /* Pods_SwiftUIStarter_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA9862E5A057F98569E2EEBD /* Pods_SwiftUIStarter_NotificationService.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 480395042A6C75D900C4D254 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 48E0B3362A66FE5F009C4312 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 480394FE2A6C75D900C4D254; + remoteInfo = NotificationService; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 480395072A6C75D900C4D254 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 480395062A6C75D900C4D254 /* NotificationService.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 13D9D8717B26488C092AB5E7 /* Pods-SwiftUIStarter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter.release.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter.release.xcconfig"; sourceTree = ""; }; + 334E3ED58090A5AEEF23410D /* Pods_SwiftUIStarter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftUIStarter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 480394FF2A6C75D900C4D254 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 480395012A6C75D900C4D254 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 480395032A6C75D900C4D254 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 489C5FFB2A6C5CCE00440747 /* CTWebViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTWebViewRepresentable.swift; sourceTree = ""; }; + 489C5FFD2A6C5E1A00440747 /* sampleHTMLCode.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = sampleHTMLCode.html; sourceTree = ""; }; + 489C5FFF2A6C616F00440747 /* CTCallbackCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTCallbackCoordinator.swift; sourceTree = ""; }; + 489C60012A6C699300440747 /* CTViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTViewModifier.swift; sourceTree = ""; }; + 48E0B33E2A66FE5F009C4312 /* SwiftUIStarter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftUIStarter.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 48E0B3412A66FE5F009C4312 /* SwiftUIStarterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIStarterApp.swift; sourceTree = ""; }; + 48E0B3432A66FE5F009C4312 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 48E0B3452A66FE61009C4312 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 48E0B3482A66FE61009C4312 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 48E0B3512A6708B5009C4312 /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 48E0B3532A67166C009C4312 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 48E0B3552A67187F009C4312 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 48E0B3562A6718B6009C4312 /* SwiftUIStarter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftUIStarter.entitlements; sourceTree = ""; }; + 48E0B3592A69C5B5009C4312 /* CTAppInboxRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CTAppInboxRepresentable.swift; sourceTree = ""; }; + 48E0B3612A69D697009C4312 /* CleverTapSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CleverTapSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 48E0B3652A69D757009C4312 /* CleverTapSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CleverTapSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A530F6FFBC26113F8B9C9B13 /* Pods-SwiftUIStarter-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter-NotificationService/Pods-SwiftUIStarter-NotificationService.debug.xcconfig"; sourceTree = ""; }; + AA9862E5A057F98569E2EEBD /* Pods_SwiftUIStarter_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftUIStarter_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C738698B25C9108B38EA207C /* Pods-SwiftUIStarter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter.debug.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter.debug.xcconfig"; sourceTree = ""; }; + D6E36C7887B8C53444BCF27F /* Pods-SwiftUIStarter-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftUIStarter-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-SwiftUIStarter-NotificationService/Pods-SwiftUIStarter-NotificationService.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 480394FC2A6C75D900C4D254 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A983E8E0003C52EE342CBDB9 /* Pods_SwiftUIStarter_NotificationService.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 48E0B33B2A66FE5F009C4312 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 48E0B35E2A69D47F009C4312 /* Pods_SwiftUIStarter.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 480395002A6C75D900C4D254 /* NotificationService */ = { + isa = PBXGroup; + children = ( + 480395012A6C75D900C4D254 /* NotificationService.swift */, + 480395032A6C75D900C4D254 /* Info.plist */, + ); + path = NotificationService; + sourceTree = ""; + }; + 48E0B3352A66FE5F009C4312 = { + isa = PBXGroup; + children = ( + 48E0B3402A66FE5F009C4312 /* SwiftUIStarter */, + 480395002A6C75D900C4D254 /* NotificationService */, + 48E0B33F2A66FE5F009C4312 /* Products */, + 575B211555AFBB0B1F132EC0 /* Pods */, + 5715B45A7DE1C4DD1431D551 /* Frameworks */, + ); + sourceTree = ""; + }; + 48E0B33F2A66FE5F009C4312 /* Products */ = { + isa = PBXGroup; + children = ( + 48E0B33E2A66FE5F009C4312 /* SwiftUIStarter.app */, + 480394FF2A6C75D900C4D254 /* NotificationService.appex */, + ); + name = Products; + sourceTree = ""; + }; + 48E0B3402A66FE5F009C4312 /* SwiftUIStarter */ = { + isa = PBXGroup; + children = ( + 489C5FFD2A6C5E1A00440747 /* sampleHTMLCode.html */, + 48E0B3562A6718B6009C4312 /* SwiftUIStarter.entitlements */, + 48E0B3552A67187F009C4312 /* Info.plist */, + 48E0B3412A66FE5F009C4312 /* SwiftUIStarterApp.swift */, + 48E0B3432A66FE5F009C4312 /* ContentView.swift */, + 48E0B3452A66FE61009C4312 /* Assets.xcassets */, + 48E0B3472A66FE61009C4312 /* Preview Content */, + 48E0B3512A6708B5009C4312 /* HomeScreen.swift */, + 48E0B3532A67166C009C4312 /* AppDelegate.swift */, + 48E0B3592A69C5B5009C4312 /* CTAppInboxRepresentable.swift */, + 489C5FFB2A6C5CCE00440747 /* CTWebViewRepresentable.swift */, + 489C5FFF2A6C616F00440747 /* CTCallbackCoordinator.swift */, + 489C60012A6C699300440747 /* CTViewModifier.swift */, + ); + path = SwiftUIStarter; + sourceTree = ""; + }; + 48E0B3472A66FE61009C4312 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 48E0B3482A66FE61009C4312 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 5715B45A7DE1C4DD1431D551 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 48E0B3652A69D757009C4312 /* CleverTapSDK.framework */, + 48E0B3612A69D697009C4312 /* CleverTapSDK.framework */, + 334E3ED58090A5AEEF23410D /* Pods_SwiftUIStarter.framework */, + AA9862E5A057F98569E2EEBD /* Pods_SwiftUIStarter_NotificationService.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 575B211555AFBB0B1F132EC0 /* Pods */ = { + isa = PBXGroup; + children = ( + C738698B25C9108B38EA207C /* Pods-SwiftUIStarter.debug.xcconfig */, + 13D9D8717B26488C092AB5E7 /* Pods-SwiftUIStarter.release.xcconfig */, + A530F6FFBC26113F8B9C9B13 /* Pods-SwiftUIStarter-NotificationService.debug.xcconfig */, + D6E36C7887B8C53444BCF27F /* Pods-SwiftUIStarter-NotificationService.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 480394FE2A6C75D900C4D254 /* NotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4803950A2A6C75D900C4D254 /* Build configuration list for PBXNativeTarget "NotificationService" */; + buildPhases = ( + 2EF2C24BBEEC32DB1ABD9B34 /* [CP] Check Pods Manifest.lock */, + 480394FB2A6C75D900C4D254 /* Sources */, + 480394FC2A6C75D900C4D254 /* Frameworks */, + 480394FD2A6C75D900C4D254 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationService; + productName = NotificationService; + productReference = 480394FF2A6C75D900C4D254 /* NotificationService.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 48E0B33D2A66FE5F009C4312 /* SwiftUIStarter */ = { + isa = PBXNativeTarget; + buildConfigurationList = 48E0B34C2A66FE61009C4312 /* Build configuration list for PBXNativeTarget "SwiftUIStarter" */; + buildPhases = ( + 4BE244E5E28319E6D713411D /* [CP] Check Pods Manifest.lock */, + 48E0B33A2A66FE5F009C4312 /* Sources */, + 48E0B33B2A66FE5F009C4312 /* Frameworks */, + 48E0B33C2A66FE5F009C4312 /* Resources */, + 6CF66421FB07ED9538E0E987 /* [CP] Embed Pods Frameworks */, + 480395072A6C75D900C4D254 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 480395052A6C75D900C4D254 /* PBXTargetDependency */, + ); + name = SwiftUIStarter; + productName = SwiftUIStarter; + productReference = 48E0B33E2A66FE5F009C4312 /* SwiftUIStarter.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 48E0B3362A66FE5F009C4312 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 480394FE2A6C75D900C4D254 = { + CreatedOnToolsVersion = 14.3; + }; + 48E0B33D2A66FE5F009C4312 = { + CreatedOnToolsVersion = 14.3; + }; + }; + }; + buildConfigurationList = 48E0B3392A66FE5F009C4312 /* Build configuration list for PBXProject "SwiftUIStarter" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 48E0B3352A66FE5F009C4312; + productRefGroup = 48E0B33F2A66FE5F009C4312 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 48E0B33D2A66FE5F009C4312 /* SwiftUIStarter */, + 480394FE2A6C75D900C4D254 /* NotificationService */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 480394FD2A6C75D900C4D254 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 48E0B33C2A66FE5F009C4312 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 48E0B3492A66FE61009C4312 /* Preview Assets.xcassets in Resources */, + 48E0B3462A66FE61009C4312 /* Assets.xcassets in Resources */, + 489C5FFE2A6C5E1A00440747 /* sampleHTMLCode.html in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2EF2C24BBEEC32DB1ABD9B34 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SwiftUIStarter-NotificationService-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4BE244E5E28319E6D713411D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SwiftUIStarter-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6CF66421FB07ED9538E0E987 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftUIStarter/Pods-SwiftUIStarter-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 480394FB2A6C75D900C4D254 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 480395022A6C75D900C4D254 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 48E0B33A2A66FE5F009C4312 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 489C60022A6C699300440747 /* CTViewModifier.swift in Sources */, + 48E0B3522A6708B5009C4312 /* HomeScreen.swift in Sources */, + 48E0B3542A67166C009C4312 /* AppDelegate.swift in Sources */, + 48E0B35A2A69C5B5009C4312 /* CTAppInboxRepresentable.swift in Sources */, + 489C60002A6C616F00440747 /* CTCallbackCoordinator.swift in Sources */, + 48E0B3442A66FE5F009C4312 /* ContentView.swift in Sources */, + 48E0B3422A66FE5F009C4312 /* SwiftUIStarterApp.swift in Sources */, + 489C5FFC2A6C5CCE00440747 /* CTWebViewRepresentable.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 480395052A6C75D900C4D254 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 480394FE2A6C75D900C4D254 /* NotificationService */; + targetProxy = 480395042A6C75D900C4D254 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 480395082A6C75D900C4D254 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A530F6FFBC26113F8B9C9B13 /* Pods-SwiftUIStarter-NotificationService.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A5J34NR598; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 480395092A6C75D900C4D254 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D6E36C7887B8C53444BCF27F /* Pods-SwiftUIStarter-NotificationService.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A5J34NR598; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo.NotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 48E0B34A2A66FE61009C4312 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 48E0B34B2A66FE61009C4312 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 48E0B34D2A66FE61009C4312 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C738698B25C9108B38EA207C /* Pods-SwiftUIStarter.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUIStarter/SwiftUIStarter.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SwiftUIStarter/Preview Content\""; + DEVELOPMENT_TEAM = A5J34NR598; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SwiftUIStarter/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 48E0B34E2A66FE61009C4312 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13D9D8717B26488C092AB5E7 /* Pods-SwiftUIStarter.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = SwiftUIStarter/SwiftUIStarter.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"SwiftUIStarter/Preview Content\""; + DEVELOPMENT_TEAM = A5J34NR598; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SwiftUIStarter/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.clevertap.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4803950A2A6C75D900C4D254 /* Build configuration list for PBXNativeTarget "NotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 480395082A6C75D900C4D254 /* Debug */, + 480395092A6C75D900C4D254 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 48E0B3392A66FE5F009C4312 /* Build configuration list for PBXProject "SwiftUIStarter" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 48E0B34A2A66FE61009C4312 /* Debug */, + 48E0B34B2A66FE61009C4312 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 48E0B34C2A66FE61009C4312 /* Build configuration list for PBXNativeTarget "SwiftUIStarter" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 48E0B34D2A66FE61009C4312 /* Debug */, + 48E0B34E2A66FE61009C4312 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 48E0B3362A66FE5F009C4312 /* Project object */; +} diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme new file mode 100644 index 00000000..33c53587 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme new file mode 100644 index 00000000..ad682d9d --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcodeproj/xcshareddata/xcschemes/SwiftUIStarter.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata b/SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..b6edc1ce --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwiftUIStarter/SwiftUIStarter/AppDelegate.swift b/SwiftUIStarter/SwiftUIStarter/AppDelegate.swift new file mode 100644 index 00000000..9c930323 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/AppDelegate.swift @@ -0,0 +1,54 @@ +import UserNotifications +import CleverTapSDK + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + registerForPush() + CleverTap.setDebugLevel(2) + CleverTap.autoIntegrate() + CleverTap.sharedInstance()?.enableDeviceNetworkInfoReporting(true) + return true + } + + func registerForPush() { + // Register for Push notifications + UNUserNotificationCenter.current().delegate = self + // request Permissions + UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .badge, .alert], completionHandler: {granted, error in + if granted { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + }) + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + NSLog("%@: failed to register for remote notifications: %@", self.description, error.localizedDescription) + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + NSLog("%@: registered for remote notifications: %@", self.description, deviceToken.description) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + NSLog("%@: did receive notification response: %@", self.description, response.notification.request.content.userInfo) + completionHandler() + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + NSLog("%@: will present notification: %@", self.description, notification.request.content.userInfo) + completionHandler([.badge, .sound, .alert]) + } + + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + NSLog("%@: did receive remote notification completionhandler: %@", self.description, userInfo) + completionHandler(UIBackgroundFetchResult.noData) + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/100.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 00000000..982c064e Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/1024.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 00000000..ad04fe16 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/114.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 00000000..7cf33f34 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/120.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 00000000..bff87133 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/144.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 00000000..b76314de Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/152.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 00000000..d11511eb Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/167.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 00000000..a1b6f81f Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/180.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 00000000..accd26c4 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/20.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 00000000..42c690d1 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/29.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 00000000..6b174bb4 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/40.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 00000000..9a6d38b6 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/50.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 00000000..ac24dd43 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/57.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 00000000..390fe663 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/58.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 00000000..b5892de0 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/60.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 00000000..e4d591b7 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/72.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 00000000..676045e9 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/76.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 00000000..9ee46360 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/80.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 00000000..654e3716 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/87.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 00000000..47e09b97 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/Contents.json b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..65b74d7e --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} \ No newline at end of file diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/Contents.json b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Contents.json b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Contents.json new file mode 100644 index 00000000..8742e746 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Logo_Logotype White BG@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Logo_Logotype Inverse Dark BG@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype Inverse Dark BG@2x.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype Inverse Dark BG@2x.png new file mode 100644 index 00000000..42f2af87 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype Inverse Dark BG@2x.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype White BG@2x.png b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype White BG@2x.png new file mode 100644 index 00000000..08a4b405 Binary files /dev/null and b/SwiftUIStarter/SwiftUIStarter/Assets.xcassets/logo.imageset/Logo_Logotype White BG@2x.png differ diff --git a/SwiftUIStarter/SwiftUIStarter/CTAppInbox.swift b/SwiftUIStarter/SwiftUIStarter/CTAppInbox.swift new file mode 100644 index 00000000..a5aac0e9 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTAppInbox.swift @@ -0,0 +1,27 @@ +import SwiftUI +import CleverTapSDK + +struct CTAppInboxRepresentable: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> CleverTapInboxViewController { + let style = CleverTapInboxStyleConfig() + style.title = "App Inbox" + style.navigationTintColor = .black + style.messageTags = ["tag1", "tag2"] + let inboxVC: CleverTapInboxViewController = (CleverTap.sharedInstance()?.newInboxViewController(with: style, andDelegate: context.coordinator))! + return inboxVC + } + + func updateUIViewController(_ uiViewController: CleverTapInboxViewController, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } + + func makeCoordinator() -> CTAppInboxCoordinator { + CTAppInboxCoordinator() + } + + class CTAppInboxCoordinator: NSObject, CleverTapInboxViewControllerDelegate { + func messageDidSelect(_ message: CleverTapInboxMessage, at index: Int32, withButtonIndex buttonIndex: Int32) { + // This is called when an inbox message or button is clicked + } + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift b/SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift new file mode 100644 index 00000000..c1a391fa --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTAppInboxRepresentable.swift @@ -0,0 +1,21 @@ +import SwiftUI +import CleverTapSDK + +struct CTAppInboxRepresentable: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> CleverTapInboxViewController { + let style = CleverTapInboxStyleConfig() + style.title = "App Inbox" + style.navigationTintColor = .black + style.messageTags = ["tag1", "tag2"] + let inboxVC: CleverTapInboxViewController = (CleverTap.sharedInstance()?.newInboxViewController(with: style, andDelegate: context.coordinator))! + return inboxVC + } + + func updateUIViewController(_ uiViewController: CleverTapInboxViewController, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } + + func makeCoordinator() -> CTCallbackCoordinator { + CTCallbackCoordinator() + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift b/SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift new file mode 100644 index 00000000..021c0b6d --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTCallbackCoordinator.swift @@ -0,0 +1,14 @@ +import CleverTapSDK + +class CTCallbackCoordinator: NSObject, CleverTapPushPermissionDelegate, CleverTapInboxViewControllerDelegate { + // MARK: CleverTapPushPermissionDelegate + func onPushPermissionResponse(_ accepted: Bool) { + print("Push Permission response called ---> accepted = \(accepted)") + } + + // MARK: CleverTapInboxViewControllerDelegate + func messageDidSelect(_ message: CleverTapInboxMessage, at index: Int32, withButtonIndex buttonIndex: Int32) { + // This is called when an inbox message or button is clicked + print("App Inbox tapped with button index: \(buttonIndex)") + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift b/SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift new file mode 100644 index 00000000..13ee0f3d --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTHomeScreen.swift @@ -0,0 +1,250 @@ +import SwiftUI +import CleverTapSDK + +var cleverTapAdditionalInstance: CleverTap = { + let ctConfig = CleverTapInstanceConfig.init(accountId: "ZWW-WWW-WWRZ", accountToken: "000-001") + return CleverTap.instance(with: ctConfig) + }() + +struct HomeScreen: View { + let eventList = [ "Record User Profile", + "Record User Profile with Properties", + "Record User Event called Product Viewed", + "Record User Event with Properties", + "Record User Charged Event", + "Record User Event to an Additional Instance", + "Show App Inbox", + "Analytics in a WebView", + "Increment User Profile Property", + "Decrement User Profile Property", + "Activate Custom domain proxy", + "Local Half Interstitial Push Primer" + ] + + @State private var viewDidLoad = false + + var body: some View { + NavigationView { + VStack { + Image("logo") + .scaledToFit() + .frame(height: 72) + List { + ForEach(0 ..< eventList.count, id: \.self) { index in + HStack { + Button("\(eventList[index])") { + buttonAction(index: index) + } + Spacer() + if (eventList[index] == "Show App Inbox") { + // Show App Inbox controller + NavigationLink(destination: CTAppInboxRepresentable()) { } + } else if (eventList[index] == "Analytics in a WebView") { + // Show Web View + NavigationLink(destination: CTWebViewRepresentable()) { } + } + } + } + } + } + .onAppear() { + print("~~~ onAppear") + if viewDidLoad == false { + viewDidLoad = true + self.registerAppInbox() + self.initializeAppInbox() + print("~~~ viewDidLoad") + } + } + } + } + + func registerAppInbox() { + CleverTap.sharedInstance()?.registerInboxUpdatedBlock({ + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + }) + } + + func initializeAppInbox() { + CleverTap.sharedInstance()?.initializeInbox(callback: ({ (success) in + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + })) + } +} + +func buttonAction(index: Int) { + switch(index) { + case 0: + recordUserProfile() + break; + case 1: + recordUserProfileWithProperties() + break; + case 2: + recordUserEventWithoutProperties() + break; + case 3: + recordUserEventWithProperties() + break; + case 4: + recordUserChargedEvent() + break; + case 5: + recordUserEventforAdditionalInstance() + break; + case 8: + incrementUserProfileProperty() + break; + case 9: + decrementUserProfileProperty() + break; + case 10: + activateCustomDomain() + break; + case 11: + createLocalHalfInterstitialPushPrimer() + break; + default: + break; + } +} + +func recordUserProfile() { + // each of the below mentioned fields are optional + // if set, these populate demographic information in the Dashboard + let dob = NSDateComponents() + dob.day = 24 + dob.month = 5 + dob.year = 1993 + let d = NSCalendar.current.date(from: dob as DateComponents) + let profile: Dictionary = [ + "Name": "Nishant" as AnyObject, // String + "Identity": 6196032 as AnyObject, // String or number + "Email": "testnishant@gmail.com" as AnyObject, // Email address of the user + "Phone": "+1415501234" as AnyObject, // Phone (with the country code, starting with +) + "Gender": "M" as AnyObject, // Can be either M or F + "Employed": "Y" as AnyObject, // Can be either Y or N + "Education": "Graduate" as AnyObject, // Can be either School, College or Graduate + "Married": "Y" as AnyObject, // Can be either Y or N + "DOB": d! as AnyObject, // Date of Birth. An NSDate object + "Age": 26 as AnyObject, // Not required if DOB is set + "Tz":"Asia/Kolkata" as AnyObject, //an abbreviation such as "PST", a full name such as "America/Los_Angeles", + //or a custom ID such as "GMT-8:00" + "Photo": "www.foobar.com/image.jpeg" as AnyObject, // URL to the Image + + // optional fields. controls whether the user will be sent email, push etc. + "MSG-email": false as AnyObject, // Disable email notifications + "MSG-push": true as AnyObject, // Enable push notifications + "MSG-sms": false as AnyObject, // Disable SMS notifications + + //custom fields + "score": 15 as AnyObject, + "cost": 10.5 as AnyObject + ] + CleverTap.sharedInstance()?.profilePush(profile) +} + +func recordUserProfileWithProperties() { + /// To set a multi-value property + CleverTap.sharedInstance()?.profileSetMultiValues(["bag", "shoes"], forKey: "myStuff") + + /// To add an additional value(s) to a multi-value property + // CleverTap.sharedInstance()?.profileAddMultiValue("coat", forKey: "myStuff") + // CleverTap.sharedInstance()?.profileAddMultiValues(["socks", "scarf"], forKey: "myStuff") + + /// To remove a value(s) from a multi-value property + // CleverTap.sharedInstance()?.profileRemoveMultiValue("bag", forKey: "myStuff") + // CleverTap.sharedInstance()?.profileRemoveMultiValues(["shoes", "coat"], forKey: "myStuff") + + /// To remove the value of a property (scalar or multi-value) + // CleverTap.sharedInstance()?.profileRemoveValue(forKey: "myStuff") +} + +func recordUserEventWithoutProperties() { + // event without properties + CleverTap.sharedInstance()?.recordEvent("Product viewed") +} + +func recordUserEventWithProperties() { + // event with properties + let props = [ + "Product name": "Casio Chronograph Watch", + "Category": "Mens Accessories", + "Price": 59.99, + "Date": NSDate() + ] as [String : Any] + CleverTap.sharedInstance()?.recordEvent("Product viewed", withProps: props) +} + +func recordUserChargedEvent() { + //charged event + let chargeDetails = [ + "Amount": 300, + "Payment mode": "Credit Card", + "Charged ID": 24052013 + ] as [String : Any] + + let item1 = [ + "Category": "books", + "Book name": "The Millionaire next door", + "Quantity": 1 + ] as [String : Any] + + let item2 = [ + "Category": "books", + "Book name": "Achieving inner zen", + "Quantity": 1 + ] as [String : Any] + + let item3 = [ + "Category": "books", + "Book name": "Chuck it, let's do it", + "Quantity": 5 + ] as [String : Any] + + CleverTap.sharedInstance()?.recordChargedEvent(withDetails: chargeDetails, andItems: [item1, item2, item3]) +} + +func recordUserEventforAdditionalInstance() { + cleverTapAdditionalInstance.recordEvent("TestCT1WProps", withProps: ["one": NSNumber.init(integerLiteral: 1)]) + cleverTapAdditionalInstance.profileSetMultiValues(["a"], forKey: "letters") +} + +func incrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileIncrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func decrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileDecrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func activateCustomDomain() { +// let storyBoard = UIStoryboard.init(name: "Main", bundle: nil) +// let customDomainVC = storyBoard.instantiateViewController(withIdentifier: "CustomDomainVC") +// self.navigationController?.pushViewController(customDomainVC, animated: true) +} + +func createLocalHalfInterstitialPushPrimer() { + CleverTap.sharedInstance()?.getNotificationPermissionStatus(completionHandler: { status in + if status == .notDetermined || status == .denied { + let localInAppBuilder = CTLocalInApp(inAppType: CTLocalInAppType.HALF_INTERSTITIAL, titleText: "Get Notified", messageText: "Please enable notifications on your device to use Push Notifications.", followDeviceOrientation: true, positiveBtnText: "Allow", negativeBtnText: "Cancel") + localInAppBuilder.setFallbackToSettings(true) + localInAppBuilder.setImageUrl("https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png") + CleverTap.sharedInstance()?.promptPushPrimer(localInAppBuilder.getSettings()) + } else { + print("Push Persmission is already enabled.") + } + }) +} + +#if DEBUG +struct CTHomeScreen_Previews: PreviewProvider { + static var previews: some View { + CTHomeScreen() + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift b/SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift new file mode 100644 index 00000000..ab876306 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTViewModifier.swift @@ -0,0 +1,48 @@ +#if canImport(SwiftUI) +import SwiftUI +import CleverTapSDK + +@available(iOS 13, *) +internal struct CTViewModifier: ViewModifier { + @State private var viewDidLoad = false + let screenName: String + + func body(content: Content) -> some View { + content.onAppear { + if viewDidLoad == false { + // `viewDidLoad` eqivalent in SwiftUI + viewDidLoad = true + if (screenName == "Home Screen") { + self.registerAppInbox() + self.initializeAppInbox() + } + // Record any CleverTap events here. + CleverTap.sharedInstance()?.recordEvent(screenName) + } + } + } + + func registerAppInbox() { + CleverTap.sharedInstance()?.registerInboxUpdatedBlock({ + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + }) + } + + func initializeAppInbox() { + CleverTap.sharedInstance()?.initializeInbox(callback: ({ (success) in + let messageCount = CleverTap.sharedInstance()?.getInboxMessageCount() + let unreadCount = CleverTap.sharedInstance()?.getInboxMessageUnreadCount() + print("Inbox Message:\(String(describing: messageCount))/\(String(describing: unreadCount)) unread") + })) + } +} + +@available(iOS 13, *) +public extension View { + func recordScreenView(screenName: String) -> some View { + self.modifier(CTViewModifier(screenName: screenName)) + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift b/SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift new file mode 100644 index 00000000..e65aee13 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/CTWebViewRepresentable.swift @@ -0,0 +1,41 @@ +import SwiftUI +import WebKit +import CleverTapSDK + +struct CTWebViewRepresentable: UIViewControllerRepresentable { + typealias UIViewControllerType = CTWebviewVC + func makeUIViewController(context: Context) -> CTWebviewVC { + let webViewVC = CTWebviewVC() + return webViewVC + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } +} + +class CTWebviewVC: UIViewController { + var webView: WKWebView! + + override func viewDidLoad() { + super.viewDidLoad() + addWebview() + } + + func addWebview() { + let ctInterface: CleverTapJSInterface = CleverTapJSInterface(config: nil) + self.webView = WKWebView (frame: self.view.frame) + self.webView.configuration.userContentController.add(ctInterface, name: "clevertap") + self.webView.loadHTMLString(self.htmlStringFromFile(with: "sampleHTMLCode"), baseURL: nil) + self.view.addSubview(self.webView) + } + + private func htmlStringFromFile(with name: String) -> String { + let path = Bundle.main.path(forResource: name, ofType: "html") + if let result = try? String(contentsOfFile: path!, encoding: String.Encoding.utf8) { + return result + } + return "" + } +} + diff --git a/SwiftUIStarter/SwiftUIStarter/ContentView.swift b/SwiftUIStarter/SwiftUIStarter/ContentView.swift new file mode 100644 index 00000000..fffcf854 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/ContentView.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct ContentView: View { + var body: some View { + HomeScreen() + } +} + +#if DEBUG +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/HomeScreen.swift b/SwiftUIStarter/SwiftUIStarter/HomeScreen.swift new file mode 100644 index 00000000..0bba97ed --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/HomeScreen.swift @@ -0,0 +1,216 @@ +import SwiftUI +import CleverTapSDK + +var cleverTapAdditionalInstance: CleverTap = { + let ctConfig = CleverTapInstanceConfig.init(accountId: "ZWW-WWW-WWRZ", accountToken: "000-001") + return CleverTap.instance(with: ctConfig) + }() + +struct HomeScreen: View { + let eventList = [ "Record User Profile", + "Record User Profile with Properties", + "Record User Event called Product Viewed", + "Record User Event with Properties", + "Record User Charged Event", + "Record User Event to an Additional Instance", + "Show App Inbox", + "Analytics in a WebView", + "Increment User Profile Property", + "Decrement User Profile Property", + "Local Half Interstitial Push Primer" + ] + + var body: some View { + NavigationView { + VStack { + Image("logo") + .scaledToFit() + .frame(height: 72) + List { + ForEach(0 ..< eventList.count, id: \.self) { index in + HStack { + Button("\(eventList[index])") { + buttonAction(index: index) + } + Spacer() + if (eventList[index] == "Show App Inbox") { + // Show App Inbox controller + NavigationLink(destination: CTAppInboxRepresentable().recordScreenView(screenName: "CT App Inbox")) { + } + } else if (eventList[index] == "Analytics in a WebView") { + // Show Web View + NavigationLink(destination: CTWebViewRepresentable().recordScreenView(screenName: "CT Web View")) { + } + } + } + } + } + } + .recordScreenView(screenName: "Home Screen") + } + } +} + +func buttonAction(index: Int) { + switch(index) { + case 0: + recordUserProfile() + break; + case 1: + recordUserProfileWithProperties() + break; + case 2: + recordUserEventWithoutProperties() + break; + case 3: + recordUserEventWithProperties() + break; + case 4: + recordUserChargedEvent() + break; + case 5: + recordUserEventforAdditionalInstance() + break; + case 8: + incrementUserProfileProperty() + break; + case 9: + decrementUserProfileProperty() + break; + case 10: + createLocalHalfInterstitialPushPrimer() + break; + default: + break; + } +} + +func recordUserProfile() { + // each of the below mentioned fields are optional + // if set, these populate demographic information in the Dashboard + let dob = NSDateComponents() + dob.day = 24 + dob.month = 5 + dob.year = 1993 + let d = NSCalendar.current.date(from: dob as DateComponents) + let profile: Dictionary = [ + "Name": "Nishant" as AnyObject, // String + "Identity": 6196032 as AnyObject, // String or number + "Email": "testnishant@gmail.com" as AnyObject, // Email address of the user + "Phone": "+1415501234" as AnyObject, // Phone (with the country code, starting with +) + "Gender": "M" as AnyObject, // Can be either M or F + "Employed": "Y" as AnyObject, // Can be either Y or N + "Education": "Graduate" as AnyObject, // Can be either School, College or Graduate + "Married": "Y" as AnyObject, // Can be either Y or N + "DOB": d! as AnyObject, // Date of Birth. An NSDate object + "Age": 26 as AnyObject, // Not required if DOB is set + "Tz":"Asia/Kolkata" as AnyObject, //an abbreviation such as "PST", a full name such as "America/Los_Angeles", + //or a custom ID such as "GMT-8:00" + "Photo": "www.foobar.com/image.jpeg" as AnyObject, // URL to the Image + + // optional fields. controls whether the user will be sent email, push etc. + "MSG-email": false as AnyObject, // Disable email notifications + "MSG-push": true as AnyObject, // Enable push notifications + "MSG-sms": false as AnyObject, // Disable SMS notifications + + //custom fields + "score": 15 as AnyObject, + "cost": 10.5 as AnyObject + ] + CleverTap.sharedInstance()?.profilePush(profile) +} + +func recordUserProfileWithProperties() { + /// To set a multi-value property + CleverTap.sharedInstance()?.profileSetMultiValues(["bag", "shoes"], forKey: "myStuff") + + /// To add an additional value(s) to a multi-value property + CleverTap.sharedInstance()?.profileAddMultiValue("coat", forKey: "myStuff") + CleverTap.sharedInstance()?.profileAddMultiValues(["socks", "scarf"], forKey: "myStuff") + + /// To remove a value(s) from a multi-value property + CleverTap.sharedInstance()?.profileRemoveMultiValue("bag", forKey: "myStuff") + CleverTap.sharedInstance()?.profileRemoveMultiValues(["shoes", "coat"], forKey: "myStuff") + + /// To remove the value of a property (scalar or multi-value) + CleverTap.sharedInstance()?.profileRemoveValue(forKey: "myStuff") +} + +func recordUserEventWithoutProperties() { + // event without properties + CleverTap.sharedInstance()?.recordEvent("Product viewed") +} + +func recordUserEventWithProperties() { + // event with properties + let props = [ + "Product name": "Casio Chronograph Watch", + "Category": "Mens Accessories", + "Price": 59.99, + "Date": NSDate() + ] as [String : Any] + CleverTap.sharedInstance()?.recordEvent("Product viewed", withProps: props) +} + +func recordUserChargedEvent() { + //charged event + let chargeDetails = [ + "Amount": 300, + "Payment mode": "Credit Card", + "Charged ID": 24052013 + ] as [String : Any] + + let item1 = [ + "Category": "books", + "Book name": "The Millionaire next door", + "Quantity": 1 + ] as [String : Any] + + let item2 = [ + "Category": "books", + "Book name": "Achieving inner zen", + "Quantity": 1 + ] as [String : Any] + + let item3 = [ + "Category": "books", + "Book name": "Chuck it, let's do it", + "Quantity": 5 + ] as [String : Any] + + CleverTap.sharedInstance()?.recordChargedEvent(withDetails: chargeDetails, andItems: [item1, item2, item3]) +} + +func recordUserEventforAdditionalInstance() { + cleverTapAdditionalInstance.recordEvent("TestCT1WProps", withProps: ["one": NSNumber.init(integerLiteral: 1)]) + cleverTapAdditionalInstance.profileSetMultiValues(["a"], forKey: "letters") +} + +func incrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileIncrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func decrementUserProfileProperty() { + CleverTap.sharedInstance()?.profileDecrementValue(by: NSNumber(value: 1), forKey: "score") +} + +func createLocalHalfInterstitialPushPrimer() { + CleverTap.sharedInstance()?.getNotificationPermissionStatus(completionHandler: { status in + if status == .notDetermined || status == .denied { + let localInAppBuilder = CTLocalInApp(inAppType: CTLocalInAppType.HALF_INTERSTITIAL, titleText: "Get Notified", messageText: "Please enable notifications on your device to use Push Notifications.", followDeviceOrientation: true, positiveBtnText: "Allow", negativeBtnText: "Cancel") + localInAppBuilder.setFallbackToSettings(true) + localInAppBuilder.setImageUrl("https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png") + CleverTap.sharedInstance()?.promptPushPrimer(localInAppBuilder.getSettings()) + } else { + print("Push Persmission is already enabled.") + } + }) +} + +#if DEBUG +struct CTHomeScreen_Previews: PreviewProvider { + static var previews: some View { + HomeScreen() + } +} +#endif diff --git a/SwiftUIStarter/SwiftUIStarter/Info.plist b/SwiftUIStarter/SwiftUIStarter/Info.plist new file mode 100644 index 00000000..51ef168b --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Info.plist @@ -0,0 +1,16 @@ + + + + + CleverTapAccountID + W9R-486-4W5Z + CleverTapToken + 6b4-2c0 + UIBackgroundModes + + fetch + processing + remote-notification + + + diff --git a/SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json b/SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarter.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift new file mode 100644 index 00000000..8d38ad53 --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/SwiftUIStarterApp.swift @@ -0,0 +1,12 @@ +import SwiftUI + +@main +struct SwiftUIStarterApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html b/SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html new file mode 100644 index 00000000..cea72b9f --- /dev/null +++ b/SwiftUIStarter/SwiftUIStarter/sampleHTMLCode.html @@ -0,0 +1,158 @@ + +
+ + + + + +

CleverTap Webview



+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ + + + + diff --git a/docs/Encryption.md b/docs/Encryption.md new file mode 100644 index 00000000..b2d61af4 --- /dev/null +++ b/docs/Encryption.md @@ -0,0 +1,29 @@ +## Encryption of PII data + +PII data is stored across the SDK and could be sensitive information. +From CleverTap iOS SDK v5.2.0 onwards, you can enable encryption for PII data wiz. **Email, Identity, Name and Phone**. + +Currently 2 levels of encryption are supported i.e None(0) and Medium(1). Encryption level is None by default. +**None** - All stored data is in plaintext +**Medium** - PII data is encrypted completely. + +### Default Instance: +The only way to set encryption level for default instance is from the `info.plist`. Add the `CleverTapEncryptionLevel` String key to info.plist file where value `1` means Medium and `0` means None. Encryption Level will be None if any other value is provided. + +### Additional Instance: +Different instances can have different encryption levels. To set an encryption level for an additional instance. +```objc +// Objective-C + +CleverTapInstanceConfig *ctConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:@"ADDITIONAL_CLEVERTAP_ACCOUNT_ID" accountToken:@"ADDITIONAL_CLEVERTAP_ACCOUNT_TOKEN"]; +[ctConfig setEncryptionLevel:CleverTapEncryptionMedium]; +CleverTap *additionalCleverTapInstance = [CleverTap instanceWithConfig:ctConfig]; +``` + +```swift +// Swift + +let ctConfig = CleverTapInstanceConfig.init(accountId: "ADDITIONAL_CLEVERTAP_ACCOUNT_ID", accountToken: "ADDITIONAL_CLEVERTAP_ACCOUNT_TOKEN") +ctConfig.encryptionLevel = CleverTapEncryptionLevel.medium +let cleverTapAdditionalInstance = CleverTap.instance(with: ctConfig) +``` \ No newline at end of file diff --git a/docs/SwiftUI.md b/docs/SwiftUI.md new file mode 100644 index 00000000..08de48ba --- /dev/null +++ b/docs/SwiftUI.md @@ -0,0 +1,89 @@ +## Integration in SwiftUI App +CleverTap iOS SDK can be integrated in SwiftUI sample app. + +#### AppDelegate in SwiftUI +SwiftUI provides a way to use AppDelegate within SwiftUI life cycle by using `@UIApplicationDelegateAdaptor`. Create a file e.g. `AppDelegate.swift` then create a class of AppDelegate and attach it with struct main entry point by `@UIApplicationDelegateAdaptor` property wrapper, Refer sample app for more details. + +```swift +import UserNotifications +import CleverTapSDK + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + registerForPush() + CleverTap.setDebugLevel(2) + CleverTap.autoIntegrate() + CleverTap.sharedInstance()?.enableDeviceNetworkInfoReporting(true) + return true + } + + func registerForPush() { + // Register for Push notifications + UNUserNotificationCenter.current().delegate = self + // request Permissions + UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .badge, .alert], completionHandler: {granted, error in + if granted { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + }) + } +} +``` + +#### App Inbox in SwiftUI +App Inbox controller can be added using `UIViewControllerRepresentable` and its callback methods can be used using `makeCoordinator` method. Refer example app for more details. + +```swift +struct CTAppInboxRepresentable: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> CleverTapInboxViewController { + let style = CleverTapInboxStyleConfig() + let inboxVC: CleverTapInboxViewController = (CleverTap.sharedInstance()?.newInboxViewController(with: style, andDelegate: context.coordinator))! + return inboxVC + } + + func updateUIViewController(_ uiViewController: CleverTapInboxViewController, context: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } + + func makeCoordinator() -> CTCallbackCoordinator { + // Callback class + } +} +``` + +#### Track Screen Views in SwiftUI +There is no direct replacement for `viewDidLoad()` method in SwiftUI, but we can acheive same behaviour using `onAppear` modifier. Refer example for more details: + +```swift +#if canImport(SwiftUI) +import SwiftUI +import CleverTapSDK + +@available(iOS 13, *) +internal struct CTViewModifier: ViewModifier { + @State private var viewDidLoad = false + let screenName: String + + func body(content: Content) -> some View { + content.onAppear { + if viewDidLoad == false { + // `viewDidLoad` eqivalent in SwiftUI + viewDidLoad = true + // Record any CleverTap events here. + CleverTap.sharedInstance()?.recordEvent(screenName) + } + } + } +} + +@available(iOS 13, *) +public extension View { + func recordScreenView(screenName: String) -> some View { + self.modifier(CTViewModifier(screenName: screenName)) + } +} +#endif + +``` \ No newline at end of file diff --git a/sdk-version.txt b/sdk-version.txt index 1b47e8f3..91ff5727 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -5.1.2 \ No newline at end of file +5.2.0