diff --git a/WebEngageAppEx.podspec b/WebEngageAppEx.podspec index 3228dbc..6619a04 100644 --- a/WebEngageAppEx.podspec +++ b/WebEngageAppEx.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |spec| spec.name = 'WebEngageAppEx' - spec.version = '1.0.2' + spec.version = '1.0.3' spec.summary = 'App Extension Target SDK for WebEngage for Rich Push Notifications support.' diff --git a/WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.h b/WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.h new file mode 100755 index 0000000..1b58c7d --- /dev/null +++ b/WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.h @@ -0,0 +1,32 @@ +#import + +/** + UIImage (animatedGIF) + + This category adds class methods to `UIImage` to create an animated `UIImage` from an animated GIF. +*/ +@interface UIImage (animatedGIF) + +/* + UIImage *animation = [UIImage animatedImageWithAnimatedGIFData:theData]; + + I interpret `theData` as a GIF. I create an animated `UIImage` using the source images in the GIF. + + The GIF stores a separate duration for each frame, in units of centiseconds (hundredths of a second). However, a `UIImage` only has a single, total `duration` property, which is a floating-point number. + + To handle this mismatch, I add each source image (from the GIF) to `animation` a varying number of times to match the ratios between the frame durations in the GIF. + + For example, suppose the GIF contains three frames. Frame 0 has duration 3. Frame 1 has duration 9. Frame 2 has duration 15. I divide each duration by the greatest common denominator of all the durations, which is 3, and add each frame the resulting number of times. Thus `animation` will contain frame 0 3/3 = 1 time, then frame 1 9/3 = 3 times, then frame 2 15/3 = 5 times. I set `animation.duration` to (3+9+15)/100 = 0.27 seconds. +*/ ++ (UIImage * _Nullable)animatedImageWithAnimatedGIFData:(NSData * _Nonnull)theData; + +/* + UIImage *image = [UIImage animatedImageWithAnimatedGIFURL:theURL]; + + I interpret the contents of `theURL` as a GIF. I create an animated `UIImage` using the source images in the GIF. + + I operate exactly like `+[UIImage animatedImageWithAnimatedGIFData:]`, except that I read the data from `theURL`. If `theURL` is not a `file:` URL, you probably want to call me on a background thread or GCD queue to avoid blocking the main thread. +*/ ++ (UIImage * _Nullable)animatedImageWithAnimatedGIFURL:(NSURL * _Nonnull)theURL; + +@end diff --git a/WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.m b/WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.m new file mode 100755 index 0000000..871d11e --- /dev/null +++ b/WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.m @@ -0,0 +1,119 @@ +#import "UIImage+animatedGIF.h" +#import + +#if __has_feature(objc_arc) +#define toCF (__bridge CFTypeRef) +#define fromCF (__bridge id) +#else +#define toCF (CFTypeRef) +#define fromCF (id) +#endif + +@implementation UIImage (animatedGIF) + +static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) { + int delayCentiseconds = 1; + CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL); + if (properties) { + CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); + if (gifProperties) { + NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime); + if (number == NULL || [number doubleValue] == 0) { + number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime); + } + if ([number doubleValue] > 0) { + // Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us. + delayCentiseconds = (int)lrint([number doubleValue] * 100); + } + } + CFRelease(properties); + } + return delayCentiseconds; +} + +static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) { + for (size_t i = 0; i < count; ++i) { + imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL); + delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i); + } +} + +static int sum(size_t const count, int const *const values) { + int theSum = 0; + for (size_t i = 0; i < count; ++i) { + theSum += values[i]; + } + return theSum; +} + +static int pairGCD(int a, int b) { + if (a < b) + return pairGCD(b, a); + while (true) { + int const r = a % b; + if (r == 0) + return b; + a = b; + b = r; + } +} + +static int vectorGCD(size_t const count, int const *const values) { + int gcd = values[0]; + for (size_t i = 1; i < count; ++i) { + // Note that after I process the first few elements of the vector, `gcd` will probably be smaller than any remaining element. By passing the smaller value as the second argument to `pairGCD`, I avoid making it swap the arguments. + gcd = pairGCD(values[i], gcd); + } + return gcd; +} + +static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) { + int const gcd = vectorGCD(count, delayCentiseconds); + size_t const frameCount = totalDurationCentiseconds / gcd; + UIImage *frames[frameCount]; + for (size_t i = 0, f = 0; i < count; ++i) { + UIImage *const frame = [UIImage imageWithCGImage:images[i]]; + for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) { + frames[f++] = frame; + } + } + return [NSArray arrayWithObjects:frames count:frameCount]; +} + +static void releaseImages(size_t const count, CGImageRef const images[count]) { + for (size_t i = 0; i < count; ++i) { + CGImageRelease(images[i]); + } +} + +static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) { + size_t const count = CGImageSourceGetCount(source); + CGImageRef images[count]; + int delayCentiseconds[count]; // in centiseconds + createImagesAndDelays(source, count, images, delayCentiseconds); + int const totalDurationCentiseconds = sum(count, delayCentiseconds); + NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds); + UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0]; + releaseImages(count, images); + return animation; +} + +static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) { + if (source) { + UIImage *const image = animatedImageWithAnimatedGIFImageSource(source); + CFRelease(source); + return image; + } else { + return nil; + } +} + ++ (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data { + return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL)); +} + ++ (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url { + return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL)); +} + +@end diff --git a/WebEngageAppEx/Classes/ContentExtension/WEXBannerPushNotificationViewController.m b/WebEngageAppEx/Classes/ContentExtension/WEXBannerPushNotificationViewController.m index a10c399..786c524 100644 --- a/WebEngageAppEx/Classes/ContentExtension/WEXBannerPushNotificationViewController.m +++ b/WebEngageAppEx/Classes/ContentExtension/WEXBannerPushNotificationViewController.m @@ -8,6 +8,7 @@ #import "WEXBannerPushNotificationViewController.h" #import "WEXRichPushNotificationViewController+Private.h" #import "UIColor+DarkMode.h" +#import "UIImage+animatedGIF.h" #define CONTENT_PADDING 10 #define TITLE_BODY_SPACE 5 @@ -69,10 +70,8 @@ - (void)setupBannerImageView { if ([attachment.URL startAccessingSecurityScopedResource]) { NSData *imageData = [NSData dataWithContentsOfFile:attachment.URL.path]; - UIImage *image = [UIImage imageWithData:imageData]; - + UIImage *image = [UIImage animatedImageWithAnimatedGIFData:imageData]; [attachment.URL stopAccessingSecurityScopedResource]; - if (image) { imageView.image = image; } else { diff --git a/WebEngageAppEx/Classes/ContentExtension/WEXCarouselPushNotificationViewController.m b/WebEngageAppEx/Classes/ContentExtension/WEXCarouselPushNotificationViewController.m index ae427df..e0675cb 100644 --- a/WebEngageAppEx/Classes/ContentExtension/WEXCarouselPushNotificationViewController.m +++ b/WebEngageAppEx/Classes/ContentExtension/WEXCarouselPushNotificationViewController.m @@ -9,6 +9,7 @@ #import "WEXCarouselPushNotificationViewController.h" #import "WEXRichPushNotificationViewController+Private.h" #import "UIColor+DarkMode.h" +#import "UIImage+animatedGIF.h" #define CONTENT_PADDING 10 #define TITLE_BODY_SPACE 5 @@ -81,7 +82,7 @@ - (void)didReceiveNotification:(UNNotification *)notification API_AVAILABLE(ios( NSString *imageURL = self.carouselItems[0][@"image"]; NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]; - UIImage *image = [UIImage imageWithData:imageData]; + UIImage *image = [UIImage animatedImageWithAnimatedGIFData:imageData]; if (image) { [self.images addObject:image]; @@ -128,7 +129,7 @@ - (void)didReceiveNotification:(UNNotification *)notification API_AVAILABLE(ios( if ([attachment.URL startAccessingSecurityScopedResource]) { NSData *imageData = [NSData dataWithContentsOfFile:attachment.URL.path]; - UIImage *image = [UIImage imageWithData:imageData]; + UIImage *image = [UIImage animatedImageWithAnimatedGIFData:imageData]; [attachment.URL stopAccessingSecurityScopedResource]; diff --git a/WebEngageAppEx/Classes/ContentExtension/WEXRatingPushNotificationViewController.m b/WebEngageAppEx/Classes/ContentExtension/WEXRatingPushNotificationViewController.m index c66666d..de53a0f 100644 --- a/WebEngageAppEx/Classes/ContentExtension/WEXRatingPushNotificationViewController.m +++ b/WebEngageAppEx/Classes/ContentExtension/WEXRatingPushNotificationViewController.m @@ -8,6 +8,7 @@ #import "WEXRatingPushNotificationViewController.h" #import "UIColor+DarkMode.h" +#import "UIImage+animatedGIF.h" //#define NO_OF_STARS 5 #define STAR_BAR_HEIGHT 50 @@ -146,7 +147,7 @@ - (void)initialiseViewHierarchy { if ([attachment.URL startAccessingSecurityScopedResource]) { NSData *imageData = [NSData dataWithContentsOfFile:attachment.URL.path]; - UIImage *image = [UIImage imageWithData:imageData]; + UIImage *image = [UIImage animatedImageWithAnimatedGIFData:imageData]; [attachment.URL stopAccessingSecurityScopedResource]; diff --git a/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController+Private.h b/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController+Private.h index 655063d..0392260 100644 --- a/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController+Private.h +++ b/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController+Private.h @@ -28,6 +28,9 @@ - (NSAttributedString *)getHtmlParsedString:(NSString *)textString isTitle:(BOOL)isTitle bckColor:(NSString *)bckColor; +- (void)setExtensionDefaults; +- (NSUserDefaults *)getSharedUserDefaults; + #endif @end diff --git a/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController.m b/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController.m index 29631e3..c480d16 100644 --- a/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController.m +++ b/WebEngageAppEx/Classes/ContentExtension/WEXRichPushNotificationViewController.m @@ -16,6 +16,8 @@ #import #import "NSMutableAttributedString+Additions.h" +#define WEX_CONTENT_EXTENSION_VERSION @"1.0.3" + API_AVAILABLE(ios(10.0)) @interface WEXRichPushNotificationViewController () @@ -95,7 +97,7 @@ - (void)didReceiveNotification:(UNNotification *)notification API_AVAILABLE(ios self.notification = notification; self.isRendering = YES; [self updateDarkModeStatus]; - + [self setExtensionDefaults]; NSString *appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WEX_APP_GROUP"]; if (!appGroup) { @@ -131,6 +133,40 @@ - (void)didReceiveNotification:(UNNotification *)notification API_AVAILABLE(ios } } +- (void)setExtensionDefaults { + NSUserDefaults *sharedDefaults = [self getSharedUserDefaults]; + // Write operation only if key is not present in the UserDefaults + if ([sharedDefaults valueForKey:@"WEG_Content_Extension_Version"] == nil) { + [sharedDefaults setValue:WEX_CONTENT_EXTENSION_VERSION forKey:@"WEG_Content_Extension_Version"]; + [sharedDefaults synchronize]; + } +} + +- (NSUserDefaults *)getSharedUserDefaults { + + NSString *appGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WEX_APP_GROUP"]; + + if (!appGroup) { + NSBundle *bundle = [NSBundle mainBundle]; + + if ([[bundle.bundleURL pathExtension] isEqualToString:@"appex"]) { + bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]]; + } + + NSString *bundleIdentifier = [bundle objectForInfoDictionaryKey:@"CFBundleIdentifier"]; + + appGroup = [NSString stringWithFormat:@"group.%@.WEGNotificationGroup", bundleIdentifier]; + } + + NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:appGroup]; + + if (!defaults) { + NSLog(@"Shared User Defaults could not be initialized. Ensure Shared App Groups have been enabled on Main App & Notification Service Extension Targets."); + } + + return defaults; +} + - (WEXRichPushLayout *)layoutForStyle:(NSString *)style { if (style && [style isEqualToString:@"CAROUSEL_V1"]) { diff --git a/WebEngageAppEx/Classes/NotificationService/WEXPushNotificationService.m b/WebEngageAppEx/Classes/NotificationService/WEXPushNotificationService.m index 4ae81da..20364a0 100644 --- a/WebEngageAppEx/Classes/NotificationService/WEXPushNotificationService.m +++ b/WebEngageAppEx/Classes/NotificationService/WEXPushNotificationService.m @@ -9,6 +9,7 @@ #import "WEXPushNotificationService.h" #import +#define WEX_SERVICE_EXTENSION_VERSION @"1.0.3" @interface WEXPushNotificationService () @@ -16,8 +17,10 @@ @interface WEXPushNotificationService () @property (nonatomic) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic) UNMutableNotificationContent *bestAttemptContent; @property (nonatomic) NSString *enviroment; +@property NSString *serviceExtensionVersion; @property NSDictionary *sharedUserDefaults; @property NSArray *customCategories; + #endif @end @@ -312,6 +315,10 @@ - (void)setExtensionDefaults { [sharedDefaults setValue:@"WEG" forKey:@"WEG_ServiceToApp"]; [sharedDefaults synchronize]; } + if ([sharedDefaults valueForKey:@"WEG_Service_Extension_Version"] == nil) { + [sharedDefaults setValue:WEX_SERVICE_EXTENSION_VERSION forKey:@"WEG_Service_Extension_Version"]; + [sharedDefaults synchronize]; + } } - (NSString *) getBaseURL{ @@ -329,6 +336,8 @@ - (NSString *) getBaseURL{ } else if ([self.enviroment.uppercaseString isEqualToString:@"UNL"]) { baseURL = @"https://c.unl.webengage.com/tracker"; + }else if ([self.enviroment.uppercaseString isEqualToString:@"KSA"]) { + baseURL = @"https://c.ksa.webengage.com/tracker"; } return baseURL; diff --git a/WebEngageBannerPush.podspec b/WebEngageBannerPush.podspec index 30b59a9..9c866ab 100644 --- a/WebEngageBannerPush.podspec +++ b/WebEngageBannerPush.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |spec| spec.name = 'WebEngageBannerPush' - spec.version = '1.0.2' + spec.version = '1.0.3' spec.summary = 'Extension Target SDK for adding WebEngage Rich Push Notifications support' spec.description = <<-DESC