diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 0c07f643d..a53b7d157 100755 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -27,7 +27,7 @@ 24EEE5A11F23A09A00B3C705 /* RiotDesignValues.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC171E7009EC00A9B29C /* RiotDesignValues.m */; }; 24EEE5A21F23A8B400B3C705 /* MXRoom+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BBE81E7009EC00A9B29C /* MXRoom+Riot.m */; }; 24EEE5A31F23A8C300B3C705 /* AvatarGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC111E7009EC00A9B29C /* AvatarGenerator.m */; }; - 24EEE5A41F24C06E00B3C705 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; 24EEE5A81F25529600B3C705 /* cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A121EDEE65000F5DC9A /* cancel@3x.png */; }; 24EEE5A91F25529900B3C705 /* cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A111EDEE65000F5DC9A /* cancel@2x.png */; }; 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A101EDEE65000F5DC9A /* cancel.png */; }; @@ -79,6 +79,7 @@ 714F6391AC0AA86C0AEB3F43 /* libPods-SiriIntents.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5666C1236223F54D4C635C54 /* libPods-SiriIntents.a */; }; 92324BE31F4F66D3009DE194 /* IncomingCallView.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324BE21F4F66D3009DE194 /* IncomingCallView.m */; }; 92324BE61F4F6A60009DE194 /* CircleButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324BE51F4F6A60009DE194 /* CircleButton.m */; }; + 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */; }; 92726A471F58737A004AD26F /* IntentHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 92726A461F58737A004AD26F /* IntentHandler.m */; }; 92726A4B1F58737A004AD26F /* SiriIntents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 92726A431F58737A004AD26F /* SiriIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 92726A511F587410004AD26F /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92726A501F587410004AD26F /* Intents.framework */; }; @@ -667,6 +668,8 @@ 92324BE21F4F66D3009DE194 /* IncomingCallView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IncomingCallView.m; sourceTree = ""; }; 92324BE41F4F6A60009DE194 /* CircleButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleButton.h; sourceTree = ""; }; 92324BE51F4F6A60009DE194 /* CircleButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleButton.m; sourceTree = ""; }; + 926FA53D1F4C132000F826C2 /* MXSession+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXSession+Riot.h"; sourceTree = ""; }; + 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXSession+Riot.m"; sourceTree = ""; }; 92726A431F58737A004AD26F /* SiriIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SiriIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 92726A451F58737A004AD26F /* IntentHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IntentHandler.h; sourceTree = ""; }; 92726A461F58737A004AD26F /* IntentHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IntentHandler.m; sourceTree = ""; }; @@ -1798,6 +1801,8 @@ F083BBEA1E7009EC00A9B29C /* UINavigationController+Riot.m */, F083BBEB1E7009EC00A9B29C /* UIViewController+RiotSearch.h */, F083BBEC1E7009EC00A9B29C /* UIViewController+RiotSearch.m */, + 926FA53D1F4C132000F826C2 /* MXSession+Riot.h */, + 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */, ); path = Categories; sourceTree = ""; @@ -2534,7 +2539,7 @@ 24EEE5AF1F25F0F500B3C705 /* Images.xcassets in Resources */, 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */, 24D6B35E1F3CA03E00FC7A71 /* FallbackViewController.xib in Resources */, - 24EEE5A41F24C06E00B3C705 /* (null) in Resources */, + 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3246,6 +3251,7 @@ F083BDF81E7009ED00A9B29C /* RoomDataSource.m in Sources */, F083BE371E7009ED00A9B29C /* RoomActivitiesView.m in Sources */, F083BE131E7009ED00A9B29C /* HomeMessagesSearchViewController.m in Sources */, + 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */, F083BE8C1E7009ED00A9B29C /* PreviewRoomTitleView.m in Sources */, F083BE271E7009ED00A9B29C /* SettingsViewController.m in Sources */, F083BE9A1E7009ED00A9B29C /* TableViewCellWithButton.m in Sources */, diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index b82717e69..e0a2b2e14 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -18,6 +18,7 @@ #import "AppDelegate.h" #import +#import #import "RecentsDataSource.h" #import "RoomDataSource.h" @@ -51,6 +52,8 @@ #import #import +#import "MXSession+Riot.h" + //#define MX_CALL_STACK_OPENWEBRTC #ifdef MX_CALL_STACK_OPENWEBRTC #import @@ -73,7 +76,7 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapStatusBarNotification"; NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateNetworkStatusDidChangeNotification"; -@interface AppDelegate () +@interface AppDelegate () { /** Reachability observer @@ -116,11 +119,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN */ NSMutableArray *mxSessionArray; - /** - The room id of the current handled remote notification (if any) - */ - NSString *remoteNotificationRoomId; - /** The fragment of the universal link being processing. Only one fragment is handled at a time. @@ -179,6 +177,16 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN @property (nonatomic, nullable, copy) void (^registrationForRemoteNotificationsCompletion)(NSError *); + +@property (nonatomic, strong) PKPushRegistry *pushRegistry; + +@property (nonatomic, getter=isHandlingPushNotification) BOOL handlingPushNotification; +@property (nonatomic) NSUInteger pushNotificationHandlingTaskIdentifier; + +@property (nonatomic, nullable) MXOnNotification notificationListenerBlock; +@property (nonatomic, nullable) dispatch_block_t pushNotificationHandlingCompletionBlock; +@property (nonatomic, nullable) dispatch_block_t pushNotificationHandlingTimeoutBlock; + @end @implementation AppDelegate @@ -454,10 +462,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - // cancel any background sync before resuming - // i.e. warn IOS that there is no new data with any received push. - [self cancelBackgroundSync]; - // Open account session(s) if this is not already done (see [initMatrixSessions] in case of background launch). [[MXKAccountManager sharedManager] prepareSessionForActiveAccounts]; @@ -471,8 +475,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { NSLog(@"[AppDelegate] applicationDidBecomeActive"); - remoteNotificationRoomId = nil; - // Check if there is crash log to send if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]) { @@ -971,67 +973,17 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { - // Register for remote notifications only if user provide access to notification feature - if (notificationSettings.types != UIUserNotificationTypeNone) - { - [self registerForRemoteNotificationsWithCompletion:nil]; - } - else - { - // Clear existing token - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setApnsDeviceToken:nil]; - } + if (notificationSettings.types == UIUserNotificationTypeNone) + return; + + self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:nil]; + self.pushRegistry.delegate = self; + self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } -- (void)application:(UIApplication*)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { - NSUInteger len = ((deviceToken.length > 8) ? 8 : deviceToken.length / 2); - NSLog(@"[AppDelegate] Got APNS token! (%@ ...)", [deviceToken subdataWithRange:NSMakeRange(0, len)]); - - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setApnsDeviceToken:deviceToken]; - - isAPNSRegistered = YES; - - if (self.registrationForRemoteNotificationsCompletion) - { - self.registrationForRemoteNotificationsCompletion(nil); - self.registrationForRemoteNotificationsCompletion = nil; - } -} - -- (void)application:(UIApplication*)app didFailToRegisterForRemoteNotificationsWithError:(NSError*)error -{ - NSLog(@"[AppDelegate] Failed to register for APNS: %@", error); - - if (self.registrationForRemoteNotificationsCompletion) - { - self.registrationForRemoteNotificationsCompletion(error); - self.registrationForRemoteNotificationsCompletion = nil; - } -} - -- (void)cancelBackgroundSync -{ - if (_completionHandler) - { - _completionHandler(UIBackgroundFetchResultNoData); - _completionHandler = nil; - } -} - -- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler -{ -#ifdef DEBUG - // log the full userInfo only in DEBUG - NSLog(@"[AppDelegate] didReceiveRemoteNotification: %@", userInfo); -#else - NSLog(@"[AppDelegate] didReceiveRemoteNotification"); -#endif - - // Look for the room id - NSString* roomId = [userInfo objectForKey:@"room_id"]; + NSString* roomId = notification.userInfo[@"room_id"]; if (roomId.length) { // TODO retrieve the right matrix session @@ -1061,71 +1013,266 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // sanity checks if (dedicatedAccount && dedicatedAccount.mxSession) { - UIApplicationState state = [UIApplication sharedApplication].applicationState; + NSLog(@"[AppDelegate] didReceiveLocalNotification: open the roomViewController %@", roomId); - // Jump to the concerned room only if the app is transitioning from the background - if (state == UIApplicationStateInactive) - { - // Check whether another remote notification is not already processed - if (!remoteNotificationRoomId) - { - remoteNotificationRoomId = roomId; - - NSLog(@"[AppDelegate] didReceiveRemoteNotification: open the roomViewController %@", roomId); - - [self showRoom:roomId andEventId:nil withMatrixSession:dedicatedAccount.mxSession]; - } - else - { - NSLog(@"[AppDelegate] didReceiveRemoteNotification: busy"); - } - } - else if (!_completionHandler && (state == UIApplicationStateBackground)) - { - _completionHandler = completionHandler; - - NSLog(@"[AppDelegate] didReceiveRemoteNotification: starts a background sync"); - - [dedicatedAccount backgroundSync:20000 success:^{ - NSLog(@"[AppDelegate] didReceiveRemoteNotification: the background sync succeeds"); - - if (_completionHandler) - { - _completionHandler(UIBackgroundFetchResultNewData); - _completionHandler = nil; - } - } failure:^(NSError *error) { - NSLog(@"[AppDelegate] didReceiveRemoteNotification: the background sync fails"); - - if (_completionHandler) - { - _completionHandler(UIBackgroundFetchResultNoData); - _completionHandler = nil; - } - }]; - - // wait that the background sync is done - return; - } + [self showRoom:roomId andEventId:nil withMatrixSession:dedicatedAccount.mxSession]; } else { - NSLog(@"[AppDelegate] didReceiveRemoteNotification : no linked session / account has been found."); + NSLog(@"[AppDelegate] didReceiveLocalNotification : no linked session / account has been found."); } } - completionHandler(UIBackgroundFetchResultNoData); } -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo +- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type { - // iOS 10 (at least up to GM beta release) does not call application:didReceiveRemoteNotification:fetchCompletionHandler: - // when the user clicks on a notification but it calls this deprecated version - // of didReceiveRemoteNotification. - // Use this method as a workaround as adviced at http://stackoverflow.com/a/39419245 - NSLog(@"[AppDelegate] didReceiveRemoteNotification (deprecated version)"); + NSData *token = credentials.token; - [self application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:^(UIBackgroundFetchResult result) { + NSUInteger len = ((token.length > 8) ? 8 : token.length / 2); + NSLog(@"[AppDelegate] Got APNS token! (%@ ...)", [token subdataWithRange:NSMakeRange(0, len)]); + + MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; + [accountManager setApnsDeviceToken:token]; + + isAPNSRegistered = YES; +} + +- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type +{ + MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; + [accountManager setApnsDeviceToken:nil]; +} + +- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type +{ + // If it isn't a first push + if (self.isHandlingPushNotification) + { + // If we have processed push notification and we're waiting to complete after a timeout + if (self.pushNotificationHandlingCompletionBlock) + { + // Cancel completion of previous push notification. + // The new completion block will be created when this push notification will be processed if timeout block wouldn't be called + dispatch_block_cancel(self.pushNotificationHandlingCompletionBlock); + self.pushNotificationHandlingCompletionBlock = nil; + } + + // On every new push cancel timeout block if any. + // This situation is possible when we sequentially receive a series of push notifications in a short amount of time. + if (self.pushNotificationHandlingTimeoutBlock) + { + dispatch_block_cancel(self.pushNotificationHandlingTimeoutBlock); + self.pushNotificationHandlingTimeoutBlock = nil; + } + + // Create and run timeout block since we don't know how much time will take a new sync request + // There is also a case when we receive push event earlier than push and we need to stop execution somehow + // so timeoutBlock will help us in this situation + dispatch_block_t timeoutBlock = [self createPushNotificationHandlingStateCleaningBlock]; + self.pushNotificationHandlingTimeoutBlock = timeoutBlock; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(120 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + timeoutBlock(); + }); + + return; + } + + UIApplication *application = [UIApplication sharedApplication]; + + if (application.applicationState == UIApplicationStateActive || application.applicationState == UIApplicationStateInactive) + return; + + MXSession *session = mxSessionArray.firstObject; + + if (session.state == MXSessionStateHomeserverNotReachable) + return; + + if (session.state == MXSessionStatePaused) + [session resume:^{}]; + + self.handlingPushNotification = YES; + + id handler = [MXSDKOptions sharedInstance].backgroundModeHandler; + self.pushNotificationHandlingTaskIdentifier = [handler startBackgroundTaskWithName:nil completion:^{ + // Maybe do smth here... }]; + + // Listen events from MXNotificationCenter + __block MXOnNotification notificationsListenerBlock = nil; + + // This variable will be point to notificationsListenerBlock but won't retain it. + // It's very important since notificationsListenerBlock is required in two another blocks which also + // are being referenced inside notificationsListenerBlock's body. + // So this weak variable allows us to avoid retain cycle. + __weak __block MXOnNotification weakNotificationsListenerBlock = nil; + + // The block which is called when a sync with server takes a lot of time + dispatch_block_t timeoutBlock = [self createPushNotificationHandlingStateCleaningBlock]; + self.pushNotificationHandlingTimeoutBlock = timeoutBlock; + + __weak typeof(self) weakSelf = self; + + notificationsListenerBlock = ^(MXEvent *event, MXRoomState *roomState, MXPushRule *rule) { + if (weakSelf.pushNotificationHandlingTimeoutBlock) + { + dispatch_block_cancel(weakSelf.pushNotificationHandlingTimeoutBlock); + weakSelf.pushNotificationHandlingTimeoutBlock = nil; + } + + if (weakSelf.pushNotificationHandlingCompletionBlock) + { + dispatch_block_cancel(weakSelf.pushNotificationHandlingCompletionBlock); + weakSelf.pushNotificationHandlingCompletionBlock = nil; + } + + // For all type of event show local notifications besides the situation + // when the type of event is call invite and we have CallKit support + BOOL isCallKitActive = [MXCallKitAdapter callKitAvailable] && [MXKAppSettings standardAppSettings].isCallKitEnabled; + if (!(event.eventType == MXEventTypeCallInvite && isCallKitActive)) + { + BOOL isEventRoomDirect = [session roomWithRoomId:event.roomId].isDirect; + NSString *notificationBody = [weakSelf notificationBodyForEvent:event inDirectRoom:isEventRoomDirect withRoomState:roomState]; + if (notificationBody) + { + UILocalNotification *eventNotification = [[UILocalNotification alloc] init]; + eventNotification.fireDate = [NSDate date]; + eventNotification.alertBody = notificationBody; + eventNotification.userInfo = @{ @"room_id" : event.roomId }; + + // Set sound name based on the value provided in action of MXPushRule + for (MXPushRuleAction *action in rule.actions) + { + if (action.actionType == MXPushRuleActionTypeSetTweak) + { + if ([action.parameters[@"set_tweak"] isEqualToString:@"sound"]) + { + NSString *soundName = action.parameters[@"value"]; + if ([soundName isEqualToString:@"default"]) + soundName = UILocalNotificationDefaultSoundName; + + eventNotification.soundName = soundName; + } + } + } + + [[UIApplication sharedApplication] scheduleLocalNotification:eventNotification]; + } + } + + dispatch_block_t completionBlock = [weakSelf createPushNotificationHandlingStateCleaningBlock]; + weakSelf.pushNotificationHandlingCompletionBlock = completionBlock; + + // This delay will help us to process push events which we receive earlier than push notifications associated with them + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + completionBlock(); + }); + }; + + weakNotificationsListenerBlock = notificationsListenerBlock; + self.notificationListenerBlock = notificationsListenerBlock; + + [session.notificationCenter listenToNotifications:notificationsListenerBlock]; + + // Run timeout block + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(120.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + timeoutBlock(); + }); +} + +/** + Create and return a block which clean all state associated wiht push notifications handling + */ +- (dispatch_block_t)createPushNotificationHandlingStateCleaningBlock +{ + __weak typeof(self) weakSelf = self; + MXSession *session = mxSessionArray.firstObject; + + return dispatch_block_create(0, ^{ + [session.notificationCenter removeListener:weakSelf.notificationListenerBlock]; + + // Update icon badge number + [UIApplication sharedApplication].applicationIconBadgeNumber = [session riot_missedDiscussionsCount]; + + id handler = [MXSDKOptions sharedInstance].backgroundModeHandler; + + NSUInteger identifier = weakSelf.pushNotificationHandlingTaskIdentifier; + + weakSelf.handlingPushNotification = NO; + weakSelf.pushNotificationHandlingTaskIdentifier = [handler invalidIdentifier]; + + weakSelf.notificationListenerBlock = nil; + weakSelf.pushNotificationHandlingTimeoutBlock = nil; + weakSelf.pushNotificationHandlingCompletionBlock = nil; + + [handler endBackgrounTaskWithIdentifier:identifier]; + }); +} + +- (nullable NSString *)notificationBodyForEvent:(MXEvent *)event inDirectRoom:(BOOL)isDirect withRoomState:(MXRoomState *)roomState +{ + if (!event.content || !event.content.count) + return nil; + + NSString *notificationBody; + NSString *eventSenderName = [roomState memberName:event.sender]; + + if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) + { + NSString *msgType = event.content[@"msgtype"]; + NSString *content = event.content[@"body"]; + + if (!isDirect) + { + NSString *roomDisplayName = roomState.displayname; + + if ([msgType isEqualToString:@"m.text"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; + else if ([msgType isEqualToString:@"m.emote"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; + else if ([msgType isEqualToString:@"m.image"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; + else + // Unencrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + } + else + { + if ([msgType isEqualToString:@"m.text"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; + else if ([msgType isEqualToString:@"m.emote"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; + else if ([msgType isEqualToString:@"m.image"]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; + else + // Unencrypted messages falls here + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; + } + } + else if (event.eventType == MXEventTypeCallInvite) + { + NSString *sdp = event.content[@"offer"][@"sdp"]; + BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; + + if (!isVideoCall) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; + } + else if (event.eventType == MXEventTypeRoomMember) + { + NSString *roomName = roomState.name; + NSString *roomAlias = roomState.aliases.firstObject; + + if (roomName) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomName]; + else if (roomAlias) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomAlias]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; + } + + return notificationBody; } - (void)refreshApplicationIconBadgeNumber @@ -1867,7 +2014,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)logout { - [[UIApplication sharedApplication] unregisterForRemoteNotifications]; + self.pushRegistry = nil; isAPNSRegistered = NO; // Clear cache diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 4d6fb79f8..4d6cc4b1e 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -328,6 +328,7 @@ "settings_fail_to_update_profile" = "Fail to update profile"; "settings_enable_push_notif" = "Notifications on this device"; +"settings_show_decrypted_content" = "Show decrypted content"; "settings_global_settings_info" = "Global notification settings are available on your %@ web client"; "settings_pin_rooms_with_missed_notif" = "Pin rooms with missed notifications"; "settings_pin_rooms_with_unread" = "Pin rooms with unread messages"; diff --git a/Riot/Categories/MXSession+Riot.h b/Riot/Categories/MXSession+Riot.h new file mode 100644 index 000000000..9bc296a98 --- /dev/null +++ b/Riot/Categories/MXSession+Riot.h @@ -0,0 +1,28 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import + +@interface MXSession (Riot) + +/** + The current number of rooms with missed notifications, including the invites. + */ +- (NSUInteger)riot_missedDiscussionsCount; + +@end diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m new file mode 100644 index 000000000..3d8805b5e --- /dev/null +++ b/Riot/Categories/MXSession+Riot.m @@ -0,0 +1,51 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXSession+Riot.h" + +#import "MXRoom+Riot.h" + +@implementation MXSession (Riot) + +- (NSUInteger)riot_missedDiscussionsCount +{ + NSUInteger missedDiscussionsCount = 0; + + // Sum all the rooms with missed notifications. + for (MXRoomSummary *roomSummary in self.roomsSummaries) + { + NSUInteger notificationCount = roomSummary.notificationCount; + + // Ignore the regular notification count if the room is in 'mentions only" mode at the Riot level. + if (roomSummary.room.isMentionsOnly) + { + // Only the highlighted missed messages must be considered here. + notificationCount = roomSummary.highlightCount; + } + + if (notificationCount) + { + missedDiscussionsCount++; + } + } + + // Add the invites count + missedDiscussionsCount += [self invitedRooms].count; + + return missedDiscussionsCount; +} + +@end diff --git a/Riot/ViewController/MasterTabBarController.m b/Riot/ViewController/MasterTabBarController.m index 8c02e2c81..fb5d451d3 100644 --- a/Riot/ViewController/MasterTabBarController.m +++ b/Riot/ViewController/MasterTabBarController.m @@ -23,6 +23,7 @@ #import "AppDelegate.h" #import "MXRoom+Riot.h" +#import "MXSession+Riot.h" @interface MasterTabBarController () { @@ -454,26 +455,7 @@ // Considering all the current sessions. for (MXSession *session in mxSessionArray) { - // Sum all the rooms with missed notifications. - for (MXRoomSummary *roomSummary in session.roomsSummaries) - { - NSUInteger notificationCount = roomSummary.notificationCount; - - // Ignore the regular notification count if the room is in 'mentions only" mode at the Riot level. - if (roomSummary.room.isMentionsOnly) - { - // Only the highlighted missed messages must be considered here. - notificationCount = roomSummary.highlightCount; - } - - if (notificationCount) - { - roomCount ++; - } - } - - // Add the invites count - roomCount += [session invitedRooms].count; + roomCount += [session riot_missedDiscussionsCount]; } return roomCount; diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index 3858e6c01..8ebd6fdf2 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -61,6 +61,7 @@ enum enum { NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX = 0, + NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT, NOTIFICATION_SETTINGS_GLOBAL_SETTINGS_INDEX, NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX, NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX, @@ -1626,6 +1627,18 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); cell = labelAndSwitchCell; } + else if (row == NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_show_decrypted_content", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = account.showDecryptedContentInNotifications; + labelAndSwitchCell.mxkSwitch.enabled = account.pushNotificationServiceIsActive; + [labelAndSwitchCell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventValueChanged]; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowDecodedContent:) forControlEvents:UIControlEventValueChanged]; + + cell = labelAndSwitchCell; + } else if (row == NOTIFICATION_SETTINGS_GLOBAL_SETTINGS_INDEX) { MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; @@ -2683,6 +2696,12 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); [MXKAppSettings standardAppSettings].enableCallKit = switchButton.isOn; } +- (void)toggleShowDecodedContent:(id)sender +{ + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + account.showDecryptedContentInNotifications = !account.showDecryptedContentInNotifications; +} + - (void)toggleLocalContactsSync:(id)sender { UISwitch *switchButton = (UISwitch*)sender;