From 9690c5a924ddafd03fc7defcbff9ea1b2966d783 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 11:56:33 +0200 Subject: [PATCH 01/11] 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 80e6502aeb727da764518e000a0cc2ec2694cd05 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 14:36:09 +0200 Subject: [PATCH 02/11] 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 3891c3ce50648f238d9706544241facf18e1141c Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 14:42:49 +0200 Subject: [PATCH 03/11] 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 ee21d5490a6be6de42a0c24bc9f55875ea9ba7f6 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 15:31:27 +0200 Subject: [PATCH 04/11] 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 75a4e125e25249a924e6916c6aa4d8fcf4464991 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 16:02:51 +0200 Subject: [PATCH 05/11] 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 e0ef5262044aab1fc47d177dae40bea660c33a38 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 16:43:38 +0200 Subject: [PATCH 06/11] 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 fb24d4b9762694d7b07d95b877a5f97bb623c5e7 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 18:22:59 +0200 Subject: [PATCH 07/11] 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 062c60a738ff76ffe965d051e5c830e1a1fe9e3d Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 24 May 2018 18:40:19 +0200 Subject: [PATCH 08/11] 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 048739c7f4bb04669fd58faf33d941929ee05208 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 29 May 2018 14:50:34 +0200 Subject: [PATCH 09/11] 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 896529c8ee563e7070816195e852304fdee67c28 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 29 May 2018 15:20:39 +0200 Subject: [PATCH 10/11] 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 a577bb92a609dc67396391a2e1f433df5e5b0e26 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 30 May 2018 11:54:35 +0200 Subject: [PATCH 11/11] 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]; } }