Sketch out the 'onboarding phone number' view.

This commit is contained in:
Matthew Chen 2019-02-13 16:18:00 -05:00
parent f25e54f58b
commit 2a4b9426c3
21 changed files with 684 additions and 25 deletions

View File

@ -76,6 +76,7 @@
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 */; };
344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; };
345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */; };
3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; };
@ -739,6 +740,7 @@
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>"; };
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>"; };
345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FASettingsViewController.h; sourceTree = "<group>"; };
@ -1466,6 +1468,7 @@
3448E1612213585C004B052E /* OnboardingBaseViewController.swift */,
3448E15D221333F5004B052E /* OnboardingController.swift */,
3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */,
3448E16322135FFA004B052E /* OnboardingPhoneNumberViewController.swift */,
3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */,
346E9D5321B040B600562252 /* RegistrationController.swift */,
340FC878204DAC8C007AEB0F /* RegistrationViewController.h */,
@ -3542,6 +3545,7 @@
4556FA681F54AA9500AF40DD /* DebugUIProfile.swift in Sources */,
45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */,
34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */,
3448E16422135FFA004B052E /* OnboardingPhoneNumberViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */,
45D2AC02204885170033C692 /* OWS2FAReminderViewController.swift in Sources */,

View File

@ -19,17 +19,6 @@ public class AccountManager: NSObject {
return OWSProfileManager.shared()
}
// MARK: -
@objc
public override init() {
super.init()
SwiftSingletons.register(self)
}
// MARK: - Dependencies
private var networkManager: TSNetworkManager {
return SSKEnvironment.shared.networkManager
}
@ -42,6 +31,15 @@ public class AccountManager: NSObject {
return TSAccountManager.sharedInstance()
}
// MARK: -
@objc
public override init() {
super.init()
SwiftSingletons.register(self)
}
// MARK: registration
@objc func registerObjc(verificationCode: String,

View File

@ -482,6 +482,17 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
[self.searchResultsController viewDidAppear:animated];
self.hasEverAppeared = YES;
dispatch_async(dispatch_get_main_queue(), ^{
id<OnboardingController> onboardingController = [OnboardingControllerImpl new];
OnboardingPhoneNumberViewController *view =
[[OnboardingPhoneNumberViewController alloc] initWithOnboardingController:onboardingController];
// OnboardingPermissionsViewController *view =
// [[OnboardingPermissionsViewController alloc] initWithOnboardingController:onboardingController];
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:view];
[self presentViewController:navigationController animated:YES completion:nil];
});
}
- (void)viewDidDisappear:(BOOL)animated

View File

