Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(EMI-2237): Update iOS in-app push notification style #11334

Merged
merged 3 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ios/Artsy/App/ARAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ - (void)setupForAppLaunch:(NSDictionary *)launchOptions
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupForAppLaunch:launchOptions];

[self setupAnalytics:application withLaunchOptions:launchOptions];

UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = [self remoteNotificationsDelegate];

[[FBSDKApplicationDelegate sharedInstance] application:application
didFinishLaunchingWithOptions:launchOptions];
Expand Down
2 changes: 1 addition & 1 deletion ios/Artsy/App/ARAppNotificationsDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#import <React/RCTDevSettings.h>
#import <UserNotifications/UNUserNotificationCenter.h>

@interface ARAppNotificationsDelegate : NSObject <JSApplicationRemoteNotificationsDelegate>
@interface ARAppNotificationsDelegate : NSObject <JSApplicationRemoteNotificationsDelegate, UNUserNotificationCenterDelegate>

typedef NS_ENUM(NSInteger, ARAppNotificationsRequestContext) {
ARAppNotificationsRequestContextLaunch,
Expand Down
47 changes: 26 additions & 21 deletions ios/Artsy/App/ARAppNotificationsDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ - (void)application:(UIApplication *)application didRegisterForRemoteNotificatio
// http://stackoverflow.com/questions/9372815/how-can-i-convert-my-device-token-nsdata-into-an-nsstring
const unsigned *tokenBytes = [deviceTokenData bytes];
NSString *deviceToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];

ARActionLog(@"Got device notification token: %@", deviceToken);
NSString *previousToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARAPNSDeviceTokenKey];
Expand Down Expand Up @@ -127,7 +127,6 @@ - (void)applicationDidReceiveRemoteNotification:(NSDictionary *)userInfo inAppli
[notificationInfo setObject:uiApplicationState forKey:@"UIApplicationState"];

NSString *url = userInfo[@"url"];
id message = userInfo[@"aps"][@"alert"] ?: url;
BOOL isConversation = url && [[[NSURL URLWithString:url] path] hasPrefix:@"/conversation/"];

if (isConversation) {
Expand All @@ -138,24 +137,10 @@ - (void)applicationDidReceiveRemoteNotification:(NSDictionary *)userInfo inAppli
// A notification was received while the app is in the background.
[self receivedNotification:notificationInfo];

} else {

if (applicationState == UIApplicationStateActive) {
// A notification was received while the app was already active, so we show our own notification view.
[self receivedNotification:notificationInfo];


NSString *title = [message isKindOfClass:[NSString class]] ? message : message[@"title"];
[ARNotificationView showNoticeInView:[self findVisibleWindow]
title:title
response:^{
[self tappedNotification:notificationInfo url:url];
}];
} else if (applicationState == UIApplicationStateInactive) {
// The user tapped a notification while the app was in background.
[self tappedNotification:notificationInfo url:url];

} else if (applicationState == UIApplicationStateInactive) {
// The user tapped a notification while the app was in background.
[self tappedNotification:notificationInfo url:url];
}
}
}

Expand Down Expand Up @@ -220,4 +205,24 @@ - (BOOL)tokensAreTheSame:(NSString *)newToken previousToken:(NSString * _Nullabl
}
}

// Handle the notification view on when the app is in the foreground
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{

NSDictionary *userInfo = notification.request.content.userInfo;
NSMutableDictionary *notificationInfo = [[NSMutableDictionary alloc] initWithDictionary:userInfo];

[self receivedNotification:notificationInfo];
completionHandler(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge);
}

// Handle the tapping on the notification when the app in the foreground
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{

NSDictionary *userInfo = response.notification.request.content.userInfo;
NSMutableDictionary *notificationInfo = [[NSMutableDictionary alloc] initWithDictionary:userInfo];

[self tappedNotification:notificationInfo url:userInfo[@"url"]];
completionHandler();
}

@end
93 changes: 63 additions & 30 deletions ios/ArtsyTests/App_Tests/ARAppNotificationsDelegateTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
#import "UIApplicationStateEnum.h"
#import "AREmission.h"
#import <Analytics/SEGAnalytics.h>
#import "UserNotifications/UNNotification.h"
#import "UserNotifications/UNNotificationRequest.h"
#import "UserNotifications/UNNotificationContent.h"

static NSDictionary *
DictionaryWithAppState(NSDictionary *input, UIApplicationState appState)
{
NSMutableDictionary *dictionary = [input mutableCopy];
dictionary[@"UIApplicationState"] = [UIApplicationStateEnum toString:appState];
if (appState != -1) {
dictionary[@"UIApplicationState"] = [UIApplicationStateEnum toString:appState];
}
return [dictionary copy];
}

Expand All @@ -31,6 +36,9 @@ - (UIViewController *)getGlobalTopViewController;
__block UIApplicationState appState = -1;
__block id mockEmissionSharedInstance = nil;
__block id mockSegmentSharedInstance = nil;
__block UNNotification *unNotification = nil;
__block UNUserNotificationCenter *currentCenter = nil;
__block void (^completionHandler)(UNNotificationPresentationOptions) = ^(UNNotificationPresentationOptions options) {};

beforeEach(^{
app = [UIApplication sharedApplication];
Expand All @@ -48,12 +56,19 @@ - (UIViewController *)getGlobalTopViewController;
[mockSegmentSharedInstance stopMocking];
});

