Structure project almost by features. Start by organizing view controllers.

This commit is contained in:
SBiOSoftWhare
2018-07-05 18:51:58 +02:00
parent f26e845a13
commit fb58bbc651
119 changed files with 776 additions and 404 deletions
@@ -0,0 +1,48 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "WidgetViewController.h"
FOUNDATION_EXPORT NSString *const kIntegrationManagerMainScreen;
FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen;
/**
`IntegrationManagerViewController` displays the Modular integration manager webapp
into a webview.
It reuses the postMessage API pipe defined in `WidgetViewController`.
*/
@interface IntegrationManagerViewController : WidgetViewController
/**
Initialise with params for the Modular interface webapp.
@param mxSession the session to use.
@param roomId the room where to set up widgets.
@param screen the screen to display in the Modular interface webapp. Can be nil.
@param widgetId the id of the widget in case of widget configuration edition. Can be nil.
*/
- (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
@@ -0,0 +1,700 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "IntegrationManagerViewController.h"
#import "WidgetManager.h"
NSString *const kIntegrationManagerMainScreen = nil;
NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
@interface IntegrationManagerViewController ()
{
MXSession *mxSession;
NSString *roomId;
NSString *screen;
NSString *widgetId;
NSString *scalarToken;
MXHTTPOperation *operation;
}
@end
@implementation IntegrationManagerViewController
- (instancetype)initForMXSession:(MXSession *)theMXSession inRoom:(NSString *)theRoomId screen:(NSString *)theScreen widgetId:(NSString *)theWidgetId
{
self = [super init];
if (self)
{
mxSession = theMXSession;
roomId = theRoomId;
screen = theScreen;
widgetId = theWidgetId;
}
return self;
}
+ (NSString*)screenForWidget:(NSString*)widgetType
{
return [NSString stringWithFormat:@"type_%@", widgetType];
}
- (void)destroy
{
[super destroy];
[operation cancel];
operation = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.URL && !operation)
{
__weak __typeof__(self) weakSelf = self;
[self startActivityIndicator];
// Make sure we have a scalar token
operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) {
typeof(self) self = weakSelf;
if (self)
{
self->operation = nil;
scalarToken = theScalarToken;
// Launch the webview on the right modular webapp page
self.URL = [self interfaceUrl];
}
} failure:^(NSError *error) {
typeof(self) self = weakSelf;
if (self)
{
self->operation = nil;
[self stopActivityIndicator];
}
}];
}
}
#pragma mark - Private methods
/**
Build the URL to use in the Modular interface webapp.
*/
- (NSString *)interfaceUrl
{
NSMutableString *url;
if (scalarToken)
{
url = [NSMutableString stringWithFormat:@"%@?scalar_token=%@&room_id=%@",
[[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsUiUrl"],
[scalarToken stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
[roomId stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
];
if (screen)
{
[url appendString:@"&screen="];
[url appendString:[screen stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
if (widgetId)
{
[url appendString:@"&integ_id="];
[url appendString:[widgetId stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
}
return url;
}
#pragma mark - Modular postMessage API implementation
- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData
{
NSString *roomIdInEvent, *userId, *action;
MXJSONModelSetString(roomIdInEvent, requestData[@"room_id"]);
MXJSONModelSetString(userId, requestData[@"user_id"]);
MXJSONModelSetString(action, requestData[@"action"]);
if ([action isEqualToString:@"close_scalar"])
{
[self withdrawViewControllerAnimated:YES completion:nil];
return;
}
if (!roomIdInEvent)
{
// 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])
{
[self sendError:[NSString stringWithFormat:NSLocalizedStringFromTable(@"widget_integration_room_not_visible", @"Vector", nil), roomIdInEvent] toRequest:requestId];
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])
{
[self getJoinRules:requestId data:requestData];
return;
}
else if ([@"set_plumbing_state" isEqualToString:action])
{
[self setPlumbingState:requestId data:requestData];
return;
}
else if ([@"get_membership_count" isEqualToString:action])
{
[self getMembershipCount:requestId data:requestData];
return;
}
else if ([@"get_room_enc_state" isEqualToString:action])
{
[self getRoomEncState:requestId data:requestData];
return;
}
else if ([@"can_send_event" isEqualToString:action])
{
[self canSendEvent:requestId data:requestData];
return;
}
if (!userId)
{
[self sendLocalisedError:@"widget_integration_missing_user_id" toRequest:requestId];
return;
}
if ([@"membership_state" isEqualToString:action])
{
[self getMembershipState:userId request:requestId data:requestData];
}
else if ([@"invite" isEqualToString:action])
{
[self inviteUser:userId request:requestId data:requestData];
}
else if ([@"bot_options" isEqualToString:action])
{
[self getBotOptions:userId request:requestId data:requestData];
}
else if ([@"set_bot_options" isEqualToString:action])
{
[self setBotOptions:userId request:requestId data:requestData];
}
else if ([@"set_bot_power" isEqualToString:action])
{
[self setBotPower:userId request:requestId data:requestData];
}
else
{
NSLog(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, requestData);
}
}
#pragma mark - Private methods
- (MXRoom *)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData
{
MXRoom *room = [mxSession roomWithRoomId:roomId];
if (!room)
{
[self sendLocalisedError:@"widget_integration_room_not_recognised" toRequest:requestId];
}
return room;
}
- (void)inviteUser:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
MXRoomMember *member = [room.state memberWithUserId:userId];
if (member && member.membership == MXMembershipJoin)
{
[self sendNSObjectResponse:@{
@"success": @(YES)
}
toRequest:requestId];
}
else
{
__weak __typeof__(self) weakSelf = self;
[room inviteUser:userId success:^{
typeof(self) self = weakSelf;
if (self)
{
[self sendNSObjectResponse:@{
@"success": @(YES)
}
toRequest:requestId];
}
} failure:^(NSError *error) {
typeof(self) self = weakSelf;
if (self)
{
[self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toRequest:requestId];
}
}];
}
}
}
- (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] Received request to set widget");
NSString *widget_id, *widgetType, *widgetUrl;
NSString *widgetName; // optional
NSDictionary *widgetData ; // optional
BOOL userWidget = NO;
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)
{
[self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; // new Error("Missing required widget fields."));
return;
}
if (!widgetType)
{
[self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId];
return;
}
NSMutableDictionary *widgetEventContent = [NSMutableDictionary dictionary];
if (widgetUrl)
{
widgetEventContent[@"type"] = widgetType;
widgetEventContent[@"url"] = widgetUrl;
if (widgetName)
{
widgetEventContent[@"name"] = widgetName;
}
if (widgetData)
{
widgetEventContent[@"data"] = widgetData;
}
}
// else this is a deletion
__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)
{
userWidgets[widget_id] = @{
@"content": widgetEventContent,
@"sender": mxSession.myUser.userId,
@"state_key": widget_id,
@"type": kWidgetMatrixEventTypeString,
@"id": widget_id,
};
}
[mxSession setAccountData:userWidgets forType:kMXAccountDataTypeUserWidgets success:^{
typeof(self) self = weakSelf;
if (self)
{
[self sendNSObjectResponse:@{
@"success": @(YES)
}
toRequest:requestId];
}
} failure:^(NSError *error) {
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) {
typeof(self) self = weakSelf;
if (self)
{
[self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId];
}
}];
}
}
}
- (void)getWidgets:(NSString*)requestId data:(NSDictionary*)requestData
{
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
NSMutableArray<NSDictionary*> *widgetStateEvents = [NSMutableArray array];
if (room)
{
NSArray<Widget*> *widgets = [[WidgetManager sharedManager] widgetsInRoom:room];
for (Widget *widget in widgets)
{
[widgetStateEvents addObject:widget.widgetEvent.JSONDictionary];
}
}
// Add user widgets (not linked to a specific room)
for (Widget *widget in [[WidgetManager sharedManager] userWidgets:mxSession])
{
[widgetStateEvents addObject:widget.widgetEvent.JSONDictionary];
}
[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;
BOOL isState = NO;
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
if (room.state.membership != MXMembershipJoin)
{
[self sendLocalisedError:@"widget_integration_must_be_in_room" toRequest:requestId];
return;
}
MXJSONModelSetString(eventType, requestData[@"event_type"]);
MXJSONModelSetBoolean(isState, requestData[@"is_state"]);
MXRoomPowerLevels *powerLevels = room.state.powerLevels;
NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:mxSession.myUser.userId];
BOOL canSend = NO;
if (isState)
{
canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:eventType]);
}
else
{
canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsMessage:eventType]);
}
if (canSend)
{
[self sendBoolResponse:YES toRequest:requestId];
}
else
{
[self sendLocalisedError:@"widget_integration_no_permission_in_room" toRequest:requestId];
}
}
}
- (void)getMembershipState:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
MXRoomMember *member = [room.state memberWithUserId:userId];
[self sendNSObjectResponse:member.originalEvent.content toRequest:requestId];
}
}
- (void)getJoinRules:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
MXEvent *event = [room.state stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject;
[self sendNSObjectResponse:event.JSONDictionary toRequest:requestId];
}
}
- (void)setPlumbingState:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", requestData[@"status"], roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
NSString *status;
MXJSONModelSetString(status, requestData[@"status"]);
if (status)
{
__weak __typeof__(self) weakSelf = self;
[room sendStateEventOfType:kMXEventTypeStringRoomPlumbing
content:@{
@"status": status
}
stateKey:nil
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 sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId];
}
}];
}
else
{
NSLog(@"[IntegrationManagerVC] setPlumbingState. Error: Plumbing state status should be a string.");
}
}
}
- (void)getBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
NSString *stateKey = [NSString stringWithFormat:@"_%@", userId];
NSArray<MXEvent*> *stateEvents = [room.state stateEventsWithType:kMXEventTypeStringRoomBotOptions];
MXEvent *botOptionsEvent;
for (MXEvent *stateEvent in stateEvents)
{
if ([stateEvent.stateKey isEqualToString:stateKey])
{
if (!botOptionsEvent || stateEvent.ageLocalTs > botOptionsEvent.ageLocalTs)
{
botOptionsEvent = stateEvent;
}
}
}
[self sendNSObjectResponse:botOptionsEvent.JSONDictionary toRequest:requestId];
}
}
- (void)setBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
NSDictionary *content;
MXJSONModelSetDictionary(content, requestData[@"content"]);
if (content)
{
__weak __typeof__(self) weakSelf = self;
NSString *stateKey = [NSString stringWithFormat:@"_%@", userId];
[room sendStateEventOfType:kMXEventTypeStringRoomBotOptions
content:content
stateKey:stateKey
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 sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId];
}
}];
}
else
{
NSLog(@"[IntegrationManagerVC] setBotOptions. Error: options should be a dict.");
}
}
}
- (void)setBotPower:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", requestData[@"level"], userId, roomId);
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
NSInteger level = -1;
MXJSONModelSetInteger(level, requestData[@"level"]);
if (level >= 0)
{
__weak __typeof__(self) weakSelf = self;
[room setPowerLevelOfUserWithUserID:userId powerLevel:level success:^{
typeof(self) self = weakSelf;
if (self)
{
[self sendNSObjectResponse:@{
@"success": @(YES)
}
toRequest:requestId];
}
} failure:^(NSError *error) {
typeof(self) self = weakSelf;
if (self)
{
[self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId];
}
}];
}
else
{
NSLog(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer.");
[self sendLocalisedError:@"widget_integration_positive_power_level" toRequest:requestId];
}
}
}
- (void)getMembershipCount:(NSString*)requestId data:(NSDictionary*)requestData
{
MXRoom *room = [self roomCheckForRequest:requestId data:requestData];
if (room)
{
NSUInteger membershipCount = room.state.joinedMembers.count;
[self sendIntegerResponse:membershipCount toRequest:requestId];
}
}
@end
@@ -0,0 +1,56 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <UIKit/UIKit.h>
#import <MatrixSDK/MatrixSDK.h>
#import <MatrixKit/MatrixKit.h>
/**
`WidgetPickerViewController` displays the list of widgets within a room plus a
way to open the integration manager for this room.
TODO: The feature is still in dev. WidgetPickerViewController` is not yet a pure
UIViewController.
As there is no specified design, the list is displayed in a simple UIAlertController.
It would be nice if this picker could directly:
- Remove a widget
- Launch the integration manager to edit a widget
- Automatically updates on widgets change
*/
@interface WidgetPickerViewController : NSObject
/**
The UIAlertController instance which handles the dialog.
*/
@property (nonatomic, readonly) UIAlertController *alertController;
/**
Create the `WidgetPickerViewController` instance.
@param mxSession the session to use.
@param roomId the room where to list available widgets.
*/
- (instancetype)initForMXSession:(MXSession*)mxSession inRoom:(NSString*)roomId;
/**
Show the dialog in a given view controller.
@param mxkViewController the mxkViewController where to show the dialog.
*/
- (void)showInViewController:(MXKViewController*)mxkViewController;
@end
@@ -0,0 +1,112 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "WidgetPickerViewController.h"
#import "AppDelegate.h"
#import "WidgetManager.h"
#import "WidgetViewController.h"
#import "IntegrationManagerViewController.h"
@interface WidgetPickerViewController ()
{
MXSession *mxSession;
NSString *roomId;
}
@end
@implementation WidgetPickerViewController
- (instancetype)initForMXSession:(MXSession*)theMXSession inRoom:(NSString*)theRoomId
{
self = [super init];
if (self)
{
mxSession = theMXSession;
roomId = theRoomId;
_alertController = [UIAlertController alertControllerWithTitle:@"Matrix Apps"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
}
return self;
}
- (void)showInViewController:(MXKViewController *)mxkViewController
{
UIAlertAction *alertAction;
MXRoom *room = [mxSession roomWithRoomId:roomId];
NSArray<Widget*> *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi]
inRoom:room];
// List widgets
for (Widget *widget in widgets)
{
alertAction = [UIAlertAction actionWithTitle:widget.name ? widget.name : widget.type
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action)
{
// Hide back button title
mxkViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
// Display the widget
[widget widgetUrl:^(NSString * _Nonnull widgetUrl) {
WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget];
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession];
widgetVC.roomDataSource = [roomDataSourceManager roomDataSourceForRoom:roomId create:NO];
[mxkViewController.navigationController pushViewController:widgetVC animated:YES];
} failure:^(NSError * _Nonnull error) {
NSLog(@"[WidgetPickerVC] Cannot display widget %@", widget);
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}];
[_alertController addAction:alertAction];
}
// Link to the integration manager
alertAction = [UIAlertAction actionWithTitle:@"Manage integrations..."
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action)
{
IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self->mxSession
inRoom:self->roomId
screen:kIntegrationManagerMainScreen
widgetId:nil];
[mxkViewController presentViewController:modularVC animated:NO completion:nil];
}];
[_alertController addAction:alertAction];
// Cancel
alertAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:UIAlertActionStyleCancel
handler:nil];
[_alertController addAction:alertAction];
// And show it
[mxkViewController presentViewController:_alertController animated:YES completion:nil];
}
@end
@@ -0,0 +1,108 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <MatrixKit/MatrixKit.h>
#import <JitsiMeet/JitsiMeet.h>
#import "WidgetManager.h"
@protocol JitsiViewControllerDelegate;
/**
The `JitsiViewController` is a VC for specifically handling a jitsi widget using the
jitsi-meet iOS SDK instead of displaying it in a webview like other modular widgets.
https://github.com/jitsi/jitsi-meet/tree/master/ios
*/
@interface JitsiViewController : MXKViewController <JitsiMeetViewDelegate>
/**
Returns the `UINib` object initialized for a `JitsiViewController`.
@return The initialized `UINib` object or `nil` if there were errors during initialization
or the nib file could not be located.
*/
+ (UINib *)nib;
/**
Creates and returns a new `JitsiViewController` object.
@discussion This is the designated initializer for programmatic instantiation.
@return An initialized `JitsiViewController` object if successful, `nil` otherwise.
*/
+ (instancetype)jitsiViewController;
/**
Make jitsi-meet iOS SDK open the jitsi conference indicated by a jitsi widget.
@param widget the jitsi widget.
@param video to indicate voice or video call.
@param success A block object called when the operation succeeds.
@param failure A block object called when the operation fails.
*/
- (void)openWidget:(Widget*)widget withVideo:(BOOL)video
success:(void (^)())success
failure:(void (^)(NSError *error))failure;
/**
Hang up the jitsi conference call in progress.
*/
- (void)hangup;
/**
The jitsi widget displayed by this `JitsiViewController`.
*/
@property (nonatomic, readonly) Widget *widget;
/**
The delegate for the view controller.
*/
@property (nonatomic) id<JitsiViewControllerDelegate> delegate;
#pragma mark - Xib attributes
// The jitsi-meet SDK view
@property (weak, nonatomic) IBOutlet JitsiMeetView *jitsiMeetView;
@property (weak, nonatomic) IBOutlet UIButton *backToAppButton;
@end
/**
Delegate for `JitsiViewController` object
*/
@protocol JitsiViewControllerDelegate <NSObject>
/**
Tells the delegate to dismiss the jitsi view controller.
@param jitsiViewController the jitsi view controller.
@param completion the block to execute at the end of the operation.
*/
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController dismissViewJitsiController:(void (^)())completion;
/**
Tells the delegate to put the jitsi view controller in background.
@param jitsiViewController the jitsi view controller.
@param completion the block to execute at the end of the operation.
*/
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController goBackToApp:(void (^)())completion;
@end
@@ -0,0 +1,177 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "JitsiViewController.h"
static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
@interface JitsiViewController ()
{
NSString *jitsiUrl;
BOOL video;
}
@end
@implementation JitsiViewController
#pragma mark - Class methods
+ (UINib *)nib
{
return [UINib nibWithNibName:NSStringFromClass(self.class)
bundle:[NSBundle bundleForClass:self.class]];
}
+ (instancetype)jitsiViewController
{
JitsiViewController *jitsiViewController = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
bundle:[NSBundle bundleForClass:self.class]];
return jitsiViewController;
}
- (void)openWidget:(Widget*)widget withVideo:(BOOL)aVideo
success:(void (^)())success
failure:(void (^)(NSError *error))failure
{
video = aVideo;
_widget = widget;
[_widget widgetUrl:^(NSString * _Nonnull widgetUrl) {
// Extract the jitsi conference id from the widget url
NSString *confId;
NSURL *url = [NSURL URLWithString:widgetUrl];
if (url)
{
NSURLComponents *components = [[NSURLComponents new] initWithURL:url resolvingAgainstBaseURL:NO];
NSArray *queryItems = [components queryItems];
for (NSURLQueryItem *item in queryItems)
{
if ([item.name isEqualToString:@"confId"])
{
confId = item.value;
break;
}
}
// And build from it the url to use in jitsi-meet sdk
if (confId)
{
jitsiUrl = [NSString stringWithFormat:@"%@%@", kJitsiServerUrl, confId];
}
}
if (jitsiUrl)
{
if (success)
{
success();
}
}
else
{
NSLog(@"[JitsiVC] Failed to load widget: %@. Widget event: %@", widget, widget.widgetEvent);
if (failure)
{
failure(nil);
}
}
} failure:^(NSError * _Nonnull error) {
NSLog(@"[JitsiVC] Failed to load widget 2: %@. Widget event: %@", widget, widget.widgetEvent);
if (failure)
{
failure(nil);
}
}];
}
- (void)hangup
{
jitsiUrl = nil;
// It would have been nicer to ask JitsiMeetView but there is no api.
// Dismissing the view controller and releasing it does the job for the moment
if (_delegate)
{
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.jitsiMeetView.delegate = self;
// Pass the URL to jitsi-meet sdk
[self.jitsiMeetView loadURLObject: @{
@"url": jitsiUrl,
@"configOverwrite": @{
@"startWithVideoMuted": @(!video)
}
}];
// TODO: Set up user info but it is not yet available in the jitsi-meet iOS SDK
// See https://github.com/jitsi/jitsi-meet/issues/1880
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Actions
- (IBAction)onBackToAppButtonPressed:(id)sender
{
if (_delegate)
{
[_delegate jitsiViewController:self goBackToApp:nil];
}
}
#pragma mark - JitsiMeetViewDelegate
- (void)conferenceFailed:(NSDictionary *)data
{
NSLog(@"[JitsiViewController] conferenceFailed - data: %@", data);
}
- (void)conferenceLeft:(NSDictionary *)data
{
dispatch_async(dispatch_get_main_queue(), ^{
// The conference is over. Let the delegate close this view controller.
if (_delegate)
{
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
}
else
{
// Do it ourself
[self dismissViewControllerAnimated:YES completion:nil];
}
});
}
@end
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JitsiViewController">
<connections>
<outlet property="backToAppButton" destination="8tr-Cb-ue8" id="aUj-co-7JA"/>
<outlet property="jitsiMeetView" destination="7hL-Cs-mak" id="7kR-Te-Klw"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7hL-Cs-mak" customClass="JitsiMeetView">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8tr-Cb-ue8">
<rect key="frame" x="10" y="5" width="44" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="CallVCBackToAppButton"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="3id-6Q-PUF"/>
<constraint firstAttribute="width" constant="44" id="JGj-Jz-SbU"/>
</constraints>
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
<color key="tintColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<inset key="titleEdgeInsets" minX="-69" minY="61" maxX="0.0" maxY="0.0"/>
<state key="normal" image="back_icon.png">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onBackToAppButtonPressed:" destination="-1" eventType="touchUpInside" id="2wo-nB-Rwd"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="7hL-Cs-mak" secondAttribute="trailing" id="8eH-2r-pjD"/>
<constraint firstAttribute="bottom" secondItem="7hL-Cs-mak" secondAttribute="bottom" id="BAo-6X-ovC"/>
<constraint firstItem="8tr-Cb-ue8" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="5" id="FPS-wy-gK6"/>
<constraint firstItem="8tr-Cb-ue8" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="10" id="Xca-R4-1cu"/>
<constraint firstItem="7hL-Cs-mak" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="s46-Fx-tT8"/>
<constraint firstItem="7hL-Cs-mak" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="x3v-Xl-cKi"/>
</constraints>
</view>
</objects>
<resources>
<image name="back_icon.png" width="12" height="23"/>
</resources>
</document>
@@ -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
@@ -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:self.widget.widgetId];
[self presentViewController:modularVC animated:NO completion:nil];
}
@end
@@ -0,0 +1,110 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "WebViewViewController.h"
#import "WidgetManager.h"
#import "MatrixKit/MatrixKit.h"
/**
`WidgetViewController` displays widget within a webview.
It also exposes a generic pipe, the postMessage API, to communicate with the
content within the webview, ie the widget (matrix app).
*/
@interface WidgetViewController : WebViewViewController
/**
The displayed widget.
*/
@property (nonatomic, readonly) Widget *widget;
/**
The room data source.
Required if the widget needs to post messages.
*/
@property (nonatomic) MXKRoomDataSource *roomDataSource;
/**
Init 'WidgetViewController' instance with a widget.
@param widgetUrl the formatted widget url.
@param widget the widget to open.
*/
- (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)widget;
/**
Display an alert over this controller.
@param error the error to display.
*/
- (void)showErrorAsAlert:(NSError*)error;
#pragma mark - postMessage API
/**
Callback called when the widget make a postMessage API request.
This method can be overidden to implement a specific API between the matrix client
and widget.
@param requestId the id of the widget request.
@param requestData the request data.
*/
- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData;
/**
Send a boolean response to a request from the widget.
@param response the response to send.
@param requestId the id of the widget request.
*/
- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId;
/**
Send an integer response to a request from the widget.
@param response the response to send.
@param requestId the id of the widget request.
*/
- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId;
/**
Send a serialiable object response to a request the widget.
@param response the response to send.
@param requestId the id of the widget request.
*/
- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId;
/**
Send a message error to a request from the widget.
@param message the error message.
@param requestId the id of the widget request.
*/
- (void)sendError:(NSString*)message toRequest:(NSString*)requestId;
/**
Send a localised message error to a request from the widget.
@param errorKey the string id of the message error.
@param requestId the id of the widget request.
*/
- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId;
@end
@@ -0,0 +1,318 @@
/*
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.
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"
#import "AppDelegate.h"
#import "IntegrationManagerViewController.h"
NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);";
@interface WidgetViewController ()
@end
@implementation WidgetViewController
@synthesize widget;
- (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)theWidget
{
self = [super initWithURL:widgetUrl];
if (self)
{
widget = theWidget;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
webView.scrollView.bounces = NO;
// Disable opacity so that the webview background uses the current interface theme
webView.opaque = NO;
if (widget)
{
self.navigationItem.title = widget.name ? widget.name : widget.type;
}
}
- (void)showErrorAsAlert:(NSError*)error
{
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
if (!title)
{
if (msg)
{
title = msg;
msg = nil;
}
else
{
title = [NSBundle mxk_localizedStringForKey:@"error"];
}
}
__weak __typeof__(self) weakSelf = self;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
typeof(self) self = weakSelf;
if (self)
{
// Leave this widget VC
[self withdrawViewControllerAnimated:YES completion:nil];
}
}]];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - WKNavigationDelegate
- (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 evaluateJavaScript:js completionHandler:nil];
[self stopActivityIndicator];
// Check connectivity
if ([AppDelegate theDelegate].isOffline)
{
// The web page may be in the cache, so its loading will be successful
// but we cannot go further, it often leads to a blank screen.
// So, display an error so that the user can escape.
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorNotConnectedToInternet
userInfo:@{
NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"network_offline_prompt", @"Vector", nil)
}];
[self showErrorAsAlert:error];
}
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
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 the scheme of the JS<->Native bridge
NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByRemovingPercentEncoding];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers
error:&error];
if (!error)
{
// Retrieve the js event payload data
NSDictionary *eventData;
MXJSONModelSetDictionary(eventData, parameters[@"event.data"]);
NSString *requestId;
MXJSONModelSetString(requestId, eventData[@"_id"]);
if (requestId)
{
[self onPostMessageRequest:requestId data:eventData];
}
else
{
NSLog(@"[WidgetVC] shouldStartLoadWithRequest: ERROR: Missing request id in postMessage API %@", parameters);
}
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
if (navigationAction.navigationType == WKNavigationTypeLinkActivated)
{
// Open links outside the app
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
// Filter out the users's scalar token
NSString *errorDescription = error.description;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*"
options:NSRegularExpressionCaseInsensitive error:nil];
errorDescription = [regex stringByReplacingMatchesInString:errorDescription
options:0
range:NSMakeRange(0, errorDescription.length)
withTemplate:@"scalar_token=..."];
NSLog(@"[WidgetVC] didFailLoadWithError: %@", errorDescription);
[self stopActivityIndicator];
[self showErrorAsAlert:error];
}
#pragma mark - postMessage API
- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData
{
NSString *action;
MXJSONModelSetString(action, requestData[@"action"]);
if ([@"m.sticker" isEqualToString:action])
{
// Extract the sticker event content and send it as is
// The key should be "data" according to https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
// TODO: Fix it once spec is finalised
NSDictionary *widgetData;
NSDictionary *stickerContent;
MXJSONModelSetDictionary(widgetData, requestData[@"widgetData"]);
if (widgetData)
{
MXJSONModelSetDictionary(stickerContent, widgetData[@"content"]);
}
if (stickerContent)
{
// Let the data source manage the sending cycle
[_roomDataSource sendEventOfType:kMXEventTypeStringSticker content:stickerContent success:nil failure:nil];
}
else
{
NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Invalid content for m.sticker: %@", requestData);
}
// 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
{
// Convert BOOL to "true" or "false"
NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI,
requestId,
response ? @"true" : @"false"];
[webView evaluateJavaScript:js completionHandler:nil];
}
- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId
{
NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI,
requestId,
@(response)];
[webView evaluateJavaScript:js completionHandler:nil];
}
- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId
{
NSString *jsString;
if (response)
{
// Convert response into a JS object through a JSON string
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response
options:0
error:0];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
jsString = [NSString stringWithFormat:@"JSON.parse('%@')", jsonString];
}
else
{
jsString = @"null";
}
NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI,
requestId,
jsString];
[webView evaluateJavaScript:js completionHandler:nil];
}
- (void)sendError:(NSString*)message toRequest:(NSString*)requestId
{
NSLog(@"[WidgetVC] sendError: Action %@ failed with message: %@", requestId, message);
// TODO: JS has an additional optional parameter: nestedError
[self sendNSObjectResponse:@{
@"error": @{
@"message": message
}
}
toRequest:requestId];
}
- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId
{
[self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toRequest:requestId];
}
@end