diff --git a/Countly.h b/Countly.h index b0957fec..a4a337fd 100644 --- a/Countly.h +++ b/Countly.h @@ -13,7 +13,7 @@ #import "CountlyFeedbackWidget.h" #import "CountlyViewTracking.h" #import "CountlyContentBuilder.h" -#import "CountlyFeedbacksNew.h" +#import "CountlyFeedbacks.h" #import "Resettable.h" #if (TARGET_OS_IOS || TARGET_OS_OSX) #import @@ -690,7 +690,7 @@ NS_ASSUME_NONNULL_BEGIN * Interface variable to access feedback widget functionalities. * @discussion Feedback widget interface for developer to interact with SDK. */ -- (CountlyFeedbacksNew *) feedback; +- (CountlyFeedbacks *) feedback; #endif diff --git a/Countly.m b/Countly.m index d6ed80bd..947616e9 100644 --- a/Countly.m +++ b/Countly.m @@ -175,11 +175,11 @@ - (void)startWithConfig:(CountlyConfig *)config } #if (TARGET_OS_IOS) - CountlyFeedbacks.sharedInstance.message = config.starRatingMessage; - CountlyFeedbacks.sharedInstance.sessionCount = config.starRatingSessionCount; - CountlyFeedbacks.sharedInstance.disableAskingForEachAppVersion = config.starRatingDisableAskingForEachAppVersion; - CountlyFeedbacks.sharedInstance.ratingCompletionForAutoAsk = config.starRatingCompletion; - [CountlyFeedbacks.sharedInstance checkForStarRatingAutoAsk]; + CountlyFeedbacksInternal.sharedInstance.message = config.starRatingMessage; + CountlyFeedbacksInternal.sharedInstance.sessionCount = config.starRatingSessionCount; + CountlyFeedbacksInternal.sharedInstance.disableAskingForEachAppVersion = config.starRatingDisableAskingForEachAppVersion; + CountlyFeedbacksInternal.sharedInstance.ratingCompletionForAutoAsk = config.starRatingCompletion; + [CountlyFeedbacksInternal.sharedInstance checkForStarRatingAutoAsk]; #endif if(config.disableLocation) @@ -1240,7 +1240,7 @@ - (void)askForStarRating:(void(^)(NSInteger rating))completion { CLY_LOG_I(@"%s %@", __FUNCTION__, completion); - [CountlyFeedbacks.sharedInstance showDialog:completion]; + [CountlyFeedbacksInternal.sharedInstance showDialog:completion]; } - (void)presentFeedbackWidgetWithID:(NSString *)widgetID completionHandler:(void (^)(NSError * error))completionHandler @@ -1262,21 +1262,21 @@ - (void)presentRatingWidgetWithID:(NSString *)widgetID closeButtonText:(NSString CLY_LOG_I(@"%s %@ %@ %@", __FUNCTION__, widgetID, closeButtonText, completionHandler); - [CountlyFeedbacks.sharedInstance presentRatingWidgetWithID:widgetID closeButtonText:closeButtonText completionHandler:completionHandler]; + [CountlyFeedbacksInternal.sharedInstance presentRatingWidgetWithID:widgetID closeButtonText:closeButtonText completionHandler:completionHandler]; } - (void)recordRatingWidgetWithID:(NSString *)widgetID rating:(NSInteger)rating email:(NSString * _Nullable)email comment:(NSString * _Nullable)comment userCanBeContacted:(BOOL)userCanBeContacted { CLY_LOG_I(@"%s %@ %ld %@ %@ %d", __FUNCTION__, widgetID, (long)rating, email, comment, userCanBeContacted); - [CountlyFeedbacks.sharedInstance recordRatingWidgetWithID:widgetID rating:rating email:email comment:comment userCanBeContacted:userCanBeContacted]; + [CountlyFeedbacksInternal.sharedInstance recordRatingWidgetWithID:widgetID rating:rating email:email comment:comment userCanBeContacted:userCanBeContacted]; } - (void)getFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError * error))completionHandler { CLY_LOG_I(@"%s %@", __FUNCTION__, completionHandler); - [CountlyFeedbacks.sharedInstance getFeedbackWidgets:completionHandler]; + [CountlyFeedbacksInternal.sharedInstance getFeedbackWidgets:completionHandler]; } - (CountlyContentBuilder *) content @@ -1284,9 +1284,9 @@ - (CountlyContentBuilder *) content return CountlyContentBuilder.sharedInstance; } -- (CountlyFeedbacksNew *) feedback +- (CountlyFeedbacks *) feedback { - return CountlyFeedbacksNew.sharedInstance; + return CountlyFeedbacks.sharedInstance; } #endif diff --git a/Countly.xcodeproj/project.pbxproj b/Countly.xcodeproj/project.pbxproj index ff5a1072..d6bc336f 100644 --- a/Countly.xcodeproj/project.pbxproj +++ b/Countly.xcodeproj/project.pbxproj @@ -48,8 +48,8 @@ 39924ED62BEBD20F00139F91 /* CountlyCrashData.m in Sources */ = {isa = PBXBuildFile; fileRef = 39924ED52BEBD20F00139F91 /* CountlyCrashData.m */; }; 39924ED82BEBD22100139F91 /* CountlyCrashData.h in Headers */ = {isa = PBXBuildFile; fileRef = 39924ED72BEBD22100139F91 /* CountlyCrashData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 399B46502C52813700AD384E /* CountlyLocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399B464F2C52813700AD384E /* CountlyLocationTests.swift */; }; - 39BDF7572CC7CA870066DE7C /* CountlyFeedbacksNew.h in Headers */ = {isa = PBXBuildFile; fileRef = 39BDF7562CC7CA870066DE7C /* CountlyFeedbacksNew.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 39BDF7592CC7CA920066DE7C /* CountlyFeedbacksNew.m in Sources */ = {isa = PBXBuildFile; fileRef = 39BDF7582CC7CA920066DE7C /* CountlyFeedbacksNew.m */; }; + 39BDF7572CC7CA870066DE7C /* CountlyFeedbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 39BDF7562CC7CA870066DE7C /* CountlyFeedbacks.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 39BDF7592CC7CA920066DE7C /* CountlyFeedbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 39BDF7582CC7CA920066DE7C /* CountlyFeedbacks.m */; }; 39EE1F102C8B341E0016D1BF /* CountlyExperimentalConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 3903429B2C8051B400238C96 /* CountlyExperimentalConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3B20A9872245225A00E3D7AE /* Countly.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B20A9852245225A00E3D7AE /* Countly.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3B20A9B22245228700E3D7AE /* CountlyConnectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B20A98D2245228300E3D7AE /* CountlyConnectionManager.h */; }; @@ -87,8 +87,8 @@ D219374C248AC71C00E5798B /* CountlyPerformanceMonitoring.m in Sources */ = {isa = PBXBuildFile; fileRef = D219374A248AC71C00E5798B /* CountlyPerformanceMonitoring.m */; }; D249BF5E254D3D180058A6C2 /* CountlyFeedbackWidget.h in Headers */ = {isa = PBXBuildFile; fileRef = D249BF5C254D3D170058A6C2 /* CountlyFeedbackWidget.h */; settings = {ATTRIBUTES = (Public, ); }; }; D249BF5F254D3D180058A6C2 /* CountlyFeedbackWidget.m in Sources */ = {isa = PBXBuildFile; fileRef = D249BF5D254D3D180058A6C2 /* CountlyFeedbackWidget.m */; }; - D2CFEF972545FBE80026B044 /* CountlyFeedbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = D2CFEF952545FBE80026B044 /* CountlyFeedbacks.h */; }; - D2CFEF982545FBE80026B044 /* CountlyFeedbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = D2CFEF962545FBE80026B044 /* CountlyFeedbacks.m */; }; + D2CFEF972545FBE80026B044 /* CountlyFeedbacksInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = D2CFEF952545FBE80026B044 /* CountlyFeedbacksInternal.h */; }; + D2CFEF982545FBE80026B044 /* CountlyFeedbacksInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = D2CFEF962545FBE80026B044 /* CountlyFeedbacksInternal.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -145,8 +145,8 @@ 39924ED52BEBD20F00139F91 /* CountlyCrashData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CountlyCrashData.m; sourceTree = ""; }; 39924ED72BEBD22100139F91 /* CountlyCrashData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CountlyCrashData.h; sourceTree = ""; }; 399B464F2C52813700AD384E /* CountlyLocationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountlyLocationTests.swift; sourceTree = ""; }; - 39BDF7562CC7CA870066DE7C /* CountlyFeedbacksNew.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CountlyFeedbacksNew.h; sourceTree = ""; }; - 39BDF7582CC7CA920066DE7C /* CountlyFeedbacksNew.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CountlyFeedbacksNew.m; sourceTree = ""; }; + 39BDF7562CC7CA870066DE7C /* CountlyFeedbacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CountlyFeedbacks.h; sourceTree = ""; }; + 39BDF7582CC7CA920066DE7C /* CountlyFeedbacks.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CountlyFeedbacks.m; sourceTree = ""; }; 3B20A9822245225A00E3D7AE /* Countly.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Countly.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B20A9852245225A00E3D7AE /* Countly.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Countly.h; sourceTree = ""; }; 3B20A9862245225A00E3D7AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -185,8 +185,8 @@ D219374A248AC71C00E5798B /* CountlyPerformanceMonitoring.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountlyPerformanceMonitoring.m; sourceTree = ""; }; D249BF5C254D3D170058A6C2 /* CountlyFeedbackWidget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountlyFeedbackWidget.h; sourceTree = ""; }; D249BF5D254D3D180058A6C2 /* CountlyFeedbackWidget.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountlyFeedbackWidget.m; sourceTree = ""; }; - D2CFEF952545FBE80026B044 /* CountlyFeedbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountlyFeedbacks.h; sourceTree = ""; }; - D2CFEF962545FBE80026B044 /* CountlyFeedbacks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountlyFeedbacks.m; sourceTree = ""; }; + D2CFEF952545FBE80026B044 /* CountlyFeedbacksInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountlyFeedbacksInternal.h; sourceTree = ""; }; + D2CFEF962545FBE80026B044 /* CountlyFeedbacksInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountlyFeedbacksInternal.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -228,8 +228,8 @@ 3B20A9782245225A00E3D7AE = { isa = PBXGroup; children = ( - 39BDF7562CC7CA870066DE7C /* CountlyFeedbacksNew.h */, - 39BDF7582CC7CA920066DE7C /* CountlyFeedbacksNew.m */, + 39BDF7562CC7CA870066DE7C /* CountlyFeedbacks.h */, + 39BDF7582CC7CA920066DE7C /* CountlyFeedbacks.m */, 39002D092C8B2E450049394F /* CountlyContentConfig.h */, 39002D0A2C8B2E450049394F /* CountlyContentConfig.m */, 3961C6AF2C6633C000DD38BA /* CountlyWebViewManager.h */, @@ -280,8 +280,8 @@ 3B20A9AA2245228500E3D7AE /* CountlyDeviceInfo.m */, 3B20A99E2245228400E3D7AE /* CountlyEvent.h */, 3B20A9952245228400E3D7AE /* CountlyEvent.m */, - D2CFEF952545FBE80026B044 /* CountlyFeedbacks.h */, - D2CFEF962545FBE80026B044 /* CountlyFeedbacks.m */, + D2CFEF952545FBE80026B044 /* CountlyFeedbacksInternal.h */, + D2CFEF962545FBE80026B044 /* CountlyFeedbacksInternal.m */, D249BF5C254D3D170058A6C2 /* CountlyFeedbackWidget.h */, D249BF5D254D3D180058A6C2 /* CountlyFeedbackWidget.m */, 3B20A99A2245228400E3D7AE /* CountlyLocationManager.h */, @@ -332,7 +332,7 @@ files = ( 39924ECE2BEBD0B700139F91 /* CountlyCrashesConfig.h in Headers */, 1A478D032AB314750056A5E7 /* CountlyExperimentInformation.h in Headers */, - 39BDF7572CC7CA870066DE7C /* CountlyFeedbacksNew.h in Headers */, + 39BDF7572CC7CA870066DE7C /* CountlyFeedbacks.h in Headers */, 3B20A9D32245228700E3D7AE /* CountlyPushNotifications.h in Headers */, 3B20A9C42245228700E3D7AE /* CountlyUserDetails.h in Headers */, 3961C6B72C6633C000DD38BA /* PassThroughBackgroundView.h in Headers */, @@ -364,7 +364,7 @@ D249BF5E254D3D180058A6C2 /* CountlyFeedbackWidget.h in Headers */, 39924ED82BEBD22100139F91 /* CountlyCrashData.h in Headers */, 399117D22C69F73D00DC4C66 /* CountlyContentBuilderInternal.h in Headers */, - D2CFEF972545FBE80026B044 /* CountlyFeedbacks.h in Headers */, + D2CFEF972545FBE80026B044 /* CountlyFeedbacksInternal.h in Headers */, 39002D0B2C8B2E450049394F /* CountlyContentConfig.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -496,11 +496,11 @@ 3B20A9D42245228700E3D7AE /* CountlyPersistency.m in Sources */, 3B20A9B32245228700E3D7AE /* CountlyNotificationService.m in Sources */, 3948A8572BAC2E7D002D09AA /* CountlySDKLimitsConfig.m in Sources */, - D2CFEF982545FBE80026B044 /* CountlyFeedbacks.m in Sources */, + D2CFEF982545FBE80026B044 /* CountlyFeedbacksInternal.m in Sources */, 1A3110632A7128CD001CB507 /* CountlyViewData.m in Sources */, 3961C6B92C6633C000DD38BA /* PassThroughBackgroundView.m in Sources */, 399117D32C69F73D00DC4C66 /* CountlyContentBuilderInternal.m in Sources */, - 39BDF7592CC7CA920066DE7C /* CountlyFeedbacksNew.m in Sources */, + 39BDF7592CC7CA920066DE7C /* CountlyFeedbacks.m in Sources */, 1A423E9E2A271E46008C4757 /* CountlyRCData.m in Sources */, 3B20A9CB2245228700E3D7AE /* CountlyRemoteConfig.m in Sources */, 3B20A9BD2245228700E3D7AE /* CountlyConnectionManager.m in Sources */, diff --git a/CountlyCommon.h b/CountlyCommon.h index 3a1f4564..69a29fce 100644 --- a/CountlyCommon.h +++ b/CountlyCommon.h @@ -15,7 +15,7 @@ #import "CountlyCrashReporter.h" #import "CountlyConfig.h" #import "CountlyViewTrackingInternal.h" -#import "CountlyFeedbacks.h" +#import "CountlyFeedbacksInternal.h" #import "CountlyFeedbackWidget.h" #import "CountlyPushNotifications.h" #import "CountlyNotificationService.h" diff --git a/CountlyConsentManager.m b/CountlyConsentManager.m index fc4bf12f..57f182b0 100644 --- a/CountlyConsentManager.m +++ b/CountlyConsentManager.m @@ -438,7 +438,7 @@ - (void)setConsentForFeedback:(BOOL)consentForFeedback { CLY_LOG_D(@"Consent for Feedback is given."); - [CountlyFeedbacks.sharedInstance checkForStarRatingAutoAsk]; + [CountlyFeedbacksInternal.sharedInstance checkForStarRatingAutoAsk]; } else { diff --git a/CountlyFeedbacks.h b/CountlyFeedbacks.h index 4518908f..446d1e55 100644 --- a/CountlyFeedbacks.h +++ b/CountlyFeedbacks.h @@ -1,41 +1,30 @@ -// CountlyFeedbacksInternal.h +// CountlyFeedbacks.h // // This code is provided under the MIT License. // // Please visit www.count.ly for more information. +// #import +#import "CountlyConfig.h" +#import "CountlyFeedbackWidget.h" -@class CountlyFeedbackWidget; - -extern NSString* const kCountlyFBKeyPlatform; -extern NSString* const kCountlyFBKeyAppVersion; -extern NSString* const kCountlyFBKeyWidgetID; -extern NSString* const kCountlyFBKeyID; - -extern NSString* const kCountlyReservedEventStarRating; - -@interface CountlyFeedbacks : NSObject +@interface CountlyFeedbacks: NSObject #if (TARGET_OS_IOS) + (instancetype)sharedInstance; -- (void)showDialog:(void(^)(NSInteger rating))completion; -- (void)presentRatingWidgetWithID:(NSString *)widgetID closeButtonText:(NSString *)closeButtonText completionHandler:(void (^)(NSError * error))completionHandler; -- (void)recordRatingWidgetWithID:(NSString *)widgetID rating:(NSInteger)rating email:(NSString *)email comment:(NSString *)comment userCanBeContacted:(BOOL)userCanBeContacted; -- (void)checkForStarRatingAutoAsk; - -- (void)getFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError *error))completionHandler; - +- (void) presentNPS; +- (void) presentNPS:(NSString *)nameIDorTag; - (void) presentNPS:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback; +- (void) presentSurvey; +- (void) presentSurvey:(NSString *)nameIDorTag; - (void) presentSurvey:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback; +- (void) presentRating; +- (void) presentRating:(NSString *)nameIDorTag; - (void) presentRating:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback; -@property (nonatomic) NSString* message; -@property (nonatomic) NSString* dismissButtonTitle; -@property (nonatomic) NSUInteger sessionCount; -@property (nonatomic) BOOL disableAskingForEachAppVersion; -@property (nonatomic, copy) void (^ratingCompletionForAutoAsk)(NSInteger); +- (void)getAvailableFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError * error))completionHandler; #endif @end diff --git a/CountlyFeedbacks.m b/CountlyFeedbacks.m index bd8ffa56..5b566c0d 100644 --- a/CountlyFeedbacks.m +++ b/CountlyFeedbacks.m @@ -1,57 +1,20 @@ -// CountlyFeedbacks.m +// CountlyFeedbacks.m // // This code is provided under the MIT License. // // Please visit www.count.ly for more information. +// +#import "CountlyFeedbacks.h" #import "CountlyCommon.h" -#if (TARGET_OS_IOS) -#import -#endif - -@interface CountlyFeedbackWidget () -+ (CountlyFeedbackWidget *)createWithDictionary:(NSDictionary *)dictionary; -@end - - - -@interface CountlyFeedbacks () -#if (TARGET_OS_IOS) -@property (nonatomic) UIAlertController* alertController; -@property (nonatomic, copy) void (^ratingCompletion)(NSInteger); -#endif -@end - -NSString* const kCountlyReservedEventStarRating = @"[CLY]_star_rating"; -NSString* const kCountlyStarRatingStatusSessionCountKey = @"kCountlyStarRatingStatusSessionCountKey"; -NSString* const kCountlyStarRatingStatusHasEverAskedAutomatically = @"kCountlyStarRatingStatusHasEverAskedAutomatically"; - -NSString* const kCountlyFBKeyPlatform = @"platform"; -NSString* const kCountlyFBKeyAppVersion = @"app_version"; -NSString* const kCountlyFBKeyRating = @"rating"; -NSString* const kCountlyFBKeyWidgetID = @"widget_id"; -NSString* const kCountlyFBKeyID = @"_id"; -NSString* const kCountlyFBKeyTargetDevices = @"target_devices"; -NSString* const kCountlyFBKeyPhone = @"phone"; -NSString* const kCountlyFBKeyTablet = @"tablet"; -NSString* const kCountlyFBKeyFeedback = @"feedback"; -NSString* const kCountlyFBKeyEmail = @"email"; -NSString* const kCountlyFBKeyComment = @"comment"; -NSString* const kCountlyFBKeyContactMe = @"contactMe"; - -const CGFloat kCountlyStarRatingButtonSize = 40.0; @implementation CountlyFeedbacks #if (TARGET_OS_IOS) -{ - UIButton* btn_star[5]; -} - + (instancetype)sharedInstance { if (!CountlyCommon.sharedInstance.hasStarted) return nil; - + static CountlyFeedbacks* s_sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{s_sharedInstance = self.new;}); @@ -60,509 +23,56 @@ + (instancetype)sharedInstance - (instancetype)init { - if (self = [super init]) - { - NSString* langDesignator = [NSLocale.preferredLanguages.firstObject substringToIndex:2]; - - NSDictionary* dictMessage = - @{ - @"en": @"How would you rate the app?", - @"tr": @"Uygulamayı nasıl değerlendirirsiniz?", - @"ja": @"あなたの評価を教えてください。", - @"zh": @"请告诉我你的评价。", - @"ru": @"Как бы вы оценили приложение?", - @"cz": @"Jak hodnotíte aplikaci?", - @"lv": @"Kā Jūs novērtētu šo lietotni?", - @"bn": @"আপনি কিভাবে এই এপ্লিক্যাশনটি মূল্যায়ন করবেন?", - @"hi": @"आप एप्लीकेशन का मूल्यांकन कैसे करेंगे?", - }; - - self.message = dictMessage[langDesignator]; - if (!self.message) - self.message = dictMessage[@"en"]; - } - - return self; -} - -#pragma mark - Star Rating - -- (void)showDialog:(void(^)(NSInteger rating))completion -{ - if (!CountlyConsentManager.sharedInstance.consentForFeedback) - return; - - self.ratingCompletion = completion; - - self.alertController = [UIAlertController alertControllerWithTitle:@" " message:self.message preferredStyle:UIAlertControllerStyleAlert]; - - CLYButton* dismissButton = [CLYButton dismissAlertButton]; - dismissButton.onClick = ^(id sender) - { - [self.alertController dismissViewControllerAnimated:YES completion:^ - { - [self finishWithRating:0]; - }]; - }; - [self.alertController.view addSubview:dismissButton]; - [dismissButton positionToTopRight]; - - CLYInternalViewController* cvc = CLYInternalViewController.new; - [cvc setPreferredContentSize:(CGSize){kCountlyStarRatingButtonSize * 5, kCountlyStarRatingButtonSize * 1.5}]; - [cvc.view addSubview:[self starView]]; - - @try - { - [self.alertController setValue:cvc forKey:@"contentViewController"]; - } - @catch (NSException* exception) - { - CLY_LOG_W(@"%s, UIAlertController's contentViewController can not be set, got exception %@", __FUNCTION__, exception); - } - - [CountlyCommon.sharedInstance tryPresentingViewController:self.alertController]; -} - -- (void)checkForStarRatingAutoAsk -{ - if (!self.sessionCount) - return; - - if (!CountlyConsentManager.sharedInstance.consentForFeedback) - return; - - NSMutableDictionary* status = [CountlyPersistency.sharedInstance retrieveStarRatingStatus].mutableCopy; - - if (self.disableAskingForEachAppVersion && status[kCountlyStarRatingStatusHasEverAskedAutomatically]) - return; - - NSString* keyForAppVersion = [kCountlyStarRatingStatusSessionCountKey stringByAppendingString:CountlyDeviceInfo.appVersion]; - NSInteger sessionCountSoFar = [status[keyForAppVersion] integerValue]; - sessionCountSoFar++; - - if (self.sessionCount == sessionCountSoFar) - { - CLY_LOG_D(@"Asking for star-rating as session count reached specified limit %d ...", (int)self.sessionCount); - - [self showDialog:self.ratingCompletionForAutoAsk]; - - status[kCountlyStarRatingStatusHasEverAskedAutomatically] = @YES; - } - - status[keyForAppVersion] = @(sessionCountSoFar); - - [CountlyPersistency.sharedInstance storeStarRatingStatus:status]; -} - -- (UIView *)starView -{ - UIView* vw_star = [UIView.alloc initWithFrame:(CGRect){0, 0, kCountlyStarRatingButtonSize * 5, kCountlyStarRatingButtonSize}]; - vw_star.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; - - for (int i = 0; i < 5; i++) - { - btn_star[i] = [UIButton.alloc initWithFrame:(CGRect){i * kCountlyStarRatingButtonSize, 0, kCountlyStarRatingButtonSize, kCountlyStarRatingButtonSize}]; - btn_star[i].titleLabel.font = [UIFont fontWithName:@"Helvetica" size:28]; - [btn_star[i] setTitle:@"★" forState:UIControlStateNormal]; - [btn_star[i] setTitleColor:[self passiveStarColor] forState:UIControlStateNormal]; - [btn_star[i] addTarget:self action:@selector(onClick_star:) forControlEvents:UIControlEventTouchUpInside]; - - [vw_star addSubview:btn_star[i]]; - } - - return vw_star; -} - -- (void)setMessage:(NSString *)message -{ - if (!message) - return; - - _message = message; -} - -- (void)onClick_star:(id)sender -{ - UIColor* color = [self activeStarColor]; - NSInteger rating = 0; - - for (int i = 0; i < 5; i++) - { - [btn_star[i] setTitleColor:color forState:UIControlStateNormal]; - - if (btn_star[i] == sender) - { - color = [self passiveStarColor]; - rating = i + 1; - } - } - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ - { - [self.alertController dismissViewControllerAnimated:YES completion:^{ [self finishWithRating:rating]; }]; - }); -} - -- (void)finishWithRating:(NSInteger)rating -{ - if (self.ratingCompletion) - self.ratingCompletion(rating); - - if (rating != 0) - { - NSMutableDictionary* segmentation = NSMutableDictionary.new; - segmentation[kCountlyFBKeyPlatform] = CountlyDeviceInfo.osName; - segmentation[kCountlyFBKeyAppVersion] = CountlyDeviceInfo.appVersion; - segmentation[kCountlyFBKeyRating] = @(rating); - - [Countly.sharedInstance recordReservedEvent:kCountlyReservedEventStarRating segmentation:segmentation]; - } - - self.alertController = nil; - self.ratingCompletion = nil; -} - -- (UIColor *)activeStarColor -{ - return [UIColor colorWithRed:253/255.0 green:148/255.0 blue:38/255.0 alpha:1]; -} - -- (UIColor *)passiveStarColor -{ - return [UIColor colorWithWhite:178/255.0 alpha:1]; -} - -#pragma mark - Feedbacks (Ratings) (Legacy Feedback Widget) - -- (void)presentRatingWidgetWithID:(NSString *)widgetID closeButtonText:(NSString *)closeButtonText completionHandler:(void (^)(NSError * error))completionHandler -{ - if (!CountlyServerConfig.sharedInstance.networkingEnabled) - { - CLY_LOG_D(@"'presentRatingWidgetWithID' is aborted: SDK Networking is disabled from server config!"); - return; - } + self = [super init]; - if (!CountlyConsentManager.sharedInstance.consentForFeedback) - return; - - if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) - return; - - if (!widgetID.length) - return; - - NSURLRequest* feedbackWidgetCheckRequest = [self widgetCheckURLRequest:widgetID]; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:feedbackWidgetCheckRequest completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) - { - NSDictionary* widgetInfo = nil; - - if (!error) - { - widgetInfo = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - } - - if (!error) - { - NSMutableDictionary* userInfo = widgetInfo.mutableCopy; - - if (![widgetInfo[kCountlyFBKeyID] isEqualToString:widgetID]) - { - userInfo[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Feedback widget with ID %@ is not available.", widgetID]; - error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbackWidgetNotAvailable userInfo:userInfo]; - } - else if (![self isDeviceTargetedByWidget:widgetInfo]) - { - userInfo[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Feedback widget with ID %@ does not include this device in target devices list.", widgetID]; - error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbackWidgetNotTargetedForDevice userInfo:userInfo]; - } - } - - if (error) - { - dispatch_async(dispatch_get_main_queue(), ^ - { - if (completionHandler) - completionHandler(error); - }); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^ - { - [self presentRatingWidgetInternal:widgetID closeButtonText:closeButtonText completionHandler:completionHandler]; - }); - }]; - - [task resume]; + return self; } -- (void)presentRatingWidgetInternal:(NSString *)widgetID closeButtonText:(NSString *)closeButtonText completionHandler:(void (^)(NSError * error))completionHandler +- (void)enterContentZone:(NSArray *)tags { - __block CLYInternalViewController* webVC = CLYInternalViewController.new; - webVC.view.backgroundColor = UIColor.whiteColor; - webVC.view.bounds = UIScreen.mainScreen.bounds; - webVC.modalPresentationStyle = UIModalPresentationCustom; - - WKWebView* webView = [WKWebView.alloc initWithFrame:webVC.view.bounds]; - webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [webVC.view addSubview:webView]; - NSURL* widgetDisplayURL = [self widgetDisplayURL:widgetID]; - [webView loadRequest:[NSURLRequest requestWithURL:widgetDisplayURL]]; - - CLYButton* dismissButton = [CLYButton dismissAlertButton:closeButtonText]; - dismissButton.onClick = ^(id sender) - { - [webVC dismissViewControllerAnimated:YES completion:^ - { - if (completionHandler) - completionHandler(nil); - - webVC = nil; - }]; - }; - [webVC.view addSubview:dismissButton]; - [dismissButton positionToTopRightConsideringStatusBar]; - - [CountlyCommon.sharedInstance tryPresentingViewController:webVC]; + [CountlyContentBuilderInternal.sharedInstance enterContentZone:tags]; } -- (NSURLRequest *)widgetCheckURLRequest:(NSString *)widgetID -{ - NSString* queryString = [CountlyConnectionManager.sharedInstance queryEssentials]; - - queryString = [queryString stringByAppendingFormat:@"&%@=%@", kCountlyFBKeyWidgetID, widgetID]; - - queryString = [queryString stringByAppendingFormat:@"&%@=%@", - kCountlyAppVersionKey, CountlyDeviceInfo.appVersion]; - - queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; - - NSString* serverOutputFeedbackWidgetEndpoint = [CountlyConnectionManager.sharedInstance.host stringByAppendingFormat:@"%@%@%@", - kCountlyEndpointO, - kCountlyEndpointFeedback, - kCountlyEndpointWidget]; - - if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) - { - NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverOutputFeedbackWidgetEndpoint]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [queryString cly_dataUTF8]; - return request.copy; - } - else - { - NSString* withQueryString = [serverOutputFeedbackWidgetEndpoint stringByAppendingFormat:@"?%@", queryString]; - NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:withQueryString]]; - return request; - } +- (void)presentNPS { + [self presentNPS:nil widgetCallback:nil]; } -- (NSURL *)widgetDisplayURL:(NSString *)widgetID -{ - NSString* queryString = [CountlyConnectionManager.sharedInstance queryEssentials]; - - queryString = [queryString stringByAppendingFormat:@"&%@=%@&%@=%@", - kCountlyFBKeyWidgetID, widgetID, - kCountlyFBKeyAppVersion, CountlyDeviceInfo.appVersion]; - - queryString = [queryString stringByAppendingFormat:@"&%@=%@", - kCountlyAppVersionKey, CountlyDeviceInfo.appVersion]; - - queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; - - NSString* URLString = [NSString stringWithFormat:@"%@%@?%@", - CountlyConnectionManager.sharedInstance.host, - kCountlyEndpointFeedback, - queryString]; - - return [NSURL URLWithString:URLString]; +- (void)presentNPS:(NSString *)nameIDorTag { + [self presentNPS:nameIDorTag widgetCallback:nil]; } -- (BOOL)isDeviceTargetedByWidget:(NSDictionary *)widgetInfo -{ - BOOL isTablet = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad; - BOOL isPhone = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone; - BOOL isTabletTargeted = [widgetInfo[kCountlyFBKeyTargetDevices][kCountlyFBKeyTablet] boolValue]; - BOOL isPhoneTargeted = [widgetInfo[kCountlyFBKeyTargetDevices][kCountlyFBKeyPhone] boolValue]; - - return ((isTablet && isTabletTargeted) || (isPhone && isPhoneTargeted)); +- (void) presentNPS:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { + [CountlyFeedbacksInternal.sharedInstance presentNPS:nameIDorTag widgetCallback:widgetCallback]; } -- (void)recordRatingWidgetWithID:(NSString *)widgetID rating:(NSInteger)rating email:(NSString *)email comment:(NSString *)comment userCanBeContacted:(BOOL)userCanBeContacted -{ - if (!CountlyConsentManager.sharedInstance.consentForFeedback) - return; - - if (!widgetID.length) - return; - - NSMutableDictionary* segmentation = NSMutableDictionary.new; - segmentation[kCountlyFBKeyPlatform] = CountlyDeviceInfo.osName; - segmentation[kCountlyFBKeyAppVersion] = CountlyDeviceInfo.appVersion; - segmentation[kCountlyFBKeyRating] = @(rating); - segmentation[kCountlyFBKeyWidgetID] = widgetID; - segmentation[kCountlyFBKeyEmail] = email; - segmentation[kCountlyFBKeyComment] = comment; - segmentation[kCountlyFBKeyContactMe] = @(userCanBeContacted); - - [Countly.sharedInstance recordReservedEvent:kCountlyReservedEventStarRating segmentation:segmentation]; +- (void)presentSurvey { + [self presentSurvey:nil widgetCallback:nil]; } - -#pragma mark - Feedbacks (Surveys, NPS) - -- (void)getFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError *error))completionHandler -{ - if (!CountlyServerConfig.sharedInstance.networkingEnabled) - { - CLY_LOG_D(@"'getFeedbackWidgets' is aborted: SDK Networking is disabled from server config!"); - return; - } - - if (!CountlyConsentManager.sharedInstance.consentForFeedback) - return; - - if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) - return; - - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self feedbacksRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) - { - NSDictionary *feedbacksResponse = nil; - - if (!error) - { - feedbacksResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - } - - if (!error) - { - if (((NSHTTPURLResponse*)response).statusCode != 200) - { - NSMutableDictionary* userInfo = feedbacksResponse.mutableCopy; - userInfo[NSLocalizedDescriptionKey] = @"Feedbacks general API error"; - error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbacksGeneralAPIError userInfo:userInfo]; - } - } - - if (error) - { - dispatch_async(dispatch_get_main_queue(), ^ - { - if (completionHandler) - completionHandler(nil, error); - }); - - return; - } - - NSMutableArray* feedbacks = NSMutableArray.new; - NSArray* rawFeedbackObjects = feedbacksResponse[@"result"]; - for (NSDictionary * feedbackDict in rawFeedbackObjects) - { - CountlyFeedbackWidget *feedback = [CountlyFeedbackWidget createWithDictionary:feedbackDict]; - if (feedback) - [feedbacks addObject:feedback]; - } - - dispatch_async(dispatch_get_main_queue(), ^ - { - if (completionHandler) - completionHandler([NSArray arrayWithArray:feedbacks], nil); - }); - }]; - - [task resume]; +- (void)presentSurvey:(NSString *)nameIDorTag { + [self presentSurvey:nameIDorTag widgetCallback:nil]; } -- (NSURLRequest *)feedbacksRequest -{ - NSString* queryString = [NSString stringWithFormat:@"%@=%@&%@=%@&%@=%@&%@=%@&%@=%@", - kCountlyQSKeyMethod, kCountlyFBKeyFeedback, - kCountlyQSKeyAppKey, CountlyConnectionManager.sharedInstance.appKey.cly_URLEscaped, - kCountlyQSKeyDeviceID, CountlyDeviceInfo.sharedInstance.deviceID.cly_URLEscaped, - kCountlyQSKeySDKName, CountlyCommon.sharedInstance.SDKName, - kCountlyQSKeySDKVersion, CountlyCommon.sharedInstance.SDKVersion]; - - queryString = [queryString stringByAppendingFormat:@"&%@=%@", - kCountlyAppVersionKey, CountlyDeviceInfo.appVersion]; - - queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; - - NSMutableString* URL = CountlyConnectionManager.sharedInstance.host.mutableCopy; - [URL appendString:kCountlyEndpointO]; - [URL appendString:kCountlyEndpointSDK]; - - if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) - { - NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]]; - request.HTTPMethod = @"POST"; - request.HTTPBody = [queryString cly_dataUTF8]; - return request.copy; - } - else - { - [URL appendFormat:@"?%@", queryString]; - NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]]; - return request; - } +- (void) presentSurvey:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { + [CountlyFeedbacksInternal.sharedInstance presentSurvey:nameIDorTag widgetCallback:widgetCallback]; } -- (void) presentNPS:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { - CLY_LOG_D(@"Presenting NPS widget with nameIDorTag: %@ and WidgetCallback: %@", nameIDorTag, widgetCallback); - [self presentFeedbackWidget:CLYFeedbackWidgetTypeNPS nameIDorTag:nameIDorTag widgetCallback:widgetCallback]; +- (void)presentRating { + [self presentRating:nil widgetCallback:nil]; } -- (void) presentSurvey:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { - CLY_LOG_D(@"Presenting Survey widget with nameIDorTag: %@ and WidgetCallback: %@", nameIDorTag, widgetCallback); - [self presentFeedbackWidget:CLYFeedbackWidgetTypeSurvey nameIDorTag:nameIDorTag widgetCallback:widgetCallback]; +- (void)presentRating:(NSString *)nameIDorTag { + [self presentRating:nameIDorTag widgetCallback:nil]; } - (void) presentRating:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { - CLY_LOG_D(@"Presenting Rating widget with nameIDorTag: %@ and WidgetCallback: %@", nameIDorTag, widgetCallback); - [self presentFeedbackWidget:CLYFeedbackWidgetTypeRating nameIDorTag:nameIDorTag widgetCallback:widgetCallback]; + [CountlyFeedbacksInternal.sharedInstance presentRating:nameIDorTag widgetCallback:widgetCallback]; } - --(void)presentFeedbackWidget:(CLYFeedbackWidgetType)widgetType nameIDorTag:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { - [self getFeedbackWidgets:^(NSArray *feedbackWidgets, NSError *error) { - if (error) { - CLY_LOG_D(@"Getting widgets list failed. Error: %@", error); - return; - } - - CLY_LOG_D(@"Successfully retrieved feedback widgets. Total widgets count: %lu", (unsigned long)feedbackWidgets.count); - - NSPredicate *typePredicate = [NSPredicate predicateWithFormat:@"type == %@", widgetType]; - NSArray *filteredWidgets = [feedbackWidgets filteredArrayUsingPredicate:typePredicate]; - - CLY_LOG_D(@"Filtered widgets count for type '%@': %lu", widgetType, (unsigned long)filteredWidgets.count); - - CountlyFeedbackWidget *widgetToPresent = nil; - - if (nameIDorTag && nameIDorTag.length > 0) { - for (CountlyFeedbackWidget *feedbackWidget in filteredWidgets) { - if ([nameIDorTag isEqualToString:feedbackWidget.name] || - [nameIDorTag isEqualToString:feedbackWidget.ID] || - [feedbackWidget.tags containsObject:nameIDorTag]) { - widgetToPresent = feedbackWidget; - CLY_LOG_D(@"Exact match found for nameIDorTag '%@'. Widget ID: %@, Name: %@", nameIDorTag, feedbackWidget.ID, feedbackWidget.name); - break; - } - } - } - - if (!widgetToPresent && filteredWidgets.count > 0) { - widgetToPresent = filteredWidgets.firstObject; - CLY_LOG_D(@"No exact match found for nameIDorTag '%@'. Falling back to the first widget of type '%@'. Widget ID: %@, Name: %@", nameIDorTag, widgetType, widgetToPresent.ID, widgetToPresent.name); - } - - if (widgetToPresent) { - [widgetToPresent presentWithCallback:widgetCallback]; - } else { - CLY_LOG_D(@"No feedback widget found for the specified type: %@", widgetType); - } - }]; +- (void)getAvailableFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError * error))completionHandler +{ + CLY_LOG_I(@"%s %@", __FUNCTION__, completionHandler); + [CountlyFeedbacksInternal.sharedInstance getFeedbackWidgets:completionHandler]; } - #endif @end diff --git a/CountlyFeedbacksInternal.h b/CountlyFeedbacksInternal.h new file mode 100644 index 00000000..85518ebe --- /dev/null +++ b/CountlyFeedbacksInternal.h @@ -0,0 +1,41 @@ +// CountlyFeedbacksInternal.h +// +// This code is provided under the MIT License. +// +// Please visit www.count.ly for more information. + +#import + +@class CountlyFeedbackWidget; + +extern NSString* const kCountlyFBKeyPlatform; +extern NSString* const kCountlyFBKeyAppVersion; +extern NSString* const kCountlyFBKeyWidgetID; +extern NSString* const kCountlyFBKeyID; + +extern NSString* const kCountlyReservedEventStarRating; + +@interface CountlyFeedbacksInternal : NSObject +#if (TARGET_OS_IOS) ++ (instancetype)sharedInstance; + +- (void)showDialog:(void(^)(NSInteger rating))completion; +- (void)presentRatingWidgetWithID:(NSString *)widgetID closeButtonText:(NSString *)closeButtonText completionHandler:(void (^)(NSError * error))completionHandler; +- (void)recordRatingWidgetWithID:(NSString *)widgetID rating:(NSInteger)rating email:(NSString *)email comment:(NSString *)comment userCanBeContacted:(BOOL)userCanBeContacted; +- (void)checkForStarRatingAutoAsk; + +- (void)getFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError *error))completionHandler; + +- (void) presentNPS:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) wigetCallback; + +- (void) presentSurvey:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) wigetCallback; + +- (void) presentRating:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) wigetCallback; + +@property (nonatomic) NSString* message; +@property (nonatomic) NSString* dismissButtonTitle; +@property (nonatomic) NSUInteger sessionCount; +@property (nonatomic) BOOL disableAskingForEachAppVersion; +@property (nonatomic, copy) void (^ratingCompletionForAutoAsk)(NSInteger); +#endif +@end diff --git a/CountlyFeedbacksInternal.m b/CountlyFeedbacksInternal.m new file mode 100644 index 00000000..4a7bb9e2 --- /dev/null +++ b/CountlyFeedbacksInternal.m @@ -0,0 +1,568 @@ +// CountlyFeedbacks.m +// +// This code is provided under the MIT License. +// +// Please visit www.count.ly for more information. + +#import "CountlyCommon.h" +#if (TARGET_OS_IOS) +#import +#endif + +@interface CountlyFeedbackWidget () ++ (CountlyFeedbackWidget *)createWithDictionary:(NSDictionary *)dictionary; +@end + + + +@interface CountlyFeedbacksInternal () +#if (TARGET_OS_IOS) +@property (nonatomic) UIAlertController* alertController; +@property (nonatomic, copy) void (^ratingCompletion)(NSInteger); +#endif +@end + +NSString* const kCountlyReservedEventStarRating = @"[CLY]_star_rating"; +NSString* const kCountlyStarRatingStatusSessionCountKey = @"kCountlyStarRatingStatusSessionCountKey"; +NSString* const kCountlyStarRatingStatusHasEverAskedAutomatically = @"kCountlyStarRatingStatusHasEverAskedAutomatically"; + +NSString* const kCountlyFBKeyPlatform = @"platform"; +NSString* const kCountlyFBKeyAppVersion = @"app_version"; +NSString* const kCountlyFBKeyRating = @"rating"; +NSString* const kCountlyFBKeyWidgetID = @"widget_id"; +NSString* const kCountlyFBKeyID = @"_id"; +NSString* const kCountlyFBKeyTargetDevices = @"target_devices"; +NSString* const kCountlyFBKeyPhone = @"phone"; +NSString* const kCountlyFBKeyTablet = @"tablet"; +NSString* const kCountlyFBKeyFeedback = @"feedback"; +NSString* const kCountlyFBKeyEmail = @"email"; +NSString* const kCountlyFBKeyComment = @"comment"; +NSString* const kCountlyFBKeyContactMe = @"contactMe"; + +const CGFloat kCountlyStarRatingButtonSize = 40.0; + +@implementation CountlyFeedbacksInternal +#if (TARGET_OS_IOS) +{ + UIButton* btn_star[5]; +} + ++ (instancetype)sharedInstance +{ + if (!CountlyCommon.sharedInstance.hasStarted) + return nil; + + static CountlyFeedbacksInternal* s_sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{s_sharedInstance = self.new;}); + return s_sharedInstance; +} + +- (instancetype)init +{ + if (self = [super init]) + { + NSString* langDesignator = [NSLocale.preferredLanguages.firstObject substringToIndex:2]; + + NSDictionary* dictMessage = + @{ + @"en": @"How would you rate the app?", + @"tr": @"Uygulamayı nasıl değerlendirirsiniz?", + @"ja": @"あなたの評価を教えてください。", + @"zh": @"请告诉我你的评价。", + @"ru": @"Как бы вы оценили приложение?", + @"cz": @"Jak hodnotíte aplikaci?", + @"lv": @"Kā Jūs novērtētu šo lietotni?", + @"bn": @"আপনি কিভাবে এই এপ্লিক্যাশনটি মূল্যায়ন করবেন?", + @"hi": @"आप एप्लीकेशन का मूल्यांकन कैसे करेंगे?", + }; + + self.message = dictMessage[langDesignator]; + if (!self.message) + self.message = dictMessage[@"en"]; + } + + return self; +} + +#pragma mark - Star Rating + +- (void)showDialog:(void(^)(NSInteger rating))completion +{ + if (!CountlyConsentManager.sharedInstance.consentForFeedback) + return; + + self.ratingCompletion = completion; + + self.alertController = [UIAlertController alertControllerWithTitle:@" " message:self.message preferredStyle:UIAlertControllerStyleAlert]; + + CLYButton* dismissButton = [CLYButton dismissAlertButton]; + dismissButton.onClick = ^(id sender) + { + [self.alertController dismissViewControllerAnimated:YES completion:^ + { + [self finishWithRating:0]; + }]; + }; + [self.alertController.view addSubview:dismissButton]; + [dismissButton positionToTopRight]; + + CLYInternalViewController* cvc = CLYInternalViewController.new; + [cvc setPreferredContentSize:(CGSize){kCountlyStarRatingButtonSize * 5, kCountlyStarRatingButtonSize * 1.5}]; + [cvc.view addSubview:[self starView]]; + + @try + { + [self.alertController setValue:cvc forKey:@"contentViewController"]; + } + @catch (NSException* exception) + { + CLY_LOG_W(@"%s, UIAlertController's contentViewController can not be set, got exception %@", __FUNCTION__, exception); + } + + [CountlyCommon.sharedInstance tryPresentingViewController:self.alertController]; +} + +- (void)checkForStarRatingAutoAsk +{ + if (!self.sessionCount) + return; + + if (!CountlyConsentManager.sharedInstance.consentForFeedback) + return; + + NSMutableDictionary* status = [CountlyPersistency.sharedInstance retrieveStarRatingStatus].mutableCopy; + + if (self.disableAskingForEachAppVersion && status[kCountlyStarRatingStatusHasEverAskedAutomatically]) + return; + + NSString* keyForAppVersion = [kCountlyStarRatingStatusSessionCountKey stringByAppendingString:CountlyDeviceInfo.appVersion]; + NSInteger sessionCountSoFar = [status[keyForAppVersion] integerValue]; + sessionCountSoFar++; + + if (self.sessionCount == sessionCountSoFar) + { + CLY_LOG_D(@"Asking for star-rating as session count reached specified limit %d ...", (int)self.sessionCount); + + [self showDialog:self.ratingCompletionForAutoAsk]; + + status[kCountlyStarRatingStatusHasEverAskedAutomatically] = @YES; + } + + status[keyForAppVersion] = @(sessionCountSoFar); + + [CountlyPersistency.sharedInstance storeStarRatingStatus:status]; +} + +- (UIView *)starView +{ + UIView* vw_star = [UIView.alloc initWithFrame:(CGRect){0, 0, kCountlyStarRatingButtonSize * 5, kCountlyStarRatingButtonSize}]; + vw_star.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; + + for (int i = 0; i < 5; i++) + { + btn_star[i] = [UIButton.alloc initWithFrame:(CGRect){i * kCountlyStarRatingButtonSize, 0, kCountlyStarRatingButtonSize, kCountlyStarRatingButtonSize}]; + btn_star[i].titleLabel.font = [UIFont fontWithName:@"Helvetica" size:28]; + [btn_star[i] setTitle:@"★" forState:UIControlStateNormal]; + [btn_star[i] setTitleColor:[self passiveStarColor] forState:UIControlStateNormal]; + [btn_star[i] addTarget:self action:@selector(onClick_star:) forControlEvents:UIControlEventTouchUpInside]; + + [vw_star addSubview:btn_star[i]]; + } + + return vw_star; +} + +- (void)setMessage:(NSString *)message +{ + if (!message) + return; + + _message = message; +} + +- (void)onClick_star:(id)sender +{ + UIColor* color = [self activeStarColor]; + NSInteger rating = 0; + + for (int i = 0; i < 5; i++) + { + [btn_star[i] setTitleColor:color forState:UIControlStateNormal]; + + if (btn_star[i] == sender) + { + color = [self passiveStarColor]; + rating = i + 1; + } + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ + { + [self.alertController dismissViewControllerAnimated:YES completion:^{ [self finishWithRating:rating]; }]; + }); +} + +- (void)finishWithRating:(NSInteger)rating +{ + if (self.ratingCompletion) + self.ratingCompletion(rating); + + if (rating != 0) + { + NSMutableDictionary* segmentation = NSMutableDictionary.new; + segmentation[kCountlyFBKeyPlatform] = CountlyDeviceInfo.osName; + segmentation[kCountlyFBKeyAppVersion] = CountlyDeviceInfo.appVersion; + segmentation[kCountlyFBKeyRating] = @(rating); + + [Countly.sharedInstance recordReservedEvent:kCountlyReservedEventStarRating segmentation:segmentation]; + } + + self.alertController = nil; + self.ratingCompletion = nil; +} + +- (UIColor *)activeStarColor +{ + return [UIColor colorWithRed:253/255.0 green:148/255.0 blue:38/255.0 alpha:1]; +} + +- (UIColor *)passiveStarColor +{ + return [UIColor colorWithWhite:178/255.0 alpha:1]; +} + +#pragma mark - Feedbacks (Ratings) (Legacy Feedback Widget) + +- (void)presentRatingWidgetWithID:(NSString *)widgetID closeButtonText:(NSString *)closeButtonText completionHandler:(void (^)(NSError * error))completionHandler +{ + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'presentRatingWidgetWithID' is aborted: SDK Networking is disabled from server config!"); + return; + } + + if (!CountlyConsentManager.sharedInstance.consentForFeedback) + return; + + if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) + return; + + if (!widgetID.length) + return; + + NSURLRequest* feedbackWidgetCheckRequest = [self widgetCheckURLRequest:widgetID]; + NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:feedbackWidgetCheckRequest completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + { + NSDictionary* widgetInfo = nil; + + if (!error) + { + widgetInfo = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + } + + if (!error) + { + NSMutableDictionary* userInfo = widgetInfo.mutableCopy; + + if (![widgetInfo[kCountlyFBKeyID] isEqualToString:widgetID]) + { + userInfo[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Feedback widget with ID %@ is not available.", widgetID]; + error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbackWidgetNotAvailable userInfo:userInfo]; + } + else if (![self isDeviceTargetedByWidget:widgetInfo]) + { + userInfo[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Feedback widget with ID %@ does not include this device in target devices list.", widgetID]; + error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbackWidgetNotTargetedForDevice userInfo:userInfo]; + } + } + + if (error) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + if (completionHandler) + completionHandler(error); + }); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^ + { + [self presentRatingWidgetInternal:widgetID closeButtonText:closeButtonText completionHandler:completionHandler]; + }); + }]; + + [task resume]; +} + +- (void)presentRatingWidgetInternal:(NSString *)widgetID closeButtonText:(NSString *)closeButtonText completionHandler:(void (^)(NSError * error))completionHandler +{ + __block CLYInternalViewController* webVC = CLYInternalViewController.new; + webVC.view.backgroundColor = UIColor.whiteColor; + webVC.view.bounds = UIScreen.mainScreen.bounds; + webVC.modalPresentationStyle = UIModalPresentationCustom; + + WKWebView* webView = [WKWebView.alloc initWithFrame:webVC.view.bounds]; + webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [webVC.view addSubview:webView]; + NSURL* widgetDisplayURL = [self widgetDisplayURL:widgetID]; + [webView loadRequest:[NSURLRequest requestWithURL:widgetDisplayURL]]; + + CLYButton* dismissButton = [CLYButton dismissAlertButton:closeButtonText]; + dismissButton.onClick = ^(id sender) + { + [webVC dismissViewControllerAnimated:YES completion:^ + { + if (completionHandler) + completionHandler(nil); + + webVC = nil; + }]; + }; + [webVC.view addSubview:dismissButton]; + [dismissButton positionToTopRightConsideringStatusBar]; + + [CountlyCommon.sharedInstance tryPresentingViewController:webVC]; +} + +- (NSURLRequest *)widgetCheckURLRequest:(NSString *)widgetID +{ + NSString* queryString = [CountlyConnectionManager.sharedInstance queryEssentials]; + + queryString = [queryString stringByAppendingFormat:@"&%@=%@", kCountlyFBKeyWidgetID, widgetID]; + + queryString = [queryString stringByAppendingFormat:@"&%@=%@", + kCountlyAppVersionKey, CountlyDeviceInfo.appVersion]; + + queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; + + NSString* serverOutputFeedbackWidgetEndpoint = [CountlyConnectionManager.sharedInstance.host stringByAppendingFormat:@"%@%@%@", + kCountlyEndpointO, + kCountlyEndpointFeedback, + kCountlyEndpointWidget]; + + if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) + { + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverOutputFeedbackWidgetEndpoint]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [queryString cly_dataUTF8]; + return request.copy; + } + else + { + NSString* withQueryString = [serverOutputFeedbackWidgetEndpoint stringByAppendingFormat:@"?%@", queryString]; + NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:withQueryString]]; + return request; + } +} + +- (NSURL *)widgetDisplayURL:(NSString *)widgetID +{ + NSString* queryString = [CountlyConnectionManager.sharedInstance queryEssentials]; + + queryString = [queryString stringByAppendingFormat:@"&%@=%@&%@=%@", + kCountlyFBKeyWidgetID, widgetID, + kCountlyFBKeyAppVersion, CountlyDeviceInfo.appVersion]; + + queryString = [queryString stringByAppendingFormat:@"&%@=%@", + kCountlyAppVersionKey, CountlyDeviceInfo.appVersion]; + + queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; + + NSString* URLString = [NSString stringWithFormat:@"%@%@?%@", + CountlyConnectionManager.sharedInstance.host, + kCountlyEndpointFeedback, + queryString]; + + return [NSURL URLWithString:URLString]; +} + +- (BOOL)isDeviceTargetedByWidget:(NSDictionary *)widgetInfo +{ + BOOL isTablet = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad; + BOOL isPhone = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone; + BOOL isTabletTargeted = [widgetInfo[kCountlyFBKeyTargetDevices][kCountlyFBKeyTablet] boolValue]; + BOOL isPhoneTargeted = [widgetInfo[kCountlyFBKeyTargetDevices][kCountlyFBKeyPhone] boolValue]; + + return ((isTablet && isTabletTargeted) || (isPhone && isPhoneTargeted)); +} + +- (void)recordRatingWidgetWithID:(NSString *)widgetID rating:(NSInteger)rating email:(NSString *)email comment:(NSString *)comment userCanBeContacted:(BOOL)userCanBeContacted +{ + if (!CountlyConsentManager.sharedInstance.consentForFeedback) + return; + + if (!widgetID.length) + return; + + NSMutableDictionary* segmentation = NSMutableDictionary.new; + segmentation[kCountlyFBKeyPlatform] = CountlyDeviceInfo.osName; + segmentation[kCountlyFBKeyAppVersion] = CountlyDeviceInfo.appVersion; + segmentation[kCountlyFBKeyRating] = @(rating); + segmentation[kCountlyFBKeyWidgetID] = widgetID; + segmentation[kCountlyFBKeyEmail] = email; + segmentation[kCountlyFBKeyComment] = comment; + segmentation[kCountlyFBKeyContactMe] = @(userCanBeContacted); + + [Countly.sharedInstance recordReservedEvent:kCountlyReservedEventStarRating segmentation:segmentation]; +} + + +#pragma mark - Feedbacks (Surveys, NPS) + +- (void)getFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError *error))completionHandler +{ + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'getFeedbackWidgets' is aborted: SDK Networking is disabled from server config!"); + return; + } + + if (!CountlyConsentManager.sharedInstance.consentForFeedback) + return; + + if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) + return; + + NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self feedbacksRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + { + NSDictionary *feedbacksResponse = nil; + + if (!error) + { + feedbacksResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + } + + if (!error) + { + if (((NSHTTPURLResponse*)response).statusCode != 200) + { + NSMutableDictionary* userInfo = feedbacksResponse.mutableCopy; + userInfo[NSLocalizedDescriptionKey] = @"Feedbacks general API error"; + error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbacksGeneralAPIError userInfo:userInfo]; + } + } + + if (error) + { + dispatch_async(dispatch_get_main_queue(), ^ + { + if (completionHandler) + completionHandler(nil, error); + }); + + return; + } + + NSMutableArray* feedbacks = NSMutableArray.new; + NSArray* rawFeedbackObjects = feedbacksResponse[@"result"]; + for (NSDictionary * feedbackDict in rawFeedbackObjects) + { + CountlyFeedbackWidget *feedback = [CountlyFeedbackWidget createWithDictionary:feedbackDict]; + if (feedback) + [feedbacks addObject:feedback]; + } + + dispatch_async(dispatch_get_main_queue(), ^ + { + if (completionHandler) + completionHandler([NSArray arrayWithArray:feedbacks], nil); + }); + }]; + + [task resume]; +} + +- (NSURLRequest *)feedbacksRequest +{ + NSString* queryString = [NSString stringWithFormat:@"%@=%@&%@=%@&%@=%@&%@=%@&%@=%@", + kCountlyQSKeyMethod, kCountlyFBKeyFeedback, + kCountlyQSKeyAppKey, CountlyConnectionManager.sharedInstance.appKey.cly_URLEscaped, + kCountlyQSKeyDeviceID, CountlyDeviceInfo.sharedInstance.deviceID.cly_URLEscaped, + kCountlyQSKeySDKName, CountlyCommon.sharedInstance.SDKName, + kCountlyQSKeySDKVersion, CountlyCommon.sharedInstance.SDKVersion]; + + queryString = [queryString stringByAppendingFormat:@"&%@=%@", + kCountlyAppVersionKey, CountlyDeviceInfo.appVersion]; + + queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; + + NSMutableString* URL = CountlyConnectionManager.sharedInstance.host.mutableCopy; + [URL appendString:kCountlyEndpointO]; + [URL appendString:kCountlyEndpointSDK]; + + if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) + { + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [queryString cly_dataUTF8]; + return request.copy; + } + else + { + [URL appendFormat:@"?%@", queryString]; + NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]]; + return request; + } +} + +- (void) presentNPS:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { + CLY_LOG_D(@"Presenting NPS widget with nameIDorTag: %@ and WidgetCallback: %@", nameIDorTag, widgetCallback); + [self presentFeedbackWidget:CLYFeedbackWidgetTypeNPS nameIDorTag:nameIDorTag widgetCallback:widgetCallback]; +} + +- (void) presentSurvey:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { + CLY_LOG_D(@"Presenting Survey widget with nameIDorTag: %@ and WidgetCallback: %@", nameIDorTag, widgetCallback); + [self presentFeedbackWidget:CLYFeedbackWidgetTypeSurvey nameIDorTag:nameIDorTag widgetCallback:widgetCallback]; +} + +- (void) presentRating:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { + CLY_LOG_D(@"Presenting Rating widget with nameIDorTag: %@ and WidgetCallback: %@", nameIDorTag, widgetCallback); + [self presentFeedbackWidget:CLYFeedbackWidgetTypeRating nameIDorTag:nameIDorTag widgetCallback:widgetCallback]; +} + + +-(void)presentFeedbackWidget:(CLYFeedbackWidgetType)widgetType nameIDorTag:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback { + [Countly.sharedInstance getFeedbackWidgets:^(NSArray *feedbackWidgets, NSError *error) { + if (error) { + CLY_LOG_D(@"Getting widgets list failed. Error: %@", error); + return; + } + + CLY_LOG_D(@"Successfully retrieved feedback widgets. Total widgets count: %lu", (unsigned long)feedbackWidgets.count); + + NSPredicate *typePredicate = [NSPredicate predicateWithFormat:@"type == %@", widgetType]; + NSArray *filteredWidgets = [feedbackWidgets filteredArrayUsingPredicate:typePredicate]; + + CLY_LOG_D(@"Filtered widgets count for type '%@': %lu", widgetType, (unsigned long)filteredWidgets.count); + + CountlyFeedbackWidget *widgetToPresent = nil; + + if (nameIDorTag && nameIDorTag.length > 0) { + for (CountlyFeedbackWidget *feedbackWidget in filteredWidgets) { + if ([nameIDorTag isEqualToString:feedbackWidget.name] || + [nameIDorTag isEqualToString:feedbackWidget.ID] || + [feedbackWidget.tags containsObject:nameIDorTag]) { + widgetToPresent = feedbackWidget; + CLY_LOG_D(@"Exact match found for nameIDorTag '%@'. Widget ID: %@, Name: %@", nameIDorTag, feedbackWidget.ID, feedbackWidget.name); + break; + } + } + } + + if (!widgetToPresent && filteredWidgets.count > 0) { + widgetToPresent = filteredWidgets.firstObject; + CLY_LOG_D(@"No exact match found for nameIDorTag '%@'. Falling back to the first widget of type '%@'. Widget ID: %@, Name: %@", nameIDorTag, widgetType, widgetToPresent.ID, widgetToPresent.name); + } + + if (widgetToPresent) { + [widgetToPresent presentWithCallback:widgetCallback]; + } else { + CLY_LOG_D(@"No feedback widget found for the specified type: %@", widgetType); + } + }]; +} + +#endif +@end diff --git a/CountlyFeedbacksNew.h b/CountlyFeedbacksNew.h deleted file mode 100644 index 1c253cd9..00000000 --- a/CountlyFeedbacksNew.h +++ /dev/null @@ -1,30 +0,0 @@ -// CountlyFeedbacks.h -// -// This code is provided under the MIT License. -// -// Please visit www.count.ly for more information. -// - -#import -#import "CountlyConfig.h" -#import "CountlyFeedbackWidget.h" - -@interface CountlyFeedbacksNew: NSObject -#if (TARGET_OS_IOS) -+ (instancetype)sharedInstance; - -- (void) presentNPS; -- (void) presentNPS:(NSString *)nameIDorTag; -- (void) presentNPS:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback; - -- (void) presentSurvey; -- (void) presentSurvey:(NSString *)nameIDorTag; -- (void) presentSurvey:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback; - -- (void) presentRating; -- (void) presentRating:(NSString *)nameIDorTag; -- (void) presentRating:(NSString *)nameIDorTag widgetCallback:(WidgetCallback) widgetCallback; - -- (void)getAvailableFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError * error))completionHandler; -#endif -@end