From 307586fa0c20fc4c2f884c7cd491662c8df7333e Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 7 Sep 2017 11:32:40 +0200 Subject: [PATCH 01/30] Modular integrations UI: Show the Modular integrations webapp into a webview --- Riot/Utils/Widgets/WidgetManager.h | 30 ++++ Riot/Utils/Widgets/WidgetManager.m | 102 +++++++++++++ .../Widgets/ModularWebAppViewController.h | 37 +++++ .../Widgets/ModularWebAppViewController.m | 134 ++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 Riot/ViewController/Widgets/ModularWebAppViewController.h create mode 100644 Riot/ViewController/Widgets/ModularWebAppViewController.m diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index 4a0833e02..c4791c62a 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -139,4 +139,34 @@ WidgetManagerErrorCode; - (void)addMatrixSession:(MXSession*)mxSession; - (void)removeMatrixSession:(MXSession*)mxSession; +// TODO +//- (void)deleteMatrixSession; + +#pragma mark - Modular interface + +/** + Make sure there is a scalar token for the given Matrix session. + + If no token was gotten and stored before, the operation will make http requests + to get one. + + @param mxSession the session to check. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (MXHTTPOperation *)getScalarTokenForMXSession:(MXSession*)mxSession + success:(void (^)(NSString *scalarToken))success + failure:(void (^)(NSError *error))failure; + +/** + The current scalar token for the given Matrix session. + + It may be nil if `getScalarTokenForMXSession` was never called before. + + @param mxSession the session to check. + @return the current scalar token . + */ +- (NSString *)scalarTokenForMXSession:(MXSession*)mxSession; + @end diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 1dbf43c80..6abd6148f 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -16,6 +16,8 @@ #import "WidgetManager.h" +#import "MXKAppSettings.h" + #pragma mark - Contants NSString *const kWidgetEventTypeString = @"im.vector.modular.widgets"; @@ -40,6 +42,9 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // MXSession kind of hash -> (Widget id -> `createWidget:` failure block). NSMutableDictionary*> *failureBlockForWidgetCreation; + + // User id -> scalar token + NSMutableDictionary *scalarTokens; } @end @@ -66,6 +71,13 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; widgetEventListener = [NSMutableDictionary dictionary]; successBlockForWidgetCreation = [NSMutableDictionary dictionary]; failureBlockForWidgetCreation = [NSMutableDictionary dictionary]; + + [self load]; + + if (!scalarTokens) + { + scalarTokens = [NSMutableDictionary dictionary]; + } } return self; } @@ -327,4 +339,94 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; [failureBlockForWidgetCreation removeObjectForKey:hash]; } +#pragma mark - Modular interface + +- (NSString *)scalarTokenForMXSession:(MXSession *)mxSession +{ + return scalarTokens[mxSession.myUser.userId]; +} + +- (MXHTTPOperation *)getScalarTokenForMXSession:(MXSession*)mxSession + success:(void (^)(NSString *scalarToken))success + failure:(void (^)(NSError *error))failure; +{ + MXHTTPOperation *operation; + + __block NSString *scalarToken = [self scalarTokenForMXSession:mxSession]; + if (scalarToken) + { + success(scalarToken); + } + else + { + __weak __typeof__(self) weakSelf = self; + + operation = [mxSession.matrixRestClient openIdToken:^(MXOpenIdToken *tokenObject) { + + typeof(self) self = weakSelf; + + if (self) + { + // Exchange the token for a scalar token + NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]; + + MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:modularRestUrl andOnUnrecognizedCertificateBlock:nil]; + + MXHTTPOperation *operation2 = [httpClient requestWithMethod:@"POST" + path:@"register" + parameters:tokenObject.JSONDictionary + success:^(NSDictionary *JSONResponse) { + + MXJSONModelSetString(scalarToken, JSONResponse[@"scalar_token"]) + self->scalarTokens[mxSession.myUser.userId] = scalarToken; + + [self save]; + + if (success) + { + success(scalarToken); + } + + } failure:^(NSError *error) { + NSLog(@"[WidgetManager] getScalarTokenForMXSession. Error in modular/register request"); + + if (failure) + { + failure(error); + } + }]; + + [operation mutateTo:operation2]; + + } + + } failure:^(NSError *error) { + NSLog(@"[WidgetManager] getScalarTokenForMXSession. Error in openIdToken request"); + + if (failure) + { + failure(error); + } + }]; + } + + return operation; +} + +#pragma mark - Private methods + +- (void)load +{ + NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; + scalarTokens = [userDefaults objectForKey:@"scalarTokens"]; +} + +- (void)save +{ + NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; + + [userDefaults setObject:scalarTokens forKey:@"scalarTokens"]; + [userDefaults synchronize]; +} + @end diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.h b/Riot/ViewController/Widgets/ModularWebAppViewController.h new file mode 100644 index 000000000..c288953fa --- /dev/null +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.h @@ -0,0 +1,37 @@ +/* + 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 + +/** + `ModularWebAppViewController` displays the Modular integration manager webapp + into a webview. + */ +@interface ModularWebAppViewController : WebViewViewController + +/** + 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; + +@end diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.m b/Riot/ViewController/Widgets/ModularWebAppViewController.m new file mode 100644 index 000000000..a1a4b9136 --- /dev/null +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.m @@ -0,0 +1,134 @@ +/* + 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 "ModularWebAppViewController.h" + +#import "WidgetManager.h" + +@interface ModularWebAppViewController () +{ + MXSession *mxSession; + NSString *roomId; + NSString *screen; + NSString *widgetId; + NSString *scalarToken; + + MXHTTPOperation *operation; +} + +@end + +@implementation ModularWebAppViewController + +- (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; +} + +- (void)destroy +{ + [super destroy]; + + [operation cancel]; + operation = nil; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + webView.scalesPageToFit = NO; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + if (!self.URL && !operation) + { + __weak __typeof__(self) weakSelf = self; + + [self startActivityIndicator]; + + operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) { + + typeof(self) self = weakSelf; + + if (self) + { + self->operation = nil; + [self stopActivityIndicator]; + + scalarToken = theScalarToken; + + self.URL = [self interfaceUrl]; + } + + } failure:^(NSError *error) { + + typeof(self) self = weakSelf; + + if (self) + { + self->operation = nil; + [self stopActivityIndicator]; + } + }]; + } +} + +#pragma mark - Private methods + +/** + Get 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; +} + +@end From 22c69dbe208c520114b9189f1e3f32101b4cdf46 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 8 Sep 2017 09:18:45 +0200 Subject: [PATCH 02/30] Modular integrations UI: Start bridging the modular js postMessage interface with objc --- .../Widgets/ModularWebAppViewController.h | 2 +- .../Widgets/ModularWebAppViewController.m | 204 +++++++++++++++++- 2 files changed, 204 insertions(+), 2 deletions(-) diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.h b/Riot/ViewController/Widgets/ModularWebAppViewController.h index c288953fa..1270611a2 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.h +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.h @@ -22,7 +22,7 @@ `ModularWebAppViewController` displays the Modular integration manager webapp into a webview. */ -@interface ModularWebAppViewController : WebViewViewController +@interface ModularWebAppViewController : WebViewViewController /** Initialise with params for the Modular interface webapp. diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.m b/Riot/ViewController/Widgets/ModularWebAppViewController.m index a1a4b9136..378af89a0 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.m +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.m @@ -18,6 +18,30 @@ #import "WidgetManager.h" +// Generic method to make a bridge between JS and the UIWebView +NSString *kJavascriptSendObjectMessage = @" \ + window.riotIOS = {}; \ + window.riotIOS.sendObjectMessage = function(parameters) { \ + var iframe = document.createElement('iframe'); \ + iframe.setAttribute('src', 'js:' + JSON.stringify(parameters)); \ + \ + document.documentElement.appendChild(iframe); \ + iframe.parentNode.removeChild(iframe); \ + iframe = null; \ + }; \ +"; + +// The function to listen to the Modular webapp messages +NSString *kJavascriptListenToModularMessages = @" \ + window.riotIOS.onMessage = function(event) { \ + sendObjectMessage({ \ + 'event.data': event.data, \ + }); \ + }; \ + window.addEventListener('message', riotIOS.onMessage, false); \ +"; + + @interface ModularWebAppViewController () { MXSession *mxSession; @@ -59,6 +83,8 @@ [super viewDidLoad]; webView.scalesPageToFit = NO; + + webView.delegate = self; } - (void)viewWillAppear:(BOOL)animated @@ -71,6 +97,7 @@ [self startActivityIndicator]; + // Make sure we a scalar token operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) { typeof(self) self = weakSelf; @@ -82,6 +109,7 @@ scalarToken = theScalarToken; + // Launch the webview on the right modular webapp page self.URL = [self interfaceUrl]; } @@ -101,7 +129,7 @@ #pragma mark - Private methods /** - Get the URL to use in the Modular interface webapp. + Build the URL to use in the Modular interface webapp. */ - (NSString *)interfaceUrl { @@ -131,4 +159,178 @@ return url; } +#pragma mark - UIWebViewDelegate + +-(void)webViewDidFinishLoad:(UIWebView *)theWebView +{ + // Setup Objs-JS bridging methods + [webView stringByEvaluatingJavaScriptFromString:kJavascriptSendObjectMessage]; + [webView stringByEvaluatingJavaScriptFromString:kJavascriptListenToModularMessages]; +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSString *urlString = [[request URL] absoluteString]; + + if ([urlString hasPrefix:@"js:"]) + { + // Listen only to scheme of the JS-UIWebView bridge + NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *error; + NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers + error:&error]; + + NSLog(@"++++ parameters: %@", parameters); + + if (!error) + { + NSDictionary *eventData; + MXJSONModelSetDictionary(eventData, parameters[@"event.data"]); + + NSString *roomIdInEvent, *userId, *action; + + MXJSONModelSetString(roomIdInEvent, eventData[@"room_id"]); + MXJSONModelSetString(userId, eventData[@"user_id"]); + MXJSONModelSetString(action, eventData[@"action"]); + + if (!roomIdInEvent) + { + //sendError(event, _t('Missing room_id in request')); + return NO; + } + + if (![roomIdInEvent isEqualToString:roomId]) + { + //sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId})); + return NO; + } + + + // These APIs don't require userId + if ([@"join_rules_state" isEqualToString:action]) + { + //getJoinRules(event, roomId); + } + else if ([@"set_plumbing_state" isEqualToString:action]) + { + //setPlumbingState(event, roomId, event.data.status); + } + else if ([@"get_membership_count" isEqualToString:action]) + { + //getMembershipCount(event, roomId); + } + else if ([@"set_widget" isEqualToString:action]) + { + //setWidget(event, roomId); + } + else if ([@"get_widgets" isEqualToString:action]) + { + //getWidgets(event, roomId); + } + else if ([@"can_send_event" isEqualToString:action]) + { + [self canSendEvent:eventData]; + //canSendEvent(event, roomId); + } + + + if (!userId) + { + //sendError(event, _t('Missing user_id in request')); + return NO; + } + + if ([@"membership_state" isEqualToString:action]) + { + //getMembershipState(event, roomId, userId); + } + else if ([@"invite" isEqualToString:action]) + { + //inviteUser(event, roomId, userId); + } + else if ([@"bot_options" isEqualToString:action]) + { + //botOptions(event, roomId, userId); + } + else if ([@"set_bot_options" isEqualToString:action]) + { + //setBotOptions(event, roomId, userId); + } + else if ([@"set_bot_power" isEqualToString:action]) + { + //setBotPower(event, roomId, userId, event.data.level); + } + else + { + NSLog(@"[ModularWebAppViewController] Unhandled postMessage event with action %@: %@", action, parameters); + } + } + return NO; + } + return YES; +} + +- (void)sendResponse:(NSString*)response toEvent:(NSDictionary*)eventData +{ + +} + +#pragma mark - Modular postMessage API + +- (MXRoom *)roomCheckWithEvent:(NSDictionary*)eventData +{ + MXRoom *room = [mxSession roomWithRoomId:roomId]; + if (!room) + { + //sendError(event, _t('This room is not recognised.')); + } + + return room; +} + +- (void)canSendEvent:(NSDictionary*)eventData +{ + NSString *eventType; + BOOL isState = NO; + + MXRoom *room = [self roomCheckWithEvent:eventData]; + + if (room) + { + if (room.state.membership != MXMembershipJoin) + { + // sendError(event, _t('You are not in this room.')); + return; + } + + MXJSONModelSetString(eventType, eventData[@"event_type"]); + MXJSONModelSetBoolean(isState, eventData[@"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 sendResponse:@"true" toEvent:eventData]; + } + else + { + //sendError(event, _t('You do not have permission to do that in this room.')); + } + } +} + @end From f22c721a686fd0c6ee47fffbf25dc95ab3b314be Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 8 Sep 2017 13:51:06 +0200 Subject: [PATCH 03/30] Modular integrations UI: Move JS code to js resource file. ObjC -> Modular JS starts to work --- Riot/Assets/js/ModularWebApp.js | 63 +++++++++++++++++++ .../Widgets/ModularWebAppViewController.m | 38 ++++------- 2 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 Riot/Assets/js/ModularWebApp.js diff --git a/Riot/Assets/js/ModularWebApp.js b/Riot/Assets/js/ModularWebApp.js new file mode 100644 index 000000000..806cb3db7 --- /dev/null +++ b/Riot/Assets/js/ModularWebApp.js @@ -0,0 +1,63 @@ +/* + 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. + */ + +window.riotIOS = {}; + +// Generic JS -> ObjC bridge +window.riotIOS.sendObjectMessageToObjC = function(parameters) { + var iframe = document.createElement('iframe'); + iframe.setAttribute('src', 'js:' + JSON.stringify(parameters)); + + document.documentElement.appendChild(iframe); + iframe.parentNode.removeChild(iframe); + iframe = null; +}; + +window.riotIOS.events = {}; + +// Listen to messages posted by Modular +window.riotIOS.onMessage = function(event) { + + // Do not SPAM ObjC with event already managed + if (riotIOS.events[event.data._id]) { + return; + } + + // Keep this event for future usage + riotIOS.events[event.data._id] = event; + + riotIOS.sendObjectMessageToObjC({ + 'event.data': event.data, + }); +}; +window.addEventListener('message', riotIOS.onMessage, false); + + +// ObjC -> Modular JS bridge +window.riotIOS.sendResponse = function(eventId, res) { + + // Retrieve the correspong JS event + var event = riotIOS.events[eventId]; + + console.log(eventId + ": " + event); + + var data = JSON.parse(JSON.stringify(event.data)); + data.response = res; + event.source.postMessage(data, event.origin); + + // Mark this event as handled + riotIOS.events[eventId] = true; +} diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.m b/Riot/ViewController/Widgets/ModularWebAppViewController.m index 378af89a0..ba112f8bf 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.m +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.m @@ -18,28 +18,7 @@ #import "WidgetManager.h" -// Generic method to make a bridge between JS and the UIWebView -NSString *kJavascriptSendObjectMessage = @" \ - window.riotIOS = {}; \ - window.riotIOS.sendObjectMessage = function(parameters) { \ - var iframe = document.createElement('iframe'); \ - iframe.setAttribute('src', 'js:' + JSON.stringify(parameters)); \ - \ - document.documentElement.appendChild(iframe); \ - iframe.parentNode.removeChild(iframe); \ - iframe = null; \ - }; \ -"; - -// The function to listen to the Modular webapp messages -NSString *kJavascriptListenToModularMessages = @" \ - window.riotIOS.onMessage = function(event) { \ - sendObjectMessage({ \ - 'event.data': event.data, \ - }); \ - }; \ - window.addEventListener('message', riotIOS.onMessage, false); \ -"; +NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; @interface ModularWebAppViewController () @@ -163,9 +142,10 @@ NSString *kJavascriptListenToModularMessages = @" -(void)webViewDidFinishLoad:(UIWebView *)theWebView { - // Setup Objs-JS bridging methods - [webView stringByEvaluatingJavaScriptFromString:kJavascriptSendObjectMessage]; - [webView stringByEvaluatingJavaScriptFromString:kJavascriptListenToModularMessages]; + // Setup js code + NSString *path = [[NSBundle mainBundle] pathForResource:@"ModularWebApp" ofType:@"js"]; + NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [webView stringByEvaluatingJavaScriptFromString:js]; } - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType @@ -272,9 +252,13 @@ NSString *kJavascriptListenToModularMessages = @" return YES; } -- (void)sendResponse:(NSString*)response toEvent:(NSDictionary*)eventData +- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData { + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + eventData[@"_id"], + response ? @"true" : @"false"]; + [webView stringByEvaluatingJavaScriptFromString:js]; } #pragma mark - Modular postMessage API @@ -324,7 +308,7 @@ NSString *kJavascriptListenToModularMessages = @" if (canSend) { - [self sendResponse:@"true" toEvent:eventData]; + [self sendBoolResponse:YES toEvent:eventData]; } else { From 8116fdceb77e891061f4b08ada25b907994237fd Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 8 Sep 2017 16:27:14 +0200 Subject: [PATCH 04/30] Modular integrations UI: Plumbed all modular requests. But it does not work --- Riot/Assets/js/ModularWebApp.js | 6 +- .../Widgets/ModularWebAppViewController.m | 70 ++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/js/ModularWebApp.js b/Riot/Assets/js/ModularWebApp.js index 806cb3db7..5dc414677 100644 --- a/Riot/Assets/js/ModularWebApp.js +++ b/Riot/Assets/js/ModularWebApp.js @@ -36,6 +36,10 @@ window.riotIOS.onMessage = function(event) { return; } + if (!event.origin) { // stupid chrome + event.origin = event.originalEvent.origin; + } + // Keep this event for future usage riotIOS.events[event.data._id] = event; @@ -52,7 +56,7 @@ window.riotIOS.sendResponse = function(eventId, res) { // Retrieve the correspong JS event var event = riotIOS.events[eventId]; - console.log(eventId + ": " + event); + console.log("sendResponse to " + event.data.action + " for "+ eventId + ": " + JSON.stringify(res)); var data = JSON.parse(JSON.stringify(event.data)); data.response = res; diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.m b/Riot/ViewController/Widgets/ModularWebAppViewController.m index ba112f8bf..c9b12c480 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.m +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.m @@ -18,6 +18,8 @@ #import "WidgetManager.h" +#import + NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; @@ -142,6 +144,12 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; -(void)webViewDidFinishLoad:(UIWebView *)theWebView { + // Setup debug + JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; + ctx[@"console"][@"log"] = ^(JSValue * msg) { + NSLog(@"----------- JavaScript: %@", msg); + }; + // Setup js code NSString *path = [[NSBundle mainBundle] pathForResource:@"ModularWebApp" ofType:@"js"]; NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; @@ -207,7 +215,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; } else if ([@"get_widgets" isEqualToString:action]) { - //getWidgets(event, roomId); + [self getWidgets:eventData]; } else if ([@"can_send_event" isEqualToString:action]) { @@ -224,7 +232,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; if ([@"membership_state" isEqualToString:action]) { - //getMembershipState(event, roomId, userId); + [self getMembershipState:userId eventData:eventData]; } else if ([@"invite" isEqualToString:action]) { @@ -261,6 +269,34 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; [webView stringByEvaluatingJavaScriptFromString:js]; } +- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData +{ + NSString *jsString; + + if (response) + { + 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:kJavascriptSendResponseToModular, + eventData[@"_id"], + jsString]; + + + NSLog(@"sendNSObjectResponse:\n%@", js); + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + #pragma mark - Modular postMessage API - (MXRoom *)roomCheckWithEvent:(NSDictionary*)eventData @@ -274,6 +310,25 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; return room; } +- (void)getWidgets:(NSDictionary*)eventData +{ + MXRoom *room = [self roomCheckWithEvent:eventData]; + + if (room) + { + NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:room]; + + NSMutableArray *widgetStateEvents = [NSMutableArray arrayWithCapacity:widgets.count]; + + for (Widget *widget in widgets) + { + [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; + } + + [self sendNSObjectResponse:widgetStateEvents toEvent:eventData]; + } +} + - (void)canSendEvent:(NSDictionary*)eventData { NSString *eventType; @@ -317,4 +372,15 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; } } +- (void)getMembershipState:(NSString*)userId eventData:(NSDictionary*)eventData +{ + MXRoom *room = [self roomCheckWithEvent:eventData]; + + if (room) + { + MXRoomMember *member = [room.state memberWithUserId:userId]; + [self sendNSObjectResponse:member.originalEvent.JSONDictionary toEvent:eventData]; + } +} + @end From a9851a65116c8dbda09bea51effacfde63adeaa3 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Mon, 11 Sep 2017 11:01:47 +0200 Subject: [PATCH 05/30] Bug Fix: Dark theme - Improvements - padlock being unthemed --- Riot/Assets/Images/e2e_unencrypted.png | Bin 240 -> 286 bytes Riot/Assets/Images/e2e_unencrypted@2x.png | Bin 345 -> 441 bytes Riot/Assets/Images/e2e_unencrypted@3x.png | Bin 420 -> 565 bytes 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Riot/Assets/Images/e2e_unencrypted.png mode change 100755 => 100644 Riot/Assets/Images/e2e_unencrypted@2x.png mode change 100755 => 100644 Riot/Assets/Images/e2e_unencrypted@3x.png diff --git a/Riot/Assets/Images/e2e_unencrypted.png b/Riot/Assets/Images/e2e_unencrypted.png old mode 100755 new mode 100644 index c7f1a4085bf5fe19c7a5aff1a5d85f8a5628f14e..622d45a5568b9df235314427f6d2158f64d11b39 GIT binary patch delta 200 zcmV;(05|{e0iFVoUVq^U3kM_`!WfCJ0001r{pj0j+mMA1*-b-kHC-o!wKGi8!u=`f$69K0000 delta 154 zcmV;L0A>H40`LKlUVq>V1P}%|KwPT~0001RNklsDKcf{29| zu=f5(u-*$;*(~@|?SM8@41z(deO+F7MG|s)?6Iu#6=#%4q7DZHF4b-E#JEVp4OSR& z!vhm`C|wc`xMObIT;0%P{UHpLWHPse@edPT^Ao2M^RY-uTL1t607*qo IM6N<$g2og+P5=M^ diff --git a/Riot/Assets/Images/e2e_unencrypted@2x.png b/Riot/Assets/Images/e2e_unencrypted@2x.png old mode 100755 new mode 100644 index e6f83cfe2c944afc5b2fe7ecdea62debddee003f..896f0d101beaf713fc925f82317889e3c1398c61 GIT binary patch delta 356 zcmV-q0h|8W0=WZ_UVq^U3kM_zp&sRT0003wNkl61D{#0)L|eEFZv*svegHw%hGp zGuzL!UPZ(Mn3)3gK15_Mj^l&EK=1u5a3AuYz;>_KJ8d?bUjVIEE2`CM8^94T0vIr- zst-khx4?XmtEzf2z_$XN2YIKe3%P-vPN$xx=?mb18&zHUW4UW!5lEeLlP)hXNs>l@ z0bC9d`SG|UNnIMbfzG*!jO-2(>1MOJfo2vJ{n=&~iTD02XKqYH#=`?|zV1P})jg8WmP0002lNklPk0$(biG46m@e4vZ!APy2@GINlxpigJHZ*c zPv+lnjhk^`s{(e4^Q`GL9wS{JO{7TvjzLV6Xg89LDXwrA8+?y%?kF1S!aHCnO?Z)g zuNfXTc%Jcs!+4(HzAJ$B*o%6J`4`@Kc*L5%1USv=#Si?sXIh$2c2K@k?kP%G^DCu< z%U>?)vXtvMrMYGsE{2_`pL!hlTdnfjrt{Bgzy@r<25i83eE={RB~o49$SnW>002ov JPDHLkV1gHdc!U4| diff --git a/Riot/Assets/Images/e2e_unencrypted@3x.png b/Riot/Assets/Images/e2e_unencrypted@3x.png old mode 100755 new mode 100644 index 4366ed4fe1065f8baa3a0316eda759ec10150c60..8b489fb2d91529518bc7dd0e6356e6c2d955510c GIT binary patch delta 481 zcmV<70UrLO1GNN@UVq^U3kM?}iZt7B0005CNklAIc+fR>}ZIP~=P z8%4zRv0Ey*jC*34rr9Q3RHnA(8=`) XGPH(BRR3hA00000NkvXXu0mjfhS}!j delta 335 zcmdnWvV?g;T>S%X76EqU%Syou85kHM824mNSstl6?Z*E76+LjSF2+jcRmyBpSF zb?Y6w|0K%|LH6%V%tP+`vA$6Iy(Pa{PiSwbQfY1j>j%9DUdrrtTN)S(UaCaDtSh)6 zSFk+f^#uK@%*k2`p@&Lu2yS3IWnu8*NMsG4PV^Q1&|rIK;W<;=FWlG}_tVbQenrUH z#`KJU?^Bk$S1hqRe__G9lOAXLKAfnTx6o1e{QYHXx#p*znfW5eK{?@@`}=wSPrLA5 daX?2~BICKzOrNfK%*@OH1fH&bF6*2UngB>ynDGDr From 0176f68afc5ba8f0a1ba1b6bad7a5839b4b10fc7 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Mon, 11 Sep 2017 11:27:47 +0200 Subject: [PATCH 06/30] Dark theme - Improvements - some hairlines being too bright - fading behind dialog modals being white rather than dark - grey highlight bar in room list being too light (i think)? --- Riot/Model/Contact/ContactsDataSource.m | 2 +- Riot/Model/RoomList/RecentsDataSource.m | 4 ++-- Riot/Utils/RiotDesignValues.h | 2 ++ Riot/Utils/RiotDesignValues.m | 8 ++++++++ Riot/ViewController/BugReportViewController.m | 5 +++++ Riot/ViewController/BugReportViewController.xib | 3 ++- Riot/ViewController/ReadReceiptsViewController.m | 3 +++ Riot/ViewController/RoomParticipantsViewController.m | 2 +- Riot/ViewController/StartChatViewController.m | 2 +- Riot/Views/RoomInputToolbar/RoomInputToolbarView.m | 2 +- Riot/Views/RoomList/RecentTableViewCell.m | 2 +- 11 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Riot/Model/Contact/ContactsDataSource.m b/Riot/Model/Contact/ContactsDataSource.m index bf6b9c054..657878d01 100644 --- a/Riot/Model/Contact/ContactsDataSource.m +++ b/Riot/Model/Contact/ContactsDataSource.m @@ -746,7 +746,7 @@ attributes:@{NSForegroundColorAttributeName : kRiotPrimaryTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:15.0]}]; [mutableSectionTitle appendAttributedString:[[NSMutableAttributedString alloc] initWithString:roomCount - attributes:@{NSForegroundColorAttributeName : kRiotColorSilver, + attributes:@{NSForegroundColorAttributeName : kRiotAuxiliaryColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:15.0]}]]; sectionTitle = mutableSectionTitle; diff --git a/Riot/Model/RoomList/RecentsDataSource.m b/Riot/Model/RoomList/RecentsDataSource.m index 4fa6e1065..900cb3bad 100644 --- a/Riot/Model/RoomList/RecentsDataSource.m +++ b/Riot/Model/RoomList/RecentsDataSource.m @@ -398,7 +398,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou attributes:@{NSForegroundColorAttributeName : kRiotPrimaryTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:15.0]}]; [mutableSectionTitle appendAttributedString:[[NSMutableAttributedString alloc] initWithString:roomCount - attributes:@{NSForegroundColorAttributeName : kRiotColorSilver, + attributes:@{NSForegroundColorAttributeName : kRiotAuxiliaryColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:15.0]}]]; sectionTitle = mutableSectionTitle; @@ -463,7 +463,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou missedNotifAndUnreadBadgeBgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, bgViewWidth, 20)]; [missedNotifAndUnreadBadgeBgView.layer setCornerRadius:10]; - missedNotifAndUnreadBadgeBgView.backgroundColor = kRiotColorSilver; + missedNotifAndUnreadBadgeBgView.backgroundColor = kRiotAuxiliaryColor; [missedNotifAndUnreadBadgeBgView addSubview:missedNotifAndUnreadBadgeLabel]; missedNotifAndUnreadBadgeLabel.center = missedNotifAndUnreadBadgeBgView.center; diff --git a/Riot/Utils/RiotDesignValues.h b/Riot/Utils/RiotDesignValues.h index d379ebbab..5ed316b18 100644 --- a/Riot/Utils/RiotDesignValues.h +++ b/Riot/Utils/RiotDesignValues.h @@ -39,6 +39,8 @@ extern UIColor *kRiotSecondaryTextColor; //subtitle, sending messages color. extern UIColor *kRiotPlaceholderTextColor; // nil is used to keep the default color. extern UIColor *kRiotTopicTextColor; extern UIColor *kRiotSelectedBgColor; // nil is used to keep the default color. +extern UIColor *kRiotAuxiliaryColor; // kRiotColorSilver by default. +extern UIColor *kRiotOverlayColor; // fading behind dialog modals. This color includes the transparency value. #pragma mark - Riot Colors extern UIColor *kRiotColorGreen; diff --git a/Riot/Utils/RiotDesignValues.m b/Riot/Utils/RiotDesignValues.m index 17db34b04..49a0b4dc9 100644 --- a/Riot/Utils/RiotDesignValues.m +++ b/Riot/Utils/RiotDesignValues.m @@ -26,6 +26,8 @@ UIColor *kRiotSecondaryTextColor; UIColor *kRiotPlaceholderTextColor; UIColor *kRiotTopicTextColor; UIColor *kRiotSelectedBgColor; +UIColor *kRiotAuxiliaryColor; +UIColor *kRiotOverlayColor; // Riot Colors UIColor *kRiotColorGreen; @@ -135,6 +137,9 @@ UIColor *kRiotDesignSearchBarTintColor = nil; kRiotDesignStatusBarStyle = UIStatusBarStyleLightContent; kRiotDesignSearchBarStyle = UIBarStyleBlack; kRiotDesignSearchBarTintColor = kRiotColorGreen; + + kRiotAuxiliaryColor = kRiotTextColorGray; + kRiotOverlayColor = [UIColor colorWithWhite:0.3 alpha:0.5]; } else { @@ -150,6 +155,9 @@ UIColor *kRiotDesignSearchBarTintColor = nil; kRiotDesignStatusBarStyle = UIStatusBarStyleDefault; kRiotDesignSearchBarStyle = UIBarStyleDefault; kRiotDesignSearchBarTintColor = nil; // Default tint color. + + kRiotAuxiliaryColor = kRiotColorSilver; + kRiotOverlayColor = [UIColor colorWithWhite:0.7 alpha:0.5]; } [[NSNotificationCenter defaultCenter] postNotificationName:kRiotDesignValuesDidChangeThemeNotification object:nil]; diff --git a/Riot/ViewController/BugReportViewController.m b/Riot/ViewController/BugReportViewController.m index b6ddec0e8..4d2ec36c0 100644 --- a/Riot/ViewController/BugReportViewController.m +++ b/Riot/ViewController/BugReportViewController.m @@ -34,6 +34,8 @@ @property (nonatomic) BOOL sendLogs; @property (nonatomic) BOOL sendScreenshot; +@property (weak, nonatomic) IBOutlet UIView *overlayView; + @end @implementation BugReportViewController @@ -138,6 +140,9 @@ self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.overlayView.backgroundColor = kRiotOverlayColor; + self.overlayView.alpha = 1.0; + self.containerView.backgroundColor = kRiotPrimaryBgColor; self.sendingContainer.backgroundColor = kRiotPrimaryBgColor; diff --git a/Riot/ViewController/BugReportViewController.xib b/Riot/ViewController/BugReportViewController.xib index 65445af46..aa9d51d2c 100644 --- a/Riot/ViewController/BugReportViewController.xib +++ b/Riot/ViewController/BugReportViewController.xib @@ -1,5 +1,5 @@ - + @@ -17,6 +17,7 @@ + diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index b1ae370eb..c88b419f2 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -97,6 +97,9 @@ self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.overlayView.backgroundColor = kRiotOverlayColor; + self.overlayView.alpha = 1.0; + self.titleLabel.textColor = kRiotPrimaryTextColor; self.containerView.backgroundColor = kRiotPrimaryBgColor; diff --git a/Riot/ViewController/RoomParticipantsViewController.m b/Riot/ViewController/RoomParticipantsViewController.m index 34d2594ea..b7d4c5651 100644 --- a/Riot/ViewController/RoomParticipantsViewController.m +++ b/Riot/ViewController/RoomParticipantsViewController.m @@ -161,7 +161,7 @@ [self refreshSearchBarItemsColor:_searchBarView]; - _searchBarHeaderBorder.backgroundColor = kRiotColorSilver; + _searchBarHeaderBorder.backgroundColor = kRiotAuxiliaryColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/StartChatViewController.m b/Riot/ViewController/StartChatViewController.m index 8944c37af..459f62ec0 100644 --- a/Riot/ViewController/StartChatViewController.m +++ b/Riot/ViewController/StartChatViewController.m @@ -148,7 +148,7 @@ [self refreshSearchBarItemsColor:_searchBarView]; - _searchBarHeaderBorder.backgroundColor = kRiotColorSilver; + _searchBarHeaderBorder.backgroundColor = kRiotAuxiliaryColor; // Check the table view style to select its bg color. self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m index 58cc588c0..bf178b2ca 100644 --- a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m +++ b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.m @@ -82,7 +82,7 @@ // Remove default toolbar background color self.backgroundColor = [UIColor clearColor]; - self.separatorView.backgroundColor = kRiotColorSilver; + self.separatorView.backgroundColor = kRiotAuxiliaryColor; // Custom the growingTextView display growingTextView.layer.cornerRadius = 0; diff --git a/Riot/Views/RoomList/RecentTableViewCell.m b/Riot/Views/RoomList/RecentTableViewCell.m index ac16519d2..8fbdda0cc 100644 --- a/Riot/Views/RoomList/RecentTableViewCell.m +++ b/Riot/Views/RoomList/RecentTableViewCell.m @@ -111,7 +111,7 @@ } else { - self.missedNotifAndUnreadIndicator.backgroundColor = kRiotColorSilver; + self.missedNotifAndUnreadIndicator.backgroundColor = kRiotAuxiliaryColor; } // Use bold font for the room title From 978e27da25ab8888bfb2af676817ca5fd8508bdc Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Mon, 11 Sep 2017 14:49:06 +0200 Subject: [PATCH 07/30] Dark theme - Improvements - spinner bg being white --- Riot/ViewController/AttachmentsViewController.m | 1 + Riot/ViewController/AuthenticationViewController.m | 1 + Riot/ViewController/BugReportViewController.m | 1 + Riot/ViewController/CallViewController.m | 1 + Riot/ViewController/ContactDetailsViewController.m | 1 + Riot/ViewController/ContactsTableViewController.m | 1 + Riot/ViewController/CountryPickerViewController.m | 1 + Riot/ViewController/DirectoryServerPickerViewController.m | 1 + Riot/ViewController/DirectoryViewController.m | 1 + Riot/ViewController/HomeFilesSearchViewController.m | 1 + Riot/ViewController/HomeMessagesSearchViewController.m | 1 + Riot/ViewController/LanguagePickerViewController.m | 1 + Riot/ViewController/MediaAlbumContentViewController.m | 1 + Riot/ViewController/MediaPickerViewController.m | 1 + Riot/ViewController/ReadReceiptsViewController.m | 1 + Riot/ViewController/RecentsViewController.m | 1 + Riot/ViewController/RoomFilesSearchViewController.m | 1 + Riot/ViewController/RoomFilesViewController.m | 1 + Riot/ViewController/RoomMemberDetailsViewController.m | 1 + Riot/ViewController/RoomMessagesSearchViewController.m | 1 + Riot/ViewController/RoomParticipantsViewController.m | 1 + Riot/ViewController/RoomSettingsViewController.m | 1 + Riot/ViewController/RoomViewController.m | 1 + Riot/ViewController/SegmentedViewController.m | 1 + Riot/ViewController/SettingsViewController.m | 1 + Riot/ViewController/UsersDevicesViewController.m | 1 + Riot/ViewController/WebViewViewController.m | 1 + 27 files changed, 27 insertions(+) diff --git a/Riot/ViewController/AttachmentsViewController.m b/Riot/ViewController/AttachmentsViewController.m index dae75ec77..934945eb8 100644 --- a/Riot/ViewController/AttachmentsViewController.m +++ b/Riot/ViewController/AttachmentsViewController.m @@ -61,6 +61,7 @@ self.view.backgroundColor = kRiotPrimaryBgColor; self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; } - (void)viewWillAppear:(BOOL)animated diff --git a/Riot/ViewController/AuthenticationViewController.m b/Riot/ViewController/AuthenticationViewController.m index bce884a06..12ea39104 100644 --- a/Riot/ViewController/AuthenticationViewController.m +++ b/Riot/ViewController/AuthenticationViewController.m @@ -176,6 +176,7 @@ self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; [self.authInputsView customizeViewRendering]; diff --git a/Riot/ViewController/BugReportViewController.m b/Riot/ViewController/BugReportViewController.m index 4d2ec36c0..cc0a332d6 100644 --- a/Riot/ViewController/BugReportViewController.m +++ b/Riot/ViewController/BugReportViewController.m @@ -139,6 +139,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.overlayView.backgroundColor = kRiotOverlayColor; self.overlayView.alpha = 1.0; diff --git a/Riot/ViewController/CallViewController.m b/Riot/ViewController/CallViewController.m index 558039dfc..431e0da4c 100644 --- a/Riot/ViewController/CallViewController.m +++ b/Riot/ViewController/CallViewController.m @@ -101,6 +101,7 @@ self.view.backgroundColor = kRiotPrimaryBgColor; self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.callerNameLabel.textColor = kRiotPrimaryTextColor; self.callStatusLabel.textColor = kRiotTopicTextColor; diff --git a/Riot/ViewController/ContactDetailsViewController.m b/Riot/ViewController/ContactDetailsViewController.m index f82834526..fa97fbe79 100644 --- a/Riot/ViewController/ContactDetailsViewController.m +++ b/Riot/ViewController/ContactDetailsViewController.m @@ -224,6 +224,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.headerView.backgroundColor = kRiotSecondaryBgColor; self.contactNameLabel.textColor = kRiotPrimaryTextColor; diff --git a/Riot/ViewController/ContactsTableViewController.m b/Riot/ViewController/ContactsTableViewController.m index 63de0c874..299bab4f8 100644 --- a/Riot/ViewController/ContactsTableViewController.m +++ b/Riot/ViewController/ContactsTableViewController.m @@ -105,6 +105,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/CountryPickerViewController.m b/Riot/ViewController/CountryPickerViewController.m index 3d6eb1aa0..b371a5bcb 100644 --- a/Riot/ViewController/CountryPickerViewController.m +++ b/Riot/ViewController/CountryPickerViewController.m @@ -73,6 +73,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.searchBar.barStyle = kRiotDesignSearchBarStyle; self.searchBar.tintColor = kRiotDesignSearchBarTintColor; diff --git a/Riot/ViewController/DirectoryServerPickerViewController.m b/Riot/ViewController/DirectoryServerPickerViewController.m index 9a0de53bd..6beb3bbe2 100644 --- a/Riot/ViewController/DirectoryServerPickerViewController.m +++ b/Riot/ViewController/DirectoryServerPickerViewController.m @@ -121,6 +121,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/DirectoryViewController.m b/Riot/ViewController/DirectoryViewController.m index 79d106565..f75eae491 100644 --- a/Riot/ViewController/DirectoryViewController.m +++ b/Riot/ViewController/DirectoryViewController.m @@ -72,6 +72,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/HomeFilesSearchViewController.m b/Riot/ViewController/HomeFilesSearchViewController.m index 885b2ada3..144a3a704 100644 --- a/Riot/ViewController/HomeFilesSearchViewController.m +++ b/Riot/ViewController/HomeFilesSearchViewController.m @@ -72,6 +72,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.searchTableView.backgroundColor = ((self.searchTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/HomeMessagesSearchViewController.m b/Riot/ViewController/HomeMessagesSearchViewController.m index fa5b80729..5f4627530 100644 --- a/Riot/ViewController/HomeMessagesSearchViewController.m +++ b/Riot/ViewController/HomeMessagesSearchViewController.m @@ -79,6 +79,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.searchTableView.backgroundColor = ((self.searchTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/LanguagePickerViewController.m b/Riot/ViewController/LanguagePickerViewController.m index 3db253420..48eb0b335 100644 --- a/Riot/ViewController/LanguagePickerViewController.m +++ b/Riot/ViewController/LanguagePickerViewController.m @@ -70,6 +70,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.searchBar.barStyle = kRiotDesignSearchBarStyle; self.searchBar.tintColor = kRiotDesignSearchBarTintColor; diff --git a/Riot/ViewController/MediaAlbumContentViewController.m b/Riot/ViewController/MediaAlbumContentViewController.m index cb8cfcd9e..785974fe6 100644 --- a/Riot/ViewController/MediaAlbumContentViewController.m +++ b/Riot/ViewController/MediaAlbumContentViewController.m @@ -114,6 +114,7 @@ self.assetsCollectionView.backgroundColor = kRiotPrimaryBgColor; self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; } - (UIStatusBarStyle)preferredStatusBarStyle diff --git a/Riot/ViewController/MediaPickerViewController.m b/Riot/ViewController/MediaPickerViewController.m index 529ad1e8f..0002f8960 100644 --- a/Riot/ViewController/MediaPickerViewController.m +++ b/Riot/ViewController/MediaPickerViewController.m @@ -183,6 +183,7 @@ static void *RecordingContext = &RecordingContext; { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.cameraVideoCaptureProgressView.progressColor = kRiotPrimaryBgColor; self.cameraVideoCaptureProgressView.unprogressColor = [UIColor clearColor]; diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index c88b419f2..28f0a10a5 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -96,6 +96,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.overlayView.backgroundColor = kRiotOverlayColor; self.overlayView.alpha = 1.0; diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index 0af833690..7a0fd765a 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -159,6 +159,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Use the primary bg color for the recents table view in plain style. self.recentsTableView.backgroundColor = kRiotPrimaryBgColor; diff --git a/Riot/ViewController/RoomFilesSearchViewController.m b/Riot/ViewController/RoomFilesSearchViewController.m index 3f5a050de..c324416e4 100644 --- a/Riot/ViewController/RoomFilesSearchViewController.m +++ b/Riot/ViewController/RoomFilesSearchViewController.m @@ -73,6 +73,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.searchTableView.backgroundColor = ((self.searchTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/RoomFilesViewController.m b/Riot/ViewController/RoomFilesViewController.m index 8b945fbbf..53319ec57 100644 --- a/Riot/ViewController/RoomFilesViewController.m +++ b/Riot/ViewController/RoomFilesViewController.m @@ -111,6 +111,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.bubblesTableView.backgroundColor = ((self.bubblesTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/RoomMemberDetailsViewController.m b/Riot/ViewController/RoomMemberDetailsViewController.m index 661cf8623..9aaed6b90 100644 --- a/Riot/ViewController/RoomMemberDetailsViewController.m +++ b/Riot/ViewController/RoomMemberDetailsViewController.m @@ -209,6 +209,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.memberHeaderView.backgroundColor = kRiotSecondaryBgColor; self.roomMemberNameLabel.textColor = kRiotPrimaryTextColor; diff --git a/Riot/ViewController/RoomMessagesSearchViewController.m b/Riot/ViewController/RoomMessagesSearchViewController.m index 1b6cecd33..0d1b8f548 100644 --- a/Riot/ViewController/RoomMessagesSearchViewController.m +++ b/Riot/ViewController/RoomMessagesSearchViewController.m @@ -74,6 +74,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.searchTableView.backgroundColor = ((self.searchTableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/RoomParticipantsViewController.m b/Riot/ViewController/RoomParticipantsViewController.m index b7d4c5651..f6bd6fea5 100644 --- a/Riot/ViewController/RoomParticipantsViewController.m +++ b/Riot/ViewController/RoomParticipantsViewController.m @@ -158,6 +158,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; [self refreshSearchBarItemsColor:_searchBarView]; diff --git a/Riot/ViewController/RoomSettingsViewController.m b/Riot/ViewController/RoomSettingsViewController.m index 551707ea4..d1c29e992 100644 --- a/Riot/ViewController/RoomSettingsViewController.m +++ b/Riot/ViewController/RoomSettingsViewController.m @@ -246,6 +246,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 8bb9a5345..9a578c929 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -417,6 +417,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Prepare jump to last unread banner self.jumpToLastUnreadBannerContainer.backgroundColor = kRiotPrimaryBgColor; diff --git a/Riot/ViewController/SegmentedViewController.m b/Riot/ViewController/SegmentedViewController.m index 41415344b..517023a61 100644 --- a/Riot/ViewController/SegmentedViewController.m +++ b/Riot/ViewController/SegmentedViewController.m @@ -172,6 +172,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; self.view.backgroundColor = kRiotPrimaryBgColor; } diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index b37d1e6e0..f8e359140 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -307,6 +307,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/UsersDevicesViewController.m b/Riot/ViewController/UsersDevicesViewController.m index dc88de02b..a92d779b6 100644 --- a/Riot/ViewController/UsersDevicesViewController.m +++ b/Riot/ViewController/UsersDevicesViewController.m @@ -83,6 +83,7 @@ { self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); diff --git a/Riot/ViewController/WebViewViewController.m b/Riot/ViewController/WebViewViewController.m index 88536d9f6..67c5e69e5 100644 --- a/Riot/ViewController/WebViewViewController.m +++ b/Riot/ViewController/WebViewViewController.m @@ -54,6 +54,7 @@ self.view.backgroundColor = kRiotPrimaryBgColor; self.defaultBarTintColor = kRiotSecondaryBgColor; self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; webView.backgroundColor = kRiotPrimaryBgColor; } From 9d49a9ed8cd47aa40586c869efda6660bb134e12 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 11 Sep 2017 16:22:28 +0200 Subject: [PATCH 08/30] Modular integrations UI: Better seperate "Modular postMessage API" code from UIWebview cooking. Manage error sending --- Riot/Assets/en.lproj/Vector.strings | 13 + .../Widgets/ModularWebAppViewController.m | 223 ++++++++++-------- 2 files changed, 141 insertions(+), 95 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 3d513af8d..e5067e691 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -510,3 +510,16 @@ "widget_no_power_to_manage" = "You need permission to manage widgets in this room"; "widget_creation_failure" = "Widget creation has failed"; +// Widget Integration Manager +"widget_integration_need_login" = "You need to be logged in."; +"widget_integration_need_to_be_able_to_invite" = "You need to be able to invite users to do that."; +"widget_integration_unable_to_create" = "Unable to create widget."; +"widget_integration_failed_to_send_request" = "Failed to send request."; +"widget_integration_room_not_recognised" = "This room is not recognised."; +"widget_integration_positive_power_level" = "Power level must be positive integer."; +"widget_integration_must_be_in_room" = "You are not in this room."; +"widget_integration_no_permission_in_room" = "You do not have permission to do that in this room."; +"widget_integration_missing_room_id" = "Missing room_id in request."; +"widget_integration_missing_user_id" = "Missing user_id in request."; +"widget_integration_room_not_visible" = "Room %@ is not visible."; + diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.m b/Riot/ViewController/Widgets/ModularWebAppViewController.m index c9b12c480..6e630c6ae 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.m +++ b/Riot/ViewController/Widgets/ModularWebAppViewController.m @@ -140,15 +140,23 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; return url; } +- (void)enableDebug +{ + // Setup console.log() -> NSLog() route + JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; + ctx[@"console"][@"log"] = ^(JSValue * msg) { + NSLog(@"-- JavaScript: %@", msg); + }; + + // Redirect all console.* logging methods to console.log + [webView stringByEvaluatingJavaScriptFromString:@"console.debug = console.log; console.info = console.log; console.warn = console.log; console.error = console.log;"]; +} + #pragma mark - UIWebViewDelegate -(void)webViewDidFinishLoad:(UIWebView *)theWebView { - // Setup debug - JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; - ctx[@"console"][@"log"] = ^(JSValue * msg) { - NSLog(@"----------- JavaScript: %@", msg); - }; + [self enableDebug]; // Setup js code NSString *path = [[NSBundle mainBundle] pathForResource:@"ModularWebApp" ofType:@"js"]; @@ -169,99 +177,110 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; NSError *error; NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; - - NSLog(@"++++ parameters: %@", parameters); - if (!error) { - NSDictionary *eventData; - MXJSONModelSetDictionary(eventData, parameters[@"event.data"]); - - NSString *roomIdInEvent, *userId, *action; - - MXJSONModelSetString(roomIdInEvent, eventData[@"room_id"]); - MXJSONModelSetString(userId, eventData[@"user_id"]); - MXJSONModelSetString(action, eventData[@"action"]); - - if (!roomIdInEvent) - { - //sendError(event, _t('Missing room_id in request')); - return NO; - } - - if (![roomIdInEvent isEqualToString:roomId]) - { - //sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId})); - return NO; - } - - - // These APIs don't require userId - if ([@"join_rules_state" isEqualToString:action]) - { - //getJoinRules(event, roomId); - } - else if ([@"set_plumbing_state" isEqualToString:action]) - { - //setPlumbingState(event, roomId, event.data.status); - } - else if ([@"get_membership_count" isEqualToString:action]) - { - //getMembershipCount(event, roomId); - } - else if ([@"set_widget" isEqualToString:action]) - { - //setWidget(event, roomId); - } - else if ([@"get_widgets" isEqualToString:action]) - { - [self getWidgets:eventData]; - } - else if ([@"can_send_event" isEqualToString:action]) - { - [self canSendEvent:eventData]; - //canSendEvent(event, roomId); - } - - - if (!userId) - { - //sendError(event, _t('Missing user_id in request')); - return NO; - } - - if ([@"membership_state" isEqualToString:action]) - { - [self getMembershipState:userId eventData:eventData]; - } - else if ([@"invite" isEqualToString:action]) - { - //inviteUser(event, roomId, userId); - } - else if ([@"bot_options" isEqualToString:action]) - { - //botOptions(event, roomId, userId); - } - else if ([@"set_bot_options" isEqualToString:action]) - { - //setBotOptions(event, roomId, userId); - } - else if ([@"set_bot_power" isEqualToString:action]) - { - //setBotPower(event, roomId, userId, event.data.level); - } - else - { - NSLog(@"[ModularWebAppViewController] Unhandled postMessage event with action %@: %@", action, parameters); - } + [self onMessage:parameters]; } + return NO; } return YES; } +#pragma mark - Modular postMessage API + +- (void)onMessage:(NSDictionary*)JSData +{ + NSDictionary *eventData; + MXJSONModelSetDictionary(eventData, JSData[@"event.data"]); + + NSString *roomIdInEvent, *userId, *action; + + MXJSONModelSetString(roomIdInEvent, eventData[@"room_id"]); + MXJSONModelSetString(userId, eventData[@"user_id"]); + MXJSONModelSetString(action, eventData[@"action"]); + + if (!roomIdInEvent) + { + [self sendLocalisedError:@"widget_integration_missing_room_id" toEvent:eventData]; + return; + } + + if (![roomIdInEvent isEqualToString:roomId]) + { + [self sendError:[NSString stringWithFormat:NSLocalizedStringFromTable(@"widget_integration_room_not_visible", @"Vector", nil), roomIdInEvent] toEvent:eventData]; + return; + } + + + // These APIs don't require userId + if ([@"join_rules_state" isEqualToString:action]) + { + //getJoinRules(event, roomId); + return; + } + else if ([@"set_plumbing_state" isEqualToString:action]) + { + //setPlumbingState(event, roomId, event.data.status); + return; + } + else if ([@"get_membership_count" isEqualToString:action]) + { + //getMembershipCount(event, roomId); + return; + } + else if ([@"set_widget" isEqualToString:action]) + { + //setWidget(event, roomId); + return; + } + else if ([@"get_widgets" isEqualToString:action]) + { + [self getWidgets:eventData]; + return; + } + else if ([@"can_send_event" isEqualToString:action]) + { + [self canSendEvent:eventData]; + return; + } + + + if (!userId) + { + [self sendLocalisedError:@"widget_integration_missing_user_id" toEvent:eventData]; + return; + } + + if ([@"membership_state" isEqualToString:action]) + { + [self getMembershipState:userId eventData:eventData]; + } + else if ([@"invite" isEqualToString:action]) + { + //inviteUser(event, roomId, userId); + } + else if ([@"bot_options" isEqualToString:action]) + { + //botOptions(event, roomId, userId); + } + else if ([@"set_bot_options" isEqualToString:action]) + { + //setBotOptions(event, roomId, userId); + } + else if ([@"set_bot_power" isEqualToString:action]) + { + //setBotPower(event, roomId, userId, event.data.level); + } + else + { + NSLog(@"[ModularWebAppViewController] Unhandled postMessage event with action %@: %@", action, JSData); + } +} + - (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData { + // Convert BOOL to "true" or "false" NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, eventData[@"_id"], response ? @"true" : @"false"]; @@ -275,6 +294,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; if (response) { + // Convert response into a JS object through a JSON string NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response options:0 error:0]; @@ -291,20 +311,33 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; eventData[@"_id"], jsString]; - - NSLog(@"sendNSObjectResponse:\n%@", js); - [webView stringByEvaluatingJavaScriptFromString:js]; } -#pragma mark - Modular postMessage API +- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData +{ + // TODO: JS has an additional optional parameter: nestedError + [self sendNSObjectResponse:@{ + @"error": @{ + @"message": message + } + } + toEvent:eventData]; +} + +- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData +{ + [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toEvent:eventData]; +} + +#pragma mark - Modular postMessage Implementation - (MXRoom *)roomCheckWithEvent:(NSDictionary*)eventData { MXRoom *room = [mxSession roomWithRoomId:roomId]; if (!room) { - //sendError(event, _t('This room is not recognised.')); + [self sendLocalisedError:@"widget_integration_room_not_recognised" toEvent:eventData]; } return room; @@ -340,7 +373,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; { if (room.state.membership != MXMembershipJoin) { - // sendError(event, _t('You are not in this room.')); + [self sendLocalisedError:@"widget_integration_must_be_in_room" toEvent:eventData]; return; } @@ -367,7 +400,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; } else { - //sendError(event, _t('You do not have permission to do that in this room.')); + [self sendLocalisedError:@"widget_integration_no_permission_in_room" toEvent:eventData]; } } } From a2910d24fadf70386b0913652eded87bac353f2c Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 11 Sep 2017 16:33:52 +0200 Subject: [PATCH 09/30] Modular integrations UI: File renaming --- .../js/{ModularWebApp.js => IntegrationManager.js} | 0 ...Controller.h => IntegrationManagerViewController.h} | 4 ++-- ...Controller.m => IntegrationManagerViewController.m} | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) rename Riot/Assets/js/{ModularWebApp.js => IntegrationManager.js} (100%) rename Riot/ViewController/Widgets/{ModularWebAppViewController.h => IntegrationManagerViewController.h} (86%) rename Riot/ViewController/Widgets/{ModularWebAppViewController.m => IntegrationManagerViewController.m} (97%) diff --git a/Riot/Assets/js/ModularWebApp.js b/Riot/Assets/js/IntegrationManager.js similarity index 100% rename from Riot/Assets/js/ModularWebApp.js rename to Riot/Assets/js/IntegrationManager.js diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.h b/Riot/ViewController/Widgets/IntegrationManagerViewController.h similarity index 86% rename from Riot/ViewController/Widgets/ModularWebAppViewController.h rename to Riot/ViewController/Widgets/IntegrationManagerViewController.h index 1270611a2..7e8483de0 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.h +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.h @@ -19,10 +19,10 @@ #import /** - `ModularWebAppViewController` displays the Modular integration manager webapp + `IntegrationManagerViewController` displays the Modular integration manager webapp into a webview. */ -@interface ModularWebAppViewController : WebViewViewController +@interface IntegrationManagerViewController : WebViewViewController /** Initialise with params for the Modular interface webapp. diff --git a/Riot/ViewController/Widgets/ModularWebAppViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m similarity index 97% rename from Riot/ViewController/Widgets/ModularWebAppViewController.m rename to Riot/ViewController/Widgets/IntegrationManagerViewController.m index 6e630c6ae..4190ddd51 100644 --- a/Riot/ViewController/Widgets/ModularWebAppViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -14,7 +14,7 @@ limitations under the License. */ -#import "ModularWebAppViewController.h" +#import "IntegrationManagerViewController.h" #import "WidgetManager.h" @@ -23,7 +23,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; -@interface ModularWebAppViewController () +@interface IntegrationManagerViewController () { MXSession *mxSession; NSString *roomId; @@ -36,7 +36,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; @end -@implementation ModularWebAppViewController +@implementation IntegrationManagerViewController - (instancetype)initForMXSession:(MXSession *)theMXSession inRoom:(NSString *)theRoomId screen:(NSString *)theScreen widgetId:(NSString *)theWidgetId { @@ -159,7 +159,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; [self enableDebug]; // Setup js code - NSString *path = [[NSBundle mainBundle] pathForResource:@"ModularWebApp" ofType:@"js"]; + NSString *path = [[NSBundle mainBundle] pathForResource:@"IntegrationManager" ofType:@"js"]; NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; [webView stringByEvaluatingJavaScriptFromString:js]; } @@ -274,7 +274,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; } else { - NSLog(@"[ModularWebAppViewController] Unhandled postMessage event with action %@: %@", action, JSData); + NSLog(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, JSData); } } From 902d0a52615907fcd66b0d8a09e6cbbc2f32aada Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 11 Sep 2017 16:35:42 +0200 Subject: [PATCH 10/30] WidgetManager: createWidget did not call the success block --- Riot/Utils/Widgets/WidgetManager.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 6abd6148f..6805f843c 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -169,6 +169,10 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return nil; } + NSString *hash = [NSString stringWithFormat:@"%p", room.mxSession]; + successBlockForWidgetCreation[hash][widgetId] = success; + failureBlockForWidgetCreation[hash][widgetId] = failure; + // Send a state event with the widget data // TODO: This API will be shortly replaced by a pure modular API return [room sendStateEventOfType:kWidgetEventTypeString @@ -199,10 +203,6 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; NSString *modularRestUrl = [[NSUserDefaults standardUserDefaults] objectForKey:@"integrationsRestUrl"]; NSString *url = [NSString stringWithFormat:@"%@/widgets/jitsi.html?confId=%@&isAudioConf=%@&displayName=$matrix_display_name&avatarUrl=$matrix_avatar_url&email=$matrix_user_id@", modularRestUrl, confId, video ? @"false" : @"true"]; - NSString *hash = [NSString stringWithFormat:@"%p", room.mxSession]; - successBlockForWidgetCreation[hash][widgetId] = success; - failureBlockForWidgetCreation[hash][widgetId] = failure; - return [self createWidget:widgetId withContent:@{ @"url": url, From 35daf83e5e72ae22352c4abc603fbcd216da1df3 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Mon, 11 Sep 2017 22:37:00 +0200 Subject: [PATCH 11/30] Dark theme - Improvements - the keyboard bubble should be darker too --- Riot/Categories/UIViewController+RiotSearch.m | 3 ++- Riot/Utils/RiotDesignValues.h | 1 + Riot/Utils/RiotDesignValues.m | 8 ++++++++ Riot/ViewController/RoomSearchViewController.m | 14 +++++++++++++- Riot/ViewController/SegmentedViewController.h | 7 ++++++- Riot/ViewController/SegmentedViewController.m | 4 ++-- Riot/ViewController/UnifiedSearchViewController.m | 14 +++++++++++++- 7 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Riot/Categories/UIViewController+RiotSearch.m b/Riot/Categories/UIViewController+RiotSearch.m index 9db702f76..becfd114c 100644 --- a/Riot/Categories/UIViewController+RiotSearch.m +++ b/Riot/Categories/UIViewController+RiotSearch.m @@ -134,7 +134,8 @@ - (void)addBackgroundImageViewToView:(UIView*)view { - UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"search_bg"]]; + UIImage *searchBgImage = [MXKTools paintImage:[UIImage imageNamed:@"search_bg"] withColor:kRiotKeyboardColor]; + UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:searchBgImage]; backgroundImageView.translatesAutoresizingMaskIntoConstraints = NO; [view addSubview:backgroundImageView]; diff --git a/Riot/Utils/RiotDesignValues.h b/Riot/Utils/RiotDesignValues.h index 5ed316b18..034a28ef5 100644 --- a/Riot/Utils/RiotDesignValues.h +++ b/Riot/Utils/RiotDesignValues.h @@ -41,6 +41,7 @@ extern UIColor *kRiotTopicTextColor; extern UIColor *kRiotSelectedBgColor; // nil is used to keep the default color. extern UIColor *kRiotAuxiliaryColor; // kRiotColorSilver by default. extern UIColor *kRiotOverlayColor; // fading behind dialog modals. This color includes the transparency value. +extern UIColor *kRiotKeyboardColor; #pragma mark - Riot Colors extern UIColor *kRiotColorGreen; diff --git a/Riot/Utils/RiotDesignValues.m b/Riot/Utils/RiotDesignValues.m index 49a0b4dc9..cd0ee8756 100644 --- a/Riot/Utils/RiotDesignValues.m +++ b/Riot/Utils/RiotDesignValues.m @@ -28,6 +28,7 @@ UIColor *kRiotTopicTextColor; UIColor *kRiotSelectedBgColor; UIColor *kRiotAuxiliaryColor; UIColor *kRiotOverlayColor; +UIColor *kRiotKeyboardColor; // Riot Colors UIColor *kRiotColorGreen; @@ -44,6 +45,8 @@ UIColor *kRiotBgColorWhite; UIColor *kRiotBgColorBlack; UIColor *kRiotColorLightGrey; UIColor *kRiotColorLightBlack; +UIColor *kRiotColorLightKeyboard; +UIColor *kRiotColorDarkKeyboard; // Riot Text Colors UIColor *kRiotTextColorBlack; @@ -92,6 +95,9 @@ UIColor *kRiotDesignSearchBarTintColor = nil; kRiotColorLightGrey = UIColorFromRGB(0xF2F2F2); kRiotColorLightBlack = UIColorFromRGB(0x353535); + + kRiotColorLightKeyboard = UIColorFromRGB(0xE7E7E7); + kRiotColorDarkKeyboard = UIColorFromRGB(0x7E7E7E); kRiotTextColorBlack = UIColorFromRGB(0x3C3C3C); kRiotTextColorDarkGray = UIColorFromRGB(0x4A4A4A); @@ -140,6 +146,7 @@ UIColor *kRiotDesignSearchBarTintColor = nil; kRiotAuxiliaryColor = kRiotTextColorGray; kRiotOverlayColor = [UIColor colorWithWhite:0.3 alpha:0.5]; + kRiotKeyboardColor = kRiotColorDarkKeyboard; } else { @@ -158,6 +165,7 @@ UIColor *kRiotDesignSearchBarTintColor = nil; kRiotAuxiliaryColor = kRiotColorSilver; kRiotOverlayColor = [UIColor colorWithWhite:0.7 alpha:0.5]; + kRiotKeyboardColor = kRiotColorLightKeyboard; } [[NSNotificationCenter defaultCenter] postNotificationName:kRiotDesignValuesDidChangeThemeNotification object:nil]; diff --git a/Riot/ViewController/RoomSearchViewController.m b/Riot/ViewController/RoomSearchViewController.m index bdbb2e2fa..f6b7ab50e 100644 --- a/Riot/ViewController/RoomSearchViewController.m +++ b/Riot/ViewController/RoomSearchViewController.m @@ -63,7 +63,7 @@ [super viewDidLoad]; - // Add the Vector background image when search bar is empty + // Add the Riot background image when search bar is empty [self addBackgroundImageViewToView:self.view]; // Initialize here the data sources if a matrix session has been already set. @@ -72,6 +72,18 @@ self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; } +- (void)userInterfaceThemeDidChange +{ + [super userInterfaceThemeDidChange]; + + UIImageView *backgroundImageView = self.backgroundImageView; + if (backgroundImageView) + { + UIImage *image = [MXKTools paintImage:backgroundImageView.image withColor:kRiotKeyboardColor]; + backgroundImageView.image = image; + } +} + - (void)destroy { [super destroy]; diff --git a/Riot/ViewController/SegmentedViewController.h b/Riot/ViewController/SegmentedViewController.h index b870f188c..b6949a7c9 100644 --- a/Riot/ViewController/SegmentedViewController.h +++ b/Riot/ViewController/SegmentedViewController.h @@ -77,6 +77,11 @@ limitations under the License. @param viewControllers the list of viewControllers to display. @param defaultSelected index of the default selected UIViewController in the list. */ -- (void)initWithTitles:(NSArray*)titles viewControllers:(NSArray*)viewControllers defaultSelected:(NSUInteger)index; +- (void)initWithTitles:(NSArray*)titles viewControllers:(NSArray*)viewControllers defaultSelected:(NSUInteger)defaultSelected; + +/** + Callback used to take into account the change of the user interface theme. + */ +- (void)userInterfaceThemeDidChange; @end diff --git a/Riot/ViewController/SegmentedViewController.m b/Riot/ViewController/SegmentedViewController.m index 517023a61..0d49ed60c 100644 --- a/Riot/ViewController/SegmentedViewController.m +++ b/Riot/ViewController/SegmentedViewController.m @@ -71,11 +71,11 @@ @param viewControllers the list of viewControllers to display. @param defaultSelected index of the default selected UIViewController in the list. */ -- (void)initWithTitles:(NSArray*)titles viewControllers:(NSArray*)someViewControllers defaultSelected:(NSUInteger)index +- (void)initWithTitles:(NSArray*)titles viewControllers:(NSArray*)someViewControllers defaultSelected:(NSUInteger)defaultSelected { viewControllers = someViewControllers; sectionTitles = titles; - _selectedIndex = index; + _selectedIndex = defaultSelected; } - (void)destroy diff --git a/Riot/ViewController/UnifiedSearchViewController.m b/Riot/ViewController/UnifiedSearchViewController.m index e1b9e5d4c..560a15bc5 100644 --- a/Riot/ViewController/UnifiedSearchViewController.m +++ b/Riot/ViewController/UnifiedSearchViewController.m @@ -95,7 +95,7 @@ [super viewDidLoad]; - // Add the Vector background image when search bar is empty + // Add the Riot background image when search bar is empty [self addBackgroundImageViewToView:self.view]; // Initialize here the data sources if a matrix session has been already set. @@ -107,6 +107,18 @@ [super showSearch:NO]; } +- (void)userInterfaceThemeDidChange +{ + [super userInterfaceThemeDidChange]; + + UIImageView *backgroundImageView = self.backgroundImageView; + if (backgroundImageView) + { + UIImage *image = [MXKTools paintImage:backgroundImageView.image withColor:kRiotKeyboardColor]; + backgroundImageView.image = image; + } +} + - (void)destroy { [super destroy]; From cc30abc89ec229e5a28b897db12f8656522ca1e8 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 14 Sep 2017 09:53:07 +0200 Subject: [PATCH 12/30] Modular integrations UI: Stubbed all modular requests. --- .../IntegrationManagerViewController.m | 94 +++++++++++++++++-- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 4190ddd51..7c890f25c 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -216,22 +216,22 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; // These APIs don't require userId if ([@"join_rules_state" isEqualToString:action]) { - //getJoinRules(event, roomId); + [self getJoinRules:eventData]; return; } else if ([@"set_plumbing_state" isEqualToString:action]) { - //setPlumbingState(event, roomId, event.data.status); + [self setPlumbingState:eventData]; return; } else if ([@"get_membership_count" isEqualToString:action]) { - //getMembershipCount(event, roomId); + [self getMembershipCount:eventData]; return; } else if ([@"set_widget" isEqualToString:action]) { - //setWidget(event, roomId); + [self setWidget:eventData]; return; } else if ([@"get_widgets" isEqualToString:action]) @@ -258,19 +258,19 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; } else if ([@"invite" isEqualToString:action]) { - //inviteUser(event, roomId, userId); + [self inviteUser:userId eventData:eventData]; } else if ([@"bot_options" isEqualToString:action]) { - //botOptions(event, roomId, userId); + [self getBotOptions:userId eventData:eventData]; } else if ([@"set_bot_options" isEqualToString:action]) { - //setBotOptions(event, roomId, userId); + [self setBotOptions:userId eventData:eventData]; } else if ([@"set_bot_power" isEqualToString:action]) { - //setBotPower(event, roomId, userId, event.data.level); + [self setBotPower:userId eventData:eventData]; } else { @@ -288,6 +288,15 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; [webView stringByEvaluatingJavaScriptFromString:js]; } +- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData +{ + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, + eventData[@"_id"], + @(response)]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + - (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData { NSString *jsString; @@ -316,6 +325,8 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; - (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData { + NSLog(@"[IntegrationManagerVC] sendError: Action %@ failed with message: %@", eventData[@"action"], message); + // TODO: JS has an additional optional parameter: nestedError [self sendNSObjectResponse:@{ @"error": @{ @@ -343,6 +354,20 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; return room; } +- (void)inviteUser:(NSString*)userId eventData:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId); + + NSAssert(NO, @"TODO"); +} + +- (void)setWidget:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] Received request to set widget in room %@.", roomId); + + NSAssert(NO, @"TODO"); +} + - (void)getWidgets:(NSDictionary*)eventData { MXRoom *room = [self roomCheckWithEvent:eventData]; @@ -407,8 +432,9 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; - (void)getMembershipState:(NSString*)userId eventData:(NSDictionary*)eventData { - MXRoom *room = [self roomCheckWithEvent:eventData]; + NSLog(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId); + MXRoom *room = [self roomCheckWithEvent:eventData]; if (room) { MXRoomMember *member = [room.state memberWithUserId:userId]; @@ -416,4 +442,54 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; } } +- (void)getJoinRules:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId); + + MXRoom *room = [self roomCheckWithEvent:eventData]; + if (room) + { + MXEvent *event = [room.state stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject; + [self sendNSObjectResponse:event.JSONDictionary toEvent:eventData]; + } +} + +- (void)setPlumbingState:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", eventData[@"status"], roomId); + + NSAssert(NO, @"TODO"); +} + +- (void)getBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId); + + NSAssert(NO, @"TODO"); +} + +- (void)setBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId); + + NSAssert(NO, @"TODO"); +} + +- (void)setBotPower:(NSString*)userId eventData:(NSDictionary*)eventData +{ + NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", eventData[@"level"], userId, roomId); + + NSAssert(NO, @"TODO"); +} + +- (void)getMembershipCount:(NSDictionary*)eventData +{ + MXRoom *room = [self roomCheckWithEvent:eventData]; + if (room) + { + NSUInteger membershipCount = room.state.joinedMembers.count; + [self sendIntegerResponse:membershipCount toEvent:eventData]; + } +} + @end From 9ec87ec467a0970a3b402a2f7ea406322ee2b12a Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 14 Sep 2017 11:33:59 +0200 Subject: [PATCH 13/30] Bug Fix - Settings: some of the labels push the switch controls off screen. #1506 --- Riot/ViewController/RoomSettingsViewController.m | 12 ++++++++++++ Riot/ViewController/SettingsViewController.m | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/Riot/ViewController/RoomSettingsViewController.m b/Riot/ViewController/RoomSettingsViewController.m index d1c29e992..083304a3b 100644 --- a/Riot/ViewController/RoomSettingsViewController.m +++ b/Riot/ViewController/RoomSettingsViewController.m @@ -1965,6 +1965,9 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti } cell = roomNotifCell; + + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; } else if (row == ROOM_SETTINGS_MAIN_SECTION_ROW_PHOTO) { @@ -2182,6 +2185,9 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti directoryVisibilitySwitch.enabled = (oneSelfPowerLevel >= powerLevels.stateDefault); cell = directoryToggleCell; + + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; } else if (indexPath.row == missingAddressWarningIndex) { @@ -2492,6 +2498,9 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti roomEncryptionBlacklistUnverifiedDevicesSwitch.on = blacklistUnverifiedDevices; cell = roomBlacklistUnverifiedDevicesCell; + + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; } else if (indexPath.row == 2) { @@ -2548,6 +2557,9 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti roomEncryptionSwitch.on = ([updatedItemsDict objectForKey:kRoomSettingsEncryptionKey] != nil); cell = roomEncryptionCell; + + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; } else { diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index f8e359140..a21b98edd 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -1256,6 +1256,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); cell.alpha = 1.0f; cell.userInteractionEnabled = YES; + [cell layoutIfNeeded]; + return cell; } @@ -1268,6 +1270,9 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); cell.mxkLabel.textColor = kRiotPrimaryTextColor; + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; + return cell; } From 4db0281d002b181285a754420336a762ebd8bc7e Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 14 Sep 2017 11:58:02 +0200 Subject: [PATCH 14/30] Bug Fix - Settings: The "Sign out" button and other buttons of this page sometimes blinks #1354 --- Riot/ViewController/SettingsViewController.m | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index f8e359140..e784bffb2 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -1318,6 +1318,12 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { signOutCell = [[MXKTableViewCellWithButton alloc] init]; } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + // Do not move this line in prepareForReuse because of https://github.com/vector-im/riot-ios/issues/1323 + signOutCell.mxkButton.titleLabel.text = nil; + } NSString* title = NSLocalizedStringFromTable(@"settings_sign_out", @"Vector", nil); @@ -1861,6 +1867,11 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { markAllBtnCell = [[MXKTableViewCellWithButton alloc] init]; } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + markAllBtnCell.mxkButton.titleLabel.text = nil; + } NSString *btnTitle = NSLocalizedStringFromTable(@"settings_mark_all_as_read", @"Vector", nil); [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; @@ -1881,6 +1892,11 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { clearCacheBtnCell = [[MXKTableViewCellWithButton alloc] init]; } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + clearCacheBtnCell.mxkButton.titleLabel.text = nil; + } NSString *btnTitle = NSLocalizedStringFromTable(@"settings_clear_cache", @"Vector", nil); [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; @@ -1901,6 +1917,11 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { reportBugBtnCell = [[MXKTableViewCellWithButton alloc] init]; } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + reportBugBtnCell.mxkButton.titleLabel.text = nil; + } NSString *btnTitle = NSLocalizedStringFromTable(@"settings_report_bug", @"Vector", nil); [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; @@ -2004,6 +2025,11 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + exportKeysBtnCell.mxkButton.titleLabel.text = nil; + } NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil); [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; From ec85532a500093ae2f2463c1b4b264cbf1a98b38 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 14 Sep 2017 16:00:50 +0200 Subject: [PATCH 15/30] Handle the room display name and its avatar at the room summary level. - Update the room display name and its avatar url when the Room Summary is updated on state events change (see `[session: updateRoomSummary: withStateEvents:]`). - Store the riot display room name and its avatar url in the Room Summary. - Add a new category `MXRoomSummary+Riot` to handle avatar display. TODO: Increase file store version in `MXFileStore.h`. --- Riot/AppDelegate.m | 3 +- Riot/Categories/MXRoom+Riot.h | 16 -- Riot/Categories/MXRoom+Riot.m | 176 -------------- Riot/Categories/MXRoomSummary+Riot.h | 35 +++ Riot/Categories/MXRoomSummary+Riot.m | 43 ++++ Riot/Model/Room/RoomPreviewData.m | 6 +- Riot/Model/RoomList/RecentCellData.m | 2 +- Riot/Model/RoomList/RecentsDataSource.m | 2 +- Riot/Model/Search/FilesSearchCellData.m | 4 +- Riot/Utils/EventFormatter.m | 219 ++++++++++++++++++ Riot/ViewController/CallViewController.m | 6 +- .../RoomSettingsViewController.m | 3 +- Riot/Views/RoomList/RecentTableViewCell.m | 4 +- Riot/Views/RoomList/RoomCollectionViewCell.m | 4 +- Riot/Views/RoomList/RoomTableViewCell.m | 6 +- Riot/Views/RoomTitle/ExpandedRoomTitleView.m | 6 +- Riot/Views/RoomTitle/PreviewRoomTitleView.m | 6 +- Riot/Views/RoomTitle/RoomAvatarTitleView.m | 4 +- Riot/Views/RoomTitle/RoomTitleView.m | 6 +- Riot/Views/RoomTitle/SimpleRoomTitleView.m | 6 +- ...MessagesSearchResultAttachmentBubbleCell.m | 4 +- .../MessagesSearchResultTextMsgBubbleCell.m | 4 +- .../ViewController/RoomsListViewController.m | 3 +- 23 files changed, 328 insertions(+), 240 deletions(-) create mode 100644 Riot/Categories/MXRoomSummary+Riot.h create mode 100644 Riot/Categories/MXRoomSummary+Riot.m diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index e2d707ddf..1b6d61f28 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -34,7 +34,6 @@ #import "MatrixSDK/MatrixSDK.h" #import "Tools.h" -#import "MXRoom+Riot.h" #import "WidgetManager.h" #import "AFNetworkReachabilityManager.h" @@ -2459,7 +2458,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [_jitsiViewController dismissViewControllerAnimated:YES completion:^{ MXRoom *room = [_jitsiViewController.widget.mxSession roomWithRoomId:_jitsiViewController.widget.roomId]; - NSString *btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"active_call_details", @"Vector", nil), room.riotDisplayname]; + NSString *btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"active_call_details", @"Vector", nil), room.summary.displayname]; [self addCallStatusBar:btnTitle]; if (completion) diff --git a/Riot/Categories/MXRoom+Riot.h b/Riot/Categories/MXRoom+Riot.h index 107c5cd20..e6e4be5a0 100644 --- a/Riot/Categories/MXRoom+Riot.h +++ b/Riot/Categories/MXRoom+Riot.h @@ -22,11 +22,6 @@ */ @interface MXRoom (Riot) -/** - The Riot displayname of the room - */ -@property(nonatomic, readonly) NSString* riotDisplayname; - /** Tell whether all the notifications are disabled for the room. */ @@ -47,17 +42,6 @@ */ @property (nonatomic) id notificationCenterDidUpdateObserver; -/** - Set the room avatar in the dedicated MXKImageView. - The vector style implies to use in order : - 1 - the default avatar if there is one - 2 - the member avatar for < 3 members rooms - 3 - the first later of the room name. - - @param mxkImageView the destinated MXKImageView. - */ -- (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView; - /** Update the room tag. diff --git a/Riot/Categories/MXRoom+Riot.m b/Riot/Categories/MXRoom+Riot.m index d6b2bb350..d6e070c07 100644 --- a/Riot/Categories/MXRoom+Riot.m +++ b/Riot/Categories/MXRoom+Riot.m @@ -23,182 +23,6 @@ @implementation MXRoom (Riot) -#pragma mark - Room avatar - -- (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView -{ - NSString* roomAvatarUrl = self.state.avatar; - - if (!roomAvatarUrl) - { - // If the room has only two members, use the avatar of the second member. - NSArray* members = self.state.members; - - if (members.count == 2) - { - NSString* myUserId = self.mxSession.myUser.userId; - - for (MXRoomMember *roomMember in members) - { - if (![roomMember.userId isEqualToString:myUserId]) - { - // Use the avatar of this member only if he joined or he is invited. - if (MXMembershipJoin == roomMember.membership || MXMembershipInvite == roomMember.membership) - { - roomAvatarUrl = roomMember.avatarUrl; - } - break; - } - } - } - } - - // Retrieve the Riot room display name to prepare the default avatar image. - // Note: this display name is nil for an "empty room" without display name (We name "empty room" a room in which the current user is the only active member). - NSString *avatarDisplayName = self.riotDisplayname; - UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:self.state.roomId withDisplayName:avatarDisplayName]; - - if (roomAvatarUrl) - { - mxkImageView.enableInMemoryCache = YES; - - [mxkImageView setImageURL:[self.mxSession.matrixRestClient urlOfContentThumbnail:roomAvatarUrl toFitViewSize:mxkImageView.frame.size withMethod:MXThumbnailingMethodCrop] withType:nil andImageOrientation:UIImageOrientationUp previewImage:avatarImage]; - } - else - { - mxkImageView.image = avatarImage; - } - - mxkImageView.contentMode = UIViewContentModeScaleAspectFill; -} - -#pragma mark - Room display name -// @TODO: May worth to refactor to use MXRoomSummary -- (NSString *)riotDisplayname -{ - // this algo is the one defined in - // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 - // calculateRoomName(room, userId) - - MXRoomState* roomState = self.state; - - if (roomState.name.length > 0) - { - return roomState.name; - } - - NSString *alias = roomState.canonicalAlias; - - if (!alias) - { - // For rooms where canonical alias is not defined, we use the 1st alias as a workaround - NSArray *aliases = roomState.aliases; - - if (aliases.count) - { - alias = [aliases[0] copy]; - } - } - - // check if there is non empty alias. - if ([alias length] > 0) - { - return alias; - } - - NSString* myUserId = self.mxSession.myUser.userId; - - NSArray* members = roomState.members; - NSMutableArray* othersActiveMembers = [[NSMutableArray alloc] init]; - NSMutableArray* activeMembers = [[NSMutableArray alloc] init]; - - for(MXRoomMember* member in members) - { - if (member.membership != MXMembershipLeave) - { - if (![member.userId isEqualToString:myUserId]) - { - [othersActiveMembers addObject:member]; - } - - [activeMembers addObject:member]; - } - } - - // sort the members by their creation (oldest first) - othersActiveMembers = [[othersActiveMembers sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { - - uint64_t originServerTs1 = 0; - uint64_t originServerTs2 = 0; - - MXRoomMember* member1 = (MXRoomMember*)obj1; - MXRoomMember* member2 = (MXRoomMember*)obj2; - - if (member1.originalEvent) - { - originServerTs1 = member1.originalEvent.originServerTs; - } - - if (member2.originalEvent) - { - originServerTs2 = member2.originalEvent.originServerTs; - } - - if (originServerTs1 == originServerTs2) - { - return NSOrderedSame; - } - else - { - return originServerTs1 > originServerTs2 ? NSOrderedDescending : NSOrderedAscending; - } - }] mutableCopy]; - - - NSString* displayName = @""; - - if (othersActiveMembers.count == 0) - { - if (activeMembers.count == 1) - { - MXRoomMember* member = [activeMembers objectAtIndex:0]; - - if (member.membership == MXMembershipInvite) - { - if (member.originalEvent.sender) - { - // extract who invited us to the room - displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_invite_from", @"Vector", nil), [roomState memberName:member.originalEvent.sender]]; - } - else - { - displayName = NSLocalizedStringFromTable(@"room_displayname_room_invite", @"Vector", nil); - } - } - } - } - else if (othersActiveMembers.count == 1) - { - MXRoomMember* member = [othersActiveMembers objectAtIndex:0]; - - displayName = [roomState memberName:member.userId]; - } - else if (othersActiveMembers.count == 2) - { - MXRoomMember* member1 = [othersActiveMembers objectAtIndex:0]; - MXRoomMember* member2 = [othersActiveMembers objectAtIndex:1]; - - displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_two_members", @"Vector", nil), [roomState memberName:member1.userId], [roomState memberName:member2.userId]]; - } - else - { - MXRoomMember* member = [othersActiveMembers objectAtIndex:0]; - displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_more_than_two_members", @"Vector", nil), [roomState memberName:member.userId], othersActiveMembers.count - 1]; - } - - return displayName; -} - #pragma mark - Room tags - (void)setRoomTag:(NSString*)tag completion:(void (^)())completion diff --git a/Riot/Categories/MXRoomSummary+Riot.h b/Riot/Categories/MXRoomSummary+Riot.h new file mode 100644 index 000000000..804a6a6be --- /dev/null +++ b/Riot/Categories/MXRoomSummary+Riot.h @@ -0,0 +1,35 @@ +/* + 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 + +/** + Define a `MXRoomSummary` category at Riot level. + */ +@interface MXRoomSummary (Riot) + +/** + Set the room avatar in the dedicated MXKImageView. + The riot style implies to use in order : + 1 - the default avatar if there is one + 2 - the member avatar for < 3 members rooms + 3 - the first letter of the room name. + + @param mxkImageView the destinated MXKImageView. + */ +- (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView; + +@end diff --git a/Riot/Categories/MXRoomSummary+Riot.m b/Riot/Categories/MXRoomSummary+Riot.m new file mode 100644 index 000000000..78465b325 --- /dev/null +++ b/Riot/Categories/MXRoomSummary+Riot.m @@ -0,0 +1,43 @@ +/* + 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 "MXRoomSummary+Riot.h" + +#import "AvatarGenerator.h" + +@implementation MXRoomSummary (Riot) + +- (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView +{ + // Use the room display name to prepare the default avatar image. + NSString *avatarDisplayName = self.displayname; + UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:self.roomId withDisplayName:avatarDisplayName]; + + if (self.avatar) + { + mxkImageView.enableInMemoryCache = YES; + + [mxkImageView setImageURL:[self.mxSession.matrixRestClient urlOfContentThumbnail:self.avatar toFitViewSize:mxkImageView.frame.size withMethod:MXThumbnailingMethodCrop] withType:nil andImageOrientation:UIImageOrientationUp previewImage:avatarImage]; + } + else + { + mxkImageView.image = avatarImage; + } + + mxkImageView.contentMode = UIViewContentModeScaleAspectFill; +} + +@end diff --git a/Riot/Model/Room/RoomPreviewData.m b/Riot/Model/Room/RoomPreviewData.m index e03cae846..39f40d2ba 100644 --- a/Riot/Model/Room/RoomPreviewData.m +++ b/Riot/Model/Room/RoomPreviewData.m @@ -17,8 +17,6 @@ #import "RoomPreviewData.h" -#import "MXRoom+Riot.h" - @implementation RoomPreviewData - (instancetype)initWithRoomId:(NSString *)roomId andSession:(MXSession *)mxSession @@ -88,8 +86,8 @@ [_roomDataSource finalizeInitialization]; _roomDataSource.markTimelineInitialEvent = YES; - _roomName = peekingRoom.riotDisplayname; - _roomAvatarUrl = peekingRoom.state.avatar; + _roomName = peekingRoom.summary.displayname; + _roomAvatarUrl = peekingRoom.summary.avatar; _roomTopic = [MXTools stripNewlineCharacters:peekingRoom.state.topic];; _roomAliases = peekingRoom.state.aliases; diff --git a/Riot/Model/RoomList/RecentCellData.m b/Riot/Model/RoomList/RecentCellData.m index b1c8623ca..6b28e9528 100644 --- a/Riot/Model/RoomList/RecentCellData.m +++ b/Riot/Model/RoomList/RecentCellData.m @@ -57,7 +57,7 @@ - (void)update { [super update]; - roomDisplayname = self.roomSummary.room.riotDisplayname; + roomDisplayname = self.roomSummary.displayname; if (!roomDisplayname.length) { roomDisplayname = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Model/RoomList/RecentsDataSource.m b/Riot/Model/RoomList/RecentsDataSource.m index 900cb3bad..83c725b59 100644 --- a/Riot/Model/RoomList/RecentsDataSource.m +++ b/Riot/Model/RoomList/RecentsDataSource.m @@ -1487,7 +1487,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou NSString* tagOrder = [room.mxSession tagOrderToBeAtIndex:newPath.row from:oldPos withTag:dstRoomTag]; - NSLog(@"[RecentsDataSource] Update the room %@ [%@] tag from %@ to %@ with tag order %@", room.state.roomId, room.riotDisplayname, oldRoomTag, dstRoomTag, tagOrder); + NSLog(@"[RecentsDataSource] Update the room %@ [%@] tag from %@ to %@ with tag order %@", room.state.roomId, room.summary.displayname, oldRoomTag, dstRoomTag, tagOrder); [room replaceTag:oldRoomTag byTag:dstRoomTag diff --git a/Riot/Model/Search/FilesSearchCellData.m b/Riot/Model/Search/FilesSearchCellData.m index 6ad642147..3eff018a7 100644 --- a/Riot/Model/Search/FilesSearchCellData.m +++ b/Riot/Model/Search/FilesSearchCellData.m @@ -17,8 +17,6 @@ #import "FilesSearchCellData.h" -#import "MXRoom+Riot.h" - @implementation FilesSearchCellData @synthesize roomId, senderDisplayName; @synthesize searchResult, title, message, date, shouldShowRoomDisplayName, roomDisplayName, attachment, isAttachmentWithThumbnail, attachmentIcon; @@ -82,7 +80,7 @@ MXRoom *room = [searchDataSource.mxSession roomWithRoomId:roomId]; if (room) { - roomDisplayName = room.riotDisplayname; + roomDisplayName = room.summary.displayname; if (!roomDisplayName.length) { roomDisplayName = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 6fb500e2d..792de6359 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -190,6 +190,225 @@ return senderAvatarUrl; } +#pragma mark - MXRoomSummaryUpdating + +- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withStateEvents:(NSArray *)stateEvents +{ + BOOL ret = [super session:session updateRoomSummary:summary withStateEvents:stateEvents]; + + // Check whether the room display name and/or the room avatar url should be updated at Riot level. + NSString *riotRoomDisplayName; + NSString *riotRoomAvatarURL; + + for (MXEvent *event in stateEvents) + { + switch (event.eventType) + { + case MXEventTypeRoomName: + case MXEventTypeRoomAliases: + case MXEventTypeRoomCanonicalAlias: + { + if (!riotRoomDisplayName.length) + { + riotRoomDisplayName = [self riotRoomDisplayNameFromRoomState:summary.room.state]; + } + break; + } + case MXEventTypeRoomMember: + { + if (!riotRoomDisplayName.length) + { + riotRoomDisplayName = [self riotRoomDisplayNameFromRoomState:summary.room.state]; + } + // Do not break here to check avatar url too. + } + case MXEventTypeRoomAvatar: + { + if (!riotRoomAvatarURL.length) + { + riotRoomAvatarURL = [self riotRoomAvatarURLFromRoomState:summary.room.state]; + } + break; + } + default: + break; + } + } + + if (riotRoomDisplayName.length && ![summary.displayname isEqualToString:riotRoomDisplayName]) + { + summary.displayname = riotRoomDisplayName; + ret = YES; + } + + if (riotRoomAvatarURL.length && ![summary.avatar isEqualToString:riotRoomAvatarURL]) + { + summary.avatar = riotRoomAvatarURL; + ret = YES; + } + + return ret; +} + +#pragma mark - Riot room display name + +- (NSString *)riotRoomDisplayNameFromRoomState:(MXRoomState *)roomState +{ + // this algo is the one defined in + // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 + // calculateRoomName(room, userId) + + // This display name is @"" for an "empty room" without display name (We name "empty room" a room in which the current user is the only active member). + + if (roomState.name.length > 0) + { + return roomState.name; + } + + NSString *alias = roomState.canonicalAlias; + + if (!alias) + { + // For rooms where canonical alias is not defined, we use the 1st alias as a workaround + NSArray *aliases = roomState.aliases; + + if (aliases.count) + { + alias = [aliases[0] copy]; + } + } + + // check if there is non empty alias. + if ([alias length] > 0) + { + return alias; + } + + NSString* myUserId = mxSession.myUser.userId; + + NSArray* members = roomState.members; + NSMutableArray* othersActiveMembers = [[NSMutableArray alloc] init]; + NSMutableArray* activeMembers = [[NSMutableArray alloc] init]; + + for(MXRoomMember* member in members) + { + if (member.membership != MXMembershipLeave) + { + if (![member.userId isEqualToString:myUserId]) + { + [othersActiveMembers addObject:member]; + } + + [activeMembers addObject:member]; + } + } + + // sort the members by their creation (oldest first) + othersActiveMembers = [[othersActiveMembers sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + + uint64_t originServerTs1 = 0; + uint64_t originServerTs2 = 0; + + MXRoomMember* member1 = (MXRoomMember*)obj1; + MXRoomMember* member2 = (MXRoomMember*)obj2; + + if (member1.originalEvent) + { + originServerTs1 = member1.originalEvent.originServerTs; + } + + if (member2.originalEvent) + { + originServerTs2 = member2.originalEvent.originServerTs; + } + + if (originServerTs1 == originServerTs2) + { + return NSOrderedSame; + } + else + { + return originServerTs1 > originServerTs2 ? NSOrderedDescending : NSOrderedAscending; + } + }] mutableCopy]; + + + NSString* displayName = @""; + + if (othersActiveMembers.count == 0) + { + if (activeMembers.count == 1) + { + MXRoomMember* member = [activeMembers objectAtIndex:0]; + + if (member.membership == MXMembershipInvite) + { + if (member.originalEvent.sender) + { + // extract who invited us to the room + displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_invite_from", @"Vector", nil), [roomState memberName:member.originalEvent.sender]]; + } + else + { + displayName = NSLocalizedStringFromTable(@"room_displayname_room_invite", @"Vector", nil); + } + } + } + } + else if (othersActiveMembers.count == 1) + { + MXRoomMember* member = [othersActiveMembers objectAtIndex:0]; + + displayName = [roomState memberName:member.userId]; + } + else if (othersActiveMembers.count == 2) + { + MXRoomMember* member1 = [othersActiveMembers objectAtIndex:0]; + MXRoomMember* member2 = [othersActiveMembers objectAtIndex:1]; + + displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_two_members", @"Vector", nil), [roomState memberName:member1.userId], [roomState memberName:member2.userId]]; + } + else + { + MXRoomMember* member = [othersActiveMembers objectAtIndex:0]; + displayName = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_displayname_more_than_two_members", @"Vector", nil), [roomState memberName:member.userId], othersActiveMembers.count - 1]; + } + + return displayName; +} + +#pragma mark - Riot room avatar url + +- (NSString *)riotRoomAvatarURLFromRoomState:(MXRoomState *)roomState +{ + NSString* roomAvatarUrl = roomState.avatar; + + if (!roomAvatarUrl) + { + // If the room has only two members, use the avatar of the second member. + NSArray* members = roomState.members; + + if (members.count == 2) + { + NSString* myUserId = mxSession.myUser.userId; + + for (MXRoomMember *roomMember in members) + { + if (![roomMember.userId isEqualToString:myUserId]) + { + // Use the avatar of this member only if he joined or he is invited. + if (MXMembershipJoin == roomMember.membership || MXMembershipInvite == roomMember.membership) + { + roomAvatarUrl = roomMember.avatarUrl; + } + break; + } + } + } + } + + return roomAvatarUrl; +} #pragma mark - Timestamp formatting diff --git a/Riot/ViewController/CallViewController.m b/Riot/ViewController/CallViewController.m index 431e0da4c..9e55b5cb7 100644 --- a/Riot/ViewController/CallViewController.m +++ b/Riot/ViewController/CallViewController.m @@ -21,8 +21,6 @@ #import "AvatarGenerator.h" -#import "MXRoom+Riot.h" - #import "UsersDevicesViewController.h" #import "RiotNavigationController.h" @@ -359,7 +357,7 @@ } else if (self.mxCall.room) { - return [AvatarGenerator generateAvatarForMatrixItem:self.mxCall.room.roomId withDisplayName:self.mxCall.room.riotDisplayname size:self.callerImageViewWidthConstraint.constant andFontSize:fontSize]; + return [AvatarGenerator generateAvatarForMatrixItem:self.mxCall.room.roomId withDisplayName:self.mxCall.room.summary.displayname size:self.callerImageViewWidthConstraint.constant andFontSize:fontSize]; } return [UIImage imageNamed:@"placeholder"]; @@ -388,7 +386,7 @@ } else if (self.mxCall.isConferenceCall) { - peerDisplayName = self.mxCall.room.riotDisplayname; + peerDisplayName = self.mxCall.room.summary.displayname; peerAvatarURL = self.mxCall.room.state.avatar; } diff --git a/Riot/ViewController/RoomSettingsViewController.m b/Riot/ViewController/RoomSettingsViewController.m index 083304a3b..68d3ff632 100644 --- a/Riot/ViewController/RoomSettingsViewController.m +++ b/Riot/ViewController/RoomSettingsViewController.m @@ -26,6 +26,7 @@ #import "Tools.h" #import "MXRoom+Riot.h" +#import "MXRoomSummary+Riot.h" #import "AppDelegate.h" @@ -1998,7 +1999,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti } else { - [mxRoom setRoomAvatarImageIn:roomPhotoCell.mxkImageView]; + [mxRoom.summary setRoomAvatarImageIn:roomPhotoCell.mxkImageView]; roomPhotoCell.userInteractionEnabled = (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomAvatar]); roomPhotoCell.mxkImageView.alpha = roomPhotoCell.userInteractionEnabled ? 1.0f : 0.5f; diff --git a/Riot/Views/RoomList/RecentTableViewCell.m b/Riot/Views/RoomList/RecentTableViewCell.m index 8fbdda0cc..b0bf69a75 100644 --- a/Riot/Views/RoomList/RecentTableViewCell.m +++ b/Riot/Views/RoomList/RecentTableViewCell.m @@ -23,7 +23,7 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" +#import "MXRoomSummary+Riot.h" @implementation RecentTableViewCell @@ -143,7 +143,7 @@ self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; - [roomCellData.roomSummary.room setRoomAvatarImageIn:self.roomAvatar]; + [roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar]; } else { diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index b47a2a8cd..e5492ac38 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -20,7 +20,7 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" +#import "MXRoomSummary+Riot.h" #import "MXTools.h" @@ -171,7 +171,7 @@ self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; - [roomCellData.roomSummary.room setRoomAvatarImageIn:self.roomAvatar]; + [roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar]; } } diff --git a/Riot/Views/RoomList/RoomTableViewCell.m b/Riot/Views/RoomList/RoomTableViewCell.m index 938038c77..4f33d2821 100644 --- a/Riot/Views/RoomList/RoomTableViewCell.m +++ b/Riot/Views/RoomList/RoomTableViewCell.m @@ -18,7 +18,7 @@ #import "RoomTableViewCell.h" #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" +#import "MXRoomSummary+Riot.h" @implementation RoomTableViewCell @@ -50,9 +50,9 @@ - (void)render:(MXRoom *)room { - [room setRoomAvatarImageIn:self.avatarImageView]; + [room.summary setRoomAvatarImageIn:self.avatarImageView]; - self.titleLabel.text = room.riotDisplayname; + self.titleLabel.text = room.summary.displayname; self.directRoomBorderView.hidden = !room.isDirect; diff --git a/Riot/Views/RoomTitle/ExpandedRoomTitleView.m b/Riot/Views/RoomTitle/ExpandedRoomTitleView.m index de0048a37..90ce7613b 100644 --- a/Riot/Views/RoomTitle/ExpandedRoomTitleView.m +++ b/Riot/Views/RoomTitle/ExpandedRoomTitleView.m @@ -19,8 +19,6 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" - @implementation ExpandedRoomTitleView + (UINib *)nib @@ -38,7 +36,7 @@ { [super customizeViewRendering]; - self.displayNameTextField.textColor = (self.mxRoom.riotDisplayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); + self.displayNameTextField.textColor = (self.mxRoom.summary.displayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); self.roomTopic.textColor = kRiotTopicTextColor; self.roomMembers.textColor = kRiotColorGreen; } @@ -49,7 +47,7 @@ if (self.mxRoom) { - self.displayNameTextField.text = self.mxRoom.riotDisplayname; + self.displayNameTextField.text = self.mxRoom.summary.displayname; if (!self.displayNameTextField.text.length) { self.displayNameTextField.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Views/RoomTitle/PreviewRoomTitleView.m b/Riot/Views/RoomTitle/PreviewRoomTitleView.m index 53ab2b615..c41a2da1e 100644 --- a/Riot/Views/RoomTitle/PreviewRoomTitleView.m +++ b/Riot/Views/RoomTitle/PreviewRoomTitleView.m @@ -19,8 +19,6 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" - @implementation PreviewRoomTitleView + (UINib *)nib @@ -64,7 +62,7 @@ self.backgroundColor = kRiotPrimaryBgColor; self.mainHeaderBackground.backgroundColor = kRiotSecondaryBgColor; - self.displayNameTextField.textColor = (self.mxRoom.riotDisplayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); + self.displayNameTextField.textColor = (self.mxRoom.summary.displayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); self.roomTopic.textColor = kRiotTopicTextColor; self.roomTopic.numberOfLines = 0; @@ -145,7 +143,7 @@ else if (self.mxRoom) { // The user is here invited to join a room (This invitation has been received from server sync) - self.displayNameTextField.text = self.mxRoom.riotDisplayname; + self.displayNameTextField.text = self.mxRoom.summary.displayname; if (!self.displayNameTextField.text.length) { self.displayNameTextField.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Views/RoomTitle/RoomAvatarTitleView.m b/Riot/Views/RoomTitle/RoomAvatarTitleView.m index b1cd35495..a9a6bb2ea 100644 --- a/Riot/Views/RoomTitle/RoomAvatarTitleView.m +++ b/Riot/Views/RoomTitle/RoomAvatarTitleView.m @@ -19,7 +19,7 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" +#import "MXRoomSummary+Riot.h" @implementation RoomAvatarTitleView @@ -71,7 +71,7 @@ if (self.mxRoom) { - [self.mxRoom setRoomAvatarImageIn:self.roomAvatar]; + [self.mxRoom.summary setRoomAvatarImageIn:self.roomAvatar]; } else if (self.roomAvatarURL) { diff --git a/Riot/Views/RoomTitle/RoomTitleView.m b/Riot/Views/RoomTitle/RoomTitleView.m index 6010a3b86..2b856af2c 100644 --- a/Riot/Views/RoomTitle/RoomTitleView.m +++ b/Riot/Views/RoomTitle/RoomTitleView.m @@ -19,8 +19,6 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" - @implementation RoomTitleView + (UINib *)nib @@ -111,7 +109,7 @@ { [super customizeViewRendering]; - self.displayNameTextField.textColor = (self.mxRoom.riotDisplayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); + self.displayNameTextField.textColor = (self.mxRoom.summary.displayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); } - (void)setRoomPreviewData:(RoomPreviewData *)roomPreviewData @@ -132,7 +130,7 @@ } else if (self.mxRoom) { - self.displayNameTextField.text = self.mxRoom.riotDisplayname; + self.displayNameTextField.text = self.mxRoom.summary.displayname; if (!self.displayNameTextField.text.length) { self.displayNameTextField.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Views/RoomTitle/SimpleRoomTitleView.m b/Riot/Views/RoomTitle/SimpleRoomTitleView.m index a18bf94f3..982784428 100644 --- a/Riot/Views/RoomTitle/SimpleRoomTitleView.m +++ b/Riot/Views/RoomTitle/SimpleRoomTitleView.m @@ -19,8 +19,6 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" - @implementation SimpleRoomTitleView + (UINib *)nib @@ -70,7 +68,7 @@ { [super customizeViewRendering]; - self.displayNameTextField.textColor = (self.mxRoom.riotDisplayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); + self.displayNameTextField.textColor = (self.mxRoom.summary.displayname.length ? kRiotPrimaryTextColor : kRiotSecondaryTextColor); } - (void)refreshDisplay @@ -79,7 +77,7 @@ if (self.mxRoom) { - self.displayNameTextField.text = self.mxRoom.riotDisplayname; + self.displayNameTextField.text = self.mxRoom.summary.displayname; if (!self.displayNameTextField.text.length) { self.displayNameTextField.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Views/Search/MessagesSearchResultAttachmentBubbleCell.m b/Riot/Views/Search/MessagesSearchResultAttachmentBubbleCell.m index be6a4424c..4968f2496 100644 --- a/Riot/Views/Search/MessagesSearchResultAttachmentBubbleCell.m +++ b/Riot/Views/Search/MessagesSearchResultAttachmentBubbleCell.m @@ -19,8 +19,6 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" - @implementation MessagesSearchResultAttachmentBubbleCell - (void)customizeTableViewCellRendering @@ -43,7 +41,7 @@ MXRoom* room = [bubbleData.mxSession roomWithRoomId:bubbleData.roomId]; if (room) { - self.roomNameLabel.text = room.riotDisplayname; + self.roomNameLabel.text = room.summary.displayname; if (!self.roomNameLabel.text.length) { self.roomNameLabel.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); diff --git a/Riot/Views/Search/MessagesSearchResultTextMsgBubbleCell.m b/Riot/Views/Search/MessagesSearchResultTextMsgBubbleCell.m index 364e93cde..8359cc26e 100644 --- a/Riot/Views/Search/MessagesSearchResultTextMsgBubbleCell.m +++ b/Riot/Views/Search/MessagesSearchResultTextMsgBubbleCell.m @@ -19,8 +19,6 @@ #import "RiotDesignValues.h" -#import "MXRoom+Riot.h" - @implementation MessagesSearchResultTextMsgBubbleCell - (void)customizeTableViewCellRendering @@ -43,7 +41,7 @@ MXRoom* room = [bubbleData.mxSession roomWithRoomId:bubbleData.roomId]; if (room) { - self.roomNameLabel.text = room.riotDisplayname; + self.roomNameLabel.text = room.summary.displayname; } else { diff --git a/RiotShareExtension/ViewController/RoomsListViewController.m b/RiotShareExtension/ViewController/RoomsListViewController.m index 709277a09..7322674d5 100644 --- a/RiotShareExtension/ViewController/RoomsListViewController.m +++ b/RiotShareExtension/ViewController/RoomsListViewController.m @@ -130,7 +130,8 @@ - (void)showShareAlertForRoomPath:(NSIndexPath *)indexPath { - NSString *receipantName = [self.dataSource getRoomAtIndexPath:indexPath].riotDisplayname; + // @TODO: the room should be instanciated here (only the room summary should be available from dataSource). + NSString *receipantName = [self.dataSource getRoomAtIndexPath:indexPath].summary.displayname; if (!receipantName.length) { receipantName = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); From 59e6e3f7ae81baae836f266a365bdf26af3a0229 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 14 Sep 2017 16:02:08 +0200 Subject: [PATCH 16/30] Update xcodeproj --- Riot.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 09d978488..e7605aacf 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -491,6 +491,7 @@ F0B4CBB11F4215E3008E99C5 /* EventDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = F0B4CBAF1F4215E3008E99C5 /* EventDetailsView.m */; }; F0B4CBB21F4215E3008E99C5 /* EventDetailsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0B4CBB01F4215E3008E99C5 /* EventDetailsView.xib */; }; F0B7A8B11F475783006E27D2 /* RoomsListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0B7A8AF1F4756A5006E27D2 /* RoomsListViewController.xib */; }; + F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F0D2ADA01F6AA5FD00A7097D /* MXRoomSummary+Riot.m */; }; F0D869EB1EC455A100BB0A2B /* create_direct_chat.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869E81EC455A100BB0A2B /* create_direct_chat.png */; }; F0D869EC1EC455A100BB0A2B /* create_direct_chat@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869E91EC455A100BB0A2B /* create_direct_chat@2x.png */; }; F0D869ED1EC455A100BB0A2B /* create_direct_chat@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869EA1EC455A100BB0A2B /* create_direct_chat@3x.png */; }; @@ -1188,6 +1189,8 @@ F0B4CBAF1F4215E3008E99C5 /* EventDetailsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EventDetailsView.m; sourceTree = ""; }; F0B4CBB01F4215E3008E99C5 /* EventDetailsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EventDetailsView.xib; sourceTree = ""; }; F0B7A8AF1F4756A5006E27D2 /* RoomsListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomsListViewController.xib; sourceTree = ""; }; + F0D2AD9F1F6AA5FD00A7097D /* MXRoomSummary+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXRoomSummary+Riot.h"; sourceTree = ""; }; + F0D2ADA01F6AA5FD00A7097D /* MXRoomSummary+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXRoomSummary+Riot.m"; sourceTree = ""; }; F0D869E81EC455A100BB0A2B /* create_direct_chat.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = create_direct_chat.png; sourceTree = ""; }; F0D869E91EC455A100BB0A2B /* create_direct_chat@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "create_direct_chat@2x.png"; sourceTree = ""; }; F0D869EA1EC455A100BB0A2B /* create_direct_chat@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "create_direct_chat@3x.png"; sourceTree = ""; }; @@ -1749,6 +1752,8 @@ F083BBE61E7009EC00A9B29C /* MXKRoomBubbleTableViewCell+Riot.m */, F083BBE71E7009EC00A9B29C /* MXRoom+Riot.h */, F083BBE81E7009EC00A9B29C /* MXRoom+Riot.m */, + F0D2AD9F1F6AA5FD00A7097D /* MXRoomSummary+Riot.h */, + F0D2ADA01F6AA5FD00A7097D /* MXRoomSummary+Riot.m */, F083BBE91E7009EC00A9B29C /* UINavigationController+Riot.h */, F083BBEA1E7009EC00A9B29C /* UINavigationController+Riot.m */, F083BBEB1E7009EC00A9B29C /* UIViewController+RiotSearch.h */, @@ -3068,6 +3073,7 @@ F083BE6E1E7009ED00A9B29C /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, 3233F7311F31F4BF006ACA81 /* JitsiViewController.m in Sources */, F083BE2A1E7009ED00A9B29C /* UsersDevicesViewController.m in Sources */, + F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */, F083BE2D1E7009ED00A9B29C /* ForgotPasswordInputsView.m in Sources */, F083BE7E1E7009ED00A9B29C /* InviteRecentTableViewCell.m in Sources */, F083BE3C1E7009ED00A9B29C /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, From c2e86317fa0cb921aecb0391f58631974d5a993d Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 14 Sep 2017 17:48:11 +0200 Subject: [PATCH 17/30] Modular integrations UI: Did all modular requests but not tested --- .../IntegrationManagerViewController.m | 246 +++++++++++++++++- 1 file changed, 240 insertions(+), 6 deletions(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 7c890f25c..71a399c8a 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -358,14 +358,115 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; { NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId); - NSAssert(NO, @"TODO"); + MXRoom *room = [mxSession roomWithRoomId:roomId]; + if (!room) + { + MXRoomMember *member = [room.state memberWithUserId:userId]; + if (member && member.membership == MXMembershipJoin) + { + [self sendNSObjectResponse:@{ + @"success": @(YES) + } + toEvent:eventData]; + } + else + { + __weak __typeof__(self) weakSelf = self; + + [room inviteUser:userId success:^{ + + typeof(self) self = weakSelf; + if (self) + { + [self sendNSObjectResponse:@{ + @"success": @(YES) + } + toEvent:eventData]; + } + + } failure:^(NSError *error) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + } + }]; + } + } } - (void)setWidget:(NSDictionary*)eventData { NSLog(@"[IntegrationManagerVC] Received request to set widget in room %@.", roomId); - NSAssert(NO, @"TODO"); + MXRoom *room = [self roomCheckWithEvent:eventData]; + + if (room) + { + NSString *widget_id, *widgetType, *widgetUrl; + NSString *widgetName; // optional + NSDictionary *widgetData ; // optional + + MXJSONModelSetString(widget_id, eventData[@"widget_id"]); + MXJSONModelSetString(widgetType, eventData[@"type"]); + MXJSONModelSetString(widgetUrl, eventData[@"url"]); + MXJSONModelSetString(widgetName, eventData[@"name"]); + MXJSONModelSetDictionary(widgetData, eventData[@"data"]); + + if (!widget_id) + { + [self sendLocalisedError:@"widget_integration_unable_to_create" toEvent:eventData]; // new Error("Missing required widget fields.")); + return; + } + + NSMutableDictionary *widgetEventContent = [NSMutableDictionary dictionary]; + if (widgetUrl) + { + if (!widgetType) + { + [self sendLocalisedError:@"widget_integration_unable_to_create" toEvent:eventData]; + return; + } + + widgetEventContent[@"type"] = widgetType; + widgetEventContent[@"url"] = widgetUrl; + + if (widgetName) + { + widgetEventContent[@"name"] = widgetName; + } + if (widgetData) + { + widgetEventContent[@"data"] = widgetData; + } + } + + __weak __typeof__(self) weakSelf = self; + + [room sendStateEventOfType:kWidgetEventTypeString + content:widgetEventContent + stateKey:widget_id + success:^(NSString *eventId) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendNSObjectResponse:@{ + @"success": @(YES) + } + toEvent:eventData]; + } + } + failure:^(NSError *error) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + } + }]; + } } - (void)getWidgets:(NSDictionary*)eventData @@ -458,28 +559,161 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; { NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", eventData[@"status"], roomId); - NSAssert(NO, @"TODO"); + MXRoom *room = [self roomCheckWithEvent:eventData]; + if (room) + { + NSString *status; + MXJSONModelSetString(status, eventData[@"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) + } + toEvent:eventData]; + } + } + failure:^(NSError *error) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + } + }]; + } + else + { + NSLog(@"[IntegrationManagerVC] setPlumbingState. Error: Plumbing state status should be a string."); + } + } } - (void)getBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData { NSLog(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId); - NSAssert(NO, @"TODO"); + MXRoom *room = [self roomCheckWithEvent:eventData]; + if (room) + { + NSString *stateKey = [NSString stringWithFormat:@"_%@", userId]; + + NSArray *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 toEvent:eventData]; + } } - (void)setBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData { NSLog(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId); - NSAssert(NO, @"TODO"); + MXRoom *room = [self roomCheckWithEvent:eventData]; + if (room) + { + NSDictionary *content; + MXJSONModelSetDictionary(content, eventData[@"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) + } + toEvent:eventData]; + } + } + failure:^(NSError *error) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + } + }]; + } + else + { + NSLog(@"[IntegrationManagerVC] setBotOptions. Error: options should be a dict."); + } + } } - (void)setBotPower:(NSString*)userId eventData:(NSDictionary*)eventData { NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", eventData[@"level"], userId, roomId); - NSAssert(NO, @"TODO"); + MXRoom *room = [self roomCheckWithEvent:eventData]; + if (room) + { + NSInteger level = -1; + MXJSONModelSetInteger(level, eventData[@"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) + } + toEvent:eventData]; + } + + } failure:^(NSError *error) { + + typeof(self) self = weakSelf; + if (self) + { + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + } + }]; + } + else + { + NSLog(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer."); + } + } } - (void)getMembershipCount:(NSDictionary*)eventData From 8cd0dcb74b8866e476142c11fc3acfe5ba8f31d1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 14 Sep 2017 17:52:21 +0200 Subject: [PATCH 18/30] Modular integrations UI: Clean i18n strings for modular --- Riot/Assets/en.lproj/Vector.strings | 1 - Riot/ViewController/Widgets/IntegrationManagerViewController.m | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index e5067e691..0937102d3 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -511,7 +511,6 @@ "widget_creation_failure" = "Widget creation has failed"; // Widget Integration Manager -"widget_integration_need_login" = "You need to be logged in."; "widget_integration_need_to_be_able_to_invite" = "You need to be able to invite users to do that."; "widget_integration_unable_to_create" = "Unable to create widget."; "widget_integration_failed_to_send_request" = "Failed to send request."; diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 71a399c8a..92add56be 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -389,7 +389,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toEvent:eventData]; } }]; } @@ -712,6 +712,7 @@ NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; else { NSLog(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer."); + [self sendLocalisedError:@"widget_integration_positive_power_level" toEvent:eventData]; } } } From 8a1a3889b5904a267bf7efa01f0dee667703a034 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 13:24:01 +0200 Subject: [PATCH 19/30] Modular integrations UI: Add Settings > Labs > Matrix Apps --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/ViewController/SettingsViewController.m | 30 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0937102d3..6a0650869 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -350,6 +350,7 @@ "settings_labs_e2e_encryption" = "End-to-End Encryption"; "settings_labs_e2e_encryption_prompt_message" = "To finish setting up encryption you must log in again."; +"settings_labs_matrix_apps" = "Matrix Apps"; "settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi"; "settings_version" = "Version %@"; diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index b37d1e6e0..694b25f4d 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -98,8 +98,9 @@ enum enum { -#ifdef USE_JITSI_WIDGET LABS_MATRIX_APPS_INDEX = 0, +#ifdef USE_JITSI_WIDGET + LABS_USE_JITSI_WIDGET_INDEX, #endif LABS_CRYPTO_INDEX, LABS_COUNT @@ -1916,11 +1917,23 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); } else if (section == SETTINGS_SECTION_LABS_INDEX) { -#ifdef USE_JITSI_WIDGET if (row == LABS_MATRIX_APPS_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_matrix_apps", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"matrixApps"]; + + [labelAndSwitchCell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsMatrixApps:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } +#ifdef USE_JITSI_WIDGET + else if (row == LABS_USE_JITSI_WIDGET_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_create_conference_with_jitsi", @"Vector", nil); labelAndSwitchCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"createConferenceCallsWithJitsi"]; @@ -2665,6 +2678,19 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); } } +- (void)toggleLabsMatrixApps:(id)sender +{ + if (sender && [sender isKindOfClass:UISwitch.class]) + { + UISwitch *switchButton = (UISwitch*)sender; + + [[NSUserDefaults standardUserDefaults] setBool:switchButton.isOn forKey:@"matrixApps"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [self.tableView reloadData]; + } +} + - (void)toggleJitsiForConference:(id)sender { if (sender && [sender isKindOfClass:UISwitch.class]) From 1085b4038b6206d1fbfcbb856a22a88c7861d7ac Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 13:27:13 +0200 Subject: [PATCH 20/30] Modular integrations UI: Add the integrations icon to the RoomVC navigation header --- Riot/Assets/Images/apps-icon.png | Bin 0 -> 578 bytes Riot/Assets/Images/apps-icon@2x.png | Bin 0 -> 977 bytes Riot/Assets/Images/apps-icon@3x.png | Bin 0 -> 1043 bytes Riot/Base.lproj/Main.storyboard | 21 +++++--- Riot/ViewController/RoomViewController.m | 62 +++++++++++++++++------ 5 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 Riot/Assets/Images/apps-icon.png create mode 100644 Riot/Assets/Images/apps-icon@2x.png create mode 100644 Riot/Assets/Images/apps-icon@3x.png diff --git a/Riot/Assets/Images/apps-icon.png b/Riot/Assets/Images/apps-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a2751b1eb0d391608783365352c05773f2973feb GIT binary patch literal 578 zcmV-I0=@l-P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2jK}1 z2{R)dVn1yF00F{DL_t(Y$L*HAYZOrw#eZjZRJysM8A*Z|ihPm_Jx#u$K z4F@gY2q8%W?0vuq!n%#Lh24$tCn<0kxC}h4z>_jdh9+yZYVsnD6&s-RXhu$qdk3e%xhjD+H=Vp1$l$-(MxPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2jK}1 z2{RenZsy+r00U7;L_t(&-tC&tYgAVh$3OSI`)1Nap?`pdf*1q~Iv89Ov$A$8q6~DR zv?7JlZkk0WU9{cUjnjp0EQL~3G*cMtrW6WQR}l*(VJf6h41&=70YZM5%$@g+3t!|p zHt)WZ)`r46tGRReoO{2+%)RG(-k8a|9aE{e5o3r@#QIRQbYWk;574}K?UkS(3~Bhp zCTp$zrym1M{?W13HMdj4i{N^_XvIb&ZdelYnZQ9!g2=^_!2HwQg!Lrg6eM26 zYi|;m3+Q4l8gb{WF=7Q%1K!fmMtC(d+^yw-U$ePH&nL!+wF7T{2i)<1mviQ3flD6# zBVa6N{)~q|44mB<3<{WO)f!jx=w`Ql2^ijF`hKg{xZ>fD<_K6Vz)My@!2di-0i}Ra zKq+8*WAGlhoSyS}F`P5kXm;DjJp4#r?q;{WtXY3hfblF)B>q$0(yJ^>=st$$Y8!to z6x;bsVf3vCKdX4d;}5XAAcmB>_ysRM7T&fmcN1UyXb(Ks-rTVHy> z%{1385mEB|B9XZ$$hu;Gn+Uywpx_}=zo^u!c(5f{~ zd3Xafa{Pdge>W?jEG0?-rGQdEDd1oA10SY+o2~kR6KUTjFHU&;!1V$b*`@*xWiN>> zMAdUs7YP+_jgxf==>S~~n8dP}Deh-L&MS+#nP<5YSu`O^o(1D%y{yN#-49I7cETid zqgqQD#L@iuvHH?eKQR582)neFvUOnMFMz4p&dwxsZ)h!L5J&edmEH~$2N$(|fG{3s zqZKdaeFLBC>;UJ0FVbB5P3AXI)DKYU)$I#uVf7z&4OU(Q@{_bgyziN15RR;FfZu51z{-HCI=CfP(8ij#i%lzuX|PNwW=y00000NkvXXu0mjfSxm~n literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images/apps-icon@3x.png b/Riot/Assets/Images/apps-icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..cbad4c960a69632ba54a5b4f87d8a613098821e5 GIT binary patch literal 1043 zcmV+u1nm2XP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2jK}1 z2{JkP8~0WK00WarL_t(|+U=arYEwZJ#=mnXxv@~VrnnXaK~T`v76j3S8}SK(ZRySz zaN$h?;zES>#)U87P7)rn0#~R zH#3(x_sj%P{Z<43ci%S03WfgpP^Nn)@>@?Xl-k2(>HBBrqCqqs!r$fGm)fO;jp4Gp zADa_}ul+N@`mmS(^yF%3HwOUmc<-#U(Q+vBxM$}9G=A)rdFmou3(|lyZvb5Xsm)t1 zqI$4CjQ1`A_z>wL`n?p;ACxu{E7+YE}d&U2Aj!Bwi2C=qz2F?@0X1tBnqmclEB=f4S*k2D-%b9i)I1kOERb z3P=H|6p#W^K*tuWvKh4ZV=IXg--=9FOxGHz#GHYmx_93 z%9JhcKc1d@Ib4>uo<9Y6GSFkUYLl~fhD+|PG;Wc>qhLE|Yi}=2FKp%jfb%p4i|JtI z5shXPn9l*B+Th)T+GOTnec(Ke=|1SNp$33O5X(XhCP|)a2PDgjfvE!Er2i`9iqJm+ zQ1t>tHkV~HC23x)>Kl&{Bg>}dGl_qBwLwf+^sL^kO$z;&n+cmiRYMJp#T1YNQa}nw z0VyDr0#ZN<=(vJaHiL57stbcvQC_T^w(2ahXOsoileASY%lu_|u_^>7CJ&*L!suVF zK>oVKe3lsXd6x}VLYX?tqiJ8F4S>hOv_KmpZ8~mKz&=oa^sA*xrBeNE5%B$ctTX+a z!FHcE|1O&;{gC`mTlE_u4=>av=U)$(m6w~Ro#|f**oRlyOsV^Q9~AdalZ~3eu$u!e z)G<9I1iDbi7`adfW3LgyU8qB>7wQflY5;Hmq(|_iC%QWSn16E6#`#xyv57KDz^I|(1YG^E`fE17dQa}nw0jU&_0#ZQ76|9o6#mahJ z1kYLjyqF^tE9-Sp@M$fIf<#mz^@p_y`vP*{E!d;WixH4IOR4ZTkPR5MWxrruV)_m` zs&`QDOT1Yq^k+hu?w!bOjShvq6Y@{qelF|9=4Ih)z&`B8n>sx_^bJaGLBl(S2%i7| N002ovPDHLkV1h5L-CF - + + @@ -36,7 +37,14 @@ - + + + + + + + + @@ -564,6 +572,7 @@ + @@ -577,10 +586,10 @@ - - - - + + + + diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 8bb9a5345..789237d77 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -109,6 +109,8 @@ #import "MXRoom+Riot.h" +#import "IntegrationManagerViewController.h" + @interface RoomViewController () { // The expanded header @@ -180,9 +182,9 @@ // Tell whether the view controller is appeared or not. BOOL isAppeared; - // The search bar buttom item back up. - UIBarButtonItem *searchBarButtonItem; - + // The right bar button items back up. + NSArray *rightBarButtonItems; + // Observe kRiotDesignValuesDidChangeThemeNotification to handle user interface theme change. id kRiotDesignValuesDidChangeThemeNotificationObserver; } @@ -360,8 +362,11 @@ [self setEventDetailsViewClass:EventDetailsView.class]; // Update navigation bar items - self.navigationItem.rightBarButtonItem.target = self; - self.navigationItem.rightBarButtonItem.action = @selector(onButtonPressed:); + for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) + { + barButtonItem.target = self; + barButtonItem.action = @selector(onButtonPressed:); + } // Prepare missed dicussion badge missedDiscussionsBarButtonCustomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 21)]; @@ -1046,8 +1051,11 @@ - (void)destroy { - searchBarButtonItem = nil; - self.navigationItem.rightBarButtonItem.enabled = NO; + rightBarButtonItems = nil; + for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) + { + barButtonItem.enabled = NO; + } if (currentAlert) { @@ -1150,17 +1158,20 @@ - (void)refreshRoomTitle { - if (searchBarButtonItem && !self.navigationItem.rightBarButtonItem) + if (rightBarButtonItems && !self.navigationItem.rightBarButtonItems) { // Restore by default the search bar button. - self.navigationItem.rightBarButtonItem = searchBarButtonItem; + self.navigationItem.rightBarButtonItems = rightBarButtonItems; } // Set the right room title view if (self.isRoomPreview) { - // Disable the search button - self.navigationItem.rightBarButtonItem.enabled = NO; + // Disable the right buttons + for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) + { + barButtonItem.enabled = NO; + } [self showPreviewHeader:YES]; } @@ -1170,8 +1181,18 @@ if (self.roomDataSource.isLive) { - // Enable the search button - self.navigationItem.rightBarButtonItem.enabled = YES; + // Enable the right buttons (Search and Integrations) + for (UIBarButtonItem *barButtonItem in self.navigationItem.rightBarButtonItems) + { + barButtonItem.enabled = YES; + } + + BOOL matrixAppsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"matrixApps"]; + if (!matrixAppsEnabled && self.navigationItem.rightBarButtonItems.count == 2) + { + // If the setting is disabled, do not show the icon + self.navigationItem.rightBarButtonItems = @[self.navigationItem.rightBarButtonItem]; + } // Do not change title view class here if the expanded header is visible. if (self.expandedHeaderContainer.hidden) @@ -1191,8 +1212,8 @@ else { // Remove the search button temporarily - searchBarButtonItem = self.navigationItem.rightBarButtonItem; - self.navigationItem.rightBarButtonItem = nil; + rightBarButtonItems = self.navigationItem.rightBarButtonItems; + self.navigationItem.rightBarButtonItems = nil; [self setRoomTitleViewClass:SimpleRoomTitleView.class]; self.titleView.editable = NO; @@ -2850,10 +2871,21 @@ - (IBAction)onButtonPressed:(id)sender { + // Search button if (sender == self.navigationItem.rightBarButtonItem) { [self performSegueWithIdentifier:@"showRoomSearch" sender:self]; } + // Matrix Apps button + else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) + { + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:@"add_integ" + widgetId:nil]; + + [self.navigationController pushViewController:modularVC animated:YES]; + } else if (sender == self.jumpToLastUnreadButton) { // Hide expanded header to restore navigation bar settings. From 734837955f0c7d00ce6c1791dab31e2d31c1ab38 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 13:36:26 +0200 Subject: [PATCH 21/30] Modular integrations UI: Make the integrations icon open the main "Manage integrations" page --- Riot/ViewController/RoomViewController.m | 4 ++-- .../Widgets/IntegrationManagerViewController.h | 3 +++ .../Widgets/IntegrationManagerViewController.m | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 789237d77..7ee9c2c14 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -367,7 +367,7 @@ barButtonItem.target = self; barButtonItem.action = @selector(onButtonPressed:); } - + // Prepare missed dicussion badge missedDiscussionsBarButtonCustomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 21)]; missedDiscussionsBarButtonCustomView.backgroundColor = [UIColor clearColor]; @@ -2881,7 +2881,7 @@ { IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId - screen:@"add_integ" + screen:kIntegrationManagerMainScreen widgetId:nil]; [self.navigationController pushViewController:modularVC animated:YES]; diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.h b/Riot/ViewController/Widgets/IntegrationManagerViewController.h index 7e8483de0..213147720 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.h +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.h @@ -18,6 +18,9 @@ #import +FOUNDATION_EXPORT NSString *const kIntegrationManagerMainScreen; +FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; + /** `IntegrationManagerViewController` displays the Modular integration manager webapp into a webview. diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 92add56be..413e8c007 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -20,7 +20,10 @@ #import -NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; +NSString *const kIntegrationManagerMainScreen = nil; +NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; + +const NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; @interface IntegrationManagerViewController () From 9c969091a022436f0cac459a97749a839e0366fa Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 13:55:33 +0200 Subject: [PATCH 22/30] Modular integrations UI: Display the webpage in fullscreen --- Riot/ViewController/RoomViewController.m | 2 +- .../Widgets/IntegrationManagerViewController.m | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 7ee9c2c14..7bbd7d4ac 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2884,7 +2884,7 @@ screen:kIntegrationManagerMainScreen widgetId:nil]; - [self.navigationController pushViewController:modularVC animated:YES]; + [self presentViewController:modularVC animated:NO completion:nil]; } else if (sender == self.jumpToLastUnreadButton) { diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 413e8c007..a70df66dd 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -203,6 +203,11 @@ const NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', MXJSONModelSetString(userId, eventData[@"user_id"]); MXJSONModelSetString(action, eventData[@"action"]); + if ([action isEqualToString:@"close_scalar"]) + { + [self withdrawViewControllerAnimated:YES completion:nil]; + } + if (!roomIdInEvent) { [self sendLocalisedError:@"widget_integration_missing_room_id" toEvent:eventData]; From fa670d35819cb9be4c3488f23e3e7a3ce670d49f Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 14:03:49 +0200 Subject: [PATCH 23/30] Modular integrations UI: Display the webpage in fullscreen --- Riot/ViewController/Widgets/IntegrationManagerViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index a70df66dd..169849b0b 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -206,6 +206,7 @@ const NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', if ([action isEqualToString:@"close_scalar"]) { [self withdrawViewControllerAnimated:YES completion:nil]; + return; } if (!roomIdInEvent) From d6908eaf1f856dbc04fa945f64e037c9f4c7e7d0 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 14:57:40 +0200 Subject: [PATCH 24/30] Modular integrations UI: Do not bounce for all the page --- Riot/ViewController/Widgets/IntegrationManagerViewController.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 169849b0b..f52e9fa99 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -23,7 +23,7 @@ NSString *const kIntegrationManagerMainScreen = nil; NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; -const NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; +NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; @interface IntegrationManagerViewController () @@ -67,6 +67,7 @@ const NSString *kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [super viewDidLoad]; webView.scalesPageToFit = NO; + webView.scrollView.bounces = NO; webView.delegate = self; } From 125253023bd779fb576468f10ad4260c951f352d Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 15 Sep 2017 15:19:36 +0200 Subject: [PATCH 25/30] Improvement - Create DM with Riot-bot on new account creation. https://github.com/vector-im/riot-meta/issues/94 --- .../AuthenticationViewController.m | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Riot/ViewController/AuthenticationViewController.m b/Riot/ViewController/AuthenticationViewController.m index 12ea39104..e7b6d3789 100644 --- a/Riot/ViewController/AuthenticationViewController.m +++ b/Riot/ViewController/AuthenticationViewController.m @@ -723,6 +723,27 @@ // Hide the custom server details in order to save customized inputs [self hideCustomServers:YES]; + // Create DM with Riot-bot on new account creation. + if (self.authType == MXKAuthenticationTypeRegister) + { + MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:userId]; + + [account.mxSession createRoom:nil + visibility:kMXRoomDirectoryVisibilityPrivate + roomAlias:nil + topic:nil + invite:@[@"@riot-bot:matrix.org"] + invite3PID:nil + isDirect:YES + preset:kMXRoomPresetTrustedPrivateChat + success:nil + failure:^(NSError *error) { + + NSLog(@"[AuthenticationVC] Create chat with riot-bot failed"); + + }]; + } + // Remove auth view controller on successful login if (self.navigationController) { From ac0b7e16b91e7fe6b20aac6ce42adbdac9299c03 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 15:35:27 +0200 Subject: [PATCH 26/30] Modular integrations UI: Clear Modular data on logout --- Riot/AppDelegate.m | 3 +++ Riot/Utils/Widgets/WidgetManager.h | 8 ++++++-- Riot/Utils/Widgets/WidgetManager.m | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 6228dd4f0..373f86b18 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1625,6 +1625,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Remove inApp notifications toggle change MXKAccount *account = notif.object; [account removeObserver:self forKeyPath:@"enableInAppNotifications"]; + + // Clear Modular data + [[WidgetManager sharedManager] deleteDataForUser:account.mxCredentials.userId]; // Logout the app when there is no available account if (![MXKAccountManager sharedManager].accounts.count) diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index c4791c62a..77b78340b 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -139,8 +139,12 @@ WidgetManagerErrorCode; - (void)addMatrixSession:(MXSession*)mxSession; - (void)removeMatrixSession:(MXSession*)mxSession; -// TODO -//- (void)deleteMatrixSession; +/** + Delete the data associated with an user. + +@param userId the id of the user. + */ +- (void)deleteDataForUser:(NSString*)userId; #pragma mark - Modular interface diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 6805f843c..169ca5c1e 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -339,6 +339,12 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; [failureBlockForWidgetCreation removeObjectForKey:hash]; } +- (void)deleteDataForUser:(NSString *)userId +{ + [scalarTokens removeObjectForKey:userId]; + [self save]; +} + #pragma mark - Modular interface - (NSString *)scalarTokenForMXSession:(MXSession *)mxSession @@ -418,7 +424,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; - (void)load { NSUserDefaults *userDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; - scalarTokens = [userDefaults objectForKey:@"scalarTokens"]; + scalarTokens = [NSMutableDictionary dictionaryWithDictionary:[userDefaults objectForKey:@"scalarTokens"]]; } - (void)save From c9a115ec858a918137d996b62f90b35aaffe0955 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 15 Sep 2017 15:36:43 +0200 Subject: [PATCH 27/30] Modular integrations UI: project --- Riot.xcodeproj/project.pbxproj | 36 +++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 6c62b7916..db91e1cb9 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -27,7 +27,7 @@ 24EEE5A11F23A09A00B3C705 /* RiotDesignValues.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC171E7009EC00A9B29C /* RiotDesignValues.m */; }; 24EEE5A21F23A8B400B3C705 /* MXRoom+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BBE81E7009EC00A9B29C /* MXRoom+Riot.m */; }; 24EEE5A31F23A8C300B3C705 /* AvatarGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC111E7009EC00A9B29C /* AvatarGenerator.m */; }; - 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + 24EEE5A41F24C06E00B3C705 /* (null) in Resources */ = {isa = PBXBuildFile; }; 24EEE5A81F25529600B3C705 /* cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A121EDEE65000F5DC9A /* cancel@3x.png */; }; 24EEE5A91F25529900B3C705 /* cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A111EDEE65000F5DC9A /* cancel@2x.png */; }; 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A101EDEE65000F5DC9A /* cancel.png */; }; @@ -65,6 +65,8 @@ 327382C41F276AED00356143 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382C01F276AED00356143 /* Vector.strings */; }; 32918EA91F473BDB0076CA16 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32918EA51F473BDB0076CA16 /* Localizable.strings */; }; 32918EAA1F473BDB0076CA16 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32918EA71F473BDB0076CA16 /* Vector.strings */; }; + 32935CB11F6056FD006888C8 /* IntegrationManagerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */; }; + 32935CB41F628BCE006888C8 /* IntegrationManager.js in Resources */ = {isa = PBXBuildFile; fileRef = 32935CB31F628BCE006888C8 /* IntegrationManager.js */; }; 32AE61E41F0A971B007255F4 /* RoomMembershipBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32AE61E21F0A971B007255F4 /* RoomMembershipBubbleCell.m */; }; 32AE61E51F0A971B007255F4 /* RoomMembershipBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32AE61E31F0A971B007255F4 /* RoomMembershipBubbleCell.xib */; }; 32AE61E91F0CE099007255F4 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32AE61E71F0CE099007255F4 /* RoomMembershipWithPaginationTitleBubbleCell.m */; }; @@ -74,6 +76,9 @@ 32AE61F41F0D2183007255F4 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32AE61F01F0D2183007255F4 /* Vector.strings */; }; 32D392181EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D392161EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.m */; }; 32D392191EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32D392171EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.xib */; }; + 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 */; }; 32FD0A3D1EB0CD9B0072B066 /* BugReportViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FD0A3B1EB0CD9B0072B066 /* BugReportViewController.m */; }; 32FD0A3E1EB0CD9B0072B066 /* BugReportViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32FD0A3C1EB0CD9B0072B066 /* BugReportViewController.xib */; }; 92324BE31F4F66D3009DE194 /* IncomingCallView.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324BE21F4F66D3009DE194 /* IncomingCallView.m */; }; @@ -630,6 +635,9 @@ 327382C11F276AED00356143 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Vector.strings; sourceTree = ""; }; 32918EA61F473BDB0076CA16 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Localizable.strings; sourceTree = ""; }; 32918EA81F473BDB0076CA16 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Vector.strings; sourceTree = ""; }; + 32935CAF1F6056FD006888C8 /* IntegrationManagerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegrationManagerViewController.h; sourceTree = ""; }; + 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationManagerViewController.m; sourceTree = ""; }; + 32935CB31F628BCE006888C8 /* IntegrationManager.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = IntegrationManager.js; sourceTree = ""; }; 32AE61E11F0A971B007255F4 /* RoomMembershipBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomMembershipBubbleCell.h; sourceTree = ""; }; 32AE61E21F0A971B007255F4 /* RoomMembershipBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomMembershipBubbleCell.m; sourceTree = ""; }; 32AE61E31F0A971B007255F4 /* RoomMembershipBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomMembershipBubbleCell.xib; sourceTree = ""; }; @@ -642,6 +650,9 @@ 32D392151EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryServerDetailTableViewCell.h; sourceTree = ""; }; 32D392161EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DirectoryServerDetailTableViewCell.m; sourceTree = ""; }; 32D392171EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DirectoryServerDetailTableViewCell.xib; sourceTree = ""; }; + 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 = ""; }; 32FD0A3A1EB0CD9B0072B066 /* BugReportViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BugReportViewController.h; sourceTree = ""; }; 32FD0A3B1EB0CD9B0072B066 /* BugReportViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugReportViewController.m; sourceTree = ""; }; 32FD0A3C1EB0CD9B0072B066 /* BugReportViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BugReportViewController.xib; sourceTree = ""; }; @@ -1323,6 +1334,8 @@ 3233F72D1F31F47E006ACA81 /* Widgets */ = { isa = PBXGroup; children = ( + 32935CAF1F6056FD006888C8 /* IntegrationManagerViewController.h */, + 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */, 3233F72E1F31F4BF006ACA81 /* JitsiViewController.h */, 3233F72F1F31F4BF006ACA81 /* JitsiViewController.m */, 3233F7301F31F4BF006ACA81 /* JitsiViewController.xib */, @@ -1388,6 +1401,14 @@ path = ru.lproj; sourceTree = ""; }; + 32935CB21F628B98006888C8 /* js */ = { + isa = PBXGroup; + children = ( + 32935CB31F628BCE006888C8 /* IntegrationManager.js */, + ); + path = js; + sourceTree = ""; + }; 32AE61EB1F0D2183007255F4 /* nl.lproj */ = { isa = PBXGroup; children = ( @@ -1485,8 +1506,9 @@ 327382BB1F276AED00356143 /* en.lproj */, 327382AE1F276AD200356143 /* fr.lproj */, 32AE61EB1F0D2183007255F4 /* nl.lproj */, - F083BB151E7009EC00A9B29C /* Images */, F083BBDA1E7009EC00A9B29C /* Sounds */, + F083BB151E7009EC00A9B29C /* Images */, + 32935CB21F628B98006888C8 /* js */, ); path = Assets; sourceTree = ""; @@ -1494,6 +1516,9 @@ F083BB151E7009EC00A9B29C /* Images */ = { isa = PBXGroup; children = ( + 32E84F9E1F6BD32700CA0B89 /* apps-icon.png */, + 32E84F9F1F6BD32700CA0B89 /* apps-icon@2x.png */, + 32E84FA01F6BD32700CA0B89 /* apps-icon@3x.png */, F0A4A1641EF7CB66003630DB /* members_list_icon.png */, F0A4A1651EF7CB66003630DB /* members_list_icon@2x.png */, F0A4A1661EF7CB66003630DB /* members_list_icon@3x.png */, @@ -2456,7 +2481,7 @@ 24EEE5AF1F25F0F500B3C705 /* Images.xcassets in Resources */, 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */, 24D6B35E1F3CA03E00FC7A71 /* FallbackViewController.xib in Resources */, - 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */, + 24EEE5A41F24C06E00B3C705 /* (null) in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2470,6 +2495,7 @@ F083BE1D1E7009ED00A9B29C /* RoomMemberDetailsViewController.xib in Resources */, F083BD5B1E7009ED00A9B29C /* chevron@3x.png in Resources */, F083BD8A1E7009ED00A9B29C /* file_music_icon@2x.png in Resources */, + 32E84FA31F6BD32700CA0B89 /* apps-icon@3x.png in Resources */, F083BE451E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib in Resources */, F083BD611E7009ED00A9B29C /* details_icon@3x.png in Resources */, F083BD9D1E7009ED00A9B29C /* main_alias_icon@3x.png in Resources */, @@ -2693,7 +2719,9 @@ F083BD591E7009ED00A9B29C /* chevron.png in Resources */, F0D869EC1EC455A100BB0A2B /* create_direct_chat@2x.png in Resources */, F083BDE11E7009ED00A9B29C /* video_icon@2x.png in Resources */, + 32E84FA21F6BD32700CA0B89 /* apps-icon@2x.png in Resources */, F083BDB51E7009ED00A9B29C /* priorityLow@3x.png in Resources */, + 32E84FA11F6BD32700CA0B89 /* apps-icon.png in Resources */, F083BE891E7009ED00A9B29C /* RoomMemberTitleView.xib in Resources */, F083BE3D1E7009ED00A9B29C /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, F0E05A431EA0F9EB004B83FB /* tab_rooms_selected@2x.png in Resources */, @@ -2768,6 +2796,7 @@ F083BD961E7009ED00A9B29C /* leave@2x.png in Resources */, F0E05A461EA0F9EB004B83FB /* tab_rooms@2x.png in Resources */, 32AE61F41F0D2183007255F4 /* Vector.strings in Resources */, + 32935CB41F628BCE006888C8 /* IntegrationManager.js in Resources */, F083BD851E7009ED00A9B29C /* favouriteOff@3x.png in Resources */, F083BD541E7009ED00A9B29C /* camera_switch@2x.png in Resources */, F083BD6A1E7009ED00A9B29C /* directChatOn@3x.png in Resources */, @@ -3134,6 +3163,7 @@ F083BE391E7009ED00A9B29C /* RoomEncryptedDataBubbleCell.m in Sources */, F083BDF01E7009ED00A9B29C /* UIViewController+RiotSearch.m in Sources */, F083BDF91E7009ED00A9B29C /* RoomEmailInvitation.m in Sources */, + 32935CB11F6056FD006888C8 /* IntegrationManagerViewController.m in Sources */, F083BE341E7009ED00A9B29C /* EncryptionInfoView.m in Sources */, F083BE641E7009ED00A9B29C /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.m in Sources */, F075BED61EBB169C00A7B68A /* RoomCollectionViewCell.m in Sources */, From 4fdf335bf1bac9284d17ac1902355ffd9bac4644 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 18 Sep 2017 16:22:59 +0200 Subject: [PATCH 28/30] Add WidgetViewController Not yet use --- Riot.xcodeproj/project.pbxproj | 6 +++ Riot/Utils/Widgets/Widget.m | 3 ++ Riot/ViewController/RoomViewController.m | 25 ++++++++-- .../Widgets/WidgetViewController.h | 33 +++++++++++++ .../Widgets/WidgetViewController.m | 48 +++++++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 Riot/ViewController/Widgets/WidgetViewController.h create mode 100644 Riot/ViewController/Widgets/WidgetViewController.m diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 84f90dcb7..2dcc6fc90 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -78,6 +78,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 */; }; + 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 */; }; 92324BE31F4F66D3009DE194 /* IncomingCallView.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324BE21F4F66D3009DE194 /* IncomingCallView.m */; }; @@ -653,6 +654,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 = ""; }; + 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 = ""; }; 32FD0A3B1EB0CD9B0072B066 /* BugReportViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugReportViewController.m; sourceTree = ""; }; 32FD0A3C1EB0CD9B0072B066 /* BugReportViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BugReportViewController.xib; sourceTree = ""; }; @@ -1337,6 +1340,8 @@ 3233F72D1F31F47E006ACA81 /* Widgets */ = { isa = PBXGroup; children = ( + 32F3AE181F6FF4E600F0F004 /* WidgetViewController.h */, + 32F3AE191F6FF4E600F0F004 /* WidgetViewController.m */, 32935CAF1F6056FD006888C8 /* IntegrationManagerViewController.h */, 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */, 3233F72E1F31F4BF006ACA81 /* JitsiViewController.h */, @@ -3102,6 +3107,7 @@ F083BE6E1E7009ED00A9B29C /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, 3233F7311F31F4BF006ACA81 /* JitsiViewController.m in Sources */, F083BE2A1E7009ED00A9B29C /* UsersDevicesViewController.m in Sources */, + 32F3AE1A1F6FF4E600F0F004 /* WidgetViewController.m in Sources */, F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */, F083BE2D1E7009ED00A9B29C /* ForgotPasswordInputsView.m in Sources */, F083BE7E1E7009ED00A9B29C /* InviteRecentTableViewCell.m in Sources */, diff --git a/Riot/Utils/Widgets/Widget.m b/Riot/Utils/Widgets/Widget.m index 381e23c48..ce3ffbbc4 100644 --- a/Riot/Utils/Widgets/Widget.m +++ b/Riot/Utils/Widgets/Widget.m @@ -46,6 +46,9 @@ withString:mxSession.myUser.displayname ? mxSession.myUser.displayname : mxSession.myUser.userId]; _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" withString:mxSession.myUser.avatarUrl ? mxSession.myUser.avatarUrl : @""]; + + // And their scalar token + _url = [_url stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@", [[WidgetManager sharedManager] scalarTokenForMXSession:mxSession]]]; } return self; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 65a01acef..04d58bec3 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -110,6 +110,7 @@ #import "MXRoom+Riot.h" #import "IntegrationManagerViewController.h" +#import "WidgetViewController.h" @interface RoomViewController () { @@ -2880,12 +2881,26 @@ // Matrix Apps button else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) { - IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self.roomDataSource.mxSession - inRoom:self.roomDataSource.roomId - screen:kIntegrationManagerMainScreen - widgetId:nil]; + // Temporary code to test `WidgetViewController` + // TODO: remove it +// NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:self.roomDataSource.room]; +// if (widgets.count) +// { +// // Hide back button title +// self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; +// +// WidgetViewController *widgetVC = [[WidgetViewController alloc] initForWidget:widgets[0]]; +// [self.navigationController pushViewController:widgetVC animated:YES]; +// } +// else + { + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:kIntegrationManagerMainScreen + widgetId:nil]; - [self presentViewController:modularVC animated:NO completion:nil]; + [self presentViewController:modularVC animated:NO completion:nil]; + } } else if (sender == self.jumpToLastUnreadButton) { diff --git a/Riot/ViewController/Widgets/WidgetViewController.h b/Riot/ViewController/Widgets/WidgetViewController.h new file mode 100644 index 000000000..eb439a3ac --- /dev/null +++ b/Riot/ViewController/Widgets/WidgetViewController.h @@ -0,0 +1,33 @@ +/* + 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" + +/** + `WidgetViewController` displays widget within a webview. + */ +@interface WidgetViewController : WebViewViewController + +/** + Init 'WidgetViewController' instance with a widget. + + @param widget the widget to open. + */ +- (instancetype)initForWidget:(Widget*)widget; + +@end diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m new file mode 100644 index 000000000..355306fac --- /dev/null +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -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" + +@interface WidgetViewController () +{ + Widget *widget; +} + +@end + +@implementation WidgetViewController + +- (instancetype)initForWidget:(Widget*)theWidget +{ + self = [super initWithURL:theWidget.url]; + if (self) + { + widget = theWidget; + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + webView.scalesPageToFit = NO; + webView.scrollView.bounces = NO; + + self.navigationItem.title = widget.name; +} + +@end From d2411ba8ea9692a5de23dc061152ae382be5ef8b Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 18 Sep 2017 17:12:31 +0200 Subject: [PATCH 29/30] Modular integrations Manager: Fix invite and listing of bots. Use scalar-staging.riot.im for the moment --- Riot/Riot-Defaults.plist | 4 ++-- .../Widgets/IntegrationManagerViewController.m | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Riot/Riot-Defaults.plist b/Riot/Riot-Defaults.plist index 7b9df11ce..da87bc19d 100644 --- a/Riot/Riot-Defaults.plist +++ b/Riot/Riot-Defaults.plist @@ -25,9 +25,9 @@ webAppUrl https://riot.im/app integrationsUiUrl - https://scalar.vector.im/ + https://scalar-staging.riot.im/scalar-web/ integrationsRestUrl - https://scalar.vector.im/api + https://scalar-staging.riot.im/scalar/api apnsDeviceToken showAllEventsInRoomHistory diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index f52e9fa99..6add0083c 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -368,8 +368,9 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', { NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId); - MXRoom *room = [mxSession roomWithRoomId:roomId]; - if (!room) + MXRoom *room = [self roomCheckWithEvent:eventData]; + + if (room) { MXRoomMember *member = [room.state memberWithUserId:userId]; if (member && member.membership == MXMembershipJoin) @@ -549,7 +550,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', if (room) { MXRoomMember *member = [room.state memberWithUserId:userId]; - [self sendNSObjectResponse:member.originalEvent.JSONDictionary toEvent:eventData]; + [self sendNSObjectResponse:member.originalEvent.content toEvent:eventData]; } } From bac740c9d2ac1ea028acf65e111004663386761f Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Tue, 19 Sep 2017 09:35:49 +0200 Subject: [PATCH 30/30] Improvement: Broaden back icon --- Riot.xcodeproj/project.pbxproj | 4 ++++ Riot/Assets/Images/back_icon.png | Bin 528 -> 1142 bytes Riot/Assets/Images/back_icon@2x.png | Bin 1201 -> 2070 bytes Riot/Assets/Images/back_icon@3x.png | Bin 1873 -> 3266 bytes Riot/Assets/Images/back_icon@4x.png | Bin 0 -> 3010 bytes .../AttachmentsViewController.m | 4 ++++ 6 files changed, 8 insertions(+) create mode 100644 Riot/Assets/Images/back_icon@4x.png diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 2dcc6fc90..8efb31570 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -496,6 +496,7 @@ F0B4CBAC1F41F090008E99C5 /* EncryptionInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0B4CBAB1F41F090008E99C5 /* EncryptionInfoView.xib */; }; F0B4CBB11F4215E3008E99C5 /* EventDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = F0B4CBAF1F4215E3008E99C5 /* EventDetailsView.m */; }; F0B4CBB21F4215E3008E99C5 /* EventDetailsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0B4CBB01F4215E3008E99C5 /* EventDetailsView.xib */; }; + F0B5430A1F70FF8F00D29293 /* back_icon@4x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0B543091F70FF8F00D29293 /* back_icon@4x.png */; }; F0B7A8B11F475783006E27D2 /* RoomsListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0B7A8AF1F4756A5006E27D2 /* RoomsListViewController.xib */; }; F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F0D2ADA01F6AA5FD00A7097D /* MXRoomSummary+Riot.m */; }; F0D869EB1EC455A100BB0A2B /* create_direct_chat.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869E81EC455A100BB0A2B /* create_direct_chat.png */; }; @@ -1202,6 +1203,7 @@ F0B4CBAE1F4215E3008E99C5 /* EventDetailsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EventDetailsView.h; sourceTree = ""; }; F0B4CBAF1F4215E3008E99C5 /* EventDetailsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EventDetailsView.m; sourceTree = ""; }; F0B4CBB01F4215E3008E99C5 /* EventDetailsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EventDetailsView.xib; sourceTree = ""; }; + F0B543091F70FF8F00D29293 /* back_icon@4x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "back_icon@4x.png"; sourceTree = ""; }; F0B7A8AF1F4756A5006E27D2 /* RoomsListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomsListViewController.xib; sourceTree = ""; }; F0D2AD9F1F6AA5FD00A7097D /* MXRoomSummary+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXRoomSummary+Riot.h"; sourceTree = ""; }; F0D2ADA01F6AA5FD00A7097D /* MXRoomSummary+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXRoomSummary+Riot.m"; sourceTree = ""; }; @@ -1577,6 +1579,7 @@ F083BB211E7009EC00A9B29C /* back_icon.png */, F083BB221E7009EC00A9B29C /* back_icon@2x.png */, F083BB231E7009EC00A9B29C /* back_icon@3x.png */, + F0B543091F70FF8F00D29293 /* back_icon@4x.png */, F083BB241E7009EC00A9B29C /* bubbles_bg_landscape.png */, F083BB251E7009EC00A9B29C /* bubbles_bg_landscape@2x.png */, F083BB261E7009EC00A9B29C /* bubbles_bg_landscape@3x.png */, @@ -2792,6 +2795,7 @@ F083BE771E7009ED00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, F083BDA71E7009ED00A9B29C /* notificationsOff.png in Resources */, F06CDD6A1EF01E3900870B75 /* RoomEmptyBubbleCell.xib in Resources */, + F0B5430A1F70FF8F00D29293 /* back_icon@4x.png in Resources */, F083BDBD1E7009ED00A9B29C /* riot_icon@2x.png in Resources */, F083BDDE1E7009ED00A9B29C /* upload_icon@2x.png in Resources */, F083BE8B1E7009ED00A9B29C /* ExpandedRoomTitleView.xib in Resources */, diff --git a/Riot/Assets/Images/back_icon.png b/Riot/Assets/Images/back_icon.png index f7f8bdd245bbae0e7eb844aef591870da7650cfe..e922a69d16cd04725242a576a7c53dba3895c038 100644 GIT binary patch literal 1142 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 literal 1201 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 literal 1873 zcmV-X2d?;uP)Px+3rR#lRA>e5nR#qfMI6Ux-gaBAf|wFO2vH#>h7e7+%O#X+Yn24#utlV_+uH4c z0Tll*kw{iF;)Mqab`QIyAXlRxM6nbEj6rC(RYM|*7b=x-h=!x>c6Z zeInOwhiev+e|i;091gZBYshJ+DJtnU?|47Y8Np6m8w zgeFNCm5CS2k}Ydu4z$>9OTsu+0w!6)2*I8$Ggw|DJElNcZrgCZqEvCl$C9|fKQ6>_ zm%*};Y$`EZ_StURs5q`1Vw{g5VR*$an3`W^C@3RamY8@btJXP2#{|KQ(u9TZawf7| zhJsxZMAlIU%Bi-G1xyuSh6hB2M`b(%bB0z}yAoBPT(>QmbB+njlQd-^8ki~`DKlCQ zwj*h2P(pnWZhLlJ$t2E|FC+}+%oQfo^ipF%NEp(L%B0S%b<70FUUc3+@WsSsFY~8w zNiK4C_C+J^A#;rb+W`Nx70h{6Ri}3ahF~KKlw0F?R8iO-&RL%p&z$vnMQT|};mOWC zBR`KAkh#vjlqq}%hDuMJW6u1ldVEvMCls8PVqK{tGKT>vpceFuftaA%^LM=el>MD|N+1 z+1A~<$3VHY_E)*WUXX(D%u(L>ciOh8%l?sVJ*fj@-n}Qexz_Qn!ugZI$AtL&s^{v0 zipi@QgKa&$=oU~*Fz&?EHA_7q#O`aq)IU~{X>FEm;nZE;%P1Hhz?H3l$&`W^rft7n zsn0o*Y4ypraO*AsJ^a~*gkfB%z(zSo3IfLI+FM`9tX5UFMWXI>$mY0h{gM>r6GV9? zDTvkdj?0k$G;9QYku4p1Xvy&n1LL`l_N6I7Ss`9V$hkUbNBANnP{uLm?Fo8D!FY<) z6e@epm10>Z?P%0D9kRv89Yg!*Srz8YI1;5`9D&tSfY}fYh}M0!16p$2F)|^Z9m2MB zPvA!Q#m@>#jg}8uKNzj+8-^^eX4Bn5#{t9c)C7g_0VmsKv}}(SK-+$UKuc>K_abyS z2$$VKH({8Xg-{oj84LHceJGl?24t?b-;eon0764Z3~yg-0b)J^tIBAK->h#=6S!|Y zJB2wjSl*xtu@Gs>$I^~vt!$rYIWGiXqphSCWrUz>&D9%+#S%nn1y>m|g`yE@jX{#as=UBjy#tlH!kW*)$t79BF zH%}lNGyI(J`c1BhMc-?7l>f8?B0c0*X_U^uQ~fl3Aujn;--G4G;%}nN)80c;Fwbq9 zB4}gfY#_B^PW|g=zS-r;MPJK%qEUy8sY_D|zY>1c1eD7p3|HP3$8-m9&NCw#AhOS! z4w?LrYO~umPQj@ckcN_tSg47s>H-`Z>?b?YC8UR>5T(J*e(yA(oTWBOlZ8&%gUOF` z<;a&#fk<+zaoLZQ7E^jmHryJo2xTW;6Io#r3`>-!!ba?*afE=@~0QZojHmfJk)91H~6S*PF4@M74xKZoeKm z{`1lfr|y=Aq>z>_H(WO1<@BX&Lyo^T&+U9AX!r1<(`DaLzZu8g>^mSKaW`VAba@|w zy7zG0y8<>ww)-V4Ad<`2WsOf9J#r_O-aILfiSKgltIzLA5*aoSK}F#d#u!l3OvcfKKW|Lhf>U|S~_kpl_{@!UGc z+wc_4A+6vHpz4yU&fAw#*dW_Fs(ZA`I_OZik}WHdRj!lw04^nbTNmu(%@k@iqf z3){+StiG3I13Xo8t%*NWF*#q09%OslI%+^9WOmzE;y2Z|AfoAD^_^_;vNTF8+JZ;bOB)9`MFI!~M;(N{!33 z=t06z9TOlD%B!`{7hKr^!FaMUr|N~OnhA|&HNN1e5dJjy zr{#d14~|pV!JN91RC*|;L)Mxy??lOcLLY~p%x^q#=`|pNqcbTz&ZKY|Jd=v~khS*G z7K7y!^EXeW*8sJ{JMq2hAqgkXHE}F-;g$d*FgiF>SR+pEZn5$o%XeLB7&Np200000 LNkvXXu0mjfLv?@A diff --git a/Riot/Assets/Images/back_icon@4x.png b/Riot/Assets/Images/back_icon@4x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e4d8d4847b0ce0c7fa983d40971ba752ac42c1d GIT binary patch literal 3010 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