From a604ba5646e3b1fcef67114523a9108b40c4796b Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 20 Sep 2019 15:53:24 +1000 Subject: [PATCH] Start implementing device linking UI --- Podfile | 1 + Podfile.lock | 8 +- Signal.xcodeproj/project.pbxproj | 6 + Signal/src/AppDelegate.m | 20 +-- Signal/src/Loki/DeviceLinkingModal.swift | 131 ++++++++++++++++++ .../AppSettings/AppSettingsViewController.m | 4 +- .../translations/en.lproj/Localizable.strings | 2 + .../LokiDeviceLinkingSession.swift | 2 +- 8 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 Signal/src/Loki/DeviceLinkingModal.swift diff --git a/Podfile b/Podfile index 0b3daa79e..1a275b7f8 100644 --- a/Podfile +++ b/Podfile @@ -78,6 +78,7 @@ target 'Signal' do pod 'FirebaseCore', '~> 6.0', :inhibit_warnings => true pod 'Fabric', '~> 1.10', :inhibit_warnings => true pod 'Crashlytics', '~> 3.13', :inhibit_warnings => true + pod 'NVActivityIndicatorView', '~> 4.7', :inhibit_warnings => true target 'SignalTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index c7287e2d9..9ef1e6e92 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -62,6 +62,9 @@ PODS: - Mantle/extobjc (= 2.1.0) - Mantle/extobjc (2.1.0) - Mixpanel (3.4.7) + - NVActivityIndicatorView (4.7.0): + - NVActivityIndicatorView/Presenter (= 4.7.0) + - NVActivityIndicatorView/Presenter (4.7.0) - PromiseKit (6.5.3): - PromiseKit/CorePromise (= 6.5.3) - PromiseKit/Foundation (= 6.5.3) @@ -220,6 +223,7 @@ DEPENDENCIES: - IGIdenticon - Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`) - Mixpanel (~> 3.4) + - NVActivityIndicatorView (~> 4.7) - PromiseKit (= 6.5.3) - PureLayout - Reachability @@ -249,6 +253,7 @@ SPEC REPOS: - IGIdenticon - libPhoneNumber-iOS - Mixpanel + - NVActivityIndicatorView - PromiseKit - PureLayout - Reachability @@ -331,6 +336,7 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: e444379ac18bbfbdefad571da735b2cd7e096caa Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b Mixpanel: 696e0a1c7f2685aa06bb23829b7a58ab7203d6c7 + NVActivityIndicatorView: b19ddab2576f805cbe0fb2306cba3476e09a1dea PromiseKit: c609029bdd801f792551a504c695c7d3098b42cd PureLayout: f08c01b8dec00bb14a1fefa3de4c7d9c265df85e Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 @@ -345,6 +351,6 @@ SPEC CHECKSUMS: YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 -PODFILE CHECKSUM: 1240a47686acf0d2d7e61e6ec3a925fbded184fa +PODFILE CHECKSUM: ff66104e65cc960ed08a74b71c27f5caf59f4239 COCOAPODS: 1.5.3 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c35ea15de..74932e1c5 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -568,6 +568,7 @@ B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */; }; B845B4D4230CD09100D759F0 /* LokiGroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */; }; B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; }; + B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; }; B891105C2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; B891105E2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; B891105F2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; @@ -1371,6 +1372,7 @@ B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiRSSFeedPoller.swift; sourceTree = ""; }; B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiGroupChatPoller.swift; sourceTree = ""; }; B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = ""; }; + B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = ""; }; B891105B2320872800F15FCC /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewController.swift; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; @@ -2640,6 +2642,7 @@ B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */, B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */, B821F2F92272CEEE002C88C0 /* SeedViewController.swift */, + B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */, ); path = Loki; sourceTree = ""; @@ -3319,6 +3322,7 @@ "${BUILT_PRODUCTS_DIR}/IGIdenticon/IGIdenticon.framework", "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", "${BUILT_PRODUCTS_DIR}/Mixpanel/Mixpanel.framework", + "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/PureLayout/PureLayout.framework", "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", @@ -3349,6 +3353,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGIdenticon.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mixpanel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NVActivityIndicatorView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PureLayout.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", @@ -3706,6 +3711,7 @@ 450D19131F85236600970622 /* RemoteVideoView.m in Sources */, 34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */, 34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */, + B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */, 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */, 451166C01FD86B98000739BA /* AccountManager.swift in Sources */, 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 3fb3c79a5..b7dbc7122 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -60,7 +60,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; static NSTimeInterval launchStartedAt; -@interface AppDelegate () +@interface AppDelegate () @property (nonatomic) BOOL hasInitialRootViewController; @property (nonatomic) BOOL areVersionMigrationsComplete; @@ -72,7 +72,6 @@ static NSTimeInterval launchStartedAt; @property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller; @property (nonatomic) LKRSSFeedPoller *lokiNewsFeedPoller; @property (nonatomic) LKRSSFeedPoller *lokiMessengerUpdatesFeedPoller; -@property (nonatomic) LKDeviceLinkingSession *lokiDeviceLinkingSession; @end @@ -1639,21 +1638,4 @@ static NSTimeInterval launchStartedAt; } } -- (void)startListeningForLinkingRequests -{ - [self.lokiDeviceLinkingSession stopListeningForLinkingRequests]; - self.lokiDeviceLinkingSession = [[LKDeviceLinkingSession alloc] initWithDelegate:self]; - [self.lokiDeviceLinkingSession startListeningForLinkingRequests]; -} - -- (void)requestUserAuthorizationFor:(LKDeviceLink *)deviceLink -{ - -} - -- (void)handleDeviceLinkingSessionTimeout -{ - -} - @end diff --git a/Signal/src/Loki/DeviceLinkingModal.swift b/Signal/src/Loki/DeviceLinkingModal.swift new file mode 100644 index 000000000..6650bb728 --- /dev/null +++ b/Signal/src/Loki/DeviceLinkingModal.swift @@ -0,0 +1,131 @@ +import NVActivityIndicatorView + +@objc(LKDeviceLinkingModal) +final class DeviceLinkingModal : UIViewController, LokiDeviceLinkingSessionDelegate { + + private lazy var deviceLinkingSession: LokiDeviceLinkingSession = { + return LokiDeviceLinkingSession(delegate: self) + }() + + // MARK: Components + private lazy var contentView: UIView = { + let result = UIView() + result.backgroundColor = .lokiDarkGray() + result.layer.cornerRadius = 4 + result.layer.masksToBounds = false + result.layer.shadowColor = UIColor.black.cgColor + result.layer.shadowRadius = 8 + result.layer.shadowOpacity = 0.64 + return result + }() + + private lazy var spinner = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: .white, padding: nil) + + private lazy var titleLabel: UILabel = { + let result = UILabel() + result.textColor = Theme.primaryColor + result.font = UIFont.ows_dynamicTypeHeadlineClamped + result.text = NSLocalizedString("Waiting for Device", comment: "") + result.numberOfLines = 0 + result.lineBreakMode = .byWordWrapping + result.textAlignment = .center + return result + }() + + private lazy var subtitleLabel: UILabel = { + let result = UILabel() + result.textColor = Theme.primaryColor + result.font = UIFont.ows_dynamicTypeCaption1Clamped + result.text = NSLocalizedString("Click the \"Link Device\" button on your other device to start the linking process", comment: "") + result.numberOfLines = 0 + result.lineBreakMode = .byWordWrapping + result.textAlignment = .center + return result + }() + + private lazy var cancelButton: OWSFlatButton = { + let result = OWSFlatButton.button(title: NSLocalizedString("Cancel", comment: ""), font: .ows_dynamicTypeBodyClamped, titleColor: .white, backgroundColor: .clear, target: self, selector: #selector(cancel)) + result.setBackgroundColors(upColor: .clear, downColor: .clear) + return result + }() + + // MARK: Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setUpViewHierarchy() + deviceLinkingSession.startListeningForLinkingRequests() + } + + private func setUpViewHierarchy() { + view.backgroundColor = .clear + // Content view + view.addSubview(contentView) + contentView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32), + view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 32), + contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + // Spinner + contentView.addSubview(spinner) + spinner.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + spinner.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + spinner.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 32), + spinner.heightAnchor.constraint(equalToConstant: 64), + spinner.widthAnchor.constraint(equalToConstant: 64) + ]) + spinner.startAnimating() + // Title label + contentView.addSubview(titleLabel) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + titleLabel.topAnchor.constraint(equalTo: spinner.bottomAnchor, constant: 32), + contentView.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 16) + ]) + // Subtitle label + contentView.addSubview(subtitleLabel) + subtitleLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + subtitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), + contentView.trailingAnchor.constraint(equalTo: subtitleLabel.trailingAnchor, constant: 16) + ]) + // Cancel button + contentView.addSubview(cancelButton) + cancelButton.translatesAutoresizingMaskIntoConstraints = false + let cancelButtonHeight = cancelButton.button.titleLabel!.font.pointSize * 48 / 17 + NSLayoutConstraint.activate([ + cancelButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + cancelButton.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 16), + contentView.trailingAnchor.constraint(equalTo: cancelButton.trailingAnchor, constant: 16), + contentView.bottomAnchor.constraint(equalTo: cancelButton.bottomAnchor, constant: 16), + cancelButton.heightAnchor.constraint(equalToConstant: cancelButtonHeight) + ]) + } + + // MARK: Device Linking + func requestUserAuthorization(for deviceLink: LokiDeviceLink) { + + } + + func handleDeviceLinkingSessionTimeout() { + + } + + // MARK: Interaction + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + let touch = touches.first! + let location = touch.location(in: view) + if contentView.frame.contains(location) { + super.touchesBegan(touches, with: event) + } else { + dismiss(animated: true, completion: nil) + } + } + + @objc private func cancel() { + dismiss(animated: true, completion: nil) + } +} diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m index 33d2e978c..bc70dee36 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m @@ -514,7 +514,9 @@ - (void)linkDevice { - + LKDeviceLinkingModal *deviceLinkingModal = [LKDeviceLinkingModal new]; + deviceLinkingModal.modalPresentationStyle = UIModalPresentationOverFullScreen; + [self presentViewController:deviceLinkingModal animated:YES completion:nil]; } - (void)showSeed diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index e9ac60bac..2e827aecf 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2622,3 +2622,5 @@ "Loki Messenger is currently in beta. For development purposes the beta version collects basic usage statistics and crash logs. In addition, the beta version doesn't provide full privacy and shouldn't be used to transmit sensitive information." = "Loki Messenger is currently in beta. For development purposes the beta version collects basic usage statistics and crash logs. In addition, the beta version doesn't provide full privacy and shouldn't be used to transmit sensitive information."; "Copy Public Key" = "Copy Public Key"; "Link Device" = "Link Device"; +"Waiting for Device" = "Waiting for Device"; +"Click the \"Link Device\" button on your other device to start the linking process" = "Click the \"Link Device\" button on your other device to start the linking process"; diff --git a/SignalServiceKit/src/Loki/API/Multi Device/LokiDeviceLinkingSession.swift b/SignalServiceKit/src/Loki/API/Multi Device/LokiDeviceLinkingSession.swift index 9b07e10d8..33d44b903 100644 --- a/SignalServiceKit/src/Loki/API/Multi Device/LokiDeviceLinkingSession.swift +++ b/SignalServiceKit/src/Loki/API/Multi Device/LokiDeviceLinkingSession.swift @@ -1,7 +1,7 @@ import PromiseKit @objc (LKDeviceLinkingSession) -final class LokiDeviceLinkingSession : NSObject { +public final class LokiDeviceLinkingSession : NSObject { private let delegate: LokiDeviceLinkingSessionDelegate private var timer: Timer? @objc public var isListeningForLinkingRequests = false