diff --git a/CHANGES.rst b/CHANGES.rst index 0f8ec2071..822cf6e91 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,11 @@ Improvements: * i18n: Add Albanian (sq). * Update project structure. Organize UI related files by feature (PR#1932). * Move image files to xcassets (PR#1932). +* Replies: Implement sending (#1911). +* Support room versioning (#1938). +* Add support of lazy-loading of room members (#1931). +* Chat screen: Add "view decrypted source" option on the selected event (#1642). +* Improve GDPR consent webview management (#1952). Changes in 0.6.20 (2018-07-13) =============================================== diff --git a/Podfile b/Podfile index d9d427d94..90d6bd801 100644 --- a/Podfile +++ b/Podfile @@ -23,15 +23,18 @@ $matrixKitVersion = '0.7.15' def import_MatrixKit if $matrixKitVersion == 'local' pod 'MatrixSDK', :path => '../matrix-ios-sdk/MatrixSDK.podspec' + pod 'MatrixSDK/SwiftSupport', :path => '../matrix-ios-sdk/MatrixSDK.podspec' pod 'MatrixSDK/JingleCallStack', :path => '../matrix-ios-sdk/MatrixSDK.podspec' pod 'MatrixKit', :path => '../matrix-ios-kit/MatrixKit.podspec' else if $matrixKitVersion == 'develop' pod 'MatrixSDK', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' + pod 'MatrixSDK/SwiftSupport', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' pod 'MatrixSDK/JingleCallStack', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' pod 'MatrixKit', :git => 'https://github.com/matrix-org/matrix-ios-kit.git', :branch => 'develop' else pod 'MatrixKit', $matrixKitVersion + pod 'MatrixSDK/SwiftSupport' pod 'MatrixSDK/JingleCallStack' end end @@ -41,15 +44,18 @@ end def import_MatrixKitAppExtension if $matrixKitVersion == 'local' pod 'MatrixSDK', :path => '../matrix-ios-sdk/MatrixSDK.podspec' + pod 'MatrixSDK/SwiftSupport', :path => '../matrix-ios-sdk/MatrixSDK.podspec' pod 'MatrixSDK/JingleCallStack', :path => '../matrix-ios-sdk/MatrixSDK.podspec' pod 'MatrixKit/AppExtension', :path => '../matrix-ios-kit/MatrixKit.podspec' else if $matrixKitVersion == 'develop' pod 'MatrixSDK', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' + pod 'MatrixSDK/SwiftSupport', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' pod 'MatrixSDK/JingleCallStack', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' pod 'MatrixKit/AppExtension', :git => 'https://github.com/matrix-org/matrix-ios-kit.git', :branch => 'develop' else pod 'MatrixKit/AppExtension', $matrixKitVersion + pod 'MatrixSDK/SwiftSupport' pod 'MatrixSDK/JingleCallStack' end end diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index a704ff8b1..c13b8c2bd 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -60,6 +60,8 @@ B169331520F3CAFC00746532 /* PublicRoomTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B169330F20F3CAFC00746532 /* PublicRoomTableViewCell.xib */; }; B169331620F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = B169331220F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m */; }; B169331720F3CBE000746532 /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = B16932F920F3C51900746532 /* RecentCellData.m */; }; + B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */; }; + B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1798301211B13B3001FD722 /* OnBoardingManager.swift */; }; B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */; }; B1B5571920EE6C4D00210D55 /* LanguagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */; }; B1B5571A20EE6C4D00210D55 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5567E20EE6C4C00210D55 /* SettingsViewController.m */; }; @@ -498,6 +500,8 @@ B169331020F3CAFC00746532 /* PublicRoomTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomTableViewCell.h; sourceTree = ""; }; B169331220F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PublicRoomsDirectoryDataSource.m; sourceTree = ""; }; B169331320F3CAFC00746532 /* PublicRoomsDirectoryDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomsDirectoryDataSource.h; sourceTree = ""; }; + B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GDPRConsentViewController.swift; sourceTree = ""; }; + B1798301211B13B3001FD722 /* OnBoardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingManager.swift; sourceTree = ""; }; B1B5567920EE6C4C00210D55 /* CountryPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CountryPickerViewController.h; sourceTree = ""; }; B1B5567A20EE6C4C00210D55 /* CountryPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CountryPickerViewController.m; sourceTree = ""; }; B1B5567C20EE6C4C00210D55 /* LanguagePickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LanguagePickerViewController.m; sourceTree = ""; }; @@ -1280,12 +1284,29 @@ path = DataSources; sourceTree = ""; }; + B17982FD2119FEA7001FD722 /* GDPR */ = { + isa = PBXGroup; + children = ( + B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */, + ); + path = GDPR; + sourceTree = ""; + }; + B1798300211B137B001FD722 /* OnBoarding */ = { + isa = PBXGroup; + children = ( + B1798301211B13B3001FD722 /* OnBoardingManager.swift */, + ); + path = OnBoarding; + sourceTree = ""; + }; B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( B1B556EA20EE6C4C00210D55 /* Main */, B1B556CA20EE6C4C00210D55 /* TabBar */, B1B556F920EE6C4C00210D55 /* Authentication */, + B17982FD2119FEA7001FD722 /* GDPR */, B1B5568420EE6C4C00210D55 /* Home */, B1B556C720EE6C4C00210D55 /* Favorites */, B1B556E720EE6C4C00210D55 /* People */, @@ -2311,6 +2332,7 @@ B1B5597C20EFC3DF00210D55 /* Managers */ = { isa = PBXGroup; children = ( + B1798300211B137B001FD722 /* OnBoarding */, B1B5598B20EFC5E400210D55 /* Analytics */, B1B5598920EFC41100210D55 /* BugReport */, B1B5598A20EFC42100210D55 /* Settings */, @@ -3078,6 +3100,7 @@ B1B5579920EF575B00210D55 /* AuthInputsView.m in Sources */, B1B5597520EFB02A00210D55 /* InviteRecentTableViewCell.m in Sources */, B1B5571E20EE6C4D00210D55 /* ContactDetailsViewController.m in Sources */, + B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */, B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */, B1B5575220EE6C4D00210D55 /* RoomKeyRequestViewController.m in Sources */, F083BD1E1E7009ED00A9B29C /* AppDelegate.m in Sources */, @@ -3088,6 +3111,7 @@ B1B558CF20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */, B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */, + B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */, B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */, B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */, B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 50acffa9d..8047f6841 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -81,7 +81,7 @@ NSString *const kAppDelegateDidTapStatusBarNotification = @"kAppDelegateDidTapStatusBarNotification"; NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateNetworkStatusDidChangeNotification"; -@interface AppDelegate () +@interface AppDelegate () { /** Reachability observer @@ -213,7 +213,12 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN @property (strong, nonatomic) UIAlertController *logoutConfirmation; @property (weak, nonatomic) UIAlertController *gdprConsentNotGivenAlertController; -@property (weak, nonatomic) UIViewController *gdprConsentViewController; +@property (weak, nonatomic) UIViewController *gdprConsentController; + +/** + Used to manage on boarding steps, like create DM with riot bot + */ +@property (strong, nonatomic) OnBoardingManager *onBoardingManager; @property (nonatomic, nullable, copy) void (^registrationForRemoteNotificationsCompletion)(NSError *); @@ -3979,10 +3984,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *consentURI = notification.userInfo[kMXHTTPClientUserConsentNotGivenErrorNotificationConsentURIKey]; if (consentURI && self.gdprConsentNotGivenAlertController.presentingViewController == nil - && self.gdprConsentViewController.presentingViewController == nil) + && self.gdprConsentController.presentingViewController == nil) { self.gdprConsentNotGivenAlertController = nil; - self.gdprConsentViewController = nil; + self.gdprConsentController = nil; UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; @@ -4022,26 +4027,56 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (void)presentGDPRConsentFromViewController:(UIViewController*)viewController consentURI:(NSString*)consentURI { - WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:consentURI]; - webViewViewController.title = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil); + GDPRConsentViewController *gdprConsentViewController = [[GDPRConsentViewController alloc] initWithURL:consentURI]; UIBarButtonItem *closeBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"close"] style:UIBarButtonItemStylePlain target:self action:@selector(dismissGDPRConsent)]; - webViewViewController.navigationItem.leftBarButtonItem = closeBarButtonItem; + gdprConsentViewController.navigationItem.leftBarButtonItem = closeBarButtonItem; - UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:webViewViewController]; + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:gdprConsentViewController]; [viewController presentViewController:navigationController animated:YES completion:nil]; - self.gdprConsentViewController = navigationController; + self.gdprConsentController = navigationController; + + gdprConsentViewController.delegate = self; } - (void)dismissGDPRConsent { - [self.gdprConsentViewController dismissViewControllerAnimated:YES completion:nil]; + [self.gdprConsentController dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - GDPRConsentViewControllerDelegate + +- (void)gdprConsentViewControllerDidConsentToGDPRWithSuccess:(GDPRConsentViewController *)gdprConsentViewController +{ + MXSession *session = mxSessionArray.firstObject; + + self.onBoardingManager = [[OnBoardingManager alloc] initWithSession:session]; + + MXWeakify(self); + MXWeakify(gdprConsentViewController); + + [gdprConsentViewController startActivityIndicator]; + + void (^createRiotBotDMcompletion)(void) = ^() { + + MXStrongifyAndReturnIfNil(self); + + [weakgdprConsentViewController stopActivityIndicator]; + [self dismissGDPRConsent]; + self.onBoardingManager = nil; + }; + + [self.onBoardingManager createRiotBotDirectMessageIfNeededWithSuccess:^{ + createRiotBotDMcompletion(); + } failure:^(NSError * _Nonnull error) { + createRiotBotDMcompletion(); + }]; } #pragma mark - Settings diff --git a/Riot/Managers/OnBoarding/OnBoardingManager.swift b/Riot/Managers/OnBoarding/OnBoardingManager.swift new file mode 100644 index 000000000..fd17d0028 --- /dev/null +++ b/Riot/Managers/OnBoarding/OnBoardingManager.swift @@ -0,0 +1,87 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// `OnBoardingManager` is used to manage onboarding steps, like create DM room with riot bot. +final public class OnBoardingManager: NSObject { + + // MARK: - Constants + + private enum Constants { + static let riotBotMatrixId = "@riot-bot:matrix.org" + static let createRiotBotDMRequestMaxNumberOfTries: UInt = UInt.max + } + + // MARK: - Properties + + private let session: MXSession + + // MARK: - Setup & Teardown + + @objc public init(session: MXSession) { + self.session = session + + super.init() + } + + // MARK: - Public + + @objc public func createRiotBotDirectMessageIfNeeded(success: (() -> Void)?, failure: ((Error) -> Void)?) { + + // Check user has joined no rooms so is a new comer + guard self.isUserJoinedARoom() == false else { + // As the user has already rooms, one of their riot client has already created a room with riot bot + success?() + return + } + + // Create DM room with Riot-bot + + let httpOperation = self.session.createRoom(name: nil, visibility: .private, alias: nil, topic: nil, invite: [Constants.riotBotMatrixId], invite3PID: nil, isDirect: true, preset: .trustedPrivateChat) { (response) in + + switch response { + case .success(_): + success?() + case .failure(let error): + NSLog("[OnBoardingManager] Create chat with riot-bot failed"); + failure?(error) + } + } + + // Make multipe tries, until we get a response + httpOperation.maxNumberOfTries = Constants.createRiotBotDMRequestMaxNumberOfTries + } + + // MARK: - Private + + private func isUserJoinedARoom() -> Bool { + guard let roomSummaries = self.session.roomsSummaries() else { + return false + } + + var isUSerJoinedARoom = false + + for roomSummary in roomSummaries { + if case .join = roomSummary.membership { + isUSerJoinedARoom = true + break + } + } + + return isUSerJoinedARoom + } +} diff --git a/Riot/Modules/GDPR/GDPRConsentViewController.swift b/Riot/Modules/GDPR/GDPRConsentViewController.swift new file mode 100644 index 000000000..0723326a1 --- /dev/null +++ b/Riot/Modules/GDPR/GDPRConsentViewController.swift @@ -0,0 +1,53 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objc protocol GDPRConsentViewControllerDelegate: class { + func gdprConsentViewControllerDidConsentToGDPRWithSuccess(_ gdprConsentViewController: GDPRConsentViewController) +} + +/// GPDR consent screen. +final public class GDPRConsentViewController: WebViewViewController { + + // MARK: - Constants + + private static let consentSuccessURLPath = "/_matrix/consent" + + // MARK: - Properties + + @objc weak var delegate: GDPRConsentViewControllerDelegate? + + // MARK: - View life cycle + + public override func viewDidLoad() { + super.viewDidLoad() + + self.title = NSLocalizedString("settings_term_conditions", tableName: "Vector", comment: "") + } + + // MARK: - Superclass Overrides + + override public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + super.webView(webView, didFinish: navigation) + + // When navigation finish on path `consentSuccessURLPath` with no query, it means that user consent to GDPR + if let url = webView.url, url.path == GDPRConsentViewController.consentSuccessURLPath, url.query == nil { + NSLog("[GDPRConsentViewController] User consent to GDPR") + self.delegate?.gdprConsentViewControllerDidConsentToGDPRWithSuccess(self) + } + } +} diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 1b2cb5d6d..59026bb56 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -2,3 +2,5 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +@import MatrixSDK; +#import "WebViewViewController.h"