mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-20 16:42:44 +02:00
Structure project almost by features. Start by organizing view controllers.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <MatrixKit/MatrixKit.h>
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
#import "WidgetManager.h"
|
||||
|
||||
@protocol JitsiViewControllerDelegate;
|
||||
|
||||
/**
|
||||
The `JitsiViewController` is a VC for specifically handling a jitsi widget using the
|
||||
jitsi-meet iOS SDK instead of displaying it in a webview like other modular widgets.
|
||||
|
||||
https://github.com/jitsi/jitsi-meet/tree/master/ios
|
||||
*/
|
||||
@interface JitsiViewController : MXKViewController <JitsiMeetViewDelegate>
|
||||
|
||||
/**
|
||||
Returns the `UINib` object initialized for a `JitsiViewController`.
|
||||
|
||||
@return The initialized `UINib` object or `nil` if there were errors during initialization
|
||||
or the nib file could not be located.
|
||||
*/
|
||||
+ (UINib *)nib;
|
||||
|
||||
/**
|
||||
Creates and returns a new `JitsiViewController` object.
|
||||
|
||||
@discussion This is the designated initializer for programmatic instantiation.
|
||||
|
||||
@return An initialized `JitsiViewController` object if successful, `nil` otherwise.
|
||||
*/
|
||||
+ (instancetype)jitsiViewController;
|
||||
|
||||
/**
|
||||
Make jitsi-meet iOS SDK open the jitsi conference indicated by a jitsi widget.
|
||||
|
||||
@param widget the jitsi widget.
|
||||
@param video to indicate voice or video call.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)openWidget:(Widget*)widget withVideo:(BOOL)video
|
||||
success:(void (^)())success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Hang up the jitsi conference call in progress.
|
||||
*/
|
||||
- (void)hangup;
|
||||
|
||||
/**
|
||||
The jitsi widget displayed by this `JitsiViewController`.
|
||||
*/
|
||||
@property (nonatomic, readonly) Widget *widget;
|
||||
|
||||
/**
|
||||
The delegate for the view controller.
|
||||
*/
|
||||
@property (nonatomic) id<JitsiViewControllerDelegate> delegate;
|
||||
|
||||
#pragma mark - Xib attributes
|
||||
|
||||
// The jitsi-meet SDK view
|
||||
@property (weak, nonatomic) IBOutlet JitsiMeetView *jitsiMeetView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *backToAppButton;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
Delegate for `JitsiViewController` object
|
||||
*/
|
||||
@protocol JitsiViewControllerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Tells the delegate to dismiss the jitsi view controller.
|
||||
|
||||
@param jitsiViewController the jitsi view controller.
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController dismissViewJitsiController:(void (^)())completion;
|
||||
|
||||
/**
|
||||
Tells the delegate to put the jitsi view controller in background.
|
||||
|
||||
@param jitsiViewController the jitsi view controller.
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController goBackToApp:(void (^)())completion;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "JitsiViewController.h"
|
||||
|
||||
static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
|
||||
|
||||
@interface JitsiViewController ()
|
||||
{
|
||||
NSString *jitsiUrl;
|
||||
|
||||
BOOL video;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiViewController
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
+ (UINib *)nib
|
||||
{
|
||||
return [UINib nibWithNibName:NSStringFromClass(self.class)
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
}
|
||||
|
||||
+ (instancetype)jitsiViewController
|
||||
{
|
||||
JitsiViewController *jitsiViewController = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
return jitsiViewController;
|
||||
}
|
||||
|
||||
- (void)openWidget:(Widget*)widget withVideo:(BOOL)aVideo
|
||||
success:(void (^)())success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
video = aVideo;
|
||||
_widget = widget;
|
||||
|
||||
[_widget widgetUrl:^(NSString * _Nonnull widgetUrl) {
|
||||
|
||||
// Extract the jitsi conference id from the widget url
|
||||
NSString *confId;
|
||||
NSURL *url = [NSURL URLWithString:widgetUrl];
|
||||
if (url)
|
||||
{
|
||||
NSURLComponents *components = [[NSURLComponents new] initWithURL:url resolvingAgainstBaseURL:NO];
|
||||
NSArray *queryItems = [components queryItems];
|
||||
|
||||
for (NSURLQueryItem *item in queryItems)
|
||||
{
|
||||
if ([item.name isEqualToString:@"confId"])
|
||||
{
|
||||
confId = item.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// And build from it the url to use in jitsi-meet sdk
|
||||
if (confId)
|
||||
{
|
||||
jitsiUrl = [NSString stringWithFormat:@"%@%@", kJitsiServerUrl, confId];
|
||||
}
|
||||
}
|
||||
|
||||
if (jitsiUrl)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[JitsiVC] Failed to load widget: %@. Widget event: %@", widget, widget.widgetEvent);
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure(nil);
|
||||
}
|
||||
}
|
||||
|
||||
} failure:^(NSError * _Nonnull error) {
|
||||
|
||||
NSLog(@"[JitsiVC] Failed to load widget 2: %@. Widget event: %@", widget, widget.widgetEvent);
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure(nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hangup
|
||||
{
|
||||
jitsiUrl = nil;
|
||||
|
||||
// It would have been nicer to ask JitsiMeetView but there is no api.
|
||||
// Dismissing the view controller and releasing it does the job for the moment
|
||||
if (_delegate)
|
||||
{
|
||||
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.jitsiMeetView.delegate = self;
|
||||
|
||||
// Pass the URL to jitsi-meet sdk
|
||||
[self.jitsiMeetView loadURLObject: @{
|
||||
@"url": jitsiUrl,
|
||||
@"configOverwrite": @{
|
||||
@"startWithVideoMuted": @(!video)
|
||||
}
|
||||
}];
|
||||
|
||||
// TODO: Set up user info but it is not yet available in the jitsi-meet iOS SDK
|
||||
// See https://github.com/jitsi/jitsi-meet/issues/1880
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)onBackToAppButtonPressed:(id)sender
|
||||
{
|
||||
if (_delegate)
|
||||
{
|
||||
[_delegate jitsiViewController:self goBackToApp:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - JitsiMeetViewDelegate
|
||||
|
||||
- (void)conferenceFailed:(NSDictionary *)data
|
||||
{
|
||||
NSLog(@"[JitsiViewController] conferenceFailed - data: %@", data);
|
||||
}
|
||||
|
||||
- (void)conferenceLeft:(NSDictionary *)data
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// The conference is over. Let the delegate close this view controller.
|
||||
if (_delegate)
|
||||
{
|
||||
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do it ourself
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JitsiViewController">
|
||||
<connections>
|
||||
<outlet property="backToAppButton" destination="8tr-Cb-ue8" id="aUj-co-7JA"/>
|
||||
<outlet property="jitsiMeetView" destination="7hL-Cs-mak" id="7kR-Te-Klw"/>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7hL-Cs-mak" customClass="JitsiMeetView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8tr-Cb-ue8">
|
||||
<rect key="frame" x="10" y="5" width="44" height="44"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCBackToAppButton"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="3id-6Q-PUF"/>
|
||||
<constraint firstAttribute="width" constant="44" id="JGj-Jz-SbU"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<color key="tintColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<inset key="titleEdgeInsets" minX="-69" minY="61" maxX="0.0" maxY="0.0"/>
|
||||
<state key="normal" image="back_icon.png">
|
||||
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onBackToAppButtonPressed:" destination="-1" eventType="touchUpInside" id="2wo-nB-Rwd"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="7hL-Cs-mak" secondAttribute="trailing" id="8eH-2r-pjD"/>
|
||||
<constraint firstAttribute="bottom" secondItem="7hL-Cs-mak" secondAttribute="bottom" id="BAo-6X-ovC"/>
|
||||
<constraint firstItem="8tr-Cb-ue8" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="5" id="FPS-wy-gK6"/>
|
||||
<constraint firstItem="8tr-Cb-ue8" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="10" id="Xca-R4-1cu"/>
|
||||
<constraint firstItem="7hL-Cs-mak" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="s46-Fx-tT8"/>
|
||||
<constraint firstItem="7hL-Cs-mak" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="x3v-Xl-cKi"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="back_icon.png" width="12" height="23"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "WidgetViewController.h"
|
||||
|
||||
@interface StickerPickerViewController : WidgetViewController
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "StickerPickerViewController.h"
|
||||
|
||||
#import "IntegrationManagerViewController.h"
|
||||
|
||||
@interface StickerPickerViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation StickerPickerViewController
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.navigationItem.title = NSLocalizedStringFromTable(@"room_action_send_sticker", @"Vector", nil);
|
||||
|
||||
// Hide back button title
|
||||
self.parentViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
|
||||
|
||||
UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(onEditButtonPressed)];
|
||||
[self.navigationItem setRightBarButtonItem: editButton animated:YES];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// Make sure the content is up-to-date when we come back from the sticker picker settings screen
|
||||
[webView reload];
|
||||
}
|
||||
|
||||
- (void)onEditButtonPressed
|
||||
{
|
||||
// Show the sticker picker settings screen
|
||||
IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc]
|
||||
initForMXSession:self.roomDataSource.mxSession
|
||||
inRoom:self.roomDataSource.roomId
|
||||
screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker]
|
||||
widgetId:self.widget.widgetId];
|
||||
|
||||
[self presentViewController:modularVC animated:NO completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "WebViewViewController.h"
|
||||
|
||||
#import "WidgetManager.h"
|
||||
#import "MatrixKit/MatrixKit.h"
|
||||
|
||||
/**
|
||||
`WidgetViewController` displays widget within a webview.
|
||||
|
||||
It also exposes a generic pipe, the postMessage API, to communicate with the
|
||||
content within the webview, ie the widget (matrix app).
|
||||
*/
|
||||
@interface WidgetViewController : WebViewViewController
|
||||
|
||||
/**
|
||||
The displayed widget.
|
||||
*/
|
||||
@property (nonatomic, readonly) Widget *widget;
|
||||
|
||||
/**
|
||||
The room data source.
|
||||
Required if the widget needs to post messages.
|
||||
*/
|
||||
@property (nonatomic) MXKRoomDataSource *roomDataSource;
|
||||
|
||||
/**
|
||||
Init 'WidgetViewController' instance with a widget.
|
||||
|
||||
@param widgetUrl the formatted widget url.
|
||||
@param widget the widget to open.
|
||||
*/
|
||||
- (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)widget;
|
||||
|
||||
/**
|
||||
Display an alert over this controller.
|
||||
|
||||
@param error the error to display.
|
||||
*/
|
||||
- (void)showErrorAsAlert:(NSError*)error;
|
||||
|
||||
|
||||
#pragma mark - postMessage API
|
||||
|
||||
/**
|
||||
Callback called when the widget make a postMessage API request.
|
||||
|
||||
This method can be overidden to implement a specific API between the matrix client
|
||||
and widget.
|
||||
|
||||
@param requestId the id of the widget request.
|
||||
@param requestData the request data.
|
||||
*/
|
||||
- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData;
|
||||
|
||||
/**
|
||||
Send a boolean response to a request from the widget.
|
||||
|
||||
@param response the response to send.
|
||||
@param requestId the id of the widget request.
|
||||
*/
|
||||
- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId;
|
||||
|
||||
/**
|
||||
Send an integer response to a request from the widget.
|
||||
|
||||
@param response the response to send.
|
||||
@param requestId the id of the widget request.
|
||||
*/
|
||||
- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId;
|
||||
|
||||
/**
|
||||
Send a serialiable object response to a request the widget.
|
||||
|
||||
@param response the response to send.
|
||||
@param requestId the id of the widget request.
|
||||
*/
|
||||
- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId;
|
||||
|
||||
/**
|
||||
Send a message error to a request from the widget.
|
||||
|
||||
@param message the error message.
|
||||
@param requestId the id of the widget request.
|
||||
*/
|
||||
- (void)sendError:(NSString*)message toRequest:(NSString*)requestId;
|
||||
|
||||
/**
|
||||
Send a localised message error to a request from the widget.
|
||||
|
||||
@param errorKey the string id of the message error.
|
||||
@param requestId the id of the widget request.
|
||||
*/
|
||||
- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "WidgetViewController.h"
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "IntegrationManagerViewController.h"
|
||||
|
||||
NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);";
|
||||
|
||||
@interface WidgetViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation WidgetViewController
|
||||
@synthesize widget;
|
||||
|
||||
- (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)theWidget
|
||||
{
|
||||
self = [super initWithURL:widgetUrl];
|
||||
if (self)
|
||||
{
|
||||
widget = theWidget;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
webView.scrollView.bounces = NO;
|
||||
|
||||
// Disable opacity so that the webview background uses the current interface theme
|
||||
webView.opaque = NO;
|
||||
|
||||
if (widget)
|
||||
{
|
||||
self.navigationItem.title = widget.name ? widget.name : widget.type;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showErrorAsAlert:(NSError*)error
|
||||
{
|
||||
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
|
||||
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
|
||||
if (!title)
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
title = msg;
|
||||
msg = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
title = [NSBundle mxk_localizedStringForKey:@"error"];
|
||||
}
|
||||
}
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
|
||||
typeof(self) self = weakSelf;
|
||||
|
||||
if (self)
|
||||
{
|
||||
// Leave this widget VC
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - WKNavigationDelegate
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
|
||||
{
|
||||
[self enableDebug];
|
||||
|
||||
// Setup js code
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:@"postMessageAPI" ofType:@"js"];
|
||||
NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
|
||||
[webView evaluateJavaScript:js completionHandler:nil];
|
||||
|
||||
[self stopActivityIndicator];
|
||||
|
||||
// Check connectivity
|
||||
if ([AppDelegate theDelegate].isOffline)
|
||||
{
|
||||
// The web page may be in the cache, so its loading will be successful
|
||||
// but we cannot go further, it often leads to a blank screen.
|
||||
// So, display an error so that the user can escape.
|
||||
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
|
||||
code:NSURLErrorNotConnectedToInternet
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"network_offline_prompt", @"Vector", nil)
|
||||
}];
|
||||
[self showErrorAsAlert:error];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
|
||||
{
|
||||
NSString *urlString = navigationAction.request.URL.absoluteString;
|
||||
|
||||
// TODO: We should use the WebKit PostMessage API and the
|
||||
// `didReceiveScriptMessage` delegate to manage the JS<->Native bridge
|
||||
if ([urlString hasPrefix:@"js:"])
|
||||
{
|
||||
// Listen only to the scheme of the JS<->Native bridge
|
||||
NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByRemovingPercentEncoding];
|
||||
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers
|
||||
error:&error];
|
||||
if (!error)
|
||||
{
|
||||
// Retrieve the js event payload data
|
||||
NSDictionary *eventData;
|
||||
MXJSONModelSetDictionary(eventData, parameters[@"event.data"]);
|
||||
|
||||
NSString *requestId;
|
||||
MXJSONModelSetString(requestId, eventData[@"_id"]);
|
||||
|
||||
if (requestId)
|
||||
{
|
||||
[self onPostMessageRequest:requestId data:eventData];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[WidgetVC] shouldStartLoadWithRequest: ERROR: Missing request id in postMessage API %@", parameters);
|
||||
}
|
||||
}
|
||||
|
||||
decisionHandler(WKNavigationActionPolicyCancel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigationAction.navigationType == WKNavigationTypeLinkActivated)
|
||||
{
|
||||
// Open links outside the app
|
||||
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
|
||||
decisionHandler(WKNavigationActionPolicyCancel);
|
||||
return;
|
||||
}
|
||||
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
|
||||
{
|
||||
// Filter out the users's scalar token
|
||||
NSString *errorDescription = error.description;
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*"
|
||||
options:NSRegularExpressionCaseInsensitive error:nil];
|
||||
errorDescription = [regex stringByReplacingMatchesInString:errorDescription
|
||||
options:0
|
||||
range:NSMakeRange(0, errorDescription.length)
|
||||
withTemplate:@"scalar_token=..."];
|
||||
|
||||
NSLog(@"[WidgetVC] didFailLoadWithError: %@", errorDescription);
|
||||
|
||||
[self stopActivityIndicator];
|
||||
[self showErrorAsAlert:error];
|
||||
}
|
||||
|
||||
#pragma mark - postMessage API
|
||||
|
||||
- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData
|
||||
{
|
||||
NSString *action;
|
||||
MXJSONModelSetString(action, requestData[@"action"]);
|
||||
|
||||
if ([@"m.sticker" isEqualToString:action])
|
||||
{
|
||||
// Extract the sticker event content and send it as is
|
||||
|
||||
// The key should be "data" according to https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
|
||||
// TODO: Fix it once spec is finalised
|
||||
NSDictionary *widgetData;
|
||||
NSDictionary *stickerContent;
|
||||
MXJSONModelSetDictionary(widgetData, requestData[@"widgetData"]);
|
||||
if (widgetData)
|
||||
{
|
||||
MXJSONModelSetDictionary(stickerContent, widgetData[@"content"]);
|
||||
}
|
||||
|
||||
if (stickerContent)
|
||||
{
|
||||
// Let the data source manage the sending cycle
|
||||
[_roomDataSource sendEventOfType:kMXEventTypeStringSticker content:stickerContent success:nil failure:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Invalid content for m.sticker: %@", requestData);
|
||||
}
|
||||
|
||||
// Consider we are done with the sticker picker widget
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
else if ([@"integration_manager_open" isEqualToString:action])
|
||||
{
|
||||
NSDictionary *widgetData;
|
||||
NSString *integType, *integId;
|
||||
MXJSONModelSetDictionary(widgetData, requestData[@"widgetData"]);
|
||||
if (widgetData)
|
||||
{
|
||||
MXJSONModelSetString(integType, widgetData[@"integType"]);
|
||||
MXJSONModelSetString(integId, widgetData[@"integId"]);
|
||||
}
|
||||
|
||||
if (integType && integId)
|
||||
{
|
||||
// Open the integration manager requested page
|
||||
IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc]
|
||||
initForMXSession:self.roomDataSource.mxSession
|
||||
inRoom:self.roomDataSource.roomId
|
||||
screen:[IntegrationManagerViewController screenForWidget:integType]
|
||||
widgetId:integId];
|
||||
|
||||
[self presentViewController:modularVC animated:NO completion:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Invalid content for integration_manager_open: %@", requestData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Unsupported action: %@: %@", action, requestData);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId
|
||||
{
|
||||
// Convert BOOL to "true" or "false"
|
||||
NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI,
|
||||
requestId,
|
||||
response ? @"true" : @"false"];
|
||||
|
||||
[webView evaluateJavaScript:js completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId
|
||||
{
|
||||
NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI,
|
||||
requestId,
|
||||
@(response)];
|
||||
|
||||
[webView evaluateJavaScript:js completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId
|
||||
{
|
||||
NSString *jsString;
|
||||
|
||||
if (response)
|
||||
{
|
||||
// Convert response into a JS object through a JSON string
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response
|
||||
options:0
|
||||
error:0];
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
|
||||
jsString = [NSString stringWithFormat:@"JSON.parse('%@')", jsonString];
|
||||
}
|
||||
else
|
||||
{
|
||||
jsString = @"null";
|
||||
}
|
||||
|
||||
NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI,
|
||||
requestId,
|
||||
jsString];
|
||||
|
||||
[webView evaluateJavaScript:js completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)sendError:(NSString*)message toRequest:(NSString*)requestId
|
||||
{
|
||||
NSLog(@"[WidgetVC] sendError: Action %@ failed with message: %@", requestId, message);
|
||||
|
||||
// TODO: JS has an additional optional parameter: nestedError
|
||||
[self sendNSObjectResponse:@{
|
||||
@"error": @{
|
||||
@"message": message
|
||||
}
|
||||
}
|
||||
toRequest:requestId];
|
||||
}
|
||||
|
||||
- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId
|
||||
{
|
||||
[self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toRequest:requestId];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user