Merge branch 'dev' of github.com:loki-project/loki-messenger-ios

pull/2/head
Niels Andriesse 5 years ago
parent 8b37e12146
commit 33c6f3a88d

@ -88,7 +88,6 @@
3448E15E221333F5004B052E /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15D221333F5004B052E /* OnboardingController.swift */; };
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */; };
3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */; };
3448E16422135FFA004B052E /* OnboardingPhoneNumberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */; };
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */; };
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; };
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */; };
@ -556,6 +555,13 @@
B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; };
B821F2ED2272CBC0002C88C0 /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2EC2272CBC0002C88C0 /* Mnemonic.swift */; };
B821F2F32272CCD9002C88C0 /* portuguese.txt in Resources */ = {isa = PBXBuildFile; fileRef = B821F2EF2272CCD9002C88C0 /* portuguese.txt */; };
B821F2F42272CCD9002C88C0 /* japanese.txt in Resources */ = {isa = PBXBuildFile; fileRef = B821F2F02272CCD9002C88C0 /* japanese.txt */; };
B821F2F52272CCD9002C88C0 /* spanish.txt in Resources */ = {isa = PBXBuildFile; fileRef = B821F2F12272CCD9002C88C0 /* spanish.txt */; };
B821F2F62272CCD9002C88C0 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = B821F2F22272CCD9002C88C0 /* english.txt */; };
B821F2F82272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */; };
B821F2FA2272CEEE002C88C0 /* OnboardingPublicKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F92272CEEE002C88C0 /* OnboardingPublicKeyViewController.swift */; };
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
@ -772,7 +778,6 @@
3448E15D221333F5004B052E /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = "<group>"; };
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSplashViewController.swift; sourceTree = "<group>"; };
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBaseViewController.swift; sourceTree = "<group>"; };
3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPhoneNumberViewController.swift; sourceTree = "<group>"; };
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCaptchaViewController.swift; sourceTree = "<group>"; };
34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = "<group>"; };
344F248C2007CCD600CFB4F4 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = "<group>"; };
@ -1339,6 +1344,13 @@
B6BC3D0C1AA544B100C2907F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = translations/da.lproj/Localizable.strings; sourceTree = "<group>"; };
B6F509961AA53F760068F56A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = translations/en.lproj/Localizable.strings; sourceTree = "<group>"; };
B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; };
B821F2EC2272CBC0002C88C0 /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = "<group>"; };
B821F2EF2272CCD9002C88C0 /* portuguese.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = portuguese.txt; path = mnemonic/portuguese.txt; sourceTree = "<group>"; };
B821F2F02272CCD9002C88C0 /* japanese.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = japanese.txt; path = mnemonic/japanese.txt; sourceTree = "<group>"; };
B821F2F12272CCD9002C88C0 /* spanish.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = spanish.txt; path = mnemonic/spanish.txt; sourceTree = "<group>"; };
B821F2F22272CCD9002C88C0 /* english.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = english.txt; path = mnemonic/english.txt; sourceTree = "<group>"; };
B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingAccountDetailsViewController.swift; sourceTree = "<group>"; };
B821F2F92272CEEE002C88C0 /* OnboardingPublicKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPublicKeyViewController.swift; sourceTree = "<group>"; };
B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = "<group>"; };
B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = "<group>"; };
@ -1522,12 +1534,13 @@
children = (
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */,
349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */,
B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */,
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */,
3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */,
3448E15D221333F5004B052E /* OnboardingController.swift */,
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */,
3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */,
34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */,
B821F2F92272CEEE002C88C0 /* OnboardingPublicKeyViewController.swift */,
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */,
34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */,
346E9D5321B040B600562252 /* RegistrationController.swift */,
@ -2418,6 +2431,7 @@
B90418E5183E9DD40038554A /* DateUtil.m */,
34B0796C1FCF46B000E248C2 /* MainAppContext.h */,
34B0796B1FCF46B000E248C2 /* MainAppContext.m */,
B821F2EC2272CBC0002C88C0 /* Mnemonic.swift */,
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */,
344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */,
344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */,
@ -2590,6 +2604,17 @@
name = Translations;
sourceTree = "<group>";
};
B821F2EE2272CC39002C88C0 /* mnemonic */ = {
isa = PBXGroup;
children = (
B821F2F22272CCD9002C88C0 /* english.txt */,
B821F2F02272CCD9002C88C0 /* japanese.txt */,
B821F2EF2272CCD9002C88C0 /* portuguese.txt */,
B821F2F12272CCD9002C88C0 /* spanish.txt */,
);
name = mnemonic;
sourceTree = "<group>";
};
D221A07E169C9E5E00537ABF = {
isa = PBXGroup;
children = (
@ -2680,6 +2705,7 @@
4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */,
B6B6C3C419193F5B00C0B76B /* Translations */,
D221A099169C9E5E00537ABF /* main.m */,
B821F2EE2272CC39002C88C0 /* mnemonic */,
D221A095169C9E5E00537ABF /* Signal-Info.plist */,
D221A09B169C9E5E00537ABF /* Signal-Prefix.pch */,
);
@ -3078,6 +3104,7 @@
B10C9B601A7049EC00ECA2BF /* pause_icon@2x.png in Resources */,
FC9120411A39EFB70074545C /* qr@2x.png in Resources */,
34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */,
B821F2F42272CCD9002C88C0 /* japanese.txt in Resources */,
B10C9B5F1A7049EC00ECA2BF /* pause_icon.png in Resources */,
AD83FF471A73428300B5C81A /* audio_play_button_blue.png in Resources */,
34330A5E1E787BD800DF2FB9 /* ElegantIcons.ttf in Resources */,
@ -3091,7 +3118,10 @@
45B74A812044AAB600CD42F8 /* chord-quiet.aifc in Resources */,
45B74A832044AAB600CD42F8 /* circles.aifc in Resources */,
45B74A892044AAB600CD42F8 /* circles-quiet.aifc in Resources */,
B821F2F32272CCD9002C88C0 /* portuguese.txt in Resources */,
B821F2F52272CCD9002C88C0 /* spanish.txt in Resources */,
4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */,
B821F2F62272CCD9002C88C0 /* english.txt in Resources */,
4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */,
45B74A7E2044AAB600CD42F8 /* complete.aifc in Resources */,
45B74A872044AAB600CD42F8 /* complete-quiet.aifc in Resources */,
@ -3187,6 +3217,7 @@
inputPaths = (
"$(SRCROOT)/ThirdParty/WebRTC/Build/WebRTC.framework",
"$(SRCROOT)/ThirdParty/Carthage/Build/iOS/ZXingObjC.framework",
"$(SRCROOT)/ThirdParty/Carthage/Build/iOS/CryptoSwift.framework",
);
name = "[Carthage] Copy Frameworks";
outputPaths = (
@ -3560,6 +3591,7 @@
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */,
45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */,
34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */,
B821F2ED2272CBC0002C88C0 /* Mnemonic.swift in Sources */,
3496957421A301A100DCFE74 /* OWSBackupAPI.swift in Sources */,
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */,
340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */,
@ -3642,7 +3674,6 @@
45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */,
34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */,
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,
3448E16422135FFA004B052E /* OnboardingPhoneNumberViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,
@ -3657,6 +3688,7 @@
457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */,
4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */,
4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */,
B821F2FA2272CEEE002C88C0 /* OnboardingPublicKeyViewController.swift in Sources */,
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */,
34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */,
@ -3673,6 +3705,7 @@
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
B821F2F82272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift in Sources */,
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -24,11 +24,11 @@ public class Onboarding2FAViewController: OnboardingBaseViewController {
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_2FA_TITLE", comment: "Title of the 'onboarding 2FA' view."))
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_2FA_TITLE", comment: "Title of the 'onboarding 2FA' view."))
let explanationLabel1 = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_2FA_EXPLANATION_1",
let explanationLabel1 = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_2FA_EXPLANATION_1",
comment: "The first explanation in the 'onboarding 2FA' view."))
let explanationLabel2 = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_2FA_EXPLANATION_2",
let explanationLabel2 = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_2FA_EXPLANATION_2",
comment: "The first explanation in the 'onboarding 2FA' view."))
explanationLabel1.font = UIFont.ows_dynamicTypeCaption1
explanationLabel2.font = UIFont.ows_dynamicTypeCaption1

