From ee0c18ae6a5c0c40d19d30e335488e733df2c11f Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 11:56:33 +0200 Subject: [PATCH 01/21] Send sticker: UI integration: RoomInputToolbar: Let user choose between "Send photo or video" and "Send sticker" https://github.com/vector-im/riot-ios/issues/1860 --- Riot/Assets/en.lproj/Vector.strings | 4 + .../RoomInputToolbar/RoomInputToolbarView.h | 16 +++ .../RoomInputToolbar/RoomInputToolbarView.m | 120 +++++++++++++----- 3 files changed, 107 insertions(+), 33 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index d17a8f452..eb29d2426 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -267,6 +267,8 @@ "room_event_action_view_encryption" = "Encryption Information"; "room_warning_about_encryption" = "End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption."; "room_event_failed_to_send" = "Failed to send"; +"room_action_send_photo_or_video" = "Send photo or video"; +"room_action_send_sticker" = "Send sticker"; // Unknown devices "unknown_devices_alert_title" = "Room contains unknown devices"; @@ -568,6 +570,8 @@ // Widget "widget_no_power_to_manage" = "You need permission to manage widgets in this room"; "widget_creation_failure" = "Widget creation has failed"; +"widget_sticker_picker_no_stickerpacks_alert" = "You don't currently have any stickerpacks enabled."; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Add some now?"; // Widget Integration Manager "widget_integration_need_to_be_able_to_invite" = "You need to be able to invite users to do that."; diff --git a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.h b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.h index 0f5797583..ccf18e143 100644 --- a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.h +++ b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.h @@ -18,12 +18,28 @@ #import "MediaPickerViewController.h" +@protocol RoomInputToolbarViewDelegate + +/** + Tells the delegate that the user wants to display the sticker picker. + + @param toolbarView the room input toolbar view. + */ +- (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView; + +@end + /** `RoomInputToolbarView` instance is a view used to handle all kinds of available inputs for a room (message composer, attachments selection...). */ @interface RoomInputToolbarView : MXKRoomInputToolbarViewWithHPGrowingText +/** + The delegate notified when inputs are ready. + */ +@property (nonatomic) id delegate; + @property (weak, nonatomic) IBOutlet UIView *mainToolbarView; @property (weak, nonatomic) IBOutlet UIView *separatorView; diff --git a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m index cd30b6802..47c243199 100644 --- a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m +++ b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m @@ -29,17 +29,21 @@ #import +#import "WidgetManager.h" +#import "IntegrationManagerViewController.h" + @interface RoomInputToolbarView() { MediaPickerViewController *mediaPicker; - // The call type selection (voice or video) - UIAlertController *callActionSheet; + // The intermediate action sheet + UIAlertController *actionSheet; } @end @implementation RoomInputToolbarView +@dynamic delegate; + (UINib *)nib { @@ -228,24 +232,53 @@ // Check whether media attachment is supported if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:presentViewController:)]) { - // MediaPickerViewController is based on the Photos framework. So it is available only for iOS 8 and later. - Class PHAsset_class = NSClassFromString(@"PHAsset"); - if (PHAsset_class) - { - mediaPicker = [MediaPickerViewController mediaPickerViewController]; - mediaPicker.mediaTypes = @[(NSString *)kUTTypeImage, (NSString *)kUTTypeMovie]; - mediaPicker.delegate = self; - UINavigationController *navigationController = [UINavigationController new]; - [navigationController pushViewController:mediaPicker animated:NO]; - - [self.delegate roomInputToolbarView:self presentViewController:navigationController]; - } - else - { - // We use UIImagePickerController by default for iOS < 8 - self.leftInputToolbarButton = self.attachMediaButton; - [super onTouchUpInside:self.leftInputToolbarButton]; - } + // Ask the user the kind of the call: voice or video? + actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + __weak typeof(self) weakSelf = self; + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_photo_or_video", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->actionSheet = nil; + + [self showMediaPicker]; + } + + }]]; + + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_sticker", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->actionSheet = nil; + + [self.delegate roomInputToolbarViewPresentStickerPicker:self]; + } + + }]]; + + [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->actionSheet = nil; + } + + }]]; + + [actionSheet popoverPresentationController].sourceView = self.voiceCallButton; + [actionSheet popoverPresentationController].sourceRect = self.voiceCallButton.bounds; + [self.window.rootViewController presentViewController:actionSheet animated:YES completion:nil]; } else { @@ -257,52 +290,52 @@ if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:placeCallWithVideo:)]) { // Ask the user the kind of the call: voice or video? - callActionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; __weak typeof(self) weakSelf = self; - [callActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"voice", @"Vector", nil) + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"voice", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; - self->callActionSheet = nil; + self->actionSheet = nil; [self.delegate roomInputToolbarView:self placeCallWithVideo:NO]; } }]]; - [callActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"video", @"Vector", nil) + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"video", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; - self->callActionSheet = nil; + self->actionSheet = nil; [self.delegate roomInputToolbarView:self placeCallWithVideo:YES]; } }]]; - [callActionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; - self->callActionSheet = nil; + self->actionSheet = nil; } }]]; - [callActionSheet popoverPresentationController].sourceView = self.voiceCallButton; - [callActionSheet popoverPresentationController].sourceRect = self.voiceCallButton.bounds; - [self.window.rootViewController presentViewController:callActionSheet animated:YES completion:nil]; + [actionSheet popoverPresentationController].sourceView = self.voiceCallButton; + [actionSheet popoverPresentationController].sourceRect = self.voiceCallButton.bounds; + [self.window.rootViewController presentViewController:actionSheet animated:YES completion:nil]; } } else if (button == self.hangupCallButton) @@ -316,15 +349,36 @@ [super onTouchUpInside:button]; } +- (void)showMediaPicker +{ + // MediaPickerViewController is based on the Photos framework. So it is available only for iOS 8 and later. + Class PHAsset_class = NSClassFromString(@"PHAsset"); + if (PHAsset_class) + { + mediaPicker = [MediaPickerViewController mediaPickerViewController]; + mediaPicker.mediaTypes = @[(NSString *)kUTTypeImage, (NSString *)kUTTypeMovie]; + mediaPicker.delegate = self; + UINavigationController *navigationController = [UINavigationController new]; + [navigationController pushViewController:mediaPicker animated:NO]; + + [self.delegate roomInputToolbarView:self presentViewController:navigationController]; + } + else + { + // We use UIImagePickerController by default for iOS < 8 + self.leftInputToolbarButton = self.attachMediaButton; + [super onTouchUpInside:self.leftInputToolbarButton]; + } +} - (void)destroy { [self dismissMediaPicker]; - if (callActionSheet) + if (actionSheet) { - [callActionSheet dismissViewControllerAnimated:NO completion:nil]; - callActionSheet = nil; + [actionSheet dismissViewControllerAnimated:NO completion:nil]; + actionSheet = nil; } [super destroy]; From 86e012026bb83ede08142c7a13a30d69f858a6b1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 14:36:09 +0200 Subject: [PATCH 02/21] Send sticker: WidgetManager: add userWidgets:ofTypes method https://github.com/vector-im/riot-ios/issues/1860 --- Riot/Utils/Widgets/WidgetManager.h | 10 ++++++++++ Riot/Utils/Widgets/WidgetManager.m | 19 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index 655a88aba..315e363e8 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -35,6 +35,7 @@ FOUNDATION_EXPORT NSString *const kWidgetModularEventTypeString; Known types widgets. */ FOUNDATION_EXPORT NSString *const kWidgetTypeJitsi; +FOUNDATION_EXPORT NSString *const kWidgetTypeStickerPicker; /** Posted when a widget has been created, updated or disabled. @@ -101,6 +102,15 @@ WidgetManagerErrorCode; */ - (NSArray *)userWidgets:(MXSession*)mxSession; +/** + List all widgets of a given type of an account. + + @param mxSession the session of the user account. + @param widgetTypes the types of widget to search. Nil means all types. + @return a list of widgets. + */ +- (NSArray *)userWidgets:(MXSession*)mxSession ofTypes:(NSArray*)widgetTypes; + /** Add a modular widget to a room. diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 48f753e64..cdbfb9835 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -23,6 +23,7 @@ NSString *const kWidgetMatrixEventTypeString = @"m.widget"; NSString *const kWidgetModularEventTypeString = @"im.vector.modular.widgets"; NSString *const kWidgetTypeJitsi = @"jitsi"; +NSString *const kWidgetTypeStickerPicker = @"m.stickerpicker"; NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification"; @@ -173,12 +174,23 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; } - (NSArray *)userWidgets:(MXSession*)mxSession +{ + return [self userWidgets:mxSession ofTypes:nil]; +} + +- (NSArray *)userWidgets:(MXSession*)mxSession ofTypes:(NSArray*)widgetTypes { // Get all widgets in the user account data NSMutableArray *userWidgets = [NSMutableArray array]; - for (NSDictionary *widgetEventContent in [mxSession.accountData accountDataForEventType:@"m.widgets"].allValues) + for (NSDictionary *widgetEventContent in [mxSession.accountData accountDataForEventType:kMXAccountDataTypeUserWidgets].allValues) { - // Patch: Modular uses a malformed key: "stateKey" instead of "state_key" + if (![widgetEventContent isKindOfClass:NSDictionary.class]) + { + NSLog(@"[WidgetManager] userWidgets: ERROR: invalid user widget format: %@", widgetEventContent); + continue; + } + + // Patch: Modular used a malformed key: "stateKey" instead of "state_key" // TODO: To remove once fixed server side NSDictionary *widgetEventContentFixed = widgetEventContent; if (!widgetEventContent[@"state_key"] && widgetEventContent[@"stateKey"]) @@ -189,7 +201,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; } MXEvent *widgetEvent = [MXEvent modelFromJSON:widgetEventContentFixed]; - if (widgetEvent) + if (widgetEvent + && (!widgetTypes || [widgetTypes containsObject:widgetEvent.content[@"type"]])) { Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:mxSession]; if (widget) From bba11ff74449cb7b85e347578486ef52be22329f Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 14:42:49 +0200 Subject: [PATCH 03/21] Send sticker: IntegrationManagerVC: add the ability to set a user widget https://github.com/vector-im/riot-ios/issues/1860 --- .../IntegrationManagerViewController.h | 8 + .../IntegrationManagerViewController.m | 178 ++++++++++++------ 2 files changed, 130 insertions(+), 56 deletions(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.h b/Riot/ViewController/Widgets/IntegrationManagerViewController.h index 3087c57f1..8edc670ce 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.h +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.h @@ -37,4 +37,12 @@ FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; */ - (instancetype)initForMXSession:(MXSession*)mxSession inRoom:(NSString*)roomId screen:(NSString*)screen widgetId:(NSString*)widgetId; +/** + Get the integration manager settings screen for a given widget type. + + @param widgetType the widget type. + @return the screen id for that widget type. + */ ++ (NSString*)screenForWidget:(NSString*)widgetType; + @end diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 86e118ecb..e00ac8f7b 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -50,6 +50,11 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; return self; } ++ (NSString*)screenForWidget:(NSString*)widgetType +{ + return [NSString stringWithFormat:@"type_%@", widgetType]; +} + - (void)destroy { [super destroy]; @@ -147,8 +152,25 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; if (!roomIdInEvent) { - [self sendLocalisedError:@"widget_integration_missing_room_id" toRequest:requestId]; - return; + // These APIs don't require roomId + // Get and set user widgets (not associated with a specific room) + // If roomId is specified, it must be validated, so room-based widgets agreed + // handled further down. + if ([@"set_widget" isEqualToString:action]) + { + [self setWidget:requestId data:requestData]; + return; + } + else if ([@"get_widgets" isEqualToString:action]) + { + [self getWidgets:requestId data:requestData]; + return; + } + else + { + [self sendLocalisedError:@"widget_integration_missing_room_id" toRequest:requestId]; + return; + } } if (![roomIdInEvent isEqualToString:roomId]) @@ -157,7 +179,6 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; return; } - // These APIs don't require userId if ([@"join_rules_state" isEqualToString:action]) { @@ -281,75 +302,120 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; - (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData { - NSLog(@"[IntegrationManagerVC] Received request to set widget in room %@.", roomId); + NSLog(@"[IntegrationManagerVC] Received request to set widget"); - MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; + NSString *widget_id, *widgetType, *widgetUrl; + NSString *widgetName; // optional + NSDictionary *widgetData ; // optional + BOOL userWidget = NO; - if (room) + MXJSONModelSetString(widget_id, requestData[@"widget_id"]); + MXJSONModelSetString(widgetType, requestData[@"type"]); + MXJSONModelSetString(widgetUrl, requestData[@"url"]); + MXJSONModelSetString(widgetName, requestData[@"name"]); + MXJSONModelSetDictionary(widgetData, requestData[@"data"]); + MXJSONModelSetBoolean(userWidget, requestData[@"userWidget"]); + + if (!widget_id) { - NSString *widget_id, *widgetType, *widgetUrl; - NSString *widgetName; // optional - NSDictionary *widgetData ; // optional + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; // new Error("Missing required widget fields.")); + return; + } - MXJSONModelSetString(widget_id, requestData[@"widget_id"]); - MXJSONModelSetString(widgetType, requestData[@"type"]); - MXJSONModelSetString(widgetUrl, requestData[@"url"]); - MXJSONModelSetString(widgetName, requestData[@"name"]); - MXJSONModelSetDictionary(widgetData, requestData[@"data"]); + if (!widgetType) + { + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; + return; + } - if (!widget_id) + NSMutableDictionary *widgetEventContent = [NSMutableDictionary dictionary]; + if (widgetUrl) + { + widgetEventContent[@"type"] = widgetType; + widgetEventContent[@"url"] = widgetUrl; + + if (widgetName) { - [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; // new Error("Missing required widget fields.")); - return; + widgetEventContent[@"name"] = widgetName; } + if (widgetData) + { + widgetEventContent[@"data"] = widgetData; + } + } + // else this is a deletion - NSMutableDictionary *widgetEventContent = [NSMutableDictionary dictionary]; + __weak __typeof__(self) weakSelf = self; + + if (userWidget) + { + // Update the user account data + NSMutableDictionary *userWidgets = [NSMutableDictionary dictionaryWithDictionary:[mxSession.accountData accountDataForEventType:kMXAccountDataTypeUserWidgets]]; + + // Delete existing widget with ID + [userWidgets removeObjectForKey:widget_id]; + + // Add new widget / update if (widgetUrl) { - if (!widgetType) - { - [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; - return; - } - - widgetEventContent[@"type"] = widgetType; - widgetEventContent[@"url"] = widgetUrl; - - if (widgetName) - { - widgetEventContent[@"name"] = widgetName; - } - if (widgetData) - { - widgetEventContent[@"data"] = widgetData; - } + userWidgets[widget_id] = @{ + @"content": widgetEventContent, + @"sender": mxSession.myUser.userId, + @"state_key": widget_id, + @"type": kWidgetMatrixEventTypeString, + @"id": widget_id, + }; } - __weak __typeof__(self) weakSelf = self; + [mxSession setAccountData:userWidgets forType:kMXAccountDataTypeUserWidgets success:^{ - // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? - [room sendStateEventOfType:kWidgetModularEventTypeString - content:widgetEventContent - stateKey:widget_id - success:^(NSString *eventId) { + typeof(self) self = weakSelf; + if (self) + { + [self sendNSObjectResponse:@{ + @"success": @(YES) + } + toRequest:requestId]; + } + } failure:^(NSError *error) { - typeof(self) self = weakSelf; - if (self) - { - [self sendNSObjectResponse:@{ - @"success": @(YES) - } - toRequest:requestId]; + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; + } + }]; + } + else + { + // Room widget + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; + if (room) + { + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + [room sendStateEventOfType:kWidgetModularEventTypeString + content:widgetEventContent + stateKey:widget_id + success:^(NSString *eventId) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendNSObjectResponse:@{ + @"success": @(YES) + } + toRequest:requestId]; + } } - } - failure:^(NSError *error) { + failure:^(NSError *error) { - typeof(self) self = weakSelf; - if (self) - { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; - } - }]; + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; + } + }]; + } } } From cfc7830f86f86e2286632d8ab5f2e6053d8ebc28 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 15:31:27 +0200 Subject: [PATCH 04/21] Send sticker: Create StickerPickerVC and use it from the RoomVC https://github.com/vector-im/riot-ios/issues/1860 --- Riot.xcodeproj/project.pbxproj | 6 ++ Riot/ViewController/RoomViewController.m | 38 ++++++++++++ .../Widgets/StickerPickerViewController.h | 21 +++++++ .../Widgets/StickerPickerViewController.m | 60 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 Riot/ViewController/Widgets/StickerPickerViewController.h create mode 100644 Riot/ViewController/Widgets/StickerPickerViewController.m diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 2908c6779..10fe0e0ac 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 32E84FA11F6BD32700CA0B89 /* apps-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 32E84F9E1F6BD32700CA0B89 /* apps-icon.png */; }; 32E84FA21F6BD32700CA0B89 /* apps-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 32E84F9F1F6BD32700CA0B89 /* apps-icon@2x.png */; }; 32E84FA31F6BD32700CA0B89 /* apps-icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 32E84FA01F6BD32700CA0B89 /* apps-icon@3x.png */; }; + 32EF474920B6EE990031695C /* StickerPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32EF474820B6EE990031695C /* StickerPickerViewController.m */; }; 32F3AE1A1F6FF4E600F0F004 /* WidgetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F3AE191F6FF4E600F0F004 /* WidgetViewController.m */; }; 32FD0A3D1EB0CD9B0072B066 /* BugReportViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FD0A3B1EB0CD9B0072B066 /* BugReportViewController.m */; }; 32FD0A3E1EB0CD9B0072B066 /* BugReportViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32FD0A3C1EB0CD9B0072B066 /* BugReportViewController.xib */; }; @@ -766,6 +767,8 @@ 32E84F9E1F6BD32700CA0B89 /* apps-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "apps-icon.png"; sourceTree = ""; }; 32E84F9F1F6BD32700CA0B89 /* apps-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "apps-icon@2x.png"; sourceTree = ""; }; 32E84FA01F6BD32700CA0B89 /* apps-icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "apps-icon@3x.png"; sourceTree = ""; }; + 32EF474720B6EE990031695C /* StickerPickerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StickerPickerViewController.h; sourceTree = ""; }; + 32EF474820B6EE990031695C /* StickerPickerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StickerPickerViewController.m; sourceTree = ""; }; 32F3AE181F6FF4E600F0F004 /* WidgetViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WidgetViewController.h; sourceTree = ""; }; 32F3AE191F6FF4E600F0F004 /* WidgetViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WidgetViewController.m; sourceTree = ""; }; 32FD0A3A1EB0CD9B0072B066 /* BugReportViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BugReportViewController.h; sourceTree = ""; }; @@ -1562,6 +1565,8 @@ 3233F7301F31F4BF006ACA81 /* JitsiViewController.xib */, 32C2356D1F7B871800E38FC5 /* WidgetPickerViewController.h */, 32C2356E1F7B871800E38FC5 /* WidgetPickerViewController.m */, + 32EF474720B6EE990031695C /* StickerPickerViewController.h */, + 32EF474820B6EE990031695C /* StickerPickerViewController.m */, ); path = Widgets; sourceTree = ""; @@ -3510,6 +3515,7 @@ F083BE661E7009ED00A9B29C /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, F083BE141E7009ED00A9B29C /* HomeViewController.m in Sources */, F083BDFB1E7009ED00A9B29C /* RoomSearchDataSource.m in Sources */, + 32EF474920B6EE990031695C /* StickerPickerViewController.m in Sources */, 3233F73C1F3306A7006ACA81 /* WidgetManager.m in Sources */, F0B8D0D31FDFECB200F34524 /* GroupTableViewCell.m in Sources */, F083BE281E7009ED00A9B29C /* StartChatViewController.m in Sources */, diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 71472f311..eca0667cf 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -115,6 +115,7 @@ #import "IntegrationManagerViewController.h" #import "WidgetPickerViewController.h" +#import "StickerPickerViewController.h" @interface RoomViewController () { @@ -2854,6 +2855,43 @@ self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; } +#pragma mark - RoomInputToolbarViewDelegate + +- (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView +{ + // Search for the sticker picker widget in the user account + Widget *widget = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.mxSession ofTypes:@[kWidgetTypeStickerPicker]].firstObject; + + if (widget) + { + // Display the widget + [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { + + StickerPickerViewController *stickerPickerVC = [[StickerPickerViewController alloc] initWithUrl:widgetUrl forWidget:widget]; + + stickerPickerVC.roomDataSource = self.roomDataSource; + + [self.navigationController pushViewController:stickerPickerVC animated:YES]; + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[RoomVC] Cannot display widget %@", widget); + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } + else + { + // The Sticker picker widget is not installed yet. Propose the user to install it + // TODO + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] + initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] + widgetId:kWidgetTypeStickerPicker]; + + [self presentViewController:modularVC animated:NO completion:nil]; + } +} + #pragma mark - MXKRoomInputToolbarViewDelegate - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView isTyping:(BOOL)typing diff --git a/Riot/ViewController/Widgets/StickerPickerViewController.h b/Riot/ViewController/Widgets/StickerPickerViewController.h new file mode 100644 index 000000000..036ac09d6 --- /dev/null +++ b/Riot/ViewController/Widgets/StickerPickerViewController.h @@ -0,0 +1,21 @@ +/* + Copyright 2018 New Vector 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 "WidgetViewController.h" + +@interface StickerPickerViewController : WidgetViewController + +@end diff --git a/Riot/ViewController/Widgets/StickerPickerViewController.m b/Riot/ViewController/Widgets/StickerPickerViewController.m new file mode 100644 index 000000000..80609ecb4 --- /dev/null +++ b/Riot/ViewController/Widgets/StickerPickerViewController.m @@ -0,0 +1,60 @@ +/* + Copyright 2018 New Vector 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 "StickerPickerViewController.h" + +#import "IntegrationManagerViewController.h" + +@interface StickerPickerViewController () + +@end + +@implementation StickerPickerViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.navigationItem.title = NSLocalizedStringFromTable(@"room_action_send_sticker", @"Vector", nil); + + // Hide back button title + self.parentViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(onEditButtonPressed)]; + [self.navigationItem setRightBarButtonItem: editButton animated:YES]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Make sure the content is up-to-date when we come back from the sticker picker settings screen + [webView reload]; +} + +- (void)onEditButtonPressed +{ + // Show the sticker picker settings screen + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] + initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] + widgetId:kWidgetTypeStickerPicker]; + + [self presentViewController:modularVC animated:NO completion:nil]; +} + +@end From 68fb1f16bf36081afddab92103d9b309c196d716 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 16:02:51 +0200 Subject: [PATCH 05/21] Send sticker: Do not list anymore user's widgets (the sticker picker atm) in the WidgetPickerVC https://github.com/vector-im/riot-ios/issues/1860 --- Riot/ViewController/RoomViewController.m | 2 +- Riot/ViewController/Widgets/WidgetPickerViewController.m | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index eca0667cf..7ffe4f630 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -3102,7 +3102,7 @@ // Matrix Apps button else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) { - if ([self widgetsCount:YES]) + if ([self widgetsCount:NO]) { WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId]; diff --git a/Riot/ViewController/Widgets/WidgetPickerViewController.m b/Riot/ViewController/Widgets/WidgetPickerViewController.m index 72b4665ee..fb663c1b0 100644 --- a/Riot/ViewController/Widgets/WidgetPickerViewController.m +++ b/Riot/ViewController/Widgets/WidgetPickerViewController.m @@ -53,15 +53,9 @@ MXRoom *room = [mxSession roomWithRoomId:roomId]; - NSArray *roomWidgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + NSArray *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] inRoom:room]; - NSArray *userWidgets = [[WidgetManager sharedManager] userWidgets:room.mxSession]; - - NSMutableArray *widgets = [NSMutableArray array]; - [widgets addObjectsFromArray:roomWidgets]; - [widgets addObjectsFromArray:userWidgets]; - // List widgets for (Widget *widget in widgets) { From ce48b54784e064b54ece1586f46b2ca033bb2d99 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 16:43:38 +0200 Subject: [PATCH 06/21] Scalar API: Add support of "get_room_enc_state" --- .../IntegrationManagerViewController.m | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index e00ac8f7b..e0568f5f0 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -179,6 +179,18 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; return; } + // Get and set room-based widgets + if ([@"set_widget" isEqualToString:action]) + { + [self setWidget:requestId data:requestData]; + return; + } + else if ([@"get_widgets" isEqualToString:action]) + { + [self getWidgets:requestId data:requestData]; + return; + } + // These APIs don't require userId if ([@"join_rules_state" isEqualToString:action]) { @@ -195,14 +207,9 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self getMembershipCount:requestId data:requestData]; return; } - else if ([@"set_widget" isEqualToString:action]) + else if ([@"get_room_enc_state" isEqualToString:action]) { - [self setWidget:requestId data:requestData]; - return; - } - else if ([@"get_widgets" isEqualToString:action]) - { - [self getWidgets:requestId data:requestData]; + [self getRoomEncState:requestId data:requestData]; return; } else if ([@"can_send_event" isEqualToString:action]) @@ -442,6 +449,15 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; [self sendNSObjectResponse:widgetStateEvents toRequest:requestId]; } +- (void)getRoomEncState:(NSString*)requestId data:(NSDictionary*)requestData +{ + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; + if (room) + { + [self sendBoolResponse:room.state.isEncrypted toRequest:requestId]; + } +} + - (void)canSendEvent:(NSString*)requestId data:(NSDictionary*)requestData { NSString *eventType; From 16419eb6e8f6423d3ebb3916bb0abbd5f16154d2 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 18:22:59 +0200 Subject: [PATCH 07/21] Send sticker: Fix the missing delete button on a sticker pack https://github.com/vector-im/riot-ios/issues/1860 --- Riot/ViewController/RoomViewController.m | 2 +- Riot/ViewController/Widgets/StickerPickerViewController.m | 2 +- Riot/ViewController/Widgets/WidgetViewController.h | 5 +++++ Riot/ViewController/Widgets/WidgetViewController.m | 4 +--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 7ffe4f630..cd5483a15 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2886,7 +2886,7 @@ initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] - widgetId:kWidgetTypeStickerPicker]; + widgetId:nil]; [self presentViewController:modularVC animated:NO completion:nil]; } diff --git a/Riot/ViewController/Widgets/StickerPickerViewController.m b/Riot/ViewController/Widgets/StickerPickerViewController.m index 80609ecb4..c4270bddc 100644 --- a/Riot/ViewController/Widgets/StickerPickerViewController.m +++ b/Riot/ViewController/Widgets/StickerPickerViewController.m @@ -52,7 +52,7 @@ initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] - widgetId:kWidgetTypeStickerPicker]; + widgetId:self.widget.widgetId]; [self presentViewController:modularVC animated:NO completion:nil]; } diff --git a/Riot/ViewController/Widgets/WidgetViewController.h b/Riot/ViewController/Widgets/WidgetViewController.h index 0f9a70685..70c280c93 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.h +++ b/Riot/ViewController/Widgets/WidgetViewController.h @@ -27,6 +27,11 @@ */ @interface WidgetViewController : WebViewViewController +/** + The displayed widget. + */ +@property (nonatomic, readonly) Widget *widget; + /** The room data source. Required if the widget needs to post messages. diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index aad876a52..e1440bc66 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -21,13 +21,11 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);"; @interface WidgetViewController () -{ - Widget *widget; -} @end @implementation WidgetViewController +@synthesize widget; - (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)theWidget { From 7eb8fb376ede91998cb7da9bfe214f522c53667a Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 18:40:19 +0200 Subject: [PATCH 08/21] Send sticker: Show the "You don't currently have any stickerpacks enabled" popup https://github.com/vector-im/riot-ios/issues/1860 --- Riot/ViewController/RoomViewController.m | 51 ++++++++++++++++++++---- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index cd5483a15..b41177142 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2881,14 +2881,51 @@ else { // The Sticker picker widget is not installed yet. Propose the user to install it - // TODO - IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] - initForMXSession:self.roomDataSource.mxSession - inRoom:self.roomDataSource.roomId - screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] - widgetId:nil]; + __weak typeof(self) weakSelf = self; - [self presentViewController:modularVC animated:NO completion:nil]; + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + NSString *alertMessage = [NSString stringWithFormat:@"%@\n%@", + NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert", @"Vector", nil), + NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert_add_now", @"Vector", nil) + ]; + + currentAlert = [UIAlertController alertControllerWithTitle:nil message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) + { + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + // Show the sticker picker settings screen + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] + initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] + widgetId:nil]; + + [self presentViewController:modularVC animated:NO completion:nil]; + } + }]]; + + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; } } From d44867cfdead7250d669990a342091ef73b582a4 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 19:01:19 +0200 Subject: [PATCH 09/21] BF: The app does not list widgets anymore --- Riot/ViewController/RoomViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 71472f311..e8a3c03b7 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -3650,7 +3650,7 @@ inRoom:self.roomDataSource.room].count; if (includeUserWidgets) { - widgetsCount = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.room.mxSession].count; + widgetsCount += [[WidgetManager sharedManager] userWidgets:self.roomDataSource.room.mxSession].count; } return widgetsCount; From 38ba63d916527d4cee90c4bdc8845eef578380af Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 25 May 2018 14:41:41 +0200 Subject: [PATCH 10/21] Update Authors --- AUTHORS.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 5253de51c..47857b64a 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -42,4 +42,6 @@ Joey Watts Arash Tabrizian * PR #1828 Fix issue #1793 Confirmation popup when leaving room * PR #1824 Fix issue #1816 Support specifying kick and ban msgs - \ No newline at end of file + +Doug Earnshaw + * PR #1865 Fix timezone interval \ No newline at end of file From 0aac1835c5faad46fe314c3db3fd84b012c2c937 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 28 May 2018 14:51:51 +0200 Subject: [PATCH 11/21] Add deactivate account english string localizations --- Riot/Assets/en.lproj/Vector.strings | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 949c079a6..cc7f7e7dd 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -316,6 +316,7 @@ "settings_flair" = "Show flair where allowed"; "settings_devices" = "DEVICES"; "settings_cryptography" = "CRYPTOGRAPHY"; +"settings_deactivate_account" = "DEACTIVATE ACCOUNT"; "settings_sign_out" = "Sign Out"; "settings_sign_out_confirmation" = "Are you sure?"; @@ -396,6 +397,8 @@ "settings_crypto_export" = "Export keys"; "settings_crypto_blacklist_unverified_devices" = "Encrypt to verified devices only"; +"settings_deactivate_my_account" = "Deactivate my account"; + // Room Details "room_details_title" = "Room Details"; "room_details_people" = "Members"; @@ -596,3 +599,22 @@ // GDPR "gdpr_consent_not_given_alert_message" = "To continue using the %@ homeserver you must review and agree to the terms and conditions."; "gdpr_consent_not_given_alert_review_now_action" = "Review now"; + +// Deactivate account + +"deactivate_account_title" = "Deactivate Account"; + +"deactivate_account_informations_part1" = "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. "; +"deactivate_account_informations_part2_emphasize" = "This action is irreversible."; +"deactivate_account_informations_part3" = "\n\nDeactivating your account "; +"deactivate_account_informations_part4_emphasize" = "does not by default cause us to forget messages you have sent. "; +"deactivate_account_informations_part5" = "If you would like us to forget your messages, please tick the box below\n\nMessage visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy."; + +"deactivate_account_forget_messages_information_part1" = "Please forget all messages I have sent when my account is deactivated ("; +"deactivate_account_forget_messages_information_part2_emphasize" = "Warning"; +"deactivate_account_forget_messages_information_part3" = ": this will cause future users to see an incomplete view of conversations)"; + +"deactivate_account_validate_action" = "Deactivate account"; + +"deactivate_account_password_alert_title" = "Deactivate Account"; +"deactivate_account_password_alert_message" = "To continue, please enter your password"; From 098c95c8226c47664fb6c5de4fa5dda0a3bdff05 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 29 May 2018 14:50:34 +0200 Subject: [PATCH 12/21] PostMessage API: better manage _id --- Riot/Assets/js/postMessageAPI.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Riot/Assets/js/postMessageAPI.js b/Riot/Assets/js/postMessageAPI.js index 2b145debe..81b1fe9cb 100644 --- a/Riot/Assets/js/postMessageAPI.js +++ b/Riot/Assets/js/postMessageAPI.js @@ -31,15 +31,6 @@ window.riotIOS.events = {}; // Listen to messages posted by the widget window.riotIOS.onMessage = function(event) { - // Do not SPAM ObjC with event already managed - if (riotIOS.events[event.data._id]) { - return; - } - - if (!event.origin) { // stupid chrome - event.origin = event.originalEvent.origin; - } - // Use an internal "_id" field for matching onMessage events and requests // _id was originally used by the Modular API. Keep it if (!event.data._id) { @@ -52,6 +43,15 @@ window.riotIOS.onMessage = function(event) { if (!event.data._id) { event.data._id = Date.now() + "-" + Math.random().toString(36); } + + // Do not SPAM ObjC with event already managed + if (riotIOS.events[event.data._id]) { + return; + } + + if (!event.origin) { // stupid chrome + event.origin = event.originalEvent.origin; + } // Keep this event for future usage riotIOS.events[event.data._id] = event; From 5f199d6a8a3d80c582f44fcb81e23309d0b27dfe Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 29 May 2018 15:20:39 +0200 Subject: [PATCH 13/21] PostMessage API: Add support of integration_manager_open --- .../Widgets/WidgetViewController.m | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index e1440bc66..8cc7cd6d6 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -17,6 +17,7 @@ #import "WidgetViewController.h" #import "AppDelegate.h" +#import "IntegrationManagerViewController.h" NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);"; @@ -214,6 +215,37 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse // Consider we are done with the sticker picker widget [self withdrawViewControllerAnimated:YES completion:nil]; } + else if ([@"integration_manager_open" isEqualToString:action]) + { + NSDictionary *widgetData; + NSString *integType, *integId; + MXJSONModelSetDictionary(widgetData, requestData[@"widgetData"]); + if (widgetData) + { + MXJSONModelSetString(integType, widgetData[@"integType"]); + MXJSONModelSetString(integId, widgetData[@"integId"]); + } + + if (integType && integId) + { + // Open the integration manager requested page + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] + initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:[IntegrationManagerViewController screenForWidget:integType] + widgetId:integId]; + + [self presentViewController:modularVC animated:NO completion:nil]; + } + else + { + NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Invalid content for integration_manager_open: %@", requestData); + } + } + else + { + NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Unsupported action: %@: %@", action, requestData); + } } - (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId From 9c5eaa088fb394760e8c18a5553213d600096250 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 30 May 2018 11:54:35 +0200 Subject: [PATCH 14/21] Send sticker: Update after Giom's remark https://github.com/vector-im/riot-ios/issues/1860 --- Riot/ViewController/RoomViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 5f2d0ab4b..093f89e17 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2924,7 +2924,7 @@ } }]]; - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"]; + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; } } From d6a1477cdd5df962fbf6243d8b3042823d20782a Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 30 May 2018 14:15:32 +0200 Subject: [PATCH 15/21] WidgetVC: Update UIWebView by WKWebview to improve Sticker Picker performance --- .../Widgets/WidgetViewController.m | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index 8cc7cd6d6..64e9be433 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -42,7 +43,6 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse { [super viewDidLoad]; - webView.scalesPageToFit = NO; webView.scrollView.bounces = NO; // Disable opacity so that the webview background uses the current interface theme @@ -91,16 +91,16 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse [self presentViewController:alert animated:YES completion:nil]; } -#pragma mark - UIWebViewDelegate +#pragma mark - WKNavigationDelegate --(void)webViewDidFinishLoad:(UIWebView *)theWebView +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { [self enableDebug]; // Setup js code NSString *path = [[NSBundle mainBundle] pathForResource:@"postMessageAPI" ofType:@"js"]; NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; - [webView stringByEvaluatingJavaScriptFromString:js]; + [webView evaluateJavaScript:js completionHandler:nil]; [self stopActivityIndicator]; @@ -119,14 +119,16 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse } } -- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { - NSString *urlString = [[request URL] absoluteString]; + NSString *urlString = navigationAction.request.URL.absoluteString; + // TODO: We should use the WebKit PostMessage API and the + // `didReceiveScriptMessage` delegate to manage the JS<->Native bridge if ([urlString hasPrefix:@"js:"]) { - // Listen only to scheme of the JS-UIWebView bridge - NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; + // Listen only to the scheme of the JS<->Native bridge + NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByRemovingPercentEncoding]; NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; @@ -151,20 +153,22 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse } } - return NO; + decisionHandler(WKNavigationActionPolicyCancel); + return; } - if (navigationType == UIWebViewNavigationTypeLinkClicked ) + if (navigationAction.navigationType == WKNavigationTypeLinkActivated) { // Open links outside the app - [[UIApplication sharedApplication] openURL:[request URL]]; - return NO; + [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; + decisionHandler(WKNavigationActionPolicyCancel); + return; } - return YES; + decisionHandler(WKNavigationActionPolicyAllow); } -- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error +- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { // Filter out the users's scalar token NSString *errorDescription = error.description; @@ -255,7 +259,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse requestId, response ? @"true" : @"false"]; - [webView stringByEvaluatingJavaScriptFromString:js]; + [webView evaluateJavaScript:js completionHandler:nil]; } - (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId @@ -264,7 +268,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse requestId, @(response)]; - [webView stringByEvaluatingJavaScriptFromString:js]; + [webView evaluateJavaScript:js completionHandler:nil]; } - (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId @@ -290,7 +294,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse requestId, jsString]; - [webView stringByEvaluatingJavaScriptFromString:js]; + [webView evaluateJavaScript:js completionHandler:nil]; } - (void)sendError:(NSString*)message toRequest:(NSString*)requestId From c2259fdb04cf5c84aca35cfd21b69f0ce2542543 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 30 May 2018 16:59:18 +0200 Subject: [PATCH 16/21] Add DeactivateAccountViewController allowing to deactivate a user account from given Matrix session --- Riot.xcodeproj/project.pbxproj | 18 ++ .../DeactivateAccountViewController.h | 45 +++ .../DeactivateAccountViewController.m | 287 ++++++++++++++++++ ...DeactivateAccountViewController.storyboard | 130 ++++++++ 4 files changed, 480 insertions(+) create mode 100644 Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.h create mode 100644 Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m create mode 100644 Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 2908c6779..ea0bbd8d1 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -109,6 +109,8 @@ 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 */; }; 9D686B069F967C4D4BBC610F /* Pods_RiotPods_SiriIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB50EEBE8214352B9EBD6394 /* Pods_RiotPods_SiriIntents.framework */; }; + B19A173920B7F94800DF0BB0 /* DeactivateAccountViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B19A173820B7F94800DF0BB0 /* DeactivateAccountViewController.m */; }; + B19A173B20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B19A173A20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard */; }; DDDE2AB95F865F2292B1D315 /* Pods_RiotPods_RiotShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23D7292481328A48B8D5D4ED /* Pods_RiotPods_RiotShareExtension.framework */; }; F0131DE51F2200D600CBF707 /* RiotSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0131DE41F2200D600CBF707 /* RiotSplitViewController.m */; }; F0173EB51FCF346800B5F6A3 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = F0173EAF1FCF346800B5F6A3 /* Vector.strings */; }; @@ -794,6 +796,9 @@ 92726A501F587410004AD26F /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; A5030B7C3C0B6EB83A9257BD /* Pods-RiotPods-Riot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-Riot.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot.debug.xcconfig"; sourceTree = ""; }; B0FAA1A49F76B0CE15C5CBD8 /* Pods_RiotPods_Riot.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_Riot.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B19A173720B7F94800DF0BB0 /* DeactivateAccountViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeactivateAccountViewController.h; sourceTree = ""; }; + B19A173820B7F94800DF0BB0 /* DeactivateAccountViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeactivateAccountViewController.m; sourceTree = ""; }; + B19A173A20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = DeactivateAccountViewController.storyboard; sourceTree = ""; }; C195C53961EA28E6900AEB68 /* Pods-Riot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riot.release.xcconfig"; path = "Pods/Target Support Files/Pods-Riot/Pods-Riot.release.xcconfig"; sourceTree = ""; }; C5258DFF261AA3AB228A3F11 /* Pods-RiotPods-RiotShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension.debug.xcconfig"; sourceTree = ""; }; DB50EEBE8214352B9EBD6394 /* Pods_RiotPods_SiriIntents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_SiriIntents.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1717,6 +1722,16 @@ path = SiriIntents; sourceTree = ""; }; + B19A173520B6F89900DF0BB0 /* DeactivateAccount */ = { + isa = PBXGroup; + children = ( + B19A173720B7F94800DF0BB0 /* DeactivateAccountViewController.h */, + B19A173820B7F94800DF0BB0 /* DeactivateAccountViewController.m */, + B19A173A20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard */, + ); + path = DeactivateAccount; + sourceTree = ""; + }; F0173EAE1FCF346800B5F6A3 /* vi.lproj */ = { isa = PBXGroup; children = ( @@ -2283,6 +2298,7 @@ 24B5103F1EFA88CC004C6AD2 /* ReadReceiptsViewController.xib */, F0B4CBA31F418D0B008E99C5 /* WebViewViewController.h */, F0B4CBA41F418D0B008E99C5 /* WebViewViewController.m */, + B19A173520B6F89900DF0BB0 /* DeactivateAccount */, ); path = ViewController; sourceTree = ""; @@ -3151,6 +3167,7 @@ F083BDDA1E7009ED00A9B29C /* typing.png in Resources */, F083BE831E7009ED00A9B29C /* RecentTableViewCell.xib in Resources */, F046DC731FE1786500E3DAF0 /* GroupHomeViewController.xib in Resources */, + B19A173B20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard in Resources */, F0E5D9141FF6FF3F00560D7F /* GroupRoomTableViewCell.xib in Resources */, F083BDB71E7009ED00A9B29C /* remove_icon@2x.png in Resources */, F083BDD31E7009ED00A9B29C /* settings_icon@3x.png in Resources */, @@ -3630,6 +3647,7 @@ F0E05A021E963103004B83FB /* FavouritesViewController.m in Sources */, F083BE941E7009ED00A9B29C /* FilesSearchTableViewCell.m in Sources */, F083BE921E7009ED00A9B29C /* SimpleRoomTitleView.m in Sources */, + B19A173920B7F94800DF0BB0 /* DeactivateAccountViewController.m in Sources */, F083BE981E7009ED00A9B29C /* MessagesSearchResultTextMsgBubbleCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.h b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.h new file mode 100644 index 000000000..4d403aa40 --- /dev/null +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.h @@ -0,0 +1,45 @@ +/* + Copyright 2018 New Vector 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 UIKit; +@import MatrixKit; + +#pragma mark - Types + +@class DeactivateAccountViewController; + +#pragma mark - Protocol + +@protocol DeactivateAccountViewControllerDelegate + +- (void)deactivateAccountViewControllerDidCancel:(DeactivateAccountViewController*)deactivateAccountViewController; +- (void)deactivateAccountViewControllerDidDeactivateWithSuccess:(DeactivateAccountViewController*)deactivateAccountViewController; + +@end + +#pragma mark - Interface + +@interface DeactivateAccountViewController : MXKViewController + +#pragma mark - Properties + +@property (nonatomic, weak) id delegate; + +#pragma mark - Class Methods + ++ (DeactivateAccountViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession; + +@end diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m new file mode 100644 index 000000000..c1aa43ceb --- /dev/null +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m @@ -0,0 +1,287 @@ +/* + Copyright 2018 New Vector 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 "DeactivateAccountViewController.h" + +#import "RiotDesignValues.h" + +#pragma mark - Defines & Constants + +static CGFloat const kButtonCornerRadius = 5.0; +static CGFloat const kTextFontSize = 15.0; +static NSTimeInterval const kActivityIndicatorAnimationDuration = 0.3; +static CGFloat const kActivityContainerViewCornerRadius = 5.0; + +#pragma mark - Private Interface + +@interface DeactivateAccountViewController () + +#pragma mark - Outlets + +@property (weak, nonatomic) IBOutlet UILabel *deactivateAccountInfosLabel; + +@property (weak, nonatomic) IBOutlet UILabel *forgetMessagesInfoLabel; +@property (weak, nonatomic) IBOutlet UIButton *forgetMessageButton; + +@property (weak, nonatomic) IBOutlet UIButton *deactivateAcccountButton; + + +#pragma mark - Private Properties + +@property (strong, nonatomic) NSDictionary *normalStringAttributes; +@property (strong, nonatomic) NSDictionary *emphasizeStringAttributes; + +@property (strong, nonatomic) MXKErrorAlertPresentation *errorPresentation; + +@end + +#pragma mark - Implementation + +@implementation DeactivateAccountViewController + +#pragma mark - Setup & Teardown + ++ (DeactivateAccountViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession +{ + DeactivateAccountViewController* viewController = [[UIStoryboard storyboardWithName:NSStringFromClass([DeactivateAccountViewController class]) bundle:[NSBundle mainBundle]] instantiateInitialViewController]; + [viewController addMatrixSession:matrixSession]; + return viewController; +} + +#pragma mark - View life cycle + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view. + + self.title = NSLocalizedStringFromTable(@"deactivate_account_title", @"Vector", nil); + + self.errorPresentation = [[MXKErrorAlertPresentation alloc] init]; + [self setupStringAttributes]; + [self setupViews]; +} + +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + +// [self.activityIndicatorContainerView.layer setCornerRadius:kActivityContainerViewCornerRadius]; + [self.deactivateAcccountButton.layer setCornerRadius:kButtonCornerRadius]; +} + +#pragma mark - Private + +- (void)setupStringAttributes +{ + self.normalStringAttributes = @{ + NSFontAttributeName: [UIFont systemFontOfSize:kTextFontSize], + NSForegroundColorAttributeName: kRiotPrimaryTextColor + }; + + + self.emphasizeStringAttributes = @{ + NSFontAttributeName: [UIFont systemFontOfSize:kTextFontSize weight:UIFontWeightBold], + NSForegroundColorAttributeName: kRiotPrimaryTextColor + }; +} + +- (void)setupViews +{ + [self setupNavigationBar]; + [self setupDeactivateAcccountButton]; + [self setupDeactivateAccountInfosLabel]; + [self setupForgetMessagesInfoLabel]; +} + +- (void)setupNavigationBar +{ + self.navigationController.navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName: kRiotColorRed }; + + UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) style:UIBarButtonItemStylePlain target:self action:@selector(cancelButtonAction:)]; + self.navigationItem.rightBarButtonItem = cancelBarButtonItem; +} + +- (void)setupDeactivateAcccountButton +{ + // Adjust button font size for small devices + self.deactivateAcccountButton.titleLabel.adjustsFontSizeToFitWidth = YES; + self.deactivateAcccountButton.titleLabel.minimumScaleFactor = 0.5; + self.deactivateAcccountButton.titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; + + self.deactivateAcccountButton.layer.masksToBounds = YES; + self.deactivateAcccountButton.backgroundColor = kRiotColorGreen; + [self.deactivateAcccountButton setTitle:NSLocalizedStringFromTable(@"deactivate_account_validate_action", @"Vector", nil) forState:UIControlStateNormal]; + [self.deactivateAcccountButton setTitleColor:kRiotColorSilver forState:UIControlStateDisabled]; +} + +- (void)setupDeactivateAccountInfosLabel +{ + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_informations_part1", @"Vector", nil) attributes:self.normalStringAttributes]]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_informations_part2_emphasize", @"Vector", nil) attributes:self.emphasizeStringAttributes]]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_informations_part3", @"Vector", nil) attributes:self.normalStringAttributes]]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_informations_part4_emphasize", @"Vector", nil) attributes:self.emphasizeStringAttributes]]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_informations_part5", @"Vector", nil) attributes:self.normalStringAttributes]]; + + [self.deactivateAccountInfosLabel setAttributedText:attributedString]; +} + +- (void)setupForgetMessagesInfoLabel +{ + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_forget_messages_information_part1", @"Vector", nil) attributes:self.normalStringAttributes]]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_forget_messages_information_part2_emphasize", @"Vector", nil) attributes:self.emphasizeStringAttributes]]; + + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"deactivate_account_forget_messages_information_part3", @"Vector", nil) attributes:self.normalStringAttributes]]; + + [self.forgetMessagesInfoLabel setAttributedText:attributedString]; +} + +- (void)enableUserActions:(BOOL)enableUserActions +{ + self.navigationItem.rightBarButtonItem.enabled = enableUserActions; + self.forgetMessageButton.enabled = enableUserActions; + self.deactivateAcccountButton.enabled = enableUserActions; +} + +- (void)presentPasswordRequiredAlertWithSubmitHandler:(void (^)(NSString *password))submitHandler + cancelHandler:(dispatch_block_t)cancelHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"deactivate_account_password_alert_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"deactivate_account_password_alert_message", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; + + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = YES; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; + + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + if (cancelHandler) + { + cancelHandler(); + } + }]]; + + __weak typeof(self) weakSelf = self; + + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"submit"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + UITextField *textField = alert.textFields.firstObject; + + typeof(weakSelf) strongSelf = weakSelf; + + if (strongSelf) + { + NSString *password = textField.text; + + if (submitHandler) + { + submitHandler(password); + } + } + }]]; + + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)deactivateAccountWithUserId:(NSString*)userId + andPassword:(NSString*)password + eraseAllMessages:(BOOL)eraseAllMessages +{ + if (password && userId) + { + [self enableUserActions:NO]; + [self startActivityIndicator]; + + // This assumes that the homeserver requires password UI auth + // for this endpoint. In reality it could be any UI auth. + + __weak typeof(self) weakSelf = self; + + NSDictionary *authParameters = @{@"user": userId, + @"password": password, + @"type": kMXLoginFlowTypePassword}; + + [self.mainSession deactivateAccountWithAuthParameters:authParameters eraseAccount:eraseAllMessages success:^{ + NSLog(@"[SettingsViewController] Deactivate account with success"); + + [weakSelf stopActivityIndicator]; + [weakSelf enableUserActions:YES]; + } failure:^(NSError *error) { + + NSLog(@"[SettingsViewController] Failed to deactivate account"); + + typeof(weakSelf) strongSelf = weakSelf; + + if (strongSelf) + { + [strongSelf stopActivityIndicator]; + [strongSelf enableUserActions:YES]; + [strongSelf.errorPresentation presentErrorFromViewController:strongSelf forError:error animated:YES handler:nil]; + } + }]; + } + else + { + NSLog(@"[SettingsViewController] Failed to deactivate account"); + [self.errorPresentation presentGenericErrorFromViewController:self animated:YES handler:nil]; + } +} + +#pragma mark - Actions + +- (void)cancelButtonAction:(id)sender +{ + [self.delegate deactivateAccountViewControllerDidCancel:self]; +} + +- (IBAction)forgetMessagesButtonAction:(UIButton*)sender +{ + self.forgetMessageButton.selected = !self.forgetMessageButton.selected; +} + +- (IBAction)deactivateAccountButtonAction:(id)sender +{ + __weak typeof(self) weakSelf = self; + + [self presentPasswordRequiredAlertWithSubmitHandler:^(NSString *password) { + + typeof(weakSelf) strongSelf = weakSelf; + + if (strongSelf) + { + NSString *userId = strongSelf.mainSession.myUser.userId; + [strongSelf deactivateAccountWithUserId:userId andPassword:password eraseAllMessages:strongSelf.forgetMessageButton.isEnabled]; + } + + } cancelHandler:^{ + + }]; +} + +@end diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard new file mode 100644 index 000000000..0f1ec1012 --- /dev/null +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ff199a99f9ae0415c0b30c66b31fa85902287afd Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 30 May 2018 17:04:58 +0200 Subject: [PATCH 17/21] Factorization of data user clear on logout. Reuse it for account deactivation. --- Riot/AppDelegate.h | 8 ++++++++ Riot/AppDelegate.m | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index c13524eac..872fa21a4 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -113,6 +113,14 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification; */ - (void)logoutWithConfirmation:(BOOL)askConfirmation completion:(void (^)(BOOL isLoggedOut))completion; +/** + Log out all the accounts without confirmation. + Show the authentication screen on successful logout. + + @param completion the block to execute at the end of the operation. + */ +- (void)logoutWithCompletion:(void (^)(BOOL isLoggedOut))completion; + #pragma mark - Matrix Accounts handling - (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index f0bfa3f95..29ffe0f65 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2667,6 +2667,16 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [topVC startActivityIndicator]; } + [self logoutWithCompletion:^(BOOL isLoggedOut) { + if (completion) + { + completion (YES); + } + }]; +} + +- (void)logoutWithCompletion:(void (^)(BOOL isLoggedOut))completion +{ self.pushRegistry = nil; isPushRegistered = NO; From d9ed6c4a84438abc8871e3a9a7afbcce80bb58b9 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 30 May 2018 17:05:42 +0200 Subject: [PATCH 18/21] Handle account deactivation from settings --- Riot/ViewController/SettingsViewController.m | 84 +++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index acb4c8126..4db9a4540 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -34,6 +34,7 @@ #import "CountryPickerViewController.h" #import "LanguagePickerViewController.h" +#import "DeactivateAccountViewController.h" #import "NBPhoneNumberUtil.h" #import "RageShakeManager.h" @@ -62,6 +63,7 @@ enum SETTINGS_SECTION_CRYPTOGRAPHY_INDEX, SETTINGS_SECTION_FLAIR_INDEX, SETTINGS_SECTION_DEVICES_INDEX, + SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX, SETTINGS_SECTION_COUNT }; @@ -130,7 +132,7 @@ enum { typedef void (^blockSettingsViewController_onReadyToDestroy)(); -@interface SettingsViewController () +@interface SettingsViewController () { // Current alert (if any). UIAlertController *currentAlert; @@ -234,6 +236,10 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); */ @property (nonatomic) BOOL newPhoneEditingEnabled; +@property (weak, nonatomic) DeactivateAccountViewController *deactivateAccountViewController; + +//@property (strong, nonatomic) ErrorPresenter *errorPresenter; + @end @implementation SettingsViewController @@ -258,6 +264,9 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); self.navigationItem.title = NSLocalizedStringFromTable(@"settings_title", @"Vector", nil); + // Remove back bar button title when pushing a view controller + self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; @@ -310,6 +319,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(onSave:)]; self.navigationItem.rightBarButtonItem.accessibilityIdentifier=@"SettingsVCNavBarSaveButton"; + // Observe user interface theme change. kRiotDesignValuesDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kRiotDesignValuesDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -1268,6 +1278,10 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); count = CRYPTOGRAPHY_COUNT; } } + else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) + { + count = 1; + } return count; } @@ -2151,6 +2165,32 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); cell = exportKeysBtnCell; } } + else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) + { + MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!deactivateAccountBtnCell) + { + deactivateAccountBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + deactivateAccountBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); + [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [deactivateAccountBtnCell.mxkButton setTintColor:kRiotColorRed]; + deactivateAccountBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; + deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = deactivateAccountBtnCell; + } return cell; } @@ -2228,6 +2268,18 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); } } + else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + { + // Check whether this section is visible + if (self.mainSession.crypto) + { + return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); + } + } + else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) + { + return NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); + } return nil; } @@ -3673,6 +3725,20 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); [self presentViewController:themePicker animated:YES completion:nil]; } +- (void)deactivateAccountAction +{ + DeactivateAccountViewController *deactivateAccountViewController = [DeactivateAccountViewController instantiateWithMatrixSession:self.mainSession]; + + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:deactivateAccountViewController]; + navigationController.modalPresentationStyle = UIModalPresentationFormSheet; + + [self presentViewController:navigationController animated:YES completion:nil]; + + deactivateAccountViewController.delegate = self; + + self.deactivateAccountViewController = deactivateAccountViewController; +} + #pragma mark - MediaPickerViewController Delegate - (void)dismissMediaPicker @@ -4033,4 +4099,20 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); [self refreshSettings]; } +#pragma mark - DeactivateAccountViewControllerDelegate + +- (void)deactivateAccountViewControllerDidDeactivateWithSuccess:(DeactivateAccountViewController *)deactivateAccountViewController +{ + NSLog(@"[SettingsViewController] Deactivate account with success"); + + [[AppDelegate theDelegate] logoutWithCompletion:^(BOOL isLoggedOut) { + NSLog(@"[SettingsViewController] Complete clear user data after account deactivation"); + }]; +} + +- (void)deactivateAccountViewControllerDidCancel:(DeactivateAccountViewController *)deactivateAccountViewController +{ + [deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil]; +} + @end From 60634cee099c9d016023ae80d42b2c09d4b71a91 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 31 May 2018 12:14:13 +0200 Subject: [PATCH 19/21] Handle theme change and analytics in DeactivateAccountViewController --- .../DeactivateAccountViewController.m | 48 +++++++++++++++++-- ...DeactivateAccountViewController.storyboard | 2 +- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m index c1aa43ceb..480cad6f4 100644 --- a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m @@ -17,13 +17,12 @@ #import "DeactivateAccountViewController.h" #import "RiotDesignValues.h" +#import "AppDelegate.h" #pragma mark - Defines & Constants static CGFloat const kButtonCornerRadius = 5.0; static CGFloat const kTextFontSize = 15.0; -static NSTimeInterval const kActivityIndicatorAnimationDuration = 0.3; -static CGFloat const kActivityContainerViewCornerRadius = 5.0; #pragma mark - Private Interface @@ -46,6 +45,8 @@ static CGFloat const kActivityContainerViewCornerRadius = 5.0; @property (strong, nonatomic) MXKErrorAlertPresentation *errorPresentation; +@property (weak, nonatomic) id themeDidChangeNotificationObserver; + @end #pragma mark - Implementation @@ -61,6 +62,19 @@ static CGFloat const kActivityContainerViewCornerRadius = 5.0; return viewController; } +- (void)destroy +{ + id notificationObserver = self.themeDidChangeNotificationObserver; + + if (notificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; + } + + [super destroy]; +} + + #pragma mark - View life cycle - (void)viewDidLoad @@ -73,18 +87,46 @@ static CGFloat const kActivityContainerViewCornerRadius = 5.0; self.errorPresentation = [[MXKErrorAlertPresentation alloc] init]; [self setupStringAttributes]; [self setupViews]; + [self userInterfaceThemeDidChange]; + [self registerThemeNotification]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Screen tracking + [[AppDelegate theDelegate] trackScreen:@"DeactivateAccount"]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; -// [self.activityIndicatorContainerView.layer setCornerRadius:kActivityContainerViewCornerRadius]; [self.deactivateAcccountButton.layer setCornerRadius:kButtonCornerRadius]; } +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return kRiotDesignStatusBarStyle; +} + #pragma mark - Private +- (void)registerThemeNotification +{ + self.themeDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kRiotDesignValuesDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + [self userInterfaceThemeDidChange]; + }]; +} + +- (void)userInterfaceThemeDidChange +{ + self.view.backgroundColor = kRiotPrimaryBgColor; + self.defaultBarTintColor = kRiotSecondaryBgColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; +} + - (void)setupStringAttributes { self.normalStringAttributes = @{ diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard index 0f1ec1012..63668b7b6 100644 --- a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.storyboard @@ -75,7 +75,7 @@ Message visibility in Matrix is similar to email. Our forgetting your messages m - + From a572ec1c7c6bbda7937a98e24dfee988becd03b6 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 31 May 2018 12:15:13 +0200 Subject: [PATCH 20/21] Remove useless commented code in SettingsViewController --- Riot/ViewController/SettingsViewController.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index 4db9a4540..fb90398b1 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -238,8 +238,6 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); @property (weak, nonatomic) DeactivateAccountViewController *deactivateAccountViewController; -//@property (strong, nonatomic) ErrorPresenter *errorPresenter; - @end @implementation SettingsViewController From 446592c97c86cf55b1b9bd6354a67c871d05852a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 31 May 2018 13:33:17 +0200 Subject: [PATCH 21/21] DeactivateAccountViewController: For password alert, remove empty cancel handler and use nil instead --- .../DeactivateAccount/DeactivateAccountViewController.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m index 480cad6f4..0ff6d3b68 100644 --- a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m @@ -321,9 +321,7 @@ static CGFloat const kTextFontSize = 15.0; [strongSelf deactivateAccountWithUserId:userId andPassword:password eraseAllMessages:strongSelf.forgetMessageButton.isEnabled]; } - } cancelHandler:^{ - - }]; + } cancelHandler:nil]; } @end