sharedExamplesFor(@"when receiving a notification", ^(id _) {
sharedExamplesFor(@"when receiving a notification", ^(NSDictionary *prefs) {
it(@"triggers an analytics event for receiving a notification", ^{

BOOL isInForeground = [prefs[@"isInForeground"] boolValue];

[[mockEmissionSharedInstance expect] sendEvent:ARAnalyticsNotificationReceived traits:DictionaryWithAppState(notification, appState)];
[[mockEmissionSharedInstance reject] sendEvent:ARAnalyticsNotificationTapped traits:OCMOCK_ANY];

[delegate applicationDidReceiveRemoteNotification:notification inApplicationState:appState];
if (isInForeground) {
[delegate userNotificationCenter:currentCenter willPresentNotification:unNotification withCompletionHandler:completionHandler];
} else {
[delegate applicationDidReceiveRemoteNotification:notification inApplicationState:appState];
}

[mockEmissionSharedInstance verify];
});
Expand Down Expand Up @@ -102,46 +117,64 @@ - (UIViewController *)getGlobalTopViewController;

describe(@"running in the foreground", ^{
beforeEach(^{
appState = UIApplicationStateActive;
appState = -1;
currentCenter = [UNUserNotificationCenter currentNotificationCenter];

// mock UserNotification notificaiton
unNotification = OCMClassMock([UNNotification class]);
UNNotificationRequest *request = OCMClassMock([UNNotificationRequest class]);
UNNotificationContent *content = OCMClassMock([UNMutableNotificationContent class]);
NSDictionary *userInfo = notification;

OCMStub([unNotification request]).andReturn(request);
OCMStub([request content]).andReturn(content);
OCMStub([content userInfo]).andReturn(userInfo);
});

itBehavesLike(@"when receiving a notification", nil);
itBehavesLike(@"when receiving a notification", @{@"isInForeground": @true});

it(@"triggers an analytics event when a notification has been tapped", ^{
[[mockEmissionSharedInstance stub] sendEvent:ARAnalyticsNotificationReceived traits:OCMOCK_ANY];
[[mockEmissionSharedInstance expect] sendEvent:ARAnalyticsNotificationTapped traits:DictionaryWithAppState(notification, appState)];

it(@"displays a notification", ^{
id mock = [OCMockObject mockForClass:[ARNotificationView class]];
[[mock expect] showNoticeInView:OCMOCK_ANY title:@"New Works For You" response:OCMOCK_ANY];
UNNotificationResponse *response = OCMClassMock([UNNotificationResponse class]);
OCMStub([response notification]).andReturn(unNotification);
void (^completionHandler)(void) = ^() {};

[delegate applicationDidReceiveRemoteNotification:notification inApplicationState:appState];
[delegate userNotificationCenter:currentCenter didReceiveNotificationResponse:response withCompletionHandler:completionHandler];

[mock verify];
[mock stopMocking];

[mockEmissionSharedInstance verify];
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was removed since we won't use the ARNotificationView class to show the notification anymore


it(@"triggers an analytics event when a notification has been tapped", ^{
id mockNotificationView = [OCMockObject mockForClass:[ARNotificationView class]];
[[mockNotificationView stub] showNoticeInView:OCMOCK_ANY
title:OCMOCK_ANY
response:[OCMArg checkWithBlock:^(dispatch_block_t callback) {
callback();
return YES;
}]];
it(@"handles notification with ab_uri", ^{
// Notification With APPBOY Uri
NSDictionary *notification = @{
@"aps": @{ @"alert": @"New Works For You", @"badge": @(42), @"content-available": @(1) },
@"ab_uri": @"http://artsy.net/works-for-you",
};

NSMutableDictionary *expectedNotification = [notification mutableCopy];
[expectedNotification removeObjectForKey:@"ab_uri"];
expectedNotification[@"url"] = notification[@"ab_uri"];

[[mockEmissionSharedInstance stub] sendEvent:ARAnalyticsNotificationReceived traits:OCMOCK_ANY];
[[mockEmissionSharedInstance expect] sendEvent:ARAnalyticsNotificationTapped traits:DictionaryWithAppState(notification, appState)];
[[mockEmissionSharedInstance expect] sendEvent:ARAnalyticsNotificationTapped traits:DictionaryWithAppState(expectedNotification, appState)];

[delegate applicationDidReceiveRemoteNotification:notification inApplicationState:appState];
UNNotificationResponse *response = OCMClassMock([UNNotificationResponse class]);
UNNotificationRequest *request = OCMClassMock([UNNotificationRequest class]);
UNNotificationContent *content = OCMClassMock([UNMutableNotificationContent class]);
NSDictionary *userInfo = notification;

[mockEmissionSharedInstance verify];
[mockNotificationView stopMocking];
});
OCMStub([response notification]).andReturn(unNotification);
OCMStub([unNotification request]).andReturn(request);
OCMStub([request content]).andReturn(content);
OCMStub([content userInfo]).andReturn(userInfo);
void (^completionHandler)(void) = ^() {};

it(@"defaults message to url", ^{
id mock = [OCMockObject mockForClass:[ARNotificationView class]];
[[mock expect] showNoticeInView:OCMOCK_ANY title:@"http://artsy.net/feature" response:OCMOCK_ANY];
[delegate applicationDidReceiveRemoteNotification:@{ @"url" : @"http://artsy.net/feature" } inApplicationState:appState];
[delegate userNotificationCenter:currentCenter didReceiveNotificationResponse:response withCompletionHandler:completionHandler];

[mock verify];
[mock stopMocking];
[mockEmissionSharedInstance verify];
});
});
});
Expand Down