diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..ccae20932 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @frankus @LuqKhan diff --git a/Apptentive/Apptentive.xcodeproj/project.pbxproj b/Apptentive/Apptentive.xcodeproj/project.pbxproj index 0df739005..44c5d1832 100644 --- a/Apptentive/Apptentive.xcodeproj/project.pbxproj +++ b/Apptentive/Apptentive.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 0116D5271E92F516001DA5CF /* ApptentiveMessageStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 0116D5251E92F516001DA5CF /* ApptentiveMessageStore.h */; }; 0116D5281E92F516001DA5CF /* ApptentiveMessageStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 0116D5261E92F516001DA5CF /* ApptentiveMessageStore.m */; }; 01201AD31FC637BE00EB3593 /* CodePointAndInteractionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 01201AD21FC637BD00EB3593 /* CodePointAndInteractionTests.m */; }; + 01211CEB268146E7009F0364 /* ApptentiveRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = 01211CE9268146E7009F0364 /* ApptentiveRandom.h */; }; + 01211CEC268146E7009F0364 /* ApptentiveRandom.m in Sources */ = {isa = PBXBuildFile; fileRef = 01211CEA268146E7009F0364 /* ApptentiveRandom.m */; }; 01216C501EBBB53E0062BD0D /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01216C4F1EBBB53E0062BD0D /* RequestTests.swift */; }; 0123005F20531698000EC3C3 /* ClauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0123005E20531698000EC3C3 /* ClauseTests.m */; }; 012B96C325118349008A56CC /* Apptentive.h in Headers */ = {isa = PBXBuildFile; fileRef = 012B96C22511832D008A56CC /* Apptentive.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -371,6 +373,8 @@ 01B9055C2411C83600E2A663 /* ApptentiveArchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = 01B9055A2411C83600E2A663 /* ApptentiveArchiver.m */; }; 01B9057E2416C52B00E2A663 /* ApptentiveURLOpener.h in Headers */ = {isa = PBXBuildFile; fileRef = 01B9057C2416C52B00E2A663 /* ApptentiveURLOpener.h */; }; 01B9057F2416C52B00E2A663 /* ApptentiveURLOpener.m in Sources */ = {isa = PBXBuildFile; fileRef = 01B9057D2416C52B00E2A663 /* ApptentiveURLOpener.m */; }; + 01D43A2826B3688200963752 /* UIWindow+Apptentive.h in Headers */ = {isa = PBXBuildFile; fileRef = 01D43A2626B3688200963752 /* UIWindow+Apptentive.h */; }; + 01D43A2926B3688200963752 /* UIWindow+Apptentive.m in Sources */ = {isa = PBXBuildFile; fileRef = 01D43A2726B3688200963752 /* UIWindow+Apptentive.m */; }; 01E04F9E1E819CD300D7E849 /* ApptentiveMessageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 01E04F9C1E819CD300D7E849 /* ApptentiveMessageManager.h */; }; 01E04F9F1E819CD300D7E849 /* ApptentiveMessageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 01E04F9D1E819CD300D7E849 /* ApptentiveMessageManager.m */; }; 01E8E2361E6E096D00786738 /* ApptentiveInteractionAppleRatingDialogController.h in Headers */ = {isa = PBXBuildFile; fileRef = 01E8E2341E6E096D00786738 /* ApptentiveInteractionAppleRatingDialogController.h */; }; @@ -461,6 +465,8 @@ 0116D5251E92F516001DA5CF /* ApptentiveMessageStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApptentiveMessageStore.h; sourceTree = ""; }; 0116D5261E92F516001DA5CF /* ApptentiveMessageStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApptentiveMessageStore.m; sourceTree = ""; }; 01201AD21FC637BD00EB3593 /* CodePointAndInteractionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePointAndInteractionTests.m; sourceTree = ""; }; + 01211CE9268146E7009F0364 /* ApptentiveRandom.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ApptentiveRandom.h; sourceTree = ""; }; + 01211CEA268146E7009F0364 /* ApptentiveRandom.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ApptentiveRandom.m; sourceTree = ""; }; 01216C4F1EBBB53E0062BD0D /* RequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = ""; }; 0123005E20531698000EC3C3 /* ClauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ClauseTests.m; sourceTree = ""; }; 012B96C22511832D008A56CC /* Apptentive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Apptentive.h; sourceTree = ""; }; @@ -842,6 +848,8 @@ 01B9057C2416C52B00E2A663 /* ApptentiveURLOpener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ApptentiveURLOpener.h; sourceTree = ""; }; 01B9057D2416C52B00E2A663 /* ApptentiveURLOpener.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ApptentiveURLOpener.m; sourceTree = ""; }; 01BEA06D2511E97500DF982D /* Apptentive.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Apptentive.modulemap; sourceTree = ""; }; + 01D43A2626B3688200963752 /* UIWindow+Apptentive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIWindow+Apptentive.h"; sourceTree = ""; }; + 01D43A2726B3688200963752 /* UIWindow+Apptentive.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIWindow+Apptentive.m"; sourceTree = ""; }; 01E04F9C1E819CD300D7E849 /* ApptentiveMessageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApptentiveMessageManager.h; sourceTree = ""; }; 01E04F9D1E819CD300D7E849 /* ApptentiveMessageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApptentiveMessageManager.m; sourceTree = ""; }; 01E8E2341E6E096D00786738 /* ApptentiveInteractionAppleRatingDialogController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApptentiveInteractionAppleRatingDialogController.h; sourceTree = ""; }; @@ -1195,6 +1203,8 @@ 01AA89B61F18177E00FB59AB /* ApptentiveAppInstall.h */, 01AA89B71F18177E00FB59AB /* ApptentiveAppInstall.m */, 0178C5681FC4A57900DABF39 /* Targeting */, + 01211CE9268146E7009F0364 /* ApptentiveRandom.h */, + 01211CEA268146E7009F0364 /* ApptentiveRandom.m */, ); path = Model; sourceTree = ""; @@ -1522,6 +1532,8 @@ 01B9055A2411C83600E2A663 /* ApptentiveArchiver.m */, 01B9057C2416C52B00E2A663 /* ApptentiveURLOpener.h */, 01B9057D2416C52B00E2A663 /* ApptentiveURLOpener.m */, + 01D43A2626B3688200963752 /* UIWindow+Apptentive.h */, + 01D43A2726B3688200963752 /* UIWindow+Apptentive.m */, ); path = Misc; sourceTree = ""; @@ -1784,6 +1796,7 @@ 01A2D1031E490A9700C2103A /* ApptentivePassThroughWindow.h in Headers */, 01A2D1391E490A9700C2103A /* ApptentivePerson.h in Headers */, 01A2D13F1E490A9700C2103A /* ApptentiveState.h in Headers */, + 01211CEB268146E7009F0364 /* ApptentiveRandom.h in Headers */, EFC308D71ECA6692006B6D36 /* ApptentiveLegacyConversationRequest.h in Headers */, EF4EAB91203F8A57003318C9 /* ApptentiveDispatchTask+Internal.h in Headers */, 01A2D1781E490A9700C2103A /* ApptentiveAboutViewController.h in Headers */, @@ -1831,6 +1844,7 @@ 012ED9292072F33F003D87F3 /* ApptentiveRetryPolicy.h in Headers */, EF4EABAB2040C0CF003318C9 /* ApptentiveLogMonitorSession.h in Headers */, EF9009901F8D370400DC5B56 /* ApptentiveLogMonitor.h in Headers */, + 01D43A2826B3688200963752 /* UIWindow+Apptentive.h in Headers */, EF8EE3AB1EBBA5D00033E7A1 /* ApptentiveJWT.h in Headers */, 01A2D1D81E490A9700C2103A /* ApptentiveDataManager.h in Headers */, 01A2D0FF1E490A9700C2103A /* ApptentiveNetworkImageIconView.h in Headers */, @@ -1983,12 +1997,13 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; - LastUpgradeCheck = 1200; + LastUpgradeCheck = 1250; ORGANIZATIONNAME = "Apptentive, Inc."; TargetAttributes = { 01A2CF901E49062700C2103A = { CreatedOnToolsVersion = 8.2.1; DevelopmentTeam = 86WML2UN43; + LastSwiftMigration = 1250; ProvisioningStyle = Automatic; }; 01A2CF991E49062800C2103A = { @@ -2284,6 +2299,7 @@ EFC308D31EC52915006B6D36 /* ApptentiveStopWatch.m in Sources */, 01A2D1261E490A9700C2103A /* ApptentiveDevice.m in Sources */, 010FE347203E4C810021C246 /* ApptentiveIndentPrinter.m in Sources */, + 01D43A2926B3688200963752 /* UIWindow+Apptentive.m in Sources */, 01A2D1AB1E490A9700C2103A /* ApptentiveAppConfiguration.m in Sources */, 01A2D1D11E490A9700C2103A /* ApptentiveSerialRequest.m in Sources */, 0140D5FB1E833103007B5130 /* ApptentiveMessage.m in Sources */, @@ -2294,6 +2310,7 @@ 01A2D1E91E490A9700C2103A /* ApptentiveSurveyViewModel.m in Sources */, 014508A11EAAB26D003326E7 /* ApptentiveConversationRequest.m in Sources */, 018FAFE41FC4AC41007C52FE /* ApptentiveOrClause.m in Sources */, + 01211CEC268146E7009F0364 /* ApptentiveRandom.m in Sources */, 01A2D1861E490A9700C2103A /* ApptentiveAttachmentCell.m in Sources */, 01A2D1FD1E490A9700C2103A /* ApptentiveSurveySingleLineCell.m in Sources */, EF4EAB96203F8B99003318C9 /* ApptentiveLogFileWriteTask.m in Sources */, @@ -2419,7 +2436,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2477,7 +2494,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2505,11 +2522,12 @@ 01A2CFA61E49062800C2103A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 86WML2UN43; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 48; + DYLIB_CURRENT_VERSION = 49; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Apptentive/Misc/ApptentiveConnect-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = "APPTENTIVE_DEBUG=1"; @@ -2520,17 +2538,20 @@ PRODUCT_BUNDLE_IDENTIFIER = com.apptentive.Apptentive; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; 01A2CFA71E49062800C2103A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 86WML2UN43; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 48; + DYLIB_CURRENT_VERSION = 49; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Apptentive/Misc/ApptentiveConnect-Prefix.pch"; INFOPLIST_FILE = Apptentive/Info.plist; @@ -2540,6 +2561,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.apptentive.Apptentive; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Apptentive/Apptentive.xcodeproj/xcshareddata/xcschemes/Apptentive.xcscheme b/Apptentive/Apptentive.xcodeproj/xcshareddata/xcschemes/Apptentive.xcscheme index f1e85f8c5..816b41894 100644 --- a/Apptentive/Apptentive.xcodeproj/xcshareddata/xcschemes/Apptentive.xcscheme +++ b/Apptentive/Apptentive.xcodeproj/xcshareddata/xcschemes/Apptentive.xcscheme @@ -1,6 +1,6 @@ -#import #import #include #import diff --git a/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagement.m b/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagement.m index 463557759..439ebd727 100644 --- a/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagement.m +++ b/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagement.m @@ -54,10 +54,10 @@ - (instancetype)init { - (nullable instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { - NSSet *classes = [NSSet setWithArray:@[[NSMutableDictionary class], [ApptentiveCount class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSMutableDictionary class], [ApptentiveCount class], [NSString class]]]; - _mutableInteractions = [coder decodeObjectOfClasses:classes forKey:InteractionsKey]; - _mutableCodePoints = [coder decodeObjectOfClasses:classes forKey:CodePointsKey]; + _mutableInteractions = [coder decodeObjectOfClasses:allowedClasses forKey:InteractionsKey]; + _mutableCodePoints = [coder decodeObjectOfClasses:allowedClasses forKey:CodePointsKey]; if ([coder containsValueForKey:VersionKey]) { _version = [coder decodeIntegerForKey:VersionKey]; } else { diff --git a/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagementManifest.m b/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagementManifest.m index a931f93b1..5ba348dce 100644 --- a/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagementManifest.m +++ b/Apptentive/Apptentive/Engagement/Model/ApptentiveEngagementManifest.m @@ -102,7 +102,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)coder { if (self) { _targets = [coder decodeObjectOfClass:[ApptentiveTargets class] forKey:TargetsKey]; - NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [ApptentiveInteraction class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [ApptentiveInteraction class], [NSString class]]]; _interactions = [coder decodeObjectOfClasses:allowedClasses forKey:InteractionsKey]; _expiry = [coder decodeObjectOfClass:[NSDate class] forKey:ExpiryKey]; } diff --git a/Apptentive/Apptentive/Engagement/Model/ApptentiveInteraction.m b/Apptentive/Apptentive/Engagement/Model/ApptentiveInteraction.m index 2ca7a8981..88a32b0d3 100644 --- a/Apptentive/Apptentive/Engagement/Model/ApptentiveInteraction.m +++ b/Apptentive/Apptentive/Engagement/Model/ApptentiveInteraction.m @@ -68,9 +68,9 @@ - (nullable instancetype)initWithCoder:(NSCoder *)coder { self.identifier = [coder decodeObjectOfClass:[NSString class] forKey:@"identifier"]; self.priority = [coder decodeIntegerForKey:@"priority"]; self.type = [coder decodeObjectOfClass:[NSString class] forKey:@"type"]; - NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSString class], [NSArray class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSString class], [NSArray class], [NSNumber class]]]; self.configuration = [coder decodeObjectOfClasses:allowedClasses forKey:@"configuration"]; - self.version = [coder decodeObjectOfClass:[NSString class] forKey:@"version"]; + self.version = [coder decodeObjectOfClass:[NSNumber class] forKey:@"version"]; } return self; } diff --git a/Apptentive/Apptentive/Engagement/Model/ApptentiveRandom.h b/Apptentive/Apptentive/Engagement/Model/ApptentiveRandom.h new file mode 100644 index 000000000..1c161d9a9 --- /dev/null +++ b/Apptentive/Apptentive/Engagement/Model/ApptentiveRandom.h @@ -0,0 +1,19 @@ +// +// ApptentiveRandom.h +// Apptentive +// +// Created by Frank Schmitt on 6/21/21. +// Copyright © 2021 Apptentive, Inc. All rights reserved. +// + +#import "ApptentiveState.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ApptentiveRandom : ApptentiveState + +@property (readonly, assign) double newRandomValue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Apptentive/Apptentive/Engagement/Model/ApptentiveRandom.m b/Apptentive/Apptentive/Engagement/Model/ApptentiveRandom.m new file mode 100644 index 000000000..b7ec8cf59 --- /dev/null +++ b/Apptentive/Apptentive/Engagement/Model/ApptentiveRandom.m @@ -0,0 +1,110 @@ +// +// ApptentiveRandom.m +// Apptentive +// +// Created by Frank Schmitt on 6/21/21. +// Copyright © 2021 Apptentive, Inc. All rights reserved. +// + +#import "ApptentiveRandom.h" + +static NSString *const RandomValuesKey = @"random_values"; + +@interface ApptentiveRandom () + +@property (strong, nonatomic) NSMutableDictionary *randomValues; + +@end + +@implementation ApptentiveRandom + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _randomValues = [NSMutableDictionary dictionary]; + } + return self; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + _randomValues = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:RandomValuesKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:self.randomValues forKey:RandomValuesKey]; +} + +#pragma mark - Targeting State + +- (NSObject *)valueForFieldWithPath:(NSString *)path { + NSArray *parts = [path componentsSeparatedByString:@"/"]; + + if (parts.count > 0 && parts.count <= 2 && [parts.lastObject isEqualToString:@"percent"]) { + NSString *key = parts.count == 2 ? parts[0] : nil; + + return @([self randomPercentForKey:key]); + } else { + ApptentiveLogError(@"Unrecognized field name “%@”", path); + return nil; + } +} + +- (NSString *)descriptionForFieldWithPath:(NSString *)path { + NSArray *parts = [path componentsSeparatedByString:@"/"]; + + if (parts.count > 0 && parts.count <= 2 && [parts.lastObject isEqualToString:@"percent"]) { + NSString *key = parts.count == 2 ? parts[0] : nil; + + return [self descriptionOfPercentForKey:key]; + } else { + return [NSString stringWithFormat:@"Unrecognized engagement field %@", path]; + } +} + +#pragma mark - Private + +- (NSString *)descriptionOfPercentForKey:(nullable NSString *)key { + if (key == nil) { + return @"Random percentage"; + } else if (self.randomValues[key] == nil) { + return [NSString stringWithFormat:@"Random percentage that will be saved for key “%@”", key]; + } else { + return [NSString stringWithFormat:@"Random percentage that was saved for key “%@”", key]; + } +} + +- (double)randomPercentForKey:(nullable NSString *)key { + return [self randomValueForKey:key] * 100.0; +} + +- (double)randomValueForKey:(nullable NSString *)key { + if (key == nil) { + return self.newRandomValue; + } else { + if (self.randomValues[key] == nil) { + self.randomValues[key] = @(self.newRandomValue); + } + + return self.randomValues[key].doubleValue; + } +} + +- (double)newRandomValue { +#if APPTENTIVE_DEBUG + ApptentiveLogInfo(@"Note: all random sampling percentages will be assigned/saved as 50% when running debug build."); + return 0.5; +#else + return (double)arc4random() / UINT32_MAX; +#endif +} + +@end diff --git a/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveComparisonClause.m b/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveComparisonClause.m index 7b8daba8c..3234bd7b1 100644 --- a/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveComparisonClause.m +++ b/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveComparisonClause.m @@ -148,7 +148,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)coder self = [super init]; if (self) { _field = [coder decodeObjectOfClass:[NSString class] forKey:FieldKey]; - NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSString class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSString class], [NSNumber class]]]; _comparisons = [coder decodeObjectOfClasses:allowedClasses forKey:ComparisonsKey]; } return self; diff --git a/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveTargets.m b/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveTargets.m index ec864016f..0ec83b39a 100644 --- a/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveTargets.m +++ b/Apptentive/Apptentive/Engagement/Model/Targeting/ApptentiveTargets.m @@ -49,7 +49,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)coder { self = [super init]; if (self) { - NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [ApptentiveInvocations class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [ApptentiveInvocations class], [NSString class]]]; _invocations = [coder decodeObjectOfClasses:allowedClasses forKey:InvocationsKey]; } return self; diff --git a/Apptentive/Apptentive/Info.plist b/Apptentive/Apptentive/Info.plist index cf543d82c..b5c597653 100644 --- a/Apptentive/Apptentive/Info.plist +++ b/Apptentive/Apptentive/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.3.3 + 5.3.4 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.h b/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.h index a7f394ff2..9b15ab1d4 100644 --- a/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.h +++ b/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.h @@ -55,7 +55,7 @@ typedef NS_ENUM(NSInteger, ATMessageCenterMessageStatus) { @property (readonly, nonatomic) NSString *greetingTitle; @property (readonly, nonatomic) NSString *greetingBody; -@property (readonly, nonatomic) NSURL *greetingImageURL; +@property (readonly, nonatomic, nullable) NSURL *greetingImageURL; @property (readonly, nonatomic) NSString *statusBody; diff --git a/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.m b/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.m index 07a6bfdb5..e93560144 100644 --- a/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.m +++ b/Apptentive/Apptentive/Message Center/ApptentiveMessageCenterViewModel.m @@ -141,7 +141,7 @@ - (NSString *)greetingBody { return self.interaction.configuration[@"greeting"][@"body"]; } -- (NSURL *)greetingImageURL { +- (nullable NSURL *)greetingImageURL { NSString *URLString = self.interaction.configuration[@"greeting"][@"image_url"]; return (URLString.length > 0) ? [NSURL URLWithString:URLString] : nil; diff --git a/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.h b/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.h index cf5e5966c..e51a6da26 100644 --- a/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.h +++ b/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.h @@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateUnreadCount; -+ (NSString *)attachmentDirectoryPathForConversationDirectory:(NSString *)storagePath; ++ (nullable NSString *)attachmentDirectoryPathForConversationDirectory:(NSString *)storagePath; @property (readonly, nonatomic) NSString *attachmentDirectoryPath; diff --git a/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.m b/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.m index af0f6e8ab..60690244a 100644 --- a/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.m +++ b/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageManager.m @@ -156,7 +156,7 @@ - (NSString *)attachmentDirectoryPath { return [[self class] attachmentDirectoryPathForConversationDirectory:self.storagePath]; } -+ (NSString *)attachmentDirectoryPathForConversationDirectory:(NSString *)conversationDirectory { ++ (nullable NSString *)attachmentDirectoryPathForConversationDirectory:(NSString *)conversationDirectory { NSString *result = [conversationDirectory stringByAppendingPathComponent:@"Attachments"]; if (![[NSFileManager defaultManager] fileExistsAtPath:result]) { diff --git a/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageStore.m b/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageStore.m index 426d9a293..badc092c1 100644 --- a/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageStore.m +++ b/Apptentive/Apptentive/Message Center/Model/ApptentiveMessageStore.m @@ -33,7 +33,7 @@ - (instancetype)init { - (nullable instancetype)initWithCoder:(NSCoder *)coder { self = [super init]; if (self) { - NSSet *allowedClasses = [NSSet setWithArray:@[[NSArray class], [ApptentiveMessage class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSMutableArray class], [ApptentiveMessage class]]]; _messages = [coder decodeObjectOfClasses:allowedClasses forKey:MessagesKey]; _lastMessageIdentifier = [coder decodeObjectOfClass:[NSString class] forKey:LastMessageIdentifierKey]; } diff --git a/Apptentive/Apptentive/Misc/ApptentiveArchiver.h b/Apptentive/Apptentive/Misc/ApptentiveArchiver.h index 1034999b5..9897f2f49 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveArchiver.h +++ b/Apptentive/Apptentive/Misc/ApptentiveArchiver.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ApptentiveArchiver : NSObject + (BOOL)archiveRootObject:(NSObject *)rootObject toFile:(NSString *)path; -+ (NSData *)archivedDataWithRootObject:(NSObject *)rootObject; ++ (nullable NSData *)archivedDataWithRootObject:(NSObject *)rootObject; @end diff --git a/Apptentive/Apptentive/Misc/ApptentiveArchiver.m b/Apptentive/Apptentive/Misc/ApptentiveArchiver.m index b8cb33e4a..71fb74a64 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveArchiver.m +++ b/Apptentive/Apptentive/Misc/ApptentiveArchiver.m @@ -20,7 +20,7 @@ + (BOOL)archiveRootObject:(NSObject *)rootObject toFile:(NSString *)path { } } -+ (NSData *)archivedDataWithRootObject:(NSObject *)rootObject { ++ (nullable NSData *)archivedDataWithRootObject:(NSObject *)rootObject { NSError *error = nil; NSData *result = nil; diff --git a/Apptentive/Apptentive/Misc/ApptentiveLog.h b/Apptentive/Apptentive/Misc/ApptentiveLog.h index 8a4ec9066..2258622b3 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveLog.h +++ b/Apptentive/Apptentive/Misc/ApptentiveLog.h @@ -40,7 +40,7 @@ void ApptentiveLogInfo(id arg, ...); void ApptentiveLogDebug(id arg, ...); void ApptentiveLogVerbose(id arg, ...); -void ApptentiveStartLogMonitor(NSString *logDir); +void ApptentiveStartLogWriter(NSString *logDir); NSArray * _Nullable ApptentiveListLogFiles(void); NS_ASSUME_NONNULL_END diff --git a/Apptentive/Apptentive/Misc/ApptentiveLog.m b/Apptentive/Apptentive/Misc/ApptentiveLog.m index 8dfdae9f6..7a094fed1 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveLog.m +++ b/Apptentive/Apptentive/Misc/ApptentiveLog.m @@ -194,7 +194,7 @@ void setShouldSanitizeApptentiveLogMessages(BOOL shouldSanitize) { _shouldSanitizeLogMessages = shouldSanitize; } -void ApptentiveStartLogMonitor(NSString *logDir) { +void ApptentiveStartLogWriter(NSString *logDir) { _logWriter = [[ApptentiveAsyncLogWriter alloc] initWithDestDir:logDir historySize:kLogHistorySize]; } diff --git a/Apptentive/Apptentive/Misc/ApptentiveLogMonitor.m b/Apptentive/Apptentive/Misc/ApptentiveLogMonitor.m index 77913c2b2..4e0254c0a 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveLogMonitor.m +++ b/Apptentive/Apptentive/Misc/ApptentiveLogMonitor.m @@ -240,24 +240,31 @@ + (BOOL)IsMobileConfigInstalled { SecPolicyRef policy = SecPolicyCreateBasicX509(); SecTrustRef trust; - OSStatus err = SecTrustCreateWithCertificates((__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)cert], policy, &trust); + OSStatus status = SecTrustCreateWithCertificates((__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)cert], policy, &trust); + if (status != 0) { + ApptentiveLogInfo(@"OSStatus is %d.", status); + } BOOL result = NO; if (@available(iOS 12.0, *)) { CFErrorRef trustError = NULL; - err = SecTrustEvaluateWithError(trust, &trustError); + status = SecTrustEvaluateWithError(trust, &trustError); result = (trustError == nil); } else { SecTrustResultType trustResult = -1; - err = SecTrustEvaluate(trust, &trustResult); + status = SecTrustEvaluate(trust, &trustResult); result = (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed); } + if (status != 0) { + ApptentiveLogInfo(@"OSStatus is %d.", status); + } + CFRelease(trust); CFRelease(policy); CFRelease(cert); diff --git a/Apptentive/Apptentive/Misc/ApptentiveLogMonitorSession.m b/Apptentive/Apptentive/Misc/ApptentiveLogMonitorSession.m index 4b269d639..9c107d56e 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveLogMonitorSession.m +++ b/Apptentive/Apptentive/Misc/ApptentiveLogMonitorSession.m @@ -15,6 +15,7 @@ #import "ApptentiveSafeCollections.h" #import "ApptentiveArchiver.h" #import "ApptentiveUnarchiver.h" +#import "UIWindow+Apptentive.h" NSNotificationName const ApptentiveLogMonitorSessionDidStart = @"ApptentiveLogMonitorSessionDidStart"; NSNotificationName const ApptentiveLogMonitorSessionDidStop = @"ApptentiveLogMonitorSessionDidStop"; @@ -27,7 +28,7 @@ @interface ApptentiveLogMonitorSession () -@property (nonatomic, strong) UIWindow *mailComposeControllerWindow; +@property (nonatomic, strong) UIWindow *presentationWindow; @property (nonatomic, assign) ApptentiveLogLevel oldLogLevel; @end @@ -64,7 +65,9 @@ - (void)start { ApptentiveLogSetLevel(ApptentiveLogLevelVerbose); dispatch_async(dispatch_get_main_queue(), ^{ - [self showReportUI]; + if (self.presentationWindow == nil) { + [self showReportUI]; + } }); [[NSNotificationCenter defaultCenter] postNotificationName:ApptentiveLogMonitorSessionDidStart object:nil]; @@ -72,7 +75,9 @@ - (void)start { - (void)resume { dispatch_async(dispatch_get_main_queue(), ^{ - [self showReportUI]; + if (self.presentationWindow == nil) { + [self showReportUI]; + } }); } @@ -86,91 +91,94 @@ - (void)stop { - (void)showReportUI { // create a custom window to show UI on top of everything - UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self.presentationWindow = [UIWindow apptentive_windowWithRootViewController:[[UIViewController alloc] init]]; // create alert controller with "Send", "Continue" and "Discard" actions UIAlertController *alertController = [UIAlertController alertControllerWithTitle:ApptentiveLocalizedString(@"Apptentive", @"Apptentive") message:ApptentiveLocalizedString(@"Troubleshooting mode", @"Troubleshooting mode") preferredStyle:UIAlertControllerStyleActionSheet]; [alertController addAction:[UIAlertAction actionWithTitle:ApptentiveLocalizedString(@"Send Report", @"Send Report") style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { - window.hidden = YES; - [self sendReportWithAttachedFiles:[ApptentiveLogMonitorSession listAttachments]]; - }]]; + self.presentationWindow.hidden = YES; + self.presentationWindow = nil; + [self sendReportWithAttachedFiles:[ApptentiveLogMonitorSession listAttachments]]; + }]]; [alertController addAction:[UIAlertAction actionWithTitle:ApptentiveLocalizedString(@"Continue", @"Continue") style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action) { - window.hidden = YES; - }]]; + self.presentationWindow.hidden = YES; + self.presentationWindow = nil; + }]]; [alertController addAction:[UIAlertAction actionWithTitle:ApptentiveLocalizedString(@"Discard Report", @"Discard Report") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *_Nonnull action) { - window.hidden = YES; - [self stop]; - }]]; + self.presentationWindow.hidden = YES; + self.presentationWindow = nil; + [self stop]; + }]]; - window.rootViewController = [[UIViewController alloc] init]; - window.windowLevel = UIWindowLevelAlert + 1; - window.hidden = NO; // don't use makeKeyAndVisible since we don't have any knowledge about the host app's UI - [window.rootViewController presentViewController:alertController animated:YES completion:nil]; + self.presentationWindow.hidden = NO; // don't use makeKeyAndVisible since we don't have any knowledge about the host app's UI + [self.presentationWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; } #pragma mark - #pragma mark Report - (void)sendReportWithAttachedFiles:(NSArray *)files { - if (![MFMailComposeViewController canSendMail]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:ApptentiveLocalizedString(@"Apptentive Log Monitor", @"Apptentive Log Monitor") message:ApptentiveLocalizedString(@"Unable to send email", @"Unable to send email") delegate:nil cancelButtonTitle:ApptentiveLocalizedString(@"OK", @"OK") otherButtonTitles:nil]; - [alertView show]; -#pragma clang diagnostic pop - - return; - } - - NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary]; - - // collecting system info - NSMutableString *messageBody = [NSMutableString new]; - [messageBody appendString:@"This email may contain sensitive content.\n Please review before sending.\n\n"]; - [messageBody appendFormat:@"App Bundle Identifier: %@\n", [NSBundle mainBundle].bundleIdentifier]; - [messageBody appendFormat:@"App Version: %@\n", [bundleInfo objectForKey:@"CFBundleShortVersionString"]]; - [messageBody appendFormat:@"App Build: %@\n", [bundleInfo objectForKey:@"CFBundleVersion"]]; - [messageBody appendFormat:@"Apptentive SDK: %@\n", kApptentiveVersionString]; - [messageBody appendFormat:@"Device Model: %@\n", [ApptentiveUtilities deviceMachine]]; - [messageBody appendFormat:@"iOS Version: %@\n", [UIDevice currentDevice].systemVersion]; - [messageBody appendFormat:@"Locale: %@", [NSLocale currentLocale].localeIdentifier]; - - NSString *emailTitle = [NSString stringWithFormat:@"%@ (iOS)", [NSBundle mainBundle].infoDictionary[@"CFBundleName"]]; - - MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init]; - mc.mailComposeDelegate = self; - [mc setSubject:emailTitle]; - [mc setMessageBody:messageBody isHTML:NO]; - [mc setToRecipients:_emailRecipients]; - - // Get the resource path and read the file using NSData - for (NSString *path in files) { - NSString *filename = [path lastPathComponent]; - NSData *fileData = [NSData dataWithContentsOfFile:path]; - if (fileData.length == 0) { - ApptentiveLogError(ApptentiveLogTagMonitor, @"Attachment file does not exist or empty: %@", path); - continue; + // Present mail view controller/unavailable alert on screen in a separate window + UIViewController *presentedViewController = nil; + + self.presentationWindow = [UIWindow apptentive_windowWithRootViewController:[[UIViewController alloc] init]]; + self.presentationWindow.hidden = NO; + + if ([MFMailComposeViewController canSendMail]) { + // collecting system info + NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary]; + + NSMutableString *messageBody = [NSMutableString new]; + [messageBody appendString:@"This email may contain sensitive content.\n Please review before sending.\n\n"]; + [messageBody appendFormat:@"App Bundle Identifier: %@\n", [NSBundle mainBundle].bundleIdentifier]; + [messageBody appendFormat:@"App Version: %@\n", [bundleInfo objectForKey:@"CFBundleShortVersionString"]]; + [messageBody appendFormat:@"App Build: %@\n", [bundleInfo objectForKey:@"CFBundleVersion"]]; + [messageBody appendFormat:@"Apptentive SDK: %@\n", kApptentiveVersionString]; + [messageBody appendFormat:@"Device Model: %@\n", [ApptentiveUtilities deviceMachine]]; + [messageBody appendFormat:@"iOS Version: %@\n", [UIDevice currentDevice].systemVersion]; + [messageBody appendFormat:@"Locale: %@", [NSLocale currentLocale].localeIdentifier]; + + NSString *emailTitle = [NSString stringWithFormat:@"%@ (iOS)", [NSBundle mainBundle].infoDictionary[@"CFBundleName"]]; + + MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init]; + mc.mailComposeDelegate = self; + [mc setSubject:emailTitle]; + [mc setMessageBody:messageBody isHTML:NO]; + [mc setToRecipients:_emailRecipients]; + + // Get the resource path and read the file using NSData + for (NSString *path in files) { + NSString *filename = [path lastPathComponent]; + NSData *fileData = [NSData dataWithContentsOfFile:path]; + if (fileData.length == 0) { + ApptentiveLogError(ApptentiveLogTagMonitor, @"Attachment file does not exist or empty: %@", path); + continue; + } + + // Add attachment + [mc addAttachmentData:fileData mimeType:@"text/plain" fileName:filename]; } - - // Add attachment - [mc addAttachmentData:fileData mimeType:@"text/plain" fileName:filename]; + + presentedViewController = mc; + } else { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:ApptentiveLocalizedString(@"Apptentive Log Monitor", @"Apptentive Log Monitor") message:ApptentiveLocalizedString(@"Unable to send email", @"Unable to send email") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:ApptentiveLocalizedString(@"OK", @"OK") + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *_Nonnull action) { + self.presentationWindow.hidden = YES; + self.presentationWindow = nil; + }]]; + + presentedViewController = alertController; } - - // Present mail view controller on screen in a separate window - UIViewController *rootController = [UIViewController new]; - - self.mailComposeControllerWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.mailComposeControllerWindow.windowLevel = UIWindowLevelAlert + 1; - self.mailComposeControllerWindow.rootViewController = rootController; - self.mailComposeControllerWindow.hidden = NO; - - [rootController presentViewController:mc animated:YES completion:nil]; + + [self.presentationWindow.rootViewController presentViewController:presentedViewController animated:YES completion:nil]; } #pragma mark - @@ -180,8 +188,8 @@ - (void)mailComposeController:(MFMailComposeViewController *)controller didFinis [controller dismissViewControllerAnimated:YES completion:^{ [self stop]; - self.mailComposeControllerWindow.hidden = YES; - self.mailComposeControllerWindow = nil; + self.presentationWindow.hidden = YES; + self.presentationWindow = nil; }]; } diff --git a/Apptentive/Apptentive/Misc/ApptentiveStoreProductViewController.m b/Apptentive/Apptentive/Misc/ApptentiveStoreProductViewController.m index 6ffebd325..f5f78a41e 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveStoreProductViewController.m +++ b/Apptentive/Apptentive/Misc/ApptentiveStoreProductViewController.m @@ -7,6 +7,7 @@ // #import "ApptentiveStoreProductViewController.h" +#import "UIWindow+Apptentive.h" NS_ASSUME_NONNULL_BEGIN @@ -21,9 +22,7 @@ @interface ApptentiveStoreProductViewController () @implementation ApptentiveStoreProductViewController - (void)presentAnimated:(BOOL)animated completion:(void (^__nullable)(void))completion { - self.apptentiveAlertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.apptentiveAlertWindow.rootViewController = [[UIViewController alloc] init]; - self.apptentiveAlertWindow.windowLevel = UIWindowLevelAlert + 1; + self.apptentiveAlertWindow = [UIWindow apptentive_windowWithRootViewController:[[UIViewController alloc] init]]; [self.apptentiveAlertWindow makeKeyAndVisible]; [self.apptentiveAlertWindow.rootViewController presentViewController:self animated:animated completion:completion]; } diff --git a/Apptentive/Apptentive/Misc/ApptentiveUtilities.h b/Apptentive/Apptentive/Misc/ApptentiveUtilities.h index 727ca5f90..f3d845a2b 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveUtilities.h +++ b/Apptentive/Apptentive/Misc/ApptentiveUtilities.h @@ -12,8 +12,8 @@ NS_ASSUME_NONNULL_BEGIN @interface ApptentiveUtilities : NSObject -+ (NSString *)applicationSupportPath; -+ (NSString * _Nullable)cacheDirectoryPath:(NSString *)path; ++ (nullable NSString *)applicationSupportPath; ++ (nullable NSString *)cacheDirectoryPath:(NSString *)path; + (NSBundle *)resourceBundle; + (UIStoryboard *)storyboard; + (UIImage *)imageNamed:(NSString *)name; diff --git a/Apptentive/Apptentive/Misc/ApptentiveUtilities.m b/Apptentive/Apptentive/Misc/ApptentiveUtilities.m index ab3bc8539..e9505f817 100644 --- a/Apptentive/Apptentive/Misc/ApptentiveUtilities.m +++ b/Apptentive/Apptentive/Misc/ApptentiveUtilities.m @@ -31,25 +31,27 @@ @implementation ApptentiveUtilities -+ (NSString *)applicationSupportPath { ++ (nullable NSString *)applicationSupportPath { static NSString *_applicationSupportPath; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _applicationSupportPath = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject; - - NSError *error; + _applicationSupportPath = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES).firstObject; + NSError *error; - if (![[NSFileManager defaultManager] createDirectoryAtPath:_applicationSupportPath withIntermediateDirectories:YES attributes:nil error:&error]) { - ApptentiveLogError(ApptentiveLogTagUtility, @"Failed to create Application Support directory: %@", _applicationSupportPath); - ApptentiveLogError(ApptentiveLogTagUtility, @"Error was: %@", error); - _applicationSupportPath = nil; - } + if (![[NSFileManager defaultManager] createDirectoryAtPath:_applicationSupportPath withIntermediateDirectories:YES attributes:nil error:&error]) { + ApptentiveLogError(ApptentiveLogTagUtility, @"Failed to create Application Support directory: %@", _applicationSupportPath); + ApptentiveLogError(ApptentiveLogTagUtility, @"Error was: %@", error); + _applicationSupportPath = nil; + } + if (_applicationSupportPath == nil) { + return; + } - if (![[NSFileManager defaultManager] setAttributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication } ofItemAtPath:_applicationSupportPath error:&error]) { - ApptentiveLogError(ApptentiveLogTagUtility, @"Failed to set file protection level: %@", _applicationSupportPath); - ApptentiveLogError(ApptentiveLogTagUtility, @"Error was: %@", error); - } + if (![[NSFileManager defaultManager] setAttributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication } ofItemAtPath:_applicationSupportPath error:&error]) { + ApptentiveLogError(ApptentiveLogTagUtility, @"Failed to set file protection level: %@", _applicationSupportPath); + ApptentiveLogError(ApptentiveLogTagUtility, @"Error was: %@", error); + } }); return _applicationSupportPath; @@ -74,6 +76,10 @@ + (NSString * _Nullable)cacheDirectoryPath:(NSString *)path { _cacheDirectoryPath = nil; } + if (_cacheDirectoryPath == nil) { + return; + } + if (![[NSFileManager defaultManager] setAttributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication } ofItemAtPath:_cacheDirectoryPath error:&error]) { ApptentiveLogError(ApptentiveLogTagUtility, @"Failed to set file protection level: %@", _cacheDirectoryPath); ApptentiveLogError(ApptentiveLogTagUtility, @"Error was: %@", error); diff --git a/Apptentive/Apptentive/Misc/UIAlertController+Apptentive.m b/Apptentive/Apptentive/Misc/UIAlertController+Apptentive.m index 843260d05..cd23a443a 100644 --- a/Apptentive/Apptentive/Misc/UIAlertController+Apptentive.m +++ b/Apptentive/Apptentive/Misc/UIAlertController+Apptentive.m @@ -7,6 +7,7 @@ // #import "UIAlertController+Apptentive.h" +#import "UIWindow+Apptentive.h" #import @@ -38,9 +39,7 @@ - (nullable UIWindow *)apptentiveAlertWindow { @implementation UIAlertController (Apptentive) - (void)apptentive_presentAnimated:(BOOL)animated completion:(void (^__nullable)(void))completion { - self.apptentiveAlertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.apptentiveAlertWindow.rootViewController = [[UIViewController alloc] init]; - self.apptentiveAlertWindow.windowLevel = UIWindowLevelAlert + 1; + self.apptentiveAlertWindow = [UIWindow apptentive_windowWithRootViewController:[[UIViewController alloc] init]]; [self.apptentiveAlertWindow makeKeyAndVisible]; [self.apptentiveAlertWindow.rootViewController presentViewController:self animated:animated completion:completion]; } diff --git a/Apptentive/Apptentive/Misc/UIWindow+Apptentive.h b/Apptentive/Apptentive/Misc/UIWindow+Apptentive.h new file mode 100644 index 000000000..7e0e4e45d --- /dev/null +++ b/Apptentive/Apptentive/Misc/UIWindow+Apptentive.h @@ -0,0 +1,19 @@ +// +// UIWindow+Apptentive.h +// Apptentive +// +// Created by Frank Schmitt on 7/29/21. +// Copyright © 2021 Apptentive, Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIWindow (Apptentive) + ++ (instancetype)apptentive_windowWithRootViewController:(UIViewController *)rootViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Apptentive/Apptentive/Misc/UIWindow+Apptentive.m b/Apptentive/Apptentive/Misc/UIWindow+Apptentive.m new file mode 100644 index 000000000..ac6370568 --- /dev/null +++ b/Apptentive/Apptentive/Misc/UIWindow+Apptentive.m @@ -0,0 +1,45 @@ +// +// UIWindow+Apptentive.m +// Apptentive +// +// Created by Frank Schmitt on 7/29/21. +// Copyright © 2021 Apptentive, Inc. All rights reserved. +// + +#import "UIWindow+Apptentive.h" + +@implementation UIWindow (Apptentive) + ++ (instancetype)apptentive_windowWithRootViewController:(UIViewController *)rootViewController { + UIWindow *window; + + if (@available(iOS 13.0, *)) { + // Look for an active foreground scene. + for (UIScene *scene in UIApplication.sharedApplication.connectedScenes.allObjects) { + if ([scene isKindOfClass:[UIWindowScene class]] && scene.activationState == UISceneActivationStateForegroundActive) { + window = [[self alloc] initWithWindowScene:(UIWindowScene *)scene]; + } + } + + if (window == nil) { + // Settle for an inactive foreground scene. + for (UIScene *scene in UIApplication.sharedApplication.connectedScenes.allObjects) { + if ([scene isKindOfClass:[UIWindowScene class]] && scene.activationState == UISceneActivationStateForegroundInactive) { + window = [[self alloc] initWithWindowScene:(UIWindowScene *)scene]; + } + } + } + } + + if (window == nil) { + // Fall back to a standard UIWindow object (won't work with scene-based apps). + window = [[self alloc] initWithFrame:[UIScreen mainScreen].bounds]; + } + + window.rootViewController = rootViewController; + window.windowLevel = UIWindowLevelAlert + 1; + + return window; +} + +@end diff --git a/Apptentive/Apptentive/Model/ApptentiveLegacyEvent.m b/Apptentive/Apptentive/Model/ApptentiveLegacyEvent.m index 53d435562..1cf345ff7 100644 --- a/Apptentive/Apptentive/Model/ApptentiveLegacyEvent.m +++ b/Apptentive/Apptentive/Model/ApptentiveLegacyEvent.m @@ -45,7 +45,7 @@ + (void)enqueueUnsentEventsInContext:(NSManagedObjectContext *)context forConver // Add custom data, interaction identifier, and extended data if (event.dictionaryData != nil) { - NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [NSString class]]]; NSDictionary *dictionaryData = [ApptentiveUnarchiver unarchivedObjectOfClasses:allowedClasses fromData:event.dictionaryData]; payload.customData = dictionaryData[@"custom_data"]; diff --git a/Apptentive/Apptentive/Model/ApptentiveLegacySurveyResponse.m b/Apptentive/Apptentive/Model/ApptentiveLegacySurveyResponse.m index 08068732e..9a9b9aac0 100644 --- a/Apptentive/Apptentive/Model/ApptentiveLegacySurveyResponse.m +++ b/Apptentive/Apptentive/Model/ApptentiveLegacySurveyResponse.m @@ -81,7 +81,7 @@ - (NSDictionary *)dictionaryForAnswers { return @{}; } else { NSDictionary *result = nil; - NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [NSString class]]]; + NSSet *allowedClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [NSString class], [NSNumber class]]]; result = [ApptentiveUnarchiver unarchivedObjectOfClasses:allowedClasses fromData:self.answersData]; return result; } diff --git a/Apptentive/Apptentive/Networking/Requests & Payloads/ApptentiveLegacyConversationRequest.m b/Apptentive/Apptentive/Networking/Requests & Payloads/ApptentiveLegacyConversationRequest.m index 8a992fc89..371eda697 100644 --- a/Apptentive/Apptentive/Networking/Requests & Payloads/ApptentiveLegacyConversationRequest.m +++ b/Apptentive/Apptentive/Networking/Requests & Payloads/ApptentiveLegacyConversationRequest.m @@ -34,7 +34,7 @@ - (NSString *)path { return @"conversation/token"; } -- (NSDictionary *)JSONDictionary { +- (nullable NSDictionary *)JSONDictionary { return nil; } diff --git a/Apptentive/Apptentive/Persistence/ApptentiveBackend.h b/Apptentive/Apptentive/Persistence/ApptentiveBackend.h index f03af8030..135b5b612 100644 --- a/Apptentive/Apptentive/Persistence/ApptentiveBackend.h +++ b/Apptentive/Apptentive/Persistence/ApptentiveBackend.h @@ -40,6 +40,8 @@ extern NSString *const ApptentiveHasSentMessageKey; */ @interface ApptentiveBackend : NSObject +@property (class, assign, nonatomic, getter=shouldGatherCarrierInfo) BOOL gatherCarrierInfo; + @property (readonly, strong, nonatomic) ApptentiveConversationManager *conversationManager; @property (readonly, strong, nonatomic) ApptentiveAppConfiguration *configuration; @property (readonly, strong, nonatomic) ApptentiveDispatchQueue *operationQueue; diff --git a/Apptentive/Apptentive/Persistence/ApptentiveBackend.m b/Apptentive/Apptentive/Persistence/ApptentiveBackend.m index 0893bd45b..573a35d98 100644 --- a/Apptentive/Apptentive/Persistence/ApptentiveBackend.m +++ b/Apptentive/Apptentive/Persistence/ApptentiveBackend.m @@ -50,6 +50,7 @@ NSString *const ATInteractionAppEventLabelLaunch = @"launch"; NSString *const ATInteractionAppEventLabelExit = @"exit"; +static BOOL _gatherCarrierInfo; @interface ApptentiveBackend () @@ -145,7 +146,7 @@ - (void)updateAndMonitorDeviceValues { [ApptentiveDevice getPermanentDeviceValues]; __weak ApptentiveBackend *weakSelf = self; - if ([CTTelephonyNetworkInfo class]) { + if ([CTTelephonyNetworkInfo class] && ApptentiveBackend.shouldGatherCarrierInfo) { _telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init]; if (@available(iOS 12.0, *)) { ApptentiveDevice.carrierName = [[_telephonyNetworkInfo.serviceSubscriberCellularProviders.allValues valueForKeyPath:@"carrierName"] componentsJoinedByString:@"|"]; @@ -765,11 +766,13 @@ - (void)conversation:(ApptentiveConversation *)conversation addApptimizeExperime #pragma mark - Paths -- (NSString *)cacheDirectoryPath { +- (nullable NSString *)cacheDirectoryPath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString *path = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; + if (paths.count == 0) { + return nil; + } - NSString *newPath = [path stringByAppendingPathComponent:@"com.apptentive"]; + NSString *newPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"com.apptentive"]; NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = nil; BOOL result = [fm createDirectoryAtPath:newPath withIntermediateDirectories:YES attributes:nil error:&error]; @@ -792,6 +795,14 @@ - (ApptentiveMessageManager *)messageManager { return self.conversationManager.messageManager; } ++ (BOOL)shouldGatherCarrierInfo { + return _gatherCarrierInfo; +} + ++ (void)setGatherCarrierInfo:(BOOL)gatherCarrierInfo { + _gatherCarrierInfo = gatherCarrierInfo; +} + #pragma mark - #pragma mark Apptimize SDK diff --git a/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.h b/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.h index 905373194..c461616a4 100644 --- a/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.h +++ b/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.h @@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic) NSString *thankYouText; @property (readonly, nonatomic) NSString *missingRequiredItemText; -@property (readonly, nonatomic) NSIndexPath *firstInvalidAnswerIndexPath; +@property (readonly, nonatomic, nullable) NSIndexPath *firstInvalidAnswerIndexPath; - (NSInteger)numberOfQuestionsInSurvey; - (NSInteger)numberOfAnswersForQuestionAtIndex:(NSInteger)index; diff --git a/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.m b/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.m index 5e9021cf6..f339fbd82 100644 --- a/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.m +++ b/Apptentive/Apptentive/Surveys/View Controllers/ApptentiveSurveyViewModel.m @@ -455,7 +455,7 @@ - (BOOL)validate:(BOOL)isSubmit { return self.invalidQuestionIndexes.count == 0; } -- (NSIndexPath *)firstInvalidAnswerIndexPath { +- (nullable NSIndexPath *)firstInvalidAnswerIndexPath { __block NSUInteger minIndex = NSNotFound; [self.invalidQuestionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *_Nonnull stop) { minIndex = MIN(minIndex, idx); @@ -535,7 +535,7 @@ - (ApptentiveSurveyQuestion *)questionAtIndex:(NSInteger)index { return [self.survey.questions objectAtIndex:index]; } -- (ApptentiveSurveyAnswer *)answerAtIndexPath:(NSIndexPath *)indexPath { +- (nullable ApptentiveSurveyAnswer *)answerAtIndexPath:(NSIndexPath *)indexPath { NSArray *answers = [self questionAtIndex:indexPath.section].answers; return answers.count > (NSUInteger)indexPath.row ? answers[indexPath.row] : nil; diff --git a/Apptentive/Apptentive/Surveys/Views/ApptentiveSurveyAnswerCell.m b/Apptentive/Apptentive/Surveys/Views/ApptentiveSurveyAnswerCell.m index 567767b8a..342bca8ad 100644 --- a/Apptentive/Apptentive/Surveys/Views/ApptentiveSurveyAnswerCell.m +++ b/Apptentive/Apptentive/Surveys/Views/ApptentiveSurveyAnswerCell.m @@ -14,7 +14,7 @@ @implementation ApptentiveSurveyAnswerCell - (BOOL)isHidden { - return super.hidden && !self.forceUnhide; + return super.hidden && !self.forceUnhide; } @end diff --git a/Apptentive/ApptentiveTests/ApptentiveConversationTests.m b/Apptentive/ApptentiveTests/ApptentiveConversationTests.m index c067516df..f6ccf8a1a 100644 --- a/Apptentive/ApptentiveTests/ApptentiveConversationTests.m +++ b/Apptentive/ApptentiveTests/ApptentiveConversationTests.m @@ -11,6 +11,7 @@ #import "ApptentiveCount.h" #import "ApptentiveDevice.h" #import "ApptentiveEngagement.h" +#import "ApptentiveRandom.h" #import "ApptentivePerson.h" #import "ApptentiveSDK.h" #import "ApptentiveVersion.h" @@ -72,6 +73,8 @@ - (void)testConversation { [self.conversation endSession]; XCTAssertNil(self.conversation.sessionIdentifier); + + XCTAssertNotNil(self.conversation.random); } - (void)testAppRelease { @@ -265,6 +268,12 @@ - (void)testEngagement { XCTAssertEqualWithAccuracy(testInteraction.lastInvoked.timeIntervalSince1970, [[NSDate date] timeIntervalSince1970], 0.01); } +- (void)testRandom { + XCTAssertEqualObjects([self.conversation.random valueForFieldWithPath:@"percent"], @50); + XCTAssertEqualObjects([self.conversation.random valueForFieldWithPath:@"xyz/percent"], @50); + XCTAssertNil([self.conversation.random valueForFieldWithPath:@"xyz/abc/percent"]); +} + - (void)testNSCoding { ApptentiveMutableConversation *mutableConversation = [self.conversation mutableCopy]; @@ -295,6 +304,8 @@ - (void)testNSCoding { [mutableConversation startSession]; + [mutableConversation valueForFieldWithPath:@"random/xyz/percent"]; + NSString *path = [NSTemporaryDirectory() stringByAppendingString:@"conversation.archive"]; [ApptentiveArchiver archiveRootObject:mutableConversation toFile:path]; @@ -334,6 +345,9 @@ - (void)testNSCoding { XCTAssertEqual(testInteraction.versionCount, 0); XCTAssertEqualWithAccuracy(testInteraction.lastInvoked.timeIntervalSince1970, [engagementTime timeIntervalSince1970], 0.01); + NSMutableDictionary *randomValues = [conversation.random valueForKey:@"randomValues"]; + XCTAssertEqualObjects(randomValues[@"xyz"], @0.5); + XCTAssertNil(conversation.sessionIdentifier); } diff --git a/Apptentive/ApptentiveTests/Info.plist b/Apptentive/ApptentiveTests/Info.plist index 243b4b37d..973ce43e0 100644 --- a/Apptentive/ApptentiveTests/Info.plist +++ b/Apptentive/ApptentiveTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.3.3 + 5.3.4 CFBundleVersion 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eb8632dc..cd295f46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 2021-08-02 - v5.3.4 + +#### Improvements + +* Add a flag to disable CoreTelephony framework import + +#### Bugs Fixed + +* Fix window-based interaction presentation for `UIScene`-based apps +* Fix unarchiving warnings for iOS 15 + # 2021-06-16 - v5.3.3 #### Bugs Fixed diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 161c01dcb..02e46b256 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - apptentive-ios (5.3.3) + - apptentive-ios (5.3.4) DEPENDENCIES: - apptentive-ios (from `..`) @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: ".." SPEC CHECKSUMS: - apptentive-ios: 90329e56e39cc4df71e67f6dd8eadc899cd8aba1 + apptentive-ios: e5be8ad11cfad2259a0063150ce898b04b7c1090 PODFILE CHECKSUM: 785a9d76c0ca2535819b754c6fe8c5c6413dbc30 -COCOAPODS: 1.8.4 +COCOAPODS: 1.10.1 diff --git a/apptentive-ios.podspec b/apptentive-ios.podspec index 9841b4fe8..0d2fe00ce 100644 --- a/apptentive-ios.podspec +++ b/apptentive-ios.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'apptentive-ios' s.module_name = 'Apptentive' - s.version = '5.3.3' + s.version = '5.3.4' s.license = 'BSD' s.summary = 'Apptentive Customer Communications SDK.' s.homepage = 'https://www.apptentive.com/'