@ -15,6 +15,8 @@ public class OnboardingBaseViewController: OWSViewController {
self.onboardingController = onboardingController
super.init(nibName: nil, bundle: nil)
self.shouldUseTheme = false
}
@available(*, unavailable, message: "use other init() instead.")
@ -54,7 +56,7 @@ public class OnboardingBaseViewController: OWSViewController {
return explanationLabel
}
func button(title: String, selector: Selector) -> UIView {
func button(title: String, selector: Selector) -> OWSFlatButton {
// TODO: Make sure this all fits if dynamic font sizes are maxed out.
let buttonHeight: CGFloat = 48
let button = OWSFlatButton.button(title: title,
@ -72,4 +74,8 @@ public class OnboardingBaseViewController: OWSViewController {
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
public override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}

View File

@ -12,6 +12,8 @@ public protocol OnboardingController: class {
func onboardingPermissionsWasSkipped(viewController: UIViewController)
func onboardingPermissionsDidComplete(viewController: UIViewController)
func onboardingPhoneNumberDidComplete(viewController: UIViewController)
}
// MARK: -
@ -28,7 +30,21 @@ public class OnboardingControllerImpl: NSObject, OnboardingController {
viewController.navigationController?.pushViewController(view, animated: true)
}
public func onboardingPermissionsWasSkipped(viewController: UIViewController) {}
public func onboardingPermissionsWasSkipped(viewController: UIViewController) {
pushPhoneNumberView(viewController: viewController)
}
public func onboardingPermissionsDidComplete(viewController: UIViewController) {}
public func onboardingPermissionsDidComplete(viewController: UIViewController) {
pushPhoneNumberView(viewController: viewController)
}
private func pushPhoneNumberView(viewController: UIViewController) {
let view = OnboardingPhoneNumberViewController(onboardingController: self)
viewController.navigationController?.pushViewController(view, animated: true)
}
public func onboardingPhoneNumberDidComplete(viewController: UIViewController) {
// CodeVerificationViewController *vc = [CodeVerificationViewController new];
// [weakSelf.navigationController pushViewController:vc animated:YES];
}
}

View File

@ -0,0 +1,514 @@
//
// 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?
// - (void)didTapLegalTerms:(UIButton *)sender
//{
// [[UIApplication sharedApplication] openURL:[NSURL URLWithString:kLegalTermsUrlString]];
// }
//#pragma mark - Keyboard notifications
//
//- (void)initializeKeyboardHandlers
//{
// UITapGestureRecognizer *outsideTabRecognizer =
// [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboardFromAppropriateSubView)];
// [self.view addGestureRecognizer:outsideTabRecognizer];
// }
//
// - (void)dismissKeyboardFromAppropriateSubView
// {
// [self.view endEditing:NO];
//}
//
override public func loadView() {
super.loadView()
// TODO: Is this still necessary?
if let navigationController = self.navigationController as? OWSNavigationController {
SignalApp.shared().signUpFlowNavigationController = navigationController
} else {
owsFailDebug("Missing or invalid navigationController")
}
populateDefaults()
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
// TODO:
// navigationItem.title = NSLocalizedString("SETTINGS_BACKUP", comment: "Label for the backup view in app settings.")
let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_PHONE_NUMBER_TITLE", comment: "Title of the 'onboarding phone number' view."))
// Country
// TODO: dynamic
let fontSizePoints: CGFloat = ScaleFromIPhone5To7Plus(16, 20)
let rowHeight: CGFloat = 40
countryNameLabel.textColor = Theme.primaryColor
countryNameLabel.font = UIFont.ows_dynamicTypeBody
countryNameLabel.setContentHuggingHorizontalLow()
countryNameLabel.setCompressionResistanceHorizontalLow()
let countryIcon = UIImage(named: (CurrentAppContext().isRTL
? "small_chevron_left"
: "small_chevron_right"))
// NavBarBackRTL
// small_chevron_right
// system_disclosure_indicator_rtl
let countryImageView = UIImageView(image: countryIcon?.withRenderingMode(.alwaysTemplate))
countryImageView.tintColor = Theme.placeholderColor
countryImageView.setContentHuggingHigh()
countryImageView.setCompressionResistanceHigh()
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)
addBottomStroke(countryRow)
callingCodeLabel.textColor = Theme.primaryColor
callingCodeLabel.font = UIFont.ows_dynamicTypeBody
callingCodeLabel.setContentHuggingHorizontalHigh()
callingCodeLabel.setCompressionResistanceHorizontalHigh()
callingCodeLabel.isUserInteractionEnabled = true
callingCodeLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(countryCodeTapped)))
addBottomStroke(callingCodeLabel)
callingCodeLabel.autoSetDimension(.width, toSize: rowHeight, relation: .greaterThanOrEqual)
phoneNumberTextField.textAlignment = .left
phoneNumberTextField.delegate = self
phoneNumberTextField.keyboardType = .numberPad
phoneNumberTextField.textColor = Theme.primaryColor
phoneNumberTextField.font = UIFont.ows_dynamicTypeBody
phoneNumberTextField.setContentHuggingHorizontalLow()
phoneNumberTextField.setCompressionResistanceHorizontalLow()
addBottomStroke(phoneNumberTextField)
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)
// TODO: Finalize copy.
let nextButton = self.button(title: NSLocalizedString("BUTTON_NEXT",
comment: "Label for the 'next' button."),
selector: #selector(nextPressed))
self.nextButton = nextButton
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
countryRow,
UIView.spacer(withHeight: 8),
phoneNumberRow,
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.autoPinWidthToSuperviewMargins()
stackView.autoPinWidthToSuperviewMargins()
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)
}
private func addBottomStroke(_ view: UIView) {
let strokeView = UIView()
strokeView.backgroundColor = Theme.middleGrayColor
view.addSubview(strokeView)
strokeView.autoSetDimension(.height, toSize: CGHairlineWidth())
strokeView.autoPinWidthToSuperview()
strokeView.autoPinEdge(toSuperviewEdge: .bottom)
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = false
phoneNumberTextField.becomeFirstResponder()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.isNavigationBarHidden = false
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 callingCode = parsedPhoneNumber.getCountryCode() else {
owsFailDebug("Could not resume re-registration; missing callingCode.")
return
}
let callingCodeText = "\(COUNTRY_CODE_PREFIX)\(callingCode)"
let countryCodes: [String] =
PhoneNumberUtil.sharedThreadLocal().countryCodes(fromCallingCode: callingCodeText)
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(callingCodeText) {
owsFailDebug("Could not resume re-registration; non-matching calling code.")
return
}
let phoneNumberWithoutCallingCode = phoneNumberE164.substring(from: callingCodeText.count)
update(withCountryName: countryName, callingCode: callingCodeText, countryCode: countryCode)
phoneNumberTextField.text = phoneNumberWithoutCallingCode
// Don't let user edit their phone number while re-registering.
phoneNumberTextField.isEnabled = false
}
// MARK: -
private var countryName = ""
private var callingCode = ""
private var countryCode = ""
private func populateDefaults() {
var countryCode: String = PhoneNumber.defaultCountryCode()
if let lastRegisteredCountryCode = self.lastRegisteredCountryCode(),
lastRegisteredCountryCode.count > 0 {
countryCode = lastRegisteredCountryCode
}
let callingCodeNumber: NSNumber = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(forRegion: countryCode)
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumber)"
if let lastRegisteredPhoneNumber = self.lastRegisteredPhoneNumber(),
lastRegisteredPhoneNumber.count > 0,
lastRegisteredPhoneNumber.hasPrefix(callingCode) {
phoneNumberTextField.text = lastRegisteredPhoneNumber.substring(from: callingCode.count)
}
var countryName = NSLocalizedString("UNKNOWN_COUNTRY_NAME", comment: "Label for unknown countries.")
if let countryNameDerived = PhoneNumberUtil.countryName(fromCountryCode: countryCode) {
countryName = countryNameDerived
}
update(withCountryName: countryName, callingCode: callingCode, countryCode: countryCode)
}
private func update(withCountryName countryName: String, callingCode: String, countryCode: String) {
AssertIsOnMainThread()
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
}
self.countryName = countryName
self.callingCode = callingCode
self.countryCode = countryCode
countryNameLabel.text = countryName
callingCodeLabel.text = callingCode
self.phoneNumberTextField.placeholder = ViewControllerUtils.examplePhoneNumber(forCountryCode: countryCode, callingCode: callingCode)
}
// MARK: - Debug
private let kKeychainService_LastRegistered = "kKeychainService_LastRegistered"
private let kKeychainKey_LastRegisteredCountryCode = "kKeychainKey_LastRegisteredCountryCode"
private let kKeychainKey_LastRegisteredPhoneNumber = "kKeychainKey_LastRegisteredPhoneNumber"
private func debugValue(forKey key: String) -> String? {
guard CurrentAppContext().isDebugBuild() else {
return nil
}
do {
let value = try CurrentAppContext().keychainStorage().string(forService: kKeychainService_LastRegistered, key: key)
return value
} catch {
owsFailDebug("Error: \(error)")
return nil
}
}
private func setDebugValue(_ value: String, forKey key: String) {
guard CurrentAppContext().isDebugBuild() else {
return
}
do {
try CurrentAppContext().keychainStorage().set(string: value, service: kKeychainService_LastRegistered, key: key)
} catch {
owsFailDebug("Error: \(error)")
}
}
private func lastRegisteredCountryCode() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredCountryCode)
}
private func setLastRegisteredCountryCode(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredCountryCode)
}
private func lastRegisteredPhoneNumber() -> String? {
return debugValue(forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
private func setLastRegisteredPhoneNumber(value: String) {
setDebugValue(value, forKey: kKeychainKey_LastRegisteredPhoneNumber)
}
// MARK: - Events
@objc func explanationLabelTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
// TODO:
}
@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("")
onboardingController.onboardingPhoneNumberDidComplete(viewController: self)
}
// 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 didTapRegisterButton() {
guard let phoneNumberText = phoneNumberTextField.text?.ows_stripped(),
phoneNumberText.count > 0 else {
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 {
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 parsedPhoneNumber = localNumber.toE164()
if UIDevice.current.isIPad {
let countryCode = self.countryCode
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.sendCode(parsedPhoneNumber: parsedPhoneNumber,
phoneNumberText: phoneNumberText,
countryCode: countryCode)
})
} else {
sendCode(parsedPhoneNumber: parsedPhoneNumber,
phoneNumberText: phoneNumberText,
countryCode: countryCode)
}
}
private func sendCode(parsedPhoneNumber: String,
phoneNumberText: String,
countryCode: String) {
ModalActivityIndicatorViewController.present(fromViewController: self,
canCancel: true) { (modal) in
self.setLastRegisteredCountryCode(value: countryCode)
self.setLastRegisteredPhoneNumber(value: phoneNumberText)
self.tsAccountManager.register(withPhoneNumber: parsedPhoneNumber,
success: {
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationSucceeded()
})
}
}, failure: { (error) in
Logger.error("Error: \(error)")
DispatchQueue.main.async {
modal.dismiss(completion: {
self.registrationFailed(error: error as NSError)
})
}
}, smsVerification: true)
}
}
private func registrationSucceeded() {
self.onboardingController.onboardingPhoneNumberDidComplete(viewController: self)
}
private func registrationFailed(error: NSError) {
if error.code == 400 {
OWSAlerts.showAlert(title: NSLocalizedString("REGISTRATION_ERROR", comment: ""),
message: NSLocalizedString("REGISTRATION_NON_VALID_NUMBER", comment: ""))
} else {
OWSAlerts.showAlert(title: error.localizedDescription,
message: error.localizedRecoverySuggestion)
}
phoneNumberTextField.becomeFirstResponder()
}
}
// MARK: -
extension OnboardingPhoneNumberViewController: UITextFieldDelegate {
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var prefix = self.callingCode
if prefix.hasPrefix(COUNTRY_CODE_PREFIX) {
prefix = prefix.substring(from: COUNTRY_CODE_PREFIX.count)
}
ViewControllerUtils.phoneNumber(textField, shouldChangeCharactersIn: range, replacementString: string, countryCode: countryCode, prefix: prefix)
// Inform our caller that we took care of performing the change.
return false
}
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
didTapRegisterButton()
textField.resignFirstResponder()
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
}
update(withCountryName: countryName, callingCode: callingCode, countryCode: countryCode)
// Trigger the formatting logic with a no-op edit.
_ = textField(phoneNumberTextField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "")
}
}