@ -0,0 +1,73 @@
import UIKit
import PromiseKit
final class OnboardingAccountDetailsViewController : OnboardingBaseViewController {
private lazy var displayNameTextField: UITextField = {
let result = UITextField()
result.textColor = Theme.primaryColor
result.font = UIFont.ows_dynamicTypeBodyClamped
result.textAlignment = .center
result.placeholder = NSLocalizedString("Display Name (Optional)", comment: "")
result.accessibilityIdentifier = "onboarding.accountDetailsStep.displayNameTextField"
return result
}()
private lazy var passwordTextField: UITextField = {
let result = UITextField()
result.textColor = Theme.primaryColor
result.font = UIFont.ows_dynamicTypeBodyClamped
result.textAlignment = .center
result.placeholder = NSLocalizedString("Password (Optional)", comment: "")
result.accessibilityIdentifier = "onboarding.accountDetailsStep.passwordTextField"
result.isSecureTextEntry = true
return result
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.createTitleLabel(text: NSLocalizedString("Create Your Loki Messenger Account", comment: ""))
titleLabel.accessibilityIdentifier = "onboarding.accountDetailsStep.titleLabel"
let topSpacer = UIView.vStretchingSpacer()
let displayNameLabel = createExplanationLabel(text: NSLocalizedString("Enter a name that will be shown to all your contacts", comment: ""))
displayNameLabel.accessibilityIdentifier = "onboarding.accountDetailsStep.displayNameLabel"
let passwordLabel = createExplanationLabel(text: NSLocalizedString("Type an optional password for added security", comment: ""))
passwordLabel.accessibilityIdentifier = "onboarding.accountDetailsStep.passwordLabel"
let bottomSpacer = UIView.vStretchingSpacer()
let nextButton = button(title: NSLocalizedString("Next", comment: ""), selector: #selector(goToSeedStep))
nextButton.accessibilityIdentifier = "onboarding.accountDetailsStep.nextButton"
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
displayNameLabel,
UIView.spacer(withHeight: 8),
displayNameTextField,
UIView.spacer(withHeight: 16),
passwordLabel,
UIView.spacer(withHeight: 8),
passwordTextField,
bottomSpacer,
nextButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
displayNameTextField.becomeFirstResponder()
}
@objc private func goToSeedStep() {
onboardingController.pushPublicKeyViewController(from: self)
}
}

@ -27,7 +27,7 @@ public class OnboardingBaseViewController: OWSViewController {
// MARK: - Factory Methods
func titleLabel(text: String) -> UILabel {
func createTitleLabel(text: String) -> UILabel {
let titleLabel = UILabel()
titleLabel.text = text
titleLabel.textColor = Theme.primaryColor
@ -38,11 +38,11 @@ public class OnboardingBaseViewController: OWSViewController {
return titleLabel
}
func explanationLabel(explanationText: String) -> UILabel {
func createExplanationLabel(text: String) -> UILabel {
let explanationLabel = UILabel()
explanationLabel.textColor = Theme.secondaryColor
explanationLabel.font = UIFont.ows_dynamicTypeSubheadlineClamped
explanationLabel.text = explanationText
explanationLabel.text = text
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping

@ -16,7 +16,7 @@ public class OnboardingCaptchaViewController: OnboardingBaseViewController {
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_CAPTCHA_TITLE", comment: "Title of the 'onboarding Captcha' view."))
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_CAPTCHA_TITLE", comment: "Title of the 'onboarding Captcha' view."))
titleLabel.accessibilityIdentifier = "onboarding.captcha." + "titleLabel"
let titleRow = UIStackView(arrangedSubviews: [

@ -111,7 +111,7 @@ public class OnboardingController: NSObject {
Logger.info("")
pushPhoneNumberView(viewController: viewController)
pushAccountDetailsViewController(from: viewController)
}
public func onboardingPermissionsDidComplete(viewController: UIViewController) {
@ -119,14 +119,19 @@ public class OnboardingController: NSObject {
Logger.info("")
pushPhoneNumberView(viewController: viewController)
pushAccountDetailsViewController(from: viewController)
}
private func pushPhoneNumberView(viewController: UIViewController) {
private func pushAccountDetailsViewController(from viewController: UIViewController) {
AssertIsOnMainThread()
let view = OnboardingPhoneNumberViewController(onboardingController: self)
viewController.navigationController?.pushViewController(view, animated: true)
let accountDetailsVC = OnboardingAccountDetailsViewController(onboardingController: self)
viewController.navigationController?.pushViewController(accountDetailsVC, animated: true)
}
func pushPublicKeyViewController(from viewController: UIViewController) {
AssertIsOnMainThread()
let publicKeyVC = OnboardingPublicKeyViewController(onboardingController: self)
viewController.navigationController?.pushViewController(publicKeyVC, animated: true)
}
public func onboardingRegistrationSucceeded(viewController: UIViewController) {
@ -152,7 +157,7 @@ public class OnboardingController: NSObject {
// from the "code verification" view. The "Captcha" view should always appear
// immediately after the "phone number" view.
while navigationController.viewControllers.count > 1 &&
!(navigationController.topViewController is OnboardingPhoneNumberViewController) {
!(navigationController.topViewController is OnboardingAccountDetailsViewController) {
navigationController.popViewController(animated: false)
}

@ -19,10 +19,10 @@ public class OnboardingPermissionsViewController: OnboardingBaseViewController {
target: self,
action: #selector(skipWasPressed))
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_PERMISSIONS_TITLE", comment: "Title of the 'onboarding permissions' view."))
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_PERMISSIONS_TITLE", comment: "Title of the 'onboarding permissions' view."))
titleLabel.accessibilityIdentifier = "onboarding.permissions." + "titleLabel"
let explanationLabel = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_PERMISSIONS_EXPLANATION",
let explanationLabel = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_PERMISSIONS_EXPLANATION",
comment: "Explanation in the 'onboarding permissions' view."))
explanationLabel.accessibilityIdentifier = "onboarding.permissions." + "explanationLabel"

@ -1,423 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import UIKit
import PromiseKit
@objc
public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
// MARK: - Dependencies
private var tsAccountManager: TSAccountManager {
return TSAccountManager.sharedInstance()
}
// MARK: -
private let countryNameLabel = UILabel()
private let callingCodeLabel = UILabel()
private let phoneNumberTextField = UITextField()
private var nextButton: OWSFlatButton?
private var phoneStrokeNormal: UIView?
private var phoneStrokeError: UIView?
private let validationWarningLabel = UILabel()
private var isPhoneNumberInvalid = false
override public func loadView() {
super.loadView()
populateDefaults()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_PHONE_NUMBER_TITLE", comment: "Title of the 'onboarding phone number' view."))
titleLabel.accessibilityIdentifier = "onboarding.phoneNumber." + "titleLabel"
// Country
let rowHeight: CGFloat = 40
countryNameLabel.textColor = Theme.primaryColor
countryNameLabel.font = UIFont.ows_dynamicTypeBodyClamped
countryNameLabel.setContentHuggingHorizontalLow()
countryNameLabel.setCompressionResistanceHorizontalLow()
countryNameLabel.accessibilityIdentifier = "onboarding.phoneNumber." + "countryNameLabel"
let countryIcon = UIImage(named: (CurrentAppContext().isRTL
? "small_chevron_left"
: "small_chevron_right"))
let countryImageView = UIImageView(image: countryIcon?.withRenderingMode(.alwaysTemplate))
countryImageView.tintColor = Theme.placeholderColor
countryImageView.setContentHuggingHigh()
countryImageView.setCompressionResistanceHigh()
countryImageView.accessibilityIdentifier = "onboarding.phoneNumber." + "countryImageView"
let countryRow = UIStackView(arrangedSubviews: [
countryNameLabel,
countryImageView
])
countryRow.axis = .horizontal
countryRow.alignment = .center
countryRow.spacing = 10
countryRow.isUserInteractionEnabled = true
countryRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(countryRowTapped)))
countryRow.autoSetDimension(.height, toSize: rowHeight)
_ = countryRow.addBottomStroke()
countryRow.accessibilityIdentifier = "onboarding.phoneNumber." + "countryRow"
callingCodeLabel.textColor = Theme.primaryColor
callingCodeLabel.font = UIFont.ows_dynamicTypeBodyClamped
callingCodeLabel.setContentHuggingHorizontalHigh()
callingCodeLabel.setCompressionResistanceHorizontalHigh()
callingCodeLabel.isUserInteractionEnabled = true
callingCodeLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(countryCodeTapped)))
_ = callingCodeLabel.addBottomStroke()
callingCodeLabel.autoSetDimension(.width, toSize: rowHeight, relation: .greaterThanOrEqual)
callingCodeLabel.accessibilityIdentifier = "onboarding.phoneNumber." + "callingCodeLabel"
phoneNumberTextField.textAlignment = .left
phoneNumberTextField.delegate = self
phoneNumberTextField.keyboardType = .numberPad
phoneNumberTextField.textColor = Theme.primaryColor
phoneNumberTextField.font = UIFont.ows_dynamicTypeBodyClamped
phoneNumberTextField.setContentHuggingHorizontalLow()
phoneNumberTextField.setCompressionResistanceHorizontalLow()
phoneNumberTextField.accessibilityIdentifier = "onboarding.phoneNumber." + "phoneNumberTextField"
phoneStrokeNormal = phoneNumberTextField.addBottomStroke()
phoneStrokeError = phoneNumberTextField.addBottomStroke(color: .ows_destructiveRed, strokeWidth: 2)
let phoneNumberRow = UIStackView(arrangedSubviews: [
callingCodeLabel,
phoneNumberTextField
])
phoneNumberRow.axis = .horizontal
phoneNumberRow.alignment = .fill
phoneNumberRow.spacing = 10
phoneNumberRow.autoSetDimension(.height, toSize: rowHeight)
callingCodeLabel.autoMatch(.height, to: .height, of: phoneNumberTextField)
validationWarningLabel.text = NSLocalizedString("ONBOARDING_PHONE_NUMBER_VALIDATION_WARNING",
comment: "Label indicating that the phone number is invalid in the 'onboarding phone number' view.")
validationWarningLabel.textColor = .ows_destructiveRed
validationWarningLabel.font = UIFont.ows_dynamicTypeSubheadlineClamped
validationWarningLabel.autoSetDimension(.height, toSize: validationWarningLabel.font.lineHeight)
validationWarningLabel.accessibilityIdentifier = "onboarding.phoneNumber." + "validationWarningLabel"
let validationWarningRow = UIView()
validationWarningRow.addSubview(validationWarningLabel)
validationWarningLabel.autoPinHeightToSuperview()
validationWarningLabel.autoPinEdge(toSuperviewEdge: .trailing)
let nextButton = self.button(title: NSLocalizedString("BUTTON_NEXT",
comment: "Label for the 'next' button."),
selector: #selector(nextPressed))
nextButton.accessibilityIdentifier = "onboarding.phoneNumber." + "nextButton"
self.nextButton = nextButton
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
countryRow,
UIView.spacer(withHeight: 8),
phoneNumberRow,
UIView.spacer(withHeight: 8),
validationWarningRow,
bottomSpacer,
nextButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
// Ensure whitespace is balanced, so inputs are vertically centered.
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
validationWarningLabel.autoPinEdge(.leading, to: .leading, of: phoneNumberTextField)
}
// MARK: - View Lifecycle
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
isPhoneNumberInvalid = false
updateViewState()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
phoneNumberTextField.becomeFirstResponder()
if tsAccountManager.isReregistering() {
// If re-registering, pre-populate the country (country code, calling code, country name)
// and phone number state.
guard let phoneNumberE164 = tsAccountManager.reregisterationPhoneNumber() else {
owsFailDebug("Could not resume re-registration; missing phone number.")
return
}
tryToReregister(phoneNumberE164: phoneNumberE164)
}
}
private func tryToReregister(phoneNumberE164: String) {
guard phoneNumberE164.count > 0 else {
owsFailDebug("Could not resume re-registration; invalid phoneNumberE164.")
return
}
guard let parsedPhoneNumber = PhoneNumber(fromE164: phoneNumberE164) else {
owsFailDebug("Could not resume re-registration; couldn't parse phoneNumberE164.")
return
}
guard let callingCodeNumeric = parsedPhoneNumber.getCountryCode() else {
owsFailDebug("Could not resume re-registration; missing callingCode.")
return
}
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumeric)"
let countryCodes: [String] =
PhoneNumberUtil.sharedThreadLocal().countryCodes(fromCallingCode: callingCode)
guard let countryCode = countryCodes.first else {
owsFailDebug("Could not resume re-registration; unknown countryCode.")
return
}
guard let countryName = PhoneNumberUtil.countryName(fromCountryCode: countryCode) else {
owsFailDebug("Could not resume re-registration; unknown countryName.")
return
}
if !phoneNumberE164.hasPrefix(callingCode) {
owsFailDebug("Could not resume re-registration; non-matching calling code.")
return
}
let phoneNumberWithoutCallingCode = phoneNumberE164.substring(from: callingCode.count)
guard countryCode.count > 0 else {
owsFailDebug("Invalid country code.")
return
}
guard countryName.count > 0 else {
owsFailDebug("Invalid country name.")
return
}
guard callingCode.count > 0 else {
owsFailDebug("Invalid calling code.")
return
}
let countryState = OnboardingCountryState(countryName: countryName, callingCode: callingCode, countryCode: countryCode)
onboardingController.update(countryState: countryState)
phoneNumberTextField.text = phoneNumberWithoutCallingCode
// Don't let user edit their phone number while re-registering.
phoneNumberTextField.isEnabled = false
updateViewState()
// Trigger the formatting logic with a no-op edit.
_ = textField(phoneNumberTextField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "")
}
// MARK: -
private var countryName: String {
get {
return onboardingController.countryState.countryName
}
}
private var callingCode: String {
get {
AssertIsOnMainThread()
return onboardingController.countryState.callingCode
}
}
private var countryCode: String {
get {
AssertIsOnMainThread()
return onboardingController.countryState.countryCode
}
}
private func populateDefaults() {
if let lastRegisteredPhoneNumber = OnboardingController.lastRegisteredPhoneNumber(),
lastRegisteredPhoneNumber.count > 0,
lastRegisteredPhoneNumber.hasPrefix(callingCode) {
phoneNumberTextField.text = lastRegisteredPhoneNumber.substring(from: callingCode.count)
} else if let phoneNumber = onboardingController.phoneNumber {
phoneNumberTextField.text = phoneNumber.userInput
}
updateViewState()
// Trigger the formatting logic with a no-op edit.
_ = textField(phoneNumberTextField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "")
}
private func updateViewState() {
AssertIsOnMainThread()
countryNameLabel.text = countryName
callingCodeLabel.text = callingCode
self.phoneNumberTextField.placeholder = ViewControllerUtils.examplePhoneNumber(forCountryCode: countryCode, callingCode: callingCode)
updateValidationWarnings()
}
private func updateValidationWarnings() {
AssertIsOnMainThread()
phoneStrokeNormal?.isHidden = isPhoneNumberInvalid
phoneStrokeError?.isHidden = !isPhoneNumberInvalid
validationWarningLabel.isHidden = !isPhoneNumberInvalid
}
// MARK: - Events
@objc func countryRowTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
showCountryPicker()
}
@objc func countryCodeTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
showCountryPicker()
}
@objc func nextPressed() {
Logger.info("")
parseAndTryToRegister()
}
// MARK: - Country Picker
private func showCountryPicker() {
guard !tsAccountManager.isReregistering() else {
return
}
let countryCodeController = CountryCodeViewController()
countryCodeController.countryCodeDelegate = self
countryCodeController.interfaceOrientationMask = .portrait
let navigationController = OWSNavigationController(rootViewController: countryCodeController)
self.present(navigationController, animated: true, completion: nil)
}
// MARK: - Register
private func parseAndTryToRegister() {
guard let phoneNumberText = phoneNumberTextField.text?.ows_stripped(),
phoneNumberText.count > 0 else {
isPhoneNumberInvalid = false
updateValidationWarnings()
OWSAlerts.showAlert(title:
NSLocalizedString("REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_TITLE",
comment: "Title of alert indicating that users needs to enter a phone number to register."),
message:
NSLocalizedString("REGISTRATION_VIEW_NO_PHONE_NUMBER_ALERT_MESSAGE",
comment: "Message of alert indicating that users needs to enter a phone number to register."))
return
}
let phoneNumber = "\(callingCode)\(phoneNumberText)"
guard let localNumber = PhoneNumber.tryParsePhoneNumber(fromUserSpecifiedText: phoneNumber),
localNumber.toE164().count > 0,
PhoneNumberValidator().isValidForRegistration(phoneNumber: localNumber) else {
isPhoneNumberInvalid = false
updateValidationWarnings()
OWSAlerts.showAlert(title:
NSLocalizedString("REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_TITLE",
comment: "Title of alert indicating that users needs to enter a valid phone number to register."),
message:
NSLocalizedString("REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_MESSAGE",
comment: "Message of alert indicating that users needs to enter a valid phone number to register."))
return
}
let e164PhoneNumber = localNumber.toE164()
onboardingController.update(phoneNumber: OnboardingPhoneNumber(e164: e164PhoneNumber, userInput: phoneNumberText))
if UIDevice.current.isIPad {
OWSAlerts.showConfirmationAlert(title: NSLocalizedString("REGISTRATION_IPAD_CONFIRM_TITLE",
comment: "alert title when registering an iPad"),
message: NSLocalizedString("REGISTRATION_IPAD_CONFIRM_BODY",
comment: "alert body when registering an iPad"),
proceedTitle: NSLocalizedString("REGISTRATION_IPAD_CONFIRM_BUTTON",
comment: "button text to proceed with registration when on an iPad"),
proceedAction: { (_) in
self.onboardingController.tryToRegister(fromViewController: self, smsVerification: true)
})
} else {
onboardingController.tryToRegister(fromViewController: self, smsVerification: true)
}
}
}
// MARK: -
extension OnboardingPhoneNumberViewController: UITextFieldDelegate {
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
ViewControllerUtils.phoneNumber(textField, shouldChangeCharactersIn: range, replacementString: string, callingCode: callingCode)
isPhoneNumberInvalid = false
updateValidationWarnings()
// Inform our caller that we took care of performing the change.
return false
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
parseAndTryToRegister()
return false
}
}
// MARK: -
extension OnboardingPhoneNumberViewController: CountryCodeViewControllerDelegate {
public func countryCodeViewController(_ vc: CountryCodeViewController, didSelectCountryCode countryCode: String, countryName: String, callingCode: String) {
guard countryCode.count > 0 else {
owsFailDebug("Invalid country code.")
return
}
guard countryName.count > 0 else {
owsFailDebug("Invalid country name.")
return
}
guard callingCode.count > 0 else {
owsFailDebug("Invalid calling code.")
return
}
let countryState = OnboardingCountryState(countryName: countryName, callingCode: callingCode, countryCode: countryCode)
onboardingController.update(countryState: countryState)
updateViewState()
// Trigger the formatting logic with a no-op edit.
_ = textField(phoneNumberTextField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "")
}
}

@ -30,10 +30,10 @@ public class OnboardingProfileViewController: OnboardingBaseViewController {
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_PROFILE_TITLE", comment: "Title of the 'onboarding profile' view."))
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_PROFILE_TITLE", comment: "Title of the 'onboarding profile' view."))
titleLabel.accessibilityIdentifier = "onboarding.profile." + "titleLabel"
let explanationLabel = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_PROFILE_EXPLANATION",
let explanationLabel = self.createExplanationLabel(text: NSLocalizedString("ONBOARDING_PROFILE_EXPLANATION",
comment: "Explanation in the 'onboarding profile' view."))
explanationLabel.accessibilityIdentifier = "onboarding.profile." + "explanationLabel"

@ -0,0 +1,73 @@
import UIKit
import PromiseKit
final class OnboardingPublicKeyViewController : OnboardingBaseViewController {
private var keyPair: ECKeyPair! { didSet { updateMnemonic() } }
private var hexEncodedPublicKey: String!
private var mnemonic: String! { didSet { mnemonicLabel.text = mnemonic } }
private lazy var mnemonicLabel: UILabel = {
let result = createExplanationLabel(text: "")
result.accessibilityIdentifier = "onboarding.publicKeyStep.mnemonicLabel"
result.alpha = 0.8
var fontTraits = result.font.fontDescriptor.symbolicTraits
fontTraits.insert(.traitItalic)
result.font = UIFont(descriptor: result.font.fontDescriptor.withSymbolicTraits(fontTraits)!, size: result.font.pointSize)
return result
}()
override public func viewDidLoad() {
super.loadView()
setUpViewHierarchy()
updateKeyPair()
}
private func setUpViewHierarchy() {
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = createTitleLabel(text: NSLocalizedString("Create Your Loki Messenger Account", comment: ""))
titleLabel.accessibilityIdentifier = "onboarding.publicKeyStep.titleLabel"
let topSpacer = UIView.vStretchingSpacer()
let explanationLabel = createExplanationLabel(text: NSLocalizedString("Please save the seed below in a safe location. They can be used to restore your account if you lose access or migrate to a new device.", comment: ""))
explanationLabel.accessibilityIdentifier = "onboarding.publicKeyStep.explanationLabel"
let bottomSpacer = UIView.vStretchingSpacer()
let registerButton = button(title: NSLocalizedString("Register", comment: ""), selector: #selector(register))
registerButton.accessibilityIdentifier = "onboarding.publicKeyStep.registerButton"
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
explanationLabel,
UIView.spacer(withHeight: 32),
mnemonicLabel,
bottomSpacer,
registerButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
}
private func updateKeyPair() {
let identityManager = OWSIdentityManager.shared()
identityManager.generateNewIdentityKey() // Generates and stores a new key pair
keyPair = identityManager.identityKeyPair()!
}
private func updateMnemonic() {
hexEncodedPublicKey = keyPair.publicKey.map { String(format: "%02hhx", $0) }.joined()
mnemonic = Mnemonic.encode(hexEncodedString: hexEncodedPublicKey)
}
@objc private func register() {
let accountManager = TSAccountManager.sharedInstance()
accountManager.phoneNumberAwaitingVerification = hexEncodedPublicKey
accountManager.didRegister()
onboardingController.verificationDidComplete(fromView: self)
}
}

@ -23,7 +23,7 @@ public class OnboardingSplashViewController: OnboardingBaseViewController {
heroImageView.setContentHuggingVerticalLow()
heroImageView.accessibilityIdentifier = "onboarding.splash." + "heroImageView"
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_SPLASH_TITLE", comment: "Title of the 'onboarding splash' view."))
let titleLabel = self.createTitleLabel(text: NSLocalizedString("ONBOARDING_SPLASH_TITLE", comment: "Title of the 'onboarding splash' view."))
view.addSubview(titleLabel)
titleLabel.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
titleLabel.accessibilityIdentifier = "onboarding.splash." + "titleLabel"

@ -275,7 +275,7 @@ public class OnboardingVerificationViewController: OnboardingBaseViewController
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
let titleLabel = self.titleLabel(text: "")
let titleLabel = self.createTitleLabel(text: "")
self.titleLabel = titleLabel
titleLabel.accessibilityIdentifier = "onboarding.verification." + "titleLabel"

@ -0,0 +1,74 @@
import CryptoSwift
enum Mnemonic {
struct Language : Hashable {
let filename: String
let prefixLength: Int
static let english = Language(filename: "english", prefixLength: 3)
static let japanese = Language(filename: "japanese", prefixLength: 3)
static let portuguese = Language(filename: "portuguese", prefixLength: 4)
static let spanish = Language(filename: "spanish", prefixLength: 4)
private static var wordSetCache: [Language:[String]] = [:]
private init(filename: String, prefixLength: Int) {
self.filename = filename
self.prefixLength = prefixLength
}
func loadWordSet() -> [String] {
if let cachedResult = Language.wordSetCache[self] {
return cachedResult
} else {
let url = Bundle.main.url(forResource: filename, withExtension: "txt")!
let contents = try! String(contentsOf: url)
let result = contents.split(separator: ",").map { String($0) }
Language.wordSetCache[self] = result
return result
}
}
}
/// Based on [mnemonic.js](https://github.com/loki-project/loki-messenger/blob/development/libloki/modules/mnemonic.js) .
static func encode(hexEncodedString string: String, language: Language = .english) -> String {
var string = string
let wordSet = language.loadWordSet()
let prefixLength = language.prefixLength
var result: [String] = []
let wordCount = wordSet.count
let characterCount = string.indices.count // Safe for this particular case
for chunkStartIndexAsInt in stride(from: 0, to: characterCount, by: 8) {
let chunkStartIndex = string.index(string.startIndex, offsetBy: chunkStartIndexAsInt)
let chunkEndIndex = string.index(chunkStartIndex, offsetBy: 8)
func swap(_ chunk: String) -> String {
func toStringIndex(_ indexAsInt: Int) -> String.Index {
return chunk.index(chunk.startIndex, offsetBy: indexAsInt)
}
let p1 = chunk[toStringIndex(6)..<toStringIndex(8)]
let p2 = chunk[toStringIndex(4)..<toStringIndex(6)]
let p3 = chunk[toStringIndex(2)..<toStringIndex(4)]
let p4 = chunk[toStringIndex(0)..<toStringIndex(2)]
return String(p1 + p2 + p3 + p4)
}
let p1 = string[string.startIndex..<chunkStartIndex]
let p2 = swap(String(string[chunkStartIndex..<chunkEndIndex]))
let p3 = string[chunkEndIndex..<string.endIndex]
string = String(p1 + p2 + p3)
}
for chunkStartIndexAsInt in stride(from: 0, to: characterCount, by: 8) {
let chunkStartIndex = string.index(string.startIndex, offsetBy: chunkStartIndexAsInt)
let chunkEndIndex = string.index(chunkStartIndex, offsetBy: 8)
let x = Int(string[chunkStartIndex..<chunkEndIndex], radix: 16)!
let w1 = x % wordCount
let w2 = ((x / wordCount) + w1) % wordCount
let w3 = (((x / wordCount) / wordCount) + w2) % wordCount
result += [ wordSet[w1], wordSet[w2], wordSet[w3] ]
}
let checksum = Array(result.map { String($0[$0.startIndex..<$0.index($0.startIndex, offsetBy: prefixLength)]) }.joined().utf8).crc32()
let checksumIndex = Int(checksum) % result.count
result.append(result[checksumIndex])
return result.joined(separator: " ")
}
}

@ -1,2 +1,2 @@
github "TheLevelUp/ZXingObjC"
github "krzyzanowskim/CryptoSwift"

@ -1 +1,2 @@
github "TheLevelUp/ZXingObjC" "3.2.2"
github "TheLevelUp/ZXingObjC" "3.6.4"
github "krzyzanowskim/CryptoSwift" "1.0.0"

Loading…
Cancel
Save