Skip to content

Commit

Permalink
Merge branch 'hotfix/1.0.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bhavesh Sarwar committed Jan 10, 2024
2 parents 4083a79 + 1b31586 commit 3badc9a
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 9 deletions.
2 changes: 1 addition & 1 deletion WebEngageAppEx.podspec
Original file line number Diff line number Diff line change
@@ -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.'

Expand Down
32 changes: 32 additions & 0 deletions WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#import <UIKit/UIKit.h>

/**
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
119 changes: 119 additions & 0 deletions WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#import "UIImage+animatedGIF.h"
#import <ImageIO/ImageIO.h>

#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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

- (NSAttributedString *)getHtmlParsedString:(NSString *)textString isTitle:(BOOL)isTitle bckColor:(NSString *)bckColor;

- (void)setExtensionDefaults;
- (NSUserDefaults *)getSharedUserDefaults;

#endif

@end
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#import <WebEngageAppEx/WEXRichPushNotificationViewController.h>
#import "NSMutableAttributedString+Additions.h"

#define WEX_CONTENT_EXTENSION_VERSION @"1.0.3"

API_AVAILABLE(ios(10.0))
@interface WEXRichPushNotificationViewController ()

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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"]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
#import "WEXPushNotificationService.h"
#import <UserNotifications/UserNotifications.h>

#define WEX_SERVICE_EXTENSION_VERSION @"1.0.3"

@interface WEXPushNotificationService ()

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
@property (nonatomic) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic) UNMutableNotificationContent *bestAttemptContent;
@property (nonatomic) NSString *enviroment;
@property NSString *serviceExtensionVersion;
@property NSDictionary<NSString *, NSString *> *sharedUserDefaults;
@property NSArray *customCategories;

#endif

@end
Expand Down Expand Up @@ -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{
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion WebEngageBannerPush.podspec
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 3badc9a

Please sign in to comment.