View File

@ -314,6 +314,15 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
return [[NSUserDefaults alloc] initWithSuiteName:SignalApplicationGroup];
}
- (BOOL)isDebugBuild
{
#ifdef DEBUG
return YES;
#else
return NO;
#endif
}
@end
NS_ASSUME_NONNULL_END

View File

@ -302,6 +302,9 @@
/* Label for generic done button. */
"BUTTON_DONE" = "Done";
/* Label for the 'next' button. */
"BUTTON_NEXT" = "Next";
/* Label for redo button. */
"BUTTON_REDO" = "Redo";
@ -1520,6 +1523,9 @@
/* Title of the 'onboarding permissions' view. */
"ONBOARDING_PERMISSIONS_TITLE" = "We need access to your contacts and notifications";
/* Title of the 'onboarding phone number' view. */
"ONBOARDING_PHONE_NUMBER_TITLE" = "Enter your phone number to get started";
/* Explanation in the 'onboarding splash' view. */
"ONBOARDING_SPLASH_EXPLANATION" = "By continuing, you agree to Signal's terms.";
@ -2354,6 +2360,9 @@
/* Displayed if for some reason we can't determine a contacts phone number *or* name */
"UNKNOWN_CONTACT_NAME" = "Unknown Contact";
/* Label for unknown countries. */
"UNKNOWN_COUNTRY_NAME" = "Unknown Country";
/* Indicates an unknown or unrecognizable value. */
"UNKNOWN_VALUE" = "Unknown";

