diff --git a/CHANGES.rst b/CHANGES.rst index ee58e9c72..0c3c05396 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Improvements: * Verification requests: Hide incoming request modal when it is no more pending (#3033). * Self-verification: Do not display incoming self verification requests at the top of the Complete Security screen. * Verification: Do not talk about QR code if only emoji is possible (#3035). + * Registration: Prefill email field when opened with universal link (PR #3173). Bug fix: * AuthenticationViewController: Remove fallback to matrix.org when authentication failed (PR #3165). diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 1c730e812..b48a570ad 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -127,6 +127,7 @@ 6E6F1CB1244E00FD0068B78B /* LocalContactsSectionHeaderContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E6F1CAF244E00FD0068B78B /* LocalContactsSectionHeaderContainerView.m */; }; 6E6F1CB324506EC50068B78B /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6F1CB224506EC50068B78B /* UITableViewCell.swift */; }; 6E6F1CB524506FA40068B78B /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6F1CB424506FA40068B78B /* UITableView.swift */; }; + 6E75C3E32458797D00AF497D /* UniversalLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E75C3E22458797D00AF497D /* UniversalLink.m */; }; 6E7A9E9C243E10E700FD039E /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7A9E9B243E10E700FD039E /* UIDevice.swift */; }; 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */; }; 92726A471F58737A004AD26F /* IntentHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 92726A461F58737A004AD26F /* IntentHandler.m */; }; @@ -913,6 +914,8 @@ 6E6F1CB0244E00FD0068B78B /* LocalContactsSectionHeaderContainerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalContactsSectionHeaderContainerView.h; sourceTree = ""; }; 6E6F1CB224506EC50068B78B /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; 6E6F1CB424506FA40068B78B /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; + 6E75C3E12458797D00AF497D /* UniversalLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UniversalLink.h; sourceTree = ""; }; + 6E75C3E22458797D00AF497D /* UniversalLink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UniversalLink.m; sourceTree = ""; }; 6E7A9E9B243E10E700FD039E /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; 926FA53D1F4C132000F826C2 /* MXSession+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXSession+Riot.h"; sourceTree = ""; }; 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXSession+Riot.m"; sourceTree = ""; }; @@ -4339,6 +4342,8 @@ F083BC151E7009EC00A9B29C /* Tools.m */, B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */, B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */, + 6E75C3E12458797D00AF497D /* UniversalLink.h */, + 6E75C3E22458797D00AF497D /* UniversalLink.m */, ); path = Utils; sourceTree = ""; @@ -5127,6 +5132,7 @@ 3232ABC2225B996200AD6A5C /* Themable.swift in Sources */, 32A6001B22C661100042C1D9 /* EditHistoryViewAction.swift in Sources */, 3232ABA7225730E100AD6A5C /* DeviceVerificationStartCoordinator.swift in Sources */, + 6E75C3E32458797D00AF497D /* UniversalLink.m in Sources */, B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */, B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */, B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */, diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index b63da118b..be2ec88ac 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -26,6 +26,7 @@ #import "Analytics.h" #import "ThemeService.h" +#import "UniversalLink.h" #pragma mark - Notifications /** @@ -42,6 +43,11 @@ extern NSString *const AppDelegateDidValidateEmailNotification; extern NSString *const AppDelegateDidValidateEmailNotificationSIDKey; extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; +/** + Posted when the property 'lastHandledUniversalLink' has changed. Notification object and userInfo will be nil. + */ +extern NSString *const AppDelegateUniversalLinkDidChangeNotification; + @interface AppDelegate : UIResponder { BOOL isPushRegistered; @@ -87,6 +93,11 @@ extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; // Current selected room id. nil if no room is presently visible. @property (strong, nonatomic) NSString *visibleRoomId; +/** + Last handled universal link (url will be formatted for several hash keys). + */ +@property (nonatomic, readonly) UniversalLink *lastHandledUniversalLink; + // New message sound id. @property (nonatomic, readonly) SystemSoundID messageSound; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 0d49dff39..31b4d3fb9 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -88,6 +88,8 @@ NSString *const AppDelegateDidValidateEmailNotification = @"AppDelegateDidValida NSString *const AppDelegateDidValidateEmailNotificationSIDKey = @"AppDelegateDidValidateEmailNotificationSIDKey"; NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDelegateDidValidateEmailNotificationClientSecretKey"; +NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUniversalLinkDidChangeNotification"; + @interface AppDelegate () { /** @@ -2038,6 +2040,19 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // iOS Patch: fix vector.im urls before using it webURL = [Tools fixURLWithSeveralHashKeys:webURL]; + // Extract required parameters from the link + NSArray *pathParams; + NSMutableDictionary *queryParams; + [self parseUniversalLinkFragment:webURL.absoluteString outPathParams:&pathParams outQueryParams:&queryParams]; + + UniversalLink *newLink = [[UniversalLink alloc] initWithUrl:webURL pathParams:pathParams queryParams:queryParams]; + if (![_lastHandledUniversalLink isEqual:newLink]) + { + _lastHandledUniversalLink = [[UniversalLink alloc] initWithUrl:webURL pathParams:pathParams queryParams:queryParams]; + // notify this change + [[NSNotificationCenter defaultCenter] postNotificationName:AppDelegateUniversalLinkDidChangeNotification object:nil]; + } + if ([webURL.path hasPrefix:@"/config"]) { return [self handleServerProvionningLink:webURL]; @@ -2098,12 +2113,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe } MXIdentityService *identityService = [[MXIdentityService alloc] initWithIdentityServer:identityServer accessToken:nil andHomeserverRestClient:homeserverRestClient]; - - // Extract required parameters from the link - NSArray *pathParams; - NSMutableDictionary *queryParams; - [self parseUniversalLinkFragment:webURL.absoluteString outPathParams:&pathParams outQueryParams:&queryParams]; - + NSString *clientSecret = queryParams[@"client_secret"]; NSString *sid = queryParams[@"sid"]; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 34cb5e90b..3a0525de7 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1038,7 +1038,7 @@ "key_verification_other_session_title" = "Verify session"; "key_verification_new_session_title" = "Verify your new session"; "key_verification_this_session_title" = "Verify this session"; -"key_verification_user_title" = "verify them"; +"key_verification_user_title" = "Verify them"; "device_verification_security_advice_emoji" = "Compare the unique emoji, ensuring they appear in the same order."; "device_verification_security_advice_number" = "Compare the numbers, ensuring they appear in the same order."; "device_verification_cancelled" = "The other party cancelled the verification."; @@ -1093,14 +1093,16 @@ // Device "device_verification_verified_title" = "Verified!"; -"device_verification_verified_description_1" = "You've successfully verified this session."; -"device_verification_verified_description_2" = "Secure messages with this user are end-to-end encrypted and not able to be read by third parties."; "device_verification_verified_got_it_button" = "Got it"; +"key_verification_verified_new_session_title" = "New session verified!"; + +"key_verification_verified_other_session_information" = "You can now read secure messages on your other session, and other users will know they can trust it."; +"key_verification_verified_new_session_information" = "You can now read secure messages on your new device, and other users will know they can trust it."; +"key_verification_verified_this_session_information" = "You can now read secure messages on this device, and other users will know they can trust it."; // User -"key_verification_verified_user_description_1" = "You’ve successfully verified this user."; -"key_verification_verified_user_description_2" = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties."; +"key_verification_verified_user_information" = "Messages with this user are end-to-end encrypted and can't be read by third parties."; // MARK: Emoji "device_verification_emoji_dog" = "Dog"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 304635fcb..d92214af9 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -878,14 +878,6 @@ internal enum VectorL10n { internal static var deviceVerificationStartWaitPartner: String { return VectorL10n.tr("Vector", "device_verification_start_wait_partner") } - /// You've successfully verified this session. - internal static var deviceVerificationVerifiedDescription1: String { - return VectorL10n.tr("Vector", "device_verification_verified_description_1") - } - /// Secure messages with this user are end-to-end encrypted and not able to be read by third parties. - internal static var deviceVerificationVerifiedDescription2: String { - return VectorL10n.tr("Vector", "device_verification_verified_description_2") - } /// Got it internal static var deviceVerificationVerifiedGotItButton: String { return VectorL10n.tr("Vector", "device_verification_verified_got_it_button") @@ -1618,17 +1610,29 @@ internal enum VectorL10n { internal static var keyVerificationTileRequestStatusWaiting: String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_waiting") } - /// verify them + /// Verify them internal static var keyVerificationUserTitle: String { return VectorL10n.tr("Vector", "key_verification_user_title") } - /// You’ve successfully verified this user. - internal static var keyVerificationVerifiedUserDescription1: String { - return VectorL10n.tr("Vector", "key_verification_verified_user_description_1") + /// You can now read secure messages on your new device, and other users will know they can trust it. + internal static var keyVerificationVerifiedNewSessionInformation: String { + return VectorL10n.tr("Vector", "key_verification_verified_new_session_information") } - /// Messages with this user in this room are end-to-end encrypted and can’t be read by third parties. - internal static var keyVerificationVerifiedUserDescription2: String { - return VectorL10n.tr("Vector", "key_verification_verified_user_description_2") + /// New session verified! + internal static var keyVerificationVerifiedNewSessionTitle: String { + return VectorL10n.tr("Vector", "key_verification_verified_new_session_title") + } + /// You can now read secure messages on your other session, and other users will know they can trust it. + internal static var keyVerificationVerifiedOtherSessionInformation: String { + return VectorL10n.tr("Vector", "key_verification_verified_other_session_information") + } + /// You can now read secure messages on this device, and other users will know they can trust it. + internal static var keyVerificationVerifiedThisSessionInformation: String { + return VectorL10n.tr("Vector", "key_verification_verified_this_session_information") + } + /// Messages with this user are end-to-end encrypted and can't be read by third parties. + internal static var keyVerificationVerifiedUserInformation: String { + return VectorL10n.tr("Vector", "key_verification_verified_user_information") } /// Can't scan? internal static var keyVerificationVerifyQrCodeCannotScanAction: String { diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 134e4eca7..abb48cd01 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -37,6 +37,11 @@ */ id kThemeServiceDidChangeThemeNotificationObserver; + /** + Observe AppDelegateUniversalLinkDidChangeNotification to handle universal link changes. + */ + id universalLinkDidChangeNotificationObserver; + /** Server discovery. */ @@ -143,7 +148,12 @@ [self userInterfaceThemeDidChange]; }]; + universalLinkDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AppDelegateUniversalLinkDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + [self updateUniversalLink]; + }]; + [self userInterfaceThemeDidChange]; + [self updateUniversalLink]; } - (void)userInterfaceThemeDidChange @@ -218,6 +228,20 @@ [self setNeedsStatusBarAppearanceUpdate]; } +- (void)updateUniversalLink +{ + UniversalLink *link = [AppDelegate theDelegate].lastHandledUniversalLink; + if (link) + { + NSString *emailAddress = link.queryParams[@"email"]; + if (emailAddress && self.authInputsView) + { + AuthInputsView *inputsView = (AuthInputsView *)self.authInputsView; + inputsView.emailTextField.text = emailAddress; + } + } +} + - (UIStatusBarStyle)preferredStatusBarStyle { return ThemeService.shared.theme.statusBarStyle; @@ -264,6 +288,12 @@ kThemeServiceDidChangeThemeNotificationObserver = nil; } + if (universalLinkDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:universalLinkDidChangeNotificationObserver]; + universalLinkDidChangeNotificationObserver = nil; + } + autoDiscovery = nil; _keyVerificationCoordinatorBridgePresenter = nil; } @@ -668,6 +698,7 @@ if ([self.authInputsView isKindOfClass:AuthInputsView.class]) { authInputsview = (AuthInputsView*)self.authInputsView; + [self updateUniversalLink]; } // Hide "Forgot password" and "Log in" buttons in case of SSO diff --git a/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard index 43b7fe2ee..bc069c0d7 100644 --- a/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.storyboard @@ -22,10 +22,10 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - + + + - + - - + + + + + @@ -134,16 +110,17 @@ - - - - + + - + + + + diff --git a/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift index 7e0975081..ed869052f 100644 --- a/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift +++ b/Riot/Modules/KeyVerification/Common/Verified/KeyVerificationVerifiedViewController.swift @@ -30,11 +30,8 @@ final class KeyVerificationVerifiedViewController: UIViewController { // MARK: Outlets @IBOutlet private weak var titleLabel: UILabel! - @IBOutlet private weak var description1Label: UILabel! - @IBOutlet private weak var description2Label: UILabel! - - @IBOutlet private weak var okButtonBackgroundView: UIView! - @IBOutlet private weak var okButton: UIButton! + @IBOutlet private weak var informationLabel: UILabel! + @IBOutlet private weak var doneButton: RoundedButton! // MARK: Private @@ -83,26 +80,28 @@ final class KeyVerificationVerifiedViewController: UIViewController { private func setupViews() { let bodyTitle: String - let descriptionTextPart1: String - let descriptionTextPart2: String + let informationText: String switch self.verificationKind { - case .otherSession, .thisSession, .newSession: + case .otherSession: bodyTitle = VectorL10n.deviceVerificationVerifiedTitle - descriptionTextPart1 = VectorL10n.deviceVerificationVerifiedDescription1 - descriptionTextPart2 = VectorL10n.deviceVerificationVerifiedDescription2 + informationText = VectorL10n.keyVerificationVerifiedOtherSessionInformation + case .newSession: + bodyTitle = VectorL10n.keyVerificationVerifiedNewSessionTitle + informationText = VectorL10n.keyVerificationVerifiedNewSessionInformation + case .thisSession: + bodyTitle = VectorL10n.deviceVerificationVerifiedTitle + informationText = VectorL10n.keyVerificationVerifiedThisSessionInformation case .user: bodyTitle = VectorL10n.deviceVerificationVerifiedTitle - descriptionTextPart1 = VectorL10n.keyVerificationVerifiedUserDescription1 - descriptionTextPart2 = VectorL10n.keyVerificationVerifiedUserDescription2 + informationText = VectorL10n.keyVerificationVerifiedUserInformation } self.title = self.verificationKind.verificationTitle self.titleLabel.text = bodyTitle - self.description1Label.text = descriptionTextPart1 - self.description2Label.text = descriptionTextPart2 + self.informationLabel.text = informationText - self.okButton.setTitle(VectorL10n.deviceVerificationVerifiedGotItButton, for: .normal) + self.doneButton.setTitle(VectorL10n.deviceVerificationVerifiedGotItButton, for: .normal) } private func update(theme: Theme) { @@ -115,11 +114,8 @@ final class KeyVerificationVerifiedViewController: UIViewController { } self.titleLabel.textColor = theme.textPrimaryColor - self.description1Label.textColor = theme.textPrimaryColor - self.description2Label.textColor = theme.textPrimaryColor - - self.okButtonBackgroundView.backgroundColor = theme.backgroundColor - theme.applyStyle(onButton: self.okButton) + self.informationLabel.textColor = theme.textPrimaryColor + self.doneButton.update(theme: theme) } private func registerThemeServiceDidChangeThemeNotification() { @@ -132,7 +128,7 @@ final class KeyVerificationVerifiedViewController: UIViewController { self.update(theme: ThemeService.shared().theme) } - @IBAction private func validateButtonAction(_ sender: Any) { + @IBAction private func doneButtonAction(_ sender: Any) { self.delegate?.keyVerificationVerifiedViewControllerDidTapSetupAction(self) } } diff --git a/Riot/Utils/UniversalLink.h b/Riot/Utils/UniversalLink.h new file mode 100644 index 000000000..df546a936 --- /dev/null +++ b/Riot/Utils/UniversalLink.h @@ -0,0 +1,35 @@ +/* + Copyright 2020 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UniversalLink : NSObject + +@property (nonatomic, copy, readonly) NSURL *url; + +@property (nonatomic, copy, readonly) NSArray *pathParams; + +@property (nonatomic, copy, readonly) NSDictionary *queryParams; + +- (id)initWithUrl:(NSURL *)url + pathParams:(NSArray *)pathParams + queryParams:(NSDictionary *)queryParams; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Riot/Utils/UniversalLink.m b/Riot/Utils/UniversalLink.m new file mode 100644 index 000000000..81bb453b4 --- /dev/null +++ b/Riot/Utils/UniversalLink.m @@ -0,0 +1,60 @@ +/* + Copyright 2020 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "UniversalLink.h" + +@implementation UniversalLink + +- (id)initWithUrl:(NSURL *)url pathParams:(NSArray *)pathParams queryParams:(NSDictionary *)queryParams +{ + self = [super init]; + if (self) + { + _url = url; + _pathParams = pathParams; + _queryParams = queryParams; + } + return self; +} + +- (BOOL)isEqual:(id)other +{ + if (other == self) + return YES; + + if (![other isKindOfClass:UniversalLink.class]) + return NO; + + UniversalLink *otherLink = (UniversalLink *)other; + + return [_url isEqual:otherLink.url] + && [_pathParams isEqualToArray:otherLink.pathParams] + && [_queryParams isEqualToDictionary:otherLink.queryParams]; +} + +- (NSUInteger)hash +{ + NSUInteger prime = 31; + NSUInteger result = 1; + + result = prime * result + [_url hash]; + result = prime * result + [_pathParams hash]; + result = prime * result + [_queryParams hash]; + + return result; +} + +@end