diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index de288b09e3b..b30e75b3ff5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -8,7 +8,6 @@
-
diff --git a/android/build.gradle b/android/build.gradle
index e42ca0bc556..1318dbf0444 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -5,10 +5,10 @@ buildscript {
ext {
googlePlayServicesVersion = "17.0.0"
firebaseMessagingVersion = "21.1.0" // matching firebaseIidVersion to avoid duplicate class error
- buildToolsVersion = "33.0.0"
+ buildToolsVersion = "31.0.0"
minSdkVersion = 21
- compileSdkVersion = 33
- targetSdkVersion = 33
+ compileSdkVersion = 31
+ targetSdkVersion = 31
firebaseIidVersion = "21.1.0" // Needed for react-native-device-info
googlePlayServicesAuthVersion = "16.0.1"
kotlinVersion = "1.5.31"
diff --git a/ios/Artsy/App/ARAnalyticsConstants.h b/ios/Artsy/App/ARAnalyticsConstants.h
index 1fe24a26d0e..be3e52477cd 100644
--- a/ios/Artsy/App/ARAnalyticsConstants.h
+++ b/ios/Artsy/App/ARAnalyticsConstants.h
@@ -7,8 +7,10 @@ extern NSString *const ARAnalyticsAppUsageCountProperty;
// Notifications
+extern NSString *const ARAnalyticsEnabledNotificationsProperty;
extern NSString *const ARAnalyticsNotificationReceived;
extern NSString *const ARAnalyticsNotificationTapped;
+extern NSString *const ARAnalyticsPushNotificationsRequested;
// Push notifications
diff --git a/ios/Artsy/App/ARAnalyticsConstants.m b/ios/Artsy/App/ARAnalyticsConstants.m
index 9429f266891..1bc48757a30 100644
--- a/ios/Artsy/App/ARAnalyticsConstants.m
+++ b/ios/Artsy/App/ARAnalyticsConstants.m
@@ -2,8 +2,10 @@
NSString *const ARAnalyticsAppUsageCountProperty = @"app launched count";
+NSString *const ARAnalyticsEnabledNotificationsProperty = @"has enabled notifications";
NSString *const ARAnalyticsNotificationReceived = @"notification received";
NSString *const ARAnalyticsNotificationTapped = @"notification tapped";
+NSString *const ARAnalyticsPushNotificationsRequested = @"push notifications requested";
NSString *const ARAnalyticsPushNotificationLocal = @"Artsy notification prompt response";
NSString *const ARAnalyticsPushNotificationApple = @"Apple notification prompt response";
diff --git a/ios/Artsy/App/ARAppDelegate+Emission.m b/ios/Artsy/App/ARAppDelegate+Emission.m
index 23f1fd308c7..3bbfa3418e7 100644
--- a/ios/Artsy/App/ARAppDelegate+Emission.m
+++ b/ios/Artsy/App/ARAppDelegate+Emission.m
@@ -65,6 +65,18 @@ - (AREmission *)setupSharedEmission
[AREmission setSharedInstance:emission];
+#pragma mark - Native Module: Push Notification Permissions
+
+ emission.APIModule.directNotificationPermissionPrompter = ^() {
+ ARAppNotificationsDelegate *delegate = [[JSDecoupledAppDelegate sharedAppDelegate] remoteNotificationsDelegate];
+ [delegate registerForDeviceNotificationsWithApple];
+ };
+
+ emission.APIModule.prepromptNotificationPermissionPrompter = ^() {
+ ARAppNotificationsDelegate *delegate = [[JSDecoupledAppDelegate sharedAppDelegate] remoteNotificationsDelegate];
+ [delegate registerForDeviceNotificationsWithContext:ARAppNotificationsRequestContextOnboarding];
+ };
+
#pragma mark - Native Module: Follow status
emission.APIModule.notificationReadStatusAssigner = ^(RCTResponseSenderBlock block) {
diff --git a/ios/Artsy/App/ARAppNotificationsDelegate.h b/ios/Artsy/App/ARAppNotificationsDelegate.h
index 3c46f98f197..0ce7054ac93 100644
--- a/ios/Artsy/App/ARAppNotificationsDelegate.h
+++ b/ios/Artsy/App/ARAppNotificationsDelegate.h
@@ -15,6 +15,10 @@ typedef NS_ENUM(NSInteger, ARAppNotificationsRequestContext) {
@property (nonatomic, readwrite, assign) ARAppNotificationsRequestContext requestContext;
+- (void)registerForDeviceNotificationsWithContext:(ARAppNotificationsRequestContext)requestContext;
- (void)applicationDidReceiveRemoteNotification:(NSDictionary *)userInfo inApplicationState:(UIApplicationState)applicationState;
+/// Used in admin tools and for react native to request permissions
+- (void)registerForDeviceNotificationsWithApple;
+
@end
diff --git a/ios/Artsy/App/ARAppNotificationsDelegate.m b/ios/Artsy/App/ARAppNotificationsDelegate.m
index fe9e9604178..7b0dad3c344 100644
--- a/ios/Artsy/App/ARAppNotificationsDelegate.m
+++ b/ios/Artsy/App/ARAppNotificationsDelegate.m
@@ -24,6 +24,157 @@
@implementation ARAppNotificationsDelegate
+#pragma mark -
+#pragma mark Local Push Notification Alerts
+
+- (void)registerForDeviceNotificationsWithContext:(ARAppNotificationsRequestContext)requestContext
+{
+ self.requestContext = requestContext;
+
+ if (![AROptions boolForOption:ARPushNotificationsSettingsPromptSeen] &&
+ [AROptions boolForOption:ARPushNotificationsAppleDialogueRejected]) {
+ // if you've rejected Apple's push notification and you've not seen our prompt to send you to settings
+ // lets show you a prompt to go to settings
+ [self displayPushNotificationSettingsPrompt];
+ } else if (![AROptions boolForOption:ARPushNotificationsAppleDialogueSeen] && [self shouldPresentPushNotificationAgain]) {
+ // As long as you've not seen Apple's dialogue already we will show you our pre-prompt.
+ [self displayPushNotificationLocalRequestPrompt];
+ } else {
+ // Otherwise fallback to requesting directly with apple to make sure we have
+ // up to date push tokens
+ [self registerForDeviceNotificationsWithApple];
+ }
+}
+
+- (void)displayPushNotificationLocalRequestPrompt
+{
+ UIAlertController *alert = [self pushNotificationPromptAlertController];
+
+ UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ [self registerUserInterest];
+ }];
+ UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Don't Allow" style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ [self registerUserDisinterest];
+ }];
+ [alert addAction:cancelAction];
+ [alert addAction:confirmAction];
+
+ [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
+}
+
+
+- (void)displayPushNotificationSettingsPrompt
+{
+ UIAlertController *alert = [self pushNotificationPromptAlertController];
+
+ UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"Go to Settings" style:UIAlertActionStyleDefault
+ handler:^(UIAlertAction *action) {
+ [self presentSettings];
+ }];
+ [alert addAction:settingsAction];
+ alert.preferredAction = settingsAction;
+ [alert addAction:[UIAlertAction actionWithTitle:@"No thanks" style:UIAlertActionStyleCancel handler:nil]];
+ [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
+
+ [AROptions setBool:YES forOption:ARPushNotificationsSettingsPromptSeen];
+}
+
+- (void)registerUserInterest
+{
+ NSString *analyticsContext = @"";
+ if (self.requestContext == ARAppNotificationsRequestContextArtistFollow) {
+ analyticsContext = @"ArtistFollow";
+ } else if (self.requestContext == ARAppNotificationsRequestContextOnboarding) {
+ analyticsContext = @"Onboarding";
+ } else if (self.requestContext == ARAppNotificationsRequestContextLaunch) {
+ analyticsContext = @"Launch";
+ }
+
+ analyticsContext = [@[@"PushNotification", analyticsContext] componentsJoinedByString:@""];
+
+ [[AREmission sharedInstance] sendEvent:ARAnalyticsPushNotificationLocal traits:@{
+ @"action_type" : @"Tap",
+ @"action_name" : @"Yes",
+ @"context_screen" : analyticsContext,
+ }];
+ [self registerForDeviceNotificationsWithApple];
+}
+
+- (void)registerUserDisinterest
+{
+ // Well, in that case we'll store today's date
+ // and prompt the user in a week's time, if they perform certain actions (e.g. follow an artist)
+
+ NSString *analyticsContext = @"";
+ if (self.requestContext == ARAppNotificationsRequestContextArtistFollow) {
+ analyticsContext = @"ArtistFollow";
+ } else if (self.requestContext == ARAppNotificationsRequestContextOnboarding) {
+ analyticsContext = @"Onboarding";
+ } else if (self.requestContext == ARAppNotificationsRequestContextLaunch) {
+ analyticsContext = @"Launch";
+ }
+
+ analyticsContext = [@[@"PushNotification", analyticsContext] componentsJoinedByString:@""];
+
+ [[AREmission sharedInstance] sendEvent:ARAnalyticsPushNotificationLocal traits:@{
+ @"action_type" : @"Tap",
+ @"action_name" : @"Cancel",
+ @"context_screen" : analyticsContext
+ }];
+ [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:ARPushNotificationsDialogueLastSeenDate];
+}
+
+- (void)presentSettings
+{
+ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
+}
+
+- (UIAlertController *)pushNotificationPromptAlertController
+{
+ return [UIAlertController alertControllerWithTitle:@"Artsy Would Like to Send You Notifications"
+ message:@"Turn on notifications to get important updates about artists you follow."
+ preferredStyle:UIAlertControllerStyleAlert];
+}
+
+- (BOOL)shouldPresentPushNotificationAgain
+{
+ // we don't want to ask too often
+ // currently, we make sure at least a week has passed by since you last saw the dialogue
+
+ NSDate *lastSeenPushNotification = [[NSUserDefaults standardUserDefaults] objectForKey:ARPushNotificationsDialogueLastSeenDate];
+
+ if (lastSeenPushNotification) {
+ NSDate *currentDate = [NSDate date];
+
+ NSTimeInterval timePassed = [currentDate timeIntervalSinceDate:lastSeenPushNotification];
+ NSTimeInterval weekInSeconds = (60 * 60 * 24 * 7);
+
+ return timePassed >= weekInSeconds;
+ } else {
+ // if you've never seen one before, we'll show you ;)
+ return YES;
+ }
+}
+
+#pragma mark -
+#pragma mark Push Notification Register
+
+- (void)registerForDeviceNotificationsWithApple
+{
+ ARActionLog(@"Registering with Apple for remote notifications.");
+ UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
+ [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ NSString *grantedString = granted ? @"YES" : @"NO";
+ [[AREmission sharedInstance] sendEvent:ARAnalyticsPushNotificationsRequested traits:@{@"granted" : grantedString}];
+ [[Appboy sharedInstance] pushAuthorizationFromUserNotificationCenter:granted];
+ }];
+
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
+ [AROptions setBool:YES forOption:ARPushNotificationsAppleDialogueSeen];
+}
+
#pragma mark -
#pragma mark Push Notification Delegate
@@ -45,6 +196,7 @@ - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotif
}];
#if (TARGET_IPHONE_SIMULATOR == 0)
ARErrorLog(@"Error registering for remote notifications: %@", error.localizedDescription);
+ [AROptions setBool:YES forOption:ARPushNotificationsAppleDialogueRejected];
#endif
}
@@ -76,9 +228,10 @@ - (void)application:(UIApplication *)application didRegisterForRemoteNotificatio
ARActionLog(@"Got device notification token: %@", deviceToken);
NSString *previousToken = [[NSUserDefaults standardUserDefaults] stringForKey:ARAPNSDeviceTokenKey];
- // Save device token for dev settings and to prevent excess calls to gravity if tokens don't change
+ // Save device token purely for the dev settings view.
[[NSUserDefaults standardUserDefaults] setValue:deviceToken forKey:ARAPNSDeviceTokenKey];
+ [[AREmission sharedInstance] sendIdentifyEvent:@{ARAnalyticsEnabledNotificationsProperty: @1}];
[[Appboy sharedInstance] registerDeviceToken:deviceTokenData];
// We only record device tokens on the Artsy service in case of Beta or App Store builds.
diff --git a/ios/Artsy/Constants/ARDefaults.h b/ios/Artsy/Constants/ARDefaults.h
index a8958ea0cf6..b63bb9c89df 100644
--- a/ios/Artsy/Constants/ARDefaults.h
+++ b/ios/Artsy/Constants/ARDefaults.h
@@ -8,6 +8,15 @@ extern NSString *const AROAuthTokenExpiryDateDefault;
extern NSString *const ARXAppTokenKeychainKey;
extern NSString *const ARXAppTokenExpiryDateDefault;
+#pragma mark -
+#pragma mark push notifications
+
+extern NSString *const ARPushNotificationsAppleDialogueSeen;
+extern NSString *const ARPushNotificationsAppleDialogueRejected;
+extern NSString *const ARPushNotificationsSettingsPromptSeen;
+extern NSString *const ARPushNotificationFollowArtist;
+extern NSString *const ARPushNotificationsDialogueLastSeenDate;
+
#pragma mark -
#pragma mark user permissions
diff --git a/ios/Artsy/Constants/ARDefaults.m b/ios/Artsy/Constants/ARDefaults.m
index 0c4061443ce..79efecb6cc1 100644
--- a/ios/Artsy/Constants/ARDefaults.m
+++ b/ios/Artsy/Constants/ARDefaults.m
@@ -12,6 +12,12 @@
NSString *const ARXAppTokenKeychainKey = @"ARXAppTokenDefault";
NSString *const ARXAppTokenExpiryDateDefault = @"ARXAppTokenExpiryDateDefault";
+NSString *const ARPushNotificationsAppleDialogueSeen = @"eigen-push-seen-dialogue";
+NSString *const ARPushNotificationsAppleDialogueRejected = @"eigen-push-reject-dialogue";
+NSString *const ARPushNotificationsSettingsPromptSeen = @"eigen-push-seen-settings-dialogue";
+NSString *const ARPushNotificationFollowArtist = @"eigen-push-followed-artist";
+NSString *const ARPushNotificationsDialogueLastSeenDate = @"eigen-push-seen-dialogue-date";
+
NSString *const ARAugmentedRealityHasSeenSetup = @"ARAugmentedRealityHasSeenSetup";
NSString *const ARAugmentedRealityHasTriedToSetup = @"ARAugmentedRealityHasTriedToSetup";
NSString *const ARAugmentedRealityCameraAccessGiven = @"ARAugmentedRealityCameraAccessGiven";
@@ -25,8 +31,18 @@ + (void)resetDefaults
// Need to save launch count for analytics
NSInteger launchCount = [[NSUserDefaults standardUserDefaults] integerForKey:ARAnalyticsAppUsageCountProperty];
+ // Preserve notification related settings
+ BOOL hasSeenNotificationPrompt = [[NSUserDefaults standardUserDefaults] boolForKey:ARPushNotificationsSettingsPromptSeen];
+ BOOL hasSeenNotificationDialogue = [[NSUserDefaults standardUserDefaults] boolForKey:ARPushNotificationsAppleDialogueSeen];
+ BOOL userPushNotificationDecision = [[NSUserDefaults standardUserDefaults] boolForKey:ARPushNotificationsAppleDialogueRejected];
+
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];
+
[[NSUserDefaults standardUserDefaults] setInteger:launchCount forKey:ARAnalyticsAppUsageCountProperty];
+ [[NSUserDefaults standardUserDefaults] setBool:hasSeenNotificationPrompt forKey:ARPushNotificationsSettingsPromptSeen];
+ [[NSUserDefaults standardUserDefaults] setBool:hasSeenNotificationDialogue forKey:ARPushNotificationsAppleDialogueSeen];
+ [[NSUserDefaults standardUserDefaults] setBool:userPushNotificationDecision forKey:ARPushNotificationsAppleDialogueRejected];
+
[[NSUserDefaults standardUserDefaults] synchronize];
}
@end
diff --git a/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.h b/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.h
index 2fcda4bd8cc..c33bdfbc806 100644
--- a/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.h
+++ b/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.h
@@ -4,8 +4,25 @@
typedef void(^ARNotificationReadStatusAssigner)(RCTResponseSenderBlock block);
+typedef void(^ARNotificationPermissionsPrompter)();
+
+typedef void(^ARRelativeURLResolver)(NSString *path, RCTPromiseResolveBlock resolve, RCTPromiseRejectBlock reject);
+
+
+/// While metaphysics is read-only, we need to rely on Eigen's
+/// v1 API access to get/set these bits of information.
+
@interface ARTemporaryAPIModule : NSObject
+
+// Just shows the apple dialog, used for explicitly asking permission in settings
+@property (nonatomic, copy, readwrite) ARNotificationPermissionsPrompter directNotificationPermissionPrompter;
+
+// Uses some logic to pre-prompt, redirect to settings, and eventually prompt with apple dialog, used on login
+@property (nonatomic, copy, readwrite) ARNotificationPermissionsPrompter prepromptNotificationPermissionPrompter;
+
@property (nonatomic, copy, readwrite) ARNotificationReadStatusAssigner notificationReadStatusAssigner;
+@property (nonatomic, copy, readwrite) ARRelativeURLResolver urlResolver;
+
@end
diff --git a/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.m b/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.m
index 234ec35998b..1b5a4f17ec4 100644
--- a/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.m
+++ b/ios/Artsy/Emission/TemporaryAPI/ARTemporaryAPIModule.m
@@ -1,16 +1,27 @@
#import "ARTemporaryAPIModule.h"
#import
#import "AREmission.h"
-#import
@implementation ARTemporaryAPIModule
RCT_EXPORT_MODULE();
-RCT_EXPORT_METHOD(markUserPermissionStatus:(BOOL)granted)
+RCT_EXPORT_METHOD(requestDirectNotificationPermissions)
{
- [[Appboy sharedInstance] pushAuthorizationFromUserNotificationCenter:granted];
+ /* Used in settings screen to directly ask user for push permissions */
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.directNotificationPermissionPrompter();
+ });
+}
+
+
+RCT_EXPORT_METHOD(requestPrepromptNotificationPermissions)
+{
+ /* Used on login with some additional logic before requesting permissions */
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.prepromptNotificationPermissionPrompter();
+ });
}
RCT_EXPORT_METHOD(fetchNotificationPermissions:(RCTResponseSenderBlock)callback)
diff --git a/ios/Artsy/View_Controllers/Admin/ARAdminSettingsViewController.m b/ios/Artsy/View_Controllers/Admin/ARAdminSettingsViewController.m
index 2577de0509b..3e03833be5e 100644
--- a/ios/Artsy/View_Controllers/Admin/ARAdminSettingsViewController.m
+++ b/ios/Artsy/View_Controllers/Admin/ARAdminSettingsViewController.m
@@ -124,6 +124,13 @@ - (ARCellData *)generateNotificationTokenPasteboardCopy;
[[UIPasteboard generalPasteboard] setValue:deviceToken forPasteboardType:(NSString *)kUTTypePlainText];
}];
}
+
+- (ARCellData *)requestNotificationsAlert;
+{
+ return [self tappableCellDataWithTitle:@"Request Receiving Notifications" selection:^{
+ [[[ARAppNotificationsDelegate alloc] init] registerForDeviceNotificationsWithApple];
+ }];
+}
#endif
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 0c6d97bfe6c..f69b1448bd3 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -536,7 +536,7 @@ PODS:
- React-Core
- react-native-view-shot (3.4.0):
- React-Core
- - react-native-webview (11.22.7):
+ - react-native-webview (11.3.1):
- React-Core
- React-perflogger (0.69.12)
- React-RCTActionSheet (0.69.12):
@@ -648,8 +648,6 @@ PODS:
- React-Core
- RNLocalize (2.0.1):
- React-Core
- - RNPermissions (3.8.1):
- - React-Core
- RNReactNativeHapticFeedback (1.13.0):
- React-Core
- RNReanimated (2.13.0):
@@ -858,7 +856,6 @@ DEPENDENCIES:
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
- - RNPermissions (from `../node_modules/react-native-permissions`)
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
@@ -1111,8 +1108,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-keychain"
RNLocalize:
:path: "../node_modules/react-native-localize"
- RNPermissions:
- :path: "../node_modules/react-native-permissions"
RNReactNativeHapticFeedback:
:path: "../node_modules/react-native-haptic-feedback"
RNReanimated:
@@ -1259,7 +1254,7 @@ SPEC CHECKSUMS:
react-native-shake: 62aa5681863203090a087842da70183c442b97f8
react-native-slider: 241935e3ea8e47599c317f512f96ee8de607d4cb
react-native-view-shot: a60a98a18c72bcaaaf2138f9aab960ae9b0d96c7
- react-native-webview: 227ba9205abb8579116b69ea5774d9744267c65a
+ react-native-webview: 07fca3f4378bd6ea26254bf63119bfd70f4fabeb
React-perflogger: 5ade0a1627352f1647d283e78331819bb46cceae
React-RCTActionSheet: 8e94f1e46e09c7035b81fe56c0ed8d78f3ccd340
React-RCTAnimation: bf2af72f03cf16528db9a830be69fa04b341a1b7
@@ -1287,7 +1282,6 @@ SPEC CHECKSUMS:
RNImageCropPicker: 648356d68fbf9911a1016b3e3723885d28373eda
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
RNLocalize: 41026b7c14878f1a1b381bc79f668f1fbf841adb
- RNPermissions: 124173e975e06dea451f5f6ce18314a404428749
RNReactNativeHapticFeedback: b83bfb4b537bdd78eb4f6ffe63c6884f7b049ead
RNReanimated: f0d66bda3d074c43c72a18f4fd4f4c26687bc1fd
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
diff --git a/package.json b/package.json
index 95b3a8b8833..324006b4f61 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,6 @@
"node": "16.x",
"yarn": "1.x"
},
- "reactNativePermissionsIOS": [
- "Notifications"
- ],
"main": "index-common.js",
"scripts": {
"android": "react-native run-android",
@@ -55,7 +52,7 @@
"pod-install": "cd ios; bundle exec pod install; cd ..; ./scripts/post-pod-install.rb",
"pod-install-repo-update": "cd ios; bundle exec pod install --repo-update; cd ..; ./scripts/post-pod-install.rb",
"postinit-metaflags": "rimraf storybook.json",
- "postinstall": "react-native setup-ios-permissions; yarn init-metaflags; prettier --write package.json; ./scripts/update-echo",
+ "postinstall": "yarn init-metaflags; prettier --write package.json; ./scripts/update-echo",
"prepare": "patch-package && husky install",
"prestart": "./scripts/set-storybook-environment.js",
"prestart-storybook": "yarn sb-rn-get-stories --config-path ./src/app/storybook",
@@ -191,7 +188,6 @@
"react-native-linear-gradient": "2.6.2",
"react-native-localize": "2.0.1",
"react-native-pager-view": "6.2.0",
- "react-native-permissions": "3.8.1",
"react-native-push-notification": "8.1.1",
"react-native-reanimated": "2.13.0",
"react-native-reanimated-zoom": "0.3.3",
@@ -205,7 +201,7 @@
"react-native-url-polyfill": "1.3.0",
"react-native-view-shot": "3.4.0",
"react-native-vimeo-iframe": "1.2.1",
- "react-native-webview": "11.22.7",
+ "react-native-webview": "11.3.1",
"react-relay": "14.1.0",
"react-spring": "8.0.23",
"react-tracking": "9.2.0",
diff --git a/scripts/changelog/generateChangelog.ts b/scripts/changelog/generateChangelog.ts
index 36df2e8c5cf..d73c196bd1c 100644
--- a/scripts/changelog/generateChangelog.ts
+++ b/scripts/changelog/generateChangelog.ts
@@ -136,4 +136,5 @@ async function main() {
}
}
+
main().catch((err) => console.error(err))
diff --git a/src/app/Components/ArtsyWebView.tests.tsx b/src/app/Components/ArtsyWebView.tests.tsx
index e93dc311d1b..eac04b9e834 100644
--- a/src/app/Components/ArtsyWebView.tests.tsx
+++ b/src/app/Components/ArtsyWebView.tests.tsx
@@ -9,7 +9,6 @@ import { stringify } from "query-string"
import Share from "react-native-share"
import WebView, { WebViewProps } from "react-native-webview"
import { WebViewNavigation } from "react-native-webview/lib/WebViewTypes"
-
import {
_test_expandGoogleAdLink as expandGoogleAdLink,
ArtsyWebView,
@@ -155,13 +154,16 @@ describe("ArtsyWebViewPage", () => {
describe("mimicBrowserBackButton", () => {
it("lets our native back button control the browser", () => {
- const mockSystemBackAction = jest.fn()
- const tree = render({ systemBackAction: mockSystemBackAction })
+ const tree = render()
+ const browserGoBack = jest
+ .spyOn(screen.UNSAFE_getByType(WebView).instance, "goBack")
+ .mockImplementation(() => undefined)
fireEvent.press(screen.getByTestId("fancy-modal-header-left-button"))
expect(goBack).toHaveBeenCalled()
+ expect(browserGoBack).not.toHaveBeenCalled()
;(goBack as any).mockReset()
- mockSystemBackAction.mockReset()
+ ;(browserGoBack as any).mockReset()
webViewProps(tree).onNavigationStateChange?.({
...mockOnNavigationStateChange,
@@ -169,13 +171,15 @@ describe("ArtsyWebViewPage", () => {
})
fireEvent.press(screen.getByTestId("fancy-modal-header-left-button"))
- expect(mockSystemBackAction).toHaveBeenCalled()
+ expect(browserGoBack).toHaveBeenCalled()
expect(goBack).not.toHaveBeenCalled()
})
it("can be overridden", () => {
- const mockSystemBackAction = jest.fn()
- const tree = render({ mimicBrowserBackButton: false, systemBackAction: mockSystemBackAction })
+ const tree = render({ mimicBrowserBackButton: false })
+ const browserGoBack = jest
+ .spyOn(screen.UNSAFE_getByType(WebView).instance, "goBack")
+ .mockImplementation(() => undefined)
webViewProps(tree).onNavigationStateChange?.({
...mockOnNavigationStateChange,
@@ -183,7 +187,7 @@ describe("ArtsyWebViewPage", () => {
})
fireEvent.press(screen.getByTestId("fancy-modal-header-left-button"))
- expect(mockSystemBackAction).not.toHaveBeenCalled()
+ expect(browserGoBack).not.toHaveBeenCalled()
expect(goBack).toHaveBeenCalled()
})
})
diff --git a/src/app/Components/ArtsyWebView.tsx b/src/app/Components/ArtsyWebView.tsx
index 335fa705cec..6d124d7d250 100644
--- a/src/app/Components/ArtsyWebView.tsx
+++ b/src/app/Components/ArtsyWebView.tsx
@@ -55,7 +55,6 @@ export const ArtsyWebViewPage = ({
mimicBrowserBackButton = true,
useRightCloseButton = false,
showShareButton = false,
- systemBackAction,
backProps,
backAction,
safeAreaEdges,
@@ -63,7 +62,6 @@ export const ArtsyWebViewPage = ({
url: string
isPresentedModally?: boolean
backProps?: GoBackProps
- systemBackAction?: () => void
backAction?: () => void
} & ArtsyWebViewConfig) => {
const saInsets = useSafeAreaInsets()
@@ -122,11 +120,7 @@ export const ArtsyWebViewPage = ({
} else if (!canGoBack) {
handleGoBack()
} else {
- if (systemBackAction) {
- systemBackAction()
- } else {
- ref.current?.goBack()
- }
+ ref.current?.goBack()
}
}
}
diff --git a/src/app/NativeModules/LegacyNativeModules.tsx b/src/app/NativeModules/LegacyNativeModules.tsx
index 5ff3986f273..197901d53ec 100644
--- a/src/app/NativeModules/LegacyNativeModules.tsx
+++ b/src/app/NativeModules/LegacyNativeModules.tsx
@@ -22,10 +22,11 @@ const noop: any = (name: string) => () =>
interface LegacyNativeModules {
ARTemporaryAPIModule: {
+ requestPrepromptNotificationPermissions(): void
+ requestDirectNotificationPermissions(): void
fetchNotificationPermissions(
callback: (error: any, result: PushAuthorizationStatus) => void
): void
- markUserPermissionStatus(granted: boolean): void
markNotificationsRead(callback: (error?: Error) => any): void
setApplicationIconBadgeNumber(n: number): void
getUserEmail(): string
@@ -127,8 +128,9 @@ const LegacyNativeModulesAndroid = {
},
ARTemporaryAPIModule: {
+ requestPrepromptNotificationPermissions: noop("requestPrepromptNotificationPermissions"),
+ requestDirectNotificationPermissions: noop("requestDirectNotificationPermissions"),
fetchNotificationPermissions: noop("fetchNotificationPermissions"),
- markUserPermissionStatus: noop("markUserPermissionStatus"),
markNotificationsRead: noop("markNotificationsRead"),
setApplicationIconBadgeNumber: () => {
console.log("TODO: make app icon badge work on android")
diff --git a/src/app/Scenes/Home/Home.tsx b/src/app/Scenes/Home/Home.tsx
index a317ae9c6a9..bb5f66ce4f0 100644
--- a/src/app/Scenes/Home/Home.tsx
+++ b/src/app/Scenes/Home/Home.tsx
@@ -67,7 +67,6 @@ import {
useMemoizedRandom,
} from "app/utils/placeholders"
import { usePrefetch } from "app/utils/queryPrefetching"
-import { requestPushNotificationsPermission } from "app/utils/requestPushNotificationsPermission"
import {
ArtworkActionTrackingProps,
extractArtworkActionTrackingProps,
@@ -155,10 +154,6 @@ const Home = memo((props: HomeProps) => {
prefetchUrl("sales")
}, [])
- useEffect(() => {
- requestPushNotificationsPermission()
- }, [])
-
// we cannot rely on mount events for screens in tab views for screen tracking
// because they can be mounted before the screen is visible
// do custom screen view instead
diff --git a/src/app/Scenes/Home/HomeContainer.tsx b/src/app/Scenes/Home/HomeContainer.tsx
index 591067c26a3..c2b45a28537 100644
--- a/src/app/Scenes/Home/HomeContainer.tsx
+++ b/src/app/Scenes/Home/HomeContainer.tsx
@@ -1,11 +1,17 @@
import { HomeQueryRenderer } from "app/Scenes/Home/Home"
import { GlobalStore } from "app/store/GlobalStore"
import { navigate } from "app/system/navigation/navigate"
+import { requestPushNotificationsPermission } from "app/utils/PushNotification"
import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { useEffect } from "react"
export const HomeContainer = () => {
const artQuizState = GlobalStore.useAppState((state) => state.auth.onboardingArtQuizState)
+ const onboardingState = GlobalStore.useAppState((state) => state.auth.onboardingState)
+ const hasRequestedPermissionsThisSession = GlobalStore.useAppState(
+ (state) => state.auth.requestedPushPermissionsThisSession
+ )
+
const isNavigationReady = GlobalStore.useAppState((state) => state.sessionState.isNavigationReady)
const shouldShowArtQuiz = useFeatureFlag("ARShowArtQuizApp")
@@ -19,6 +25,14 @@ export const HomeContainer = () => {
navigateToArtQuiz()
return
}
+
+ if (
+ !hasRequestedPermissionsThisSession &&
+ (!onboardingState || onboardingState === "complete" || onboardingState === "none")
+ ) {
+ requestPushNotificationsPermission()
+ GlobalStore.actions.auth.setState({ requestedPushPermissionsThisSession: true })
+ }
}, [shouldShowArtQuiz, artQuizState, navigateToArtQuiz, isNavigationReady])
if (shouldShowArtQuiz && artQuizState === "incomplete") {
diff --git a/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx b/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx
index 209f40d0460..ca8ba72e327 100644
--- a/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx
+++ b/src/app/Scenes/MyProfile/MyProfilePushNotifications.tsx
@@ -3,6 +3,7 @@ import { MyProfilePushNotificationsQuery } from "__generated__/MyProfilePushNoti
import { MyProfilePushNotifications_me$data } from "__generated__/MyProfilePushNotifications_me.graphql"
import { PageWithSimpleHeader } from "app/Components/PageWithSimpleHeader"
import { SwitchMenu } from "app/Components/SwitchMenu"
+import { LegacyNativeModules } from "app/NativeModules/LegacyNativeModules"
import { updateMyUserProfile } from "app/Scenes/MyAccount/updateMyUserProfile"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import {
@@ -10,7 +11,6 @@ import {
PushAuthorizationStatus,
} from "app/utils/PushNotification"
import { renderWithPlaceholder } from "app/utils/renderWithPlaceholder"
-import { requestSystemPermissions } from "app/utils/requestPushNotificationsPermission"
import useAppState from "app/utils/useAppState"
import { debounce } from "lodash"
import React, { useCallback, useEffect, useState } from "react"
@@ -83,7 +83,7 @@ export const AllowPushNotificationsBanner = () => (