View File

@ -4,6 +4,8 @@
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@class CountryCodeViewController;
@protocol CountryCodeViewControllerDelegate <NSObject>
@ -26,3 +28,5 @@
@property (nonatomic) UIInterfaceOrientationMask interfaceOrientationMask;
@end
NS_ASSUME_NONNULL_END

View File

@ -11,6 +11,8 @@
#import "UIView+OWS.h"
#import <SignalServiceKit/NSString+SSK.h>
NS_ASSUME_NONNULL_BEGIN
@interface CountryCodeViewController () <OWSTableViewControllerDelegate, UISearchBarDelegate>
@property (nonatomic, readonly) UISearchBar *searchBar;
@ -170,3 +172,5 @@
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@ -22,6 +22,13 @@ extern NSString *const TappedStatusBarNotification;
replacementString:(NSString *)insertionText
countryCode:(NSString *)countryCode;
// The same method, but it temporarily adds a prefix during the formatting process.
+ (void)phoneNumberTextField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
countryCode:(NSString *)countryCode
prefix:(nullable NSString *)prefix;
+ (void)ows2FAPINTextField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText;

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "ViewControllerUtils.h"
@ -23,7 +23,19 @@ const NSUInteger kMax2FAPinLength = 16;
replacementString:(NSString *)insertionText
countryCode:(NSString *)countryCode
{
return [self phoneNumberTextField:textField
shouldChangeCharactersInRange:range
replacementString:insertionText
countryCode:countryCode
prefix:nil];
}
+ (void)phoneNumberTextField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
countryCode:(NSString *)countryCode
prefix:(nullable NSString *)prefix
{
// Phone numbers takes many forms.
//
// * We only want to let the user enter decimal digits.
@ -40,6 +52,7 @@ const NSUInteger kMax2FAPinLength = 16;
// * Take partial input if possible.
NSString *oldText = textField.text;
// Construct the new contents of the text field by:
// 1. Determining the "left" substring: the contents of the old text _before_ the deletion range.
// Filtering will remove non-decimal digit characters like hyphen "-".
@ -76,6 +89,12 @@ const NSUInteger kMax2FAPinLength = 16;
// 5. Construct the "formatted" new text by inserting a hyphen if necessary.
// reformat the phone number, trying to keep the cursor beside the inserted or deleted digit
NSUInteger cursorPositionAfterChange = MIN(left.length + center.length, textAfterChange.length);
// if (prefix.length > 0) {
// textAfterChange = [prefix stringByAppendingString:textAfterChange];
// cursorPositionAfterChange += prefix.length;
// }
NSString *textAfterReformat =
[PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:textAfterChange
withSpecifiedCountryCodeString:countryCode];
@ -83,6 +102,23 @@ const NSUInteger kMax2FAPinLength = 16;
from:textAfterChange
to:textAfterReformat
stickingRightward:isJustDeletion];
// if (prefix.length > 0) {
// OWSAssertDebug([textAfterReformat hasPrefix:prefix]);
// if ([textAfterReformat hasPrefix:prefix]) {
// textAfterReformat = [textAfterReformat substringFromIndex:prefix.length];
// cursorPositionAfterReformat -= prefix.length;
//
// NSRange trimRange = [textAfterReformat
// rangeOfCharacterFromSet:NSCharacterSet.whitespaceCharacterSet.invertedSet]; if (trimRange.location >
// 0) {
// textAfterReformat = [textAfterReformat
// stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
// cursorPositionAfterReformat -= trimRange.location;
// }
// }
// }
textField.text = textAfterReformat;
UITextPosition *pos =
[textField positionFromPosition:textField.beginningOfDocument offset:(NSInteger)cursorPositionAfterReformat];

