Skip to content

Commit

Permalink
[Spaces] M10.6.1 Handle space link #4498
Browse files Browse the repository at this point in the history
- Fixed universal links
- Added support for space links
  • Loading branch information
gileluard committed Sep 22, 2021
1 parent 8e4b98f commit 14f0533
Show file tree
Hide file tree
Showing 21 changed files with 428 additions and 66 deletions.
4 changes: 4 additions & 0 deletions Riot/Modules/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ extension AppCoordinator: LegacyAppDelegateDelegate {
func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didRemove account: MXKAccount!) {
self.userSessionsService.removeUserSession(relatedToAccount: account)
}

func legacyAppDelegate(_ legacyAppDelegate: LegacyAppDelegate!, didNavigateToSpaceWithId spaceId: String!) {
self.sideMenuCoordinator?.select(spaceWithId: spaceId)
}
}

// MARK: - SplitViewCoordinatorDelegate
Expand Down
25 changes: 25 additions & 0 deletions Riot/Modules/Application/LegacyAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,29 @@ UINavigationControllerDelegate
*/
- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL;

/**
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
The fragment can contain a '?'. So there are two kinds of parameters: path params and query params.
It is in the form of /[pathParam1]/[pathParam2]?[queryParam1Key]=[queryParam1Value]&[queryParam2Key]=[queryParam2Value]
@note this method should be private but is used by RoomViewController. This should be moved to a univresal link parser class
@param fragment the fragment to parse.
@param outPathParams the decoded path params.
@param outQueryParams the decoded query params. If there is no query params, it will be nil.
*/
- (void)parseUniversalLinkFragment:(NSString*)fragment outPathParams:(NSArray<NSString*> **)outPathParams outQueryParams:(NSMutableDictionary **)outQueryParams;

/**
Open the dedicated space with the given ID.
This method will open only joined or invited spaces.
@note this method is temporary and should be moved to a dedicated coordinator
@param spaceId ID of the space.
*/
- (void)openSpaceWithId:(NSString*)spaceId;

#pragma mark - App version management

