From 10bc7ceb62a21d0a0ae58540eda95d2be7d46da3 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 22 Jul 2024 16:08:06 -0600 Subject: [PATCH 01/84] Use secondary action to move to the email view instead of switch --- Simplenote/AuthViewController+Swift.swift | 17 +++++++ Simplenote/AuthViewController.h | 4 ++ Simplenote/AuthViewController.xib | 61 ++++++++++++++++++++--- Simplenote/AuthenticationMode.swift | 19 ++++--- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index f75ca3f38..38a379564 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -30,6 +30,17 @@ extension AuthViewController { wordPressSSOButton.image = NSImage(named: .wordPressLogo)?.tinted(with: .simplenoteBrandColor) wordPressSSOButton.title = Localization.dotcomSSOAction wordPressSSOButton.contentTintColor = .simplenoteTertiaryTextColor + + setupActionsSeparatorView() + } + + private func setupActionsSeparatorView() { + leadingSeparatorView.wantsLayer = true + leadingSeparatorView.layer?.backgroundColor = NSColor.lightGray.cgColor + trailingSeparatorView.wantsLayer = true + trailingSeparatorView.layer?.backgroundColor = NSColor.lightGray.cgColor + + separatorLabel.textColor = .lightGray } } @@ -103,6 +114,7 @@ extension AuthViewController { wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha switchAuthenticationView.isHidden = !mode.isSwitchVisible + actionsSeparatorView.isHidden = !mode.showActionSeparator } /// Animates Visible / Invisible components, based on the specified state @@ -181,6 +193,11 @@ extension AuthViewController { return nextVC } + + @objc + func pushEmailLoginView() { + containingNavigationController?.push(nextViewController()) + } } diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 5d85ff413..83c4990cd 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -22,6 +22,10 @@ @property (nonatomic, strong) IBOutlet NSView *wordPressSSOContainerView; @property (nonatomic, strong) IBOutlet NSButton *wordPressSSOButton; @property (weak) IBOutlet NSView *switchAuthenticationView; +@property (weak) IBOutlet NSView *actionsSeparatorView; +@property (weak) IBOutlet NSView *leadingSeparatorView; +@property (weak) IBOutlet NSTextField *separatorLabel; +@property (weak) IBOutlet NSView *trailingSeparatorView; @property (nonatomic, strong) IBOutlet NSLayoutConstraint *passwordFieldHeightConstraint; @property (nonatomic, strong) IBOutlet NSLayoutConstraint *secondaryActionHeightConstraint; diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index d77d84527..687c4025b 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -10,16 +10,20 @@ + + + + @@ -30,13 +34,13 @@ - + - + - + @@ -44,7 +48,7 @@ - + @@ -55,13 +59,13 @@ - + - + @@ -70,7 +74,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -184,12 +224,15 @@ + + + @@ -200,6 +243,7 @@ + @@ -210,6 +254,7 @@ + diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 0ae05869a..e8371a251 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -19,8 +19,9 @@ class AuthenticationMode: NSObject { let isSecondaryActionVisible: Bool let isWordPressVisible: Bool let isSwitchVisible: Bool + let showActionSeparator: Bool - init(primaryActionText: String, primaryActionAnimationText: String, primaryActionSelector: Selector, secondaryActionText: String?, secondaryActionSelector: Selector?, switchActionText: String, switchActionTip: String, switchTargetMode: @escaping () -> AuthenticationMode, isPasswordVisible: Bool, isSecondaryActionVisible: Bool, isWordPressVisible: Bool, isSwitchVisible: Bool) { + init(primaryActionText: String, primaryActionAnimationText: String, primaryActionSelector: Selector, secondaryActionText: String?, secondaryActionSelector: Selector?, switchActionText: String, switchActionTip: String, switchTargetMode: @escaping () -> AuthenticationMode, isPasswordVisible: Bool, isSecondaryActionVisible: Bool, isWordPressVisible: Bool, isSwitchVisible: Bool, showActionSeparator: Bool) { self.primaryActionText = primaryActionText self.primaryActionAnimationText = primaryActionAnimationText self.primaryActionSelector = primaryActionSelector @@ -33,6 +34,7 @@ class AuthenticationMode: NSObject { self.isSecondaryActionVisible = isSecondaryActionVisible self.isWordPressVisible = isWordPressVisible self.isSwitchVisible = isSwitchVisible + self.showActionSeparator = showActionSeparator } } @@ -90,7 +92,8 @@ extension AuthenticationMode { isPasswordVisible: true, isSecondaryActionVisible: true, isWordPressVisible: true, - isSwitchVisible: false) + isSwitchVisible: false, + showActionSeparator: true) } /// Auth Mode: Login is handled via Magic Links! @@ -108,7 +111,8 @@ extension AuthenticationMode { isPasswordVisible: false, isSecondaryActionVisible: true, isWordPressVisible: true, - isSwitchVisible: false) + isSwitchVisible: false, + showActionSeparator: true) } /// Auth Mode: SignUp @@ -118,15 +122,16 @@ extension AuthenticationMode { AuthenticationMode(primaryActionText: SignupStrings.primaryAction, primaryActionAnimationText: SignupStrings.primaryAnimationText, primaryActionSelector: #selector(AuthViewController.pressedSignUp), - secondaryActionText: nil, - secondaryActionSelector: nil, + secondaryActionText: SignupStrings.switchAction, + secondaryActionSelector: #selector(AuthViewController.pushEmailLoginView), switchActionText: SignupStrings.switchAction, switchActionTip: SignupStrings.switchTip, switchTargetMode: { .loginWithMagicLink }, isPasswordVisible: false, - isSecondaryActionVisible: false, + isSecondaryActionVisible: true, isWordPressVisible: false, - isSwitchVisible: true) + isSwitchVisible: false, + showActionSeparator: false) } } From 6fa0e2ccaf6f3c60e63764d1ed7765d58d20c97c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 22 Jul 2024 16:18:34 -0600 Subject: [PATCH 02/84] Removed the switch mode button --- Simplenote/AuthViewController+Swift.swift | 14 ----- Simplenote/AuthViewController.h | 3 -- Simplenote/AuthViewController.m | 1 - Simplenote/AuthViewController.xib | 63 ++++------------------- Simplenote/AuthenticationMode.swift | 26 ++++------ 5 files changed, 19 insertions(+), 88 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 38a379564..c7a7551c4 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -20,12 +20,6 @@ extension AuthViewController { // Secondary Action secondaryActionButton.contentTintColor = .simplenoteBrandColor - // Toggle Signup: Tip - switchTipField.textColor = .simplenoteTertiaryTextColor - - // Toggle Signup: Action - switchActionButton.contentTintColor = .simplenoteBrandColor - // WordPress SSO wordPressSSOButton.image = NSImage(named: .wordPressLogo)?.tinted(with: .simplenoteBrandColor) wordPressSSOButton.title = Localization.dotcomSSOAction @@ -78,8 +72,6 @@ extension AuthViewController { func refreshButtonTitles() { actionButton.title = mode.primaryActionText secondaryActionButton.title = mode.secondaryActionText?.uppercased() ?? "" - switchTipField.stringValue = mode.switchActionTip.uppercased() - switchActionButton.title = mode.switchActionText.uppercased() } /// Makes sure unused components (in the current mode) are effectively disabled @@ -113,7 +105,6 @@ extension AuthViewController { secondaryActionButton.alphaValue = mode.secondaryActionFieldAlpha wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha - switchAuthenticationView.isHidden = !mode.isSwitchVisible actionsSeparatorView.isHidden = !mode.showActionSeparator } @@ -179,11 +170,6 @@ extension AuthViewController { performSelector(onMainThread: secondaryActionSelector, with: nil, waitUntilDone: false) } - @IBAction - func switchAuthenticationMode(_ sender: Any) { - containingNavigationController?.push(nextViewController()) - } - private func nextViewController() -> AuthViewController { let nextMode = mode.nextMode() diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 83c4990cd..6bb688721 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -17,11 +17,8 @@ @property (nonatomic, strong) IBOutlet NSButton *actionButton; @property (nonatomic, strong) IBOutlet NSProgressIndicator *actionProgress; @property (nonatomic, strong) IBOutlet NSButton *secondaryActionButton; -@property (nonatomic, strong) IBOutlet NSTextField *switchTipField; -@property (nonatomic, strong) IBOutlet NSButton *switchActionButton; @property (nonatomic, strong) IBOutlet NSView *wordPressSSOContainerView; @property (nonatomic, strong) IBOutlet NSButton *wordPressSSOButton; -@property (weak) IBOutlet NSView *switchAuthenticationView; @property (weak) IBOutlet NSView *actionsSeparatorView; @property (weak) IBOutlet NSView *leadingSeparatorView; @property (weak) IBOutlet NSTextField *separatorLabel; diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index f8fde38bd..acbc1eeed 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -81,7 +81,6 @@ - (void)setInterfaceEnabled:(BOOL)enabled { [self.passwordField setEnabled:enabled]; [self.actionButton setEnabled:enabled]; [self.secondaryActionButton setEnabled:enabled]; - [self.switchActionButton setEnabled:enabled]; [self.wordPressSSOButton setEnabled:enabled]; } diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index 687c4025b..9f71a8600 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -20,9 +20,6 @@ - - - @@ -34,13 +31,13 @@ - + - + - + @@ -48,7 +45,7 @@ - + @@ -59,13 +56,13 @@ - + - + @@ -74,7 +71,7 @@ - + - + @@ -151,46 +148,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -243,7 +200,6 @@ - @@ -254,7 +210,6 @@ - diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index e8371a251..395cc2189 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -11,29 +11,32 @@ class AuthenticationMode: NSObject { let secondaryActionText: String? let secondaryActionSelector: Selector? - let switchActionText: String - let switchActionTip: String let switchTargetMode: () -> AuthenticationMode let isPasswordVisible: Bool let isSecondaryActionVisible: Bool let isWordPressVisible: Bool - let isSwitchVisible: Bool let showActionSeparator: Bool - init(primaryActionText: String, primaryActionAnimationText: String, primaryActionSelector: Selector, secondaryActionText: String?, secondaryActionSelector: Selector?, switchActionText: String, switchActionTip: String, switchTargetMode: @escaping () -> AuthenticationMode, isPasswordVisible: Bool, isSecondaryActionVisible: Bool, isWordPressVisible: Bool, isSwitchVisible: Bool, showActionSeparator: Bool) { + init(primaryActionText: String, + primaryActionAnimationText: String, + primaryActionSelector: Selector, + secondaryActionText: String?, + secondaryActionSelector: Selector?, + switchTargetMode: @escaping () -> AuthenticationMode, + isPasswordVisible: Bool, + isSecondaryActionVisible: Bool, + isWordPressVisible: Bool, + showActionSeparator: Bool) { self.primaryActionText = primaryActionText self.primaryActionAnimationText = primaryActionAnimationText self.primaryActionSelector = primaryActionSelector self.secondaryActionText = secondaryActionText self.secondaryActionSelector = secondaryActionSelector - self.switchActionText = switchActionText - self.switchActionTip = switchActionTip self.switchTargetMode = switchTargetMode self.isPasswordVisible = isPasswordVisible self.isSecondaryActionVisible = isSecondaryActionVisible self.isWordPressVisible = isWordPressVisible - self.isSwitchVisible = isSwitchVisible self.showActionSeparator = showActionSeparator } } @@ -86,13 +89,10 @@ extension AuthenticationMode { primaryActionSelector: #selector(AuthViewController.pressedLogInWithPassword), secondaryActionText: LoginStrings.secondaryAction, secondaryActionSelector: #selector(AuthViewController.openForgotPasswordURL), - switchActionText: LoginStrings.switchAction, - switchActionTip: LoginStrings.switchTip, switchTargetMode: { .signup }, isPasswordVisible: true, isSecondaryActionVisible: true, isWordPressVisible: true, - isSwitchVisible: false, showActionSeparator: true) } @@ -105,13 +105,10 @@ extension AuthenticationMode { primaryActionSelector: #selector(AuthViewController.pressedLoginWithMagicLink), secondaryActionText: MagicLinkStrings.secondaryAction, secondaryActionSelector: #selector(AuthViewController.switchToPasswordAuth), - switchActionText: MagicLinkStrings.switchAction, - switchActionTip: MagicLinkStrings.switchTip, switchTargetMode: { .signup }, isPasswordVisible: false, isSecondaryActionVisible: true, isWordPressVisible: true, - isSwitchVisible: false, showActionSeparator: true) } @@ -124,13 +121,10 @@ extension AuthenticationMode { primaryActionSelector: #selector(AuthViewController.pressedSignUp), secondaryActionText: SignupStrings.switchAction, secondaryActionSelector: #selector(AuthViewController.pushEmailLoginView), - switchActionText: SignupStrings.switchAction, - switchActionTip: SignupStrings.switchTip, switchTargetMode: { .loginWithMagicLink }, isPasswordVisible: false, isSecondaryActionVisible: true, isWordPressVisible: false, - isSwitchVisible: false, showActionSeparator: false) } } From a670f8e72d1e5328e3360ae9380f42e4c311c672 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Mon, 22 Jul 2024 16:35:30 -0600 Subject: [PATCH 03/84] Redesign wordpress sso button --- Simplenote/AuthViewController+Swift.swift | 6 +++-- Simplenote/AuthViewController.xib | 27 +++++++++++------------ Simplenote/AuthenticationMode.swift | 2 +- Simplenote/NSColor+Theme.swift | 4 ++++ 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index c7a7551c4..00d46c05c 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -21,9 +21,11 @@ extension AuthViewController { secondaryActionButton.contentTintColor = .simplenoteBrandColor // WordPress SSO - wordPressSSOButton.image = NSImage(named: .wordPressLogo)?.tinted(with: .simplenoteBrandColor) wordPressSSOButton.title = Localization.dotcomSSOAction - wordPressSSOButton.contentTintColor = .simplenoteTertiaryTextColor + wordPressSSOButton.contentTintColor = .white + wordPressSSOContainerView.wantsLayer = true + wordPressSSOContainerView.layer?.backgroundColor = NSColor.simplenoteWPBlue50Color.cgColor + wordPressSSOContainerView.layer?.cornerRadius = 5 setupActionsSeparatorView() } diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index 9f71a8600..6493dcb7f 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -31,13 +31,13 @@ - + - + - + @@ -45,7 +45,7 @@ - + @@ -56,13 +56,13 @@ - + - + @@ -71,7 +71,7 @@ - + - + @@ -149,11 +149,11 @@ - + @@ -135,9 +132,6 @@ - - - diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 4c445ec6e..1304ef8c8 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -1,18 +1,36 @@ import Foundation +// MARK: - Authentication Actions +// +enum AuthenticationActionName { + case primary + case secondary + case tertiary + case quaternary +} + +struct AuthenticationActionDescriptor { + let name: AuthenticationActionName + let selector: Selector + let text: String? + let attributedText: NSAttributedString? + + init(name: AuthenticationActionName, selector: Selector, text: String?, attributedText: NSAttributedString? = nil) { + self.name = name + self.selector = selector + self.text = text + self.attributedText = attributedText + } +} // MARK: - AuthenticationMode // class AuthenticationMode: NSObject { let title: String let header: String? + let actions: [AuthenticationActionDescriptor] - let primaryActionText: String let primaryActionAnimationText: String - let primaryActionSelector: Selector - - let secondaryActionText: String? - let secondaryActionSelector: Selector? let switchTargetMode: () -> AuthenticationMode @@ -24,11 +42,8 @@ class AuthenticationMode: NSObject { init(title: String, header: String? = nil, - primaryActionText: String, + actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, - primaryActionSelector: Selector, - secondaryActionText: String?, - secondaryActionSelector: Selector?, switchTargetMode: @escaping () -> AuthenticationMode, isPasswordVisible: Bool, isSecondaryActionVisible: Bool, @@ -37,11 +52,8 @@ class AuthenticationMode: NSObject { isIntroView: Bool = false) { self.title = title self.header = header - self.primaryActionText = primaryActionText + self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText - self.primaryActionSelector = primaryActionSelector - self.secondaryActionText = secondaryActionText - self.secondaryActionSelector = secondaryActionSelector self.switchTargetMode = switchTargetMode self.isPasswordVisible = isPasswordVisible self.isSecondaryActionVisible = isSecondaryActionVisible @@ -92,12 +104,16 @@ extension AuthenticationMode { @objc static var onboarding: AuthenticationMode { return AuthenticationMode(title: "Onboarding", - header: nil, - primaryActionText: SignupStrings.primaryAction, + header: nil, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(AuthViewController.pushSignupView), + text: SignupStrings.primaryAction), + AuthenticationActionDescriptor(name: .secondary, + selector: #selector(AuthViewController.pushEmailLoginView), + text: LoginStrings.primaryAction) + ], primaryActionAnimationText: SignupStrings.primaryAnimationText, - primaryActionSelector: #selector(AuthViewController.pushSignupView), - secondaryActionText: LoginStrings.primaryAction, - secondaryActionSelector: #selector(AuthViewController.pushEmailLoginView), switchTargetMode: { .requestLoginCode }, isPasswordVisible: false, isSecondaryActionVisible: true, isWordPressVisible: false, @@ -110,11 +126,15 @@ extension AuthenticationMode { static func loginWithPassword(header: String? = nil) -> AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In with Password", comment: "LogIn Interface Title"), header: header, - primaryActionText: LoginStrings.primaryAction, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(AuthViewController.pressedLogInWithPassword), + text: LoginStrings.primaryAction), + AuthenticationActionDescriptor(name: .secondary, + selector: #selector(AuthViewController.openForgotPasswordURL), + text: LoginStrings.secondaryAction) + ], primaryActionAnimationText: LoginStrings.primaryAnimationText, - primaryActionSelector: #selector(AuthViewController.pressedLogInWithPassword), - secondaryActionText: LoginStrings.secondaryAction, - secondaryActionSelector: #selector(AuthViewController.openForgotPasswordURL), switchTargetMode: { .signup }, isPasswordVisible: true, isSecondaryActionVisible: true, @@ -128,11 +148,15 @@ extension AuthenticationMode { @objc static var requestLoginCode: AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In", comment: "LogIn Interface Title"), - primaryActionText: MagicLinkStrings.primaryAction, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(AuthViewController.pressedLoginWithMagicLink), + text: MagicLinkStrings.primaryAction), + AuthenticationActionDescriptor(name: .secondary, + selector: #selector(AuthViewController.switchToPasswordAuth), + text: MagicLinkStrings.secondaryAction) + ], primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, - primaryActionSelector: #selector(AuthViewController.pressedLoginWithMagicLink), - secondaryActionText: MagicLinkStrings.secondaryAction, - secondaryActionSelector: #selector(AuthViewController.switchToPasswordAuth), switchTargetMode: { .signup }, isPasswordVisible: false, isSecondaryActionVisible: true, @@ -145,11 +169,12 @@ extension AuthenticationMode { @objc static var signup: AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Sign Up", comment: "SignUp Interface Title"), - primaryActionText: SignupStrings.primaryAction, + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(AuthViewController.pressedSignUp), + text: SignupStrings.primaryAction) + ], primaryActionAnimationText: SignupStrings.primaryAnimationText, - primaryActionSelector: #selector(AuthViewController.pressedSignUp), - secondaryActionText: SignupStrings.switchAction, - secondaryActionSelector: #selector(AuthViewController.pushEmailLoginView), switchTargetMode: { .requestLoginCode }, isPasswordVisible: false, isSecondaryActionVisible: true, From 7ba9271893c2e65f5aa594c7fc20eaa30300e805 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 15:11:10 -0600 Subject: [PATCH 06/84] Dropped secondary view boolean and animation size props --- Simplenote/AuthViewController+Swift.swift | 11 +++++------ Simplenote/AuthenticationMode.swift | 18 ++---------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 7d4b65b35..4094821b2 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -86,7 +86,10 @@ extension AuthViewController { .secondary: secondaryActionButton ] - allActionViews.forEach({ $0.isHidden = true }) + allActionViews.forEach({ + $0.isHidden = true + $0.isEnabled = false + }) for descriptor in mode.actions { guard let actionView = viewMap[descriptor.name] else { @@ -101,6 +104,7 @@ extension AuthViewController { actionView.action = descriptor.selector actionView.isHidden = false + actionView.isEnabled = true } } @@ -108,7 +112,6 @@ extension AuthViewController { /// func refreshEnabledComponents() { passwordField.isEnabled = mode.isPasswordVisible - secondaryActionButton.isEnabled = mode.isSecondaryActionVisible wordPressSSOButton.isEnabled = mode.isWordPressVisible } @@ -128,11 +131,9 @@ extension AuthViewController { /// func refreshVisibleComponentsWithoutAnimation() { passwordFieldHeightConstraint.constant = mode.passwordFieldHeight - secondaryActionHeightConstraint.constant = mode.secondaryActionFieldHeight wordPressSSOHeightConstraint.constant = mode.wordPressSSOFieldHeight passwordField.alphaValue = mode.passwordFieldAlpha - secondaryActionButton.alphaValue = mode.secondaryActionFieldAlpha wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha actionsSeparatorView.isHidden = !mode.showActionSeparator @@ -149,11 +150,9 @@ extension AuthViewController { context.duration = AppKitConstants.duration0_2 passwordFieldHeightConstraint.animator().constant = mode.passwordFieldHeight - secondaryActionHeightConstraint.animator().constant = mode.secondaryActionFieldHeight wordPressSSOHeightConstraint.animator().constant = mode.wordPressSSOFieldHeight passwordField.alphaValue = mode.passwordFieldAlpha - secondaryActionButton.alphaValue = mode.secondaryActionFieldAlpha wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha view.layoutSubtreeIfNeeded() diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 1304ef8c8..fc124f30b 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -35,7 +35,6 @@ class AuthenticationMode: NSObject { let switchTargetMode: () -> AuthenticationMode let isPasswordVisible: Bool - let isSecondaryActionVisible: Bool let isWordPressVisible: Bool let showActionSeparator: Bool let isIntroView: Bool @@ -46,7 +45,6 @@ class AuthenticationMode: NSObject { primaryActionAnimationText: String, switchTargetMode: @escaping () -> AuthenticationMode, isPasswordVisible: Bool, - isSecondaryActionVisible: Bool, isWordPressVisible: Bool, showActionSeparator: Bool, isIntroView: Bool = false) { @@ -56,7 +54,6 @@ class AuthenticationMode: NSObject { self.primaryActionAnimationText = primaryActionAnimationText self.switchTargetMode = switchTargetMode self.isPasswordVisible = isPasswordVisible - self.isSecondaryActionVisible = isSecondaryActionVisible self.isWordPressVisible = isWordPressVisible self.showActionSeparator = showActionSeparator self.isIntroView = isIntroView @@ -76,10 +73,6 @@ extension AuthenticationMode { isPasswordVisible ? CGFloat(40) : .zero } - var secondaryActionFieldHeight: CGFloat { - isSecondaryActionVisible ? CGFloat(20) : .zero - } - var wordPressSSOFieldHeight: CGFloat { isWordPressVisible ? CGFloat(40) : .zero } @@ -87,11 +80,7 @@ extension AuthenticationMode { var passwordFieldAlpha: CGFloat { isPasswordVisible ? AppKitConstants.alpha1_0 : AppKitConstants.alpha0_0 } - - var secondaryActionFieldAlpha: CGFloat { - isSecondaryActionVisible ? AppKitConstants.alpha1_0 : AppKitConstants.alpha0_0 - } - + var wordPressSSOFieldAlpha: CGFloat { isWordPressVisible ? AppKitConstants.alpha1_0 : AppKitConstants.alpha0_0 } @@ -113,9 +102,9 @@ extension AuthenticationMode { selector: #selector(AuthViewController.pushEmailLoginView), text: LoginStrings.primaryAction) ], + primaryActionAnimationText: SignupStrings.primaryAnimationText, switchTargetMode: { .requestLoginCode }, isPasswordVisible: false, - isSecondaryActionVisible: true, isWordPressVisible: false, showActionSeparator: false, isIntroView: true) @@ -137,7 +126,6 @@ extension AuthenticationMode { primaryActionAnimationText: LoginStrings.primaryAnimationText, switchTargetMode: { .signup }, isPasswordVisible: true, - isSecondaryActionVisible: true, isWordPressVisible: true, showActionSeparator: true) } @@ -159,7 +147,6 @@ extension AuthenticationMode { primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, switchTargetMode: { .signup }, isPasswordVisible: false, - isSecondaryActionVisible: true, isWordPressVisible: true, showActionSeparator: true) } @@ -177,7 +164,6 @@ extension AuthenticationMode { primaryActionAnimationText: SignupStrings.primaryAnimationText, switchTargetMode: { .requestLoginCode }, isPasswordVisible: false, - isSecondaryActionVisible: true, isWordPressVisible: false, showActionSeparator: false) } From b5ad6fd41c75ff80b2bf8c3718c57a61264c1c3b Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 15:58:58 -0600 Subject: [PATCH 07/84] Updated wordpress sso button to be the tertiary button --- Simplenote/AuthViewController+Swift.swift | 38 ++++++++++++++--------- Simplenote/AuthViewController.h | 4 +-- Simplenote/AuthViewController.m | 15 +-------- Simplenote/AuthViewController.xib | 13 +++----- Simplenote/AuthenticationMode.swift | 5 ++- 5 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 4094821b2..23e88932c 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -24,11 +24,11 @@ extension AuthViewController { secondaryActionButton.contentTintColor = .simplenoteBrandColor // WordPress SSO - wordPressSSOButton.title = Localization.dotcomSSOAction - wordPressSSOButton.contentTintColor = .white - wordPressSSOContainerView.wantsLayer = true - wordPressSSOContainerView.layer?.backgroundColor = NSColor.simplenoteWPBlue50Color.cgColor - wordPressSSOContainerView.layer?.cornerRadius = 5 + + tertiaryButton.contentTintColor = .white + tertiaryButton.wantsLayer = true + tertiaryButton.layer?.backgroundColor = NSColor.simplenoteWPBlue50Color.cgColor + tertiaryButton.layer?.cornerRadius = 5 setupActionsSeparatorView() } @@ -64,7 +64,7 @@ extension AuthViewController { /// # All of the Action Views /// private var allActionViews: [NSButton] { - [actionButton, secondaryActionButton] + [actionButton, secondaryActionButton, tertiaryButton] } } @@ -83,7 +83,8 @@ extension AuthViewController { private func refreshActionViews() { let viewMap: [AuthenticationActionName: NSButton] = [ .primary: actionButton, - .secondary: secondaryActionButton + .secondary: secondaryActionButton, + .tertiary: tertiaryButton ] allActionViews.forEach({ @@ -112,7 +113,6 @@ extension AuthViewController { /// func refreshEnabledComponents() { passwordField.isEnabled = mode.isPasswordVisible - wordPressSSOButton.isEnabled = mode.isWordPressVisible } /// Shows / Hides relevant components, based on the specified state @@ -131,10 +131,8 @@ extension AuthViewController { /// func refreshVisibleComponentsWithoutAnimation() { passwordFieldHeightConstraint.constant = mode.passwordFieldHeight - wordPressSSOHeightConstraint.constant = mode.wordPressSSOFieldHeight passwordField.alphaValue = mode.passwordFieldAlpha - wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha actionsSeparatorView.isHidden = !mode.showActionSeparator simplenoteTitleView.isHidden = !mode.isIntroView @@ -150,10 +148,10 @@ extension AuthViewController { context.duration = AppKitConstants.duration0_2 passwordFieldHeightConstraint.animator().constant = mode.passwordFieldHeight - wordPressSSOHeightConstraint.animator().constant = mode.wordPressSSOFieldHeight +// wordPressSSOHeightConstraint.animator().constant = mode.wordPressSSOFieldHeight passwordField.alphaValue = mode.passwordFieldAlpha - wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha +// wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha view.layoutSubtreeIfNeeded() } @@ -280,7 +278,20 @@ extension AuthViewController { self.showAuthenticationError(forCode: statusCode, responseString: nil) } } - + + @objc + func wordpressSSOAction() { + let sessionsState = "app-\(UUID().uuidString)" + //TODO: Set the constant somewhere else + UserDefaults.standard.set(sessionsState, forKey: "SPAuthSessionKey") + + let requestURL = NSString(format: SPWPSignInAuthURL as NSString, SPCredentials.wpcomClientID, SPCredentials.wpcomRedirectURL) + let encodedURL = requestURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) + NSWorkspace.shared.open(URL(string: encodedURL!)!) + + SPTracker.trackWPCCButtonPressed() + } + @IBAction func handleNewlineInField(_ field: NSControl) { if field.isEqual(passwordField.textField) { @@ -386,7 +397,6 @@ extension AuthViewController { private enum Localization { static let emailPlaceholder = NSLocalizedString("Email", comment: "Placeholder text for login field") static let passwordPlaceholder = NSLocalizedString("Password", comment: "Placeholder text for password field") - static let dotcomSSOAction = NSLocalizedString("Log in with WordPress.com", comment: "button title for wp.com sign in button") static let compromisedPasswordAlert = NSLocalizedString("Compromised Password", comment: "Compromised passsword alert title") static let compromisedPasswordMessage = NSLocalizedString("This password has appeared in a data breach, which puts your account at high risk of compromise. To protect your data, you'll need to update your password before being able to log in again.", comment: "Compromised password alert message") static let changePasswordAction = NSLocalizedString("Change Password", comment: "Change password action") diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 15281c08b..8ea75a1d2 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -20,8 +20,8 @@ @property (nonatomic, strong) IBOutlet NSButton *actionButton; @property (nonatomic, strong) IBOutlet NSProgressIndicator *actionProgress; @property (nonatomic, strong) IBOutlet NSButton *secondaryActionButton; -@property (nonatomic, strong) IBOutlet NSView *wordPressSSOContainerView; -@property (nonatomic, strong) IBOutlet NSButton *wordPressSSOButton; +@property (nonatomic, strong) IBOutlet NSView *tertiaryButtonContainerView; +@property (nonatomic, strong) IBOutlet NSButton *tertiaryButton; @property (weak) IBOutlet NSView *actionsSeparatorView; @property (weak) IBOutlet NSView *leadingSeparatorView; @property (weak) IBOutlet NSTextField *separatorLabel; diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 17d4db644..5b22b3aea 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -81,25 +81,12 @@ - (void)setInterfaceEnabled:(BOOL)enabled { [self.passwordField setEnabled:enabled]; [self.actionButton setEnabled:enabled]; [self.secondaryActionButton setEnabled:enabled]; - [self.wordPressSSOButton setEnabled:enabled]; + [self.tertiaryButton setEnabled:enabled]; } #pragma mark - WordPress SSO -- (IBAction)wpccSignInAction:(id)sender -{ - NSString *sessionState = [[NSUUID UUID] UUIDString]; - sessionState = [@"app-" stringByAppendingString:sessionState]; - [[NSUserDefaults standardUserDefaults] setObject:sessionState forKey:SPAuthSessionKey]; - - NSString *requestUrl = [NSString stringWithFormat:SPWPSignInAuthURL, SPCredentials.wpcomClientID, SPCredentials.wpcomRedirectURL, sessionState]; - NSString *encodedUrl = [requestUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:encodedUrl]]; - - [SPTracker trackWPCCButtonPressed]; -} - - (IBAction)signInErrorAction:(NSNotification *)notification { NSString *errorMessage = NSLocalizedString(@"An error was encountered while signing in.", @"Sign in error message"); diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index ca646a27a..aea559ff5 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -23,11 +23,11 @@ + + - - @@ -169,21 +169,18 @@ - + - diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index fc124f30b..4760af936 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -142,7 +142,9 @@ extension AuthenticationMode { text: MagicLinkStrings.primaryAction), AuthenticationActionDescriptor(name: .secondary, selector: #selector(AuthViewController.switchToPasswordAuth), - text: MagicLinkStrings.secondaryAction) + text: MagicLinkStrings.secondaryAction), + AuthenticationActionDescriptor(name: .tertiary, + selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) ], primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, switchTargetMode: { .signup }, @@ -178,6 +180,7 @@ private enum LoginStrings { static let secondaryAction = NSLocalizedString("Forgot your Password?", comment: "Forgot Password Button") static let switchAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") static let switchTip = NSLocalizedString("Need an account?", comment: "Link to create an account") + static let wordpressAction = NSLocalizedString("Log in with WordPress.com", comment: "Title to use wordpress login instead of email") } private enum MagicLinkStrings { From c5207747b56802385647e586662356425570e0be Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 16:13:50 -0600 Subject: [PATCH 08/84] removed unused changes to wordpress button --- Simplenote/AuthViewController+Swift.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 23e88932c..abb757af9 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -148,10 +148,8 @@ extension AuthViewController { context.duration = AppKitConstants.duration0_2 passwordFieldHeightConstraint.animator().constant = mode.passwordFieldHeight -// wordPressSSOHeightConstraint.animator().constant = mode.wordPressSSOFieldHeight passwordField.alphaValue = mode.passwordFieldAlpha -// wordPressSSOButton.alphaValue = mode.wordPressSSOFieldAlpha view.layoutSubtreeIfNeeded() } From 5daedbf0c102019a3bcbafbd598bef7af2a8cc06 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 16:53:46 -0600 Subject: [PATCH 09/84] Updated authentication mode to include different input values --- Simplenote/AuthViewController+Swift.swift | 17 ++++++++++------- Simplenote/AuthenticationMode.swift | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index abb757af9..d27681fd4 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -76,7 +76,7 @@ extension AuthViewController { func refreshInterface(animated: Bool) { clearAuthenticationError() refreshActionViews() - refreshEnabledComponents() + refreshInputViews() refreshVisibleComponents(animated: animated) } @@ -99,20 +99,23 @@ extension AuthViewController { } if let title = descriptor.text { - let modifiedTitle = descriptor.name == .secondary ? title.uppercased() : title - actionView.title = modifiedTitle + actionView.title = title } actionView.action = descriptor.selector + actionView.target = self actionView.isHidden = false actionView.isEnabled = true } } - /// Makes sure unused components (in the current mode) are effectively disabled - /// - func refreshEnabledComponents() { - passwordField.isEnabled = mode.isPasswordVisible + private func refreshInputViews() { + let inputElements = mode.inputElements + + usernameField.isHidden = !inputElements.contains(.username) + passwordField.isHidden = !inputElements.contains(.password) + actionsSeparatorView.isHidden = !inputElements.contains(.actionSeparator) + } /// Shows / Hides relevant components, based on the specified state diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 4760af936..53c328024 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -1,5 +1,16 @@ import Foundation +// MARK: - Authentication Elements +// +struct AuthenticationInputElements: OptionSet, Hashable { + let rawValue: UInt + + static let username = AuthenticationInputElements(rawValue: 1 << 0) + static let password = AuthenticationInputElements(rawValue: 1 << 1) + static let code = AuthenticationInputElements(rawValue: 1 << 2) + static let actionSeparator = AuthenticationInputElements(rawValue: 1 << 3) +} + // MARK: - Authentication Actions // enum AuthenticationActionName { @@ -28,6 +39,7 @@ struct AuthenticationActionDescriptor { class AuthenticationMode: NSObject { let title: String let header: String? + let inputElements: AuthenticationInputElements let actions: [AuthenticationActionDescriptor] let primaryActionAnimationText: String @@ -41,6 +53,7 @@ class AuthenticationMode: NSObject { init(title: String, header: String? = nil, + inputElements: AuthenticationInputElements, actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, switchTargetMode: @escaping () -> AuthenticationMode, @@ -50,6 +63,7 @@ class AuthenticationMode: NSObject { isIntroView: Bool = false) { self.title = title self.header = header + self.inputElements = inputElements self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText self.switchTargetMode = switchTargetMode @@ -94,13 +108,14 @@ extension AuthenticationMode { static var onboarding: AuthenticationMode { return AuthenticationMode(title: "Onboarding", header: nil, + inputElements: [], actions: [ AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pushSignupView), text: SignupStrings.primaryAction), AuthenticationActionDescriptor(name: .secondary, selector: #selector(AuthViewController.pushEmailLoginView), - text: LoginStrings.primaryAction) + text: LoginStrings.primaryAction.uppercased()) ], primaryActionAnimationText: SignupStrings.primaryAnimationText, @@ -115,6 +130,7 @@ extension AuthenticationMode { static func loginWithPassword(header: String? = nil) -> AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In with Password", comment: "LogIn Interface Title"), header: header, + inputElements: [.username, .password], actions: [ AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedLogInWithPassword), @@ -136,6 +152,7 @@ extension AuthenticationMode { @objc static var requestLoginCode: AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In", comment: "LogIn Interface Title"), + inputElements: [.username], actions: [ AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedLoginWithMagicLink), @@ -158,6 +175,7 @@ extension AuthenticationMode { @objc static var signup: AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Sign Up", comment: "SignUp Interface Title"), + inputElements: [.username], actions: [ AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedSignUp), From 0779ce1ec0361d62b62ebaee7e2126400bd67f98 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 16:55:44 -0600 Subject: [PATCH 10/84] Dropped switch target mode --- Simplenote/AuthViewController+Swift.swift | 10 ---------- Simplenote/AuthenticationMode.swift | 21 ++++----------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index d27681fd4..f3743fb61 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -196,16 +196,6 @@ extension AuthViewController { return vc } - private func nextViewController() -> AuthViewController { - let nextMode = mode.nextMode() - - let nextVC = AuthViewController() - nextVC.authenticator = authenticator - nextVC.mode = nextMode - - return nextVC - } - @objc func pushEmailLoginView() { containingNavigationController?.push(authViewController(with: .requestLoginCode)) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 53c328024..efbe8e85a 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -43,8 +43,6 @@ class AuthenticationMode: NSObject { let actions: [AuthenticationActionDescriptor] let primaryActionAnimationText: String - - let switchTargetMode: () -> AuthenticationMode let isPasswordVisible: Bool let isWordPressVisible: Bool @@ -56,7 +54,6 @@ class AuthenticationMode: NSObject { inputElements: AuthenticationInputElements, actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, - switchTargetMode: @escaping () -> AuthenticationMode, isPasswordVisible: Bool, isWordPressVisible: Bool, showActionSeparator: Bool, @@ -66,7 +63,6 @@ class AuthenticationMode: NSObject { self.inputElements = inputElements self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText - self.switchTargetMode = switchTargetMode self.isPasswordVisible = isPasswordVisible self.isWordPressVisible = isWordPressVisible self.showActionSeparator = showActionSeparator @@ -78,11 +74,6 @@ class AuthenticationMode: NSObject { // extension AuthenticationMode { - @objc - func nextMode() -> AuthenticationMode { - switchTargetMode() - } - var passwordFieldHeight: CGFloat { isPasswordVisible ? CGFloat(40) : .zero } @@ -117,11 +108,10 @@ extension AuthenticationMode { selector: #selector(AuthViewController.pushEmailLoginView), text: LoginStrings.primaryAction.uppercased()) ], - - primaryActionAnimationText: SignupStrings.primaryAnimationText, - switchTargetMode: { .requestLoginCode }, isPasswordVisible: false, - isWordPressVisible: false, - showActionSeparator: false, + primaryActionAnimationText: SignupStrings.primaryAnimationText, + isPasswordVisible: false, + isWordPressVisible: false, + showActionSeparator: false, isIntroView: true) } @@ -140,7 +130,6 @@ extension AuthenticationMode { text: LoginStrings.secondaryAction) ], primaryActionAnimationText: LoginStrings.primaryAnimationText, - switchTargetMode: { .signup }, isPasswordVisible: true, isWordPressVisible: true, showActionSeparator: true) @@ -164,7 +153,6 @@ extension AuthenticationMode { selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) ], primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, - switchTargetMode: { .signup }, isPasswordVisible: false, isWordPressVisible: true, showActionSeparator: true) @@ -182,7 +170,6 @@ extension AuthenticationMode { text: SignupStrings.primaryAction) ], primaryActionAnimationText: SignupStrings.primaryAnimationText, - switchTargetMode: { .requestLoginCode }, isPasswordVisible: false, isWordPressVisible: false, showActionSeparator: false) From 5fc7e7ec4173a9bbd1b6b1e63c77acd1b55a28ca Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 16:57:04 -0600 Subject: [PATCH 11/84] Dropped isWordpressVisible on authenticationMode --- Simplenote/AuthenticationMode.swift | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index efbe8e85a..75624f419 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -45,7 +45,6 @@ class AuthenticationMode: NSObject { let primaryActionAnimationText: String let isPasswordVisible: Bool - let isWordPressVisible: Bool let showActionSeparator: Bool let isIntroView: Bool @@ -55,7 +54,6 @@ class AuthenticationMode: NSObject { actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, isPasswordVisible: Bool, - isWordPressVisible: Bool, showActionSeparator: Bool, isIntroView: Bool = false) { self.title = title @@ -64,7 +62,6 @@ class AuthenticationMode: NSObject { self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText self.isPasswordVisible = isPasswordVisible - self.isWordPressVisible = isWordPressVisible self.showActionSeparator = showActionSeparator self.isIntroView = isIntroView } @@ -77,18 +74,10 @@ extension AuthenticationMode { var passwordFieldHeight: CGFloat { isPasswordVisible ? CGFloat(40) : .zero } - - var wordPressSSOFieldHeight: CGFloat { - isWordPressVisible ? CGFloat(40) : .zero - } - + var passwordFieldAlpha: CGFloat { isPasswordVisible ? AppKitConstants.alpha1_0 : AppKitConstants.alpha0_0 } - - var wordPressSSOFieldAlpha: CGFloat { - isWordPressVisible ? AppKitConstants.alpha1_0 : AppKitConstants.alpha0_0 - } } @@ -110,7 +99,6 @@ extension AuthenticationMode { ], primaryActionAnimationText: SignupStrings.primaryAnimationText, isPasswordVisible: false, - isWordPressVisible: false, showActionSeparator: false, isIntroView: true) } @@ -131,7 +119,6 @@ extension AuthenticationMode { ], primaryActionAnimationText: LoginStrings.primaryAnimationText, isPasswordVisible: true, - isWordPressVisible: true, showActionSeparator: true) } @@ -154,7 +141,6 @@ extension AuthenticationMode { ], primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, isPasswordVisible: false, - isWordPressVisible: true, showActionSeparator: true) } @@ -171,7 +157,6 @@ extension AuthenticationMode { ], primaryActionAnimationText: SignupStrings.primaryAnimationText, isPasswordVisible: false, - isWordPressVisible: false, showActionSeparator: false) } } From 40b97e7d4af16261b8644c615362b4138dd05120 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 16:59:29 -0600 Subject: [PATCH 12/84] Removed show action separator --- Simplenote/AuthViewController+Swift.swift | 1 - Simplenote/AuthenticationMode.swift | 15 ++++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index f3743fb61..d8c91bc68 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -137,7 +137,6 @@ extension AuthViewController { passwordField.alphaValue = mode.passwordFieldAlpha - actionsSeparatorView.isHidden = !mode.showActionSeparator simplenoteTitleView.isHidden = !mode.isIntroView simplenoteSubTitleView.isHidden = !mode.isIntroView diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 75624f419..e0bb02074 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -45,7 +45,6 @@ class AuthenticationMode: NSObject { let primaryActionAnimationText: String let isPasswordVisible: Bool - let showActionSeparator: Bool let isIntroView: Bool init(title: String, @@ -54,7 +53,6 @@ class AuthenticationMode: NSObject { actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, isPasswordVisible: Bool, - showActionSeparator: Bool, isIntroView: Bool = false) { self.title = title self.header = header @@ -62,7 +60,6 @@ class AuthenticationMode: NSObject { self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText self.isPasswordVisible = isPasswordVisible - self.showActionSeparator = showActionSeparator self.isIntroView = isIntroView } } @@ -99,7 +96,6 @@ extension AuthenticationMode { ], primaryActionAnimationText: SignupStrings.primaryAnimationText, isPasswordVisible: false, - showActionSeparator: false, isIntroView: true) } @@ -118,8 +114,7 @@ extension AuthenticationMode { text: LoginStrings.secondaryAction) ], primaryActionAnimationText: LoginStrings.primaryAnimationText, - isPasswordVisible: true, - showActionSeparator: true) + isPasswordVisible: true) } /// Auth Mode: Login is handled via Magic Links! @@ -128,7 +123,7 @@ extension AuthenticationMode { @objc static var requestLoginCode: AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In", comment: "LogIn Interface Title"), - inputElements: [.username], + inputElements: [.username, .actionSeparator], actions: [ AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedLoginWithMagicLink), @@ -140,8 +135,7 @@ extension AuthenticationMode { selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) ], primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, - isPasswordVisible: false, - showActionSeparator: true) + isPasswordVisible: false) } /// Auth Mode: SignUp @@ -156,8 +150,7 @@ extension AuthenticationMode { text: SignupStrings.primaryAction) ], primaryActionAnimationText: SignupStrings.primaryAnimationText, - isPasswordVisible: false, - showActionSeparator: false) + isPasswordVisible: false) } } From 112e9279d1934f1ef31db8747546ac252ea440ae Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 17:05:17 -0600 Subject: [PATCH 13/84] Fixed wordpress sso button crash --- Simplenote/AuthViewController+Swift.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index d8c91bc68..c50e40b99 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -275,7 +275,7 @@ extension AuthViewController { //TODO: Set the constant somewhere else UserDefaults.standard.set(sessionsState, forKey: "SPAuthSessionKey") - let requestURL = NSString(format: SPWPSignInAuthURL as NSString, SPCredentials.wpcomClientID, SPCredentials.wpcomRedirectURL) + let requestURL = String(format: SPWPSignInAuthURL, SPCredentials.wpcomClientID, SPCredentials.wpcomRedirectURL, sessionsState) let encodedURL = requestURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) NSWorkspace.shared.open(URL(string: encodedURL!)!) From 1a437d479924ce53bf059fa6c042d10bbf8076f8 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 17:07:08 -0600 Subject: [PATCH 14/84] added SPAuthSessionKey to user defaults keys --- Simplenote/AuthViewController+Swift.swift | 3 +-- Simplenote/AuthViewController.m | 2 -- Simplenote/UserDefaults+Simplenote.swift | 1 + 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index c50e40b99..0711e7934 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -272,8 +272,7 @@ extension AuthViewController { @objc func wordpressSSOAction() { let sessionsState = "app-\(UUID().uuidString)" - //TODO: Set the constant somewhere else - UserDefaults.standard.set(sessionsState, forKey: "SPAuthSessionKey") + UserDefaults.standard.set(sessionsState, forKey: .SPAuthSessionKey) let requestURL = String(format: SPWPSignInAuthURL, SPCredentials.wpcomClientID, SPCredentials.wpcomRedirectURL, sessionsState) let encodedURL = requestURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 5b22b3aea..f7208edab 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -6,8 +6,6 @@ #pragma mark - Constants -static NSString *SPAuthSessionKey = @"SPAuthSessionKey"; - #pragma mark - Private diff --git a/Simplenote/UserDefaults+Simplenote.swift b/Simplenote/UserDefaults+Simplenote.swift index 5fff0fa2c..e56e1ca74 100644 --- a/Simplenote/UserDefaults+Simplenote.swift +++ b/Simplenote/UserDefaults+Simplenote.swift @@ -15,6 +15,7 @@ extension UserDefaults { case themeName = "VSThemeManagerThemePrefKey" case fontSize = "kFontSizePreferencesKey" case indexNotesForSpotlight + case SPAuthSessionKey } } From 78d58c9732cd66948a5a7e17dfc32b2220ae3e07 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Tue, 23 Jul 2024 17:11:18 -0600 Subject: [PATCH 15/84] Added code input view --- Simplenote/AuthViewController.h | 15 ++++++++------- Simplenote/AuthViewController.xib | 30 +++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 8ea75a1d2..9fb6ff2bd 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -11,21 +11,22 @@ @property (nonatomic, strong) IBOutlet NSStackView *stackView; @property (nonatomic, strong) IBOutlet NSImageView *logoImageView; -@property (nonatomic, strong) IBOutlet NSTextField *simplenoteTitleView; -@property (nonatomic, strong) IBOutlet NSTextField *simplenoteSubTitleView; -@property (nonatomic, strong) IBOutlet NSTextField *headerLabel; +@property (nonatomic, strong) IBOutlet NSTextField *simplenoteTitleView; +@property (nonatomic, strong) IBOutlet NSTextField *simplenoteSubTitleView; +@property (nonatomic, strong) IBOutlet NSTextField *headerLabel; @property (nonatomic, strong) IBOutlet NSTextField *errorField; @property (nonatomic, strong) IBOutlet SPAuthenticationTextField *usernameField; @property (nonatomic, strong) IBOutlet SPAuthenticationTextField *passwordField; +@property (nonatomic, strong) IBOutlet SPAuthenticationTextField *codeTextField; @property (nonatomic, strong) IBOutlet NSButton *actionButton; @property (nonatomic, strong) IBOutlet NSProgressIndicator *actionProgress; @property (nonatomic, strong) IBOutlet NSButton *secondaryActionButton; @property (nonatomic, strong) IBOutlet NSView *tertiaryButtonContainerView; @property (nonatomic, strong) IBOutlet NSButton *tertiaryButton; -@property (weak) IBOutlet NSView *actionsSeparatorView; -@property (weak) IBOutlet NSView *leadingSeparatorView; -@property (weak) IBOutlet NSTextField *separatorLabel; -@property (weak) IBOutlet NSView *trailingSeparatorView; +@property (nonatomic, strong) IBOutlet NSView *actionsSeparatorView; +@property (nonatomic, strong) IBOutlet NSView *leadingSeparatorView; +@property (nonatomic, strong) IBOutlet NSTextField *separatorLabel; +@property (nonatomic, strong) IBOutlet NSView *trailingSeparatorView; @property (nonatomic, strong) IBOutlet NSLayoutConstraint *passwordFieldHeightConstraint; @property (nonatomic, strong) IBOutlet NSLayoutConstraint *secondaryActionHeightConstraint; diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index aea559ff5..3bf9eb2cc 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -11,6 +11,7 @@ + @@ -34,13 +35,13 @@ - + - + - + @@ -48,7 +49,7 @@ - + @@ -56,7 +57,7 @@ - + @@ -64,7 +65,7 @@ - + @@ -72,7 +73,7 @@ - + @@ -83,13 +84,13 @@ - + - + @@ -97,6 +98,15 @@ + + + + + + + + + @@ -221,6 +231,7 @@ + @@ -234,6 +245,7 @@ + From 8f2978fbc0806551e3420aba2e54290a9f3f76eb Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 12:18:50 -0600 Subject: [PATCH 16/84] Add auth with code view --- Simplenote/AuthViewController+Swift.swift | 88 ++++++++++++++++++++--- Simplenote/AuthViewController.h | 5 ++ Simplenote/AuthViewController.m | 2 + Simplenote/AuthViewController.xib | 56 +++++++++++---- Simplenote/AuthenticationMode.swift | 28 ++++++++ 5 files changed, 156 insertions(+), 23 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 0711e7934..0f02366b5 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -6,6 +6,10 @@ extension AuthViewController { @objc func setupInterface() { + if state == nil { + state = AuthenticationState() + } + simplenoteTitleView.stringValue = "Simplenote" simplenoteSubTitleView.textColor = .simplenoteGray50Color simplenoteSubTitleView.stringValue = NSLocalizedString("The simplest way to keep notes.", comment: "Simplenote subtitle") @@ -20,17 +24,26 @@ extension AuthViewController { passwordField.placeholderString = Localization.passwordPlaceholder passwordField.delegate = self + codeTextField.placeholderString = Localization.codePlaceholder + codeTextField.delegate = self + // Secondary Action secondaryActionButton.contentTintColor = .simplenoteBrandColor - // WordPress SSO - + // tertiary button tertiaryButton.contentTintColor = .white tertiaryButton.wantsLayer = true tertiaryButton.layer?.backgroundColor = NSColor.simplenoteWPBlue50Color.cgColor tertiaryButton.layer?.cornerRadius = 5 + // quarternary button + quarternaryButtonView.wantsLayer = true + quarternaryButtonView.layer?.borderWidth = 2 + quarternaryButtonView.layer?.borderColor = .black + quarternaryButtonView.layer?.cornerRadius = 5 + setupActionsSeparatorView() + setupAdditionalButtons() } private func setupActionsSeparatorView() { @@ -41,6 +54,13 @@ extension AuthViewController { separatorLabel.textColor = .lightGray } + + private func setupAdditionalButtons() { + let modeActions = mode.actions + + tertiaryButtonContainerView.isHidden = !modeActions.contains(where: { $0.name == .tertiary }) + quarternaryButtonView.isHidden = !modeActions.contains(where: { $0.name == .quaternary }) + } } // MARK: - Dynamic Properties @@ -64,7 +84,7 @@ extension AuthViewController { /// # All of the Action Views /// private var allActionViews: [NSButton] { - [actionButton, secondaryActionButton, tertiaryButton] + [actionButton, secondaryActionButton, tertiaryButton, quarternaryButton] } } @@ -84,7 +104,8 @@ extension AuthViewController { let viewMap: [AuthenticationActionName: NSButton] = [ .primary: actionButton, .secondary: secondaryActionButton, - .tertiary: tertiaryButton + .tertiary: tertiaryButton, + .quaternary: quarternaryButton ] allActionViews.forEach({ @@ -114,6 +135,7 @@ extension AuthViewController { usernameField.isHidden = !inputElements.contains(.username) passwordField.isHidden = !inputElements.contains(.password) + codeTextField.isHidden = !inputElements.contains(.code) actionsSeparatorView.isHidden = !inputElements.contains(.actionSeparator) } @@ -187,9 +209,10 @@ extension AuthViewController { ensureUsernameIsFirstResponder() } - private func authViewController(with mode: AuthenticationMode) -> AuthViewController { + private func authViewController(with mode: AuthenticationMode, state: AuthenticationState) -> AuthViewController { let vc = AuthViewController() vc.authenticator = authenticator + vc.state = state vc.mode = mode return vc @@ -197,12 +220,21 @@ extension AuthViewController { @objc func pushEmailLoginView() { - containingNavigationController?.push(authViewController(with: .requestLoginCode)) + containingNavigationController?.push(authViewController(with: .requestLoginCode, state: state)) } @objc func pushSignupView() { - containingNavigationController?.push(authViewController(with: .signup)) + containingNavigationController?.push(authViewController(with: .signup, state: state)) + } + + @objc + func pushPasswordView() { + //TODO: Present password view + } + + func pushCodeLoginView() { + containingNavigationController?.push(authViewController(with: .loginWithCode, state: state)) } } @@ -256,19 +288,31 @@ extension AuthViewController { startActionAnimation() setInterfaceEnabled(false) + do { let email = usernameText let remote = LoginRemote() try await remote.requestLoginEmail(email: email) - presentMagicLinkRequestedView(email: email) - + pushCodeLoginView() } catch { let statusCode = (error as? RemoteError)?.statusCode ?? .zero self.showAuthenticationError(forCode: statusCode, responseString: nil) } } + @MainActor + func loginWithCode(username: String, code: String) async throws { + let remote = LoginRemote() + + do { + let confirmation = try await remote.requestLoginConfirmation(email: username, authCode: code.uppercased()) + authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) + } catch { + //TODO: Handle errors + } + } + @objc func wordpressSSOAction() { let sessionsState = "app-\(UUID().uuidString)" @@ -281,6 +325,11 @@ extension AuthViewController { SPTracker.trackWPCCButtonPressed() } + @objc + func performLogInWithCode() { + + } + @IBAction func handleNewlineInField(_ field: NSControl) { if field.isEqual(passwordField.textField) { @@ -293,6 +342,25 @@ extension AuthViewController { return } } + + @objc + func updateState(with object: Any) { + guard let field = object as? NSTextField, + let superView = field.superview else { + return + } + + switch superView { + case usernameField: + state.username = usernameField.stringValue + case passwordField: + state.password = passwordField.stringValue + case codeTextField: + state.code = passwordField.stringValue + default: + return + } + } } @@ -324,6 +392,7 @@ extension AuthViewController { view.window?.transition(to: vc) } + //TODO: Drop this method? func presentMagicLinkRequestedView(email: String) { guard let authWindowController else { return @@ -386,6 +455,7 @@ extension AuthViewController { private enum Localization { static let emailPlaceholder = NSLocalizedString("Email", comment: "Placeholder text for login field") static let passwordPlaceholder = NSLocalizedString("Password", comment: "Placeholder text for password field") + static let codePlaceholder = NSLocalizedString("Code", comment: "Placeholder text for code field") static let compromisedPasswordAlert = NSLocalizedString("Compromised Password", comment: "Compromised passsword alert title") static let compromisedPasswordMessage = NSLocalizedString("This password has appeared in a data breach, which puts your account at high risk of compromise. To protect your data, you'll need to update your password before being able to log in again.", comment: "Compromised password alert message") static let changePasswordAction = NSLocalizedString("Change Password", comment: "Change password action") diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 9fb6ff2bd..2d7ac9194 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -3,6 +3,7 @@ @import Simperium_OSX; @class AuthenticationMode; +@class AuthenticationState; // MARK: - AuthViewController: Simperium's Authentication UI @@ -27,6 +28,9 @@ @property (nonatomic, strong) IBOutlet NSView *leadingSeparatorView; @property (nonatomic, strong) IBOutlet NSTextField *separatorLabel; @property (nonatomic, strong) IBOutlet NSView *trailingSeparatorView; +@property (nonatomic, strong) IBOutlet NSView *quarternaryButtonView; +@property (nonatomic, strong) IBOutlet NSButton *quarternaryButton; + @property (nonatomic, strong) IBOutlet NSLayoutConstraint *passwordFieldHeightConstraint; @property (nonatomic, strong) IBOutlet NSLayoutConstraint *secondaryActionHeightConstraint; @@ -34,6 +38,7 @@ @property (nonatomic, strong) SPAuthenticator *authenticator; @property (nonatomic, strong) AuthenticationMode *mode; +@property (nonatomic, strong) AuthenticationState *state; - (void)pressedLogInWithPassword; - (void)pressedLoginWithMagicLink; diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index f7208edab..0c9fd006f 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -364,6 +364,8 @@ - (void)controlTextDidChange:(NSNotification *)obj { if (currentEvent.type == NSEventTypeKeyDown && [currentEvent.charactersIgnoringModifiers isEqualToString:@"\r"]) { [self handleNewlineInField:obj.object]; } + + [self updateStateWith:obj.object]; } @end diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index 3bf9eb2cc..c462da3a0 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -18,6 +18,8 @@ + + @@ -35,13 +37,13 @@ - + - + - + @@ -49,7 +51,7 @@ - + @@ -57,7 +59,7 @@ - + @@ -65,7 +67,7 @@ - + @@ -73,7 +75,7 @@ - + @@ -84,13 +86,13 @@ - + - + @@ -99,7 +101,7 @@ - + @@ -108,7 +110,7 @@ - + - + @@ -180,7 +182,7 @@ - + + + + + + + + + + @@ -210,10 +234,12 @@ + + @@ -232,6 +258,7 @@ + @@ -246,6 +273,7 @@ + diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index e0bb02074..123d3303e 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -1,5 +1,14 @@ import Foundation +// MARK: - State +// +@objcMembers +class AuthenticationState: NSObject { + var username = String() + var password = String() + var code = String() +} + // MARK: - Authentication Elements // struct AuthenticationInputElements: OptionSet, Hashable { @@ -152,6 +161,25 @@ extension AuthenticationMode { primaryActionAnimationText: SignupStrings.primaryAnimationText, isPasswordVisible: false) } + + /// Login with Code: Submit Code + Authenticate the user + /// + static var loginWithCode: AuthenticationMode { + AuthenticationMode(title: NSLocalizedString("Enter Code", comment: "LogIn Interface Title"), + header: NSLocalizedString("We've sent a code to {{EMAIL}}. The code will be valid for a few minutes.", comment: "Header for the Login with Code UI. Please preserve the {{EMAIL}} string as is!"), + inputElements: [.code, .actionSeparator], + actions: [ + AuthenticationActionDescriptor(name: .primary, + selector: #selector(AuthViewController.performLogInWithCode), + text: NSLocalizedString("Log In", comment: "LogIn Interface Title")), + AuthenticationActionDescriptor(name: .quaternary, + selector: #selector(AuthViewController.pushPasswordView), + text: NSLocalizedString("Enter password", comment: "Enter Password fallback Action")), + ], + primaryActionAnimationText: "", + isPasswordVisible: false) + } + } From 9d09f06c5d143e3fbc9712e6cdeedbb90764256d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 12:25:44 -0600 Subject: [PATCH 17/84] Added login with password screen and actions --- Simplenote/AuthViewController+Swift.swift | 4 ++-- Simplenote/AuthViewController.m | 6 +++--- Simplenote/AuthenticationMode.swift | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 0f02366b5..28f04d2c1 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -230,7 +230,7 @@ extension AuthViewController { @objc func pushPasswordView() { - //TODO: Present password view + containingNavigationController?.push(authViewController(with: .loginWithPassword(), state: state)) } func pushCodeLoginView() { @@ -327,7 +327,7 @@ extension AuthViewController { @objc func performLogInWithCode() { - + //TODO: Add login with code } @IBAction diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 0c9fd006f..9af9b93b2 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -168,7 +168,7 @@ - (void)performLoginWithPassword { [self startActionAnimation]; [self setInterfaceEnabled:NO]; - [self.authenticator authenticateWithUsername:self.usernameText password:self.passwordText success:^{ + [self.authenticator authenticateWithUsername:self.state.username password:self.state.password success:^{ // NO-OP } failure:^(NSInteger responseCode, NSString *responseString, NSError *error) { [self showAuthenticationErrorForCode:responseCode responseString: responseString]; @@ -230,7 +230,7 @@ - (void)openResetPasswordURL { - (BOOL)validateUsername { NSError *error = nil; - if ([self.validator validateUsername:self.usernameText error:&error]) { + if ([self.validator validateUsername:self.state.username error:&error]) { return YES; } @@ -241,7 +241,7 @@ - (BOOL)validateUsername { - (BOOL)validatePasswordSecurity { NSError *error = nil; - if ([self.validator validatePasswordWithUsername:self.usernameText password:self.passwordText error:&error]) { + if ([self.validator validatePasswordWithUsername:self.state.username password:self.state.password error:&error]) { return YES; } diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 123d3303e..3ebc667ba 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -113,7 +113,7 @@ extension AuthenticationMode { static func loginWithPassword(header: String? = nil) -> AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In with Password", comment: "LogIn Interface Title"), header: header, - inputElements: [.username, .password], + inputElements: [.password], actions: [ AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedLogInWithPassword), @@ -137,9 +137,6 @@ extension AuthenticationMode { AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedLoginWithMagicLink), text: MagicLinkStrings.primaryAction), - AuthenticationActionDescriptor(name: .secondary, - selector: #selector(AuthViewController.switchToPasswordAuth), - text: MagicLinkStrings.secondaryAction), AuthenticationActionDescriptor(name: .tertiary, selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) ], From b2e2a34385918c95cbd813eef20363920d383da9 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 12:39:24 -0600 Subject: [PATCH 18/84] Adde code authentication --- Simplenote/AuthViewController+Swift.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 28f04d2c1..0304caaf0 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -302,7 +302,15 @@ extension AuthViewController { } @MainActor - func loginWithCode(username: String, code: String) async throws { + func loginWithCode(username: String, code: String) async { + defer { + stopActionAnimation() + setInterfaceEnabled(true) + } + + startActionAnimation() + setInterfaceEnabled(false) + let remote = LoginRemote() do { @@ -327,7 +335,9 @@ extension AuthViewController { @objc func performLogInWithCode() { - //TODO: Add login with code + Task { + await loginWithCode(username: state.username, code: state.code) + } } @IBAction @@ -356,7 +366,7 @@ extension AuthViewController { case passwordField: state.password = passwordField.stringValue case codeTextField: - state.code = passwordField.stringValue + state.code = codeTextField.stringValue default: return } From 5f0be571821e9ade797a6cbb9c8f5c5c04398f94 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 12:44:39 -0600 Subject: [PATCH 19/84] Dropped remaining fading animation changes to auth view --- Simplenote/AuthViewController+Swift.swift | 54 +++++------------------ Simplenote/AuthViewController.m | 2 +- Simplenote/AuthenticationMode.swift | 32 +++----------- 3 files changed, 17 insertions(+), 71 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 0304caaf0..e8e0a9707 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -44,6 +44,7 @@ extension AuthViewController { setupActionsSeparatorView() setupAdditionalButtons() + setupLabels() } private func setupActionsSeparatorView() { @@ -61,6 +62,13 @@ extension AuthViewController { tertiaryButtonContainerView.isHidden = !modeActions.contains(where: { $0.name == .tertiary }) quarternaryButtonView.isHidden = !modeActions.contains(where: { $0.name == .quaternary }) } + + private func setupLabels() { + simplenoteTitleView.isHidden = !mode.isIntroView + simplenoteSubTitleView.isHidden = !mode.isIntroView + + headerLabel.isHidden = mode.header == nil + } } // MARK: - Dynamic Properties @@ -92,12 +100,11 @@ extension AuthViewController { // extension AuthViewController { - @objc(refreshInterfaceWithAnimation:) - func refreshInterface(animated: Bool) { + @objc(refreshInterface) + func refreshInterface() { clearAuthenticationError() refreshActionViews() refreshInputViews() - refreshVisibleComponents(animated: animated) } private func refreshActionViews() { @@ -140,45 +147,6 @@ extension AuthViewController { } - /// Shows / Hides relevant components, based on the specified state - /// - func refreshVisibleComponents(animated: Bool) { - if animated { - refreshVisibleComponentsWithAnimation() - } else { - refreshVisibleComponentsWithoutAnimation() - } - } - - /// Shows / Hides relevant components, based on the specified state - /// - Note: Trust me on this one. It's cleaner to have specific methods, rather than making a single one support the `animated` flag. - /// Notice that AppKit requires us to go thru `animator()`. - /// - func refreshVisibleComponentsWithoutAnimation() { - passwordFieldHeightConstraint.constant = mode.passwordFieldHeight - - passwordField.alphaValue = mode.passwordFieldAlpha - - simplenoteTitleView.isHidden = !mode.isIntroView - simplenoteSubTitleView.isHidden = !mode.isIntroView - - headerLabel.isHidden = mode.header == nil - } - - /// Animates Visible / Invisible components, based on the specified state - /// - func refreshVisibleComponentsWithAnimation() { - NSAnimationContext.runAnimationGroup { context in - context.duration = AppKitConstants.duration0_2 - - passwordFieldHeightConstraint.animator().constant = mode.passwordFieldHeight - - passwordField.alphaValue = mode.passwordFieldAlpha - - view.layoutSubtreeIfNeeded() - } - } - /// Drops any Errors onscreen /// @objc @@ -205,7 +173,7 @@ extension AuthViewController { return } - refreshInterface(animated: true) + refreshInterface() ensureUsernameIsFirstResponder() } diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 9af9b93b2..5f63d5cfe 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -38,7 +38,7 @@ - (void)viewDidLoad [super viewDidLoad]; [self setupInterface]; - [self refreshInterfaceWithAnimation:NO]; + [self refreshInterface]; [self startListeningToNotifications]; } diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 3ebc667ba..a3985a5c2 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -52,8 +52,7 @@ class AuthenticationMode: NSObject { let actions: [AuthenticationActionDescriptor] let primaryActionAnimationText: String - - let isPasswordVisible: Bool + let isIntroView: Bool init(title: String, @@ -61,32 +60,16 @@ class AuthenticationMode: NSObject { inputElements: AuthenticationInputElements, actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, - isPasswordVisible: Bool, isIntroView: Bool = false) { self.title = title self.header = header self.inputElements = inputElements self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText - self.isPasswordVisible = isPasswordVisible self.isIntroView = isIntroView } } -// MARK: - Dynamic Properties -// -extension AuthenticationMode { - - var passwordFieldHeight: CGFloat { - isPasswordVisible ? CGFloat(40) : .zero - } - - var passwordFieldAlpha: CGFloat { - isPasswordVisible ? AppKitConstants.alpha1_0 : AppKitConstants.alpha0_0 - } -} - - // MARK: - Static Properties // extension AuthenticationMode { @@ -104,7 +87,6 @@ extension AuthenticationMode { text: LoginStrings.primaryAction.uppercased()) ], primaryActionAnimationText: SignupStrings.primaryAnimationText, - isPasswordVisible: false, isIntroView: true) } @@ -122,8 +104,7 @@ extension AuthenticationMode { selector: #selector(AuthViewController.openForgotPasswordURL), text: LoginStrings.secondaryAction) ], - primaryActionAnimationText: LoginStrings.primaryAnimationText, - isPasswordVisible: true) + primaryActionAnimationText: LoginStrings.primaryAnimationText) } /// Auth Mode: Login is handled via Magic Links! @@ -140,8 +121,7 @@ extension AuthenticationMode { AuthenticationActionDescriptor(name: .tertiary, selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) ], - primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, - isPasswordVisible: false) + primaryActionAnimationText: MagicLinkStrings.primaryAnimationText) } /// Auth Mode: SignUp @@ -155,8 +135,7 @@ extension AuthenticationMode { selector: #selector(AuthViewController.pressedSignUp), text: SignupStrings.primaryAction) ], - primaryActionAnimationText: SignupStrings.primaryAnimationText, - isPasswordVisible: false) + primaryActionAnimationText: SignupStrings.primaryAnimationText) } /// Login with Code: Submit Code + Authenticate the user @@ -173,8 +152,7 @@ extension AuthenticationMode { selector: #selector(AuthViewController.pushPasswordView), text: NSLocalizedString("Enter password", comment: "Enter Password fallback Action")), ], - primaryActionAnimationText: "", - isPasswordVisible: false) + primaryActionAnimationText: "") } } From 50b48297a5cb56291f272a5635c204517ac37c88 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 24 Jul 2024 15:45:37 -0300 Subject: [PATCH 20/84] NavigationController: Drops Back Button Height Animation --- Simplenote/SPNavigationController.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 221ee3001..a1bce90f3 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -7,8 +7,6 @@ class SPNavigationController: NSViewController { private var viewStack: [NSViewController] = [] private var backButton: NSButton! - private var backButtonHeightConstraint: NSLayoutConstraint! - var hideBackButton: Bool { viewStack.count < 2 } @@ -59,12 +57,11 @@ class SPNavigationController: NSViewController { button.bezelStyle = .accessoryBarAction view.addSubview(backButton) - backButtonHeightConstraint = backButton.heightAnchor.constraint(equalToConstant: 0) NSLayoutConstraint.activate([ backButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10), backButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 30), backButton.widthAnchor.constraint(equalToConstant: 50), - backButtonHeightConstraint + backButton.heightAnchor.constraint(equalToConstant: 30) ]) return button @@ -175,7 +172,6 @@ class SPNavigationController: NSViewController { fadingView?.animator().alphaValue = alpha leadingConstraint.animator().constant += view.frame.width * multiplier trailingConstraint.animator().constant += view.frame.width * multiplier - backButtonHeightConstraint.animator().constant = hideBackButton ? 0 : 30 backButton.animator().isHidden = hideBackButton } completionHandler: { onCompletion() From 4ec31a7649adc134df9c7cd2307274e030e2a1c2 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 24 Jul 2024 15:46:14 -0300 Subject: [PATCH 21/84] NavigationController: Updates Attach API Signature --- Simplenote/SPNavigationController.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index a1bce90f3..45d695fe4 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -82,7 +82,7 @@ class SPNavigationController: NSViewController { currentView.removeConstraints(currentView.constraints) } - guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, currentView: currentView) else { + guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, below: currentView) else { return } @@ -104,12 +104,13 @@ class SPNavigationController: NSViewController { } @discardableResult - private func attachView(subview: NSView, currentView: NSView?, behindCurrent: Bool = false) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { - if behindCurrent { - view.addSubview(subview, positioned: .below, relativeTo: currentView) + private func attachView(subview: NSView, below siblingView: NSView?) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { + if let siblingView { + view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) } + subview.translatesAutoresizingMaskIntoConstraints = false let leadingAnchor = subview.leadingAnchor.constraint(equalTo: view.leadingAnchor) @@ -136,9 +137,9 @@ class SPNavigationController: NSViewController { guard let nextViewController = viewStack.last else { return } - - attachView(subview: nextViewController.view, currentView: currentViewController.view, behindCurrent: true) - + + attachView(subview: nextViewController.view, below: currentViewController.view) + animateTransition(slidingView: currentViewController.view, fadingView: nextViewController.view, direction: .leadingToTrailing) { self.dettach(child: currentViewController) } From c88bfdca644c0f62556c75406c2f22bbbd47f363 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 24 Jul 2024 15:46:33 -0300 Subject: [PATCH 22/84] NavigationController: Updates popViewController API --- Simplenote/SPNavigationController.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 45d695fe4..e22c326a7 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -129,12 +129,7 @@ class SPNavigationController: NSViewController { // MARK: - Remove view from stack func popViewController() { - guard viewStack.count > 1 else { - return - } - - let currentViewController = viewStack.removeLast() - guard let nextViewController = viewStack.last else { + guard viewStack.count > 1, let currentViewController = viewStack.popLast(), let nextViewController = viewStack.last else { return } From 1aaa3ac3a0159e27b76c1cd5942363763a3525e3 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 24 Jul 2024 15:48:27 -0300 Subject: [PATCH 23/84] NavigationController --- Simplenote/SPNavigationController.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index e22c326a7..9cfaa69d8 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -38,12 +38,19 @@ class SPNavigationController: NSViewController { initialView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(initialView) + + /// "Hint" we wanna occupy as little as possible. This constraint is meant to be broken, but the layout system will + /// attempt to reduce the Height, when possible + /// + let minimumHeightConstraint = view.heightAnchor.constraint(equalToConstant: .zero) + minimumHeightConstraint.priority = .init(1) NSLayoutConstraint.activate([ initialView.leadingAnchor.constraint(equalTo: view.leadingAnchor), initialView.trailingAnchor.constraint(equalTo: view.trailingAnchor), initialView.topAnchor.constraint(equalTo: backButton.bottomAnchor), - initialView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + initialView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + minimumHeightConstraint ]) } From 70a3188f34f5eea093281cd30e00dfaa73247afc Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 12:49:31 -0600 Subject: [PATCH 24/84] Fixed animation names on primary button --- Simplenote/AuthViewController+Swift.swift | 3 +-- Simplenote/AuthenticationMode.swift | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index e8e0a9707..aa3cd5143 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -354,8 +354,7 @@ extension AuthViewController { @objc func stopActionAnimation() { - //TODO: Fix animating title changes -// actionButton.title = mode.primaryActionText + actionButton.title = mode.action(withName: .primary)?.text ?? String() actionProgress.stopAnimation(nil) } } diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index a3985a5c2..39a9a50f3 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -70,6 +70,14 @@ class AuthenticationMode: NSObject { } } +// MARK: - Convenience Properties +// +extension AuthenticationMode { + func action(withName name: AuthenticationActionName) -> AuthenticationActionDescriptor? { + actions.first(where: { $0.name == name }) + } +} + // MARK: - Static Properties // extension AuthenticationMode { @@ -152,7 +160,7 @@ extension AuthenticationMode { selector: #selector(AuthViewController.pushPasswordView), text: NSLocalizedString("Enter password", comment: "Enter Password fallback Action")), ], - primaryActionAnimationText: "") + primaryActionAnimationText: LoginStrings.primaryAnimationText) } } From 4422b7bc6c2884606dc5c6a651ec293c78426464 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Wed, 24 Jul 2024 15:50:06 -0300 Subject: [PATCH 25/84] NavigationController: Fixes resizing animation --- Simplenote/SPNavigationController.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 9cfaa69d8..07dedd73d 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -85,8 +85,12 @@ class SPNavigationController: NSViewController { let currentView = topViewController?.view attach(child: viewController) - if let currentView { - currentView.removeConstraints(currentView.constraints) + + /// Disable Bottom Constraint + /// This allows for the enclosing NSWindow to resize, just enough to fit the `nextViewController.view` + /// + if let currentView, let bottomConstraint = view.firstContraint(firstView: currentView, firstAttribute: .bottom) { + bottomConstraint.isActive = false } guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, below: currentView) else { @@ -139,6 +143,13 @@ class SPNavigationController: NSViewController { guard viewStack.count > 1, let currentViewController = viewStack.popLast(), let nextViewController = viewStack.last else { return } + + /// Disable Bottom Constraint + /// This allows for the enclosing NSWindow to resize, just enough to fit the `nextViewController.view` + /// + if let currentBottomConstraint = view.firstContraint(firstView: currentViewController.view, firstAttribute: .bottom) { + currentBottomConstraint.isActive = false + } attachView(subview: nextViewController.view, below: currentViewController.view) From 8d04f77d1ef77a557594693898eeafff728920fc Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 13:05:10 -0600 Subject: [PATCH 26/84] Added error handling on code auth --- Simplenote/AuthViewController+Swift.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index aa3cd5143..96fe5e81d 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -285,7 +285,8 @@ extension AuthViewController { let confirmation = try await remote.requestLoginConfirmation(email: username, authCode: code.uppercased()) authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) } catch { - //TODO: Handle errors + let statusCode = (error as? RemoteError)?.statusCode ?? .zero + self.showAuthenticationError(forCode: statusCode, responseString: nil) } } From 8b257664f2254976fc2e90e1cbd87d3fa337c431 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 13:22:24 -0600 Subject: [PATCH 27/84] Fixed issue where on signup > back the buttons don't work --- Simplenote/SignupVerificationViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simplenote/SignupVerificationViewController.swift b/Simplenote/SignupVerificationViewController.swift index 7878e8159..54258de67 100644 --- a/Simplenote/SignupVerificationViewController.swift +++ b/Simplenote/SignupVerificationViewController.swift @@ -126,8 +126,9 @@ private extension SignupVerificationViewController { func presentAuthenticationInteface() { let authViewController = AuthViewController() + let navigationController = SPNavigationController(initialViewController: authViewController) authViewController.authenticator = authenticator - view.window?.transition(to: authViewController) + view.window?.transition(to: navigationController) } } From 6c22d00eeecd6d277a6447b7b1b48997bc5443a4 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 13:27:16 -0600 Subject: [PATCH 28/84] Fixed hitting enter in auth text field not running the primary command --- Simplenote/AuthViewController+Swift.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 96fe5e81d..94cefd466 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -311,7 +311,7 @@ extension AuthViewController { @IBAction func handleNewlineInField(_ field: NSControl) { - if field.isEqual(passwordField.textField) { + if shouldHandleNewLine(in: field) { guard let primaryActionDescriptor = mode.actions.first(where: { $0.name == .primary }) else { assertionFailure() return @@ -322,6 +322,10 @@ extension AuthViewController { } } + func shouldHandleNewLine(in field: NSControl) -> Bool { + field.isEqual(usernameField.textField) || field.isEqual(passwordField.textField) || field.isEqual(codeTextField.textField) + } + @objc func updateState(with object: Any) { guard let field = object as? NSTextField, From 889148765867259eba5d086ab1ad79ac1b8e463f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 13:37:29 -0600 Subject: [PATCH 29/84] Make first text field take first responder on authVC appear --- Simplenote/AuthViewController+Swift.swift | 18 +++++++++++++++--- Simplenote/AuthViewController.m | 1 - 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 94cefd466..ea769fe4d 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -69,6 +69,10 @@ extension AuthViewController { headerLabel.isHidden = mode.header == nil } + + open override func viewDidAppear() { + ensureFirstTextFieldIsFirstResponder() + } } // MARK: - Dynamic Properties @@ -94,6 +98,14 @@ extension AuthViewController { private var allActionViews: [NSButton] { [actionButton, secondaryActionButton, tertiaryButton, quarternaryButton] } + + private var allInputViews: [SPAuthenticationTextField] { + [usernameField, passwordField, codeTextField] + } + + private var firstVisibleTextField: SPAuthenticationTextField? { + allInputViews.first(where: { $0.isHidden == false }) + } } // MARK: - Refreshing @@ -157,8 +169,8 @@ extension AuthViewController { /// Marks the Username Field as the First Responder /// @objc - func ensureUsernameIsFirstResponder() { - usernameField?.textField.becomeFirstResponder() + func ensureFirstTextFieldIsFirstResponder() { + firstVisibleTextField?.textField.becomeFirstResponder() view.needsDisplay = true } } @@ -174,7 +186,7 @@ extension AuthViewController { } refreshInterface() - ensureUsernameIsFirstResponder() + ensureFirstTextFieldIsFirstResponder() } private func authViewController(with mode: AuthenticationMode, state: AuthenticationState) -> AuthViewController { diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 5f63d5cfe..ef481b588 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -68,7 +68,6 @@ - (void)openForgotPasswordURL { - (void)setMode:(AuthenticationMode *)mode { _mode = mode; - [self didUpdateAuthenticationMode]; } From b00cd56f3ddccd742fc7b50b943e07411eb9824b Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 13:38:23 -0600 Subject: [PATCH 30/84] added super.viewDidAppear to override method in authVC --- Simplenote/AuthViewController+Swift.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index ea769fe4d..3ad94d224 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -71,6 +71,8 @@ extension AuthViewController { } open override func viewDidAppear() { + super.viewDidAppear() + ensureFirstTextFieldIsFirstResponder() } } From 01b6dad23e7d13f845a5379fc48e3d4ade8dd163 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 13:39:28 -0600 Subject: [PATCH 31/84] Dropped unused setMode method on auth VC --- Simplenote/AuthViewController.m | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index ef481b588..afbb7d3ff 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -63,14 +63,6 @@ - (void)openForgotPasswordURL { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:forgotPasswordURL]]; } - -#pragma mark - Dynamic Properties - -- (void)setMode:(AuthenticationMode *)mode { - _mode = mode; -} - - #pragma mark - Interface Helpers - (void)setInterfaceEnabled:(BOOL)enabled { From 27d624c92a8d992534cc48945092f5d2d0560b5d Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 14:23:31 -0600 Subject: [PATCH 32/84] Added header text to auth view --- Simplenote/AuthViewController+Swift.swift | 11 ++++++++++- Simplenote/AuthViewController.xib | 2 +- Simplenote/AuthenticationMode.swift | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 3ad94d224..d50865881 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -45,6 +45,7 @@ extension AuthViewController { setupActionsSeparatorView() setupAdditionalButtons() setupLabels() + setupHeaderView() } private func setupActionsSeparatorView() { @@ -66,8 +67,16 @@ extension AuthViewController { private func setupLabels() { simplenoteTitleView.isHidden = !mode.isIntroView simplenoteSubTitleView.isHidden = !mode.isIntroView + } + + private func setupHeaderView() { + guard let headerText = mode.buildHeaderText(email: state.username) else { + headerLabel.isHidden = true + return + } - headerLabel.isHidden = mode.header == nil + headerLabel.attributedStringValue = headerText + headerLabel.isHidden = false } open override func viewDidAppear() { diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index c462da3a0..202b83429 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -68,7 +68,7 @@ - + diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 39a9a50f3..b45a80eaf 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -78,6 +78,26 @@ extension AuthenticationMode { } } +// MARK: - Public Properties +// +extension AuthenticationMode { + + func buildHeaderText(email: String) -> NSAttributedString? { + guard let header = header?.replacingOccurrences(of: "{{EMAIL}}", with: email) else { + return nil + } + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + + return NSMutableAttributedString(string: header, attributes: [ + .font: NSFont.systemFont(ofSize: 16, weight: .regular), + .paragraphStyle: paragraphStyle + ], highlighting: email, highlightAttributes: [ + .font: NSFont.systemFont(ofSize: 16, weight: .bold) + ]) + } +} + // MARK: - Static Properties // extension AuthenticationMode { From 5df2e1e8ab63587cf07d14d80f12a4d2a1d61649 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 14:24:44 -0600 Subject: [PATCH 33/84] Fixed enter password border to correct size --- Simplenote/AuthViewController+Swift.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index d50865881..f42eafca8 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -38,7 +38,7 @@ extension AuthViewController { // quarternary button quarternaryButtonView.wantsLayer = true - quarternaryButtonView.layer?.borderWidth = 2 + quarternaryButtonView.layer?.borderWidth = 1 quarternaryButtonView.layer?.borderColor = .black quarternaryButtonView.layer?.cornerRadius = 5 From 2eed460cfe7d4c801c29ed12dfdde7cc88f997dc Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 14:40:51 -0600 Subject: [PATCH 34/84] Added validation of login code input --- Simplenote/AuthViewController+Swift.swift | 6 ++++++ Simplenote/AuthViewController.h | 1 + Simplenote/AuthViewController.m | 14 ++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index f42eafca8..553793041 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -299,6 +299,12 @@ extension AuthViewController { setInterfaceEnabled(true) } + clearAuthenticationError() + + if !validateCode() { + return + } + startActionAnimation() setInterfaceEnabled(false) diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 2d7ac9194..26e05eac4 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -44,6 +44,7 @@ - (void)pressedLoginWithMagicLink; - (void)pressedSignUp; - (void)openForgotPasswordURL; +- (BOOL)validateCode; - (void)setInterfaceEnabled:(BOOL)enabled; diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index afbb7d3ff..51fca537f 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -254,6 +254,15 @@ - (BOOL)mustUpgradePasswordStrength { return [self.validator mustPerformPasswordResetWithUsername:self.usernameText password:self.passwordText]; } +- (BOOL)validateCodeInput { + if (self.state.code.length >= 6) { + return YES; + } + + [self showAuthenticationError:NSLocalizedString(@"Login Code is too short", comment: @"Message displayed when a login code is too short")]; + return NO; +} + - (BOOL)validateSignIn { return [self validateConnection] && [self validateUsername] && @@ -270,6 +279,11 @@ - (BOOL)validateSignUp { [self validateUsername]; } +- (BOOL)validateCode { + return [self validateConnection] && + [self validateCodeInput]; +} + - (void)showAuthenticationError:(NSString *)errorMessage { [self.errorField setStringValue:errorMessage]; } From 76575476b171ba59445906fd34826ede95c1822f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 14:51:01 -0600 Subject: [PATCH 35/84] Set progress indicator on auth view to white --- Simplenote/AuthViewController+Swift.swift | 10 ++++++---- .../NSProgressIndicator+Simplenote.swift | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 553793041..1e1cc2ec0 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -42,6 +42,8 @@ extension AuthViewController { quarternaryButtonView.layer?.borderColor = .black quarternaryButtonView.layer?.cornerRadius = 5 + actionProgress.set(tintColor: .white) + setupActionsSeparatorView() setupAdditionalButtons() setupLabels() @@ -275,15 +277,15 @@ extension AuthViewController { stopActionAnimation() setInterfaceEnabled(true) } - + startActionAnimation() setInterfaceEnabled(false) do { - let email = usernameText - let remote = LoginRemote() - try await remote.requestLoginEmail(email: email) +// let email = usernameText +// let remote = LoginRemote() +// try await remote.requestLoginEmail(email: email) pushCodeLoginView() } catch { diff --git a/Simplenote/NSProgressIndicator+Simplenote.swift b/Simplenote/NSProgressIndicator+Simplenote.swift index aa253a70a..50c2d71cc 100644 --- a/Simplenote/NSProgressIndicator+Simplenote.swift +++ b/Simplenote/NSProgressIndicator+Simplenote.swift @@ -17,4 +17,24 @@ extension NSProgressIndicator { progressIndicator.startAnimation(view) return progressIndicator } + + func set(tintColor: NSColor) { + guard let adjustedTintColor = NSColor.white.usingColorSpace(.deviceRGB) else { + return + } + + let tintColorRedComponent = adjustedTintColor.redComponent + let tintColorGreenComponent = adjustedTintColor.greenComponent + let tintColorBlueComponent = adjustedTintColor.blueComponent + + let tintColorMinComponentsVector = CIVector(x: tintColorRedComponent, y: tintColorGreenComponent, z: tintColorBlueComponent, w: 0.0) + let tintColorMaxComponentsVector = CIVector(x: tintColorRedComponent, y: tintColorGreenComponent, z: tintColorBlueComponent, w: 1.0) + + let colorClampFilter = CIFilter(name: "CIColorClamp")! + colorClampFilter.setDefaults() + colorClampFilter.setValue(tintColorMinComponentsVector, forKey: "inputMinComponents") + colorClampFilter.setValue(tintColorMaxComponentsVector, forKey: "inputMaxComponents") + + contentFilters = [colorClampFilter] + } } From b87ecff6fd6779c178ffdb812af42ed55dbcc9ca Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 15:00:37 -0600 Subject: [PATCH 36/84] Removed commenting out some code that wasn't supposed to get committed --- Simplenote/AuthViewController+Swift.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 1e1cc2ec0..d093c42fb 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -283,9 +283,9 @@ extension AuthViewController { do { -// let email = usernameText -// let remote = LoginRemote() -// try await remote.requestLoginEmail(email: email) + let email = usernameText + let remote = LoginRemote() + try await remote.requestLoginEmail(email: email) pushCodeLoginView() } catch { From e9dcc5cb2b25da03f27015a32fb573f57446d328 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 15:14:22 -0600 Subject: [PATCH 37/84] Updated quarternary button color --- Simplenote/AuthViewController+Swift.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index d093c42fb..3e75d567b 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -41,6 +41,7 @@ extension AuthViewController { quarternaryButtonView.layer?.borderWidth = 1 quarternaryButtonView.layer?.borderColor = .black quarternaryButtonView.layer?.cornerRadius = 5 + quarternaryButton.contentTintColor = .black actionProgress.set(tintColor: .white) From 3c11525be489c8ff9521e3da71e05f83f10466f9 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Wed, 24 Jul 2024 15:46:18 -0600 Subject: [PATCH 38/84] Refactored how the auth view controller is initialized --- Simplenote/AuthViewController+Swift.swift | 24 ++++++++++------------- Simplenote/AuthViewController.h | 1 + Simplenote/AuthViewController.m | 12 ++++++++++++ Simplenote/AuthWindowController.swift | 2 +- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 3e75d567b..1707364fc 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -6,10 +6,6 @@ extension AuthViewController { @objc func setupInterface() { - if state == nil { - state = AuthenticationState() - } - simplenoteTitleView.stringValue = "Simplenote" simplenoteSubTitleView.textColor = .simplenoteGray50Color simplenoteSubTitleView.stringValue = NSLocalizedString("The simplest way to keep notes.", comment: "Simplenote subtitle") @@ -203,32 +199,32 @@ extension AuthViewController { ensureFirstTextFieldIsFirstResponder() } - private func authViewController(with mode: AuthenticationMode, state: AuthenticationState) -> AuthViewController { - let vc = AuthViewController() - vc.authenticator = authenticator - vc.state = state - vc.mode = mode + private func pushNewAuthViewController(with mode: AuthenticationMode, state: AuthenticationState) { + guard let authVC = AuthViewController(mode: mode, state: state) else { + return + } + authVC.authenticator = authenticator - return vc + containingNavigationController?.push(authVC) } @objc func pushEmailLoginView() { - containingNavigationController?.push(authViewController(with: .requestLoginCode, state: state)) + pushNewAuthViewController(with: .requestLoginCode, state: state) } @objc func pushSignupView() { - containingNavigationController?.push(authViewController(with: .signup, state: state)) + pushNewAuthViewController(with: .signup, state: state) } @objc func pushPasswordView() { - containingNavigationController?.push(authViewController(with: .loginWithPassword(), state: state)) + pushNewAuthViewController(with: .loginWithPassword(), state: state) } func pushCodeLoginView() { - containingNavigationController?.push(authViewController(with: .loginWithCode, state: state)) + pushNewAuthViewController(with: .loginWithCode, state: state) } } diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 26e05eac4..6e5cf8836 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -40,6 +40,7 @@ @property (nonatomic, strong) AuthenticationMode *mode; @property (nonatomic, strong) AuthenticationState *state; +- (instancetype)initWithMode:(AuthenticationMode*)mode state:(AuthenticationState*)state; - (void)pressedLogInWithPassword; - (void)pressedLoginWithMagicLink; - (void)pressedSignUp; diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 51fca537f..cc1092376 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -23,11 +23,23 @@ - (void)dealloc [[NSNotificationCenter defaultCenter] removeObserver: self]; } +- (instancetype)initWithMode:(AuthenticationMode*)mode state:(AuthenticationState*)state; +{ + if (self = [super init]) { + self.validator = [SPAuthenticationValidator new]; + self.mode = mode; + self.state = state; + } + + return self; +} + - (instancetype)init { if (self = [super init]) { self.validator = [SPAuthenticationValidator new]; self.mode = [AuthenticationMode onboarding]; + self.state = [AuthenticationState new]; } return self; diff --git a/Simplenote/AuthWindowController.swift b/Simplenote/AuthWindowController.swift index cf9ab2256..27487b96f 100644 --- a/Simplenote/AuthWindowController.swift +++ b/Simplenote/AuthWindowController.swift @@ -26,7 +26,7 @@ class AuthWindowController: NSWindowController, SPAuthenticationInterface { } init() { - self.authViewController = AuthViewController() + self.authViewController = AuthViewController(mode: .onboarding, state: AuthenticationState()) let navigationController = SPNavigationController(initialViewController: authViewController) let window = NSWindow(contentViewController: navigationController) window.styleMask = [.borderless, .closable, .titled, .fullSizeContentView] From 3f63677e797074bb1b678900c4131f43c1364e67 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 25 Jul 2024 11:05:45 -0600 Subject: [PATCH 39/84] Show code input on auth view controller --- Simplenote/AuthViewController.xib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController.xib b/Simplenote/AuthViewController.xib index 202b83429..99cfdb123 100644 --- a/Simplenote/AuthViewController.xib +++ b/Simplenote/AuthViewController.xib @@ -106,7 +106,7 @@ - + From 78b48bd9352fcbf8fb1b34915f7af8ba2b2c68e8 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 25 Jul 2024 11:12:13 -0600 Subject: [PATCH 40/84] Added password header to sp auth with password view --- Simplenote/AuthViewController+Swift.swift | 4 ++-- Simplenote/AuthenticationMode.swift | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 1707364fc..e3c5a0b57 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -220,7 +220,7 @@ extension AuthViewController { @objc func pushPasswordView() { - pushNewAuthViewController(with: .loginWithPassword(), state: state) + pushNewAuthViewController(with: .loginWithPassword, state: state) } func pushCodeLoginView() { @@ -235,7 +235,7 @@ extension AuthViewController { @IBAction func switchToPasswordAuth(_ sender: Any) { - mode = AuthenticationMode.loginWithPassword(header: nil) + mode = AuthenticationMode.loginWithPassword } @objc diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index b45a80eaf..3dbd75039 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -120,9 +120,9 @@ extension AuthenticationMode { /// Auth Mode: Login with Username + Password /// - static func loginWithPassword(header: String? = nil) -> AuthenticationMode { + static var loginWithPassword: AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In with Password", comment: "LogIn Interface Title"), - header: header, + header: LoginStrings.loginWithEmailEmailHeader, inputElements: [.password], actions: [ AuthenticationActionDescriptor(name: .primary, @@ -195,6 +195,7 @@ private enum LoginStrings { static let switchAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") static let switchTip = NSLocalizedString("Need an account?", comment: "Link to create an account") static let wordpressAction = NSLocalizedString("Log in with WordPress.com", comment: "Title to use wordpress login instead of email") + static let loginWithEmailEmailHeader = NSLocalizedString("Enter the password for the account {{EMAIL}}", comment: "Header for Login With Password. Please preserve the {{EMAIL}} substring") } private enum MagicLinkStrings { From 3d352de5352a5a4b2d6fbe010d6de59587a6ef6e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 25 Jul 2024 11:23:16 -0600 Subject: [PATCH 41/84] Dont highlight email text when becoming first responder on back --- Simplenote.xcodeproj/project.pbxproj | 4 ++++ Simplenote/AuthViewController+Swift.swift | 8 ++++---- .../SPAuthenticationTextField+Simplenote.swift | 13 +++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 Simplenote/SPAuthenticationTextField+Simplenote.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 274aef269..f4bc06741 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -338,6 +338,7 @@ BACA687C2C0A764C005409C1 /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BACA687A2C0A7634005409C1 /* KeychainPasswordItem.swift */; }; BAD4ECC026E6FC0A00881CC4 /* markdown-light.css in Resources */ = {isa = PBXBuildFile; fileRef = BAF8D5DB26AE3BE800CA9383 /* markdown-light.css */; }; BAE66CAA26AF647500398FF3 /* Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA938CEB26ACFF4A00BE5A1D /* Remote.swift */; }; + BAEBDD4A2C52C14500335700 /* SPAuthenticationTextField+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAEBDD492C52C14500335700 /* SPAuthenticationTextField+Simplenote.swift */; }; BAF1B0C32BC9B1C500B55F73 /* NoteWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF1B0C12BC9B1C500B55F73 /* NoteWindow.swift */; }; BAF1B0C92BCEDFD400B55F73 /* NoteWindowControllersManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF1B0C72BCDDA9600B55F73 /* NoteWindowControllersManager.swift */; }; BAF4E5DE2C49C08A009B891F /* SPNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF4E5DD2C49C08A009B891F /* SPNavigationController.swift */; }; @@ -807,6 +808,7 @@ BACA68752C0A72F3005409C1 /* NSString+Intents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSString+Intents.swift"; sourceTree = ""; }; BACA68772C0A7537005409C1 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; BACA687A2C0A7634005409C1 /* KeychainPasswordItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainPasswordItem.swift; sourceTree = ""; }; + BAEBDD492C52C14500335700 /* SPAuthenticationTextField+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPAuthenticationTextField+Simplenote.swift"; sourceTree = ""; }; BAF1B0C12BC9B1C500B55F73 /* NoteWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWindow.swift; sourceTree = ""; }; BAF1B0C72BCDDA9600B55F73 /* NoteWindowControllersManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteWindowControllersManager.swift; sourceTree = ""; }; BAF4E5DD2C49C08A009B891F /* SPNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPNavigationController.swift; sourceTree = ""; }; @@ -1244,6 +1246,7 @@ BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */, BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */, BAF4E5DF2C49C17A009B891F /* NSViewController+Simplenote.swift */, + BAEBDD492C52C14500335700 /* SPAuthenticationTextField+Simplenote.swift */, ); name = Extensions; sourceTree = ""; @@ -2243,6 +2246,7 @@ B5ACE42F24785D8C00AB02C7 /* BackgroundView.swift in Sources */, B5F5415525F0137100CAF52C /* MagicLinkAuthenticator.swift in Sources */, B58A71BF258422CC00601641 /* NSBezierPath+Simplenote.swift in Sources */, + BAEBDD4A2C52C14500335700 /* SPAuthenticationTextField+Simplenote.swift in Sources */, B587D7EE2C2217B1006645CF /* LoginRemoteProtocol.swift in Sources */, BAF4E5DE2C49C08A009B891F /* SPNavigationController.swift in Sources */, B5DD0F922476309000C8DD41 /* NoteTableCellView.swift in Sources */, diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index e3c5a0b57..61f3ff68b 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -180,7 +180,7 @@ extension AuthViewController { /// @objc func ensureFirstTextFieldIsFirstResponder() { - firstVisibleTextField?.textField.becomeFirstResponder() + firstVisibleTextField?.becomeFirstResponder() view.needsDisplay = true } } @@ -280,9 +280,9 @@ extension AuthViewController { do { - let email = usernameText - let remote = LoginRemote() - try await remote.requestLoginEmail(email: email) +// let email = usernameText +// let remote = LoginRemote() +// try await remote.requestLoginEmail(email: email) pushCodeLoginView() } catch { diff --git a/Simplenote/SPAuthenticationTextField+Simplenote.swift b/Simplenote/SPAuthenticationTextField+Simplenote.swift new file mode 100644 index 000000000..69adb2d83 --- /dev/null +++ b/Simplenote/SPAuthenticationTextField+Simplenote.swift @@ -0,0 +1,13 @@ +import Simperium_OSX + +extension SPAuthenticationTextField { + @discardableResult + override open func becomeFirstResponder() -> Bool { + let responderStatus = textField.becomeFirstResponder() + + let selectedRange = textField.currentEditor()?.selectedRange + textField.currentEditor()?.selectedRange = NSMakeRange(selectedRange?.length ?? .zero, .zero) + + return responderStatus + } +} From 2e972b53d95d4c50301843245c19ff03b8cb4784 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 25 Jul 2024 11:29:55 -0600 Subject: [PATCH 42/84] Fixed issue going to link invalid view back made auth not clickable --- Simplenote/AuthWindowController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simplenote/AuthWindowController.swift b/Simplenote/AuthWindowController.swift index 27487b96f..41ddf6ba9 100644 --- a/Simplenote/AuthWindowController.swift +++ b/Simplenote/AuthWindowController.swift @@ -78,8 +78,9 @@ extension AuthWindowController { } let authViewController = AuthViewController() + let navigationController = SPNavigationController(initialViewController: authViewController) authViewController.authenticator = authenticator - window.transition(to: authViewController) + window.transition(to: navigationController) } func switchToMagicLinkRequestedUI(email: String) { From dda5310bba36f8ee05487416d78a44799f9985a2 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Thu, 25 Jul 2024 11:31:39 -0600 Subject: [PATCH 43/84] Removed comments on code that requests login codes --- Simplenote/AuthViewController+Swift.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 61f3ff68b..93b693ca9 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -280,9 +280,9 @@ extension AuthViewController { do { -// let email = usernameText -// let remote = LoginRemote() -// try await remote.requestLoginEmail(email: email) + let email = usernameText + let remote = LoginRemote() + try await remote.requestLoginEmail(email: email) pushCodeLoginView() } catch { From fd6845f874b2578f62ab5f9e29ee876e2dc80118 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 14:41:52 -0300 Subject: [PATCH 44/84] Wires SimplenoteEndpoints SPM --- Simplenote.xcodeproj/project.pbxproj | 57 ++++++------------- .../xcshareddata/swiftpm/Package.resolved | 11 +++- Simplenote/AccountDeletionController.swift | 2 + .../AccountVerificationController.swift | 2 + Simplenote/AuthViewController+Swift.swift | 2 + Simplenote/MagicLinkAuthenticator.swift | 1 + .../SimperiumAuthenticatorProtocol.swift | 5 ++ ...ntVerificationController+TestHelpers.swift | 9 ++- .../AccountVerificationControllerTests.swift | 1 + .../AccountVerificationRemoteTests.swift | 8 ++- SimplenoteTests/LoginRemoteTests.swift | 3 +- .../MagicLinkAuthenticatorTests.swift | 1 + .../MockAccountVerificationRemote.swift | 2 + SimplenoteTests/MockLoginRemote.swift | 3 +- SimplenoteTests/MockURLSession.swift | 4 +- SimplenoteTests/SignupRemoteTests.swift | 5 +- 16 files changed, 68 insertions(+), 48 deletions(-) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 274aef269..2ba6bf3d0 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -80,7 +80,6 @@ B500993D24213B370037A431 /* String+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500993B24213B370037A431 /* String+Simplenote.swift */; }; B500993F242140500037A431 /* NSStringSimplenoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500993E2421404F0037A431 /* NSStringSimplenoteTests.swift */; }; B501AAD32437E5600084CDA3 /* AppKitConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501AAD12437E5250084CDA3 /* AppKitConstants.swift */; }; - B502C1DE25BA2EB700145D6C /* AccountRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1DC25BA2EB700145D6C /* AccountRemote.swift */; }; B502C1E725BA2F0800145D6C /* AccountVerificationRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1E625BA2F0800145D6C /* AccountVerificationRemoteTests.swift */; }; B502C1EF25BA2F2400145D6C /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1EE25BA2F2400145D6C /* MockURLSession.swift */; }; B502C1F725BA2F3F00145D6C /* MockURLSessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1F625BA2F3F00145D6C /* MockURLSessionDataTask.swift */; }; @@ -105,6 +104,7 @@ B518D37E2507C356006EA7F8 /* StringSimplenoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518D37D2507C356006EA7F8 /* StringSimplenoteTests.swift */; }; B51AFE6E25D30A1800A196DF /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51AFE6C25D30A1800A196DF /* SearchField.swift */; }; B51AFE7725D36CDD00A196DF /* NSFont+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51AFE7525D36CDD00A196DF /* NSFont+Simplenote.swift */; }; + B51D44582C52AB2200F296A7 /* SimplenoteEndpoints in Frameworks */ = {isa = PBXBuildFile; productRef = B51D44572C52AB2200F296A7 /* SimplenoteEndpoints */; }; B51D85F525A8B392005F08CE /* NoteListPrefixFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D85F325A8B392005F08CE /* NoteListPrefixFormatter.swift */; }; B51E9FE222E615FA004F16B4 /* SPExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51E9FE022E615FA004F16B4 /* SPExporter.swift */; }; B51E9FE622E644A0004F16B4 /* NSObject+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51E9FE422E644A0004F16B4 /* NSObject+Helpers.swift */; }; @@ -172,13 +172,10 @@ B58117E325B9E5D200927E0C /* AccountVerificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58117E125B9E5D200927E0C /* AccountVerificationController.swift */; }; B58117E925B9EE7B00927E0C /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58117E725B9EE7B00927E0C /* Button.swift */; }; B586C063245CCD35009508EC /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B586C061245CCD35009508EC /* SplitViewController.swift */; }; - B587D7DE2C21FDDF006645CF /* LoginRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7DD2C21FDDF006645CF /* LoginRemote.swift */; }; - B587D7E22C220765006645CF /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E12C220765006645CF /* URLSessionProtocol.swift */; }; B587D7E42C22086F006645CF /* LoginRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E32C22086F006645CF /* LoginRemoteTests.swift */; }; B587D7E72C2214DB006645CF /* MagicLinkAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E62C2214DB006645CF /* MagicLinkAuthenticatorTests.swift */; }; B587D7EA2C221575006645CF /* SimperiumAuthenticatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E92C221575006645CF /* SimperiumAuthenticatorProtocol.swift */; }; B587D7EC2C2215D9006645CF /* MockSimperiumAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7EB2C2215D9006645CF /* MockSimperiumAuthenticator.swift */; }; - B587D7EE2C2217B1006645CF /* LoginRemoteProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7ED2C2217B1006645CF /* LoginRemoteProtocol.swift */; }; B587D7F02C2217EE006645CF /* MockLoginRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7EF2C2217EE006645CF /* MockLoginRemote.swift */; }; B58942FB24E5EE8E006EC232 /* NSWindowFrameAutosaveName+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58942F924E5EE8E006EC232 /* NSWindowFrameAutosaveName+Simplenote.swift */; }; B58A71BF258422CC00601641 /* NSBezierPath+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58A71BD258422CC00601641 /* NSBezierPath+Simplenote.swift */; }; @@ -255,8 +252,6 @@ B5F04FD721596551004B1AA0 /* PrivacyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F04FD521596551004B1AA0 /* PrivacyViewController.swift */; }; B5F3150A24496A970036872D /* TableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F3150824496A970036872D /* TableRowView.swift */; }; B5F50916247319360042FA1D /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F50914247319360042FA1D /* SplitView.swift */; }; - B5F5414925EFFC2200CAF52C /* SignupRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5414725EFFC2200CAF52C /* SignupRemote.swift */; }; - B5F5414F25EFFC2A00CAF52C /* RemoteConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5414D25EFFC2A00CAF52C /* RemoteConstants.swift */; }; B5F5415525F0137100CAF52C /* MagicLinkAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5415325F0137100CAF52C /* MagicLinkAuthenticator.swift */; }; B5F807CD2481982B0048CBD7 /* Note+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F807CB2481982B0048CBD7 /* Note+Simplenote.swift */; }; B5F807D02481A2010048CBD7 /* Simperium+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F807CE2481A2010048CBD7 /* Simperium+Simplenote.swift */; }; @@ -292,7 +287,6 @@ BA94CB6F2C0E4AFA00B34EA7 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA94CB6E2C0E4AFA00B34EA7 /* Downloader.swift */; }; BAA0A88B26BA39150006260E /* Date+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A88626B9F8B40006260E /* Date+Simplenote.swift */; }; BAA0A88F26BA39200006260E /* AccountDeletionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A88126B9F8970006260E /* AccountDeletionController.swift */; }; - BAA0A89326BA39260006260E /* RemoteError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A87926B9F0B50006260E /* RemoteError.swift */; }; BAAA71D52C07A10E00244C01 /* SPManagedObject+Intents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAA71D42C07A10E00244C01 /* SPManagedObject+Intents.swift */; }; BAAA71D72C07A11500244C01 /* Note+Intents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAA71D62C07A11500244C01 /* Note+Intents.swift */; }; BAAA71D82C07A12F00244C01 /* NoteContentHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6672FB225C7F12F00090DE3 /* NoteContentHelper.swift */; }; @@ -337,7 +331,6 @@ BACA687B2C0A7634005409C1 /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BACA687A2C0A7634005409C1 /* KeychainPasswordItem.swift */; }; BACA687C2C0A764C005409C1 /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BACA687A2C0A7634005409C1 /* KeychainPasswordItem.swift */; }; BAD4ECC026E6FC0A00881CC4 /* markdown-light.css in Resources */ = {isa = PBXBuildFile; fileRef = BAF8D5DB26AE3BE800CA9383 /* markdown-light.css */; }; - BAE66CAA26AF647500398FF3 /* Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA938CEB26ACFF4A00BE5A1D /* Remote.swift */; }; BAF1B0C32BC9B1C500B55F73 /* NoteWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF1B0C12BC9B1C500B55F73 /* NoteWindow.swift */; }; BAF1B0C92BCEDFD400B55F73 /* NoteWindowControllersManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF1B0C72BCDDA9600B55F73 /* NoteWindowControllersManager.swift */; }; BAF4E5DE2C49C08A009B891F /* SPNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF4E5DD2C49C08A009B891F /* SPNavigationController.swift */; }; @@ -496,7 +489,6 @@ B500993B24213B370037A431 /* String+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Simplenote.swift"; sourceTree = ""; }; B500993E2421404F0037A431 /* NSStringSimplenoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSStringSimplenoteTests.swift; sourceTree = ""; }; B501AAD12437E5250084CDA3 /* AppKitConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKitConstants.swift; sourceTree = ""; }; - B502C1DC25BA2EB700145D6C /* AccountRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRemote.swift; sourceTree = ""; }; B502C1E625BA2F0800145D6C /* AccountVerificationRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationRemoteTests.swift; sourceTree = ""; }; B502C1EE25BA2F2400145D6C /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; B502C1F625BA2F3F00145D6C /* MockURLSessionDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionDataTask.swift; sourceTree = ""; }; @@ -601,13 +593,10 @@ B58117E125B9E5D200927E0C /* AccountVerificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationController.swift; sourceTree = ""; }; B58117E725B9EE7B00927E0C /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B586C061245CCD35009508EC /* SplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = ""; }; - B587D7DD2C21FDDF006645CF /* LoginRemote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginRemote.swift; sourceTree = ""; }; - B587D7E12C220765006645CF /* URLSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = ""; }; B587D7E32C22086F006645CF /* LoginRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRemoteTests.swift; sourceTree = ""; }; B587D7E62C2214DB006645CF /* MagicLinkAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkAuthenticatorTests.swift; sourceTree = ""; }; B587D7E92C221575006645CF /* SimperiumAuthenticatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimperiumAuthenticatorProtocol.swift; sourceTree = ""; }; B587D7EB2C2215D9006645CF /* MockSimperiumAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSimperiumAuthenticator.swift; sourceTree = ""; }; - B587D7ED2C2217B1006645CF /* LoginRemoteProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRemoteProtocol.swift; sourceTree = ""; }; B587D7EF2C2217EE006645CF /* MockLoginRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginRemote.swift; sourceTree = ""; }; B58942F924E5EE8E006EC232 /* NSWindowFrameAutosaveName+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWindowFrameAutosaveName+Simplenote.swift"; sourceTree = ""; }; B58A71BD258422CC00601641 /* NSBezierPath+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSBezierPath+Simplenote.swift"; sourceTree = ""; }; @@ -722,8 +711,6 @@ B5F04FD521596551004B1AA0 /* PrivacyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyViewController.swift; sourceTree = ""; }; B5F3150824496A970036872D /* TableRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableRowView.swift; sourceTree = ""; }; B5F50914247319360042FA1D /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; - B5F5414725EFFC2200CAF52C /* SignupRemote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignupRemote.swift; sourceTree = ""; }; - B5F5414D25EFFC2A00CAF52C /* RemoteConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConstants.swift; sourceTree = ""; }; B5F5415325F0137100CAF52C /* MagicLinkAuthenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagicLinkAuthenticator.swift; sourceTree = ""; }; B5F807CB2481982B0048CBD7 /* Note+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Note+Simplenote.swift"; sourceTree = ""; }; B5F807CE2481A2010048CBD7 /* Simperium+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Simperium+Simplenote.swift"; sourceTree = ""; }; @@ -772,13 +759,11 @@ BA5A65982C091E6000F605A6 /* MockStorageValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStorageValidator.swift; sourceTree = ""; }; BA5F020426BB57F000581E92 /* NSAlert+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAlert+Simplenote.swift"; sourceTree = ""; }; BA8CF21B2BFD20770087F33D /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; - BA938CEB26ACFF4A00BE5A1D /* Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Remote.swift; sourceTree = ""; }; BA938CED26AD055400BE5A1D /* AccountVerificationController+TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountVerificationController+TestHelpers.swift"; sourceTree = ""; }; BA94CB642C0E46CC00B34EA7 /* Uploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uploader.swift; sourceTree = ""; }; BA94CB672C0E47DD00B34EA7 /* NSURLSessionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSURLSessionConfiguration+Extensions.swift"; sourceTree = ""; }; BA94CB6B2C0E4A6100B34EA7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; BA94CB6E2C0E4AFA00B34EA7 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; - BAA0A87926B9F0B50006260E /* RemoteError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteError.swift; sourceTree = ""; }; BAA0A88126B9F8970006260E /* AccountDeletionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountDeletionController.swift; sourceTree = ""; }; BAA0A88626B9F8B40006260E /* Date+Simplenote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Simplenote.swift"; sourceTree = ""; }; BAA4854925D5E22000F3BDB9 /* SearchQuery+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchQuery+Simplenote.swift"; sourceTree = ""; }; @@ -832,6 +817,7 @@ BA78AF712B5B2BC300DCF896 /* AutomatticTracks in Frameworks */, B5609AF024EF171D0097777A /* SimplenoteFoundation in Frameworks */, 12FDC872D726DADDBE206EC4 /* Pods_Automattic_Simplenote.framework in Frameworks */, + B51D44582C52AB2200F296A7 /* SimplenoteEndpoints in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -929,7 +915,6 @@ 463774DB171F111600E2E333 /* Models */, BA0B43B826F2FC2900B44A8C /* Preferences */, BA5A658D2C090FDD00F605A6 /* Protocols */, - B502C1DB25BA2EA200145D6C /* Remote */, B57CB879244DEBD500BA7969 /* Settings */, B5C63355251E734100C8BF46 /* Storyboards */, B56FA78D2437C650002CB9FF /* Themes */, @@ -1166,20 +1151,6 @@ name = Constants; sourceTree = ""; }; - B502C1DB25BA2EA200145D6C /* Remote */ = { - isa = PBXGroup; - children = ( - B5F5414D25EFFC2A00CAF52C /* RemoteConstants.swift */, - B502C1DC25BA2EB700145D6C /* AccountRemote.swift */, - B587D7DD2C21FDDF006645CF /* LoginRemote.swift */, - B5F5414725EFFC2200CAF52C /* SignupRemote.swift */, - BA938CEB26ACFF4A00BE5A1D /* Remote.swift */, - BAA0A87926B9F0B50006260E /* RemoteError.swift */, - B587D7E12C220765006645CF /* URLSessionProtocol.swift */, - ); - name = Remote; - sourceTree = ""; - }; B502C1E525BA2EFB00145D6C /* Remote */ = { isa = PBXGroup; children = ( @@ -1401,7 +1372,6 @@ children = ( B5F5415325F0137100CAF52C /* MagicLinkAuthenticator.swift */, B587D7E92C221575006645CF /* SimperiumAuthenticatorProtocol.swift */, - B587D7ED2C2217B1006645CF /* LoginRemoteProtocol.swift */, ); name = Authentication; sourceTree = ""; @@ -1784,6 +1754,7 @@ B5609AEF24EF171D0097777A /* SimplenoteFoundation */, 3FAC7B24254BBCA600B47276 /* SimplenoteInterlinks */, BA78AF702B5B2BC300DCF896 /* AutomatticTracks */, + B51D44572C52AB2200F296A7 /* SimplenoteEndpoints */, ); productName = Simplenote; productReference = 466FFF2F17CC10A800399652 /* Simplenote.app */; @@ -1891,6 +1862,7 @@ B5609AEE24EF171D0097777A /* XCRemoteSwiftPackageReference "SimplenoteFoundation-Swift" */, B50FB24A251C08910028DE25 /* XCRemoteSwiftPackageReference "SimplenoteInterlinks-Swift" */, BA78AF6D2B5B2BAE00DCF896 /* XCRemoteSwiftPackageReference "Automattic-Tracks-iOS" */, + B51D44562C52AB2200F296A7 /* XCRemoteSwiftPackageReference "SimplenoteEndpoints-Swift" */, ); productRefGroup = 26F72A8914032D2A00A7935E /* Products */; projectDirPath = ""; @@ -2097,7 +2069,6 @@ B51E9FE622E644A0004F16B4 /* NSObject+Helpers.swift in Sources */, A6BF919625E4F09F003C2018 /* NoteEditorMetadata.swift in Sources */, 466FFEAB17CC10A800399652 /* Tag.m in Sources */, - B5F5414F25EFFC2A00CAF52C /* RemoteConstants.swift in Sources */, B5C9776D25408BD200702C10 /* SeparatorTableViewCell.swift in Sources */, B50FB215251BB2E00028DE25 /* PopoverWindowController.swift in Sources */, B58BBD2B2523D4CA0025135F /* Window.swift in Sources */, @@ -2111,7 +2082,6 @@ 375D294121E033D1007AB25A /* html_smartypants.c in Sources */, B5E061782450AEDA0076111A /* ToolbarView.swift in Sources */, BA71EC242BC88FD000F42CB1 /* CSSearchable+Helpers.swift in Sources */, - B502C1DE25BA2EB700145D6C /* AccountRemote.swift in Sources */, F998F3EC22853C49008C2B59 /* CrashLogging.swift in Sources */, B5919365245A7AD300A70C0C /* NSScreen+Simplenote.swift in Sources */, B5C620AC257ED4CF008359A9 /* NSAnimationContext+Simplenote.swift in Sources */, @@ -2125,7 +2095,6 @@ B58BBD3E2523FF160025135F /* PopoverWindow.swift in Sources */, B5985AD6242950B40044EDE9 /* NSColor+Simplenote.swift in Sources */, B5469FCB2587DE3F007ED7BE /* SortMode.swift in Sources */, - BAA0A89326BA39260006260E /* RemoteError.swift in Sources */, B535014C249D5908003837C8 /* HorizontalScrollView.swift in Sources */, B5C7DD3E243E1F1900BEE354 /* VersionsViewController.swift in Sources */, B57CB87C244DEC4B00BA7969 /* MigrationsHandler.swift in Sources */, @@ -2167,7 +2136,6 @@ B53BF1A524AC38C100938C34 /* VersionsController.swift in Sources */, B5C7DD47243E51E200BEE354 /* PublishViewController.swift in Sources */, B56FA79C2437D2E0002CB9FF /* NSAppearance+Simplenote.swift in Sources */, - BAE66CAA26AF647500398FF3 /* Remote.swift in Sources */, B55C313D24A55ED800B23B3F /* NSVisualEffectView+Simplenote.swift in Sources */, 375D293F21E033D1007AB25A /* context_test.c in Sources */, B55C312724A5117F00B23B3F /* SpacerTableViewCell.swift in Sources */, @@ -2222,9 +2190,7 @@ 3712FC871FE1ACAA008544AC /* Element.swift in Sources */, B555CE3725ED81F200610BC6 /* AuthWindowController.swift in Sources */, B5F04FD121594B6D004B1AA0 /* Simperium+Simplenote.m in Sources */, - B587D7E22C220765006645CF /* URLSessionProtocol.swift in Sources */, B58117E325B9E5D200927E0C /* AccountVerificationController.swift in Sources */, - B5F5414925EFFC2200CAF52C /* SignupRemote.swift in Sources */, B5EB3AD82458CDB50089858D /* ThemeOption.swift in Sources */, B59848831BCDB95A005EFBBE /* SPTracker.m in Sources */, B5283BD123B679C00085826F /* NSMutableAttributedString+Simplenote.swift in Sources */, @@ -2243,7 +2209,6 @@ B5ACE42F24785D8C00AB02C7 /* BackgroundView.swift in Sources */, B5F5415525F0137100CAF52C /* MagicLinkAuthenticator.swift in Sources */, B58A71BF258422CC00601641 /* NSBezierPath+Simplenote.swift in Sources */, - B587D7EE2C2217B1006645CF /* LoginRemoteProtocol.swift in Sources */, BAF4E5DE2C49C08A009B891F /* SPNavigationController.swift in Sources */, B5DD0F922476309000C8DD41 /* NoteTableCellView.swift in Sources */, B503FF4924848D0B00066059 /* TagAttachmentCell.swift in Sources */, @@ -2263,7 +2228,6 @@ B501AAD32437E5600084CDA3 /* AppKitConstants.swift in Sources */, B57CB87F244DED2300BA7969 /* Bundle+Simplenote.swift in Sources */, 3712FC831FE1ACAA008544AC /* Storage.swift in Sources */, - B587D7DE2C21FDDF006645CF /* LoginRemote.swift in Sources */, 375D293521E033D1007AB25A /* autolink.c in Sources */, B5F3150A24496A970036872D /* TableRowView.swift in Sources */, B5EDF330258A8EF00066D91D /* NoteListFilter.swift in Sources */, @@ -2986,6 +2950,14 @@ minimumVersion = 1.1.0; }; }; + B51D44562C52AB2200F296A7 /* XCRemoteSwiftPackageReference "SimplenoteEndpoints-Swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Automattic/SimplenoteEndpoints-Swift"; + requirement = { + branch = trunk; + kind = branch; + }; + }; B5609AE624EEC9860097777A /* XCRemoteSwiftPackageReference "SimplenoteSearch-Swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "git@github.com:Automattic/SimplenoteSearch-Swift.git"; @@ -3018,6 +2990,11 @@ package = B50FB24A251C08910028DE25 /* XCRemoteSwiftPackageReference "SimplenoteInterlinks-Swift" */; productName = SimplenoteInterlinks; }; + B51D44572C52AB2200F296A7 /* SimplenoteEndpoints */ = { + isa = XCSwiftPackageProductDependency; + package = B51D44562C52AB2200F296A7 /* XCRemoteSwiftPackageReference "SimplenoteEndpoints-Swift" */; + productName = SimplenoteEndpoints; + }; B5609AE724EEC9860097777A /* SimplenoteSearch */ = { isa = XCSwiftPackageProductDependency; package = B5609AE624EEC9860097777A /* XCRemoteSwiftPackageReference "SimplenoteSearch-Swift" */; diff --git a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved index 76e690e81..b15b28a3f 100644 --- a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "1f66ac352dc04da5870abacba5f42306bd560111ad295c7c60a13a7af5c25ec8", + "originHash" : "7f8dc02b0065be779873bdc5b585794a5005edfeb5a5e51a04644d5b3c18e322", "pins" : [ { "identity" : "automattic-tracks-ios", @@ -19,6 +19,15 @@ "version" : "8.28.0" } }, + { + "identity" : "simplenoteendpoints-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Automattic/SimplenoteEndpoints-Swift", + "state" : { + "branch" : "trunk", + "revision" : "3d1c0a5db39ca798a7de10e7faeef8ae66e941e6" + } + }, { "identity" : "simplenotefoundation-swift", "kind" : "remoteSourceControl", diff --git a/Simplenote/AccountDeletionController.swift b/Simplenote/AccountDeletionController.swift index 09b533e5a..c569227b4 100644 --- a/Simplenote/AccountDeletionController.swift +++ b/Simplenote/AccountDeletionController.swift @@ -1,4 +1,6 @@ import Foundation +import SimplenoteEndpoints + @objc class AccountDeletionController: NSObject { diff --git a/Simplenote/AccountVerificationController.swift b/Simplenote/AccountVerificationController.swift index 3c9b186e6..84169c828 100644 --- a/Simplenote/AccountVerificationController.swift +++ b/Simplenote/AccountVerificationController.swift @@ -1,4 +1,6 @@ import Foundation +import SimplenoteEndpoints + // MARK: - AccountVerificationController // diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 3e75d567b..ad359bd26 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -1,4 +1,6 @@ import Foundation +import SimplenoteEndpoints + // MARK: - AuthViewController: Interface Initialization // diff --git a/Simplenote/MagicLinkAuthenticator.swift b/Simplenote/MagicLinkAuthenticator.swift index 05cbdcce0..dc0b3c578 100644 --- a/Simplenote/MagicLinkAuthenticator.swift +++ b/Simplenote/MagicLinkAuthenticator.swift @@ -1,4 +1,5 @@ import Foundation +import SimplenoteEndpoints // MARK: - Notifications diff --git a/Simplenote/SimperiumAuthenticatorProtocol.swift b/Simplenote/SimperiumAuthenticatorProtocol.swift index 97314f8be..a06fdb822 100644 --- a/Simplenote/SimperiumAuthenticatorProtocol.swift +++ b/Simplenote/SimperiumAuthenticatorProtocol.swift @@ -1,4 +1,6 @@ import Foundation +import SimplenoteEndpoints +import Simperium_OSX // MARK: - SimperiumAuthenticatorProtocol @@ -9,3 +11,6 @@ protocol SimperiumAuthenticatorProtocol { extension SPAuthenticator: SimperiumAuthenticatorProtocol { } + + +extension SPUser: UserProtocol { } diff --git a/SimplenoteTests/AccountVerificationController+TestHelpers.swift b/SimplenoteTests/AccountVerificationController+TestHelpers.swift index 990a3c23c..2ed9dfbf8 100644 --- a/SimplenoteTests/AccountVerificationController+TestHelpers.swift +++ b/SimplenoteTests/AccountVerificationController+TestHelpers.swift @@ -1,8 +1,15 @@ import Foundation +import SimplenoteEndpoints @testable import Simplenote + extension AccountVerificationController { func randomResult() -> Result { - return Bool.random() ? .success(nil) : .failure(RemoteError.requestError(0, nil)) + if Bool.random() { + return .success(nil) + } + + let error = RemoteError(statusCode: .zero, response: nil, networkError: nil) + return .failure(error) } } diff --git a/SimplenoteTests/AccountVerificationControllerTests.swift b/SimplenoteTests/AccountVerificationControllerTests.swift index 7f494b820..89f0254d7 100644 --- a/SimplenoteTests/AccountVerificationControllerTests.swift +++ b/SimplenoteTests/AccountVerificationControllerTests.swift @@ -1,4 +1,5 @@ import XCTest +import SimplenoteEndpoints @testable import Simplenote // MARK: - AccountVerificationControllerTests diff --git a/SimplenoteTests/AccountVerificationRemoteTests.swift b/SimplenoteTests/AccountVerificationRemoteTests.swift index 09e8ba60d..98f074f47 100644 --- a/SimplenoteTests/AccountVerificationRemoteTests.swift +++ b/SimplenoteTests/AccountVerificationRemoteTests.swift @@ -1,6 +1,8 @@ import XCTest +import SimplenoteEndpoints @testable import Simplenote + // MARK: - AccountVerificationRemoteTests // class AccountVerificationRemoteTests: XCTestCase { @@ -16,12 +18,14 @@ class AccountVerificationRemoteTests: XCTestCase { func testFailureWhenStatusCodeIs4xxOr5xx() { for _ in 0..<5 { let statusCode = Int.random(in: 400..<600) - test(withStatusCode: statusCode, expectedResult: Result.failure(RemoteError.requestError(statusCode, nil))) + let error = RemoteError(statusCode: statusCode, response: nil, networkError: nil) + test(withStatusCode: statusCode, expectedResult: Result.failure(error)) } } func testFailureWhenNoResponse() { - test(withStatusCode: nil, expectedResult: Result.failure(RemoteError.network)) + let expectedError = RemoteError(statusCode: .zero, response: nil, networkError: nil) + test(withStatusCode: nil, expectedResult: Result.failure(expectedError)) } private func test(withStatusCode statusCode: Int?, expectedResult: Result) { diff --git a/SimplenoteTests/LoginRemoteTests.swift b/SimplenoteTests/LoginRemoteTests.swift index 8412499cc..13e10ce95 100644 --- a/SimplenoteTests/LoginRemoteTests.swift +++ b/SimplenoteTests/LoginRemoteTests.swift @@ -1,4 +1,5 @@ import XCTest +import SimplenoteEndpoints @testable import Simplenote class LoginRemoteTests: XCTestCase { @@ -13,7 +14,7 @@ class LoginRemoteTests: XCTestCase { do { try await loginRemote.requestLoginEmail(email: "email@gmail.com") } catch { - XCTAssertEqual(error as? RemoteError, RemoteError.init(statusCode: statusCode)) + XCTAssertEqual(error as? RemoteError, RemoteError(statusCode: statusCode, response: nil, networkError: nil)) } } diff --git a/SimplenoteTests/MagicLinkAuthenticatorTests.swift b/SimplenoteTests/MagicLinkAuthenticatorTests.swift index e77fc435b..d03067f8e 100644 --- a/SimplenoteTests/MagicLinkAuthenticatorTests.swift +++ b/SimplenoteTests/MagicLinkAuthenticatorTests.swift @@ -1,4 +1,5 @@ import XCTest +import SimplenoteEndpoints @testable import Simplenote diff --git a/SimplenoteTests/MockAccountVerificationRemote.swift b/SimplenoteTests/MockAccountVerificationRemote.swift index d499e11ee..c976fce38 100644 --- a/SimplenoteTests/MockAccountVerificationRemote.swift +++ b/SimplenoteTests/MockAccountVerificationRemote.swift @@ -1,6 +1,8 @@ import XCTest +@testable import SimplenoteEndpoints @testable import Simplenote + // MARK: - MockAccountVerificationRemote // class MockAccountRemote: AccountRemote { diff --git a/SimplenoteTests/MockLoginRemote.swift b/SimplenoteTests/MockLoginRemote.swift index c0cdf7c54..4b586b98d 100644 --- a/SimplenoteTests/MockLoginRemote.swift +++ b/SimplenoteTests/MockLoginRemote.swift @@ -1,4 +1,5 @@ import Foundation +import SimplenoteEndpoints @testable import Simplenote @@ -15,7 +16,7 @@ class MockLoginRemote: LoginRemoteProtocol { func requestLoginConfirmation(email: String, authCode: String) async throws -> LoginConfirmationResponse { guard let response = onLoginConfirmationRequest?(email, authCode) else { - throw RemoteError.network + throw RemoteError(statusCode: .zero, response: nil, networkError: nil) } return response diff --git a/SimplenoteTests/MockURLSession.swift b/SimplenoteTests/MockURLSession.swift index ed510e6fe..c96749d78 100644 --- a/SimplenoteTests/MockURLSession.swift +++ b/SimplenoteTests/MockURLSession.swift @@ -1,6 +1,8 @@ import Foundation +import SimplenoteEndpoints @testable import Simplenote + // MARK: - MockURLSession // class MockURLSession: URLSessionProtocol { @@ -31,6 +33,6 @@ class MockURLSession: URLSessionProtocol { return (responseData, urlResponse) } - throw RemoteError.network + throw RemoteError(statusCode: .zero, response: nil, networkError: nil) } } diff --git a/SimplenoteTests/SignupRemoteTests.swift b/SimplenoteTests/SignupRemoteTests.swift index 97bc72faf..a446c0963 100644 --- a/SimplenoteTests/SignupRemoteTests.swift +++ b/SimplenoteTests/SignupRemoteTests.swift @@ -1,6 +1,8 @@ import XCTest +import SimplenoteEndpoints @testable import Simplenote + class SignupRemoteTests: XCTestCase { private lazy var urlSession = MockURLSession() private lazy var signupRemote = SignupRemote(urlSession: urlSession) @@ -11,7 +13,8 @@ class SignupRemoteTests: XCTestCase { func testFailureWhenStatusCodeIs4xxOr5xx() { let statusCode = Int.random(in: 400..<600) - verifySignupSucceeds(withStatusCode: statusCode, email: "email@gmail.com", expectedSuccess: Result.failure(RemoteError.requestError(statusCode, nil))) + let expectedError = RemoteError(statusCode: statusCode, response: nil, networkError: nil) + verifySignupSucceeds(withStatusCode: statusCode, email: "email@gmail.com", expectedSuccess: Result.failure(expectedError)) } func testRequestSetsEmailToCorrectCase() throws { From 33f7d15e2590d4e0eea89a1a936515518824dd00 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 14:42:05 -0300 Subject: [PATCH 45/84] Drops duplicated code --- Simplenote/AccountRemote.swift | 57 --------------------- Simplenote/LoginRemote.swift | 46 ----------------- Simplenote/LoginRemoteProtocol.swift | 12 ----- Simplenote/Remote.swift | 75 ---------------------------- Simplenote/RemoteConstants.swift | 12 ----- Simplenote/RemoteError.swift | 42 ---------------- Simplenote/SignupRemote.swift | 27 ---------- Simplenote/URLSessionProtocol.swift | 9 ---- 8 files changed, 280 deletions(-) delete mode 100644 Simplenote/AccountRemote.swift delete mode 100644 Simplenote/LoginRemote.swift delete mode 100644 Simplenote/LoginRemoteProtocol.swift delete mode 100644 Simplenote/Remote.swift delete mode 100644 Simplenote/RemoteConstants.swift delete mode 100644 Simplenote/RemoteError.swift delete mode 100644 Simplenote/SignupRemote.swift delete mode 100644 Simplenote/URLSessionProtocol.swift diff --git a/Simplenote/AccountRemote.swift b/Simplenote/AccountRemote.swift deleted file mode 100644 index a908f0087..000000000 --- a/Simplenote/AccountRemote.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -// MARK: - AccountVerificationRemote -// -class AccountRemote: Remote { - // MARK: Performing tasks - - /// Send verification request for specified email address - /// - func verify(email: String, completion: @escaping (_ result: Result) -> Void) { - let request = verificationURLRequest(with: email)! - - performDataTask(with: request, completion: completion) - } - - /// Send account deletion request for user - /// - func requestDelete(_ user: SPUser, completion: @escaping (_ result: Result) -> Void) { - let request = deleteRequest(with: user)! - - performDataTask(with: request, completion: completion) - } - - // MARK: URL Requests - - private func verificationURLRequest(with email: String) -> URLRequest? { - guard let base64EncodedEmail = email.data(using: .utf8)?.base64EncodedString() else { - return nil - } - - let verificationURL = URL(string: SimplenoteConstants.simplenoteVerificationURL)! - var request = URLRequest(url: verificationURL.appendingPathComponent(base64EncodedEmail), - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: RemoteConstants.timeout) - request.httpMethod = RemoteConstants.Method.GET - - return request - } - - private func deleteRequest(with user: SPUser) -> URLRequest? { - let url = URL(string: SimplenoteConstants.accountDeletionURL)! - - var request = URLRequest(url: url, - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: RemoteConstants.timeout) - request.httpMethod = RemoteConstants.Method.POST - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let body = [ - "username": user.email.lowercased(), - "token": user.authToken - ] - request.httpBody = try? JSONEncoder().encode(body) - - return request - } -} diff --git a/Simplenote/LoginRemote.swift b/Simplenote/LoginRemote.swift deleted file mode 100644 index bcbd5b0bf..000000000 --- a/Simplenote/LoginRemote.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation - -// MARK: - LoginRemote -// -class LoginRemote: Remote { - - func requestLoginEmail(email: String) async throws { - let request = requestForLoginRequest(email: email) - try await performDataTask(with: request) - } - - func requestLoginConfirmation(email: String, authCode: String) async throws -> LoginConfirmationResponse { - let request = requestForLoginCompletion(email: email, authCode: authCode) - return try await performDataTask(with: request, type: LoginConfirmationResponse.self) - } -} - - -// MARK: - LoginConfirmationResponse -// -struct LoginConfirmationResponse: Codable, Equatable { - let username: String - let syncToken: String -} - - -// MARK: - Private API(s) -// -private extension LoginRemote { - - func requestForLoginRequest(email: String) -> URLRequest { - let url = URL(string: SimplenoteConstants.loginRequestURL)! - return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ - "request_source": SimplenoteConstants.simplenotePlatformName, - "username": email.lowercased() - ]) - } - - func requestForLoginCompletion(email: String, authCode: String) -> URLRequest { - let url = URL(string: SimplenoteConstants.loginCompletionURL)! - return requestForURL(url, method: RemoteConstants.Method.POST, httpBody: [ - "username": email, - "auth_code": authCode - ]) - } -} diff --git a/Simplenote/LoginRemoteProtocol.swift b/Simplenote/LoginRemoteProtocol.swift deleted file mode 100644 index 25da99396..000000000 --- a/Simplenote/LoginRemoteProtocol.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - - -// MARK: - LoginRemoteProtocol -// -protocol LoginRemoteProtocol { - func requestLoginEmail(email: String) async throws - func requestLoginConfirmation(email: String, authCode: String) async throws -> LoginConfirmationResponse -} - - -extension LoginRemote: LoginRemoteProtocol { } diff --git a/Simplenote/Remote.swift b/Simplenote/Remote.swift deleted file mode 100644 index ebbe09ec2..000000000 --- a/Simplenote/Remote.swift +++ /dev/null @@ -1,75 +0,0 @@ -import Foundation - - -class Remote { - private let urlSession: URLSessionProtocol - - init(urlSession: URLSessionProtocol = URLSession.shared) { - self.urlSession = urlSession - } - - /// Send task for remote - /// Sublcassing Notes: To be able to send a task it is required to first setup the URL request for the task to use - /// - func performDataTask(with request: URLRequest, completion: @escaping (_ result: Result) -> Void) { - let dataTask = urlSession.dataTask(with: request) { (data, response, dataTaskError) in - DispatchQueue.main.async { - - if let error = RemoteError(statusCode: response?.responseStatusCode ?? .zero, error: dataTaskError) { - completion(.failure(error)) - return - } - - completion(.success(data)) - } - } - - dataTask.resume() - } - - /// Performs a URLSession Data Task - /// - @discardableResult - func performDataTask(with request: URLRequest) async throws -> Data { - let (data, response) = try await urlSession.data(for: request, delegate: nil) - - if let error = RemoteError(statusCode: response.responseStatusCode) { - throw error - } - - return data - } - - /// Performs a URLSession Data Task, and decodes a given Type - /// - func performDataTask(with request: URLRequest, type: T.Type) async throws -> T { - let data = try await performDataTask(with: request) - - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - return try decoder.decode(type, from: data) - } - - /// Builds a URLRequest for the specified URL / Method / params - /// - func requestForURL(_ url: URL, method: String, httpBody: [String: String]?) -> URLRequest { - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: RemoteConstants.timeout) - - request.httpMethod = method - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - if let httpBody { - request.httpBody = try? JSONEncoder().encode(httpBody) - } - - return request - } -} - - -extension URLResponse { - - var responseStatusCode: Int { - (self as? HTTPURLResponse)?.statusCode ?? .zero - } -} diff --git a/Simplenote/RemoteConstants.swift b/Simplenote/RemoteConstants.swift deleted file mode 100644 index 4a081c0c2..000000000 --- a/Simplenote/RemoteConstants.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -// MARK: Remote Constants -// -struct RemoteConstants { - struct Method { - static let GET = "GET" - static let POST = "POST" - } - - static let timeout: TimeInterval = 30 -} diff --git a/Simplenote/RemoteError.swift b/Simplenote/RemoteError.swift deleted file mode 100644 index e0a6935b5..000000000 --- a/Simplenote/RemoteError.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation - -enum RemoteError: Error { - case network - case requestError(Int, Error?) -} - -extension RemoteError { - - init?(statusCode: Int, error: Error? = nil) { - if statusCode / 100 == 2 { - return nil - } - - self = statusCode > 0 ? .requestError(statusCode, error) : .network - } -} - - -extension RemoteError { - var statusCode: Int { - switch self { - case .requestError(let statusCode, _): - return statusCode - default: - return .zero - } - } -} - -extension RemoteError: Equatable { - static func == (lhs: RemoteError, rhs: RemoteError) -> Bool { - switch (lhs, rhs) { - case (.network, .network): - return true - case (.requestError(let lhsStatus, let lhsError), .requestError(let rhsStatus, let rhsError)): - return lhsStatus == rhsStatus && lhsError?.localizedDescription == rhsError?.localizedDescription - default: - return false - } - } -} diff --git a/Simplenote/SignupRemote.swift b/Simplenote/SignupRemote.swift deleted file mode 100644 index f41c5a2b2..000000000 --- a/Simplenote/SignupRemote.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -// MARK: - SignupRemote -// -class SignupRemote: Remote { - - /// Send signup request for specified email address - /// - func requestSignup(email: String, completion: @escaping (_ result: Result) -> Void) { - let requestURL = request(with: email)! - - performDataTask(with: requestURL, completion: completion) - } - - private func request(with email: String) -> URLRequest? { - let url = URL(string: SimplenoteConstants.simplenoteRequestSignupURL)! - - var request = URLRequest(url: url, - cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, - timeoutInterval: RemoteConstants.timeout) - request.httpMethod = RemoteConstants.Method.POST - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = try? JSONEncoder().encode(["username": email.lowercased()]) - - return request - } -} diff --git a/Simplenote/URLSessionProtocol.swift b/Simplenote/URLSessionProtocol.swift deleted file mode 100644 index 5e2af7b21..000000000 --- a/Simplenote/URLSessionProtocol.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - - -protocol URLSessionProtocol { - func dataTask(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTask - func data(for request: URLRequest, delegate: (any URLSessionTaskDelegate)?) async throws -> (Data, URLResponse) -} - -extension URLSession: URLSessionProtocol { } From 0190e677b94fbc26371f16ef6bcbf84ba84dcea3 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 14:43:14 -0300 Subject: [PATCH 46/84] Drops duplicated tests --- Simplenote.xcodeproj/project.pbxproj | 20 ----- .../AccountVerificationRemoteTests.swift | 55 -------------- SimplenoteTests/LoginRemoteTests.swift | 66 ----------------- SimplenoteTests/SignupRemoteTests.swift | 73 ------------------- 4 files changed, 214 deletions(-) delete mode 100644 SimplenoteTests/AccountVerificationRemoteTests.swift delete mode 100644 SimplenoteTests/LoginRemoteTests.swift delete mode 100644 SimplenoteTests/SignupRemoteTests.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 2ba6bf3d0..dec641d7e 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -80,7 +80,6 @@ B500993D24213B370037A431 /* String+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500993B24213B370037A431 /* String+Simplenote.swift */; }; B500993F242140500037A431 /* NSStringSimplenoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500993E2421404F0037A431 /* NSStringSimplenoteTests.swift */; }; B501AAD32437E5600084CDA3 /* AppKitConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501AAD12437E5250084CDA3 /* AppKitConstants.swift */; }; - B502C1E725BA2F0800145D6C /* AccountVerificationRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1E625BA2F0800145D6C /* AccountVerificationRemoteTests.swift */; }; B502C1EF25BA2F2400145D6C /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1EE25BA2F2400145D6C /* MockURLSession.swift */; }; B502C1F725BA2F3F00145D6C /* MockURLSessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C1F625BA2F3F00145D6C /* MockURLSessionDataTask.swift */; }; B502C22325BA345700145D6C /* AccountVerificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B502C22225BA345700145D6C /* AccountVerificationControllerTests.swift */; }; @@ -172,7 +171,6 @@ B58117E325B9E5D200927E0C /* AccountVerificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58117E125B9E5D200927E0C /* AccountVerificationController.swift */; }; B58117E925B9EE7B00927E0C /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58117E725B9EE7B00927E0C /* Button.swift */; }; B586C063245CCD35009508EC /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B586C061245CCD35009508EC /* SplitViewController.swift */; }; - B587D7E42C22086F006645CF /* LoginRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E32C22086F006645CF /* LoginRemoteTests.swift */; }; B587D7E72C2214DB006645CF /* MagicLinkAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E62C2214DB006645CF /* MagicLinkAuthenticatorTests.swift */; }; B587D7EA2C221575006645CF /* SimperiumAuthenticatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7E92C221575006645CF /* SimperiumAuthenticatorProtocol.swift */; }; B587D7EC2C2215D9006645CF /* MockSimperiumAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B587D7EB2C2215D9006645CF /* MockSimperiumAuthenticator.swift */; }; @@ -264,7 +262,6 @@ BA1B70122C1CD8D5008282D7 /* RecoveryArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1B70112C1CD8D5008282D7 /* RecoveryArchiver.swift */; }; BA2BF3402C07C75500A7C894 /* FindNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2BF33F2C07C75500A7C894 /* FindNoteIntentHandler.swift */; }; BA2C65CF26FE996A00FA84E1 /* NSButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */; }; - BA4C6D16264CA8C000B723A7 /* SignupRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */; }; BA4C6D18264CAAF800B723A7 /* URLRequest+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */; }; BA4F223E2C1255A500144EDA /* SPCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E196C0230F5F5300F5658A /* SPCredentials.swift */; }; BA54F2462C0E63C700DBCE9D /* AppendNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA54F2452C0E63C700DBCE9D /* AppendNoteIntentHandler.swift */; }; @@ -489,7 +486,6 @@ B500993B24213B370037A431 /* String+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Simplenote.swift"; sourceTree = ""; }; B500993E2421404F0037A431 /* NSStringSimplenoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSStringSimplenoteTests.swift; sourceTree = ""; }; B501AAD12437E5250084CDA3 /* AppKitConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKitConstants.swift; sourceTree = ""; }; - B502C1E625BA2F0800145D6C /* AccountVerificationRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationRemoteTests.swift; sourceTree = ""; }; B502C1EE25BA2F2400145D6C /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; B502C1F625BA2F3F00145D6C /* MockURLSessionDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSessionDataTask.swift; sourceTree = ""; }; B502C22225BA345700145D6C /* AccountVerificationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationControllerTests.swift; sourceTree = ""; }; @@ -593,7 +589,6 @@ B58117E125B9E5D200927E0C /* AccountVerificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationController.swift; sourceTree = ""; }; B58117E725B9EE7B00927E0C /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B586C061245CCD35009508EC /* SplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = ""; }; - B587D7E32C22086F006645CF /* LoginRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRemoteTests.swift; sourceTree = ""; }; B587D7E62C2214DB006645CF /* MagicLinkAuthenticatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkAuthenticatorTests.swift; sourceTree = ""; }; B587D7E92C221575006645CF /* SimperiumAuthenticatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimperiumAuthenticatorProtocol.swift; sourceTree = ""; }; B587D7EB2C2215D9006645CF /* MockSimperiumAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSimperiumAuthenticator.swift; sourceTree = ""; }; @@ -743,7 +738,6 @@ BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSButton+Extensions.swift"; sourceTree = ""; }; BA39C4542C0133F30004B2A9 /* SimplenoteDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SimplenoteDebug.entitlements; sourceTree = ""; }; BA39C4552C0134180004B2A9 /* IntentsExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IntentsExtensionDebug.entitlements; sourceTree = ""; }; - BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupRemoteTests.swift; sourceTree = ""; }; BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Simplenote.swift"; sourceTree = ""; }; BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSSearchable+Helpers.swift"; sourceTree = ""; }; BA52005C2BC88397003F1B75 /* NSManagedObjectContext+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Simplenote.swift"; sourceTree = ""; }; @@ -1151,16 +1145,6 @@ name = Constants; sourceTree = ""; }; - B502C1E525BA2EFB00145D6C /* Remote */ = { - isa = PBXGroup; - children = ( - B502C1E625BA2F0800145D6C /* AccountVerificationRemoteTests.swift */, - B587D7E32C22086F006645CF /* LoginRemoteTests.swift */, - BA4C6D15264CA8C000B723A7 /* SignupRemoteTests.swift */, - ); - name = Remote; - sourceTree = ""; - }; B51E9FE322E64473004F16B4 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1578,7 +1562,6 @@ B5C1F0AD242E9AAA00A627E3 /* Mocks */, B57401A725B7D9A60058960E /* Model */, B5B5F33B243244D300F31720 /* Handlers */, - B502C1E525BA2EFB00145D6C /* Remote */, B5469FD825880106007ED7BE /* Settings */, B5CBB06D241976230003C271 /* Info.plist */, ); @@ -2264,11 +2247,9 @@ B50099322421218B0037A431 /* NSTextViewSimplenoteTests.swift in Sources */, A6672FBF25C7F77000090DE3 /* NoteBodyExcerptTests.swift in Sources */, A6672FBE25C7F77000090DE3 /* StringContentSliceTests.swift in Sources */, - B587D7E42C22086F006645CF /* LoginRemoteTests.swift in Sources */, B5EF32B1258D075C0069EC7D /* NoteListControllerTests.swift in Sources */, B587D7EC2C2215D9006645CF /* MockSimperiumAuthenticator.swift in Sources */, B502C1F725BA2F3F00145D6C /* MockURLSessionDataTask.swift in Sources */, - B502C1E725BA2F0800145D6C /* AccountVerificationRemoteTests.swift in Sources */, B502C1EF25BA2F2400145D6C /* MockURLSession.swift in Sources */, B587D7E72C2214DB006645CF /* MagicLinkAuthenticatorTests.swift in Sources */, BA938CEE26AD055400BE5A1D /* AccountVerificationController+TestHelpers.swift in Sources */, @@ -2279,7 +2260,6 @@ B502C22B25BA347700145D6C /* MockAccountVerificationRemote.swift in Sources */, B5469FE225880181007ED7BE /* NSUserDefaults+Tests.swift in Sources */, B5EF32A8258D05640069EC7D /* MockStorage.swift in Sources */, - BA4C6D16264CA8C000B723A7 /* SignupRemoteTests.swift in Sources */, B500993A242131410037A431 /* UnicodeScalarSimplenoteTests.swift in Sources */, B5CBB073241976280003C271 /* NSAttributedStringToMarkdownConverterTests.swift in Sources */, B518D37E2507C356006EA7F8 /* StringSimplenoteTests.swift in Sources */, diff --git a/SimplenoteTests/AccountVerificationRemoteTests.swift b/SimplenoteTests/AccountVerificationRemoteTests.swift deleted file mode 100644 index 98f074f47..000000000 --- a/SimplenoteTests/AccountVerificationRemoteTests.swift +++ /dev/null @@ -1,55 +0,0 @@ -import XCTest -import SimplenoteEndpoints -@testable import Simplenote - - -// MARK: - AccountVerificationRemoteTests -// -class AccountVerificationRemoteTests: XCTestCase { - private lazy var urlSession = MockURLSession() - private lazy var remote = AccountRemote(urlSession: urlSession) - - func testSuccessWhenStatusCodeIs2xx() { - for _ in 0..<5 { - test(withStatusCode: Int.random(in: 200..<300), expectedResult: Result.success(nil)) - } - } - - func testFailureWhenStatusCodeIs4xxOr5xx() { - for _ in 0..<5 { - let statusCode = Int.random(in: 400..<600) - let error = RemoteError(statusCode: statusCode, response: nil, networkError: nil) - test(withStatusCode: statusCode, expectedResult: Result.failure(error)) - } - } - - func testFailureWhenNoResponse() { - let expectedError = RemoteError(statusCode: .zero, response: nil, networkError: nil) - test(withStatusCode: nil, expectedResult: Result.failure(expectedError)) - } - - private func test(withStatusCode statusCode: Int?, expectedResult: Result) { - urlSession.data = (nil, - response(with: statusCode), - nil) - - let expectation = self.expectation(description: "Verify is called") - - remote.verify(email: UUID().uuidString) { (result) in - XCTAssertEqual(result, expectedResult) - expectation.fulfill() - } - - waitForExpectations(timeout: 1, handler: nil) - } - - private func response(with statusCode: Int?) -> HTTPURLResponse? { - guard let statusCode = statusCode else { - return nil - } - return HTTPURLResponse(url: URL(fileURLWithPath: "/"), - statusCode: statusCode, - httpVersion: nil, - headerFields: nil) - } -} diff --git a/SimplenoteTests/LoginRemoteTests.swift b/SimplenoteTests/LoginRemoteTests.swift deleted file mode 100644 index 13e10ce95..000000000 --- a/SimplenoteTests/LoginRemoteTests.swift +++ /dev/null @@ -1,66 +0,0 @@ -import XCTest -import SimplenoteEndpoints -@testable import Simplenote - -class LoginRemoteTests: XCTestCase { - private lazy var urlSession = MockURLSession() - private lazy var loginRemote = LoginRemote(urlSession: urlSession) - - - func testLoginRequestFailsWhenStatusCodeIs4xxOr5xx() async { - let statusCode = Int.random(in: 400..<600) - mockSessionResponse(statusCode: statusCode) - - do { - try await loginRemote.requestLoginEmail(email: "email@gmail.com") - } catch { - XCTAssertEqual(error as? RemoteError, RemoteError(statusCode: statusCode, response: nil, networkError: nil)) - } - } - - func testLoginRequestSetsEmailToCorrectCase() async throws { - mockSessionResponse(statusCode: 200) - try await loginRemote.requestLoginEmail(email: "EMAIL@gmail.com") - - let expecation = "email@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } - - func testLoginConfirmationEncodesKeyAndCodeFieldsAndGetsValidTokenInReturn() async throws { - /// Setup: Response - let expectedResponse = LoginConfirmationResponse(username: "username", syncToken: "syncToken") - let encodedResponseBody = try JSONEncoder().encode(expectedResponse) - - /// Setup: Session - mockSessionResponse(statusCode: 200, payload: encodedResponseBody) - - /// Run - let decodedResponse = try await loginRemote.requestLoginConfirmation(email: "1234@567.com", authCode: "5678") - - /// Verify - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - - XCTAssertEqual(body["username"], "1234@567.com") - XCTAssertEqual(body["auth_code"], "5678") - - XCTAssertEqual(expectedResponse, decodedResponse) - } -} - -private extension LoginRemoteTests { - - func mockSessionResponse(statusCode: Int, payload: Data = Data()) { - let urlResponse = HTTPURLResponse(url: URL(fileURLWithPath: "/"), - statusCode: statusCode, - httpVersion: nil, - headerFields: nil) - - urlSession.data = (payload, - urlResponse, - nil) - - } -} diff --git a/SimplenoteTests/SignupRemoteTests.swift b/SimplenoteTests/SignupRemoteTests.swift deleted file mode 100644 index a446c0963..000000000 --- a/SimplenoteTests/SignupRemoteTests.swift +++ /dev/null @@ -1,73 +0,0 @@ -import XCTest -import SimplenoteEndpoints -@testable import Simplenote - - -class SignupRemoteTests: XCTestCase { - private lazy var urlSession = MockURLSession() - private lazy var signupRemote = SignupRemote(urlSession: urlSession) - - func testSuccessWhenStatusCodeIs2xx() { - verifySignupSucceeds(withStatusCode: Int.random(in: 200..<300), email: "email@gmail.com", expectedSuccess: Result.success(nil)) - } - - func testFailureWhenStatusCodeIs4xxOr5xx() { - let statusCode = Int.random(in: 400..<600) - let expectedError = RemoteError(statusCode: statusCode, response: nil, networkError: nil) - verifySignupSucceeds(withStatusCode: statusCode, email: "email@gmail.com", expectedSuccess: Result.failure(expectedError)) - } - - func testRequestSetsEmailToCorrectCase() throws { - signupRemote.requestSignup(email: "EMAIL@gmail.com", completion: { _ in }) - - let expecation = "email@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } - - func testRequestSetsEmailToCorrectCaseWithSpecialCharacters() throws { - signupRemote.requestSignup(email: "EMAIL123456@#$%^@gmail.com", completion: { _ in }) - - let expecation = "email123456@#$%^@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } - - func testRequestSetsEmailToCorrectCaseWithMixedCase() throws { - signupRemote.requestSignup(email: "eMaIl@gmail.com", completion: { _ in }) - - let expecation = "email@gmail.com" - let body: Dictionary = try XCTUnwrap(urlSession.lastRequest?.decodeHtmlBody()) - let decodedEmail = try XCTUnwrap(body["username"]) - - XCTAssertEqual(expecation, decodedEmail) - } -} - -private extension SignupRemoteTests { - func verifySignupSucceeds(withStatusCode statusCode: Int, email: String, expectedSuccess: Result) { - urlSession.data = (nil, - mockResponse(with: statusCode), - nil) - - let expectation = self.expectation(description: "Verify is called") - - signupRemote.requestSignup(email: email) { (result) in - XCTAssertEqual(result, expectedSuccess) - expectation.fulfill() - } - - waitForExpectations(timeout: Constants.expectationTimeout, handler: nil) - } - - func mockResponse(with statusCode: Int) -> HTTPURLResponse? { - return HTTPURLResponse(url: URL(fileURLWithPath: "/"), - statusCode: statusCode, - httpVersion: nil, - headerFields: nil) - } -} From 25e030ca9925ac0cfb2b140076d911ab7f2715ff Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 15:23:37 -0300 Subject: [PATCH 47/84] Wires SimplenoteEndpoints Mark 1.0.0 --- Simplenote.xcodeproj/project.pbxproj | 4 ++-- Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index dec641d7e..2c9c266be 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -2934,8 +2934,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Automattic/SimplenoteEndpoints-Swift"; requirement = { - branch = trunk; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; }; }; B5609AE624EEC9860097777A /* XCRemoteSwiftPackageReference "SimplenoteSearch-Swift" */ = { diff --git a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved index b15b28a3f..fedcadab1 100644 --- a/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Simplenote.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/SimplenoteEndpoints-Swift", "state" : { - "branch" : "trunk", - "revision" : "3d1c0a5db39ca798a7de10e7faeef8ae66e941e6" + "revision" : "3d1c0a5db39ca798a7de10e7faeef8ae66e941e6", + "version" : "1.0.0" } }, { From a861df586a71217b1698da0a5661ae823f16be50 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 18:41:04 -0300 Subject: [PATCH 48/84] Implements AuthenticationError --- Simplenote.xcodeproj/project.pbxproj | 4 +++ Simplenote/AuthenticationError.swift | 54 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 Simplenote/AuthenticationError.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 2c9c266be..0e9879fb4 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -104,6 +104,7 @@ B51AFE6E25D30A1800A196DF /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51AFE6C25D30A1800A196DF /* SearchField.swift */; }; B51AFE7725D36CDD00A196DF /* NSFont+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51AFE7525D36CDD00A196DF /* NSFont+Simplenote.swift */; }; B51D44582C52AB2200F296A7 /* SimplenoteEndpoints in Frameworks */ = {isa = PBXBuildFile; productRef = B51D44572C52AB2200F296A7 /* SimplenoteEndpoints */; }; + B51D44672C52F5AE00F296A7 /* AuthenticationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D44662C52F5AE00F296A7 /* AuthenticationError.swift */; }; B51D85F525A8B392005F08CE /* NoteListPrefixFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D85F325A8B392005F08CE /* NoteListPrefixFormatter.swift */; }; B51E9FE222E615FA004F16B4 /* SPExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51E9FE022E615FA004F16B4 /* SPExporter.swift */; }; B51E9FE622E644A0004F16B4 /* NSObject+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51E9FE422E644A0004F16B4 /* NSObject+Helpers.swift */; }; @@ -510,6 +511,7 @@ B518D37D2507C356006EA7F8 /* StringSimplenoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringSimplenoteTests.swift; sourceTree = ""; }; B51AFE6C25D30A1800A196DF /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = ""; }; B51AFE7525D36CDD00A196DF /* NSFont+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFont+Simplenote.swift"; sourceTree = ""; }; + B51D44662C52F5AE00F296A7 /* AuthenticationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationError.swift; sourceTree = ""; }; B51D85F325A8B392005F08CE /* NoteListPrefixFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteListPrefixFormatter.swift; sourceTree = ""; }; B51E9FE022E615FA004F16B4 /* SPExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPExporter.swift; sourceTree = ""; }; B51E9FE422E644A0004F16B4 /* NSObject+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Helpers.swift"; sourceTree = ""; }; @@ -1356,6 +1358,7 @@ children = ( B5F5415325F0137100CAF52C /* MagicLinkAuthenticator.swift */, B587D7E92C221575006645CF /* SimperiumAuthenticatorProtocol.swift */, + B51D44662C52F5AE00F296A7 /* AuthenticationError.swift */, ); name = Authentication; sourceTree = ""; @@ -2196,6 +2199,7 @@ B5DD0F922476309000C8DD41 /* NoteTableCellView.swift in Sources */, B503FF4924848D0B00066059 /* TagAttachmentCell.swift in Sources */, 466FFED417CC10A800399652 /* SPTableView.m in Sources */, + B51D44672C52F5AE00F296A7 /* AuthenticationError.swift in Sources */, 375D293921E033D1007AB25A /* escape.c in Sources */, BA0B43CA26F2FCFC00B44A8C /* PreferencesViewController.swift in Sources */, B53BF19D24ABDE7C00938C34 /* DateFormatter+Simplenote.swift in Sources */, diff --git a/Simplenote/AuthenticationError.swift b/Simplenote/AuthenticationError.swift new file mode 100644 index 000000000..72effd418 --- /dev/null +++ b/Simplenote/AuthenticationError.swift @@ -0,0 +1,54 @@ +import Foundation + + +// MARK: - AuthenticationError +// +public enum AuthenticationError: Error { + case compromisedPassword + case invalidCode + case loginBadCredentials + case network + case requestNotFound + case tooManyAttempts + case unverifiedEmail + case unknown(statusCode: Int, response: String?, error: Error?) +} + + +// MARK: - Initializers +// +extension AuthenticationError { + + /// Returns the AuthenticationError for a given Login statusCode + Response + /// + public init(statusCode: Int, response: String?, error: Error?) { + switch statusCode { + case .zero: + self = .network + case 400 where response == ErrorResponse.requestNotFound: + self = .requestNotFound + case 400 where response == ErrorResponse.invalidCode: + self = .invalidCode + case 401 where response == ErrorResponse.compromisedPassword: + self = .compromisedPassword + case 401: + self = .loginBadCredentials + case 403 where response == ErrorResponse.requiresVerification: + self = .unverifiedEmail + case 429: + self = .tooManyAttempts + default: + self = .unknown(statusCode: statusCode, response: response, error: error) + } + } +} + + +// MARK: - Error Responses +// +private struct ErrorResponse { + static let compromisedPassword = "compromised password" + static let requiresVerification = "verification required" + static let requestNotFound = "request-not-found" + static let invalidCode = "invalid-code" +} From a836f0bbdeae21708df57deb5747ab6094c13898 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 18:41:14 -0300 Subject: [PATCH 49/84] AuthenticationMode: Adds missing newline --- Simplenote/AuthenticationMode.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index b45a80eaf..fdc51831d 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -147,7 +147,8 @@ extension AuthenticationMode { selector: #selector(AuthViewController.pressedLoginWithMagicLink), text: MagicLinkStrings.primaryAction), AuthenticationActionDescriptor(name: .tertiary, - selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) + selector: #selector(AuthViewController.wordpressSSOAction), + text: LoginStrings.wordpressAction) ], primaryActionAnimationText: MagicLinkStrings.primaryAnimationText) } From 93daffff7de8f97f16ac275a08e7f82f1bbf2636 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 18:43:09 -0300 Subject: [PATCH 50/84] AuthViewController: Exposing Alert API(s) --- Simplenote/AuthViewController.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 26e05eac4..fa8456a56 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -49,6 +49,8 @@ - (void)setInterfaceEnabled:(BOOL)enabled; - (void)presentPasswordResetAlert; +- (void)presentPasswordCompromisedAlert; +- (void)presentUnverifiedEmailAlert; - (void)showAuthenticationErrorForCode:(NSInteger)responseCode responseString:(NSString *)responseString; @end From d76ea14391ef4c681f39acab6e8edbc182dca8b8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 18:45:24 -0300 Subject: [PATCH 51/84] AuthViewController: Wires AuthenticationError --- Simplenote/AuthViewController+Swift.swift | 50 ++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index ad359bd26..d1a665100 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -259,7 +259,7 @@ extension AuthViewController { case .success: self.presentSignupVerification(email: email) case .failure(let result): - self.showAuthenticationError(forCode: result.statusCode, responseString: nil) + self.showAuthenticationError(forCode: result.statusCode, responseString: result.response) } self.stopActionAnimation() @@ -292,8 +292,9 @@ extension AuthViewController { pushCodeLoginView() } catch { - let statusCode = (error as? RemoteError)?.statusCode ?? .zero - self.showAuthenticationError(forCode: statusCode, responseString: nil) + // TODO: Once Xcode 16 goes GM, *please* wire Typed Errors here? (it'll always be a RemoteError instance) + let remoteError = error as? RemoteError + self.showAuthenticationError(forCode: remoteError?.statusCode ?? .zero, responseString: remoteError?.response) } } @@ -319,8 +320,9 @@ extension AuthViewController { let confirmation = try await remote.requestLoginConfirmation(email: username, authCode: code.uppercased()) authenticator.authenticate(withUsername: confirmation.username, token: confirmation.syncToken) } catch { - let statusCode = (error as? RemoteError)?.statusCode ?? .zero - self.showAuthenticationError(forCode: statusCode, responseString: nil) + // TODO: Once Xcode 16 goes GM, *please* wire Typed Errors here? (it'll always be a RemoteError instance) + let remoteError = error as? RemoteError + self.showAuthenticationError(forCode: remoteError?.statusCode ?? .zero, responseString: remoteError?.response) } } @@ -421,6 +423,44 @@ extension AuthViewController { // MARK: - Login Error Handling // extension AuthViewController { + + @objc(showAuthenticationError:) + func showAuthenticationError(_ error: String) { + errorField.stringValue = error + } + + @objc(showAuthenticationErrorForCode:responseString:) + func showAuthenticationError(statusCode: Int, responseString: String?) { + let error = AuthenticationError(statusCode: statusCode, response: responseString, error: nil) + switch error { + case .compromisedPassword: + presentPasswordCompromisedAlert() + + case .invalidCode: + let message = NSLocalizedString("The code you've entered is invalid.", comment: "Login po sCode Invalid Error") + showAuthenticationError(message) + + case .loginBadCredentials: + let message = NSLocalizedString("Bad email or password", comment: "Error for authorization failure") + showAuthenticationError(message) + + case .requestNotFound: +// TODO + break + + case .tooManyAttempts: + let message = NSLocalizedString("Too many log in attempts. Try again later.", comment: "Error for too many login attempts") + showAuthenticationError(message) + + case .unverifiedEmail: + presentUnverifiedEmailAlert() + + default: + let message = NSLocalizedString("We're having problems. Please try again soon.", comment: "Generic error") + showAuthenticationError(message) + } + } + @objc func showCompromisedPasswordAlert(for window: NSWindow, completion: @escaping (NSApplication.ModalResponse) -> Void) { let alert = NSAlert() From 5ce7710907417a0e9f2b88aad7ea348711e1007c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 18:45:35 -0300 Subject: [PATCH 52/84] AuthViewController: Drops legacy code --- Simplenote/AuthViewController.m | 43 --------------------------------- 1 file changed, 43 deletions(-) diff --git a/Simplenote/AuthViewController.m b/Simplenote/AuthViewController.m index 51fca537f..6bf5f7f39 100644 --- a/Simplenote/AuthViewController.m +++ b/Simplenote/AuthViewController.m @@ -284,49 +284,6 @@ - (BOOL)validateCode { [self validateCodeInput]; } -- (void)showAuthenticationError:(NSString *)errorMessage { - [self.errorField setStringValue:errorMessage]; -} - -- (void)showAuthenticationErrorForCode:(NSInteger)responseCode responseString:(NSString *)responseString { - switch (responseCode) { - case 409: - [self showAuthenticationError:NSLocalizedString(@"That email is already being used", @"Error when address is in use")]; - [self.view.window makeFirstResponder:self.usernameField]; - break; - case 401: - if ([self isPasswordCompromisedResponse:responseString]) { - [self presentPasswordCompromisedAlert]; - } else { - [self showAuthenticationError:NSLocalizedString(@"Bad email or password", @"Error for bad email or password")]; - } - break; - case 403: - if ([self isRequiresVerificationdResponse:responseString]) { - [self presentUnverifiedEmailAlert]; - } else { - [self showAuthenticationError:NSLocalizedString(@"Authorization failed", @"Error for authorization failure")]; - } - break; - case 429: - [self showAuthenticationError:NSLocalizedString(@"Too many log in attempts. Try again later.", @"Error for too many login attempts")]; - break; - default: - [self showAuthenticationError:NSLocalizedString(@"We're having problems. Please try again soon.", @"Generic error")]; - break; - } -} - -- (BOOL)isPasswordCompromisedResponse:(NSString *)responseString -{ - return ([responseString isEqual:@"compromised password"]); -} - -- (BOOL)isRequiresVerificationdResponse:(NSString *)responseString -{ - return ([responseString isEqual:@"verification required"]); -} - -(void)presentPasswordCompromisedAlert { __weak typeof(self) weakSelf = self; From e675d46cfc7337abc37dc180690969f1958b85ff Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Thu, 25 Jul 2024 18:46:51 -0300 Subject: [PATCH 53/84] AuthViewController: Wires requestNotFound --- Simplenote/AuthViewController+Swift.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index d1a665100..94c47a555 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -445,8 +445,8 @@ extension AuthViewController { showAuthenticationError(message) case .requestNotFound: -// TODO - break + let message = NSLocalizedString("The authentication code you've requested has expired. Please request a new one", comment: "Login Code no longer exists") + showAuthenticationError(message) case .tooManyAttempts: let message = NSLocalizedString("Too many log in attempts. Try again later.", comment: "Error for too many login attempts") From 5e08a6a96c0cbf3d63bd3f5cbfd8a85cce7f6259 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 09:27:16 -0600 Subject: [PATCH 54/84] Improved back button appearance --- Simplenote/SPNavigationController.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 07dedd73d..10bbe18da 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -62,6 +62,12 @@ class SPNavigationController: NSViewController { backButton = button backButton.isHidden = hideBackButton button.bezelStyle = .accessoryBarAction + button.cell?.isBordered = false + button.contentTintColor = .darkGray + button.wantsLayer = true + button.layer?.cornerRadius = 5 + let trackingArea = NSTrackingArea(rect: backButton.bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self) + button.addTrackingArea(trackingArea) view.addSubview(backButton) NSLayoutConstraint.activate([ @@ -192,3 +198,15 @@ class SPNavigationController: NSViewController { } } } + +// MARK: - Button Hover Color Animation +// +extension SPNavigationController { + override func mouseEntered(with event: NSEvent) { + backButton.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor + } + + override func mouseExited(with event: NSEvent) { + backButton.layer?.backgroundColor = .clear + } +} From 045295c0e5c58d80baf363cc7f40669380b91790 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 09:29:40 -0600 Subject: [PATCH 55/84] Updated text for login with email button --- Simplenote/AuthenticationMode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 3dbd75039..e6ecd2390 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -199,7 +199,7 @@ private enum LoginStrings { } private enum MagicLinkStrings { - static let primaryAction = NSLocalizedString("Instantly Log In with Email", comment: "Title of button for logging in") + static let primaryAction = NSLocalizedString("Log in with email", comment: "Title of button for logging in") static let primaryAnimationText = NSLocalizedString("Requesting Email...", comment: "Title of button for logging in") static let secondaryAction = NSLocalizedString("Continue with Password", comment: "Continue with Password Action") static let switchAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") From e2b670b740ac43b480325639ceeebcd656f8e05c Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 13:21:26 -0300 Subject: [PATCH 56/84] AuthViewController: Handling requestNotFound Error --- Simplenote.xcodeproj/project.pbxproj | 4 ++++ Simplenote/AuthViewController+Swift.swift | 16 ++++++++++++++-- Simplenote/NSAlert+AuthErrors.swift | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 Simplenote/NSAlert+AuthErrors.swift diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 0e9879fb4..1a92ca983 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ B511799C242036BD005F8936 /* NSTextView+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B511799A242036BD005F8936 /* NSTextView+Simplenote.swift */; }; B5132FA923C4B9760065DD80 /* NSTextStorage+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5132FA723C4B9760065DD80 /* NSTextStorage+Simplenote.swift */; }; B51374F71AC0591900825FCC /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = B51374F51AC0591900825FCC /* SPConstants.m */; }; + B5146B092C53FBC40064AE72 /* NSAlert+AuthErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5146B082C53FBC40064AE72 /* NSAlert+AuthErrors.swift */; }; B51699852480B93100ACF2F4 /* TagsField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51699832480B93100ACF2F4 /* TagsField.swift */; }; B5177CBC25EEB01600A8D834 /* AuthViewController+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5177CBA25EEB01600A8D834 /* AuthViewController+Swift.swift */; }; B5177CC525EEBF6900A8D834 /* SignupVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5177CC325EEBF6900A8D834 /* SignupVerificationViewController.swift */; }; @@ -501,6 +502,7 @@ B5132FA723C4B9760065DD80 /* NSTextStorage+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+Simplenote.swift"; sourceTree = ""; }; B51374F41AC0591900825FCC /* SPConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConstants.h; sourceTree = ""; }; B51374F51AC0591900825FCC /* SPConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConstants.m; sourceTree = ""; }; + B5146B082C53FBC40064AE72 /* NSAlert+AuthErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAlert+AuthErrors.swift"; sourceTree = ""; }; B51699832480B93100ACF2F4 /* TagsField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagsField.swift; sourceTree = ""; }; B5177CBA25EEB01600A8D834 /* AuthViewController+Swift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AuthViewController+Swift.swift"; sourceTree = ""; }; B5177CC325EEBF6900A8D834 /* SignupVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupVerificationViewController.swift; sourceTree = ""; }; @@ -1154,6 +1156,7 @@ B57CB87D244DED2300BA7969 /* Bundle+Simplenote.swift */, BA52005A2BC878F1003F1B75 /* CSSearchable+Helpers.swift */, B53BF19B24ABDE7C00938C34 /* DateFormatter+Simplenote.swift */, + B5146B082C53FBC40064AE72 /* NSAlert+AuthErrors.swift */, B5C620AA257ED4CF008359A9 /* NSAnimationContext+Simplenote.swift */, B56FA79A2437D2E0002CB9FF /* NSAppearance+Simplenote.swift */, B5B17F392425668400DD5B34 /* NSAttributedString+Simplenote.swift */, @@ -2092,6 +2095,7 @@ B54F9A6B24D0BDC100BCF754 /* TagTextFormatter.swift in Sources */, B5D21CB824881EF600D57A34 /* Array+Simplenote.swift in Sources */, B542FE4F25D42E8900A3582D /* ContentSlice.swift in Sources */, + B5146B092C53FBC40064AE72 /* NSAlert+AuthErrors.swift in Sources */, B5117999242036B1005F8936 /* NSString+Simplenote.swift in Sources */, B5E0863C2449012700DEF476 /* NSImage+Simplenote.swift in Sources */, BAC5DFB72C069993002AD7EF /* SharedStorageMigrator.swift in Sources */, diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 94c47a555..6aee9f4ff 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -445,8 +445,7 @@ extension AuthViewController { showAuthenticationError(message) case .requestNotFound: - let message = NSLocalizedString("The authentication code you've requested has expired. Please request a new one", comment: "Login Code no longer exists") - showAuthenticationError(message) + showLoginCodeExpiredAlert() case .tooManyAttempts: let message = NSLocalizedString("Too many log in attempts. Try again later.", comment: "Error for too many login attempts") @@ -461,6 +460,19 @@ extension AuthViewController { } } + func showLoginCodeExpiredAlert() { + guard let window = view.window else { + return + } + + let alert = NSAlert.buildLoginCodeExpiredAlert() + alert.beginSheetModal(for: window) { [weak self] _ in + DispatchQueue.main.async { + self?.containingNavigationController?.popViewController() + } + } + } + @objc func showCompromisedPasswordAlert(for window: NSWindow, completion: @escaping (NSApplication.ModalResponse) -> Void) { let alert = NSAlert() diff --git a/Simplenote/NSAlert+AuthErrors.swift b/Simplenote/NSAlert+AuthErrors.swift new file mode 100644 index 000000000..edc3a1dff --- /dev/null +++ b/Simplenote/NSAlert+AuthErrors.swift @@ -0,0 +1,20 @@ +import Foundation + + +// MARK: - Alert + AuthError +// +extension NSAlert { + + static func buildLoginCodeExpiredAlert() -> NSAlert { + let titleText = NSLocalizedString("Sorry!", comment: "LoginCode Expired Title") + let messageText = NSLocalizedString("The authentication code you've requested has expired. Please request a new one", comment: "LoginCode Expired Message") + let acceptText = NSLocalizedString("Accept", comment: "Accept Message") + + let alert = NSAlert() + alert.messageText = titleText + alert.informativeText = messageText + alert.addButton(withTitle: acceptText) + + return alert + } +} From b566a3c1b1da86832cf8daa4959298879bd597f4 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 13:22:51 -0300 Subject: [PATCH 57/84] AuthViewController: Drops optionals --- Simplenote/AuthViewController+Swift.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 6aee9f4ff..0d3414f85 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -467,8 +467,12 @@ extension AuthViewController { let alert = NSAlert.buildLoginCodeExpiredAlert() alert.beginSheetModal(for: window) { [weak self] _ in + guard let navigationController = self?.containingNavigationController else { + return + } + DispatchQueue.main.async { - self?.containingNavigationController?.popViewController() + navigationController.popViewController() } } } From c41d64da942b0c7b793f4b04bd780294765b9d0f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:22:15 -0300 Subject: [PATCH 58/84] AuthViewController: Fixes warning --- Simplenote/AuthViewController.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Simplenote/AuthViewController.h b/Simplenote/AuthViewController.h index 89e8e178d..f3cf75eb6 100644 --- a/Simplenote/AuthViewController.h +++ b/Simplenote/AuthViewController.h @@ -52,6 +52,5 @@ - (void)presentPasswordResetAlert; - (void)presentPasswordCompromisedAlert; - (void)presentUnverifiedEmailAlert; -- (void)showAuthenticationErrorForCode:(NSInteger)responseCode responseString:(NSString *)responseString; @end From d41677ed5045c162b762a9ba8e8dfb2d03576b30 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:22:38 -0300 Subject: [PATCH 59/84] AuthViewController: Fixes incorrect invocation --- Simplenote/AuthViewController+Swift.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 585023bcc..43968963d 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -255,7 +255,7 @@ extension AuthViewController { case .success: self.presentSignupVerification(email: email) case .failure(let result): - self.showAuthenticationError(forCode: result.statusCode, responseString: result.response) + self.showAuthenticationError(statusCode: result.statusCode, responseString: result.response) } self.stopActionAnimation() @@ -290,7 +290,7 @@ extension AuthViewController { } catch { // TODO: Once Xcode 16 goes GM, *please* wire Typed Errors here? (it'll always be a RemoteError instance) let remoteError = error as? RemoteError - self.showAuthenticationError(forCode: remoteError?.statusCode ?? .zero, responseString: remoteError?.response) + self.showAuthenticationError(statusCode: remoteError?.statusCode ?? .zero, responseString: remoteError?.response) } } @@ -318,7 +318,7 @@ extension AuthViewController { } catch { // TODO: Once Xcode 16 goes GM, *please* wire Typed Errors here? (it'll always be a RemoteError instance) let remoteError = error as? RemoteError - self.showAuthenticationError(forCode: remoteError?.statusCode ?? .zero, responseString: remoteError?.response) + self.showAuthenticationError(statusCode: remoteError?.statusCode ?? .zero, responseString: remoteError?.response) } } From 20781d49012aeddeb1c61ccabf4093dbb1568a17 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:23:03 -0300 Subject: [PATCH 60/84] AuthViewController: username and password now depend on the State --- Simplenote/AuthViewController+Swift.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 43968963d..4034eae5d 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -93,12 +93,12 @@ extension AuthViewController { @objc var usernameText: String { - usernameField.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + state.username } @objc var passwordText: String { - passwordField.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + state.password } var authWindowController: AuthWindowController? { From 275b49266f5c5e332c16b260470414185052ade8 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:23:13 -0300 Subject: [PATCH 61/84] AuthViewController: Adds missing trimming --- Simplenote/AuthViewController+Swift.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index 4034eae5d..b19deb055 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -367,9 +367,9 @@ extension AuthViewController { switch superView { case usernameField: - state.username = usernameField.stringValue + state.username = usernameField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) case passwordField: - state.password = passwordField.stringValue + state.password = passwordField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) case codeTextField: state.code = codeTextField.stringValue default: From 5cbb3021ce4a005900564ff0b6492ed6d308d538 Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:23:27 -0300 Subject: [PATCH 62/84] AuthViewController: Updates API visibility --- Simplenote/AuthViewController+Swift.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index b19deb055..d4abd5de8 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -11,6 +11,7 @@ extension AuthViewController { simplenoteTitleView.stringValue = "Simplenote" simplenoteSubTitleView.textColor = .simplenoteGray50Color simplenoteSubTitleView.stringValue = NSLocalizedString("The simplest way to keep notes.", comment: "Simplenote subtitle") + // Error Label errorField.stringValue = "" errorField.textColor = .red @@ -271,7 +272,7 @@ extension AuthViewController { } @MainActor - func performLoginWithEmailRequestInTask() async { + private func performLoginWithEmailRequestInTask() async { defer { stopActionAnimation() setInterfaceEnabled(true) @@ -416,6 +417,7 @@ extension AuthViewController { } } + // MARK: - Login Error Handling // extension AuthViewController { From 7e5a60a907e86de0a932803eb0cdaedf1831e75e Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:23:35 -0300 Subject: [PATCH 63/84] AuthViewController: Drops dead API(s) --- Simplenote/AuthViewController+Swift.swift | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index d4abd5de8..cf9530f6c 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -102,10 +102,6 @@ extension AuthViewController { state.password } - var authWindowController: AuthWindowController? { - view.window?.windowController as? AuthWindowController - } - /// # All of the Action Views /// private var allActionViews: [NSButton] { @@ -235,11 +231,6 @@ extension AuthViewController { // MARK: - Handlers // extension AuthViewController { - - @IBAction - func switchToPasswordAuth(_ sender: Any) { - mode = AuthenticationMode.loginWithPassword - } @objc func performSignupRequest() { @@ -406,15 +397,6 @@ extension AuthViewController { let vc = SignupVerificationViewController(email: email, authenticator: authenticator) view.window?.transition(to: vc) } - - //TODO: Drop this method? - func presentMagicLinkRequestedView(email: String) { - guard let authWindowController else { - return - } - - authWindowController.switchToMagicLinkRequestedUI(email: email) - } } From 9608f74bd61a962f6b6898f3f8fc5ded0b78e53f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 14:51:35 -0300 Subject: [PATCH 64/84] SPNavigationController: Window Animation WIP --- Simplenote/SPNavigationController.swift | 38 +++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 10bbe18da..e8302f2a1 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -14,6 +14,10 @@ class SPNavigationController: NSViewController { var topViewController: NSViewController? { viewStack.last } + + private lazy var heightConstraint: NSLayoutConstraint = { + view.heightAnchor.constraint(equalToConstant: 300) + }() init(initialViewController: NSViewController) { super.init(nibName: nil, bundle: nil) @@ -24,7 +28,7 @@ class SPNavigationController: NSViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func loadView() { guard let initialViewController = topViewController else { fatalError() @@ -38,7 +42,7 @@ class SPNavigationController: NSViewController { initialView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(initialView) - + /// "Hint" we wanna occupy as little as possible. This constraint is meant to be broken, but the layout system will /// attempt to reduce the Height, when possible /// @@ -122,13 +126,35 @@ class SPNavigationController: NSViewController { @discardableResult private func attachView(subview: NSView, below siblingView: NSView?) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { + + NSLog("# Before Window Height \(view.window?.frame.height.description) - size \(subview.intrinsicContentSize) - \(subview.fittingSize)") + + let padding = (view.window?.frame.height ?? .zero) - backButton.frame.minY + NSLog("# Button Origin \(backButton.frame.minY) - \(padding)") + + let finalHeight = subview.fittingSize.height + padding + NSLog("# Button Origin \(finalHeight)") + if let siblingView { + heightConstraint.constant = siblingView.fittingSize.height + padding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) } subview.translatesAutoresizingMaskIntoConstraints = false + + heightConstraint.isActive = true + + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.4 + context.timingFunction = .init(name: .easeInEaseOut) + + heightConstraint.animator().constant = finalHeight + } completionHandler: { + + } + let leadingAnchor = subview.leadingAnchor.constraint(equalTo: view.leadingAnchor) let trailingAnchor = subview.trailingAnchor.constraint(equalTo: view.trailingAnchor) @@ -136,11 +162,11 @@ class SPNavigationController: NSViewController { NSLayoutConstraint.activate([ leadingAnchor, trailingAnchor, - subview.topAnchor.constraint(equalTo: backButton.bottomAnchor), - subview.bottomAnchor.constraint(equalTo: view.bottomAnchor) + subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) //, +// subview.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) - + NSLog("# After Window Height \(view.window?.frame.height.description) - size \(subview.intrinsicContentSize) - \(subview.fittingSize)") return (leading: leadingAnchor, trailing: trailingAnchor) } @@ -184,7 +210,7 @@ class SPNavigationController: NSViewController { let multiplier: CGFloat = direction == .leadingToTrailing ? 1 : -1 let alpha: CGFloat = direction == .leadingToTrailing ? 1 : 0 - + NSAnimationContext.runAnimationGroup { context in context.duration = 0.4 context.timingFunction = .init(name: .easeInEaseOut) From f128d176eefc8fcdc918d02138a1e5ab9cc898ca Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:12:41 -0600 Subject: [PATCH 65/84] Removed magic numbers when setting navigation controller back button --- Simplenote/SPNavigationController.swift | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index e8302f2a1..b3c788649 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -75,10 +75,10 @@ class SPNavigationController: NSViewController { view.addSubview(backButton) NSLayoutConstraint.activate([ - backButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10), - backButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 30), - backButton.widthAnchor.constraint(equalToConstant: 50), - backButton.heightAnchor.constraint(equalToConstant: 30) + backButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Constants.buttonViewLeadingPadding), + backButton.topAnchor.constraint(equalTo: view.topAnchor, constant: Constants.buttonViewTopPadding), + backButton.widthAnchor.constraint(equalToConstant: Constants.buttonViewWidth), + backButton.heightAnchor.constraint(equalToConstant: Constants.buttonViewHeight) ]) return button @@ -236,3 +236,10 @@ extension SPNavigationController { backButton.layer?.backgroundColor = .clear } } + +private struct Constants { + static let buttonViewWidth = CGFloat(50) + static let buttonViewHeight = CGFloat(30) + static let buttonViewTopPadding = CGFloat(30) + static let buttonViewLeadingPadding = CGFloat(10) +} From 28698242cb916dc96e97dec2b8c394301101b43c Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:14:11 -0600 Subject: [PATCH 66/84] Dropped setting view bottom constraints in favor of calculated height --- Simplenote/SPNavigationController.swift | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index b3c788649..149e4f795 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -95,13 +95,6 @@ class SPNavigationController: NSViewController { let currentView = topViewController?.view attach(child: viewController) - - /// Disable Bottom Constraint - /// This allows for the enclosing NSWindow to resize, just enough to fit the `nextViewController.view` - /// - if let currentView, let bottomConstraint = view.firstContraint(firstView: currentView, firstAttribute: .bottom) { - bottomConstraint.isActive = false - } guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, below: currentView) else { return @@ -162,8 +155,7 @@ class SPNavigationController: NSViewController { NSLayoutConstraint.activate([ leadingAnchor, trailingAnchor, - subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) //, -// subview.bottomAnchor.constraint(equalTo: view.bottomAnchor) + subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) ]) NSLog("# After Window Height \(view.window?.frame.height.description) - size \(subview.intrinsicContentSize) - \(subview.fittingSize)") @@ -175,13 +167,6 @@ class SPNavigationController: NSViewController { guard viewStack.count > 1, let currentViewController = viewStack.popLast(), let nextViewController = viewStack.last else { return } - - /// Disable Bottom Constraint - /// This allows for the enclosing NSWindow to resize, just enough to fit the `nextViewController.view` - /// - if let currentBottomConstraint = view.firstContraint(firstView: currentViewController.view, firstAttribute: .bottom) { - currentBottomConstraint.isActive = false - } attachView(subview: nextViewController.view, below: currentViewController.view) From 3cf78c8ce7b31e4c49112184a2081ce3a00e92e3 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:16:12 -0600 Subject: [PATCH 67/84] Removed calculation for padding height --- Simplenote/SPNavigationController.swift | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 149e4f795..f60c3b899 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -119,14 +119,9 @@ class SPNavigationController: NSViewController { @discardableResult private func attachView(subview: NSView, below siblingView: NSView?) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { - - NSLog("# Before Window Height \(view.window?.frame.height.description) - size \(subview.intrinsicContentSize) - \(subview.fittingSize)") - - let padding = (view.window?.frame.height ?? .zero) - backButton.frame.minY - NSLog("# Button Origin \(backButton.frame.minY) - \(padding)") - + + let padding = Constants.buttonViewLeadingPadding + Constants.buttonViewHeight let finalHeight = subview.fittingSize.height + padding - NSLog("# Button Origin \(finalHeight)") if let siblingView { heightConstraint.constant = siblingView.fittingSize.height + padding @@ -158,7 +153,6 @@ class SPNavigationController: NSViewController { subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) ]) - NSLog("# After Window Height \(view.window?.frame.height.description) - size \(subview.intrinsicContentSize) - \(subview.fittingSize)") return (leading: leadingAnchor, trailing: trailingAnchor) } From 37a9d8a55e91773e1f1e9c6c92a7bcea3d4433df Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:20:59 -0600 Subject: [PATCH 68/84] Allow for navigation controller to not animate view size changes --- Simplenote/SPNavigationController.swift | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index f60c3b899..039e71dea 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -96,7 +96,7 @@ class SPNavigationController: NSViewController { attach(child: viewController) - guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, below: currentView) else { + guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, below: currentView, animated: animated) else { return } @@ -118,7 +118,7 @@ class SPNavigationController: NSViewController { } @discardableResult - private func attachView(subview: NSView, below siblingView: NSView?) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { + private func attachView(subview: NSView, below siblingView: NSView?, animated: Bool) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { let padding = Constants.buttonViewLeadingPadding + Constants.buttonViewHeight let finalHeight = subview.fittingSize.height + padding @@ -131,19 +131,8 @@ class SPNavigationController: NSViewController { } subview.translatesAutoresizingMaskIntoConstraints = false - - heightConstraint.isActive = true - - NSAnimationContext.runAnimationGroup { context in - context.duration = 0.4 - context.timingFunction = .init(name: .easeInEaseOut) - - heightConstraint.animator().constant = finalHeight - } completionHandler: { - - } - + heightConstraint.isActive = true let leadingAnchor = subview.leadingAnchor.constraint(equalTo: view.leadingAnchor) let trailingAnchor = subview.trailingAnchor.constraint(equalTo: view.trailingAnchor) @@ -153,17 +142,29 @@ class SPNavigationController: NSViewController { subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) ]) + guard animated else { + heightConstraint.constant = finalHeight + return (leading: leadingAnchor, trailing: trailingAnchor) + } + + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.4 + context.timingFunction = .init(name: .easeInEaseOut) + + heightConstraint.animator().constant = finalHeight + } + return (leading: leadingAnchor, trailing: trailingAnchor) } // MARK: - Remove view from stack - func popViewController() { + func popViewController(animated: Bool = true) { guard viewStack.count > 1, let currentViewController = viewStack.popLast(), let nextViewController = viewStack.last else { return } - attachView(subview: nextViewController.view, below: currentViewController.view) - + attachView(subview: nextViewController.view, below: currentViewController.view, animated: animated) + animateTransition(slidingView: currentViewController.view, fadingView: nextViewController.view, direction: .leadingToTrailing) { self.dettach(child: currentViewController) } From d25e7a8a9aeafd23d1470c61d01974da5b6493c5 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:23:27 -0600 Subject: [PATCH 69/84] Setup height constraint in loadView instead of lazy var --- Simplenote/SPNavigationController.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 039e71dea..b7e8d1186 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -15,9 +15,7 @@ class SPNavigationController: NSViewController { viewStack.last } - private lazy var heightConstraint: NSLayoutConstraint = { - view.heightAnchor.constraint(equalToConstant: 300) - }() + private var heightConstraint: NSLayoutConstraint? = nil init(initialViewController: NSViewController) { super.init(nibName: nil, bundle: nil) @@ -35,6 +33,7 @@ class SPNavigationController: NSViewController { } view = NSView() + heightConstraint = view.heightAnchor.constraint(equalToConstant: .zero) let initialView = initialViewController.view backButton = insertBackButton() @@ -124,7 +123,7 @@ class SPNavigationController: NSViewController { let finalHeight = subview.fittingSize.height + padding if let siblingView { - heightConstraint.constant = siblingView.fittingSize.height + padding + heightConstraint?.constant = siblingView.fittingSize.height + padding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) @@ -132,7 +131,7 @@ class SPNavigationController: NSViewController { subview.translatesAutoresizingMaskIntoConstraints = false - heightConstraint.isActive = true + heightConstraint?.isActive = true let leadingAnchor = subview.leadingAnchor.constraint(equalTo: view.leadingAnchor) let trailingAnchor = subview.trailingAnchor.constraint(equalTo: view.trailingAnchor) @@ -143,7 +142,7 @@ class SPNavigationController: NSViewController { ]) guard animated else { - heightConstraint.constant = finalHeight + heightConstraint?.constant = finalHeight return (leading: leadingAnchor, trailing: trailingAnchor) } @@ -151,7 +150,7 @@ class SPNavigationController: NSViewController { context.duration = 0.4 context.timingFunction = .init(name: .easeInEaseOut) - heightConstraint.animator().constant = finalHeight + heightConstraint?.animator().constant = finalHeight } return (leading: leadingAnchor, trailing: trailingAnchor) From ee044c210b050e17707f75b3fb4391994107d7fc Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:33:56 -0600 Subject: [PATCH 70/84] Refactor creating the height constraint for sp nav controller --- Simplenote/SPNavigationController.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index b7e8d1186..4ea661556 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -34,14 +34,16 @@ class SPNavigationController: NSViewController { view = NSView() heightConstraint = view.heightAnchor.constraint(equalToConstant: .zero) + heightConstraint?.isActive = true let initialView = initialViewController.view backButton = insertBackButton() view.translatesAutoresizingMaskIntoConstraints = false initialView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(initialView) - + attach(child: initialViewController) + attachView(subview: initialViewController.view, below: nil, animated: false) + /// "Hint" we wanna occupy as little as possible. This constraint is meant to be broken, but the layout system will /// attempt to reduce the Height, when possible /// @@ -131,7 +133,6 @@ class SPNavigationController: NSViewController { subview.translatesAutoresizingMaskIntoConstraints = false - heightConstraint?.isActive = true let leadingAnchor = subview.leadingAnchor.constraint(equalTo: view.leadingAnchor) let trailingAnchor = subview.trailingAnchor.constraint(equalTo: view.trailingAnchor) From d2481f65860e1bedc285f16cc62c82970a0e712f Mon Sep 17 00:00:00 2001 From: Jorge Leandro Perez Date: Fri, 26 Jul 2024 15:45:45 -0300 Subject: [PATCH 71/84] AuthenticationMode: RateLimiting Fallback --- Simplenote/AuthViewController+Swift.swift | 6 +++++ Simplenote/AuthenticationMode.swift | 29 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Simplenote/AuthViewController+Swift.swift b/Simplenote/AuthViewController+Swift.swift index cf9530f6c..100d62de9 100644 --- a/Simplenote/AuthViewController+Swift.swift +++ b/Simplenote/AuthViewController+Swift.swift @@ -428,6 +428,12 @@ extension AuthViewController { showLoginCodeExpiredAlert() case .tooManyAttempts: + if let fallbackMode = mode.rateLimitingFallbackMode?() { + pushNewAuthViewController(with: fallbackMode, state: state) + break + } + + /// No fallback =( let message = NSLocalizedString("Too many log in attempts. Try again later.", comment: "Error for too many login attempts") showAuthenticationError(message) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index 406199e02..b0ae820aa 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -50,9 +50,8 @@ class AuthenticationMode: NSObject { let header: String? let inputElements: AuthenticationInputElements let actions: [AuthenticationActionDescriptor] - let primaryActionAnimationText: String - + let rateLimitingFallbackMode: (() -> AuthenticationMode)? let isIntroView: Bool init(title: String, @@ -60,12 +59,14 @@ class AuthenticationMode: NSObject { inputElements: AuthenticationInputElements, actions: [AuthenticationActionDescriptor], primaryActionAnimationText: String, + rateLimitingFallbackMode: (() -> AuthenticationMode)? = nil, isIntroView: Bool = false) { self.title = title self.header = header self.inputElements = inputElements self.actions = actions self.primaryActionAnimationText = primaryActionAnimationText + self.rateLimitingFallbackMode = rateLimitingFallbackMode self.isIntroView = isIntroView } } @@ -78,6 +79,7 @@ extension AuthenticationMode { } } + // MARK: - Public Properties // extension AuthenticationMode { @@ -121,8 +123,20 @@ extension AuthenticationMode { /// Auth Mode: Login with Username + Password /// static var loginWithPassword: AuthenticationMode { + buildLoginWithPasswordMode(header: LoginStrings.loginWithEmailEmailHeader) + } + + /// Auth Mode: Login with Username + Password + Rate Limiting Header + /// + static var loginWithPasswordRateLimited: AuthenticationMode { + buildLoginWithPasswordMode(header: LoginStrings.loginWithEmailLimitHeader) + } + + /// Builds the loginWithPassword Mode with the specified Header + /// + private static func buildLoginWithPasswordMode(header: String) -> AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In with Password", comment: "LogIn Interface Title"), - header: LoginStrings.loginWithEmailEmailHeader, + header: header, inputElements: [.password], actions: [ AuthenticationActionDescriptor(name: .primary, @@ -143,14 +157,17 @@ extension AuthenticationMode { AuthenticationMode(title: NSLocalizedString("Log In", comment: "LogIn Interface Title"), inputElements: [.username, .actionSeparator], actions: [ - AuthenticationActionDescriptor(name: .primary, + AuthenticationActionDescriptor(name: .primary, selector: #selector(AuthViewController.pressedLoginWithMagicLink), text: MagicLinkStrings.primaryAction), AuthenticationActionDescriptor(name: .tertiary, selector: #selector(AuthViewController.wordpressSSOAction), text: LoginStrings.wordpressAction) ], - primaryActionAnimationText: MagicLinkStrings.primaryAnimationText) + primaryActionAnimationText: MagicLinkStrings.primaryAnimationText, + rateLimitingFallbackMode: { + AuthenticationMode.loginWithPasswordRateLimited + }) } /// Auth Mode: SignUp @@ -197,6 +214,8 @@ private enum LoginStrings { static let switchTip = NSLocalizedString("Need an account?", comment: "Link to create an account") static let wordpressAction = NSLocalizedString("Log in with WordPress.com", comment: "Title to use wordpress login instead of email") static let loginWithEmailEmailHeader = NSLocalizedString("Enter the password for the account {{EMAIL}}", comment: "Header for Login With Password. Please preserve the {{EMAIL}} substring") + static let loginWithEmailLimitHeader = NSLocalizedString("Log in with email failed, please enter the password for {{EMAIL}}", comment: "Header for Enter Password UI, when the user performed too many requests") + } private enum MagicLinkStrings { From 95a74e7996610641bec9aeae7c743acfd55e94d0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 12:56:54 -0600 Subject: [PATCH 72/84] Fixed back button not hiding when back to initial vc w/o animations --- Simplenote/SPNavigationController.swift | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 4ea661556..e472933a2 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -41,7 +41,6 @@ class SPNavigationController: NSViewController { view.translatesAutoresizingMaskIntoConstraints = false initialView.translatesAutoresizingMaskIntoConstraints = false - attach(child: initialViewController) attachView(subview: initialViewController.view, below: nil, animated: false) /// "Hint" we wanna occupy as little as possible. This constraint is meant to be broken, but the layout system will @@ -92,7 +91,7 @@ class SPNavigationController: NSViewController { // MARK: - Add a View to the stack // - func push(_ viewController: NSViewController, animated: Bool = true) { + func push(_ viewController: NSViewController, animated: Bool = false) { let currentView = topViewController?.view attach(child: viewController) @@ -102,6 +101,8 @@ class SPNavigationController: NSViewController { } guard animated else { + currentView?.removeFromSuperview() + backButton.animator().isHidden = hideBackButton return } @@ -123,9 +124,10 @@ class SPNavigationController: NSViewController { let padding = Constants.buttonViewLeadingPadding + Constants.buttonViewHeight let finalHeight = subview.fittingSize.height + padding - - if let siblingView { - heightConstraint?.constant = siblingView.fittingSize.height + padding + + if let siblingView, + animated { + heightConstraint?.constant = finalHeight view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) @@ -158,13 +160,19 @@ class SPNavigationController: NSViewController { } // MARK: - Remove view from stack - func popViewController(animated: Bool = true) { + func popViewController(animated: Bool = false) { guard viewStack.count > 1, let currentViewController = viewStack.popLast(), let nextViewController = viewStack.last else { return } attachView(subview: nextViewController.view, below: currentViewController.view, animated: animated) + guard animated else { + self.dettach(child: currentViewController) + backButton.isHidden = hideBackButton + return + } + animateTransition(slidingView: currentViewController.view, fadingView: nextViewController.view, direction: .leadingToTrailing) { self.dettach(child: currentViewController) } From e9d89053ba051469fb6c20f8ca9c1bdf1c07b688 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 13:05:41 -0600 Subject: [PATCH 73/84] Refactored nav controller .attachView to not return constraints --- Simplenote/SPNavigationController.swift | 33 +++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index e472933a2..82ba1b644 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -91,14 +91,11 @@ class SPNavigationController: NSViewController { // MARK: - Add a View to the stack // - func push(_ viewController: NSViewController, animated: Bool = false) { + func push(_ viewController: NSViewController, animated: Bool = true) { let currentView = topViewController?.view attach(child: viewController) - - guard let (leadingAnchor, trailingAnchor) = attachView(subview: viewController.view, below: currentView, animated: animated) else { - return - } + attachView(subview: viewController.view, below: currentView, animated: animated) guard animated else { currentView?.removeFromSuperview() @@ -106,9 +103,6 @@ class SPNavigationController: NSViewController { return } - leadingAnchor.constant = view.frame.width - trailingAnchor.constant = view.frame.width - animateTransition(slidingView: viewController.view, fadingView: currentView, direction: .trailingToLeading) { currentView?.removeFromSuperview() } @@ -119,15 +113,14 @@ class SPNavigationController: NSViewController { viewStack.append(child) } - @discardableResult - private func attachView(subview: NSView, below siblingView: NSView?, animated: Bool) -> (leading: NSLayoutConstraint, trailing: NSLayoutConstraint)? { + private func attachView(subview: NSView, below siblingView: NSView?, animated: Bool) { let padding = Constants.buttonViewLeadingPadding + Constants.buttonViewHeight let finalHeight = subview.fittingSize.height + padding if let siblingView, animated { - heightConstraint?.constant = finalHeight + heightConstraint?.constant = siblingView.fittingSize.height + padding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) @@ -135,18 +128,15 @@ class SPNavigationController: NSViewController { subview.translatesAutoresizingMaskIntoConstraints = false - let leadingAnchor = subview.leadingAnchor.constraint(equalTo: view.leadingAnchor) - let trailingAnchor = subview.trailingAnchor.constraint(equalTo: view.trailingAnchor) - NSLayoutConstraint.activate([ - leadingAnchor, - trailingAnchor, + subview.leadingAnchor.constraint(equalTo: view.leadingAnchor), + subview.trailingAnchor.constraint(equalTo: view.trailingAnchor), subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) ]) guard animated else { heightConstraint?.constant = finalHeight - return (leading: leadingAnchor, trailing: trailingAnchor) + return } NSAnimationContext.runAnimationGroup { context in @@ -156,11 +146,11 @@ class SPNavigationController: NSViewController { heightConstraint?.animator().constant = finalHeight } - return (leading: leadingAnchor, trailing: trailingAnchor) + return } // MARK: - Remove view from stack - func popViewController(animated: Bool = false) { + func popViewController(animated: Bool = true) { guard viewStack.count > 1, let currentViewController = viewStack.popLast(), let nextViewController = viewStack.last else { return } @@ -196,6 +186,11 @@ class SPNavigationController: NSViewController { return } + if direction == .trailingToLeading { + leadingConstraint.constant = view.frame.width + trailingConstraint.constant = view.frame.width + } + let multiplier: CGFloat = direction == .leadingToTrailing ? 1 : -1 let alpha: CGFloat = direction == .leadingToTrailing ? 1 : 0 From f7fc04456b148e3fc667289b706419c071145f8e Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 13:51:39 -0600 Subject: [PATCH 74/84] removed unused .animator() call when not animating Co-authored-by: Jorge Leandro Perez --- Simplenote/SPNavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 82ba1b644..f5342d5d8 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -99,7 +99,7 @@ class SPNavigationController: NSViewController { guard animated else { currentView?.removeFromSuperview() - backButton.animator().isHidden = hideBackButton + backButton.isHidden = hideBackButton return } From d86ecd79dcd192fdf3fc8146b04258fcdca91c51 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 13:52:06 -0600 Subject: [PATCH 75/84] dropped unused self in nav controller Co-authored-by: Jorge Leandro Perez --- Simplenote/SPNavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index f5342d5d8..00007b22f 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -158,7 +158,7 @@ class SPNavigationController: NSViewController { attachView(subview: nextViewController.view, below: currentViewController.view, animated: animated) guard animated else { - self.dettach(child: currentViewController) + dettach(child: currentViewController) backButton.isHidden = hideBackButton return } From 1cb42ff0cf4c2c4de4f275d40464c5fc085fc9e0 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 13:54:20 -0600 Subject: [PATCH 76/84] Drop unneeded return --- Simplenote/SPNavigationController.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 00007b22f..3f4a69a9f 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -145,8 +145,6 @@ class SPNavigationController: NSViewController { heightConstraint?.animator().constant = finalHeight } - - return } // MARK: - Remove view from stack From 8afb39fa459352a8094f7946723bc2eb3b6f8993 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 13:59:10 -0600 Subject: [PATCH 77/84] Removed the low priority height constraint, it wasnt needed anymore --- Simplenote/SPNavigationController.swift | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 3f4a69a9f..6c61f48d7 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -15,7 +15,7 @@ class SPNavigationController: NSViewController { viewStack.last } - private var heightConstraint: NSLayoutConstraint? = nil + private var heightConstraint: NSLayoutConstraint! init(initialViewController: NSViewController) { super.init(nibName: nil, bundle: nil) @@ -34,7 +34,6 @@ class SPNavigationController: NSViewController { view = NSView() heightConstraint = view.heightAnchor.constraint(equalToConstant: .zero) - heightConstraint?.isActive = true let initialView = initialViewController.view backButton = insertBackButton() @@ -43,18 +42,12 @@ class SPNavigationController: NSViewController { attachView(subview: initialViewController.view, below: nil, animated: false) - /// "Hint" we wanna occupy as little as possible. This constraint is meant to be broken, but the layout system will - /// attempt to reduce the Height, when possible - /// - let minimumHeightConstraint = view.heightAnchor.constraint(equalToConstant: .zero) - minimumHeightConstraint.priority = .init(1) - NSLayoutConstraint.activate([ initialView.leadingAnchor.constraint(equalTo: view.leadingAnchor), initialView.trailingAnchor.constraint(equalTo: view.trailingAnchor), initialView.topAnchor.constraint(equalTo: backButton.bottomAnchor), initialView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - minimumHeightConstraint + heightConstraint ]) } @@ -120,7 +113,7 @@ class SPNavigationController: NSViewController { if let siblingView, animated { - heightConstraint?.constant = siblingView.fittingSize.height + padding + heightConstraint.constant = siblingView.fittingSize.height + padding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) @@ -135,7 +128,7 @@ class SPNavigationController: NSViewController { ]) guard animated else { - heightConstraint?.constant = finalHeight + heightConstraint.constant = finalHeight return } @@ -143,7 +136,7 @@ class SPNavigationController: NSViewController { context.duration = 0.4 context.timingFunction = .init(name: .easeInEaseOut) - heightConstraint?.animator().constant = finalHeight + heightConstraint.animator().constant = finalHeight } } From be3e3632a29aa6321f82882cb2e54eaf3b764fb7 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 14:05:37 -0600 Subject: [PATCH 78/84] Refactored window animation out of attach sub view --- Simplenote/SPNavigationController.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 6c61f48d7..093718d7c 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -16,6 +16,9 @@ class SPNavigationController: NSViewController { } private var heightConstraint: NSLayoutConstraint! + private var totalTopPadding: CGFloat { + Constants.buttonViewLeadingPadding + Constants.buttonViewHeight + } init(initialViewController: NSViewController) { super.init(nibName: nil, bundle: nil) @@ -41,6 +44,7 @@ class SPNavigationController: NSViewController { initialView.translatesAutoresizingMaskIntoConstraints = false attachView(subview: initialViewController.view, below: nil, animated: false) + resizeWindow(to: initialViewController.view, animated: false) NSLayoutConstraint.activate([ initialView.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -89,6 +93,7 @@ class SPNavigationController: NSViewController { attach(child: viewController) attachView(subview: viewController.view, below: currentView, animated: animated) + resizeWindow(to: viewController.view, animated: animated) guard animated else { currentView?.removeFromSuperview() @@ -107,25 +112,25 @@ class SPNavigationController: NSViewController { } private func attachView(subview: NSView, below siblingView: NSView?, animated: Bool) { - - let padding = Constants.buttonViewLeadingPadding + Constants.buttonViewHeight - let finalHeight = subview.fittingSize.height + padding + subview.translatesAutoresizingMaskIntoConstraints = false if let siblingView, animated { - heightConstraint.constant = siblingView.fittingSize.height + padding + heightConstraint.constant = siblingView.fittingSize.height + totalTopPadding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) } - subview.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ subview.leadingAnchor.constraint(equalTo: view.leadingAnchor), subview.trailingAnchor.constraint(equalTo: view.trailingAnchor), subview.topAnchor.constraint(equalTo: backButton.bottomAnchor) ]) + } + + private func resizeWindow(to subview: NSView, animated: Bool) { + let finalHeight = subview.fittingSize.height + totalTopPadding guard animated else { heightConstraint.constant = finalHeight @@ -147,6 +152,7 @@ class SPNavigationController: NSViewController { } attachView(subview: nextViewController.view, below: currentViewController.view, animated: animated) + resizeWindow(to: nextViewController.view, animated: animated) guard animated else { dettach(child: currentViewController) From 04041c4a7726c43a506b0135fdc1347076945553 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 14:07:01 -0600 Subject: [PATCH 79/84] fixed calculated top padding size --- Simplenote/SPNavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 093718d7c..2542465eb 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -17,7 +17,7 @@ class SPNavigationController: NSViewController { private var heightConstraint: NSLayoutConstraint! private var totalTopPadding: CGFloat { - Constants.buttonViewLeadingPadding + Constants.buttonViewHeight + Constants.buttonViewTopPadding + Constants.buttonViewHeight } init(initialViewController: NSViewController) { From 25c19af21c7cec9c7e84ca328c9571c549bb0c82 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 14:08:47 -0600 Subject: [PATCH 80/84] Dropped unused animated argument in attachView --- Simplenote/SPNavigationController.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 2542465eb..166ff0560 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -43,7 +43,7 @@ class SPNavigationController: NSViewController { view.translatesAutoresizingMaskIntoConstraints = false initialView.translatesAutoresizingMaskIntoConstraints = false - attachView(subview: initialViewController.view, below: nil, animated: false) + attachView(subview: initialViewController.view, below: nil) resizeWindow(to: initialViewController.view, animated: false) NSLayoutConstraint.activate([ @@ -92,7 +92,7 @@ class SPNavigationController: NSViewController { let currentView = topViewController?.view attach(child: viewController) - attachView(subview: viewController.view, below: currentView, animated: animated) + attachView(subview: viewController.view, below: currentView) resizeWindow(to: viewController.view, animated: animated) guard animated else { @@ -111,12 +111,11 @@ class SPNavigationController: NSViewController { viewStack.append(child) } - private func attachView(subview: NSView, below siblingView: NSView?, animated: Bool) { + private func attachView(subview: NSView, below siblingView: NSView?) { subview.translatesAutoresizingMaskIntoConstraints = false - if let siblingView, - animated { - heightConstraint.constant = siblingView.fittingSize.height + totalTopPadding + if let siblingView { +// heightConstraint.constant = siblingView.fittingSize.height + totalTopPadding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) @@ -151,7 +150,7 @@ class SPNavigationController: NSViewController { return } - attachView(subview: nextViewController.view, below: currentViewController.view, animated: animated) + attachView(subview: nextViewController.view, below: currentViewController.view) resizeWindow(to: nextViewController.view, animated: animated) guard animated else { From cf8a7a58bcaeaf2e452d34a0c7b347b7fc13d896 Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 14:11:26 -0600 Subject: [PATCH 81/84] Dropped unused height constraint change --- Simplenote/SPNavigationController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 166ff0560..215e65609 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -115,7 +115,6 @@ class SPNavigationController: NSViewController { subview.translatesAutoresizingMaskIntoConstraints = false if let siblingView { -// heightConstraint.constant = siblingView.fittingSize.height + totalTopPadding view.addSubview(subview, positioned: .below, relativeTo: siblingView) } else { view.addSubview(subview) From 6ac8089d2a247363b4bee358983cebfdd542eeab Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 15:21:44 -0600 Subject: [PATCH 82/84] Dropped conflicting constraint when setting up navigation view --- Simplenote/SPNavigationController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Simplenote/SPNavigationController.swift b/Simplenote/SPNavigationController.swift index 215e65609..c55ded91b 100644 --- a/Simplenote/SPNavigationController.swift +++ b/Simplenote/SPNavigationController.swift @@ -50,7 +50,6 @@ class SPNavigationController: NSViewController { initialView.leadingAnchor.constraint(equalTo: view.leadingAnchor), initialView.trailingAnchor.constraint(equalTo: view.trailingAnchor), initialView.topAnchor.constraint(equalTo: backButton.bottomAnchor), - initialView.bottomAnchor.constraint(equalTo: view.bottomAnchor), heightConstraint ]) } From dd58f3bd4176f149c7af87d2329dfa0237f4fcbe Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 15:22:22 -0600 Subject: [PATCH 83/84] Updated language for requesting email code --- Simplenote/AuthenticationMode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index b0ae820aa..f107467c1 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -220,7 +220,7 @@ private enum LoginStrings { private enum MagicLinkStrings { static let primaryAction = NSLocalizedString("Log in with email", comment: "Title of button for logging in") - static let primaryAnimationText = NSLocalizedString("Requesting Email...", comment: "Title of button for logging in") + static let primaryAnimationText = NSLocalizedString("Requesting email...", comment: "Title of button for logging in") static let secondaryAction = NSLocalizedString("Continue with Password", comment: "Continue with Password Action") static let switchAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") static let switchTip = NSLocalizedString("Need an account?", comment: "Link to create an account") From 0813755034d4bf5ce1bb7f8324bf37d148d6813f Mon Sep 17 00:00:00 2001 From: Charlie Scheer Date: Fri, 26 Jul 2024 15:28:01 -0600 Subject: [PATCH 84/84] Updated button and header strings for auth --- Simplenote/AuthenticationMode.swift | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Simplenote/AuthenticationMode.swift b/Simplenote/AuthenticationMode.swift index f107467c1..988cd9c0f 100644 --- a/Simplenote/AuthenticationMode.swift +++ b/Simplenote/AuthenticationMode.swift @@ -207,11 +207,9 @@ extension AuthenticationMode { // MARK: - Localization // private enum LoginStrings { - static let primaryAction = NSLocalizedString("Log In", comment: "Title of button for logging in") - static let primaryAnimationText = NSLocalizedString("Logging In...", comment: "Title of button for logging in") - static let secondaryAction = NSLocalizedString("Forgot your Password?", comment: "Forgot Password Button") - static let switchAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") - static let switchTip = NSLocalizedString("Need an account?", comment: "Link to create an account") + static let primaryAction = NSLocalizedString("Log in", comment: "Title of button for logging in") + static let primaryAnimationText = NSLocalizedString("Logging in...", comment: "Title of button for logging in") + static let secondaryAction = NSLocalizedString("Forgot your password?", comment: "Forgot Password Button") static let wordpressAction = NSLocalizedString("Log in with WordPress.com", comment: "Title to use wordpress login instead of email") static let loginWithEmailEmailHeader = NSLocalizedString("Enter the password for the account {{EMAIL}}", comment: "Header for Login With Password. Please preserve the {{EMAIL}} substring") static let loginWithEmailLimitHeader = NSLocalizedString("Log in with email failed, please enter the password for {{EMAIL}}", comment: "Header for Enter Password UI, when the user performed too many requests") @@ -221,14 +219,10 @@ private enum LoginStrings { private enum MagicLinkStrings { static let primaryAction = NSLocalizedString("Log in with email", comment: "Title of button for logging in") static let primaryAnimationText = NSLocalizedString("Requesting email...", comment: "Title of button for logging in") - static let secondaryAction = NSLocalizedString("Continue with Password", comment: "Continue with Password Action") - static let switchAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") - static let switchTip = NSLocalizedString("Need an account?", comment: "Link to create an account") + static let secondaryAction = NSLocalizedString("Continue with password", comment: "Continue with Password Action") } private enum SignupStrings { - static let primaryAction = NSLocalizedString("Sign Up", comment: "Title of button for signing up") - static let primaryAnimationText = NSLocalizedString("Signing Up...", comment: "Title of button for logging in") - static let switchAction = NSLocalizedString("Log In", comment: "Title of button for logging in up") - static let switchTip = NSLocalizedString("Already have an account?", comment: "Link to sign in to an account") + static let primaryAction = NSLocalizedString("Sign up", comment: "Title of button for signing up") + static let primaryAnimationText = NSLocalizedString("Signing up...", comment: "Title of button for logging in") }