View File

@ -87,6 +87,22 @@ extension UIView {
return view
}
@objc
public class func hStretchingSpacer() -> UIView {
let view = UIView()
view.setContentHuggingHorizontalLow()
view.setCompressionResistanceHorizontalLow()
return view
}
@objc
public class func vStretchingSpacer() -> UIView {
let view = UIView()
view.setContentHuggingVerticalLow()
view.setCompressionResistanceVerticalLow()
return view
}
@objc
public func applyScaleAspectFitLayout(subview: UIView, aspectRatio: CGFloat) -> [NSLayoutConstraint] {
guard subviews.contains(subview) else {

View File

@ -146,7 +146,7 @@ typedef NS_ENUM(NSUInteger, OWSRegistrationState) {
// Returns YES on success.
- (BOOL)resetForReregistration;
- (NSString *)reregisterationPhoneNumber;
- (nullable NSString *)reregisterationPhoneNumber;
- (BOOL)isReregistering;
#pragma mark - Manual Message Fetch

View File

@ -625,7 +625,7 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
}
}
- (NSString *)reregisterationPhoneNumber
- (nullable NSString *)reregisterationPhoneNumber
{
OWSAssertDebug([self isReregistering]);

View File

@ -18,12 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
+ (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString;
+ (NSString *)callingCodeFromCountryCode:(NSString *)countryCode;
+ (NSString *)countryNameFromCountryCode:(NSString *)countryCode;
+ (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode;
+ (NSArray *)countryCodesForSearchTerm:(nullable NSString *)searchTerm;
// Returns a list of country codes for a calling code in descending
// order of population.
- (NSArray *)countryCodesFromCallingCode:(NSString *)callingCode;
- (NSArray<NSString *> *)countryCodesFromCallingCode:(NSString *)callingCode;
// Returns the most likely country code for a calling code based on population.
- (NSString *)probableCountryCodeForCallingCode:(NSString *)callingCode;

View File

@ -82,7 +82,8 @@ NS_ASSUME_NONNULL_BEGIN
}
// country code -> country name
+ (NSString *)countryNameFromCountryCode:(NSString *)countryCode {
+ (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode
{
OWSAssertDebug(countryCode);
NSDictionary *countryCodeComponent = @{NSLocaleCountryCode : countryCode};

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "TestAppContext.h"
@ -148,6 +148,15 @@ NS_ASSUME_NONNULL_BEGIN
return self.mockAppSharedDataDirectoryPath;
}
- (BOOL)isDebugBuild
{
#ifdef DEBUG
return YES;
#else
return NO;
#endif
}
@end
#endif

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@ -102,6 +102,8 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
- (NSUserDefaults *)appUserDefaults;
- (BOOL)isDebugBuild;
@end
id<AppContext> CurrentAppContext(void);

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -8,4 +8,8 @@ public extension String {
func rtlSafeAppend(_ string: String) -> String {
return (self as NSString).rtlSafeAppend(string)
}
public func substring(from index: Int) -> String {
return String(self[self.index(self.startIndex, offsetBy: index)...])
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "ShareAppExtensionContext.h"
@ -234,6 +234,15 @@ NS_ASSUME_NONNULL_BEGIN
return [[NSUserDefaults alloc] initWithSuiteName:SignalApplicationGroup];
}
- (BOOL)isDebugBuild
{
#ifdef DEBUG
return YES;
#else
return NO;
#endif
}
@end
NS_ASSUME_NONNULL_END