mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-26 19:34:25 +02:00
Merge branch 'develop' into ismail/4384_room_summary_store
This commit is contained in:
@@ -80,6 +80,9 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
|
||||
self.setupLogger()
|
||||
self.setupTheme()
|
||||
|
||||
// Setup navigation router store
|
||||
_ = NavigationRouterStore.shared
|
||||
|
||||
if BuildSettings.enableSideMenu {
|
||||
self.addSideMenu()
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe
|
||||
|
||||
NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification";
|
||||
|
||||
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
|
||||
@interface LegacyAppDelegate () <GDPRConsentViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, PushNotificationServiceDelegate, SetPinCoordinatorBridgePresenterDelegate, CallPresenterDelegate, SpaceDetailPresenterDelegate>
|
||||
{
|
||||
/**
|
||||
Reachability observer
|
||||
@@ -201,7 +201,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
|
||||
@property (weak, nonatomic) UIAlertController *incomingKeyVerificationRequestAlertController;
|
||||
|
||||
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
|
||||
@property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter;
|
||||
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
|
||||
@property (nonatomic, strong) SpaceDetailPresenter *spaceDetailPresenter;
|
||||
@@ -674,9 +673,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
// Register to GDPR consent not given notification
|
||||
[self registerUserConsentNotGivenNotification];
|
||||
|
||||
// Register to identity server terms not signed notification
|
||||
[self registerIdentityServiceTermsNotSignedNotification];
|
||||
|
||||
// Start monitoring reachability
|
||||
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
|
||||
|
||||
@@ -1847,6 +1843,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
[self checkLocalPrivateKeysInSession:mxSession];
|
||||
|
||||
[self.pushNotificationService checkPushKitPushersInSession:mxSession];
|
||||
|
||||
// Validate the availability of local contact sync for any changes to the
|
||||
// authorization of contacts access that may have occurred since the last launch.
|
||||
if (BuildSettings.allowLocalContactsAccess)
|
||||
{
|
||||
[MXKContactManager.sharedManager validateSyncLocalContactsState];
|
||||
}
|
||||
}
|
||||
else if (mxSession.state == MXSessionStateClosed)
|
||||
{
|
||||
@@ -4131,82 +4134,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Identity server service terms
|
||||
|
||||
// Observe identity server terms not signed notification
|
||||
- (void)registerIdentityServiceTermsNotSignedNotification
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleIdentityServiceTermsNotSignedNotification:) name:MXIdentityServiceTermsNotSignedNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)handleIdentityServiceTermsNotSignedNotification:(NSNotification*)notification
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] IS Terms: handleIdentityServiceTermsNotSignedNotification.");
|
||||
|
||||
NSString *baseURL;
|
||||
NSString *accessToken;
|
||||
|
||||
MXJSONModelSetString(baseURL, notification.userInfo[MXIdentityServiceNotificationIdentityServerKey]);
|
||||
MXJSONModelSetString(accessToken, notification.userInfo[MXIdentityServiceNotificationAccessTokenKey]);
|
||||
|
||||
[self presentIdentityServerTermsWithBaseURL:baseURL andAccessToken:accessToken];
|
||||
}
|
||||
|
||||
- (void)presentIdentityServerTermsWithBaseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken
|
||||
{
|
||||
MXSession *mxSession = self.mxSessions.firstObject;
|
||||
|
||||
if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession
|
||||
baseUrl:baseURL
|
||||
serviceType:MXServiceTypeIdentityService
|
||||
outOfContext:YES
|
||||
accessToken:accessToken];
|
||||
|
||||
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
|
||||
|
||||
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self.presentedViewController animated:YES];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter *)coordinatorBridgePresenter session:(MXSession *)session
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] IS Terms: User has declined the use of the default IS.");
|
||||
|
||||
// The user does not want to use the proposed IS.
|
||||
// Disable IS feature on user's account
|
||||
[session setIdentityServer:nil andAccessToken:nil];
|
||||
[session setAccountDataIdentityServer:nil success:^{
|
||||
} failure:^(NSError *error) {
|
||||
MXLogDebug(@"[AppDelegate] IS Terms: Error: %@", error);
|
||||
}];
|
||||
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Settings
|
||||
|
||||
- (void)setupUserDefaults
|
||||
|
||||
@@ -417,7 +417,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
|
||||
NSIndexPath *currentSelectedCellIndexPath = nil;
|
||||
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
|
||||
if (masterTabBarController.currentRoomViewController)
|
||||
if (masterTabBarController.selectedRoomId)
|
||||
{
|
||||
// Look for the rank of this selected room in displayed recents
|
||||
currentSelectedCellIndexPath = [self.dataSource cellIndexPathWithRoomId:masterTabBarController.selectedRoomId andMatrixSession:masterTabBarController.selectedRoomSession];
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
}
|
||||
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.avatarUrl
|
||||
roomId:roomCellData.roomIdentifier
|
||||
displayName:roomCellData.roomDisplayname
|
||||
mediaManager:roomCellData.mxSession.mediaManager];
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
|
||||
NSIndexPath *currentSelectedCellIndexPath = nil;
|
||||
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
|
||||
if (masterTabBarController.currentGroupDetailViewController)
|
||||
if (masterTabBarController.selectedGroup)
|
||||
{
|
||||
// Look for the rank of this selected group in displayed groups
|
||||
currentSelectedCellIndexPath = [self.dataSource cellIndexPathWithGroupId:masterTabBarController.selectedGroup.groupId];
|
||||
|
||||
@@ -807,7 +807,7 @@
|
||||
contact = [[MXKContact alloc] initMatrixContactWithDisplayName:userId andMatrixID:userId];
|
||||
}
|
||||
|
||||
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController contactDetailsViewController];
|
||||
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate];
|
||||
contactDetailsViewController.enableVoipCall = NO;
|
||||
contactDetailsViewController.contact = contact;
|
||||
|
||||
|
||||
@@ -964,7 +964,7 @@
|
||||
|
||||
if (contact)
|
||||
{
|
||||
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController contactDetailsViewController];
|
||||
ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate];
|
||||
contactDetailsViewController.enableVoipCall = NO;
|
||||
contactDetailsViewController.contact = contact;
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Communities GroupDetails
|
||||
/*
|
||||
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
|
||||
import UIKit
|
||||
|
||||
final class GroupDetailsCoordinator: GroupDetailsCoordinatorProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: GroupDetailsCoordinatorParameters
|
||||
private let groupDetailsViewController: GroupDetailsViewController
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: GroupDetailsCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: GroupDetailsCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
let groupDetailsViewController: GroupDetailsViewController = GroupDetailsViewController.instantiate()
|
||||
self.groupDetailsViewController = groupDetailsViewController
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
self.groupDetailsViewController.setGroup(self.parameters.group, withMatrixSession: self.parameters.session)
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.groupDetailsViewController
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Communities GroupDetails
|
||||
/*
|
||||
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
|
||||
|
||||
/// GroupDetailsCoordinator input parameters
|
||||
struct GroupDetailsCoordinatorParameters {
|
||||
|
||||
/// The Matrix session
|
||||
let session: MXSession
|
||||
|
||||
/// The group for which the details are displayed
|
||||
let group: MXGroup
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Communities GroupDetails
|
||||
/*
|
||||
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
|
||||
|
||||
protocol GroupDetailsCoordinatorDelegate: AnyObject {
|
||||
func groupDetailsCoordinatorDidCancel(_ coordinator: GroupDetailsCoordinatorProtocol)
|
||||
}
|
||||
|
||||
/// `GroupDetailsCoordinatorProtocol` is a protocol describing a Coordinator that handle communities navigation flow.
|
||||
protocol GroupDetailsCoordinatorProtocol: Coordinator, Presentable {
|
||||
var delegate: GroupDetailsCoordinatorDelegate? { get }
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
@discussion This is the designated initializer for programmatic instantiation.
|
||||
@return An initialized `GroupDetailsViewController` object if successful, `nil` otherwise.
|
||||
*/
|
||||
+ (instancetype)groupDetailsViewController;
|
||||
+ (instancetype)instantiate;
|
||||
|
||||
/**
|
||||
Set the group for which the details are displayed.
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
}
|
||||
|
||||
+ (instancetype)groupDetailsViewController
|
||||
+ (instancetype)instantiate
|
||||
{
|
||||
return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
@@ -117,6 +117,9 @@
|
||||
[self initWithTitles:titles viewControllers:viewControllers defaultSelected:0];
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
|
||||
self.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
|
||||
@@ -66,6 +66,19 @@
|
||||
*/
|
||||
@property (weak, nonatomic) IBOutlet UITableView *contactsTableView;
|
||||
|
||||
/**
|
||||
When true, the footer that allows the user to enable local contacts sync will
|
||||
never be shown. When false, the footer will shown when the user hasn't enabled
|
||||
contact sync.
|
||||
*/
|
||||
@property (nonatomic) BOOL disableFindYourContactsFooter;
|
||||
|
||||
/**
|
||||
Indicates when there's an active search. This is used to determine when the contacts
|
||||
access footer should be hidden in order to list the results from the server.
|
||||
*/
|
||||
@property (nonatomic) BOOL contactsAreFilteredWithSearch;
|
||||
|
||||
/**
|
||||
If YES, the table view will scroll at the top on the next data source refresh.
|
||||
It comes back to NO after each refresh.
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#define CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT 30.0
|
||||
#define CONTACTS_TABLEVC_LOCALCONTACTS_SECTION_HEADER_HEIGHT 65.0
|
||||
|
||||
@interface ContactsTableViewController ()
|
||||
@interface ContactsTableViewController () <FindYourContactsFooterViewDelegate, ServiceTermsModalCoordinatorBridgePresenterDelegate>
|
||||
{
|
||||
/**
|
||||
Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar.
|
||||
@@ -41,6 +41,10 @@
|
||||
id kThemeServiceDidChangeThemeNotificationObserver;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) FindYourContactsFooterView *findYourContactsFooterView;
|
||||
|
||||
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ContactsTableViewController
|
||||
@@ -65,6 +69,10 @@
|
||||
{
|
||||
[super finalizeInit];
|
||||
|
||||
// By default, allow the find your contacts footer to be
|
||||
// shown when local contacts sync hasn't been enabled.
|
||||
self.disableFindYourContactsFooter = NO;
|
||||
|
||||
// Setup `MXKViewControllerHandling` properties
|
||||
self.enableBarTintColorStatusChange = NO;
|
||||
self.rageShakeManager = [RageShakeManager sharedManager];
|
||||
@@ -92,6 +100,7 @@
|
||||
|
||||
// Hide line separators of empty cells
|
||||
self.contactsTableView.tableFooterView = [[UIView alloc] init];
|
||||
self.contactsAreFilteredWithSearch = NO;
|
||||
|
||||
// Observe user interface theme change.
|
||||
kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
@@ -150,20 +159,6 @@
|
||||
// Screen tracking
|
||||
[[Analytics sharedInstance] trackScreen:_screenName];
|
||||
|
||||
if (BuildSettings.allowLocalContactsAccess)
|
||||
{
|
||||
// Check whether the access to the local contacts has not been already asked
|
||||
// and check that the user has decided to use or not to use an identity server
|
||||
if ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusNotDetermined
|
||||
|| !contactsDataSource.mxSession.hasAccountDataIdentityServerValue)
|
||||
{
|
||||
// Allow by default the local contacts sync in order to discover matrix users.
|
||||
// This setting change will trigger the loading of the local contacts, which will automatically
|
||||
// ask user permission to access their local contacts.
|
||||
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Observe kAppDelegateDidTapStatusBarNotification.
|
||||
kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
|
||||
@@ -171,16 +166,18 @@
|
||||
|
||||
}];
|
||||
|
||||
// Load the local contacts for display.
|
||||
[self refreshLocalContacts];
|
||||
[self refreshContactsTable];
|
||||
|
||||
// Show the contacts access footer if necessary.
|
||||
[self updateFooterViewVisibility];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// Load the local contacts for display.
|
||||
// In viewDidAppear as it may trigger a request for contacts access.
|
||||
[self refreshLocalContacts];
|
||||
[super viewDidLayoutSubviews];
|
||||
[self updateFooterViewHeight];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
@@ -203,6 +200,83 @@
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
Creates a new `FindYourContactsFooterView` and caches it in
|
||||
the `findYourContactsFooterView` property before returning it for use.
|
||||
*/
|
||||
- (FindYourContactsFooterView*)makeFooterView
|
||||
{
|
||||
FindYourContactsFooterView *footerView = [FindYourContactsFooterView instantiate];
|
||||
footerView.delegate = self;
|
||||
|
||||
self.findYourContactsFooterView = footerView;
|
||||
|
||||
return footerView;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks whether local contacts sync is ready to use or if there are any search results
|
||||
in the table, hiding the find your contacts footer if so. Otherwise the footer is shown
|
||||
so long as it hasn't been disabled.
|
||||
*/
|
||||
- (void)updateFooterViewVisibility
|
||||
{
|
||||
if (!BuildSettings.allowLocalContactsAccess || self.disableFindYourContactsFooter)
|
||||
{
|
||||
self.contactsTableView.tableFooterView = [[UIView alloc] init];
|
||||
return;
|
||||
}
|
||||
|
||||
// With contacts access granted, contact sync enabled and an identity server, the footer can be hidden.
|
||||
if ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized
|
||||
&& MXKAppSettings.standardAppSettings.syncLocalContacts
|
||||
&& contactsDataSource.mxSession.identityService.areAllTermsAgreed)
|
||||
{
|
||||
self.contactsTableView.tableFooterView = [[UIView alloc] init];
|
||||
return;
|
||||
}
|
||||
|
||||
// If the footer is to be shown, hide it when there's an active search.
|
||||
if (self.contactsAreFilteredWithSearch)
|
||||
{
|
||||
self.contactsTableView.tableFooterView = [[UIView alloc] init];
|
||||
return;
|
||||
}
|
||||
|
||||
self.contactsTableView.tableFooterView = self.findYourContactsFooterView ?: [self makeFooterView];
|
||||
[self updateFooterViewHeight];
|
||||
}
|
||||
|
||||
/**
|
||||
Updates the height of the find your contacts footer to fill all available space.
|
||||
*/
|
||||
- (void)updateFooterViewHeight
|
||||
{
|
||||
if (self.findYourContactsFooterView && self.findYourContactsFooterView == self.contactsTableView.tableFooterView)
|
||||
{
|
||||
// Calculate the natural size of the footer
|
||||
CGSize fittingSize = CGSizeMake(self.view.frame.size.width, UILayoutFittingCompressedSize.height);
|
||||
CGSize footerSize = [self.findYourContactsFooterView systemLayoutSizeFittingSize:fittingSize];
|
||||
|
||||
// Calculate the height available for the footer
|
||||
CGFloat availableHeight = self.contactsTableView.bounds.size.height - self.contactsTableView.adjustedContentInset.top - self.contactsTableView.adjustedContentInset.bottom;
|
||||
if (self.contactsTableView.tableHeaderView)
|
||||
{
|
||||
availableHeight -= self.contactsTableView.tableHeaderView.frame.size.height;
|
||||
}
|
||||
|
||||
// Fill all available height unless the footer is larger, in which case use its natural height
|
||||
CGFloat finalHeight = availableHeight > footerSize.height ? availableHeight : footerSize.height;
|
||||
self.findYourContactsFooterView.frame = CGRectMake(self.findYourContactsFooterView.frame.origin.x,
|
||||
self.findYourContactsFooterView.frame.origin.y,
|
||||
self.findYourContactsFooterView.frame.size.width,
|
||||
finalHeight);
|
||||
|
||||
// This assignment is technically redundant, but does prompt the table view to recalculate its content size
|
||||
self.contactsTableView.tableFooterView = self.findYourContactsFooterView;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayList:(ContactsDataSource*)listDataSource
|
||||
{
|
||||
// Cancel registration on existing dataSource if any
|
||||
@@ -228,42 +302,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not scan local contacts in background if the user has not decided yet about using
|
||||
// an identity server
|
||||
BOOL doRefreshLocalContacts = NO;
|
||||
for (MXSession *session in self.mxSessions)
|
||||
{
|
||||
if (session.hasAccountDataIdentityServerValue)
|
||||
{
|
||||
doRefreshLocalContacts = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the application is allowed to access the local contacts.
|
||||
if (doRefreshLocalContacts
|
||||
if (MXKAppSettings.standardAppSettings.syncLocalContacts
|
||||
&& contactsDataSource.mxSession.identityService.areAllTermsAgreed
|
||||
&& [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
|
||||
{
|
||||
// Check the user permission for syncing local contacts. This permission was handled independently on previous application version.
|
||||
if (![MXKAppSettings standardAppSettings].syncLocalContacts)
|
||||
{
|
||||
// Check whether it was not requested yet.
|
||||
if (![MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested)
|
||||
{
|
||||
[MXKAppSettings standardAppSettings].syncLocalContactsPermissionRequested = YES;
|
||||
|
||||
[MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) {
|
||||
|
||||
if (granted)
|
||||
{
|
||||
// Allow local contacts sync in order to discover matrix users.
|
||||
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the local contacts list.
|
||||
[[MXKContactManager sharedManager] refreshLocalContacts];
|
||||
}
|
||||
@@ -292,7 +334,7 @@
|
||||
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
|
||||
NSIndexPath *currentSelectedCellIndexPath = nil;
|
||||
MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController;
|
||||
if (masterTabBarController.currentContactDetailViewController)
|
||||
if (masterTabBarController.selectedContact)
|
||||
{
|
||||
// Look for the rank of this selected contact in displayed recents
|
||||
currentSelectedCellIndexPath = [contactsDataSource cellIndexPathWithContact:masterTabBarController.selectedContact];
|
||||
@@ -321,6 +363,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setContactsAreFilteredWithSearch:(BOOL)contactsAreFilteredWithSearch
|
||||
{
|
||||
// Filter out redundant assignments.
|
||||
if (_contactsAreFilteredWithSearch != contactsAreFilteredWithSearch)
|
||||
{
|
||||
_contactsAreFilteredWithSearch = contactsAreFilteredWithSearch;
|
||||
[self updateFooterViewVisibility];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSourceDelegate
|
||||
|
||||
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
|
||||
@@ -426,6 +478,8 @@
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
[contactsDataSource searchWithPattern:searchText forceReset:NO];
|
||||
|
||||
self.contactsAreFilteredWithSearch = searchText.length ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
||||
@@ -460,4 +514,126 @@
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - FindYourContactsFooterViewDelegate
|
||||
|
||||
- (void)contactsFooterViewDidRequestFindContacts:(FindYourContactsFooterView *)footerView
|
||||
{
|
||||
// First check the identity if service terms have already been accepted
|
||||
if (self->contactsDataSource.mxSession.identityService.areAllTermsAgreed)
|
||||
{
|
||||
// If they have we only require local contacts access.
|
||||
[self checkAccessForContacts];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
// The preparation can take some time so indicate this to the user
|
||||
[self startActivityIndicator];
|
||||
footerView.isActionEnabled = NO;
|
||||
|
||||
[self->contactsDataSource.mxSession prepareIdentityServiceForTermsWithDefault:RiotSettings.shared.identityServerUrlString
|
||||
success:^(MXSession *session, NSString *baseURL, NSString *accessToken) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self stopActivityIndicator];
|
||||
footerView.isActionEnabled = YES;
|
||||
|
||||
// Present the terms of the identity server.
|
||||
[self presentIdentityServerTermsWithSession:session baseURL:baseURL andAccessToken:accessToken];
|
||||
} failure:^(NSError *error) {
|
||||
// The error was already logged before the block is called
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self stopActivityIndicator];
|
||||
footerView.isActionEnabled = YES;
|
||||
|
||||
// Alert the user that something went wrong.
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:VectorL10n.findYourContactsIdentityServiceError
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:MatrixKitL10n.ok
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:nil]];
|
||||
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkAccessForContacts
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
// Check for contacts access, showing a pop-up if necessary.
|
||||
[MXKTools checkAccessForContacts:VectorL10n.contactsAddressBookPermissionDeniedAlertTitle
|
||||
withManualChangeMessage:VectorL10n.contactsAddressBookPermissionDeniedAlertMessage
|
||||
showPopUpInViewController:self
|
||||
completionHandler:^(BOOL granted) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
if (granted)
|
||||
{
|
||||
// When granted, local contacts can be shown.
|
||||
[self showLocalContacts];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showLocalContacts
|
||||
{
|
||||
// Enable local contacts sync and display.
|
||||
MXKAppSettings.standardAppSettings.syncLocalContacts = YES;
|
||||
self->contactsDataSource.showLocalContacts = YES;
|
||||
|
||||
// Attempt to refresh the contacts manager.
|
||||
[self refreshLocalContacts];
|
||||
|
||||
// Hide the find your contacts footer.
|
||||
[self updateFooterViewVisibility];
|
||||
}
|
||||
|
||||
#pragma mark - Identity server service terms
|
||||
|
||||
- (void)presentIdentityServerTermsWithSession:(MXSession*)mxSession baseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken
|
||||
{
|
||||
if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession
|
||||
baseUrl:baseURL
|
||||
serviceType:MXServiceTypeIdentityService
|
||||
accessToken:accessToken];
|
||||
|
||||
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
|
||||
|
||||
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
|
||||
}
|
||||
|
||||
#pragma mark ServiceTermsModalCoordinatorBridgePresenterDelegate
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
[self checkAccessForContacts];
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter *)coordinatorBridgePresenter session:(MXSession *)session
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -50,6 +50,14 @@ typedef enum : NSUInteger
|
||||
NSMutableArray<MXKContact*> *filteredMatrixContacts;
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the data source should include local contacts in the table view. The default
|
||||
value is set at initialisation to match the `MXKAppSettings` value for `syncLocalContacts`.
|
||||
Note: After updating this property, the table view's data will need to be reloaded for it to have
|
||||
any effect.
|
||||
*/
|
||||
@property (nonatomic) BOOL showLocalContacts;
|
||||
|
||||
/**
|
||||
Get the contact at the given index path.
|
||||
|
||||
|
||||
@@ -92,6 +92,16 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
self = [super initWithMatrixSession:mxSession];
|
||||
if (self) {
|
||||
// Only show local contacts when contact sync is enabled and the identity server terms of service have been accepted.
|
||||
_showLocalContacts = MXKAppSettings.standardAppSettings.syncLocalContacts && self.mxSession.identityService.areAllTermsAgreed;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKContactManagerDidUpdateMatrixContactsNotification object:nil];
|
||||
@@ -473,8 +483,8 @@
|
||||
searchInputSection = count++;
|
||||
}
|
||||
|
||||
// Keep visible the header for the both contact sections, even if their are empty.
|
||||
if (BuildSettings.allowLocalContactsAccess)
|
||||
// Keep visible the header for the both contact sections, even if they're are empty.
|
||||
if (BuildSettings.allowLocalContactsAccess && self.showLocalContacts && [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
|
||||
{
|
||||
filteredLocalContactsSection = count++;
|
||||
}
|
||||
@@ -489,7 +499,7 @@
|
||||
}
|
||||
|
||||
// Keep visible the local contact header, even if the section is empty.
|
||||
if (BuildSettings.allowLocalContactsAccess)
|
||||
if (BuildSettings.allowLocalContactsAccess && self.showLocalContacts && [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
|
||||
{
|
||||
filteredLocalContactsSection = count++;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Contacts ContactDetails
|
||||
/*
|
||||
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
|
||||
import UIKit
|
||||
|
||||
final class ContactDetailsCoordinator: ContactDetailsCoordinatorProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: ContactDetailsCoordinatorParameters
|
||||
private let contactDetailsViewController: ContactDetailsViewController
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: ContactDetailsCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: ContactDetailsCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
let contactDetailsViewController: ContactDetailsViewController = ContactDetailsViewController.instantiate()
|
||||
contactDetailsViewController.contact = self.parameters.contact
|
||||
contactDetailsViewController.enableVoipCall = self.parameters.enableVoipCall
|
||||
self.contactDetailsViewController = contactDetailsViewController
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.contactDetailsViewController
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Contacts ContactDetails
|
||||
/*
|
||||
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
|
||||
|
||||
/// ContactDetailsCoordinator input parameters
|
||||
struct ContactDetailsCoordinatorParameters {
|
||||
|
||||
/// The displayed contact
|
||||
let contact: MXKContact
|
||||
|
||||
/// Enable voip call (voice/video). NO by default
|
||||
let enableVoipCall: Bool
|
||||
|
||||
init(contact: MXKContact,
|
||||
enableVoipCall: Bool = false) {
|
||||
self.contact = contact
|
||||
self.enableVoipCall = enableVoipCall
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Contacts ContactDetails
|
||||
/*
|
||||
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
|
||||
|
||||
protocol ContactDetailsCoordinatorDelegate: AnyObject {
|
||||
func contactDetailsCoordinatorDidCancel(_ coordinator: ContactDetailsCoordinatorProtocol)
|
||||
}
|
||||
|
||||
/// `ContactDetailsCoordinatorProtocol` is a protocol describing a Coordinator that handle contact details navigation flow.
|
||||
protocol ContactDetailsCoordinatorProtocol: Coordinator, Presentable {
|
||||
var delegate: ContactDetailsCoordinatorDelegate? { get }
|
||||
}
|
||||
@@ -69,7 +69,7 @@ typedef enum : NSUInteger
|
||||
@discussion This is the designated initializer for programmatic instantiation.
|
||||
@return An initialized `ContactDetailsViewController` object if successful, `nil` otherwise.
|
||||
*/
|
||||
+ (instancetype)contactDetailsViewController;
|
||||
+ (instancetype)instantiate;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
}
|
||||
|
||||
+ (instancetype)contactDetailsViewController
|
||||
+ (instancetype)instantiate
|
||||
{
|
||||
return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
@@ -142,6 +142,9 @@
|
||||
// Define directly the navigation titleView with the custom title view instance. Do not use anymore a container.
|
||||
self.navigationItem.titleView = contactTitleView;
|
||||
|
||||
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
|
||||
self.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
|
||||
[tap setNumberOfTouchesRequired:1];
|
||||
[tap setNumberOfTapsRequired:1];
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// 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 UIKit
|
||||
import Reusable
|
||||
|
||||
@objc protocol FindYourContactsFooterViewDelegate {
|
||||
func contactsFooterViewDidRequestFindContacts(_ footerView: FindYourContactsFooterView)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
class FindYourContactsFooterView: UIView, NibLoadable, Themable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
weak var delegate: FindYourContactsFooterViewDelegate?
|
||||
|
||||
/// Whether or not the view's button responds to taps.
|
||||
var isActionEnabled: Bool {
|
||||
get { button.isEnabled }
|
||||
set { button.isEnabled = newValue }
|
||||
}
|
||||
|
||||
@IBOutlet weak private var containerView: UIView!
|
||||
@IBOutlet weak private var titleLabel: UILabel!
|
||||
@IBOutlet weak private var messageLabel: UILabel!
|
||||
@IBOutlet weak private var button: CustomRoundedButton!
|
||||
@IBOutlet weak private var footerLabel: UILabel!
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
static func instantiate() -> Self {
|
||||
let view = Self.loadFromNib()
|
||||
view.update(theme: ThemeService.shared().theme)
|
||||
return view
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
containerView.layer.cornerRadius = 8
|
||||
button.layer.cornerRadius = 8
|
||||
|
||||
titleLabel.text = VectorL10n.findYourContactsTitle
|
||||
messageLabel.text = VectorL10n.findYourContactsMessage(BuildSettings.bundleDisplayName)
|
||||
button.setTitle(VectorL10n.findYourContactsButtonTitle, for: .normal)
|
||||
footerLabel.text = VectorL10n.findYourContactsFooter
|
||||
}
|
||||
|
||||
func update(theme: Theme) {
|
||||
tintColor = theme.colors.accent
|
||||
|
||||
containerView.backgroundColor = theme.colors.quinaryContent
|
||||
|
||||
titleLabel.font = theme.fonts.bodySB
|
||||
titleLabel.textColor = theme.colors.primaryContent
|
||||
|
||||
messageLabel.font = theme.fonts.body
|
||||
messageLabel.textColor = theme.colors.secondaryContent
|
||||
|
||||
button.titleLabel?.font = theme.fonts.body
|
||||
button.backgroundColor = theme.colors.accent
|
||||
button.setTitleColor(theme.colors.background, for: .normal)
|
||||
|
||||
footerLabel.font = theme.fonts.footnote.withSize(13)
|
||||
footerLabel.textColor = theme.colors.tertiaryContent
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
@IBAction private func buttonAction(_ sender: Any) {
|
||||
delegate?.contactsFooterViewDidRequestFindContacts(self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="FindYourContactsFooterView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="424"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="67f-7H-bSH">
|
||||
<rect key="frame" x="16" y="45.5" width="288" height="362.5"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="find_your_contacts_facepile" translatesAutoresizingMaskIntoConstraints="NO" id="q3p-2U-JA5">
|
||||
<rect key="frame" x="89" y="46" width="110" height="46"/>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Start by listing your contacts" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9oC-ce-hpT">
|
||||
<rect key="frame" x="20" y="138" width="248" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Let Element show your contacts so you can quickly start chatting with those you know best." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B3j-1i-EAf">
|
||||
<rect key="frame" x="20" y="171" width="248" height="61"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v93-cs-jXa" customClass="CustomRoundedButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="16" y="257" width="256" height="45"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="45" id="Spc-d4-SvP"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Allow contacts access"/>
|
||||
<connections>
|
||||
<action selector="buttonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="XLO-8r-P8S"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This can be disabled anytime from settings" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0A6-6s-nu6">
|
||||
<rect key="frame" x="20" y="314" width="248" height="31.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="29" id="MZF-wd-sd6"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="opaqueSeparatorColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="0A6-6s-nu6" firstAttribute="top" secondItem="v93-cs-jXa" secondAttribute="bottom" constant="12" id="9zY-sf-Hrg"/>
|
||||
<constraint firstItem="q3p-2U-JA5" firstAttribute="top" secondItem="67f-7H-bSH" secondAttribute="top" constant="46" id="Bpl-IE-fhm"/>
|
||||
<constraint firstAttribute="trailing" secondItem="9oC-ce-hpT" secondAttribute="trailing" constant="20" symbolic="YES" id="D9c-Dn-jPU"/>
|
||||
<constraint firstAttribute="bottom" secondItem="0A6-6s-nu6" secondAttribute="bottom" constant="17" id="EYu-Qu-iQn"/>
|
||||
<constraint firstAttribute="trailing" secondItem="B3j-1i-EAf" secondAttribute="trailing" constant="20" symbolic="YES" id="Hg6-yF-XYk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0A6-6s-nu6" secondAttribute="trailing" constant="20" symbolic="YES" id="HpJ-VI-vji"/>
|
||||
<constraint firstItem="v93-cs-jXa" firstAttribute="top" secondItem="B3j-1i-EAf" secondAttribute="bottom" constant="25" id="NCh-1k-Wmw"/>
|
||||
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="343" id="Ty6-Fa-X0Y"/>
|
||||
<constraint firstItem="v93-cs-jXa" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="16" id="aF8-bI-HgQ"/>
|
||||
<constraint firstItem="9oC-ce-hpT" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="20" symbolic="YES" id="bli-Ms-T40"/>
|
||||
<constraint firstItem="B3j-1i-EAf" firstAttribute="top" secondItem="9oC-ce-hpT" secondAttribute="bottom" constant="12" id="dAh-bo-3yO"/>
|
||||
<constraint firstItem="q3p-2U-JA5" firstAttribute="centerX" secondItem="67f-7H-bSH" secondAttribute="centerX" id="giT-QS-qOz"/>
|
||||
<constraint firstAttribute="trailing" secondItem="v93-cs-jXa" secondAttribute="trailing" constant="16" id="mWk-gL-jIf"/>
|
||||
<constraint firstItem="B3j-1i-EAf" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="20" symbolic="YES" id="pgw-mQ-EiG"/>
|
||||
<constraint firstItem="0A6-6s-nu6" firstAttribute="leading" secondItem="67f-7H-bSH" secondAttribute="leading" constant="20" symbolic="YES" id="r3U-zK-ma2"/>
|
||||
<constraint firstItem="9oC-ce-hpT" firstAttribute="top" secondItem="q3p-2U-JA5" secondAttribute="bottom" constant="46" id="s1P-kS-bhX"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<constraints>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="67f-7H-bSH" secondAttribute="bottom" constant="16" id="6sY-dx-OLw"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="67f-7H-bSH" secondAttribute="trailing" constant="16" id="NHG-OU-6GO"/>
|
||||
<constraint firstItem="67f-7H-bSH" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="QLf-8P-5HB"/>
|
||||
<constraint firstItem="67f-7H-bSH" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="fqj-2T-EL6"/>
|
||||
<constraint firstItem="67f-7H-bSH" firstAttribute="top" relation="greaterThanOrEqual" secondItem="iN0-l3-epB" secondAttribute="top" constant="16" id="yU6-gF-alq"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="button" destination="v93-cs-jXa" id="ANT-X6-MXQ"/>
|
||||
<outlet property="containerView" destination="67f-7H-bSH" id="jcP-BN-jNZ"/>
|
||||
<outlet property="footerLabel" destination="0A6-6s-nu6" id="GOr-Nv-eVE"/>
|
||||
<outlet property="messageLabel" destination="B3j-1i-EAf" id="Edi-X9-bma"/>
|
||||
<outlet property="titleLabel" destination="9oC-ce-hpT" id="rOi-fH-kO0"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="63.768115942028992" y="308.70535714285711"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="find_your_contacts_facepile" width="110" height="46"/>
|
||||
<systemColor name="opaqueSeparatorColor">
|
||||
<color red="0.77647058823529413" green="0.77647058823529413" blue="0.78431372549019607" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -243,7 +243,7 @@
|
||||
|
||||
// Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller.
|
||||
NSIndexPath *currentSelectedCellIndexPath = nil;
|
||||
if (masterTabBarController.currentRoomViewController)
|
||||
if (masterTabBarController.selectedRoomId)
|
||||
{
|
||||
// Look for the rank of this selected room in displayed recents
|
||||
currentSelectedCellIndexPath = [dataSource cellIndexPathWithRoomId:masterTabBarController.selectedRoomId andMatrixSession:masterTabBarController.selectedRoomSession];
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
[titles addObject:[VectorL10n searchPeople]];
|
||||
peopleSearchViewController = [ContactsTableViewController contactsTableViewController];
|
||||
peopleSearchViewController.contactsTableViewControllerDelegate = self;
|
||||
peopleSearchViewController.disableFindYourContactsFooter = YES;
|
||||
[viewControllers addObject:peopleSearchViewController];
|
||||
|
||||
// add Files tab
|
||||
@@ -247,6 +248,7 @@
|
||||
|
||||
// Init the search for people
|
||||
peopleSearchDataSource = [[ContactsDataSource alloc] initWithMatrixSession:mainSession];
|
||||
peopleSearchDataSource.showLocalContacts = NO;
|
||||
peopleSearchDataSource.areSectionsShrinkable = YES;
|
||||
peopleSearchDataSource.displaySearchInputInContactsList = YES;
|
||||
peopleSearchDataSource.contactCellAccessoryImage = [[UIImage imageNamed: @"disclosure_icon"] vc_tintedImageUsingColor:ThemeService.shared.theme.textSecondaryColor];;
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
}
|
||||
|
||||
[self.roomAvatar vc_setRoomAvatarImageWith:roomCellData.avatarUrl
|
||||
roomId:roomCellData.roomIdentifier
|
||||
displayName:roomCellData.roomDisplayname
|
||||
mediaManager:roomCellData.mxSession.mediaManager];
|
||||
}
|
||||
|
||||
@@ -745,7 +745,6 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
|
||||
|
||||
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession baseUrl:config.baseUrl
|
||||
serviceType:MXServiceTypeIntegrationManager
|
||||
outOfContext:NO
|
||||
accessToken:config.scalarToken];
|
||||
|
||||
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
|
||||
@@ -762,14 +761,6 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
@@ -778,4 +769,9 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -151,7 +151,6 @@
|
||||
|
||||
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:widget.mxSession baseUrl:config.baseUrl
|
||||
serviceType:MXServiceTypeIntegrationManager
|
||||
outOfContext:NO
|
||||
accessToken:config.scalarToken];
|
||||
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
|
||||
|
||||
@@ -173,16 +172,15 @@
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -656,8 +656,7 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
||||
MXLogDebug(@"[WidgetVC] presentTerms for %@", config.baseUrl);
|
||||
|
||||
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:widget.mxSession baseUrl:config.baseUrl
|
||||
serviceType:MXServiceTypeIntegrationManager
|
||||
outOfContext:NO
|
||||
serviceType:MXServiceTypeIntegrationManager
|
||||
accessToken:config.scalarToken];
|
||||
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
|
||||
|
||||
@@ -677,14 +676,6 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
@@ -693,4 +684,9 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -61,7 +61,7 @@ final class KeyBackupRecoverCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
MXLog.debug("[KeyBackupRecoverCoordinatorBridgePresenter] Push complete security from \(navigationController)")
|
||||
|
||||
let navigationRouter = NavigationRouter(navigationController: navigationController)
|
||||
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
|
||||
|
||||
let keyBackupSetupCoordinator = KeyBackupRecoverCoordinator(session: self.session, keyBackupVersion: keyBackupVersion, navigationRouter: navigationRouter)
|
||||
keyBackupSetupCoordinator.delegate = self
|
||||
|
||||
@@ -101,7 +101,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Push complete security from \(navigationController)")
|
||||
|
||||
let navigationRouter = NavigationRouter(navigationController: navigationController)
|
||||
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
|
||||
|
||||
let keyVerificationCoordinator = KeyVerificationCoordinator(session: self.session, flow: .completeSecurity(isNewSignIn), navigationRouter: navigationRouter)
|
||||
keyVerificationCoordinator.delegate = self
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room Room
|
||||
/*
|
||||
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
|
||||
import UIKit
|
||||
|
||||
final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: RoomCoordinatorParameters
|
||||
private let roomViewController: RoomViewController
|
||||
private let activityIndicatorPresenter: ActivityIndicatorPresenterType
|
||||
private var selectedEventId: String?
|
||||
|
||||
private var roomDataSourceManager: MXKRoomDataSourceManager {
|
||||
return MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
|
||||
}
|
||||
|
||||
/// Indicate true if the Coordinator has started once
|
||||
private var hasStartedOnce: Bool {
|
||||
return self.roomViewController.delegate != nil
|
||||
}
|
||||
|
||||
private var navigationRouter: NavigationRouterType? {
|
||||
|
||||
var finalNavigationRouter: NavigationRouterType?
|
||||
|
||||
if let navigationRouter = self.parameters.navigationRouter {
|
||||
finalNavigationRouter = navigationRouter
|
||||
} else if let navigationRouterStore = self.parameters.navigationRouterStore, let currentNavigationController = self.roomViewController.navigationController {
|
||||
// If no navigationRouter has been provided, try to get the navigation router from the current RoomViewController navigation controller if exists
|
||||
finalNavigationRouter = navigationRouterStore.navigationRouter(for: currentNavigationController)
|
||||
}
|
||||
|
||||
return finalNavigationRouter
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: RoomCoordinatorDelegate?
|
||||
|
||||
var canReleaseRoomDataSource: Bool {
|
||||
// If the displayed data is not a preview, let the manager release the room data source
|
||||
// (except if the view controller has the room data source ownership).
|
||||
return self.parameters.previewData == nil && self.roomViewController.roomDataSource != nil && self.roomViewController.hasRoomDataSourceOwnership == false
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: RoomCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
self.selectedEventId = parameters.eventId
|
||||
|
||||
self.roomViewController = RoomViewController.instantiate()
|
||||
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
self.start(withCompletion: nil)
|
||||
}
|
||||
|
||||
// NOTE: Completion closure has been added for legacy architecture purpose.
|
||||
// Remove this completion after LegacyAppDelegate refactor.
|
||||
func start(withCompletion completion: (() -> Void)?) {
|
||||
self.roomViewController.delegate = self
|
||||
|
||||
// Detect when view controller has been dismissed by gesture when presented modally (not in full screen).
|
||||
self.roomViewController.presentationController?.delegate = self
|
||||
|
||||
if let eventId = self.selectedEventId {
|
||||
self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion)
|
||||
} else {
|
||||
self.loadRoom(withId: self.parameters.roomId, completion: completion)
|
||||
}
|
||||
|
||||
// Add `roomViewController` to the NavigationRouter, only if it has been explicitly set as parameter
|
||||
if let navigationRouter = self.parameters.navigationRouter {
|
||||
if navigationRouter.modules.isEmpty == false {
|
||||
navigationRouter.push(self.roomViewController, animated: true, popCompletion: nil)
|
||||
} else {
|
||||
navigationRouter.setRootModule(self.roomViewController, popCompletion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func start(withEventId eventId: String, completion: (() -> Void)?) {
|
||||
|
||||
self.selectedEventId = eventId
|
||||
|
||||
if self.hasStartedOnce {
|
||||
self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion)
|
||||
} else {
|
||||
self.start(withCompletion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.roomViewController
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadRoom(withId roomId: String, completion: (() -> Void)?) {
|
||||
|
||||
// Present activity indicator when retrieving roomDataSource for given room ID
|
||||
self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false)
|
||||
|
||||
let roomDataSourceManager: MXKRoomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
|
||||
|
||||
// LIVE: Show the room live timeline managed by MXKRoomDataSourceManager
|
||||
roomDataSourceManager.roomDataSource(forRoom: roomId, create: true, onComplete: { [weak self] (roomDataSource) in
|
||||
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
|
||||
if let roomDataSource = roomDataSource {
|
||||
self.roomViewController.displayRoom(roomDataSource)
|
||||
}
|
||||
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
|
||||
private func loadRoom(withId roomId: String, and eventId: String, completion: (() -> Void)?) {
|
||||
|
||||
// Present activity indicator when retrieving roomDataSource for given room ID
|
||||
self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false)
|
||||
|
||||
// Open the room on the requested event
|
||||
RoomDataSource.load(withRoomId: roomId,
|
||||
initialEventId: eventId,
|
||||
andMatrixSession: self.parameters.session) { [weak self] (dataSource) in
|
||||
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
|
||||
guard let roomDataSource = dataSource as? RoomDataSource else {
|
||||
return
|
||||
}
|
||||
|
||||
roomDataSource.markTimelineInitialEvent = true
|
||||
self.roomViewController.displayRoom(roomDataSource)
|
||||
|
||||
// Give the data source ownership to the room view controller.
|
||||
self.roomViewController.hasRoomDataSourceOwnership = true
|
||||
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoomIdentifiable
|
||||
extension RoomCoordinator: RoomIdentifiable {
|
||||
|
||||
var roomId: String? {
|
||||
return self.parameters.roomId
|
||||
}
|
||||
|
||||
var mxSession: MXSession? {
|
||||
self.parameters.session
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
extension RoomCoordinator: UIAdaptivePresentationControllerDelegate {
|
||||
|
||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||
self.delegate?.roomCoordinatorDidDismissInteractively(self)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoomViewControllerDelegate
|
||||
extension RoomCoordinator: RoomViewControllerDelegate {
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String) {
|
||||
self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID)
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, showMemberDetails roomMember: MXRoomMember) {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
func roomViewControllerShowRoomDetails(_ roomViewController: RoomViewController) {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
func roomViewControllerDidLeaveRoom(_ roomViewController: RoomViewController) {
|
||||
self.delegate?.roomCoordinatorDidLeaveRoom(self)
|
||||
}
|
||||
|
||||
func roomViewControllerPreviewDidTapCancel(_ roomViewController: RoomViewController) {
|
||||
self.delegate?.roomCoordinatorDidCancelRoomPreview(self)
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, startChatWithUserId userId: String, completion: @escaping () -> Void) {
|
||||
AppDelegate.theDelegate().createDirectChat(withUserId: userId, completion: completion)
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, showCompleteSecurityFor session: MXSession) {
|
||||
AppDelegate.theDelegate().presentCompleteSecurity(for: session)
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, handleUniversalLinkFragment fragment: String, from universalLinkURL: URL?) -> Bool {
|
||||
return AppDelegate.theDelegate().handleUniversalLinkFragment(fragment, from: universalLinkURL)
|
||||
}
|
||||
|
||||
func roomViewController(_ roomViewController: RoomViewController, handleUniversalLinkURL universalLinkURL: URL) -> Bool {
|
||||
return AppDelegate.theDelegate().handleUniversalLinkURL(universalLinkURL)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@objc protocol RoomCoordinatorBridgePresenterDelegate {
|
||||
func roomCoordinatorBridgePresenterDidLeaveRoom(_ bridgePresenter: RoomCoordinatorBridgePresenter)
|
||||
func roomCoordinatorBridgePresenterDidCancelRoomPreview(_ bridgePresenter: RoomCoordinatorBridgePresenter)
|
||||
func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didSelectRoomWithId roomId: String)
|
||||
func roomCoordinatorBridgePresenterDidDismissInteractively(_ bridgePresenter: RoomCoordinatorBridgePresenter)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
class RoomCoordinatorBridgePresenterParameters: NSObject {
|
||||
|
||||
/// The matrix session in which the room should be available.
|
||||
let session: MXSession
|
||||
|
||||
/// The room identifier
|
||||
let roomId: String
|
||||
|
||||
/// If not nil, the room will be opened on this event.
|
||||
let eventId: String?
|
||||
|
||||
/// The data for the room preview.
|
||||
let previewData: RoomPreviewData?
|
||||
|
||||
init(session: MXSession,
|
||||
roomId: String,
|
||||
eventId: String?,
|
||||
previewData: RoomPreviewData?) {
|
||||
self.session = session
|
||||
self.roomId = roomId
|
||||
self.eventId = eventId
|
||||
self.previewData = previewData
|
||||
}
|
||||
}
|
||||
|
||||
/// RoomCoordinatorBridgePresenter enables to start RoomCoordinator from a view controller.
|
||||
/// This bridge is used while waiting for global usage of coordinator pattern.
|
||||
/// **WARNING**: This class breaks the Coordinator abstraction and it has been introduced for **Objective-C compatibility only** (mainly for integration in legacy view controllers). Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
|
||||
@objcMembers
|
||||
final class RoomCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let bridgeParameters: RoomCoordinatorBridgePresenterParameters
|
||||
private var coordinator: RoomCoordinator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
weak var delegate: RoomCoordinatorBridgePresenterDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: RoomCoordinatorBridgePresenterParameters) {
|
||||
self.bridgeParameters = parameters
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func present(from viewController: UIViewController, animated: Bool) {
|
||||
|
||||
let coordinator = self.createRoomCoordinator()
|
||||
coordinator.delegate = self
|
||||
let presentable = coordinator.toPresentable()
|
||||
presentable.modalPresentationStyle = .formSheet
|
||||
viewController.present(presentable, animated: animated, completion: nil)
|
||||
coordinator.start()
|
||||
|
||||
self.coordinator = coordinator
|
||||
}
|
||||
|
||||
func push(from navigationController: UINavigationController, animated: Bool) {
|
||||
|
||||
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
|
||||
|
||||
let coordinator = self.createRoomCoordinator(with: navigationRouter)
|
||||
coordinator.delegate = self
|
||||
coordinator.start() // Will trigger view controller push
|
||||
|
||||
self.coordinator = coordinator
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool, completion: (() -> Void)?) {
|
||||
guard let coordinator = self.coordinator else {
|
||||
return
|
||||
}
|
||||
coordinator.toPresentable().dismiss(animated: animated) {
|
||||
self.coordinator = nil
|
||||
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func createRoomCoordinator(with navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController())) -> RoomCoordinator {
|
||||
|
||||
let coordinatorParameters: RoomCoordinatorParameters
|
||||
|
||||
if let previewData = self.bridgeParameters.previewData {
|
||||
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, previewData: previewData)
|
||||
} else {
|
||||
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, session: self.bridgeParameters.session, roomId: self.bridgeParameters.roomId, eventId: self.bridgeParameters.eventId)
|
||||
}
|
||||
|
||||
return RoomCoordinator(parameters: coordinatorParameters)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoomNotificationSettingsCoordinatorDelegate
|
||||
extension RoomCoordinatorBridgePresenter: RoomCoordinatorDelegate {
|
||||
|
||||
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) {
|
||||
self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId)
|
||||
}
|
||||
|
||||
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
|
||||
self.delegate?.roomCoordinatorBridgePresenterDidLeaveRoom(self)
|
||||
}
|
||||
|
||||
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {
|
||||
self.delegate?.roomCoordinatorBridgePresenterDidCancelRoomPreview(self)
|
||||
}
|
||||
|
||||
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) {
|
||||
self.delegate?.roomCoordinatorBridgePresenterDidDismissInteractively(self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// RoomCoordinator input parameters
|
||||
struct RoomCoordinatorParameters {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The navigation router that manage physical navigation
|
||||
let navigationRouter: NavigationRouterType?
|
||||
|
||||
/// The navigation router store that enables to get a NavigationRouter from a navigation controller
|
||||
/// `navigationRouter` property takes priority on `navigationRouterStore`
|
||||
let navigationRouterStore: NavigationRouterStoreProtocol?
|
||||
|
||||
/// The matrix session in which the room should be available.
|
||||
let session: MXSession
|
||||
|
||||
/// The room identifier
|
||||
let roomId: String
|
||||
|
||||
/// If not nil, the room will be opened on this event.
|
||||
let eventId: String?
|
||||
|
||||
/// The data for the room preview.
|
||||
let previewData: RoomPreviewData?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private init(navigationRouter: NavigationRouterType?,
|
||||
navigationRouterStore: NavigationRouterStoreProtocol?,
|
||||
session: MXSession,
|
||||
roomId: String,
|
||||
eventId: String?,
|
||||
previewData: RoomPreviewData?) {
|
||||
self.navigationRouter = navigationRouter
|
||||
self.navigationRouterStore = navigationRouterStore
|
||||
self.session = session
|
||||
self.roomId = roomId
|
||||
self.eventId = eventId
|
||||
self.previewData = previewData
|
||||
}
|
||||
|
||||
/// Init to present a joined room
|
||||
init(navigationRouter: NavigationRouterType? = nil,
|
||||
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
|
||||
session: MXSession,
|
||||
roomId: String,
|
||||
eventId: String? = nil) {
|
||||
|
||||
self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: session, roomId: roomId, eventId: eventId, previewData: nil)
|
||||
}
|
||||
|
||||
/// Init to present a room preview
|
||||
init(navigationRouter: NavigationRouterType? = nil,
|
||||
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
|
||||
previewData: RoomPreviewData) {
|
||||
|
||||
self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: previewData.mxSession, roomId: previewData.roomId, eventId: nil, previewData: previewData)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// File created from ScreenTemplate
|
||||
// $ createScreen.sh Room Room
|
||||
/*
|
||||
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
|
||||
|
||||
protocol RoomCoordinatorDelegate: AnyObject {
|
||||
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol)
|
||||
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol)
|
||||
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String)
|
||||
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol)
|
||||
}
|
||||
|
||||
/// `RoomCoordinatorProtocol` is a protocol describing a Coordinator that handle room navigation flow.
|
||||
protocol RoomCoordinatorProtocol: Coordinator, Presentable, RoomIdentifiable {
|
||||
var delegate: RoomCoordinatorDelegate? { get }
|
||||
|
||||
// Indicate if the underlying RoomDataSource can be released
|
||||
var canReleaseRoomDataSource: Bool { get }
|
||||
|
||||
/// Start the Coordinator with a setup completion.
|
||||
/// NOTE: Completion closure has been added for legacy architecture purpose.
|
||||
/// Remove this completion after LegacyAppDelegate refactor.
|
||||
/// - Parameters:
|
||||
/// - completion: called when the RoomDataSource has finish to load.
|
||||
func start(withCompletion completion: (() -> Void)?)
|
||||
|
||||
/// Use this method when the room screen is already shown and you want to go to a specific event.
|
||||
/// i.e User tap on push notification message for the current displayed room
|
||||
/// - Parameters:
|
||||
/// - eventId: The id of the event to display.
|
||||
/// - completion: called when the RoomDataSource has finish to load.
|
||||
func start(withEventId eventId: String, completion: (() -> Void)?)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// `RoomIdentifiable` describes an object tied to a specific room id.
|
||||
/// Useful to identify existing objects that should be removed when the user leaves a room for example.
|
||||
protocol RoomIdentifiable {
|
||||
var roomId: String? { get }
|
||||
var mxSession: MXSession? { get }
|
||||
}
|
||||
@@ -73,7 +73,7 @@ final class RoomInfoCoordinatorBridgePresenter: NSObject {
|
||||
}
|
||||
|
||||
func push(from navigationController: UINavigationController, animated: Bool) {
|
||||
let navigationRouter = NavigationRouter(navigationController: navigationController)
|
||||
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
|
||||
|
||||
let roomInfoCoordinator = RoomInfoCoordinator(parameters: self.coordinatorParameters, navigationRouter: navigationRouter)
|
||||
roomInfoCoordinator.delegate = self
|
||||
|
||||
@@ -410,6 +410,9 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
|
||||
[self vc_removeBackTitle];
|
||||
|
||||
// Display leftBarButtonItems or leftBarButtonItem to the right of the Back button
|
||||
self.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
|
||||
[self setupRemoveJitsiWidgetRemoveView];
|
||||
|
||||
// Replace the default input toolbar view.
|
||||
|
||||
@@ -37,9 +37,9 @@ final class ServiceTermsModalScreenCoordinator: ServiceTermsModalScreenCoordinat
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(serviceTerms: MXServiceTerms, outOfContext: Bool = false) {
|
||||
init(serviceTerms: MXServiceTerms) {
|
||||
|
||||
let serviceTermsModalScreenViewModel = ServiceTermsModalScreenViewModel(serviceTerms: serviceTerms, outOfContext: outOfContext)
|
||||
let serviceTermsModalScreenViewModel = ServiceTermsModalScreenViewModel(serviceTerms: serviceTerms)
|
||||
let serviceTermsModalScreenViewController = ServiceTermsModalScreenViewController.instantiate(with: serviceTermsModalScreenViewModel)
|
||||
self.serviceTermsModalScreenViewModel = serviceTermsModalScreenViewModel
|
||||
self.serviceTermsModalScreenViewController = serviceTermsModalScreenViewController
|
||||
@@ -70,8 +70,4 @@ extension ServiceTermsModalScreenCoordinator: ServiceTermsModalScreenViewModelCo
|
||||
func serviceTermsModalScreenViewModelDidDecline(_ viewModel: ServiceTermsModalScreenViewModelType) {
|
||||
self.delegate?.serviceTermsModalScreenCoordinatorDidDecline(self)
|
||||
}
|
||||
|
||||
func serviceTermsModalScreenViewModelDidCancel(_ viewModel: ServiceTermsModalScreenViewModelType) {
|
||||
self.delegate?.serviceTermsModalScreenCoordinatorDidCancel(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ protocol ServiceTermsModalScreenCoordinatorDelegate: AnyObject {
|
||||
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType)
|
||||
func serviceTermsModalScreenCoordinator(_ coordinator: ServiceTermsModalScreenCoordinatorType, displayPolicy policy: MXLoginPolicyData)
|
||||
func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType)
|
||||
func serviceTermsModalScreenCoordinatorDidCancel(_ coordinator: ServiceTermsModalScreenCoordinatorType)
|
||||
}
|
||||
|
||||
/// `ServiceTermsModalScreenCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
|
||||
|
||||
@@ -24,5 +24,4 @@ enum ServiceTermsModalScreenViewAction {
|
||||
case display(MXLoginPolicyData)
|
||||
case accept
|
||||
case decline
|
||||
case cancel
|
||||
}
|
||||
|
||||
+119
-75
@@ -1,12 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
|
||||
<device id="retina6_1" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -15,95 +14,131 @@
|
||||
<objects>
|
||||
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="ServiceTermsModalScreenViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9U2-KL-ZVA">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="852"/>
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="768"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="852"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt" userLabel="Content">
|
||||
<rect key="frame" x="5" y="0.0" width="365" height="768"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="852"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="gff-MZ-3bp">
|
||||
<rect key="frame" x="20" y="20" width="325" height="195.66666666666666"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="To continue you need to accept the Terms of this service." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
|
||||
<rect key="frame" x="20" y="20" width="374" height="100"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="100" id="1bP-8m-xrd"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="swq-xw-ItG">
|
||||
<rect key="frame" x="20" y="128" width="374" height="600"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="dcx-ju-f0Q">
|
||||
<rect key="frame" x="20" y="736" width="374" height="96"/>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="1000" image="find_your_contacts_facepile" translatesAutoresizingMaskIntoConstraints="NO" id="XWa-N5-VF2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="325" height="46"/>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="FZk-3I-Zbs">
|
||||
<rect key="frame" x="0.0" y="70" width="325" height="125.66666666666669"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="d2a-Dw-Pu5"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Accept"/>
|
||||
<connections>
|
||||
<action selector="acceptButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QHr-gh-dAD">
|
||||
<rect key="frame" x="0.0" y="52" width="374" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="y6d-Vg-5PP"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Decline">
|
||||
<color key="titleColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="declineButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="MTU-9k-8yo"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="To continue, accept the below terms and conditions" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
|
||||
<rect key="frame" x="0.0" y="0.0" width="325" height="40.666666666666664"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This will allow someone to find you if they have your phone number or email saved in their phone contacts." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5fA-rc-XMP">
|
||||
<rect key="frame" x="0.0" y="64.666666666666657" width="325" height="61"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</stackView>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" scrollEnabled="NO" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="swq-xw-ItG">
|
||||
<rect key="frame" x="0.0" y="235.66666666666669" width="365" height="120"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="bxI-mu-qng" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="1K2-u8-QsL"/>
|
||||
<constraint firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" constant="20" symbolic="YES" id="41D-TP-69f"/>
|
||||
<constraint firstItem="swq-xw-ItG" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="8" symbolic="YES" id="9Gg-Xb-o2W"/>
|
||||
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="20" symbolic="YES" id="W1m-x0-TyS"/>
|
||||
<constraint firstItem="dcx-ju-f0Q" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="Wcx-d6-M14"/>
|
||||
<constraint firstAttribute="trailing" secondItem="swq-xw-ItG" secondAttribute="trailing" constant="20" symbolic="YES" id="Y5v-Gg-xkM"/>
|
||||
<constraint firstAttribute="bottom" secondItem="dcx-ju-f0Q" secondAttribute="bottom" constant="20" symbolic="YES" id="cpa-Lq-Mvs"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dcx-ju-f0Q" secondAttribute="trailing" constant="20" symbolic="YES" id="eGO-kM-neh"/>
|
||||
<constraint firstAttribute="width" priority="750" constant="500" id="glD-Sz-73O"/>
|
||||
<constraint firstItem="dcx-ju-f0Q" firstAttribute="top" secondItem="swq-xw-ItG" secondAttribute="bottom" constant="8" symbolic="YES" id="zYm-2k-kvi"/>
|
||||
<constraint firstItem="swq-xw-ItG" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="ze1-Iw-v9U"/>
|
||||
<constraint firstAttribute="height" constant="120" id="phu-oG-oGN"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Service Terms Cell" textLabel="inf-zH-Iu8" style="IBUITableViewCellStyleDefault" id="krZ-0R-qfI">
|
||||
<rect key="frame" x="16" y="49" width="333" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="krZ-0R-qfI" id="I7f-2P-YXj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="333" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="inf-zH-Iu8">
|
||||
<rect key="frame" x="16" y="0.0" width="301" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="0.0"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
</tableView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="dcx-ju-f0Q">
|
||||
<rect key="frame" x="20" y="602" width="325" height="146"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This can be disabled anytime in settings." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qmt-nE-Lg7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="325" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="32" id="PCs-bC-vNT"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
|
||||
<rect key="frame" x="0.0" y="44" width="325" height="45"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="45" id="d2a-Dw-Pu5"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Accept"/>
|
||||
<connections>
|
||||
<action selector="acceptButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="uvI-tt-Nfj"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QHr-gh-dAD">
|
||||
<rect key="frame" x="0.0" y="101" width="325" height="45"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="45" id="y6d-Vg-5PP"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Decline">
|
||||
<color key="titleColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="declineButtonAction:" destination="V8j-Lb-PgC" eventType="touchUpInside" id="MTU-9k-8yo"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" id="63a-5e-ptU"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="e7g-um-WO4" secondAttribute="centerX" id="P2G-mq-gQW"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="QgV-SO-5yf"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="e7g-um-WO4" secondAttribute="leading" id="YPo-u1-PtT"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="e7g-um-WO4" secondAttribute="top" id="rhQ-96-szL"/>
|
||||
<constraint firstItem="gff-MZ-3bp" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="20" symbolic="YES" id="RbQ-et-xoU"/>
|
||||
<constraint firstItem="dcx-ju-f0Q" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="Wcx-d6-M14"/>
|
||||
<constraint firstAttribute="trailing" secondItem="swq-xw-ItG" secondAttribute="trailing" id="Y5v-Gg-xkM"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dcx-ju-f0Q" secondAttribute="trailing" constant="20" symbolic="YES" id="eGO-kM-neh"/>
|
||||
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="glD-Sz-73O"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gff-MZ-3bp" secondAttribute="trailing" constant="20" symbolic="YES" id="iF6-Ga-3JK"/>
|
||||
<constraint firstItem="gff-MZ-3bp" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="x7i-4D-XSX"/>
|
||||
<constraint firstAttribute="bottom" secondItem="dcx-ju-f0Q" secondAttribute="bottom" constant="20" symbolic="YES" id="xcR-k0-EIW"/>
|
||||
<constraint firstItem="dcx-ju-f0Q" firstAttribute="top" secondItem="swq-xw-ItG" secondAttribute="bottom" priority="250" constant="8" symbolic="YES" id="zYm-2k-kvi"/>
|
||||
<constraint firstItem="swq-xw-ItG" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" id="ze1-Iw-v9U"/>
|
||||
<constraint firstItem="swq-xw-ItG" firstAttribute="top" secondItem="gff-MZ-3bp" secondAttribute="bottom" constant="20" id="zfd-m4-7dW"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="e7g-um-WO4" firstAttribute="height" secondItem="9U2-KL-ZVA" secondAttribute="height" priority="500" id="GP8-i4-Fqh"/>
|
||||
<constraint firstAttribute="trailing" secondItem="e7g-um-WO4" secondAttribute="trailing" id="GyG-Fh-PME"/>
|
||||
<constraint firstItem="e7g-um-WO4" firstAttribute="width" secondItem="9U2-KL-ZVA" secondAttribute="width" id="Ok2-WQ-Zgc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="e7g-um-WO4" secondAttribute="bottom" id="Y46-NP-zAc"/>
|
||||
<constraint firstItem="e7g-um-WO4" firstAttribute="leading" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="aoV-Yh-AcD"/>
|
||||
<constraint firstItem="e7g-um-WO4" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="pFN-bA-SHw"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="9U2-KL-ZVA" secondAttribute="leading" id="UAm-kU-6ug"/>
|
||||
<constraint firstAttribute="trailing" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="VcR-aV-I19"/>
|
||||
<constraint firstAttribute="bottom" secondItem="voD-3Q-ryt" secondAttribute="bottom" priority="500" id="fvu-aR-JZh"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="jJg-B5-Qva"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="centerX" secondItem="9U2-KL-ZVA" secondAttribute="centerX" id="kG2-ZG-S35"/>
|
||||
<constraint firstItem="voD-3Q-ryt" firstAttribute="height" relation="greaterThanOrEqual" secondItem="9U2-KL-ZVA" secondAttribute="height" id="kWh-0d-9t0"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="voD-3Q-ryt" secondAttribute="trailing" id="mLA-DD-xEj"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
|
||||
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="9U2-KL-ZVA" secondAttribute="bottom" id="7Cb-nY-CsO"/>
|
||||
@@ -111,19 +146,28 @@
|
||||
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="9U2-KL-ZVA" secondAttribute="trailing" id="sbD-ek-vGJ"/>
|
||||
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="9U2-KL-ZVA" secondAttribute="top" id="wTB-V6-IHV"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="acceptButton" destination="DOt-5E-FjF" id="ktw-U4-efQ"/>
|
||||
<outlet property="declineButton" destination="QHr-gh-dAD" id="QOS-rE-4SP"/>
|
||||
<outlet property="messageLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
|
||||
<outlet property="descriptionLabel" destination="5fA-rc-XMP" id="OkU-OE-DgA"/>
|
||||
<outlet property="footerLabel" destination="Qmt-nE-Lg7" id="BOZ-Qy-Pxj"/>
|
||||
<outlet property="imageView" destination="XWa-N5-VF2" id="YbG-3e-03Q"/>
|
||||
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
|
||||
<outlet property="tableView" destination="swq-xw-ItG" id="Fwb-Sc-bec"/>
|
||||
<outlet property="tableViewHeightConstraint" destination="phu-oG-oGN" id="rTd-2V-qVf"/>
|
||||
<outlet property="titleLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-3198" y="-647"/>
|
||||
<point key="canvasLocation" x="-3199.1999999999998" y="-647.29064039408865"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="find_your_contacts_facepile" width="110" height="46"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
+124
-134
@@ -22,17 +22,27 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
/// Reuse identifier for the prototype cell in the storyboard.
|
||||
static let cellReuseIdentifier = "Service Terms Cell"
|
||||
static let minimumTableViewHeight: CGFloat = 120
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Outlets
|
||||
|
||||
@IBOutlet private weak var scrollView: UIScrollView!
|
||||
|
||||
@IBOutlet private weak var messageLabel: UILabel!
|
||||
@IBOutlet private weak var imageView: UIImageView!
|
||||
@IBOutlet private weak var titleLabel: UILabel!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
@IBOutlet private weak var footerLabel: UILabel!
|
||||
@IBOutlet private weak var tableView: UITableView!
|
||||
@IBOutlet private weak var acceptButton: UIButton!
|
||||
@IBOutlet private weak var declineButton: UIButton!
|
||||
|
||||
@IBOutlet private weak var tableViewHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var viewModel: ServiceTermsModalScreenViewModelType!
|
||||
@@ -41,9 +51,8 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
private var activityPresenter: ActivityIndicatorPresenter!
|
||||
|
||||
private var policies: [MXLoginPolicyData] = []
|
||||
|
||||
/// Policies checked by the end user
|
||||
private var checkedPolicies: Set<Int> = []
|
||||
|
||||
private var tableHeaderView: ServiceTermsModalTableHeaderView!
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@@ -60,8 +69,6 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
self.title = VectorL10n.serviceTermsModalTitle
|
||||
|
||||
self.setupViews()
|
||||
self.activityPresenter = ActivityIndicatorPresenter()
|
||||
@@ -75,6 +82,11 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
self.viewModel.process(viewAction: .load)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
tableViewHeightConstraint.constant = max(Constants.minimumTableViewHeight, tableView.contentSize.height)
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return self.theme.statusBarStyle
|
||||
}
|
||||
@@ -84,19 +96,30 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
private func update(theme: Theme) {
|
||||
self.theme = theme
|
||||
|
||||
self.view.backgroundColor = theme.headerBackgroundColor
|
||||
view.backgroundColor = theme.headerBackgroundColor
|
||||
|
||||
if let navigationBar = self.navigationController?.navigationBar {
|
||||
if let navigationBar = navigationController?.navigationBar {
|
||||
theme.applyStyle(onNavigationBar: navigationBar)
|
||||
}
|
||||
|
||||
self.messageLabel.textColor = theme.textPrimaryColor
|
||||
titleLabel.font = theme.fonts.bodySB
|
||||
titleLabel.textColor = theme.colors.primaryContent
|
||||
|
||||
descriptionLabel.font = theme.fonts.body
|
||||
descriptionLabel.textColor = theme.colors.secondaryContent
|
||||
|
||||
tableHeaderView.update(theme: theme)
|
||||
|
||||
footerLabel.font = theme.fonts.footnote.withSize(13)
|
||||
footerLabel.textColor = theme.colors.secondaryContent
|
||||
|
||||
self.acceptButton.backgroundColor = theme.backgroundColor
|
||||
theme.applyStyle(onButton: self.acceptButton)
|
||||
acceptButton.titleLabel?.font = theme.fonts.body
|
||||
acceptButton.setTitleColor(theme.colors.background, for: .normal)
|
||||
acceptButton.backgroundColor = theme.colors.accent
|
||||
|
||||
theme.applyStyle(onButton: self.declineButton)
|
||||
self.declineButton.setTitleColor(self.theme.warningColor, for: .normal)
|
||||
declineButton.titleLabel?.font = theme.fonts.body
|
||||
declineButton.setTitleColor(theme.warningColor, for: .normal)
|
||||
|
||||
self.refreshViews()
|
||||
}
|
||||
@@ -110,40 +133,53 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
}
|
||||
|
||||
private func setupViews() {
|
||||
let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in
|
||||
self?.cancelButtonAction()
|
||||
}
|
||||
|
||||
self.navigationItem.rightBarButtonItem = cancelBarButtonItem
|
||||
|
||||
self.setupTableView()
|
||||
self.scrollView.keyboardDismissMode = .interactive
|
||||
|
||||
self.messageLabel.text = VectorL10n.serviceTermsModalMessage(self.viewModel.serviceUrl)
|
||||
self.titleLabel.text = VectorL10n.serviceTermsModalTitleMessage
|
||||
self.footerLabel.text = VectorL10n.serviceTermsModalFooter
|
||||
|
||||
self.tableHeaderView.serviceURLLabel.text = viewModel.serviceUrl
|
||||
|
||||
self.acceptButton.setTitle(VectorL10n.serviceTermsModalAcceptButton, for: .normal)
|
||||
self.acceptButton.setTitle(VectorL10n.serviceTermsModalAcceptButton, for: .highlighted)
|
||||
self.refreshAcceptButton()
|
||||
self.acceptButton.layer.cornerRadius = 8
|
||||
|
||||
if self.viewModel.outOfContext
|
||||
&& self.viewModel.serviceType == MXServiceTypeIdentityService {
|
||||
self.title = VectorL10n.serviceTermsModalTitleIdentityServer
|
||||
self.messageLabel.text = VectorL10n.serviceTermsModalMessageIdentityServer(self.viewModel.serviceUrl)
|
||||
|
||||
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .normal)
|
||||
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .highlighted)
|
||||
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .normal)
|
||||
self.declineButton.setTitle(VectorL10n.serviceTermsModalDeclineButton, for: .highlighted)
|
||||
|
||||
if self.viewModel.serviceType == MXServiceTypeIdentityService {
|
||||
self.descriptionLabel.text = VectorL10n.serviceTermsModalDescriptionIdentityServer
|
||||
self.tableHeaderView.titleLabel.text = VectorL10n.serviceTermsModalTableHeaderIdentityServer
|
||||
self.imageView.image = Asset.Images.findYourContactsFacepile.image
|
||||
} else {
|
||||
self.declineButton.isHidden = true
|
||||
self.descriptionLabel.text = VectorL10n.serviceTermsModalDescriptionIntegrationManager
|
||||
self.tableHeaderView.titleLabel.text = VectorL10n.serviceTermsModalTableHeaderIntegrationManager
|
||||
self.imageView.image = Asset.Images.integrationManagerIconpile.image
|
||||
}
|
||||
}
|
||||
|
||||
private func setupTableView() {
|
||||
self.tableView.delegate = self
|
||||
self.tableView.dataSource = self
|
||||
self.tableView.separatorStyle = .none
|
||||
self.tableView.alwaysBounceVertical = false
|
||||
self.tableView.backgroundColor = .clear
|
||||
self.tableView.register(TableViewCellWithCheckBoxAndLabel.nib(), forCellReuseIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier())
|
||||
guard let tableView = tableView else { return }
|
||||
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.separatorStyle = .none
|
||||
tableView.alwaysBounceVertical = false
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.register(TableViewCellWithCheckBoxAndLabel.nib(), forCellReuseIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier())
|
||||
|
||||
tableHeaderView = ServiceTermsModalTableHeaderView.instantiate()
|
||||
tableHeaderView.delegate = self
|
||||
tableView.tableHeaderView = tableHeaderView
|
||||
|
||||
tableView.addConstraint(NSLayoutConstraint(item: tableView,
|
||||
attribute: .width,
|
||||
relatedBy: .equal,
|
||||
toItem: tableHeaderView,
|
||||
attribute: .width,
|
||||
multiplier: 1,
|
||||
constant: 10))
|
||||
}
|
||||
|
||||
private func render(viewState: ServiceTermsModalScreenViewState) {
|
||||
@@ -161,13 +197,14 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
|
||||
private func renderLoading() {
|
||||
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
|
||||
self.acceptButton.isEnabled = false
|
||||
}
|
||||
|
||||
private func renderLoaded(policies: [MXLoginPolicyData], alreadyAcceptedPoliciesUrls: [String]) {
|
||||
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
|
||||
self.policies = policies
|
||||
self.updateCheckedPolicies(with: alreadyAcceptedPoliciesUrls)
|
||||
self.acceptButton.isEnabled = true
|
||||
|
||||
self.refreshViews()
|
||||
}
|
||||
@@ -187,21 +224,6 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
|
||||
private func refreshViews() {
|
||||
self.tableView.reloadData()
|
||||
self.refreshAcceptButton()
|
||||
}
|
||||
|
||||
private func refreshAcceptButton() {
|
||||
// Enable the button only if the user has accepted all policies
|
||||
self.acceptButton.isEnabled = (self.policies.count == self.checkedPolicies.count)
|
||||
}
|
||||
|
||||
// Pre-check policies already accepted by the user
|
||||
private func updateCheckedPolicies(with acceptedPoliciesUrls: [String]) {
|
||||
for url in acceptedPoliciesUrls {
|
||||
if let policyIndex = self.policies.firstIndex(where: { $0.url == url }) {
|
||||
checkedPolicies.insert(policyIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,37 +236,6 @@ final class ServiceTermsModalScreenViewController: UIViewController {
|
||||
@IBAction private func declineButtonAction(_ sender: Any) {
|
||||
self.viewModel.process(viewAction: .decline)
|
||||
}
|
||||
|
||||
private func cancelButtonAction() {
|
||||
self.viewModel.process(viewAction: .cancel)
|
||||
}
|
||||
|
||||
@objc private func didTapCheckbox(sender: UITapGestureRecognizer) {
|
||||
|
||||
guard let policyIndex = sender.view?.tag else {
|
||||
return
|
||||
}
|
||||
|
||||
let isCheckBoxSelected: Bool
|
||||
|
||||
if self.checkedPolicies.contains(policyIndex) {
|
||||
self.checkedPolicies.remove(policyIndex)
|
||||
isCheckBoxSelected = false
|
||||
} else {
|
||||
checkedPolicies.insert(policyIndex)
|
||||
isCheckBoxSelected = true
|
||||
}
|
||||
|
||||
if let checkBoxImageView = sender.view as? UIImageView {
|
||||
if isCheckBoxSelected {
|
||||
checkBoxImageView.accessibilityTraits.insert(.selected)
|
||||
} else {
|
||||
checkBoxImageView.accessibilityTraits.remove(.selected)
|
||||
}
|
||||
}
|
||||
|
||||
self.refreshViews()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -259,72 +250,71 @@ extension ServiceTermsModalScreenViewController: ServiceTermsModalScreenViewMode
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
extension ServiceTermsModalScreenViewController: UITableViewDataSource {
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
// Use individual sections for each policy so the cells aren't grouped together.
|
||||
return policies.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return self.policies.count
|
||||
return 1
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
// Reduce the height between sections to only be the footer height value.
|
||||
return CGFloat.leastNormalMagnitude
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
// Modify the footer size to reduce cell spacing.
|
||||
return 8.0
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellReuseIdentifier, for: indexPath)
|
||||
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCellWithCheckBoxAndLabel.defaultReuseIdentifier(), for: indexPath) as? TableViewCellWithCheckBoxAndLabel else {
|
||||
fatalError("\(String(describing: TableViewCellWithCheckBoxAndLabel.self)) should be registered")
|
||||
}
|
||||
let policy = policies[indexPath.section]
|
||||
|
||||
let policy = policies[indexPath.row]
|
||||
let checked = checkedPolicies.contains(indexPath.row)
|
||||
|
||||
cell.label.attributedText = self.cellLabel(for: policy)
|
||||
cell.label.font = .systemFont(ofSize: 15)
|
||||
cell.isEnabled = checked
|
||||
cell.textLabel?.text = policy.name
|
||||
cell.textLabel?.textColor = theme.colors.primaryContent
|
||||
cell.textLabel?.font = theme.fonts.body
|
||||
cell.vc_setAccessoryDisclosureIndicator(withTheme: self.theme)
|
||||
cell.backgroundColor = self.theme.backgroundColor
|
||||
|
||||
if let checkBox = cell.checkBox, checkBox.gestureRecognizers?.isEmpty ?? true {
|
||||
let gesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapCheckbox))
|
||||
gesture.numberOfTapsRequired = 1
|
||||
gesture.numberOfTouchesRequired = 1
|
||||
|
||||
checkBox.isUserInteractionEnabled = true
|
||||
checkBox.tag = indexPath.row
|
||||
checkBox.addGestureRecognizer(gesture)
|
||||
|
||||
checkBox.isAccessibilityElement = true
|
||||
checkBox.accessibilityTraits = .button
|
||||
checkBox.accessibilityLabel = VectorL10n.accessibilityCheckboxLabel
|
||||
checkBox.accessibilityHint = VectorL10n.serviceTermsModalPolicyCheckboxAccessibilityHint(policy.name)
|
||||
}
|
||||
cell.accessoryView?.tintColor = theme.colors.quarterlyContent
|
||||
cell.backgroundColor = theme.colors.background
|
||||
cell.selectionStyle = .default
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func cellLabel(for policy: MXLoginPolicyData) -> NSAttributedString {
|
||||
|
||||
// TableViewCellWithCheckBoxAndLabel does not have a detailTextLabel
|
||||
// Do it by hand
|
||||
|
||||
var labelDetail: String = ""
|
||||
switch self.viewModel.serviceType {
|
||||
case MXServiceTypeIdentityService:
|
||||
labelDetail = VectorL10n.serviceTermsModalDescriptionForIdentityServer1
|
||||
+ "\n"
|
||||
+ VectorL10n.serviceTermsModalDescriptionForIdentityServer2
|
||||
case MXServiceTypeIntegrationManager:
|
||||
labelDetail = VectorL10n.serviceTermsModalDescriptionForIntegrationManager
|
||||
default: break
|
||||
}
|
||||
|
||||
let label = NSMutableAttributedString(string: policy.name,
|
||||
attributes: [.foregroundColor: theme.textPrimaryColor])
|
||||
label.append(NSAttributedString(string: "\n"))
|
||||
label.append(NSAttributedString(string: labelDetail,
|
||||
attributes: [.foregroundColor: theme.textSecondaryColor]))
|
||||
return label
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
extension ServiceTermsModalScreenViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let policy = policies[indexPath.row]
|
||||
self.viewModel.process(viewAction: .display(policy))
|
||||
let policy = policies[indexPath.section]
|
||||
viewModel.process(viewAction: .display(policy))
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ServiceTermsModalTableHeaderViewDelegate
|
||||
extension ServiceTermsModalScreenViewController: ServiceTermsModalTableHeaderViewDelegate {
|
||||
func tableHeaderViewDidTapInformationButton() {
|
||||
let title: String
|
||||
let message: String
|
||||
|
||||
if viewModel.serviceType == MXServiceTypeIdentityService {
|
||||
title = VectorL10n.serviceTermsModalInformationTitleIdentityServer
|
||||
message = VectorL10n.serviceTermsModalInformationDescriptionIdentityServer
|
||||
} else {
|
||||
title = VectorL10n.serviceTermsModalInformationTitleIntegrationManager
|
||||
message = VectorL10n.serviceTermsModalInformationDescriptionIntegrationManager
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: Bundle.mxk_localizedString(forKey: "ok"), style: .default))
|
||||
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import Foundation
|
||||
final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
let outOfContext: Bool
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -43,9 +42,8 @@ final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelTy
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(serviceTerms: MXServiceTerms, outOfContext: Bool) {
|
||||
init(serviceTerms: MXServiceTerms) {
|
||||
self.serviceTerms = serviceTerms
|
||||
self.outOfContext = outOfContext
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -60,8 +58,6 @@ final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelTy
|
||||
self.acceptTerms()
|
||||
case .decline:
|
||||
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidDecline(self)
|
||||
case .cancel:
|
||||
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidCancel(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +95,14 @@ final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelTy
|
||||
return
|
||||
}
|
||||
self.update(viewState: .accepted)
|
||||
|
||||
// Send a notification to update the identity service immediately.
|
||||
if self.serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
let userInfo = [MXIdentityServiceNotificationIdentityServerKey: self.serviceTerms.baseUrl]
|
||||
NotificationCenter.default.post(name: .MXIdentityServiceTermsAccepted, object: nil, userInfo: userInfo)
|
||||
}
|
||||
|
||||
// Notify the delegate.
|
||||
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidAccept(self)
|
||||
|
||||
}, failure: { [weak self] (error) in
|
||||
|
||||
@@ -26,7 +26,6 @@ protocol ServiceTermsModalScreenViewModelCoordinatorDelegate: AnyObject {
|
||||
func serviceTermsModalScreenViewModel(_ coordinator: ServiceTermsModalScreenViewModelType, displayPolicy policy: MXLoginPolicyData)
|
||||
func serviceTermsModalScreenViewModelDidAccept(_ viewModel: ServiceTermsModalScreenViewModelType)
|
||||
func serviceTermsModalScreenViewModelDidDecline(_ viewModel: ServiceTermsModalScreenViewModelType)
|
||||
func serviceTermsModalScreenViewModelDidCancel(_ viewModel: ServiceTermsModalScreenViewModelType)
|
||||
}
|
||||
|
||||
/// Protocol describing the view model used by `ServiceTermsModalScreenViewController`
|
||||
@@ -34,9 +33,6 @@ protocol ServiceTermsModalScreenViewModelType {
|
||||
|
||||
var serviceUrl: String { get }
|
||||
var serviceType: MXServiceType { get }
|
||||
/// If true, terms are displayed out of a context of a flow (like a background 3pids lookup)
|
||||
/// In this case, the wording needs to provide more information about the intent
|
||||
var outOfContext: Bool { get }
|
||||
var policies: [MXLoginPolicyData]? { get set }
|
||||
var alreadyAcceptedPoliciesUrls: [String] { get set }
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// 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 UIKit
|
||||
import Reusable
|
||||
|
||||
protocol ServiceTermsModalTableHeaderViewDelegate: AnyObject {
|
||||
func tableHeaderViewDidTapInformationButton()
|
||||
}
|
||||
|
||||
class ServiceTermsModalTableHeaderView: UIView, NibLoadable, Themable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
weak var delegate: ServiceTermsModalTableHeaderViewDelegate?
|
||||
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var serviceURLLabel: UILabel!
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
static func instantiate() -> Self {
|
||||
let view = Self.loadFromNib()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.update(theme: ThemeService.shared().theme)
|
||||
return view
|
||||
}
|
||||
|
||||
func update(theme: Theme) {
|
||||
titleLabel.font = theme.fonts.footnote
|
||||
titleLabel.textColor = theme.colors.secondaryContent
|
||||
|
||||
serviceURLLabel.font = theme.fonts.callout
|
||||
serviceURLLabel.textColor = theme.colors.secondaryContent
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
@IBAction private func buttonAction(_ sender: Any) {
|
||||
delegate?.tableHeaderViewDidTapInformationButton()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ServiceTermsModalTableHeaderView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="328" height="72"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Server Terms" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nQB-Sg-E1U">
|
||||
<rect key="frame" x="16" y="0.0" width="80.5" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="Lab-Gt-CFg"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="https://domain.com" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nYB-Yu-HV9">
|
||||
<rect key="frame" x="16" y="24" width="141" height="36"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="36" id="ggs-su-fvA"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ztE-u7-qV6">
|
||||
<rect key="frame" x="292" y="1" width="20" height="22"/>
|
||||
<state key="normal" image="information_button"/>
|
||||
<connections>
|
||||
<action selector="buttonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="EeS-ya-xp5"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="nYB-Yu-HV9" secondAttribute="bottom" constant="12" id="1MG-vA-cVG"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="ztE-u7-qV6" secondAttribute="trailing" constant="16" id="C1f-Ex-0Ip"/>
|
||||
<constraint firstItem="ztE-u7-qV6" firstAttribute="centerY" secondItem="nQB-Sg-E1U" secondAttribute="centerY" id="C86-gf-hip"/>
|
||||
<constraint firstItem="ztE-u7-qV6" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="nQB-Sg-E1U" secondAttribute="trailing" constant="8" symbolic="YES" id="Gtr-PA-pv5"/>
|
||||
<constraint firstItem="nQB-Sg-E1U" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="Uci-Xz-AGE"/>
|
||||
<constraint firstItem="nYB-Yu-HV9" firstAttribute="top" secondItem="nQB-Sg-E1U" secondAttribute="bottom" id="VCK-IC-YUJ"/>
|
||||
<constraint firstItem="nYB-Yu-HV9" firstAttribute="leading" secondItem="nQB-Sg-E1U" secondAttribute="leading" id="epR-po-phu"/>
|
||||
<constraint firstItem="nQB-Sg-E1U" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="mOs-ee-5dI"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="serviceURLLabel" destination="nYB-Yu-HV9" id="2dD-hK-MjJ"/>
|
||||
<outlet property="titleLabel" destination="nQB-Sg-E1U" id="PfB-Sr-6fE"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="40.579710144927539" y="37.834821428571423"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="information_button" width="20" height="20"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -19,7 +19,7 @@
|
||||
import UIKit
|
||||
|
||||
@objcMembers
|
||||
final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
|
||||
final class ServiceTermsModalCoordinator: NSObject, ServiceTermsModalCoordinatorType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@@ -28,7 +28,6 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
|
||||
private let navigationRouter: NavigationRouterType
|
||||
private let session: MXSession
|
||||
private let serviceTerms: MXServiceTerms
|
||||
private let outOfContext: Bool
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -38,11 +37,10 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
|
||||
weak var delegate: ServiceTermsModalCoordinatorDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, outOfContext: Bool, accessToken: String) {
|
||||
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, accessToken: String) {
|
||||
self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController())
|
||||
self.session = session
|
||||
self.serviceTerms = MXServiceTerms(baseUrl: baseUrl, serviceType: serviceType, matrixSession: session, accessToken: accessToken)
|
||||
self.outOfContext = outOfContext
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
@@ -53,6 +51,8 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
|
||||
rootCoordinator.start()
|
||||
|
||||
self.add(childCoordinator: rootCoordinator)
|
||||
|
||||
self.toPresentable().presentationController?.delegate = self
|
||||
|
||||
self.navigationRouter.setRootModule(rootCoordinator)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
|
||||
// MARK: - Private methods
|
||||
|
||||
private func createServiceTermsModalLoadTermsScreenCoordinator() -> ServiceTermsModalScreenCoordinator {
|
||||
let coordinator = ServiceTermsModalScreenCoordinator(serviceTerms: self.serviceTerms, outOfContext: self.outOfContext)
|
||||
let coordinator = ServiceTermsModalScreenCoordinator(serviceTerms: self.serviceTerms)
|
||||
coordinator.delegate = self
|
||||
return coordinator
|
||||
}
|
||||
@@ -87,12 +87,29 @@ final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
|
||||
@objc private func didTapCancelOnPolicyScreen() {
|
||||
self.removePolicyScreen()
|
||||
}
|
||||
|
||||
/// Removes the identity server from the `MXSession` and it's account data.
|
||||
private func disableIdentityServer() {
|
||||
MXLog.debug("[ServiceTermsModalCoordinator] IS Terms: User has declined the use of the default IS.")
|
||||
|
||||
// The user does not want to use the proposed IS.
|
||||
// Disable IS feature on user's account
|
||||
session.setIdentityServer(nil, andAccessToken: nil)
|
||||
session.setAccountDataIdentityServer(nil, success: nil) { error in
|
||||
guard let errorDescription = error?.localizedDescription else { return }
|
||||
MXLog.error("[ServiceTermsModalCoordinator] IS Terms: Error: \(errorDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ServiceTermsModalLoadTermsScreenCoordinatorDelegate
|
||||
extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelegate {
|
||||
|
||||
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
|
||||
if serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
|
||||
}
|
||||
|
||||
self.delegate?.serviceTermsModalCoordinatorDidAccept(self)
|
||||
}
|
||||
|
||||
@@ -101,10 +118,22 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
|
||||
}
|
||||
|
||||
func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
|
||||
if serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
|
||||
disableIdentityServer()
|
||||
}
|
||||
|
||||
self.delegate?.serviceTermsModalCoordinatorDidDecline(self)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceTermsModalScreenCoordinatorDidCancel(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
|
||||
self.delegate?.serviceTermsModalCoordinatorDidCancel(self)
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension ServiceTermsModalCoordinator: UIAdaptivePresentationControllerDelegate {
|
||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||
if serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(0, category: MXKAnalyticsCategory.contacts.rawValue, name: AnalyticsContactsIdentityServerAccepted)
|
||||
}
|
||||
|
||||
self.delegate?.serviceTermsModalCoordinatorDidDismissInteractively(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import Foundation
|
||||
@objc protocol ServiceTermsModalCoordinatorBridgePresenterDelegate {
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter, session: MXSession)
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
|
||||
}
|
||||
|
||||
/// ServiceTermsModalCoordinatorBridgePresenter enables to start ServiceTermsModalCoordinator from a view controller.
|
||||
@@ -36,7 +36,6 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
|
||||
private let session: MXSession
|
||||
private let baseUrl: String
|
||||
private let serviceType: MXServiceType
|
||||
private let outOfContext: Bool
|
||||
private let accessToken: String
|
||||
private var coordinator: ServiceTermsModalCoordinator?
|
||||
|
||||
@@ -50,11 +49,10 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, outOfContext: Bool = false, accessToken: String) {
|
||||
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, accessToken: String) {
|
||||
self.session = session
|
||||
self.baseUrl = baseUrl
|
||||
self.serviceType = serviceType
|
||||
self.outOfContext = outOfContext
|
||||
self.accessToken = accessToken
|
||||
super.init()
|
||||
}
|
||||
@@ -67,10 +65,9 @@ final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
|
||||
// }
|
||||
|
||||
func present(from viewController: UIViewController, animated: Bool) {
|
||||
let serviceTermsModalCoordinator = ServiceTermsModalCoordinator(session: self.session, baseUrl: self.baseUrl, serviceType: self.serviceType, outOfContext: self.outOfContext, accessToken: accessToken)
|
||||
let serviceTermsModalCoordinator = ServiceTermsModalCoordinator(session: self.session, baseUrl: self.baseUrl, serviceType: self.serviceType, accessToken: accessToken)
|
||||
serviceTermsModalCoordinator.delegate = self
|
||||
let presentable = serviceTermsModalCoordinator.toPresentable()
|
||||
presentable.presentationController?.delegate = self
|
||||
viewController.present(presentable, animated: animated, completion: nil)
|
||||
serviceTermsModalCoordinator.start()
|
||||
|
||||
@@ -100,36 +97,13 @@ extension ServiceTermsModalCoordinatorBridgePresenter: ServiceTermsModalCoordina
|
||||
|
||||
func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType) {
|
||||
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(self)
|
||||
|
||||
if serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(1, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceTermsModalCoordinatorDidDecline(_ coordinator: ServiceTermsModalCoordinatorType) {
|
||||
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline(self, session: self.session)
|
||||
|
||||
if serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(0, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType) {
|
||||
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(self)
|
||||
|
||||
if serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(0, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension ServiceTermsModalCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate {
|
||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(self)
|
||||
|
||||
if serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.sharedInstance().trackValue(0, category: kMXKAnalyticsContactsCategory, name: AnalyticsContactsIdentityServerAccepted)
|
||||
}
|
||||
|
||||
func serviceTermsModalCoordinatorDidDismissInteractively(_ coordinator: ServiceTermsModalCoordinatorType) {
|
||||
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import Foundation
|
||||
protocol ServiceTermsModalCoordinatorDelegate: AnyObject {
|
||||
func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType)
|
||||
func serviceTermsModalCoordinatorDidDecline(_ coordinator: ServiceTermsModalCoordinatorType)
|
||||
func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType)
|
||||
func serviceTermsModalCoordinatorDidDismissInteractively(_ coordinator: ServiceTermsModalCoordinatorType)
|
||||
}
|
||||
|
||||
/// `ServiceTermsModalCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow.
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ final class SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter: NSObject
|
||||
private let threePid: MX3PID
|
||||
|
||||
private var coordinator: SettingsDiscoveryThreePidDetailsCoordinator?
|
||||
private var router: NavigationRouter?
|
||||
private var router: NavigationRouterType?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@@ -45,7 +45,7 @@ final class SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter: NSObject
|
||||
|
||||
func push(from navigationController: UINavigationController, animated: Bool, popCompletion: (() -> Void)?) {
|
||||
|
||||
let router = NavigationRouter(navigationController: navigationController)
|
||||
let router = NavigationRouterStore.shared.navigationRouter(for: navigationController)
|
||||
|
||||
let settingsDiscoveryThreePidDetailsCoordinator = SettingsDiscoveryThreePidDetailsCoordinator(session: self.session, threePid: self.threePid)
|
||||
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ final class SettingsIdentityServerCoordinatorBridgePresenter: NSObject {
|
||||
// MARK: Private
|
||||
|
||||
private let session: MXSession
|
||||
private var router: NavigationRouter?
|
||||
private var router: NavigationRouterType?
|
||||
private var coordinator: SettingsIdentityServerCoordinator?
|
||||
|
||||
// MARK: Public
|
||||
@@ -50,7 +50,7 @@ final class SettingsIdentityServerCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
func push(from navigationController: UINavigationController, animated: Bool, popCompletion: (() -> Void)?) {
|
||||
|
||||
let router = NavigationRouter(navigationController: navigationController)
|
||||
let router = NavigationRouterStore.shared.navigationRouter(for: navigationController)
|
||||
|
||||
let settingsIdentityServerCoordinator = SettingsIdentityServerCoordinator(session: self.session)
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@ extension SettingsIdentityServerViewController: ServiceTermsModalCoordinatorBrid
|
||||
self.hideTerms(accepted: false)
|
||||
}
|
||||
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) {
|
||||
func serviceTermsModalCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter) {
|
||||
self.hideTerms(accepted: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ enum
|
||||
|
||||
enum {
|
||||
LOCAL_CONTACTS_SYNC_INDEX,
|
||||
LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX
|
||||
LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX,
|
||||
LOCAL_CONTACTS_SYNC_DESCRIPTION_INDEX
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -179,6 +180,7 @@ SignOutAlertPresenterDelegate,
|
||||
SingleImagePickerPresenterDelegate,
|
||||
SettingsDiscoveryTableViewSectionDelegate, SettingsDiscoveryViewModelCoordinatorDelegate,
|
||||
SettingsIdentityServerCoordinatorBridgePresenterDelegate,
|
||||
ServiceTermsModalCoordinatorBridgePresenterDelegate,
|
||||
TableViewSectionsDelegate>
|
||||
{
|
||||
// Current alert (if any).
|
||||
@@ -280,6 +282,8 @@ TableViewSectionsDelegate>
|
||||
|
||||
@property (nonatomic, strong) UserInteractiveAuthenticationService *userInteractiveAuthenticationService;
|
||||
|
||||
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SettingsViewController
|
||||
@@ -462,7 +466,12 @@ TableViewSectionsDelegate>
|
||||
{
|
||||
[sectionLocalContacts addRowWithTag:LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX];
|
||||
}
|
||||
sectionLocalContacts.headerTitle = [VectorL10n settingsContacts];
|
||||
else
|
||||
{
|
||||
[sectionLocalContacts addRowWithTag:LOCAL_CONTACTS_SYNC_DESCRIPTION_INDEX];
|
||||
}
|
||||
NSString *headerTitle = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone ? VectorL10n.settingsPhoneContacts : VectorL10n.settingsContacts;
|
||||
sectionLocalContacts.headerTitle = headerTitle;
|
||||
[tmpSections addObject:sectionLocalContacts];
|
||||
}
|
||||
|
||||
@@ -2196,7 +2205,7 @@ TableViewSectionsDelegate>
|
||||
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
|
||||
|
||||
labelAndSwitchCell.mxkLabel.numberOfLines = 0;
|
||||
labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsContactsDiscoverMatrixUsers];
|
||||
labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsContactsEnableSync;
|
||||
labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].syncLocalContacts;
|
||||
labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor;
|
||||
labelAndSwitchCell.mxkSwitch.enabled = YES;
|
||||
@@ -2224,6 +2233,15 @@ TableViewSectionsDelegate>
|
||||
[cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
|
||||
}
|
||||
else if (row == LOCAL_CONTACTS_SYNC_DESCRIPTION_INDEX)
|
||||
{
|
||||
MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView];
|
||||
descriptionCell.textLabel.text = VectorL10n.settingsContactsEnableSyncDescription;
|
||||
descriptionCell.textLabel.numberOfLines = 0;
|
||||
descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
|
||||
cell = descriptionCell;
|
||||
}
|
||||
}
|
||||
else if (section == SECTION_TAG_ADVANCED)
|
||||
{
|
||||
@@ -3102,17 +3120,51 @@ TableViewSectionsDelegate>
|
||||
{
|
||||
if (sender.on)
|
||||
{
|
||||
[MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) {
|
||||
|
||||
[MXKAppSettings standardAppSettings].syncLocalContacts = granted;
|
||||
// First check if the service terms have already been accepted
|
||||
MXSession *session = self.mxSessions.firstObject;
|
||||
if (session.identityService.areAllTermsAgreed)
|
||||
{
|
||||
// If they have we only require local contacts access.
|
||||
[self checkAccessForContacts];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
[self updateSections];
|
||||
}];
|
||||
// The preparation can take some time so indicate this to the user
|
||||
[self startActivityIndicator];
|
||||
|
||||
[session prepareIdentityServiceForTermsWithDefault:RiotSettings.shared.identityServerUrlString
|
||||
success:^(MXSession *session, NSString *baseURL, NSString *accessToken) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self stopActivityIndicator];
|
||||
|
||||
// Present the terms of the identity server.
|
||||
[self presentIdentityServerTermsWithSession:session baseURL:baseURL andAccessToken:accessToken];
|
||||
} failure:^(NSError *error) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self stopActivityIndicator];
|
||||
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:VectorL10n.findYourContactsIdentityServiceError
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:MatrixKitL10n.ok
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:nil]];
|
||||
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
|
||||
[MXKAppSettings standardAppSettings].syncLocalContacts = NO;
|
||||
[self updateSections];
|
||||
}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[MXKAppSettings standardAppSettings].syncLocalContacts = NO;
|
||||
|
||||
[self updateSections];
|
||||
}
|
||||
}
|
||||
@@ -4457,6 +4509,28 @@ TableViewSectionsDelegate>
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Local Contacts Sync
|
||||
|
||||
- (void)checkAccessForContacts
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
// Check for contacts access, showing a pop-up if necessary.
|
||||
[MXKTools checkAccessForContacts:VectorL10n.contactsAddressBookPermissionDeniedAlertTitle
|
||||
withManualChangeMessage:VectorL10n.contactsAddressBookPermissionDeniedAlertMessage
|
||||
showPopUpInViewController:self
|
||||
completionHandler:^(BOOL granted) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
if (granted)
|
||||
{
|
||||
// When granted, local contacts can be shown.
|
||||
[MXKAppSettings standardAppSettings].syncLocalContacts = YES;
|
||||
[self updateSections];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Identity server
|
||||
|
||||
@@ -4468,7 +4542,25 @@ TableViewSectionsDelegate>
|
||||
identityServerSettingsCoordinatorBridgePresenter.delegate = self;
|
||||
}
|
||||
|
||||
#pragma mark - SettingsIdentityServerCoordinatorBridgePresenterDelegate
|
||||
- (void)presentIdentityServerTermsWithSession:(MXSession*)mxSession baseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken
|
||||
{
|
||||
if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession
|
||||
baseUrl:baseURL
|
||||
serviceType:MXServiceTypeIdentityService
|
||||
accessToken:accessToken];
|
||||
|
||||
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
|
||||
|
||||
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
|
||||
}
|
||||
|
||||
#pragma mark SettingsIdentityServerCoordinatorBridgePresenterDelegate
|
||||
|
||||
- (void)settingsIdentityServerCoordinatorBridgePresenterDelegateDidComplete:(SettingsIdentityServerCoordinatorBridgePresenter *)coordinatorBridgePresenter
|
||||
{
|
||||
@@ -4476,6 +4568,31 @@ TableViewSectionsDelegate>
|
||||
[self refreshSettings];
|
||||
}
|
||||
|
||||
#pragma mark ServiceTermsModalCoordinatorBridgePresenterDelegate
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
|
||||
[self checkAccessForContacts];
|
||||
}];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession *)session
|
||||
{
|
||||
// Disable the contacts toggle as the terms weren't accepted.
|
||||
[self updateSections];
|
||||
|
||||
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
|
||||
{
|
||||
[self updateSections]; // Disables the contacts toggle as the terms weren't accepted.
|
||||
self.serviceTermsModalCoordinatorBridgePresenter = nil;
|
||||
}
|
||||
|
||||
#pragma mark - TableViewSectionsDelegate
|
||||
|
||||
- (void)tableViewSectionsDidUpdateSections:(TableViewSections *)sections
|
||||
|
||||
@@ -32,6 +32,12 @@ class SplitViewCoordinatorParameters {
|
||||
|
||||
final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let detailModulesCheckDelay: Double = 0.3
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
@@ -42,6 +48,11 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
|
||||
private weak var masterPresentable: SplitViewMasterPresentable?
|
||||
private var detailNavigationController: UINavigationController?
|
||||
private var detailNavigationRouter: NavigationRouterType?
|
||||
|
||||
private var selectedNavigationRouter: NavigationRouterType? {
|
||||
return self.masterPresentable?.selectedNavigationRouter
|
||||
}
|
||||
|
||||
private weak var tabBarCoordinator: TabBarCoordinatorType?
|
||||
|
||||
@@ -77,13 +88,17 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
|
||||
self.splitViewController.delegate = self
|
||||
|
||||
// Create primary controller
|
||||
let tabBarCoordinator = self.createTabBarCoordinator()
|
||||
tabBarCoordinator.delegate = self
|
||||
tabBarCoordinator.splitViewMasterPresentableDelegate = self
|
||||
tabBarCoordinator.start(with: spaceId)
|
||||
|
||||
let detailNavigationController = self.createDetailNavigationController()
|
||||
// Create secondary controller
|
||||
let placeholderDetailViewController = self.createPlaceholderDetailsViewController()
|
||||
let detailNavigationController = RiotNavigationController(rootViewController: placeholderDetailViewController)
|
||||
|
||||
// Setup split view controller
|
||||
self.splitViewController.viewControllers = [tabBarCoordinator.toPresentable(), detailNavigationController]
|
||||
|
||||
self.add(childCoordinator: tabBarCoordinator)
|
||||
@@ -91,8 +106,11 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
self.tabBarCoordinator = tabBarCoordinator
|
||||
self.masterPresentable = tabBarCoordinator
|
||||
self.detailNavigationController = detailNavigationController
|
||||
self.detailNavigationRouter = NavigationRouter(navigationController: detailNavigationController)
|
||||
|
||||
self.parameters.router.setRootModule(self.splitViewController)
|
||||
|
||||
self.registerNavigationRouterNotifications()
|
||||
} else {
|
||||
// Pop to home screen when selecting a new space
|
||||
self.popToHome(animated: true) {
|
||||
@@ -105,7 +123,7 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.splitViewController
|
||||
}
|
||||
|
||||
|
||||
// TODO: Do not expose publicly this method
|
||||
func restorePlaceholderDetails() {
|
||||
// Be sure that the primary is then visible too.
|
||||
@@ -113,22 +131,14 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
splitViewController.preferredDisplayMode = .allVisible
|
||||
}
|
||||
|
||||
if splitViewController.viewControllers.count == 2 {
|
||||
let mainViewController = splitViewController.viewControllers[0]
|
||||
|
||||
let emptyDetailsViewController = self.createPlaceholderDetailsViewController()
|
||||
|
||||
splitViewController.viewControllers = [mainViewController, emptyDetailsViewController]
|
||||
}
|
||||
self.resetDetailNavigationControllerWithPlaceholder(animated: false)
|
||||
|
||||
// Release the current selected item (room/contact/group...).
|
||||
self.tabBarCoordinator?.releaseSelectedItems()
|
||||
}
|
||||
|
||||
func popToHome(animated: Bool, completion: (() -> Void)?) {
|
||||
if let secondNavController = self.detailNavigationController {
|
||||
secondNavController.popToRootViewController(animated: animated)
|
||||
}
|
||||
self.resetDetailNavigationControllerWithPlaceholder(animated: animated)
|
||||
|
||||
// Force back to the main screen if this is not the one that is displayed
|
||||
self.tabBarCoordinator?.popToHome(animated: animated, completion: completion)
|
||||
@@ -149,54 +159,196 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType {
|
||||
return tabBarCoordinator
|
||||
}
|
||||
|
||||
private func createDetailNavigationController() -> UINavigationController {
|
||||
let placeholderDetailViewController = self.createPlaceholderDetailsViewController()
|
||||
let detailNavigationController = RiotNavigationController(rootViewController: placeholderDetailViewController)
|
||||
return detailNavigationController
|
||||
private func resetDetailNavigationControllerWithPlaceholder(animated: Bool) {
|
||||
guard let detailNavigationRouter = self.detailNavigationRouter else {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if placeholder is already shown
|
||||
if detailNavigationRouter.modules.count == 1 && detailNavigationRouter.modules.last is PlaceholderDetailViewController {
|
||||
return
|
||||
}
|
||||
|
||||
// Set placeholder screen as root controller of detail navigation controller
|
||||
let placeholderDetailsVC = self.createPlaceholderDetailsViewController()
|
||||
detailNavigationRouter.setRootModule(placeholderDetailsVC, hideNavigationBar: false, animated: animated, popCompletion: nil)
|
||||
}
|
||||
|
||||
private func isPlaceholderShown(from secondaryViewController: UIViewController) -> Bool {
|
||||
|
||||
if let detailNavigationController = secondaryViewController as? UINavigationController, let topViewController = detailNavigationController.viewControllers.last {
|
||||
return topViewController is PlaceholderDetailViewController
|
||||
} else {
|
||||
return secondaryViewController is PlaceholderDetailViewController
|
||||
}
|
||||
}
|
||||
|
||||
private func releaseRoomDataSourceIfNeeded(for roomCoordinator: RoomCoordinatorProtocol) {
|
||||
|
||||
guard roomCoordinator.canReleaseRoomDataSource,
|
||||
let session = roomCoordinator.mxSession,
|
||||
let roomId = roomCoordinator.roomId else {
|
||||
return
|
||||
}
|
||||
|
||||
let existingRoomCoordinatorWithSameRoomId = self.detailModules.first { presentable -> Bool in
|
||||
if let currentRoomCoordinator = presentable as? RoomCoordinatorProtocol {
|
||||
return currentRoomCoordinator.roomId == roomCoordinator.roomId
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
guard existingRoomCoordinatorWithSameRoomId == nil else {
|
||||
MXLog.debug("[SplitViewCoordinator] Do not release RoomDataSource for room id \(roomId), another RoomCoordinator with same room id using it")
|
||||
return
|
||||
}
|
||||
|
||||
let dataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: session)
|
||||
dataSourceManager?.closeRoomDataSource(withRoomId: roomId, forceClose: false)
|
||||
}
|
||||
|
||||
private func registerNavigationRouterNotifications() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(navigationRouterDidPopViewController(_:)), name: NavigationRouter.didPopModule, object: nil)
|
||||
}
|
||||
|
||||
@objc private func navigationRouterDidPopViewController(_ notification: Notification) {
|
||||
|
||||
guard let userInfo = notification.userInfo,
|
||||
let navigationRouter = userInfo[NavigationRouter.NotificationUserInfoKey.navigationRouter] as? NavigationRouterType,
|
||||
let poppedController = userInfo[NavigationRouter.NotificationUserInfoKey.viewController] as? UIViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
// In our split view configuration is possible to have nested navigation controller (see https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/)).
|
||||
// When the split view controller has one column visible with the detail navigation controller nested inside the primary,
|
||||
// check to see whether the primary navigation controller is popping the detail navigation controller.
|
||||
// In this case the detail navigation controller will be popped but not its content. It means completions will not be called.
|
||||
if navigationRouter === self.selectedNavigationRouter,
|
||||
let poppedNavigationController = poppedController as? UINavigationController,
|
||||
poppedNavigationController == self.detailNavigationController {
|
||||
|
||||
// Clear the detailNavigationRouter to trigger completions associated to each controllers
|
||||
self.detailNavigationRouter?.popAllModules(animated: false)
|
||||
}
|
||||
|
||||
if let poppedModule = userInfo[NavigationRouter.NotificationUserInfoKey.module] as? Presentable {
|
||||
|
||||
if let roomCoordinator = poppedModule as? RoomCoordinatorProtocol {
|
||||
|
||||
// If the RoomCoordinator view controller is popped from the detail navigation controller, check if the associated room data source should be released.
|
||||
// If there is no other RoomCoordinator using the same data source, release it.
|
||||
// A small delay is set to be sure navigation stack manipulation ended before checking the whole stack.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.detailModulesCheckDelay) {
|
||||
self.releaseRoomDataSourceIfNeeded(for: roomCoordinator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UISplitViewControllerDelegate
|
||||
extension SplitViewCoordinator: UISplitViewControllerDelegate {
|
||||
|
||||
/// Provide the new secondary view controller for the split view interface.
|
||||
/// This method returns the view controller to use as the secondary view controller in the expanded split view interface (when 2 column are visible).
|
||||
/// Sample case: large iPhone goes from portrait to landsacpe.
|
||||
func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? {
|
||||
|
||||
if let detailViewController = self.masterPresentable?.secondViewControllerWhenSeparatedFromPrimary() {
|
||||
return detailViewController
|
||||
// If the primary root controller of the UISplitViewController is a UINavigationController,
|
||||
// it's possible to have nested navigation controllers due to private property `_allowNestedNavigationControllers` set to true (https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/).
|
||||
// So if the top view controller of the primary navigation controller is a navigation controller and it corresponds to the existing `detailNavigationController` instance.
|
||||
// Return `detailNavigationController` as is, it will be used as the secondary view of the split view controller.
|
||||
if let topMostNavigationController = self.selectedNavigationRouter?.modules.last as? UINavigationController, topMostNavigationController == self.detailNavigationController {
|
||||
|
||||
return self.detailNavigationController
|
||||
}
|
||||
|
||||
// Else return the default empty details view controller from the storyboard.
|
||||
// Else return the default empty details view controller.
|
||||
// Be sure that the primary is then visible too.
|
||||
if splitViewController.displayMode == .primaryHidden {
|
||||
splitViewController.preferredDisplayMode = .allVisible
|
||||
}
|
||||
|
||||
return self.createPlaceholderDetailsViewController()
|
||||
// Restore detail navigation controller with placeholder as root
|
||||
self.resetDetailNavigationControllerWithPlaceholder(animated: false)
|
||||
|
||||
// Return up to date detail navigation controller
|
||||
// In any cases `detailNavigationController` will be used as secondary view of the split view controller.
|
||||
return self.detailNavigationController
|
||||
}
|
||||
|
||||
/// Adjust the primary view controller and incorporate the secondary view controller into the collapsed interface if needed.
|
||||
/// Return false to let the split view controller try to incorporate the secondary view controller's content into the collapsed interface,
|
||||
/// or true to indicate that you do not want the split view controller to do anything with the secondary view controller.
|
||||
/// Sample case: large iPhone goes from landscape to portrait.
|
||||
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
|
||||
return self.masterPresentable?.collapseDetailViewController ?? false
|
||||
|
||||
// If the secondary view is the placeholder screen do not merge the secondary into the primary.
|
||||
// Note: In this case, the secondaryViewController will be automatically discarded.
|
||||
if self.isPlaceholderShown(from: secondaryViewController) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return false to let the split view controller try to incorporate the secondary view controller's content into the collapsed interface.
|
||||
// If the primary root controller of a UISplitViewController is a UINavigationController,
|
||||
// it's possible to have nested navigation controllers due to private property `_allowNestedNavigationControllers` set to true (https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/).
|
||||
// So in this case returning false here will push the `detailNavigationController` on top of the `primaryNavigationController`.
|
||||
// Sample primary view stack:
|
||||
// primaryNavigationController[
|
||||
// MasterTabBarController,
|
||||
// detailNavigationController[RoomViewController, RoomInfoListViewController]]
|
||||
// Note that normally pushing a navigation controller on top of a navigation controller don't work.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - UINavigationControllerDelegate
|
||||
// MARK: - TabBarCoordinatorDelegate
|
||||
extension SplitViewCoordinator: TabBarCoordinatorDelegate {
|
||||
func tabBarCoordinatorDidCompleteAuthentication(_ coordinator: TabBarCoordinatorType) {
|
||||
self.delegate?.splitViewCoordinatorDidCompleteAuthentication(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - SplitViewMasterPresentableDelegate
|
||||
// MARK: - SplitViewMasterPresentableDelegate
|
||||
extension SplitViewCoordinator: SplitViewMasterPresentableDelegate {
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable) {
|
||||
MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: \(presentable) wantsToDisplay detailPresentable: \(detailPresentable)")
|
||||
|
||||
var detailModules: [Presentable] {
|
||||
return self.detailNavigationRouter?.modules ?? []
|
||||
}
|
||||
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToReplaceDetailWith detailPresentable: Presentable, popCompletion: (() -> Void)?) {
|
||||
MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: \(presentable) wantsToReplaceDetailWith detailPresentable: \(detailPresentable)")
|
||||
|
||||
guard let detailNavigationController = self.detailNavigationController else {
|
||||
MXLog.debug("[SplitViewCoordinator] splitViewMasterPresentable: Failed to display because detailNavigationController is nil")
|
||||
return
|
||||
}
|
||||
|
||||
detailNavigationController.viewControllers = [detailPresentable.toPresentable()]
|
||||
let detailController = detailPresentable.toPresentable()
|
||||
|
||||
// Reset the detail navigation controller with the given detail controller
|
||||
self.detailNavigationRouter?.setRootModule(detailPresentable, popCompletion: popCompletion)
|
||||
|
||||
// This will call first UISplitViewControllerDelegate method: `splitViewController(_:showDetail:sender:)`, if implemented, to give the opportunity to customise `UISplitViewController.showDetailViewController(:sender:)` behavior.
|
||||
// - If the split view controller is collpased (one column visible):
|
||||
// The `detailNavigationController` will be pushed on top of the primary navigation controller.
|
||||
// In fact if the primary root controller of a UISplitViewController is a UINavigationController,
|
||||
// it's possible to have nested navigation controllers due to private property `_allowNestedNavigationControllers` set to true (https://blog.malcolmhall.com/2017/01/27/default-behaviour-of-uisplitviewcontroller-collapsesecondaryviewcontroller/).
|
||||
// - Else if the split view controller is not collpased (two column visible)
|
||||
// It will set the `detailNavigationController` as the secondary view of the split view controller
|
||||
self.splitViewController.showDetailViewController(detailNavigationController, sender: nil)
|
||||
|
||||
// Set leftBarButtonItem with split view display mode button if there is no leftBarButtonItem defined
|
||||
detailController.vc_setupDisplayModeLeftBarButtonItemIfNeeded()
|
||||
}
|
||||
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToStack detailPresentable: Presentable, popCompletion: (() -> Void)?) {
|
||||
|
||||
guard let detailNavigationRouter = self.detailNavigationRouter else {
|
||||
MXLog.debug("[SplitViewCoordinator] Failed to stack \(detailPresentable) because detailNavigationRouter is nil")
|
||||
return
|
||||
}
|
||||
|
||||
detailNavigationRouter.push(detailPresentable, animated: true, popCompletion: popCompletion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,30 @@
|
||||
import UIKit
|
||||
|
||||
protocol SplitViewMasterPresentableDelegate: AnyObject {
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable)
|
||||
|
||||
/// Detail items from the split view
|
||||
var detailModules: [Presentable] { get }
|
||||
|
||||
/// Replace split view detail with the given detailPresentable
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToReplaceDetailWith detailPresentable: Presentable, popCompletion: (() -> Void)?)
|
||||
|
||||
/// Stack the detailPresentable on the existing split view detail stack
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToStack detailPresentable: Presentable, popCompletion: (() -> Void)?)
|
||||
}
|
||||
|
||||
/// `SplitViewMasterPresentableDelegate` default implementation
|
||||
extension SplitViewMasterPresentableDelegate {
|
||||
func splitViewMasterPresentable(_ presentable: Presentable, wantsToDisplay detailPresentable: Presentable) {
|
||||
splitViewMasterPresentable(presentable, wantsToReplaceDetailWith: detailPresentable, popCompletion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Protocol used by the master view presentable of a UISplitViewController
|
||||
protocol SplitViewMasterPresentable: AnyObject, Presentable {
|
||||
|
||||
var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? { get set }
|
||||
var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? { get set }
|
||||
|
||||
/// Indicate true if the detail can be collapsed
|
||||
var collapseDetailViewController: Bool { get }
|
||||
|
||||
/// Return the detail view controller to display when the detail is separated from the master view controller
|
||||
func secondViewControllerWhenSeparatedFromPrimary() -> UIViewController?
|
||||
/// Return the currently selected and visible NavigationRouter
|
||||
/// It will be used to manage detail controllers
|
||||
var selectedNavigationRouter: NavigationRouterType? { get }
|
||||
}
|
||||
|
||||
@@ -50,27 +50,28 @@ final class InviteFriendsHeaderView: UIView, NibLoadable, Themable {
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
self.button.setTitle(VectorL10n.inviteFriendsAction(BuildSettings.bundleDisplayName), for: .normal)
|
||||
self.button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
|
||||
button.setTitle(VectorL10n.inviteFriendsAction(BuildSettings.bundleDisplayName), for: .normal)
|
||||
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
|
||||
button.layer.cornerRadius = 8
|
||||
button.layer.borderWidth = 2
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func update(theme: Theme) {
|
||||
self.backgroundColor = theme.backgroundColor
|
||||
button.layer.borderColor = theme.tintColor.cgColor
|
||||
button.setTitleColor(theme.tintColor, for: .normal)
|
||||
button.setTitleColor(theme.tintColor.withAlphaComponent(Constants.buttonHighlightedAlpha), for: .highlighted)
|
||||
button.vc_setBackgroundColor(theme.baseColor, for: .normal)
|
||||
|
||||
self.button.setTitleColor(theme.baseTextPrimaryColor, for: .normal)
|
||||
self.button.setTitleColor(theme.baseTextPrimaryColor.withAlphaComponent(Constants.buttonHighlightedAlpha), for: .highlighted)
|
||||
self.button.vc_setBackgroundColor(theme.tintColor, for: .normal)
|
||||
let buttonImage = Asset.Images.shareActionButton.image.vc_tintedImage(usingColor: theme.tintColor)
|
||||
|
||||
let buttonImage = Asset.Images.shareActionButton.image.vc_tintedImage(usingColor: theme.baseIconPrimaryColor)
|
||||
|
||||
self.button.setImage(buttonImage, for: .normal)
|
||||
button.setImage(buttonImage, for: .normal)
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
@objc private func buttonAction(_ sender: UIButton) {
|
||||
self.delegate?.inviteFriendsHeaderView(self, didTapButton: button)
|
||||
delegate?.inviteFriendsHeaderView(self, didTapButton: button)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -11,14 +11,14 @@
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="cxh-dz-aGG" customClass="InviteFriendsHeaderView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="70"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="64"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K8C-8y-oEb" customClass="CustomRoundedButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="47" y="10" width="320" height="50"/>
|
||||
<rect key="frame" x="35.5" y="10" width="343" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="JuE-b9-RNu"/>
|
||||
<constraint firstAttribute="width" priority="750" constant="320" id="ujQ-vj-edr"/>
|
||||
<constraint firstAttribute="height" constant="44" id="JuE-b9-RNu"/>
|
||||
<constraint firstAttribute="width" priority="750" constant="343" id="ujQ-vj-edr"/>
|
||||
</constraints>
|
||||
<inset key="contentEdgeInsets" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="20" maxY="0.0"/>
|
||||
@@ -35,15 +35,15 @@
|
||||
<constraints>
|
||||
<constraint firstItem="K8C-8y-oEb" firstAttribute="top" secondItem="cxh-dz-aGG" secondAttribute="top" constant="10" id="dLb-B4-eBJ"/>
|
||||
<constraint firstItem="K8C-8y-oEb" firstAttribute="centerX" secondItem="cxh-dz-aGG" secondAttribute="centerX" id="hNU-VW-Hbx"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="K8C-8y-oEb" secondAttribute="trailing" constant="20" id="nXF-QG-u1t"/>
|
||||
<constraint firstItem="K8C-8y-oEb" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="cxh-dz-aGG" secondAttribute="leading" constant="20" id="rZm-C4-mTe"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="K8C-8y-oEb" secondAttribute="trailing" constant="16" id="nXF-QG-u1t"/>
|
||||
<constraint firstItem="K8C-8y-oEb" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="cxh-dz-aGG" secondAttribute="leading" constant="16" id="rZm-C4-mTe"/>
|
||||
<constraint firstAttribute="bottom" secondItem="K8C-8y-oEb" secondAttribute="bottom" constant="10" id="tDj-72-Eek"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="button" destination="K8C-8y-oEb" id="xU3-t7-lLR"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="114.49275362318842" y="-637.5"/>
|
||||
<point key="canvasLocation" x="114.49275362318842" y="-639.50892857142856"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
|
||||
@@ -120,9 +120,6 @@
|
||||
_searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||
[self refreshSearchBarItemsColor:_searchBarView];
|
||||
|
||||
// Hide line separators of empty cells
|
||||
self.contactsTableView.tableFooterView = [[UIView alloc] init];
|
||||
|
||||
[self.contactsTableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"];
|
||||
|
||||
// Redirect table data source
|
||||
@@ -167,13 +164,13 @@
|
||||
|
||||
[self refreshSearchBarItemsColor:_searchBarView];
|
||||
|
||||
_searchBarHeaderBorder.backgroundColor = ThemeService.shared.theme.headerBorderColor;
|
||||
|
||||
// Check the table view style to select its bg color.
|
||||
self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor);
|
||||
self.contactsTableView.backgroundColor = ((self.contactsTableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.baseColor : ThemeService.shared.theme.headerBackgroundColor);
|
||||
self.view.backgroundColor = self.contactsTableView.backgroundColor;
|
||||
self.contactsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor;
|
||||
|
||||
_searchBarHeaderBorder.backgroundColor = self.contactsTableView.backgroundColor;
|
||||
|
||||
if (self.contactsTableView.dataSource)
|
||||
{
|
||||
[self.contactsTableView reloadData];
|
||||
@@ -680,7 +677,6 @@
|
||||
{
|
||||
// bar tint color
|
||||
searchBar.barTintColor = searchBar.tintColor = ThemeService.shared.theme.tintColor;
|
||||
searchBar.tintColor = ThemeService.shared.theme.tintColor;
|
||||
|
||||
// FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals.
|
||||
|
||||
@@ -691,28 +687,27 @@
|
||||
// Magnifying glass icon.
|
||||
UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView;
|
||||
leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
leftImageView.tintColor = ThemeService.shared.theme.tintColor;
|
||||
leftImageView.tintColor = ThemeService.shared.theme.textSecondaryColor;
|
||||
|
||||
// remove the gray background color
|
||||
UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"];
|
||||
UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"];
|
||||
// Use the theme's grey color.
|
||||
// The effect views are needed due to minimal style.
|
||||
// With default style there is a border above the search bar.
|
||||
searchBarTextField.backgroundColor = ThemeService.shared.theme.textQuinaryColor;
|
||||
UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"];
|
||||
UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"];
|
||||
effectBackgroundTop.hidden = YES;
|
||||
effectBackgroundBottom.hidden = YES;
|
||||
|
||||
// place holder
|
||||
if (searchBarTextField.placeholder)
|
||||
{
|
||||
searchBarTextField.textColor = ThemeService.shared.theme.placeholderTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
[contactsDataSource searchWithPattern:searchText forceReset:NO];
|
||||
|
||||
self.contactsAreFilteredWithSearch = searchText.length ? YES : NO;
|
||||
}
|
||||
|
||||
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
|
||||
{
|
||||
{
|
||||
self.isAddParticipantSearchBarEditing = YES;
|
||||
searchBar.showsCancelButton = NO;
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -25,7 +23,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="kNf-Ll-jvH">
|
||||
<rect key="frame" x="0.0" y="70" width="375" height="597"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="617"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="StartChatVCTableView"/>
|
||||
@@ -36,10 +34,10 @@
|
||||
</connections>
|
||||
</tableView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zm7-AB-ZtE">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="50"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
|
||||
<subviews>
|
||||
<searchBar contentMode="redraw" searchBarStyle="minimal" translatesAutoresizingMaskIntoConstraints="NO" id="bsq-3U-VjV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
|
||||
<rect key="frame" x="8" y="0.0" width="359" height="50"/>
|
||||
<textInputTraits key="textInputTraits" returnKeyType="done"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="StartChatVCSearchBarView"/>
|
||||
@@ -60,9 +58,9 @@
|
||||
<accessibility key="accessibilityConfiguration" identifier="StartChatVCSearchBar"/>
|
||||
<constraints>
|
||||
<constraint firstItem="gcy-W7-89G" firstAttribute="leading" secondItem="Zm7-AB-ZtE" secondAttribute="leading" id="4Yn-dN-O2U"/>
|
||||
<constraint firstItem="bsq-3U-VjV" firstAttribute="leading" secondItem="Zm7-AB-ZtE" secondAttribute="leading" id="6ze-Az-ymf"/>
|
||||
<constraint firstItem="bsq-3U-VjV" firstAttribute="leading" secondItem="Zm7-AB-ZtE" secondAttribute="leading" constant="8" id="6ze-Az-ymf"/>
|
||||
<constraint firstAttribute="bottom" secondItem="bsq-3U-VjV" secondAttribute="bottom" id="KDW-SI-sG6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="bsq-3U-VjV" secondAttribute="trailing" id="ZlE-SL-UfQ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="bsq-3U-VjV" secondAttribute="trailing" constant="8" id="ZlE-SL-UfQ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gcy-W7-89G" secondAttribute="trailing" id="hqD-vA-OM5"/>
|
||||
<constraint firstAttribute="bottom" secondItem="gcy-W7-89G" secondAttribute="bottom" id="ibU-h7-mHt"/>
|
||||
<constraint firstAttribute="height" constant="50" id="kSM-fg-IHB"/>
|
||||
@@ -70,6 +68,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Dt9-ew-iCA"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="kNf-Ll-jvH" secondAttribute="bottom" id="0YZ-gM-dTC"/>
|
||||
@@ -80,7 +79,7 @@
|
||||
<constraint firstAttribute="trailing" secondItem="Zm7-AB-ZtE" secondAttribute="trailing" id="rMP-c5-JSU"/>
|
||||
<constraint firstItem="kNf-Ll-jvH" firstAttribute="top" secondItem="Zm7-AB-ZtE" secondAttribute="bottom" id="tYv-VV-8dI"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Dt9-ew-iCA"/>
|
||||
<point key="canvasLocation" x="135" y="115"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
|
||||
@@ -19,17 +19,13 @@
|
||||
|
||||
#import "AuthenticationViewController.h"
|
||||
|
||||
#import "RoomPreviewData.h"
|
||||
#import "HomeViewController.h"
|
||||
#import "FavouritesViewController.h"
|
||||
#import "PeopleViewController.h"
|
||||
#import "RoomsViewController.h"
|
||||
#import "GroupsViewController.h"
|
||||
|
||||
#import "RoomViewController.h"
|
||||
#import "ContactDetailsViewController.h"
|
||||
#import "GroupDetailsViewController.h"
|
||||
#import "UnifiedSearchViewController.h"
|
||||
|
||||
#define TABBAR_HOME_INDEX 0
|
||||
#define TABBAR_FAVOURITES_INDEX 1
|
||||
#define TABBAR_PEOPLE_INDEX 2
|
||||
@@ -175,23 +171,17 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) {
|
||||
@property (nonatomic, readonly) RoomsViewController *roomsViewController;
|
||||
@property (nonatomic, readonly) GroupsViewController *groupsViewController;
|
||||
|
||||
// The current unified search screen if any
|
||||
@property (nonatomic, weak) UnifiedSearchViewController *unifiedSearchViewController;
|
||||
|
||||
// References on the currently selected room and its view controller
|
||||
@property (nonatomic, readonly) RoomViewController *currentRoomViewController;
|
||||
// References on the currently selected room
|
||||
@property (nonatomic, readonly) NSString *selectedRoomId;
|
||||
@property (nonatomic, readonly) NSString *selectedEventId;
|
||||
@property (nonatomic, readonly) MXSession *selectedRoomSession;
|
||||
@property (nonatomic, readonly) MXKRoomDataSource *selectedRoomDataSource;
|
||||
@property (nonatomic, readonly) RoomPreviewData *selectedRoomPreviewData;
|
||||
|
||||
// References on the currently selected contact and its view controller
|
||||
@property (nonatomic, readonly) ContactDetailsViewController *currentContactDetailViewController;
|
||||
// References on the currently selected contact
|
||||
@property (nonatomic, readonly) MXKContact *selectedContact;
|
||||
|
||||
// References on the currently selected group and its view controller
|
||||
@property (nonatomic, readonly) GroupDetailsViewController *currentGroupDetailViewController;
|
||||
// References on the currently selected group
|
||||
@property (nonatomic, readonly) MXGroup *selectedGroup;
|
||||
@property (nonatomic, readonly) MXSession *selectedGroupSession;
|
||||
|
||||
@@ -203,13 +193,18 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) {
|
||||
|
||||
- (void)removeTabAt:(MasterTabBarIndex)index;
|
||||
|
||||
- (void)selectTabAtIndex:(MasterTabBarIndex)tabBarIndex;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol MasterTabBarControllerDelegate <NSObject>
|
||||
|
||||
- (void)masterTabBarControllerDidCompleteAuthentication:(MasterTabBarController *)masterTabBarController;
|
||||
- (void)masterTabBarController:(MasterTabBarController*)masterTabBarController wantsToDisplayDetailViewController:(UIViewController*)detailViewController;
|
||||
- (void)masterTabBarController:(MasterTabBarController*)masterTabBarController needsSideMenuIconWithNotification:(BOOL)displayNotification;
|
||||
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession completion:(void (^)(void))completion;
|
||||
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomPreviewWithData:(RoomPreviewData*)roomPreviewData;
|
||||
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectContact:(MXKContact*)contact;
|
||||
- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession;
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
#import "MasterTabBarController.h"
|
||||
|
||||
#import "UnifiedSearchViewController.h"
|
||||
|
||||
#import "RecentsDataSource.h"
|
||||
#import "GroupsDataSource.h"
|
||||
|
||||
@@ -234,12 +232,6 @@
|
||||
|
||||
[[AppDelegate theDelegate] checkAppVersion];
|
||||
}
|
||||
|
||||
if (self.unifiedSearchViewController)
|
||||
{
|
||||
[self.unifiedSearchViewController destroy];
|
||||
self.unifiedSearchViewController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated
|
||||
@@ -325,6 +317,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)selectTabAtIndex:(MasterTabBarIndex)tabBarIndex
|
||||
{
|
||||
NSInteger index = [self indexOfTabItemWithTag:tabBarIndex];
|
||||
self.selectedIndex = index;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSArray<MXSession*>*)mxSessions
|
||||
@@ -588,60 +586,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showRoomDetails
|
||||
{
|
||||
[self releaseCurrentDetailsViewController];
|
||||
|
||||
if (_selectedRoomPreviewData)
|
||||
{
|
||||
// Replace the rootviewcontroller with a room view controller
|
||||
// Get the RoomViewController from the storyboard
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
|
||||
_currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"];
|
||||
|
||||
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:_currentRoomViewController];
|
||||
|
||||
[_currentRoomViewController displayRoomPreview:_selectedRoomPreviewData];
|
||||
_selectedRoomPreviewData = nil;
|
||||
|
||||
[self setupLeftBarButtonItem];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXWeakify(self);
|
||||
void (^openRoomDataSource)(MXKRoomDataSource *roomDataSource) = ^(MXKRoomDataSource *roomDataSource) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
// Replace the rootviewcontroller with a room view controller
|
||||
// Get the RoomViewController from the storyboard
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
|
||||
self->_currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"];
|
||||
|
||||
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:self.currentRoomViewController];
|
||||
|
||||
[self.currentRoomViewController displayRoom:roomDataSource];
|
||||
|
||||
[self setupLeftBarButtonItem];
|
||||
|
||||
};
|
||||
|
||||
if (_selectedRoomDataSource)
|
||||
{
|
||||
// If the room data source is already loaded, display it
|
||||
openRoomDataSource(_selectedRoomDataSource);
|
||||
_selectedRoomDataSource = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else, load it. The user may see the EmptyDetailsViewControllerStoryboardId
|
||||
// screen in this case
|
||||
[self dataSourceOfRoomToDisplay:^(MXKRoomDataSource *roomDataSource) {
|
||||
openRoomDataSource(roomDataSource);
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)selectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
[self selectRoomWithId:roomId andEventId:eventId inMatrixSession:matrixSession completion:nil];
|
||||
@@ -649,98 +593,51 @@
|
||||
|
||||
- (void)selectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession completion:(void (^)(void))completion
|
||||
{
|
||||
if (_selectedRoomId && [_selectedRoomId isEqualToString:roomId]
|
||||
&& _selectedEventId && [_selectedEventId isEqualToString:eventId]
|
||||
&& _selectedRoomSession && _selectedRoomSession == matrixSession)
|
||||
{
|
||||
// Nothing to do
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self releaseSelectedItem];
|
||||
|
||||
_selectedRoomId = roomId;
|
||||
_selectedEventId = eventId;
|
||||
_selectedRoomSession = matrixSession;
|
||||
_selectedRoomSession = matrixSession;
|
||||
|
||||
if (roomId && matrixSession)
|
||||
{
|
||||
// Preload the data source before performing the segue
|
||||
MXWeakify(self);
|
||||
[self dataSourceOfRoomToDisplay:^(MXKRoomDataSource *roomDataSource) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
self->_selectedRoomDataSource = roomDataSource;
|
||||
|
||||
[self showRoomDetails];
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self releaseSelectedItem];
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}
|
||||
[self.masterTabBarDelegate masterTabBarController:self didSelectRoomWithId:roomId andEventId:eventId inMatrixSession:matrixSession completion:completion];
|
||||
|
||||
[self refreshSelectedControllerSelectedCellIfNeeded];
|
||||
}
|
||||
|
||||
- (void)showRoomPreview:(RoomPreviewData *)roomPreviewData
|
||||
{
|
||||
[self releaseSelectedItem];
|
||||
|
||||
_selectedRoomPreviewData = roomPreviewData;
|
||||
_selectedRoomId = roomPreviewData.roomId;
|
||||
_selectedRoomSession = roomPreviewData.mxSession;
|
||||
|
||||
[self showRoomDetails];
|
||||
[self.masterTabBarDelegate masterTabBarController:self didSelectRoomPreviewWithData:roomPreviewData];
|
||||
|
||||
[self refreshSelectedControllerSelectedCellIfNeeded];
|
||||
}
|
||||
|
||||
- (void)selectContact:(MXKContact*)contact
|
||||
{
|
||||
[self releaseSelectedItem];
|
||||
|
||||
_selectedContact = contact;
|
||||
|
||||
[self showContactDetails];
|
||||
}
|
||||
|
||||
- (void)showContactDetails
|
||||
{
|
||||
[self releaseCurrentDetailsViewController];
|
||||
[self.masterTabBarDelegate masterTabBarController:self didSelectContact:contact];
|
||||
|
||||
// Replace the rootviewcontroller with a contact details view controller
|
||||
_currentContactDetailViewController = [ContactDetailsViewController contactDetailsViewController];
|
||||
_currentContactDetailViewController.enableVoipCall = NO;
|
||||
_currentContactDetailViewController.contact = _selectedContact;
|
||||
|
||||
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:_currentContactDetailViewController];
|
||||
|
||||
[self setupLeftBarButtonItem];
|
||||
[self refreshSelectedControllerSelectedCellIfNeeded];
|
||||
}
|
||||
|
||||
- (void)selectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
[self releaseSelectedItem];
|
||||
|
||||
_selectedGroup = group;
|
||||
_selectedGroupSession = matrixSession;
|
||||
|
||||
[self showGroupDetails];
|
||||
}
|
||||
|
||||
- (void)showGroupDetails
|
||||
{
|
||||
[self releaseCurrentDetailsViewController];
|
||||
[self.masterTabBarDelegate masterTabBarController:self didSelectGroup:group inMatrixSession:matrixSession];
|
||||
|
||||
// Replace the rootviewcontroller with a group details view controller
|
||||
_currentGroupDetailViewController = [GroupDetailsViewController groupDetailsViewController];
|
||||
[_currentGroupDetailViewController setGroup:_selectedGroup withMatrixSession:_selectedGroupSession];
|
||||
|
||||
[self.masterTabBarDelegate masterTabBarController:self wantsToDisplayDetailViewController:_currentGroupDetailViewController];
|
||||
|
||||
[self setupLeftBarButtonItem];
|
||||
[self refreshSelectedControllerSelectedCellIfNeeded];
|
||||
}
|
||||
|
||||
- (void)releaseSelectedItem
|
||||
@@ -748,15 +645,12 @@
|
||||
_selectedRoomId = nil;
|
||||
_selectedEventId = nil;
|
||||
_selectedRoomSession = nil;
|
||||
_selectedRoomDataSource = nil;
|
||||
_selectedRoomPreviewData = nil;
|
||||
|
||||
_selectedContact = nil;
|
||||
|
||||
_selectedGroup = nil;
|
||||
_selectedGroupSession = nil;
|
||||
|
||||
[self releaseCurrentDetailsViewController];
|
||||
_selectedGroupSession = nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)missedDiscussionsCount
|
||||
@@ -844,84 +738,6 @@
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
Load the data source of the room to open.
|
||||
|
||||
@param onComplete a block providing the loaded room data source.
|
||||
*/
|
||||
- (void)dataSourceOfRoomToDisplay:(void (^)(MXKRoomDataSource *roomDataSource))onComplete
|
||||
{
|
||||
// Check whether an event has been selected from messages or files search tab.
|
||||
MXEvent *selectedSearchEvent = self.unifiedSearchViewController.selectedSearchEvent;
|
||||
MXSession *selectedSearchEventSession = self.unifiedSearchViewController.selectedSearchEventSession;
|
||||
|
||||
if (!selectedSearchEvent)
|
||||
{
|
||||
if (!_selectedEventId)
|
||||
{
|
||||
// LIVE: Show the room live timeline managed by MXKRoomDataSourceManager
|
||||
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:_selectedRoomSession];
|
||||
|
||||
[roomDataSourceManager roomDataSourceForRoom:_selectedRoomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) {
|
||||
onComplete(roomDataSource);
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open the room on the requested event
|
||||
[RoomDataSource loadRoomDataSourceWithRoomId:_selectedRoomId initialEventId:_selectedEventId andMatrixSession:_selectedRoomSession onComplete:^(id roomDataSource) {
|
||||
|
||||
((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES;
|
||||
|
||||
// Give the data source ownership to the room view controller.
|
||||
self.currentRoomViewController.hasRoomDataSourceOwnership = YES;
|
||||
|
||||
onComplete(roomDataSource);
|
||||
}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Search result: Create a temp timeline from the selected event
|
||||
[RoomDataSource loadRoomDataSourceWithRoomId:selectedSearchEvent.roomId initialEventId:selectedSearchEvent.eventId andMatrixSession:selectedSearchEventSession onComplete:^(id roomDataSource) {
|
||||
|
||||
[roomDataSource finalizeInitialization];
|
||||
|
||||
((RoomDataSource*)roomDataSource).markTimelineInitialEvent = YES;
|
||||
|
||||
// Give the data source ownership to the room view controller.
|
||||
self.currentRoomViewController.hasRoomDataSourceOwnership = YES;
|
||||
|
||||
onComplete(roomDataSource);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupLeftBarButtonItem
|
||||
{
|
||||
if (self.splitViewController)
|
||||
{
|
||||
// Refresh selected cell without scrolling the selected cell (We suppose it's visible here)
|
||||
[self refreshCurrentSelectedCell:NO];
|
||||
|
||||
if (_currentRoomViewController)
|
||||
{
|
||||
_currentRoomViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
|
||||
_currentRoomViewController.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
}
|
||||
else if (_currentContactDetailViewController)
|
||||
{
|
||||
_currentContactDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
|
||||
_currentContactDetailViewController.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
}
|
||||
else if (_currentGroupDetailViewController)
|
||||
{
|
||||
_currentGroupDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
|
||||
_currentGroupDetailViewController.navigationItem.leftItemsSupplementBackButton = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void)setupTitleView
|
||||
{
|
||||
titleView = [MainTitleView new];
|
||||
@@ -936,6 +752,15 @@
|
||||
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
|
||||
}
|
||||
|
||||
- (void)refreshSelectedControllerSelectedCellIfNeeded
|
||||
{
|
||||
if (self.splitViewController)
|
||||
{
|
||||
// Refresh selected cell without scrolling the selected cell (We suppose it's visible here)
|
||||
[self refreshCurrentSelectedCell:NO];
|
||||
}
|
||||
}
|
||||
|
||||
// Made the actual selected view controller update its selected cell.
|
||||
- (void)refreshCurrentSelectedCell:(BOOL)forceVisible
|
||||
{
|
||||
@@ -947,37 +772,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)releaseCurrentDetailsViewController
|
||||
{
|
||||
// Release the existing details view controller (if any).
|
||||
if (_currentRoomViewController)
|
||||
{
|
||||
// If the displayed data is not a preview, let the manager release the room data source
|
||||
// (except if the view controller has the room data source ownership).
|
||||
if (!_currentRoomViewController.roomPreviewData && _currentRoomViewController.roomDataSource && !_currentRoomViewController.hasRoomDataSourceOwnership)
|
||||
{
|
||||
MXSession *mxSession = _currentRoomViewController.roomDataSource.mxSession;
|
||||
MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession];
|
||||
|
||||
// Let the manager release live room data sources where the user is in
|
||||
[roomDataSourceManager closeRoomDataSourceWithRoomId:_currentRoomViewController.roomDataSource.roomId forceClose:NO];
|
||||
}
|
||||
|
||||
[_currentRoomViewController destroy];
|
||||
_currentRoomViewController = nil;
|
||||
}
|
||||
else if (_currentContactDetailViewController)
|
||||
{
|
||||
[_currentContactDetailViewController destroy];
|
||||
_currentContactDetailViewController = nil;
|
||||
}
|
||||
else if (_currentGroupDetailViewController)
|
||||
{
|
||||
[_currentGroupDetailViewController destroy];
|
||||
_currentGroupDetailViewController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden
|
||||
{
|
||||
_hidden = hidden;
|
||||
|
||||
@@ -18,18 +18,6 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/// TabBarCoordinator input parameters
|
||||
class TabBarCoordinatorParameters {
|
||||
|
||||
let userSessionsService: UserSessionsService
|
||||
let appNavigator: AppNavigatorProtocol
|
||||
|
||||
init(userSessionsService: UserSessionsService, appNavigator: AppNavigatorProtocol) {
|
||||
self.userSessionsService = userSessionsService
|
||||
self.appNavigator = appNavigator
|
||||
}
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
|
||||
@@ -39,8 +27,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
|
||||
private let parameters: TabBarCoordinatorParameters
|
||||
|
||||
/// Completion called when `popToHomeAnimated:` has been completed.
|
||||
private var popToHomeViewControllerCompletion: (() -> Void)?
|
||||
// Indicate if the Coordinator has started once
|
||||
private var hasStartedOnce: Bool {
|
||||
return self.masterTabBarController != nil
|
||||
}
|
||||
|
||||
// TODO: Move MasterTabBarController navigation code here
|
||||
// and if possible use a simple: `private let tabBarController: UITabBarController`
|
||||
@@ -57,6 +47,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
return parameters.userSessionsService.mainUserSession?.matrixSession
|
||||
}
|
||||
|
||||
private var isTabBarControllerTopMostController: Bool {
|
||||
return self.navigationRouter.modules.last is MasterTabBarController
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
@@ -85,8 +79,8 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
func start(with spaceId: String?) {
|
||||
self.currentSpaceId = spaceId
|
||||
|
||||
// If start has been done once do setup view controllers again
|
||||
if self.masterTabBarController == nil {
|
||||
// If start has been done once do not setup view controllers again
|
||||
if self.hasStartedOnce == false {
|
||||
let masterTabBarController = self.createMasterTabBarController()
|
||||
masterTabBarController.masterTabBarDelegate = self
|
||||
self.masterTabBarController = masterTabBarController
|
||||
@@ -102,20 +96,18 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
}
|
||||
|
||||
self.registerUserSessionsServiceNotifications()
|
||||
self.registerSessionChange()
|
||||
|
||||
if let homeViewController = homeViewControllerWrapperViewController {
|
||||
let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: masterTabBarController,
|
||||
bannerPresenter: homeViewController,
|
||||
themeService: ThemeService.shared())
|
||||
versionCheckCoordinator.start()
|
||||
add(childCoordinator: versionCheckCoordinator)
|
||||
}
|
||||
}
|
||||
|
||||
self.updateMasterTabBarController(with: spaceId)
|
||||
|
||||
self.registerUserSessionsServiceNotifications()
|
||||
self.registerSessionChange()
|
||||
|
||||
if let homeViewController = homeViewControllerWrapperViewController {
|
||||
let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: masterTabBarController,
|
||||
bannerPresenter: homeViewController,
|
||||
themeService: ThemeService.shared())
|
||||
versionCheckCoordinator.start()
|
||||
add(childCoordinator: versionCheckCoordinator)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
@@ -127,43 +119,70 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
}
|
||||
|
||||
func popToHome(animated: Bool, completion: (() -> Void)?) {
|
||||
|
||||
// Force back to the main screen if this is not the one that is displayed
|
||||
if masterTabBarController != masterNavigationController.visibleViewController {
|
||||
|
||||
// Listen to the masterNavigationController changes
|
||||
// We need to be sure that masterTabBarController is back to the screen
|
||||
popToHomeViewControllerCompletion = completion
|
||||
masterNavigationController.delegate = self
|
||||
|
||||
let didPopToHome: (() -> Void) = {
|
||||
|
||||
// For unknown reason, the navigation bar is not restored correctly by [popToViewController:animated:]
|
||||
// when a ViewController has hidden it (see MXKAttachmentsViewController).
|
||||
// Patch: restore navigation bar by default here.
|
||||
self.masterNavigationController.isNavigationBarHidden = false
|
||||
|
||||
if masterNavigationController.viewControllers.last == masterTabBarController {
|
||||
self.navigationController(masterNavigationController, didShow: masterTabBarController, animated: false)
|
||||
// Release the current selected item (room/contact/...).
|
||||
self.masterTabBarController.releaseSelectedItem()
|
||||
|
||||
// Select home tab
|
||||
self.masterTabBarController.selectTab(at: .home)
|
||||
|
||||
completion?()
|
||||
}
|
||||
|
||||
// If MasterTabBarController is not visible because there is a modal above it
|
||||
// but still the top view controller of navigation controller
|
||||
if self.isTabBarControllerTopMostController {
|
||||
didPopToHome()
|
||||
} else {
|
||||
masterNavigationController.popToViewController(masterTabBarController, animated: animated)
|
||||
// Otherwise MasterTabBarController is not the top controller of the navigation controller
|
||||
|
||||
// Waiting for `self.navigationRouter` popping to MasterTabBarController
|
||||
var token: NSObjectProtocol?
|
||||
token = NotificationCenter.default.addObserver(forName: NavigationRouter.didPopModule, object: self.navigationRouter, queue: OperationQueue.main) { [weak self] (notification) in
|
||||
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
// If MasterTabBarController is now the top most controller in navigation controller stack call the completion
|
||||
if self.isTabBarControllerTopMostController {
|
||||
|
||||
didPopToHome()
|
||||
|
||||
if let token = token {
|
||||
NotificationCenter.default.removeObserver(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop to root view controller
|
||||
self.navigationRouter.popToRootModule(animated: animated)
|
||||
}
|
||||
} else {
|
||||
// Tab bar controller is already visible
|
||||
// Select the Home tab
|
||||
masterTabBarController.selectedIndex = Int(TABBAR_HOME_INDEX)
|
||||
masterTabBarController.selectTab(at: .home)
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SplitViewMasterPresentable
|
||||
|
||||
var collapseDetailViewController: Bool {
|
||||
if (masterTabBarController.currentRoomViewController == nil) && (masterTabBarController.currentContactDetailViewController == nil) && (masterTabBarController.currentGroupDetailViewController == nil) {
|
||||
// Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func secondViewControllerWhenSeparatedFromPrimary() -> UIViewController? {
|
||||
// Return the top view controller of the master navigation controller, if it is a navigation controller itself.
|
||||
if let topViewController = masterNavigationController.topViewController as? UINavigationController {
|
||||
// Keep the detail scene
|
||||
return topViewController
|
||||
}
|
||||
return nil
|
||||
var selectedNavigationRouter: NavigationRouterType? {
|
||||
return self.navigationRouter
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
@@ -302,6 +321,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
self.parameters.appNavigator.sideMenu.show(from: self.masterTabBarController, animated: true)
|
||||
}
|
||||
|
||||
private func dismissSideMenu(animated: Bool) {
|
||||
self.parameters.appNavigator.sideMenu.dismiss(animated: animated)
|
||||
}
|
||||
|
||||
// FIXME: Should be displayed per tab.
|
||||
private func showSettings() {
|
||||
let viewController = self.createSettingsViewController()
|
||||
@@ -313,23 +336,96 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
private func showUnifiedSearch() {
|
||||
let viewController = self.createUnifiedSearchController()
|
||||
|
||||
self.masterTabBarController.unifiedSearchViewController = viewController
|
||||
self.navigationRouter.push(viewController, animated: true, popCompletion: nil)
|
||||
}
|
||||
|
||||
// FIXME: Should be displayed from a tab.
|
||||
private func showContactDetails() {
|
||||
// TODO: Implement
|
||||
private func showContactDetails(with contact: MXKContact) {
|
||||
|
||||
let coordinatorParameters = ContactDetailsCoordinatorParameters(contact: contact)
|
||||
let coordinator = ContactDetailsCoordinator(parameters: coordinatorParameters)
|
||||
coordinator.start()
|
||||
self.add(childCoordinator: coordinator)
|
||||
|
||||
self.replaceSplitViewDetails(with: coordinator) { [weak self] in
|
||||
self?.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Should be displayed from a tab.
|
||||
private func showRoomDetails() {
|
||||
// TODO: Implement
|
||||
private func showGroupDetails(with group: MXGroup, for matrixSession: MXSession) {
|
||||
let coordinatorParameters = GroupDetailsCoordinatorParameters(session: matrixSession, group: group)
|
||||
let coordinator = GroupDetailsCoordinator(parameters: coordinatorParameters)
|
||||
coordinator.start()
|
||||
self.add(childCoordinator: coordinator)
|
||||
|
||||
self.replaceSplitViewDetails(with: coordinator) {
|
||||
[weak self] in
|
||||
self?.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Should be displayed from a tab.
|
||||
private func showGroupDetails() {
|
||||
// TODO: Implement
|
||||
private func showRoom(with roomId: String) {
|
||||
|
||||
guard let matrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else {
|
||||
return
|
||||
}
|
||||
|
||||
self.showRoom(with: roomId, eventId: nil, matrixSession: matrixSession)
|
||||
}
|
||||
|
||||
private func showRoom(with roomId: String, eventId: String?, matrixSession: MXSession, completion: (() -> Void)? = nil) {
|
||||
|
||||
// RoomCoordinator will be presented by the split view.
|
||||
// As we don't know which navigation controller instance will be used,
|
||||
// give the NavigationRouterStore instance and let it find the associated navigation controller
|
||||
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, session: matrixSession, roomId: roomId, eventId: eventId)
|
||||
|
||||
self.showRoom(with: roomCoordinatorParameters, completion: completion)
|
||||
}
|
||||
|
||||
private func showRoomPreview(with previewData: RoomPreviewData) {
|
||||
|
||||
// RoomCoordinator will be presented by the split view
|
||||
// We don't which navigation controller instance will be used
|
||||
// Give the NavigationRouterStore instance and let it find the associated navigation controller if needed
|
||||
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, previewData: previewData)
|
||||
|
||||
self.showRoom(with: roomCoordinatorParameters)
|
||||
}
|
||||
|
||||
private func showRoom(with parameters: RoomCoordinatorParameters, completion: (() -> Void)? = nil) {
|
||||
|
||||
if let topRoomCoordinator = self.splitViewMasterPresentableDelegate?.detailModules.last as? RoomCoordinatorProtocol,
|
||||
parameters.roomId == topRoomCoordinator.roomId && parameters.session == topRoomCoordinator.mxSession {
|
||||
|
||||
// RoomCoordinator with the same room id and Matrix session is shown
|
||||
|
||||
if let eventId = parameters.eventId {
|
||||
// If there is an event id ask the RoomCoordinator to start with this one
|
||||
topRoomCoordinator.start(withEventId: eventId, completion: completion)
|
||||
} else {
|
||||
// If there is no event id defined do nothing
|
||||
completion?()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let coordinator = RoomCoordinator(parameters: parameters)
|
||||
coordinator.delegate = self
|
||||
coordinator.start(withCompletion: completion)
|
||||
self.add(childCoordinator: coordinator)
|
||||
|
||||
self.replaceSplitViewDetails(with: coordinator) {
|
||||
[weak self] in
|
||||
// NOTE: The RoomDataSource releasing is handled in SplitViewCoordinator
|
||||
self?.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the split view is collapsed (one column visible) it will push the Presentable on the primary navigation controller, otherwise it will show the Presentable as the secondary view of the split view.
|
||||
private func replaceSplitViewDetails(with presentable: Presentable, popCompletion: (() -> Void)? = nil) {
|
||||
self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToReplaceDetailWith: presentable, popCompletion: popCompletion)
|
||||
}
|
||||
|
||||
// MARK: UserSessions management
|
||||
@@ -387,45 +483,27 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UINavigationControllerDelegate
|
||||
extension TabBarCoordinator: UINavigationControllerDelegate {
|
||||
|
||||
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
|
||||
|
||||
if viewController == masterTabBarController {
|
||||
masterNavigationController.delegate = nil
|
||||
|
||||
// For unknown reason, the navigation bar is not restored correctly by [popToViewController:animated:]
|
||||
// when a ViewController has hidden it (see MXKAttachmentsViewController).
|
||||
// Patch: restore navigation bar by default here.
|
||||
masterNavigationController.isNavigationBarHidden = false
|
||||
|
||||
// Release the current selected item (room/contact/...).
|
||||
masterTabBarController.releaseSelectedItem()
|
||||
|
||||
if let popToHomeViewControllerCompletion = self.popToHomeViewControllerCompletion {
|
||||
let popToHomeViewControllerCompletion2: (() -> Void)? = popToHomeViewControllerCompletion
|
||||
self.popToHomeViewControllerCompletion = nil
|
||||
|
||||
DispatchQueue.main.async {
|
||||
popToHomeViewControllerCompletion2?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MasterTabBarControllerDelegate
|
||||
extension TabBarCoordinator: MasterTabBarControllerDelegate {
|
||||
|
||||
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelectRoomPreviewWith roomPreviewData: RoomPreviewData!) {
|
||||
self.showRoomPreview(with: roomPreviewData)
|
||||
}
|
||||
|
||||
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelect contact: MXKContact!) {
|
||||
self.showContactDetails(with: contact)
|
||||
}
|
||||
|
||||
func masterTabBarControllerDidCompleteAuthentication(_ masterTabBarController: MasterTabBarController!) {
|
||||
self.delegate?.tabBarCoordinatorDidCompleteAuthentication(self)
|
||||
}
|
||||
|
||||
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, wantsToDisplayDetailViewController detailViewController: UIViewController!) {
|
||||
|
||||
self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToDisplay: detailViewController)
|
||||
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelectRoomWithId roomId: String!, andEventId eventId: String!, inMatrixSession matrixSession: MXSession!, completion: (() -> Void)!) {
|
||||
self.showRoom(with: roomId, eventId: eventId, matrixSession: matrixSession, completion: completion)
|
||||
}
|
||||
|
||||
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelect group: MXGroup!, inMatrixSession matrixSession: MXSession!) {
|
||||
self.showGroupDetails(with: group, for: matrixSession)
|
||||
}
|
||||
|
||||
func masterTabBarController(_ masterTabBarController: MasterTabBarController!, needsSideMenuIconWithNotification displayNotification: Bool) {
|
||||
@@ -439,6 +517,26 @@ extension TabBarCoordinator: MasterTabBarControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoomCoordinatorDelegate
|
||||
extension TabBarCoordinator: RoomCoordinatorDelegate {
|
||||
|
||||
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) {
|
||||
self.remove(childCoordinator: coordinator)
|
||||
}
|
||||
|
||||
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
|
||||
self.navigationRouter.popModule(animated: true)
|
||||
}
|
||||
|
||||
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {
|
||||
self.navigationRouter.popModule(animated: true)
|
||||
}
|
||||
|
||||
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) {
|
||||
self.showRoom(with: roomId)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// TabBarCoordinator input parameters
|
||||
class TabBarCoordinatorParameters {
|
||||
|
||||
let userSessionsService: UserSessionsService
|
||||
let appNavigator: AppNavigatorProtocol
|
||||
|
||||
init(userSessionsService: UserSessionsService, appNavigator: AppNavigatorProtocol) {
|
||||
self.userSessionsService = userSessionsService
|
||||
self.appNavigator = appNavigator
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user