Automated cherry pick of #3884 (#3887)

* Handle iOS reply action on native side

* Make linter happy

* Revert rn vector changes to .pbxproj

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
This commit is contained in:
Mattermost Build 2020-02-04 16:53:00 +01:00 committed by GitHub
parent 5f0df6eb49
commit 3daa365e44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 218 additions and 142 deletions

View file

@ -12,7 +12,6 @@ class PushNotification {
constructor() {
this.onRegister = null;
this.onNotification = null;
this.onReply = null;
this.deviceNotification = null;
this.deviceToken = null;
@ -55,7 +54,6 @@ class PushNotification {
configure(options) {
this.onRegister = options.onRegister;
this.onNotification = options.onNotification;
this.onReply = options.onReply;
if (this.onRegister && this.deviceToken) {
this.onRegister({token: this.deviceToken});

View file

@ -19,14 +19,11 @@ import {t} from 'app/utils/i18n';
const CATEGORY = 'CAN_REPLY';
const REPLY_ACTION = 'REPLY_ACTION';
const replies = new Set();
class PushNotification {
constructor() {
this.deviceNotification = null;
this.onRegister = null;
this.onNotification = null;
this.onReply = null;
this.reduxStore = null;
NotificationsIOS.addEventListener(DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT, this.onRemoteNotificationsRegistered);
@ -48,22 +45,10 @@ class PushNotification {
}
};
handleReply = (notification, text, completion) => {
const data = notification.getData();
if (this.onReply && !replies.has(data.identifier)) {
replies.add(data.identifier);
this.onReply(data, text, completion);
} else {
completion();
}
};
configure(options) {
this.reduxStore = options.reduxStore;
this.onRegister = options.onRegister;
this.onNotification = options.onNotification;
this.onReply = options.onReply;
this.requestNotificationReplyPermissions();
@ -175,18 +160,14 @@ class PushNotification {
this.handleNotification(info, true, false);
};
onNotificationOpened = (notification, completion, action) => {
if (action.identifier === REPLY_ACTION) {
this.handleReply(notification, action.text, completion);
} else {
const data = notification.getData();
const info = {
...data,
message: data.body || notification.getMessage(),
};
this.handleNotification(info, false, true);
completion();
}
onNotificationOpened = (notification, completion) => {
const data = notification.getData();
const info = {
...data,
message: data.body || notification.getMessage(),
};
this.handleNotification(info, false, true);
completion();
};
onRemoteNotificationsRegistered = (deviceToken) => {

View file

@ -1,62 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {PostTypes} from 'mattermost-redux/action_types';
import PushNotificationUtils from './push_notifications';
jest.mock('app/init/credentials', () => ({
getAppCredentials: jest.fn().mockReturnValue({
username: 'device-token,current-user-id',
password: 'token,url',
}),
getCurrentServerUrl: jest.fn().mockReturnValue('url'),
}));
jest.mock('app/actions/views/root', () => ({
createPostForNotificationReply: jest.fn().mockImplementation(() => {
return () => ({error: 'error'});
}),
}));
jest.mock('app/selectors/i18n', () => ({
getCurrentLocale: jest.fn().mockReturnValue('en'),
}));
jest.mock('react-native-notifications', () => {
return {
requestPermissions: jest.fn(),
addEventListener: jest.fn(),
NotificationAction: jest.fn(),
NotificationCategory: jest.fn(),
localNotification: jest.fn(),
getInitialNotification: jest.fn(() => Promise.resolve()),
};
});
const mockStore = configureMockStore([thunk]);
const store = mockStore({});
PushNotificationUtils.configure(store);
describe('PushNotifications', () => {
it('should add channel_id to failed push notification reply', async () => {
let notification = PushNotificationUtils.getNotification();
expect(notification).toBe(null);
const channelID = 'channel-id';
const data = {channel_id: channelID};
const text = 'text';
const completion = () => {};
await PushNotificationUtils.onPushNotificationReply(data, text, completion);
const storeActions = store.getActions();
const receivedPost = storeActions.some((action) => action.type === PostTypes.RECEIVED_POST);
expect(receivedPost).toBe(false);
notification = PushNotificationUtils.getNotification();
expect(notification.userInfo.channel_id).toBe(channelID);
});
});

View file

@ -5,6 +5,7 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* MattermostTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* MattermostTests.m */; };
0111A42B7F264BCF8CBDE3ED /* OpenSans-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */; };
@ -22,6 +23,7 @@
499F7A652354F51900E7AF6E /* OnPasteEventManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 499F7A642354F51900E7AF6E /* OnPasteEventManager.m */; };
499F7AF1235511FD00E7AF6E /* Mattermost-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 499F7AF0235511FC00E7AF6E /* Mattermost-Regular.otf */; };
49B4C050230C981C006E919E /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */; };
552835DCC0C24FC691EE6CAB /* Roboto-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */; };
55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */; };
62A8448264674B4D95A5A7C2 /* OpenSans-Semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */; };
@ -184,6 +186,8 @@
499F7B412355141200E7AF6E /* libYoutubePlayer-in-WKWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libYoutubePlayer-in-WKWebView.a"; sourceTree = BUILT_PRODUCTS_DIR; };
49B4BF51230C83D2006E919E /* libz.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.dylib; path = ../../../../../../../../../usr/lib/libz.1.dylib; sourceTree = "<group>"; };
49D0879035464473A11A0E1C /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; };
536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RNNotificationEventHandler+HandleReplyAction.m"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.m"; sourceTree = "<group>"; };
536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNNotificationEventHandler+HandleReplyAction.h"; path = "Mattermost/RNNotificationEventHandler+HandleReplyAction.h"; sourceTree = "<group>"; };
563B800AC53A447FA18F47D3 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = "<group>"; };
57CB4735B7E57B50D0B50E16 /* Pods-MattermostTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.debug.xcconfig"; path = "Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig"; sourceTree = "<group>"; };
5BB993883BB9CFFBD2BEB9FC /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -265,7 +269,6 @@
D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Bold.ttf"; path = "../assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
EB4F0DF36537B0B21BE962FB /* Pods-Mattermost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.debug.xcconfig"; path = "Target Support Files/Pods-Mattermost/Pods-Mattermost.debug.xcconfig"; sourceTree = "<group>"; };
FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = "<group>"; };
81061F4CBB31484A94D5A8EE /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -370,6 +373,8 @@
13B07FAE1A68108700A75B9A /* Mattermost */ = {
isa = PBXGroup;
children = (
536CC6C223E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.h */,
536CC6C123E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m */,
499F7A632354F51900E7AF6E /* OnPasteEventManager.h */,
499F7A642354F51900E7AF6E /* OnPasteEventManager.m */,
499F7A612354F4CC00E7AF6E /* NSData+MimeType.h */,
@ -816,56 +821,56 @@
showEnvVarsInLog = 0;
};
1D22DC6FC0A37F7F657B8274 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_ROOT}/../../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/YTPlayerView-iframe-player.html",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n";
showEnvVarsInLog = 0;
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_ROOT}/../../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/YTPlayerView-iframe-player.html",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n";
showEnvVarsInLog = 0;
};
AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
@ -980,6 +985,7 @@
499F7A652354F51900E7AF6E /* OnPasteEventManager.m in Sources */,
7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */,
7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */,
536CC6C323E79287002C478C /* RNNotificationEventHandler+HandleReplyAction.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -0,0 +1,17 @@
//
// RNNotificationEventHandler+HandleReplyAction.h
// Mattermost
//
// Created by Miguel Alatzar on 1/29/20.
// Copyright © 2020 Mattermost. All rights reserved.
//
#import <react-native-notifications/RNNotificationEventHandler.h>
#import <react-native-notifications/RNNotificationCenter.h>
@interface RNNotificationEventHandler (HandleReplyAction)
@property (nonatomic, strong) RNNotificationCenter *notificationCenter;
@end
extern NSString *const ReplyActionID;

View file

@ -0,0 +1,136 @@
//
// RNNotificationEventHandler+HandleReplyAction.m
// Mattermost
//
// Created by Miguel Alatzar on 1/29/20.
// Copyright © 2020 Mattermost. All rights reserved.
//
#import "AppDelegate.h"
#import "RNNotificationEventHandler+HandleReplyAction.h"
#import <react-native-notifications/RNNotificationParser.h>
#import <UploadAttachments-Bridging-Header.h>
#import <objc/runtime.h>
#define notificationCenterKey @"notificationCenter"
NSString *const ReplyActionID = @"REPLY_ACTION";
@implementation RNNotificationEventHandler (HandleReplyAction)
- (RNNotificationCenter *)notificationCenter{
return objc_getAssociatedObject(self, notificationCenterKey);
}
- (void)setNotificationCenter:(RNNotificationCenter *)notificationCenter{
objc_setAssociatedObject(self, notificationCenterKey, notificationCenter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (void)load {
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
Class class = [self class];
SEL originalSelector = @selector(didReceiveNotificationResponse:completionHandler:);
SEL swizzledSelector = @selector(handleReplyAction_didReceiveNotificationResponse:completionHandler:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)sendReply:(UNNotificationResponse *)response {
StoreManager *store = [StoreManager shared];
[store getEntities:true];
NSString *serverUrl = [store getServerUrl];
NSString *sessionToken = [store getToken];
if (serverUrl == nil || sessionToken == nil) {
[self handleReplyFailure:@""];
return;
}
NSString *completionKey = response.notification.request.identifier;
NSDictionary *parsedResponse = [RNNotificationParser parseNotificationResponse:response];
NSString *message = [parsedResponse valueForKeyPath:@"action.text"];
NSString *channelId = [parsedResponse valueForKeyPath:@"payload.channel_id"];
NSString *rootId = [parsedResponse valueForKeyPath:@"payload.root_id"];
if (rootId == nil) {
rootId = [parsedResponse valueForKeyPath:@"payload.post_id"];
}
NSDictionary *post = @{
@"message": message,
@"channel_id": channelId,
@"root_id": rootId
};
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:0 error:&error];
if (!postData) {
[self handleReplyFailure:channelId];
return;
}
NSString *urlString = [NSString stringWithFormat:@"%@/api/v4/posts", serverUrl];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:[NSString stringWithFormat:@"Bearer %@", sessionToken] forHTTPHeaderField:@"Authorization"];
[request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode == 201) {
[self handleReplySuccess:completionKey];
} else {
[self handleReplyFailure:channelId];
}
}];
[task resume];
}
- (void) handleReplySuccess:(NSString *)completionKey {
[[RNNotificationsStore sharedInstance] completeAction:completionKey];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:[notifications count]];
}];
}
- (void) handleReplyFailure:(NSString *)channelId {
RNNotificationCenter *notificationCenter = [self notificationCenter];
if (!notificationCenter) {
notificationCenter = [RNNotificationCenter new];
[self setNotificationCenter:notificationCenter];
}
NSString *id = [[NSUUID UUID] UUIDString];;
NSDictionary *notification = @{
@"body": @"Message failed to send.",
@"alertAction": @"",
@"userInfo": @{
@"localNotification": @YES,
@"localTest": @YES,
@"channel_id": channelId
}
};
[notificationCenter sendLocalNotification:notification withId:id];
}
#pragma mark - Method Swizzling
- (void)handleReplyAction_didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(void))completionHandler {
if ([response.actionIdentifier isEqualToString:ReplyActionID]) {
[self sendReply:response];
completionHandler();
} else {
[self handleReplyAction_didReceiveNotificationResponse:response completionHandler:completionHandler];
}
}
@end