/**
Expand Down Expand Up @@ -271,4 +294,6 @@ UINavigationControllerDelegate

- (void)legacyAppDelegate:(LegacyAppDelegate*)legacyAppDelegate didRemoveAccount:(MXKAccount*)account;

- (void)legacyAppDelegate:(LegacyAppDelegate*)legacyAppDelegate didNavigateToSpaceWithId:(NSString*)spaceId;

@end
112 changes: 93 additions & 19 deletions Riot/Modules/Application/LegacyAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@

NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification";

@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate>
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
{
/**
Reachability observer
Expand Down Expand Up @@ -204,6 +204,7 @@ @interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificat
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter;
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
@property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter;

/**
Used to manage on boarding steps, like create DM with riot bot
Expand Down Expand Up @@ -1344,8 +1345,11 @@ - (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universa

if (room.summary.roomType == MXRoomTypeSpace)
{
// Indicates that spaces are not supported
[self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES];
[self restoreInitialDisplay:^{
self.spaceDetailPresenter = [SpaceDetailPresenter new];
self.spaceDetailPresenter.delegate = self;
[self.spaceDetailPresenter presentForSpaceWithId:room.roomId from:self.masterNavigationController sourceView:nil session:account.mxSession animated:YES];
}];
}
else
{
Expand Down Expand Up @@ -1458,24 +1462,21 @@ - (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universa
roomPreviewData.viaServers = queryParams[@"via"];
}

// Is it a link to an event of a room?
// If yes, the event will be displayed once the room is joined
roomPreviewData.eventId = (pathParams.count >= 3) ? pathParams[2] : nil;

// Try to get more information about the room before opening its preview
[roomPreviewData peekInRoom:^(BOOL succeeded) {

// Note: the activity indicator will not disappear if the session is not ready
[homeViewController stopActivityIndicator];

// If no data is available for this room, we name it with the known room alias (if any).
if (!succeeded && universalLinkFragmentPendingRoomAlias[roomIdOrAlias])
[account.mxSession.matrixRestClient roomSummaryWith:roomIdOrAlias via:roomPreviewData.viaServers success:^(MXPublicRoom *room) {
if ([room.roomTypeString isEqualToString:MXRoomTypeStringSpace])
{
[homeViewController stopActivityIndicator];

self.spaceDetailPresenter = [SpaceDetailPresenter new];
self.spaceDetailPresenter.delegate = self;
[self.spaceDetailPresenter presentForSpaceWithSummary:room from:self.masterNavigationController sourceView:nil session:account.mxSession animated:YES];
}
else
{
roomPreviewData.roomName = universalLinkFragmentPendingRoomAlias[roomIdOrAlias];
[self peekInRoomWithId:roomIdOrAlias forPreviewData:roomPreviewData params:pathParams];
}
universalLinkFragmentPendingRoomAlias = nil;

[self showRoomPreview:roomPreviewData];
} failure:^(NSError *error) {
[self peekInRoomWithId:roomIdOrAlias forPreviewData:roomPreviewData params:pathParams];
}];
}

Expand Down Expand Up @@ -1607,6 +1608,33 @@ - (void)resetPendingUniversalLink
}
}

- (void)peekInRoomWithId:(NSString*)roomIdOrAlias forPreviewData:(RoomPreviewData *)roomPreviewData params:(NSArray<NSString*> *)pathParams
{
// Is it a link to an event of a room?
// If yes, the event will be displayed once the room is joined
roomPreviewData.eventId = (pathParams.count >= 3) ? pathParams[2] : nil;

MXWeakify(self);
// Try to get more information about the room before opening its preview
[roomPreviewData peekInRoom:^(BOOL succeeded) {
MXStrongifyAndReturnIfNil(self);

MXKViewController *homeViewController = (MXKViewController*)self.masterTabBarController.selectedViewController;

// Note: the activity indicator will not disappear if the session is not ready
[homeViewController stopActivityIndicator];

// If no data is available for this room, we name it with the known room alias (if any).
if (!succeeded && self->universalLinkFragmentPendingRoomAlias[roomIdOrAlias])
{
roomPreviewData.roomName = self->universalLinkFragmentPendingRoomAlias[roomIdOrAlias];
}
self->universalLinkFragmentPendingRoomAlias = nil;

[self showRoomPreview:roomPreviewData];
}];
}

/**
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
Expand Down Expand Up @@ -4382,4 +4410,50 @@ - (void)clearCache
[URLPreviewService.shared clearStore];
}

#pragma mark - Spaces

-(void)openSpaceWithId:(NSString *)spaceId
{
MXSession *session = mxSessionArray.firstObject;
if ([session.spaceService getSpaceWithId:spaceId]) {
[self restoreInitialDisplay:^{
[self.delegate legacyAppDelegate:self didNavigateToSpaceWithId:spaceId];
}];
}
else
{
MXWeakify(self);
__block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:MXSpaceService.didBuildSpaceGraph object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
MXStrongifyAndReturnIfNil(self);

[[NSNotificationCenter defaultCenter] removeObserver:observer];

if ([session.spaceService getSpaceWithId:spaceId]) {
[self restoreInitialDisplay:^{
[self.delegate legacyAppDelegate:self didNavigateToSpaceWithId:spaceId];
}];
}
}];
}
}

#pragma mark - SpaceDetailPresenterDelegate

- (void)spaceDetailPresenterDidComplete:(SpaceDetailPresenter *)presenter
{
self.spaceDetailPresenter = nil;
}

- (void)spaceDetailPresenter:(SpaceDetailPresenter *)presenter didOpenSpaceWithId:(NSString *)spaceId
{
self.spaceDetailPresenter = nil;
[self openSpaceWithId:spaceId];
}

- (void)spaceDetailPresenter:(SpaceDetailPresenter *)presenter didJoinSpaceWithId:(NSString *)spaceId
{
self.spaceDetailPresenter = nil;
[self openSpaceWithId:spaceId];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import Foundation

class HomeViewControllerWithBannerWrapperViewController: UIViewController, BannerPresentationProtocol {
class HomeViewControllerWithBannerWrapperViewController: MXKViewController, BannerPresentationProtocol {

@objc let homeViewController: HomeViewController
private var bannerContainerView: UIView!
Expand Down
127 changes: 127 additions & 0 deletions Riot/Modules/Room/RoomViewController+Spaces.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// Copyright 2021 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 Foundation

/// this extension is temprorary and implements navigation to the Space bootom sheet. This should be moved to an universal link flow coordinator
extension RoomViewController {
@objc func handleSpaceUniversalLink(with url: URL) {
let url = Tools.fixURL(withSeveralHashKeys: url)

var pathParams: NSArray?
var queryParams: NSMutableDictionary?
AppDelegate.theDelegate().parseUniversalLinkFragment(url?.fragment, outPathParams: &pathParams, outQueryParams: &queryParams)

// Sanity check
guard let pathParams = pathParams as? [String], pathParams.count > 0 else {
MXLog.error("[RoomViewController] Universal link: Error: No path parameters")
return
}

var roomIdOrAlias: String?
var eventId: String?
var userId: String?
var groupId: String?

// Check permalink to room or event
if pathParams[0] == "room" && pathParams.count >= 2 {

// The link is the form of "/room/[roomIdOrAlias]" or "/room/[roomIdOrAlias]/[eventId]"
roomIdOrAlias = pathParams[1]

// Is it a link to an event of a room?
eventId = pathParams.count >= 3 ? pathParams[2] : nil

} else if pathParams[0] == "group" && pathParams.count >= 2 {

// The link is the form of "/group/[groupId]"
groupId = pathParams[1]

} else if (pathParams[0].hasPrefix("#") || pathParams[0].hasPrefix("!")) && pathParams.count >= 1 {

// The link is the form of "/#/[roomIdOrAlias]" or "/#/[roomIdOrAlias]/[eventId]"
// Such links come from matrix.to permalinks
roomIdOrAlias = pathParams[0]
eventId = pathParams.count >= 2 ? pathParams[1] : nil

} else if pathParams[0] == "user" && pathParams.count == 2 { // Check permalink to a user
// The link is the form of "/user/userId"
userId = pathParams[1]
} else if pathParams[0].hasPrefix("@") && pathParams.count == 1 {
// The link is the form of "/#/[userId]"
// Such links come from matrix.to permalinks
userId = pathParams[0]
}

guard let roomIdOrAlias = roomIdOrAlias else {
AppDelegate.theDelegate().handleUniversalLinkURL(url)
return
}

self.startActivityIndicator()

var viaServers: [String] = []
if let queryParams = queryParams as? [String: Any], let via = queryParams["via"] as? [String] {
viaServers = via
}

if roomIdOrAlias.hasPrefix("#") {
self.mainSession.matrixRestClient.roomId(forRoomAlias: roomIdOrAlias) { [weak self] response in
guard let self = self else {
return
}

guard let roomId = response.value else {
self.stopActivityIndicator()

if response.error != nil {
let errorMessage = VectorL10n.roomDoesNotExist(roomIdOrAlias)
AppDelegate.theDelegate().showAlert(withTitle: nil, message: errorMessage)
}
return
}

self.requestSummary(forRoomWithId: roomId, via: viaServers, from: url)
}
} else {
self.requestSummary(forRoomWithId: roomIdOrAlias, via: viaServers, from: url)
}
}

private func requestSummary(forRoomWithId roomId: String, via: [String], from url: URL?) {
if self.mainSession.spaceService.getSpace(withId: roomId) != nil {
self.stopActivityIndicator()
self.showSpaceDetail(withId: roomId)
return
}

self.mainSession.matrixRestClient.roomSummary(with: roomId, via: via) { [weak self] response in
guard let self = self else {
return
}

self.stopActivityIndicator()

guard let publicRoom = response.value, publicRoom.roomTypeString == MXRoomTypeString.space.rawValue else {
AppDelegate.theDelegate().handleUniversalLinkURL(url)
return
}

self.showSpaceDetail(with: publicRoom)
}
}

}
4 changes: 4 additions & 0 deletions Riot/Modules/Room/RoomViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification;
*/
- (void)displayRoomPreview:(RoomPreviewData*)roomPreviewData;

- (void)showSpaceDetailWithPublicRoom:(MXPublicRoom *)publicRoom;

- (void)showSpaceDetailWithId:(NSString *)spaceId;

/**
Action used to handle some buttons.
*/
Expand Down
Loading

0 comments on commit 14f0533

Please sign in to comment.