Merge pull request #2644 from vector-im/riot_2600_2

Prompt to accept integration manager policies on use
This commit is contained in:
manuroe
2019-08-14 13:39:20 +02:00
committed by GitHub
21 changed files with 1251 additions and 19 deletions
+6
View File
@@ -1,3 +1,9 @@
Changes in 0.9.3 (2019-08-)
===============================================
Improvements:
* Prompt to accept integration manager policies on use (#2600).
Changes in 0.9.2 (2019-08-08)
===============================================
+68
View File
@@ -95,6 +95,17 @@
32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */; };
32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995421FA2AB700698084 /* SettingsKeyBackupViewAction.swift */; };
32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995621FB07A400698084 /* SettingsKeyBackupTableViewSection.swift */; };
32DB557522FDADE50016329E /* ServiceTermsModalCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556922FDADE50016329E /* ServiceTermsModalCoordinatorType.swift */; };
32DB557622FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556A22FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift */; };
32DB557722FDADE50016329E /* ServiceTermsModalCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556B22FDADE50016329E /* ServiceTermsModalCoordinator.swift */; };
32DB557822FDADE50016329E /* ServiceTermsModalScreenViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556D22FDADE50016329E /* ServiceTermsModalScreenViewState.swift */; };
32DB557922FDADE50016329E /* ServiceTermsModalScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556E22FDADE50016329E /* ServiceTermsModalScreenViewModel.swift */; };
32DB557A22FDADE50016329E /* ServiceTermsModalScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556F22FDADE50016329E /* ServiceTermsModalScreenViewController.swift */; };
32DB557B22FDADE50016329E /* ServiceTermsModalScreenViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32DB557022FDADE50016329E /* ServiceTermsModalScreenViewController.storyboard */; };
32DB557C22FDADE50016329E /* ServiceTermsModalScreenViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557122FDADE50016329E /* ServiceTermsModalScreenViewModelType.swift */; };
32DB557D22FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557222FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift */; };
32DB557E22FDADE50016329E /* ServiceTermsModalScreenViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557322FDADE50016329E /* ServiceTermsModalScreenViewAction.swift */; };
32DB557F22FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB557422FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift */; };
32F6B9692270623100BBA352 /* DeviceVerificationDataLoadingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9632270623000BBA352 /* DeviceVerificationDataLoadingCoordinator.swift */; };
32F6B96A2270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9642270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift */; };
32F6B96B2270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9652270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift */; };
@@ -701,6 +712,17 @@
32D7159E2146CC6F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Vector.strings; sourceTree = "<group>"; };
32D7159F2146CC7F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
32D715A02146CC8800DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
32DB556922FDADE50016329E /* ServiceTermsModalCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalCoordinatorType.swift; sourceTree = "<group>"; };
32DB556A22FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
32DB556B22FDADE50016329E /* ServiceTermsModalCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalCoordinator.swift; sourceTree = "<group>"; };
32DB556D22FDADE50016329E /* ServiceTermsModalScreenViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenViewState.swift; sourceTree = "<group>"; };
32DB556E22FDADE50016329E /* ServiceTermsModalScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenViewModel.swift; sourceTree = "<group>"; };
32DB556F22FDADE50016329E /* ServiceTermsModalScreenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenViewController.swift; sourceTree = "<group>"; };
32DB557022FDADE50016329E /* ServiceTermsModalScreenViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ServiceTermsModalScreenViewController.storyboard; sourceTree = "<group>"; };
32DB557122FDADE50016329E /* ServiceTermsModalScreenViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenViewModelType.swift; sourceTree = "<group>"; };
32DB557222FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenCoordinatorType.swift; sourceTree = "<group>"; };
32DB557322FDADE50016329E /* ServiceTermsModalScreenViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenViewAction.swift; sourceTree = "<group>"; };
32DB557422FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTermsModalScreenCoordinator.swift; sourceTree = "<group>"; };
32F6B9632270623000BBA352 /* DeviceVerificationDataLoadingCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingCoordinator.swift; sourceTree = "<group>"; };
32F6B9642270623100BBA352 /* DeviceVerificationDataLoadingViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewState.swift; sourceTree = "<group>"; };
32F6B9652270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewAction.swift; sourceTree = "<group>"; };
@@ -1689,6 +1711,40 @@
path = KeyBackup;
sourceTree = "<group>";
};
32DB556722FDADE50016329E /* ServiceTerms */ = {
isa = PBXGroup;
children = (
32DB556822FDADE50016329E /* Modal */,
);
path = ServiceTerms;
sourceTree = "<group>";
};
32DB556822FDADE50016329E /* Modal */ = {
isa = PBXGroup;
children = (
32DB556922FDADE50016329E /* ServiceTermsModalCoordinatorType.swift */,
32DB556A22FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift */,
32DB556B22FDADE50016329E /* ServiceTermsModalCoordinator.swift */,
32DB556C22FDADE50016329E /* Modal */,
);
path = Modal;
sourceTree = "<group>";
};
32DB556C22FDADE50016329E /* Modal */ = {
isa = PBXGroup;
children = (
32DB556D22FDADE50016329E /* ServiceTermsModalScreenViewState.swift */,
32DB556E22FDADE50016329E /* ServiceTermsModalScreenViewModel.swift */,
32DB556F22FDADE50016329E /* ServiceTermsModalScreenViewController.swift */,
32DB557022FDADE50016329E /* ServiceTermsModalScreenViewController.storyboard */,
32DB557122FDADE50016329E /* ServiceTermsModalScreenViewModelType.swift */,
32DB557222FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift */,
32DB557322FDADE50016329E /* ServiceTermsModalScreenViewAction.swift */,
32DB557422FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift */,
);
path = Modal;
sourceTree = "<group>";
};
4220F60B660591FD80AF3428 /* Pods */ = {
isa = PBXGroup;
children = (
@@ -2178,6 +2234,7 @@
B1B5567620EE6C4C00210D55 /* Modules */ = {
isa = PBXGroup;
children = (
32DB556722FDADE50016329E /* ServiceTerms */,
3232AB94225730E100AD6A5C /* DeviceVerification */,
B1B556EA20EE6C4C00210D55 /* Main */,
B1B556CA20EE6C4C00210D55 /* TabBar */,
@@ -3833,6 +3890,7 @@
B1B5574D20EE6C4D00210D55 /* MediaPickerViewController.xib in Resources */,
B1B5575020EE6C4D00210D55 /* AuthenticationViewController.xib in Resources */,
B14F142E22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard in Resources */,
32DB557B22FDADE50016329E /* ServiceTermsModalScreenViewController.storyboard in Resources */,
B1B5574320EE6C4D00210D55 /* CallViewController.xib in Resources */,
F083BDEA1E7009ED00A9B29C /* ringback.mp3 in Resources */,
F083BDF21E7009ED00A9B29C /* GoogleService-Info.plist in Resources */,
@@ -4152,6 +4210,7 @@
B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */,
B1B9DEDE22E9D9890065E677 /* EmojiServiceType.swift in Sources */,
3232ABA9225730E100AD6A5C /* DeviceVerificationStartViewModel.swift in Sources */,
32DB557C22FDADE50016329E /* ServiceTermsModalScreenViewModelType.swift in Sources */,
B16932FA20F3C51A00746532 /* RecentCellData.m in Sources */,
B16932F220F3C49E00746532 /* GroupsDataSource.m in Sources */,
B1B5581C20EF625800210D55 /* RoomAvatarTitleView.m in Sources */,
@@ -4227,6 +4286,7 @@
B169331420F3CAFC00746532 /* PublicRoomTableViewCell.m in Sources */,
32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */,
B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */,
32DB557822FDADE50016329E /* ServiceTermsModalScreenViewState.swift in Sources */,
B1B558E120EF768F00210D55 /* RoomMembershipCollapsedBubbleCell.m in Sources */,
B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */,
B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */,
@@ -4247,6 +4307,7 @@
3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */,
B1B9DEE822EB34EF0065E677 /* ReactionHistoryCoordinatorType.swift in Sources */,
B14F143122144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewState.swift in Sources */,
32DB557F22FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift in Sources */,
B1098C1121ED07E4000DDA48 /* NavigationRouterType.swift in Sources */,
B1B5573D20EE6C4D00210D55 /* WebViewViewController.m in Sources */,
3209451221F1C1430088CAA2 /* BlackTheme.swift in Sources */,
@@ -4254,6 +4315,8 @@
3232ABBC2257BE6500AD6A5C /* DeviceVerificationVerifyViewAction.swift in Sources */,
F05927C91FDED836009F2A68 /* MXGroup+Riot.m in Sources */,
B1B5594520EF7BD000210D55 /* TableViewCellWithCollectionView.m in Sources */,
32DB557722FDADE50016329E /* ServiceTermsModalCoordinator.swift in Sources */,
32DB557922FDADE50016329E /* ServiceTermsModalScreenViewModel.swift in Sources */,
32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */,
32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */,
F083BDEF1E7009ED00A9B29C /* UINavigationController+Riot.m in Sources */,
@@ -4264,6 +4327,7 @@
B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */,
3232AB512256558300AD6A5C /* TemplateScreenViewAction.swift in Sources */,
3232AB4E2256558300AD6A5C /* TemplateScreenViewModelType.swift in Sources */,
32DB557D22FDADE50016329E /* ServiceTermsModalScreenCoordinatorType.swift in Sources */,
B1B5590520EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
3232ABA5225730E100AD6A5C /* DeviceVerificationStartViewModelType.swift in Sources */,
B1B558DD20EF768F00210D55 /* RoomIncomingEncryptedTextMsgBubbleCell.m in Sources */,
@@ -4274,6 +4338,7 @@
B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */,
B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */,
B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */,
32DB557A22FDADE50016329E /* ServiceTermsModalScreenViewController.swift in Sources */,
B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */,
B1B5573C20EE6C4D00210D55 /* MasterTabBarController.m in Sources */,
B1DCC61B22E5E17100625807 /* EmojiPickerCoordinator.swift in Sources */,
@@ -4447,6 +4512,7 @@
B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */,
B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */,
B1B5575C20EE6C4D00210D55 /* DirectoryViewController.m in Sources */,
32DB557622FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift in Sources */,
B1B558BD20EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */,
B1B5577020EE702800210D55 /* WidgetPickerViewController.m in Sources */,
B1B558D320EF768F00210D55 /* RoomOutgoingEncryptedTextMsgBubbleCell.m in Sources */,
@@ -4462,6 +4528,7 @@
32B94DF9228EC26400716A26 /* ReactionsMenuViewAction.swift in Sources */,
B1B5599420EFC5E400210D55 /* DecryptionFailureTracker.m in Sources */,
F083BDF01E7009ED00A9B29C /* UIViewController+RiotSearch.m in Sources */,
32DB557522FDADE50016329E /* ServiceTermsModalCoordinatorType.swift in Sources */,
F083BDF91E7009ED00A9B29C /* RoomEmailInvitation.m in Sources */,
B1D211E422C18E3800D939BD /* ReactionsMenuViewModelType.swift in Sources */,
324A2055225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift in Sources */,
@@ -4470,6 +4537,7 @@
3232ABB52257BE6400AD6A5C /* DeviceVerificationVerifyCoordinatorType.swift in Sources */,
32BF994F21FA29A400698084 /* SettingsKeyBackupViewModel.swift in Sources */,
B190F55D22CE5A9700AEB493 /* EditHistorySection.swift in Sources */,
32DB557E22FDADE50016329E /* ServiceTermsModalScreenViewAction.swift in Sources */,
32A6002022C66FCF0042C1D9 /* EditHistoryMessage.swift in Sources */,
B1B5574920EE6C4D00210D55 /* RiotSplitViewController.m in Sources */,
B1B5574E20EE6C4D00210D55 /* DirectoryServerPickerViewController.m in Sources */,
+7
View File
@@ -691,6 +691,13 @@
"gdpr_consent_not_given_alert_message" = "To continue using the %@ homeserver you must review and agree to the terms and conditions.";
"gdpr_consent_not_given_alert_review_now_action" = "Review now";
// Service terms
"service_terms_modal_title" = "Terms Of Service";
"service_terms_modal_message" = "To continue you need to accept the Terms of this service.";
"service_terms_modal_accept_button" = "Accept";
"service_terms_modal_description_for_identity_server" = "Be discoverable by others";
"service_terms_modal_description_for_integration_manager" = "Use Bots, bridges, widgets and sticker packs";
// Deactivate account
"deactivate_account_title" = "Deactivate Account";
+5
View File
@@ -92,6 +92,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.RoomContextualMenuViewController>(storyboard: RoomContextualMenuViewController.self)
}
internal enum ServiceTermsModalScreenViewController: StoryboardType {
internal static let storyboardName = "ServiceTermsModalScreenViewController"
internal static let initialScene = InitialSceneType<Riot.ServiceTermsModalScreenViewController>(storyboard: ServiceTermsModalScreenViewController.self)
}
internal enum SimpleScreenTemplateViewController: StoryboardType {
internal static let storyboardName = "SimpleScreenTemplateViewController"
+20
View File
@@ -2342,6 +2342,26 @@ internal enum VectorL10n {
internal static var sending: String {
return VectorL10n.tr("Vector", "sending")
}
/// Accept
internal static var serviceTermsModalAcceptButton: String {
return VectorL10n.tr("Vector", "service_terms_modal_accept_button")
}
/// Be discoverable by others
internal static var serviceTermsModalDescriptionForIdentityServer: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_for_identity_server")
}
/// Use Bots, bridges, widgets and sticker packs
internal static var serviceTermsModalDescriptionForIntegrationManager: String {
return VectorL10n.tr("Vector", "service_terms_modal_description_for_integration_manager")
}
/// To continue you need to accept the Terms of this service.
internal static var serviceTermsModalMessage: String {
return VectorL10n.tr("Vector", "service_terms_modal_message")
}
/// Terms Of Service
internal static var serviceTermsModalTitle: String {
return VectorL10n.tr("Vector", "service_terms_modal_title")
}
/// Add email address
internal static var settingsAddEmailAddress: String {
return VectorL10n.tr("Vector", "settings_add_email_address")
+3 -1
View File
@@ -56,10 +56,12 @@ typedef enum : NSUInteger
WidgetManagerErrorCodeNotEnoughPower,
WidgetManagerErrorCodeCreationFailed,
WidgetManagerErrorCodeNoIntegrationsServerConfigured,
WidgetManagerErrorCodeFailedToConnectToIntegrationsServer
WidgetManagerErrorCodeFailedToConnectToIntegrationsServer,
WidgetManagerErrorCodeTermsNotSigned
}
WidgetManagerErrorCode;
FOUNDATION_EXPORT NSString *const WidgetManagerErrorOpenIdTokenKey;
/**
The `WidgetManager` helps to handle modular widgets.
+30 -6
View File
@@ -513,6 +513,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
MXHTTPOperation *operation;
NSString *userId = mxSession.myUser.userId;
NSLog(@"[WidgetManager] registerForScalarToken");
WidgetManagerConfig *config = [self configForUser:userId];
if (!config.hasUrls)
{
@@ -537,15 +539,22 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
NSString *scalarToken;
MXJSONModelSetString(scalarToken, JSONResponse[@"scalar_token"])
config.scalarToken = scalarToken;
config.scalarToken = scalarToken;
self->configs[userId] = config;
[self saveConfigs];
// Validate it (this mostly checks to see if the IM needs us to agree to some terms)
MXHTTPOperation *operation3 = [self validateScalarToken:scalarToken forMXSession:mxSession complete:^(BOOL valid) {
if (success)
{
success(scalarToken);
}
if (success)
{
success(scalarToken);
}
} failure:failure];
[operation mutateTo:operation3];
} failure:^(NSError *error) {
NSLog(@"[WidgetManager] registerForScalarToken: Failed to register. Error: %@", error);
@@ -622,7 +631,22 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
}
else if (failure)
{
failure(error);
MXError *mxError = [[MXError alloc] initWithNSError:error];
if ([mxError.errcode isEqualToString:kMXErrCodeStringTermsNotSigned])
{
NSLog(@"[WidgetManager] validateScalarToke. Error: Need to accept terms");
NSError *termsNotSignedError = [NSError errorWithDomain:WidgetManagerErrorDomain
code:WidgetManagerErrorCodeTermsNotSigned
userInfo:@{
NSLocalizedDescriptionKey:error.userInfo[NSLocalizedDescriptionKey]
}];
failure(termsNotSignedError);
}
else
{
failure(error);
}
}
}];
}
@@ -36,6 +36,38 @@ class WidgetManagerConfig: NSObject, NSCoding {
}
}
var baseUrl: NSString? {
// Same comment as https://github.com/matrix-org/matrix-react-sdk/blob/1b0d8510a2ee93beddcd34c2d5770aa9fc76b1d9/src/ScalarAuthClient.js#L108
// The terms endpoints are new and so live on standard _matrix prefixes,
// but IM rest urls are currently configured with paths, so remove the
// path from the base URL before passing it to the js-sdk
// We continue to use the full URL for the calls done by
// Riot-iOS, but the standard terms API called
// by the matrix-ios-sdk lives on the standard _matrix path. This means we
// don't support running IMs on a non-root path, but it's the only
// realistic way of transitioning to _matrix paths since configs in
// the wild contain bits of the API path.
// Once we've fully transitioned to _matrix URLs, we can give people
// a grace period to update their configs, then use the rest url as
// a regular base url.
guard let apiUrl = self.apiUrl as String?, let imApiUrl = URL(string: apiUrl) else {
return nil
}
guard var baseUrl = URL(string: "/", relativeTo: imApiUrl)?.absoluteString else {
return nil
}
if baseUrl.hasSuffix("/") {
// SDK doest not like trailing /
baseUrl = String(baseUrl.dropLast())
}
return baseUrl as NSString
}
init(apiUrl: NSString?, uiUrl: NSString?) {
self.apiUrl = apiUrl
self.uiUrl = uiUrl
@@ -26,7 +26,7 @@ NSString *const kIntegrationManagerMainScreen = nil;
NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
@interface IntegrationManagerViewController ()
@interface IntegrationManagerViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
{
MXSession *mxSession;
NSString *roomId;
@@ -37,6 +37,8 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
MXHTTPOperation *operation;
}
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@end
@implementation IntegrationManagerViewController
@@ -67,10 +69,15 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
operation = nil;
}
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidLoad
{
[super viewWillAppear:animated];
[super viewDidLoad];
[self loadData];
}
- (void)loadData
{
if (!self.URL && !operation)
{
[self startActivityIndicator];
@@ -94,9 +101,17 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
self->operation = nil;
[self stopActivityIndicator];
[self withdrawViewControllerAnimated:YES completion:^{
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
if ([error.domain isEqualToString:WidgetManagerErrorDomain]
&& error.code == WidgetManagerErrorCodeTermsNotSigned)
{
[self presentTerms];
}
else
{
[self withdrawViewControllerAnimated:YES completion:^{
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}
}];
}
}
@@ -681,4 +696,39 @@ NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";
}];
}
#pragma mark - Service terms
- (void)presentTerms
{
WidgetManagerConfig *config = [[WidgetManager sharedManager] configForUser:mxSession.myUser.userId];
NSLog(@"[IntegrationManagerVC] presentTerms for %@", config.baseUrl);
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession baseUrl:config.baseUrl
serviceType:MXServiceTypeIntegrationManager
accessToken:config.scalarToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self loadData];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self withdrawViewControllerAnimated:YES completion:nil];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end
@@ -19,10 +19,13 @@
#import "AppDelegate.h"
#import "IntegrationManagerViewController.h"
#import "Riot-Swift.h"
NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);";
@interface WidgetViewController ()
@interface WidgetViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@end
@@ -363,15 +366,71 @@ NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse
MXStrongifyAndReturnIfNil(self);
NSLog(@"[WidgetVC] fixScalarToken: DONE");
self.URL = [self stringByReplacingScalarTokenInString:self.URL byScalarToken:scalarToken];
self->webView.hidden = NO;
[self loadDataWithScalarToken:scalarToken];
} failure:^(NSError *error) {
NSLog(@"[WidgetVC] fixScalarToken: Error: %@", error);
[self showErrorAsAlert:error];
if ([error.domain isEqualToString:WidgetManagerErrorDomain]
&& error.code == WidgetManagerErrorCodeTermsNotSigned)
{
[self presentTerms];
}
else
{
[self showErrorAsAlert:error];
}
}];
}
- (void)loadDataWithScalarToken:(NSString*)scalarToken
{
self.URL = [self stringByReplacingScalarTokenInString:self.URL byScalarToken:scalarToken];
self->webView.hidden = NO;
}
#pragma mark - Service terms
- (void)presentTerms
{
if (self.serviceTermsModalCoordinatorBridgePresenter)
{
return;
}
WidgetManagerConfig *config = [[WidgetManager sharedManager] configForUser:widget.mxSession.myUser.userId];
NSLog(@"[WidgetVC] presentTerms for %@", config.baseUrl);
ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:widget.mxSession baseUrl:config.baseUrl
serviceType:MXServiceTypeIntegrationManager accessToken:config.scalarToken];
serviceTermsModalCoordinatorBridgePresenter.delegate = self;
[serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
MXWeakify(self);
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
MXStrongifyAndReturnIfNil(self);
WidgetManagerConfig *config = [[WidgetManager sharedManager] configForUser:self->widget.mxSession.myUser.userId];
[self loadDataWithScalarToken:config.scalarToken];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
[self withdrawViewControllerAnimated:YES completion:nil];
}];
self.serviceTermsModalCoordinatorBridgePresenter = nil;
}
@end
@@ -0,0 +1,73 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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 ServiceTermsModalScreenCoordinator: ServiceTermsModalScreenCoordinatorType {
// MARK: - Properties
// MARK: Private
private var serviceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelType
private let serviceTermsModalScreenViewController: ServiceTermsModalScreenViewController
// Must be used only internally
var childCoordinators: [Coordinator] = []
// MARK: Public
weak var delegate: ServiceTermsModalScreenCoordinatorDelegate?
// MARK: - Setup
init(serviceTerms: MXServiceTerms) {
let serviceTermsModalScreenViewModel = ServiceTermsModalScreenViewModel(serviceTerms: serviceTerms)
let serviceTermsModalScreenViewController = ServiceTermsModalScreenViewController.instantiate(with: serviceTermsModalScreenViewModel)
self.serviceTermsModalScreenViewModel = serviceTermsModalScreenViewModel
self.serviceTermsModalScreenViewController = serviceTermsModalScreenViewController
}
// MARK: - Public methods
func start() {
self.serviceTermsModalScreenViewModel.coordinatorDelegate = self
}
func toPresentable() -> UIViewController {
return self.serviceTermsModalScreenViewController
}
}
// MARK: - ServiceTermsModalScreenViewModelCoordinatorDelegate
extension ServiceTermsModalScreenCoordinator: ServiceTermsModalScreenViewModelCoordinatorDelegate {
func serviceTermsModalScreenViewModelDidAccept(_ viewModel: ServiceTermsModalScreenViewModelType) {
self.delegate?.serviceTermsModalScreenCoordinatorDidAccept(self)
}
func serviceTermsModalScreenViewModel(_ coordinator: ServiceTermsModalScreenViewModelType, displayPolicy policy: MXLoginPolicyData) {
self.delegate?.serviceTermsModalScreenCoordinator(self, displayPolicy: policy)
}
func serviceTermsModalScreenViewModelDidCancel(_ viewModel: ServiceTermsModalScreenViewModelType) {
self.delegate?.serviceTermsModalScreenCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,30 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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 ServiceTermsModalScreenCoordinatorDelegate: class {
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType)
func serviceTermsModalScreenCoordinator(_ coordinator: ServiceTermsModalScreenCoordinatorType, displayPolicy policy: MXLoginPolicyData)
func serviceTermsModalScreenCoordinatorDidCancel(_ coordinator: ServiceTermsModalScreenCoordinatorType)
}
/// `ServiceTermsModalScreenCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol ServiceTermsModalScreenCoordinatorType: Coordinator, Presentable {
var delegate: ServiceTermsModalScreenCoordinatorDelegate? { get }
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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
/// ServiceTermsModalScreenViewController view actions exposed to view model
enum ServiceTermsModalScreenViewAction {
case load
case display(MXLoginPolicyData)
case accept
case cancel
}
@@ -0,0 +1,111 @@
<?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>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Service Terms Modal Screen View Controller-->
<scene sceneID="mt5-wz-YKA">
<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"/>
<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"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7g-um-WO4">
<rect key="frame" x="0.0" y="0.0" width="414" height="852"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="voD-3Q-ryt">
<rect key="frame" x="0.0" y="0.0" width="414" height="852"/>
<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="652"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DOt-5E-FjF">
<rect key="frame" x="20" y="788" 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>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="bxI-mu-qng" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="1K2-u8-QsL"/>
<constraint firstAttribute="bottom" secondItem="DOt-5E-FjF" secondAttribute="bottom" constant="20" symbolic="YES" id="2Vt-c1-fPz"/>
<constraint firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" constant="20" symbolic="YES" id="41D-TP-69f"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="43V-Ya-vks"/>
<constraint firstItem="swq-xw-ItG" firstAttribute="top" secondItem="bxI-mu-qng" secondAttribute="bottom" constant="8" symbolic="YES" id="9Gg-Xb-o2W"/>
<constraint firstItem="DOt-5E-FjF" firstAttribute="top" secondItem="swq-xw-ItG" secondAttribute="bottom" constant="8" symbolic="YES" id="K8z-wm-8yT"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="top" secondItem="voD-3Q-ryt" secondAttribute="top" constant="20" symbolic="YES" id="W1m-x0-TyS"/>
<constraint firstAttribute="trailing" secondItem="swq-xw-ItG" secondAttribute="trailing" constant="20" symbolic="YES" id="Y5v-Gg-xkM"/>
<constraint firstAttribute="width" priority="750" constant="500" id="glD-Sz-73O"/>
<constraint firstAttribute="trailing" secondItem="DOt-5E-FjF" secondAttribute="trailing" constant="20" symbolic="YES" id="lpw-9u-VDG"/>
<constraint firstItem="swq-xw-ItG" firstAttribute="leading" secondItem="voD-3Q-ryt" secondAttribute="leading" constant="20" symbolic="YES" id="ze1-Iw-v9U"/>
</constraints>
</view>
</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"/>
</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"/>
</constraints>
</scrollView>
</subviews>
<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"/>
<constraint firstItem="9U2-KL-ZVA" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" id="GdQ-hK-muG"/>
<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="messageLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="scrollView" destination="9U2-KL-ZVA" id="ojG-2y-X7b"/>
<outlet property="tableView" destination="swq-xw-ItG" id="Fwb-Sc-bec"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198" y="-647"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,293 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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
final class ServiceTermsModalScreenViewController: UIViewController {
// MARK: - Constants
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var scrollView: UIScrollView!
@IBOutlet private weak var messageLabel: UILabel!
@IBOutlet private weak var tableView: UITableView!
@IBOutlet private weak var acceptButton: UIButton!
// MARK: Private
private var viewModel: ServiceTermsModalScreenViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var policies: [MXLoginPolicyData] = []
/// Policies checked by the end user
private var checkedPolicies: Set<Int> = []
// MARK: - Setup
class func instantiate(with viewModel: ServiceTermsModalScreenViewModelType) -> ServiceTermsModalScreenViewController {
let viewController = StoryboardScene.ServiceTermsModalScreenViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = VectorL10n.serviceTermsModalTitle
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.viewDelegate = self
self.viewModel.process(viewAction: .load)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.theme.statusBarStyle
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.messageLabel.textColor = theme.textPrimaryColor
self.acceptButton.backgroundColor = theme.backgroundColor
theme.applyStyle(onButton: self.acceptButton)
self.refreshViews()
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
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.acceptButton.setTitle(VectorL10n.serviceTermsModalAcceptButton, for: .normal)
self.acceptButton.setTitle(VectorL10n.serviceTermsModalAcceptButton, for: .highlighted)
self.refreshAcceptButton()
}
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())
}
private func render(viewState: ServiceTermsModalScreenViewState) {
switch viewState {
case .loading:
self.renderLoading()
case .loaded(let policies, let alreadyAcceptedPoliciesUrls):
self.renderLoaded(policies: policies, alreadyAcceptedPoliciesUrls: alreadyAcceptedPoliciesUrls)
case .accepted:
self.renderAccepted()
case .error(let error):
self.render(error: error)
}
}
private func renderLoading() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderLoaded(policies: [MXLoginPolicyData], alreadyAcceptedPoliciesUrls: [String]) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.policies = policies
self.updateCheckedPolicies(with: alreadyAcceptedPoliciesUrls)
self.refreshViews()
}
private func renderAccepting() {
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
}
private func renderAccepted() {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
}
private func render(error: Error) {
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
}
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)
}
}
}
// MARK: - Actions
@IBAction private func acceptButtonAction(_ sender: Any) {
self.viewModel.process(viewAction: .accept)
}
private func cancelButtonAction() {
self.viewModel.process(viewAction: .cancel)
}
@objc private func didTapCheckbox(sender: UITapGestureRecognizer) {
guard let policyIndex = sender.view?.tag else {
return
}
if self.checkedPolicies.contains(policyIndex) {
self.checkedPolicies.remove(policyIndex)
} else {
checkedPolicies.insert(policyIndex)
}
self.refreshViews()
}
}
// MARK: - ServiceTermsModalScreenViewModelViewDelegate
extension ServiceTermsModalScreenViewController: ServiceTermsModalScreenViewModelViewDelegate {
func serviceTermsModalScreenViewModel(_ viewModel: ServiceTermsModalScreenViewModelType, didUpdateViewState viewSate: ServiceTermsModalScreenViewState) {
self.render(viewState: viewSate)
}
}
// MARK: - UITableViewDataSource
extension ServiceTermsModalScreenViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.policies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
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.row]
let checked = checkedPolicies.contains(indexPath.row)
cell.label.attributedText = self.cellLabel(for: policy)
cell.label.font = .systemFont(ofSize: 15)
cell.isEnabled = checked
cell.accessoryType = .disclosureIndicator
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)
}
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.serviceTermsModalDescriptionForIdentityServer
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
}
}
extension ServiceTermsModalScreenViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let policy = policies[indexPath.row]
self.viewModel.process(viewAction: .display(policy))
}
}
@@ -0,0 +1,128 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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
final class ServiceTermsModalScreenViewModel: ServiceTermsModalScreenViewModelType {
// MARK: - Properties
// MARK: Private
private let serviceTerms: MXServiceTerms
// MARK: Public
var serviceType: MXServiceType {
return serviceTerms.serviceType
}
var policies: [MXLoginPolicyData]?
var alreadyAcceptedPoliciesUrls: [String] = []
weak var viewDelegate: ServiceTermsModalScreenViewModelViewDelegate?
weak var coordinatorDelegate: ServiceTermsModalScreenViewModelCoordinatorDelegate?
// MARK: - Setup
init(serviceTerms: MXServiceTerms) {
self.serviceTerms = serviceTerms
}
deinit {
}
// MARK: - Public
func process(viewAction: ServiceTermsModalScreenViewAction) {
switch viewAction {
case .load:
self.loadTerms()
case .display(let policy):
self.coordinatorDelegate?.serviceTermsModalScreenViewModel(self, displayPolicy: policy)
case .accept:
self.acceptTerms()
case .cancel:
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidCancel(self)
}
}
// MARK: - Private
private func loadTerms() {
self.update(viewState: .loading)
self.serviceTerms.terms({ [weak self] (terms, alreadyAcceptedTermsUrls) in
guard let self = self else {
return
}
let policies = self.processTerms(terms: terms)
self.policies = policies
self.alreadyAcceptedPoliciesUrls = alreadyAcceptedTermsUrls ?? []
self.update(viewState: .loaded(policies: policies, alreadyAcceptedPoliciesUrls: self.alreadyAcceptedPoliciesUrls))
}, failure: { [weak self] error in
guard let self = self else {
return
}
self.update(viewState: .error(error))
})
}
private func acceptTerms() {
self.update(viewState: .loading)
self.serviceTerms.agree(toTerms: self.termsUrls, success: { [weak self] in
guard let self = self else {
return
}
self.update(viewState: .accepted)
self.coordinatorDelegate?.serviceTermsModalScreenViewModelDidAccept(self)
}, failure: { [weak self] (error) in
guard let self = self else {
return
}
self.update(viewState: .error(error))
})
}
private func processTerms(terms: MXLoginTerms?) -> [MXLoginPolicyData] {
if let policies = terms?.policiesData(forLanguage: Bundle.mxk_language(), defaultLanguage: Bundle.mxk_fallbackLanguage()) {
return policies
} else {
print("[ServiceTermsModalScreenViewModel] processTerms: Error: No terms for \(String(describing: terms))")
return []
}
}
private var termsUrls: [String] {
guard let policies = self.policies else {
return []
}
return policies.map({ return $0.url })
}
private func update(viewState: ServiceTermsModalScreenViewState) {
self.viewDelegate?.serviceTermsModalScreenViewModel(self, didUpdateViewState: viewState)
}
}
@@ -0,0 +1,42 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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 ServiceTermsModalScreenViewModelViewDelegate: class {
func serviceTermsModalScreenViewModel(_ viewModel: ServiceTermsModalScreenViewModelType, didUpdateViewState viewSate: ServiceTermsModalScreenViewState)
}
protocol ServiceTermsModalScreenViewModelCoordinatorDelegate: class {
func serviceTermsModalScreenViewModel(_ coordinator: ServiceTermsModalScreenViewModelType, displayPolicy policy: MXLoginPolicyData)
func serviceTermsModalScreenViewModelDidAccept(_ viewModel: ServiceTermsModalScreenViewModelType)
func serviceTermsModalScreenViewModelDidCancel(_ viewModel: ServiceTermsModalScreenViewModelType)
}
/// Protocol describing the view model used by `ServiceTermsModalScreenViewController`
protocol ServiceTermsModalScreenViewModelType {
var serviceType: MXServiceType { get }
var policies: [MXLoginPolicyData]? { get set }
var alreadyAcceptedPoliciesUrls: [String] { get set }
var viewDelegate: ServiceTermsModalScreenViewModelViewDelegate? { get set }
var coordinatorDelegate: ServiceTermsModalScreenViewModelCoordinatorDelegate? { get set }
func process(viewAction: ServiceTermsModalScreenViewAction)
}
@@ -0,0 +1,27 @@
// File created from ScreenTemplate
// $ createScreen.sh Modal/Show ServiceTermsModalScreen
/*
Copyright 2019 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
/// ServiceTermsModalScreenViewController view state
enum ServiceTermsModalScreenViewState {
case loading
case loaded(policies: [MXLoginPolicyData], alreadyAcceptedPoliciesUrls: [String])
case accepted
case error(Error)
}
@@ -0,0 +1,104 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh Modal ServiceTermsModal ServiceTermsModalScreen
/*
Copyright 2019 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
@objcMembers
final class ServiceTermsModalCoordinator: ServiceTermsModalCoordinatorType {
// MARK: - Properties
// MARK: Private
private let navigationRouter: NavigationRouterType
private let session: MXSession
private let serviceTerms: MXServiceTerms
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: ServiceTermsModalCoordinatorDelegate?
// MARK: - Setup
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)
}
// MARK: - Public methods
func start() {
let rootCoordinator = self.createServiceTermsModalLoadTermsScreenCoordinator()
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
self.navigationRouter.setRootModule(rootCoordinator)
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
}
// MARK: - Private methods
private func createServiceTermsModalLoadTermsScreenCoordinator() -> ServiceTermsModalScreenCoordinator {
let coordinator = ServiceTermsModalScreenCoordinator(serviceTerms: self.serviceTerms)
coordinator.delegate = self
return coordinator
}
private func showPolicy(policy: MXLoginPolicyData) {
// Display the policy webpage into our webview
let webViewViewController: WebViewViewController = WebViewViewController(url: policy.url)
webViewViewController.title = policy.name
let leftBarButtonItem: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "back_icon"), style: .plain, target: self, action: #selector(didTapCancelOnPolicyScreen))
webViewViewController.navigationItem.leftBarButtonItem = leftBarButtonItem
self.navigationRouter.push(webViewViewController, animated: true, popCompletion: nil)
}
private func removePolicyScreen() {
self.navigationRouter.popModule(animated: true)
}
@objc private func didTapCancelOnPolicyScreen() {
self.removePolicyScreen()
}
}
// MARK: - ServiceTermsModalLoadTermsScreenCoordinatorDelegate
extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelegate {
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorDidAccept(self)
}
func serviceTermsModalScreenCoordinator(_ coordinator: ServiceTermsModalScreenCoordinatorType, displayPolicy policy: MXLoginPolicyData) {
self.showPolicy(policy: policy)
}
func serviceTermsModalScreenCoordinatorDidCancel(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorDidCancel(self)
}
}
@@ -0,0 +1,95 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh Modal ServiceTermsModal ServiceTermsModalLoadTermsScreen
/*
Copyright 2019 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 ServiceTermsModalCoordinatorBridgePresenterDelegate {
func serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
func serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: ServiceTermsModalCoordinatorBridgePresenter)
}
/// ServiceTermsModalCoordinatorBridgePresenter enables to start ServiceTermsModalCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
@objcMembers
final class ServiceTermsModalCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let session: MXSession
private let baseUrl: String
private let serviceType: MXServiceType
private let accessToken: String
private var coordinator: ServiceTermsModalCoordinator?
// MARK: Public
weak var delegate: ServiceTermsModalCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(session: MXSession, baseUrl: String, serviceType: MXServiceType, accessToken: String) {
self.session = session
self.baseUrl = baseUrl
self.serviceType = serviceType
self.accessToken = accessToken
super.init()
}
// MARK: - Public
// NOTE: Default value feature is not compatible with Objective-C.
// func present(from viewController: UIViewController, animated: Bool) {
// self.present(from: viewController, animated: animated)
// }
func present(from viewController: UIViewController, animated: Bool) {
let serviceTermsModalCoordinator = ServiceTermsModalCoordinator(session: self.session, baseUrl: self.baseUrl, serviceType: self.serviceType, accessToken: accessToken)
serviceTermsModalCoordinator.delegate = self
viewController.present(serviceTermsModalCoordinator.toPresentable(), animated: animated, completion: nil)
serviceTermsModalCoordinator.start()
self.coordinator = serviceTermsModalCoordinator
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
if let completion = completion {
completion()
}
}
}
}
// MARK: - ServiceTermsModalCoordinatorDelegate
extension ServiceTermsModalCoordinatorBridgePresenter: ServiceTermsModalCoordinatorDelegate {
func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept(self)
}
func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType) {
self.delegate?.serviceTermsModalCoordinatorBridgePresenterDelegateDidCancel(self)
}
}
@@ -0,0 +1,29 @@
// File created from FlowTemplate
// $ createRootCoordinator.sh Modal ServiceTermsModal ServiceTermsModalLoadTermsScreen
/*
Copyright 2019 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 ServiceTermsModalCoordinatorDelegate: class {
func serviceTermsModalCoordinatorDidAccept(_ coordinator: ServiceTermsModalCoordinatorType)
func serviceTermsModalCoordinatorDidCancel(_ coordinator: ServiceTermsModalCoordinatorType)
}
/// `ServiceTermsModalCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow.
protocol ServiceTermsModalCoordinatorType: Coordinator, Presentable {
var delegate: ServiceTermsModalCoordinatorDelegate? { get }
}