From 49e46747167d2eb17f7f231209eb8adc077d436d Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 12 Oct 2023 11:12:32 +0200 Subject: [PATCH 01/38] feat: add login with --- .../Authorization.xcodeproj/project.pbxproj | 16 +++ .../Presentation/Login/SignInView.swift | 1 + .../Presentation/Login/SignInViewModel.swift | 11 +- .../Registration/SignUpView.swift | 4 + .../Registration/SignUpViewModel.swift | 11 +- .../SocialSign/SocialSignView.swift | 87 ++++++++++++++ .../SocialSign/SocialSignViewModel.swift | 108 ++++++++++++++++++ .../Authorization/SwiftGen/Strings.swift | 14 +++ .../en.lproj/Localizable.strings | 8 ++ Core/Core.xcodeproj/project.pbxproj | 99 ++++++++++++++++ .../Assets.xcassets/Socials/Contents.json | 6 + .../Socials/icon_apple.imageset/Contents.json | 22 ++++ .../icon_apple.imageset/icon_apple@2x.png | Bin 0 -> 660 bytes .../icon_apple.imageset/icon_apple@3x.png | Bin 0 -> 915 bytes .../Contents.json | 22 ++++ .../icon_facebook_white@2x.png | Bin 0 -> 800 bytes .../icon_facebook_white@3x.png | Bin 0 -> 1049 bytes .../icon_google_white.imageset/Contents.json | 22 ++++ .../icon_google_white@2x.png | Bin 0 -> 1464 bytes .../icon_google_white@3x.png | Bin 0 -> 1996 bytes .../Contents.json | 22 ++++ .../icon_microsoft_white@2x.png | Bin 0 -> 398 bytes .../icon_microsoft_white@3x.png | Bin 0 -> 463 bytes .../AvoidingHelpers/Error/CustomError.swift | 21 ++++ .../Providers/AppleSingInProvider.swift | 54 +++++++++ .../Providers/FacebookSingInProvider.swift | 31 +++++ .../Providers/GoogleSingInProvider.swift | 27 +++++ .../Providers/MicrosoftSingInProvider.swift | 86 ++++++++++++++ Core/Core/Extensions/DebugLog.swift | 25 ++++ Core/Core/Extensions/ResultExtension.swift | 23 ++++ Core/Core/SwiftGen/Assets.swift | 4 + Core/Core/View/Base/LabelButton.swift | 59 ++++++++++ Podfile.lock | 2 +- 33 files changed, 782 insertions(+), 3 deletions(-) create mode 100644 Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift create mode 100644 Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift create mode 100644 Core/Core/Assets.xcassets/Socials/Contents.json create mode 100644 Core/Core/Assets.xcassets/Socials/icon_apple.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@2x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@3x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@2x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@3x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@2x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@3x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@2x.png create mode 100644 Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@3x.png create mode 100644 Core/Core/AvoidingHelpers/Error/CustomError.swift create mode 100644 Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift create mode 100644 Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift create mode 100644 Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift create mode 100644 Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift create mode 100644 Core/Core/Extensions/DebugLog.swift create mode 100644 Core/Core/Extensions/ResultExtension.swift create mode 100644 Core/Core/View/Base/LabelButton.swift diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index fba945920..11f841636 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ 0770DE6B28D0C035006D8A5D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0770DE6D28D0C035006D8A5D /* Localizable.strings */; }; 0770DE7128D0C0E7006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7028D0C0E7006D8A5D /* Strings.swift */; }; 5FB79D2802949372CDAF08D6 /* Pods_App_Authorization_AuthorizationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FAE9B7FD61FF88C9C4FE1E8 /* Pods_App_Authorization_AuthorizationTests.framework */; }; + BA8B3A322AD5487300D25EF5 /* SocialSignView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A312AD5487300D25EF5 /* SocialSignView.swift */; }; + BADB3F552AD6DFC3004D5CFA /* SocialSignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F542AD6DFC3004D5CFA /* SocialSignViewModel.swift */; }; DE843D6BB1B9DDA398494890 /* Pods_App_Authorization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47BCFB7C19382EECF15131B6 /* Pods_App_Authorization.framework */; }; /* End PBXBuildFile section */ @@ -75,6 +77,8 @@ 96C85172770225EB81A6D2DA /* Pods-App-Authorization.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.releasedev.xcconfig"; sourceTree = ""; }; 9BF6A1004A955E24527FCF0F /* Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig"; sourceTree = ""; }; A99D45203C981893C104053A /* Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig"; sourceTree = ""; }; + BA8B3A312AD5487300D25EF5 /* SocialSignView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialSignView.swift; sourceTree = ""; }; + BADB3F542AD6DFC3004D5CFA /* SocialSignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialSignViewModel.swift; sourceTree = ""; }; E78971D8E6ED2116BBF9FD66 /* Pods-App-Authorization.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.release.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.release.xcconfig"; sourceTree = ""; }; F52826C68AEA1CF4769389EA /* Pods-App-Authorization.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.releasestage.xcconfig"; sourceTree = ""; }; F5802BBA113276950ABCD9B3 /* Pods-App-Authorization.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.releaseprod.xcconfig"; sourceTree = ""; }; @@ -139,6 +143,7 @@ 071009CC28D1E24000344290 /* Presentation */ = { isa = PBXGroup; children = ( + BA8B3A302AD5485100D25EF5 /* SocialSign */, 020C31BD290AADA700D6DEA2 /* Base */, 071009C528D1D9FA00344290 /* Login */, 07169462296D93E000E3DED6 /* Registration */, @@ -258,6 +263,15 @@ path = ../Pods; sourceTree = ""; }; + BA8B3A302AD5485100D25EF5 /* SocialSign */ = { + isa = PBXGroup; + children = ( + BA8B3A312AD5487300D25EF5 /* SocialSignView.swift */, + BADB3F542AD6DFC3004D5CFA /* SocialSignViewModel.swift */, + ); + path = SocialSign; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -467,6 +481,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BADB3F552AD6DFC3004D5CFA /* SocialSignViewModel.swift in Sources */, 02066B442906D72400F4307E /* SignUpView.swift in Sources */, 0770DE7128D0C0E7006D8A5D /* Strings.swift in Sources */, 025F40E229D360E20064C183 /* ResetPasswordViewModel.swift in Sources */, @@ -477,6 +492,7 @@ 0770DE4E28D0A677006D8A5D /* SignInView.swift in Sources */, 02F3BFE5292533720051930C /* AuthorizationRouter.swift in Sources */, 071009C728D1DA4F00344290 /* SignInViewModel.swift in Sources */, + BA8B3A322AD5487300D25EF5 /* SocialSignView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index fd98fde7c..3a5905d95 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -109,6 +109,7 @@ public struct SignInView: View { .padding(.top, 40) } } + SocialSignView(onSigned: viewModel.sign) Spacer() } .padding(.horizontal, 24) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 6d8ebfdee..c44addd81 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -79,7 +79,16 @@ public class SignInViewModel: ObservableObject { } } } - + + func sign(with result: Result) { + result.success { social in + debugLog(social) + } + result.failure { error in + errorMessage = error.localizedDescription + } + } + func trackSignUpClicked() { analytics.signUpClicked() } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index 2ce5f263c..5daa01f8a 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -103,6 +103,10 @@ public struct SignUpView: View { .padding(.bottom, 80) .frame(maxWidth: .infinity) } + SocialSignView( + signType: .register, + onSigned: viewModel.register + ) Spacer() } .padding(.horizontal, 24) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index a2142684f..1c4d493f4 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -106,7 +106,16 @@ public class SignUpViewModel: ObservableObject { } } } - + + func register(with result: Result) { + result.success { social in + debugLog(social) + } + result.failure { error in + errorMessage = error.localizedDescription + } + } + func trackCreateAccountClicked() { analytics.createAccountClicked() } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift new file mode 100644 index 000000000..a2d5c75fd --- /dev/null +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -0,0 +1,87 @@ +// +// SocialSignView.swift +// Authorization +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import SwiftUI +import Core + +struct SocialSignView: View { + + // MARK: - Properties - + + @StateObject var viewModel: SocialSignViewModel + + init( + signType: SignType = .signIn, + onSigned: @escaping (Result) -> Void + ) { + let viewModel: SocialSignViewModel = .init(onSigned: onSigned) + self._viewModel = .init(wrappedValue: viewModel) + } + + enum SignType { + case signIn + case register + } + var signType: SignType = .signIn + + private var title: String { + switch signType { + case .signIn: + return AuthLocalization.signInWith + case .register: + return AuthLocalization.signInRegister + } + } + + // MARK: - Views - + + var body: some View { + VStack(spacing: 10) { + headerView + buttonsView + } + .padding(.bottom, 20) + } + + private var headerView: some View { + HStack { + Text("\(AuthLocalization.or) \(title.lowercased()):") + .padding(.vertical, 20) + .font(.system(size: 17, weight: .medium)) + Spacer() + } + } + + private var buttonsView: some View { + Group { + LabelButton( + image: CoreAssets.iconApple.swiftUIImage, + title: "\(title) \(AuthLocalization.apple)", + backgroundColor: .black, + action: viewModel.signInWithApple + ) + LabelButton( + image: CoreAssets.iconGoogleWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.google)", + backgroundColor: .blue, + action: viewModel.signInWithGoogle + ) + LabelButton( + image: CoreAssets.iconFacebookWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.facebook)", + backgroundColor: .blue, + action: viewModel.signInWithFacebook + ) + LabelButton( + image: CoreAssets.iconMicrosoftWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.microsoft)", + backgroundColor: .black, + action: viewModel.signInWithMicrosoft + ) + } + } +} diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift new file mode 100644 index 000000000..9d8c99ea3 --- /dev/null +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -0,0 +1,108 @@ +// +// SocialSignViewModel.swift +// Authorization +// +// Created by Eugene Yatsenko on 11.10.2023. +// + +import SwiftUI +import Core +import AuthenticationServices +import FacebookLogin +import GoogleSignIn +import MSAL + +enum Socials { + case apple(ASAuthorizationAppleIDCredential) + case facebook(LoginManagerLoginResult) + case google(GIDSignInResult) + case microsoft(MSALAccount, String) +} + +final public class SocialSignViewModel: ObservableObject { + + // MARK: - Properties - + + private var onSigned: ((Result) -> Void) + + init(onSigned: @escaping (Result) -> Void) { + self.onSigned = onSigned + } + + private let appleSingInProvider: AppleSingInProvider = .init() + private let googleSingInProvider: GoogleSingInProvider = .init() + private let facebookSingInProvider: FacebookSingInProvider = .init() + private let microsoftSingInProvider: MicrosoftSingInProvider = .init() + + private var topViewController: UIViewController? { + UIApplication.topViewController() + } + + // MARK: - Public Intens - + + func signInWithApple() { + appleSingInProvider.request { [weak self] result in + guard let self = self else { + return + } + result.success { self.success(with: .apple($0)) } + result.failure(self.failure) + } + } + + func signInWithGoogle() { + topViewController.flatMap { + googleSingInProvider.signIn( + withPresenting: $0 + ) { [weak self] result, error in + guard let self = self else { + return + } + result.flatMap { self.success(with: .google($0)) } + error.flatMap(self.failure) + } + } + } + + func signInWithFacebook() { + topViewController.flatMap { + facebookSingInProvider.signIn( + withPresenting: $0, + completion: { [weak self] result, error in + guard let self = self else { + return + } + result.flatMap { self.success(with: .facebook($0)) } + error.flatMap(self.failure) + } + ) + } + } + + func signInWithMicrosoft() { + topViewController.flatMap { + microsoftSingInProvider.signIn( + withPresenting: $0 + ) { [weak self] account, token, error in + guard let self = self else { + return + } + if let account = account, let token = token { + self.success(with: .microsoft(account, token)) + } + if let error = error { + self.failure(error) + } + } + } + } + + private func success(with social: Socials) { + onSigned(.success(social)) + } + + private func failure(_ error: Error) { + onSigned(.failure(error)) + } + +} diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index 9d7d92b4e..88894cb4d 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -10,6 +10,20 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces public enum AuthLocalization { + /// Apple + public static let apple = AuthLocalization.tr("Localizable", "APPLE", fallback: "Apple") + /// Facebook + public static let facebook = AuthLocalization.tr("Localizable", "FACEBOOK", fallback: "Facebook") + /// Google + public static let google = AuthLocalization.tr("Localizable", "GOOGLE", fallback: "Google") + /// Microsoft + public static let microsoft = AuthLocalization.tr("Localizable", "MICROSOFT", fallback: "Microsoft") + /// Or + public static let or = AuthLocalization.tr("Localizable", "OR", fallback: "Or") + /// Register with + public static let signInRegister = AuthLocalization.tr("Localizable", "SIGN_IN_REGISTER", fallback: "Register with") + /// Sign in with + public static let signInWith = AuthLocalization.tr("Localizable", "SIGN_IN_WITH", fallback: "Sign in with") public enum Error { /// Invalid email address public static let invalidEmailAddress = AuthLocalization.tr("Localizable", "ERROR.INVALID_EMAIL_ADDRESS", fallback: "Invalid email address") diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index 88365042c..c68796dbf 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -29,3 +29,11 @@ "FORGOT.REQUEST" = "Reset password"; "FORGOT.CHECK_TITLE" = "Check your email"; "FORGOT.CHECK_Description" = "We have sent a password recover instructions to your email "; + +"SIGN_IN_WITH" = "Sign in with"; +"SIGN_IN_REGISTER" = "Register with"; +"APPLE" = "Apple"; +"GOOGLE" = "Google"; +"FACEBOOK" = "Facebook"; +"MICROSOFT" = "Microsoft"; +"OR" = "Or"; diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index eed739e2e..7e1f2eb9f 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -112,6 +112,17 @@ 0770DE7928D0C4A9006D8A5D /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7828D0C4A9006D8A5D /* RoundedCorners.swift */; }; 0770DE7B28D0C78C006D8A5D /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7A28D0C78C006D8A5D /* Theme.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; + BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; + BA8FA6612AD5974300EA029A /* AppleSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */; }; + BA8FA6642AD597DC00EA029A /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6632AD597DC00EA029A /* CustomError.swift */; }; + BA8FA6682AD59A5700EA029A /* LabelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6672AD59A5700EA029A /* LabelButton.swift */; }; + BA8FA66A2AD59B5500EA029A /* GoogleSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */; }; + BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; + BA8FA66E2AD59E7D00EA029A /* FacebookSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */; }; + BA8FA6702AD59EA300EA029A /* MicrosoftSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */; }; + BADB3F532AD6B3A5004D5CFA /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BADB3F522AD6B3A5004D5CFA /* MSAL */; }; + BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; + BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; /* End PBXBuildFile section */ @@ -241,6 +252,14 @@ 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = ""; }; 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; + BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; + BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSingInProvider.swift; sourceTree = ""; }; + BA8FA6632AD597DC00EA029A /* CustomError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; + BA8FA6672AD59A5700EA029A /* LabelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelButton.swift; sourceTree = ""; }; + BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSingInProvider.swift; sourceTree = ""; }; + BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookSingInProvider.swift; sourceTree = ""; }; + BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftSingInProvider.swift; sourceTree = ""; }; + BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -258,8 +277,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */, 025EF2F62971740000B838AB /* YouTubePlayerKit in Frameworks */, C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */, + BADB3F532AD6B3A5004D5CFA /* MSAL in Frameworks */, + BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -277,6 +299,8 @@ 027BD3A12909470F00392132 /* AvoidingHelpers */ = { isa = PBXGroup; children = ( + BA8FA6622AD597D300EA029A /* Error */, + BA8FA65F2AD5973500EA029A /* Providers */, 027BD3A22909471900392132 /* Avoider */, 027BD3A32909471F00392132 /* Scroller */, 027BD3A42909472500392132 /* State */, @@ -341,6 +365,8 @@ 0727878228D31287002E9142 /* DispatchQueue+App.swift */, 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */, 02284C172A3B1AE00007117F /* UIApplicationExtension.swift */, + BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */, + BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -536,10 +562,30 @@ 027BD3C42909707700392132 /* Shake.swift */, 023A1135291432B200D0D354 /* RegistrationTextField.swift */, 023A1137291432FD00D0D354 /* FieldConfiguration.swift */, + BA8FA6672AD59A5700EA029A /* LabelButton.swift */, ); path = Base; sourceTree = ""; }; + BA8FA65F2AD5973500EA029A /* Providers */ = { + isa = PBXGroup; + children = ( + BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */, + BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */, + BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */, + BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */, + ); + path = Providers; + sourceTree = ""; + }; + BA8FA6622AD597D300EA029A /* Error */ = { + isa = PBXGroup; + children = ( + BA8FA6632AD597DC00EA029A /* CustomError.swift */, + ); + path = Error; + sourceTree = ""; + }; C9DFE47E699CFFA85A77AF2C /* Pods */ = { isa = PBXGroup; children = ( @@ -621,6 +667,9 @@ name = Core; packageProductDependencies = ( 025EF2F52971740000B838AB /* YouTubePlayerKit */, + BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */, + BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */, + BADB3F522AD6B3A5004D5CFA /* MSAL */, ); productName = Core; productReference = 0770DE0828D07831006D8A5D /* Core.framework */; @@ -657,6 +706,9 @@ mainGroup = 0770DDFE28D07831006D8A5D; packageReferences = ( 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, + BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, + BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, + BADB3F512AD6B3A5004D5CFA /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, ); productRefGroup = 0770DE0928D07831006D8A5D /* Products */; projectDirPath = ""; @@ -775,6 +827,7 @@ 028F9F37293A44C700DE65D0 /* Data_ResetPassword.swift in Sources */, 022C64E429AE0191000F532B /* TextWithUrls.swift in Sources */, 0283348028D4DCD200C828FC /* ViewExtension.swift in Sources */, + BA8FA66A2AD59B5500EA029A /* GoogleSingInProvider.swift in Sources */, 02A4833529B8A73400D33F33 /* CorePersistenceProtocol.swift in Sources */, 02512FF0299533DF0024D438 /* CoreDataHandlerProtocol.swift in Sources */, 0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */, @@ -783,6 +836,7 @@ 027BD3BE2909478B00392132 /* UIResponder+CurrentResponder.swift in Sources */, 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */, 076F297F2A1F80C800967E7D /* Pagination.swift in Sources */, + BA8FA6642AD597DC00EA029A /* CustomError.swift in Sources */, 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */, 02C917F029CDA99E00DBB8BD /* Data_Dashboard.swift in Sources */, 024FCD0028EF1CD300232339 /* WebBrowser.swift in Sources */, @@ -794,6 +848,8 @@ 027BD3A82909474200392132 /* KeyboardAvoidingViewController.swift in Sources */, 0770DE7B28D0C78C006D8A5D /* Theme.swift in Sources */, 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */, + BA8FA6612AD5974300EA029A /* AppleSingInProvider.swift in Sources */, + BA8FA6702AD59EA300EA029A /* MicrosoftSingInProvider.swift in Sources */, 020306CC2932C0C4000949EA /* PickerView.swift in Sources */, 027BD3C52909707700392132 /* Shake.swift in Sources */, 027BD39C2908810C00392132 /* RegisterUser.swift in Sources */, @@ -815,11 +871,14 @@ 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, 027DB33528D8C8FE002B6862 /* Data_MyCourse.swift in Sources */, 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */, + BA8FA6682AD59A5700EA029A /* LabelButton.swift in Sources */, 02A4833C29B8C57800D33F33 /* DownloadView.swift in Sources */, 027BD3AD2909475000392132 /* KeyboardScroller.swift in Sources */, 070019A528F6F17900D5FC78 /* Data_Media.swift in Sources */, 024D723529C8BB1A006D36ED /* NavigationBar.swift in Sources */, + BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */, 0727877928D23BE0002E9142 /* RequestInterceptor.swift in Sources */, + BA8FA66E2AD59E7D00EA029A /* FacebookSingInProvider.swift in Sources */, 0770DE2E28D09743006D8A5D /* API.swift in Sources */, 028CE96929858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift in Sources */, 027BD3A92909474200392132 /* KeyboardAvoidingViewControllerRepr.swift in Sources */, @@ -834,6 +893,7 @@ 024D865E28F02C6B0077E0A0 /* WebView.swift in Sources */, 02F164372902A9EB0090DDEF /* StringExtension.swift in Sources */, 0231CDBE2922422D00032416 /* CSSInjector.swift in Sources */, + BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */, 0236961928F9A26900EEF206 /* AuthRepository.swift in Sources */, 023A1136291432B200D0D354 /* RegistrationTextField.swift in Sources */, 02C2DC0829B63D6200F4445D /* WebViewHTML.swift in Sources */, @@ -1855,6 +1915,30 @@ minimumVersion = 1.5.0; }; }; + BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.0.0; + }; + }; + BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/facebook/facebook-ios-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 14.1.0; + }; + }; + BADB3F512AD6B3A5004D5CFA /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/AzureAD/microsoft-authentication-library-for-objc"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.2.17; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1863,6 +1947,21 @@ package = 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */; productName = YouTubePlayerKit; }; + BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */ = { + isa = XCSwiftPackageProductDependency; + package = BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignIn; + }; + BADB3F522AD6B3A5004D5CFA /* MSAL */ = { + isa = XCSwiftPackageProductDependency; + package = BADB3F512AD6B3A5004D5CFA /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */; + productName = MSAL; + }; + BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */ = { + isa = XCSwiftPackageProductDependency; + package = BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; + productName = FacebookLogin; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Core/Core/Assets.xcassets/Socials/Contents.json b/Core/Core/Assets.xcassets/Socials/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Core/Core/Assets.xcassets/Socials/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/Contents.json b/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/Contents.json new file mode 100644 index 000000000..f98d12c7c --- /dev/null +++ b/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_apple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_apple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@2x.png b/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ca46033ab4cd4630f767c5d5d802a3c030946ec GIT binary patch literal 660 zcmV;F0&D$=P)zCs5uexRS=Hj+_+5)@3^&i)AZL zSsU-OMchky?%=KOvBmkk%x&{$tZ)rfPEi^=woxv^UE5_FH&|ahLq6MWDoh?er^{Kh zO&f};659VyTuk+GtPNg7OQeb8mnBZ;#0oi|j~!)V4100HgfC8dHbV9Jvn}>i6_zMF z;MNRWO&v#^+?@K`b9-jItv~L7dj^NFXW|dI0(-D!QMwd37ihh`D{asf*eu1|j#L{w z3M4Ow8gbADI|v7L2Md7**b{4mM}bAnW{es|+$~OF%Yc+x&=t6WEh28@54r-ku; zD6YU2>=7?6U6E%6D`hP)>wqn!Y)_K<{5ZHk7{qKlcG%eWV1lgN4+iZwiFBS~1cH|8 zz7Y;H&4Y4D#ET(ZmWPrD^RyR2_g%{Ww6ZtDo|Mayz>Wtk%(%S5uF9Ec<$npv8bWO| z$0bm8xAYe5=$h)DuJ2S`!d?^CsDVjY=$RsGK-Q5WD>{llX=I%gqDg~w&weBHVp!-0 umD_Ui&E*&*HS46ECw(8SwKAugZT$nDmtTxb$wCPL0000P)nJ0@?|ZPJlas%>>B?s>GdJ*YTatkED$R z-+Ow5oa68#-JMPr@Wvbe6(Sn5-LtLPwro>agGFUtV4Eoh@(w48c#?mt*fLm#h2Hv5 zjgdh+eqN?<4$XLvrm%))$gQ@0p<6SBW>`R(C{(T+Lo4DbO6Lxmp#ffnX2>NCDQs1T zufPIUiDENohebRlcytO2^aw54E@1(3duxaX(w-$X=@2U+yE@kV9N_?vTO}pjCIj&GIZXLT-*ez=lxa{46qf54Ue> zM;5A?gmGsnmE1-@g?=iw>pHRNoU`N($z$lILPMyq7slSJW9a9movL=t*<0vm-j!$n z6~=xA)?Mdu;d|9}Flc+$!a@0K=mgXjn?XN$XrL|j0=g+;=VxnS{1h8(i`AYQ7O}zK zVo7@e+EK*@W4W<8tci_ILu+EkvB6g*ww*2#bhG1q_I+ru?b!;tk$Q6$8f=R_URC|y z5j?fE#r}YPCZU|cS{Mtm{j9nM+G01bfjmapYc$!bLGeXf*eRc0H7gcf`|NkvVclGv zyToo_2Z>M-EH&`XFqR8S*a_vDJXAN9=Q|mU5z39{Fi7wzp0QQ=nb@i43ycx-tht3M z=viNAw&dNa@5#F*@6Mu?3h!MDE3-78<-KJY-&#{ZH_r}BtwGJ87wMGAn!$FOj3e!k=`u?r{WoRi?!nuMD=1rl(;1*op2=<86X2U|S+iwmolKc64$_rM7 zL;rp3;m>q>loWXy`Jzs8az(8LhO=S&3Eyqrc>mJdM+|RIl6&3sb*{e%a((_~!hd~D puG2dy^(5D5ol{z)^)ltYzW@n)sO9)zkTn1R002ovPDHLkV1mT;toHx_ literal 0 HcmV?d00001 diff --git a/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/Contents.json b/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/Contents.json new file mode 100644 index 000000000..af6600d1a --- /dev/null +++ b/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_facebook_white@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_facebook_white@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@2x.png b/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..109a676046af4dc94f930d1dc3d5b514955efa3f GIT binary patch literal 800 zcmV+*1K<3KP)lIA%S{kAxO9R`H@HraW`eX6BpZ|spc6D3s1lgep_m^=2KsqV z<<&0kSHj4)0QklPAOs>V=%UD$WD9L2XR?H^K@cP$#31n`IHJohvPfGO*pU<23)wR{ zbA~Y?(J|Q(hx1OhBU9iTjx9(k5^Zlr8%Ok3U(1Ef#+KHD2WSIj zvkH&3?C?Lg9ma~<28mIQ7&_8Xe2SI%A6CX0o}vow=`lZnEQ)MnHeAH(NGTXjLc;%p zAO4!rHzO|<^+)gr3%^7^&o<=EiY^BbfTG^GQ`M1MRhoa-wAtV%`_mk7hcdiEvv8^P zBjWMOY17r~Hrn>){B+%x{A=>`8)A`%B?e?NTL*rgv?SnZmPbBpm=K*pUEaGh ziyyb>^Oe^9zJ-j%iNeE*4Ejx;A$Qe=u;!2c!2G7q0|hp#HhvMW zrw=?ri&`L16or!Ck|>s^LC925!;y16offY+u0piXTt3MbO4@q>K0z ei^6U+RP+b`ibVn+mM94T0000gi6@nC1s&B`UZIv6O|-5$|AoA-|-iOoh;piz5Gm*tn-q1Tq)% zkHR5ki*YO{^O8RlE-&QUd$_9Ok&-WQD62Ys;}%^)VKlI9xkndJzeUM$D-LJ) zNT-9FYDN8QQ>yq@b>EW;QDRPBa6BYF(k3mE2c8P;ko+!N#aon(Q&RGejE&}0vzs;7 z8MU}pDW`dyMOl#Mlay+>xZ~@6Y+6W6ep%3gQ8KlEkWt+Nd{?2LZ~(OzW?{>3YWYt3 zdIdjdwmTdbX`kZKGbr;GpTuwk9o8nMyZP;UOgF=3J2E}_iZEFBY{f4&!cf-uMexTP z!eYxQ&I*4^SE=BS1T8QRu#0(KX=@_(;zT|ag}H%0mQ6>U#?|GiM(Bc=Q-MYKjpXYhe^dK5+*3$QY$+_mf6_ z{?8oZ#m?`yraLk?y3_xPQ31^C>BIY&*%@2-nqdAY^t`WYiuVG;}*U0gEW2xID2 zg5i=BKkQCaJz%(O-TRg>1NNHN-Coi*wK+7e(Xia}Ct(KYHE*^t)xOE_A2^Muy9()v z=C*%Wg)s^ap-SzYg5A4(!s0w3yj@Q&c_<4@s@9ccBD&Y}HG6p8SA}v)g^>vxPQK$b zrayC*byglKS0atONnO^)?XG-Gy4U5@c292`xEje~iRcZ088advY(i zKJ;f)oNOF2-Xj7XYosiK$iq25#7jDbvR8Xvsr+TId4v2GCHcsEjknanZ^--yg{qc> Ty#Z_K00000NkvXXu0mjfylU7l literal 0 HcmV?d00001 diff --git a/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/Contents.json b/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/Contents.json new file mode 100644 index 000000000..3c4d94ecc --- /dev/null +++ b/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_google_white@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_google_white@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@2x.png b/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..64c2157f944381a6ebc9b8c954f913809093929d GIT binary patch literal 1464 zcmV;p1xNacP)5%6XnJgN2gNy5 zX(g(f8xq7`1V|w*)QBTU-6EByv=HZ`bz<-C@Wzf!>}0*W_S$iOQoOt4H{SowyqWiQ zCIBxm1U%xA(jyC!p)f{d!q}0LyCjLp9sm^+VxT$E8da0v0ab~AGSGu!jbQ>T#DD}H zS0s?YPgN3y>DI3oG^h~Q;+I28P+xsM3d9_9CD zBJek(O%fe<(-V%wFQ4v_GZ|wBPZg*!gpu%}*y4mETRVft{oNr7=1eVG+X_NpN(^Ks zTBC`w1Dlrn^2tEgWptC<^%Txw48}HBeIs^umg2c^oc6-72jG1t+G9UW+1c&tHr!Qs zi^Cqkg&&SBhMm~%5YGpvp3(1B>?(W?XOLioJjfSOW*%ceJ5ickuBsd19I>R8!8HjYK*3pwkVJaA5Y_T(V8LUQl?zY=->*rWGiGyq0ZGhSK5X1bs65(6- z&#^U^W2=MgtR_hqIvspPtvtLx9_R{#Nud(qSqb@*&8UB7W1($i^Ufg$wMgQb?(Z$< zBK4p!Rg#vB;vaYj{I5JjYXsq0CHx~jFO3ieGxD@De9U^R+xPPXMU-%86+GIizI_@NzA-?wv9t!;j>0~!c1^Npy0 zg>X*u$t4ilQjq%bunG-^h>^6@F16(DBfYsk3)jIFeUodLVvtVo5FDGy`AdQ0{9MHJ zQ+7}%DV^F-!6gJEH3l&mJcQw&ay2EZ7x!s6V95~&brR4(h`KKGBZ_Br&DFht2{fyj zIx)uX*Pwx5jOE1>+epc5QrW$N;s98&Ga}&apsN;YGA(<=sYkOj#s`#H92#zHP)2i4 zVaIRjHU%04T@*EbB&m3_EE>jrE5kJ=!57cpf}5$=)w@HtdZC8&O|Fk&H(#?cOU5n* zTKNgx*Sn$dg59hh8GiV9Mxh8pA?yNeBs;;4LqXn%K8W%ob;-O{$5DS3la9d`O++R6C2^2 zpg}Tmul)bim36+JV>d|isM{|J;*Y-nPh`pPYv2Gr@1y_UJ2Cgc2vkA;WNO#|=6IdR zq(af^aZxJYHTR){33D7=fIkosm^9(Wje6!nvQC9-O{*tEzNhEWkNr-nMbKp~l-#?R zw{M)C!opE-3xOzKH4~so-R(iRo24PGP(t{CSMudlKM4CDUIc>2GHBT9y*!66zGVrW zw?jd3Q{b&%%^Oe>T`Ng)#~{hQW?H%iZ~5k@AAz*?u9e9JZ1u>Fdb?TjZZ(JyXLU}< zeE){Pw>(%>hP^pG|5?k}ZuXY1uY2_X!sct>UmDH15n<8Tr!9k4ww5m=3+JMmz>Hpe zIt}WG;rkPwy?PbQroU7(T|7WbvE7r(&J0B08x5YW0+a)Ld-~_0TVr;1j<+VzGN zItt-cfC(a*xI6Tv?UoM5)jn)?9DopJ1U%1gc)Zx}Gse}ysN)6Pini~!J;aV34wvxh zbQXi$R3*7mRsVaSU`$v`3ko$MgnLlO&|4jh3zERiy6Y!Ha}C6! SX*>-80000K~#7F?VQ_B z99100e`hXSgzC1XrN%Ta#s?oX#S{psnhsE-iJF3#X!W6B;u8<8F)=o#VM%O#Z2K3m zJSc6Vp?xqK%VnAvL)xZL-%Lz8Hqo|F%W~NQyEAkA{bpI%UG{o*c3C$45ZGaEyL|U| zJHK-lfG0d57Z?;%u`4zQ7)_49gX+VJ}h%mE9S~bqCe%#cc zu=p91hxUw4>fkc23NzE*+=F){LA+3040OAJz2i6h;1aF~vt}WKP<%lW(N$IMb;HH- z5@zo0R&O}$2%gOCzLScyv z7{U3Dz#Z%Kf*|>dLZpl_YjNccxD5lH1HXo*sxO(f(+#>FNOsyCP4m<$ON}^a)UY%- znyuBkAxh+BreLO{nMCn|d*FbNNf@_W2ycwN6V_WlcKtFGfCopKd|0^ANZxf( zh)uy4Md^pUVTNf*ZEjgfGU`Wf`tG+k1sF&kw?!f1EM|G4%4<#Ty0iS-KHw`dh`|i2 zz@ij`xH?9)t^Ug9S$O)>KS6pX1i4clq7dsAL)M~L8?l8f=JD8b5c*?Rwzn!9QHXVm zk!?$hJ;16gsCnluRKIZ_vV!x0auJ1Cx0vv#I)x8Oizw?sH7xzu0QP)Es{8o&j@{^m zGQzPKawCA$m8D$23_Cvg%Xv(OWyyK95lazAm_#>9dLjm63d%`b%U@0|6vYt+3#Fsk zUQqS=eW-c&t`mcZaPk_g?)Hx7ZbW zG~b*@u24r{3&9r4(D>K2e%e+{Q)v{PL{TMK$9x8^P+8^SS;V?Rg6)ml(QS!K%nM=! zSlfyLWrQ8mW|5f9ydXx=mShwQ2JAp|TWG>Tf7NywMF|iz#=W z2$KSdmT!RojKT9z#z9})-l`w0VKu`L-}DuHzfQLtw%ikXQy3JHeh%# zqT7-RVFLsSD_m$%PN`yZJ|EpySQ`XFV_-0zTRn5ndJ4W+Xu)>H)$`y6t1AIeEBxRF zjBCJiug|0yS8|4r;n4~l)n@qtL_^K02&c|Q0ue6`Txh(efP_CN_i?F&Isz*aZnts{)3$Qh?m$#weXUxQ1_FT@{U z{6y>UM_woqBvloSdjb8++IV5ABkcn{)fKnGb6<^~SWHSdbRN9az2zCI{mfF2NYEvydl&orx-g1j#$^|ZA>UwtrI?Zp znAHZi(xp0m+#_l&gza=AH&_>j(l-8dVhvfx@xWpDX>~v3fX8CtiLDZ6Q4O9GwH9ai9w>Ib)6J&>}QxPJW_0N+P@9<<8$|bAc<~7Y-)oyUF^<#ku%=Z zkh$7cjQWN|!IISn;uu)FG$iia)Q?fPmG>MpSq`~X^YHsUP#`F#bevrr$x{>>2Mi_K zy-AqKW0wtVMdUrZNaAqD2AeN-yB?HVFa!SkV&7EwsayGGLc>-Rf3gEfXOtR8Iz8&D zJjGpHwTVyUNB0^KbuSHH(_%>0F#DZ=7+0@+)CrYyT_DK%5FnA1Q*BMdA85`zrTggk zv{%rirf(kmK~HxgSy~7}n#|Z?e3QsJl70+GS$+wsXFdaF)EWl zr9a8$1LLQ6_|lz{ahAZUvgoq}+h*2rrS5xJ+5H`G>2a!4{Fy`{G72-@bva;GT5T+* ziBsx?JikJ4|7qaL!?^wUH%!~%n$NSjN7m~cT8BHaHjyXX%v6Pj;tsi%5mF z(#4fna#{dFXd? zU(&9H6+G5TJ`g}y7{ZA=Ap3ZUrE^LYoU`6c1!RVM4nJh^c}wqi2xH2cRHh zage(c!@6@aFM%AEbVpxD28NCO+1W1``n`2d^h=J@+|j1 zlsoZ7y|JL*X{pZX=wu)1KF(Lp*>gjb4?d7zQuh7riR1ehh~#a1!z1M{|E}R5`-wYu znCI%n?OXpbd{5)4pAU`s4h2^f%s5g!u|iMiyz8T#9ya2g&mWc4eD=Cyy?WgSb%o7) z&da}zT_F0+A+>aV_}4&@quQ@hj~Mov*Z*Aiacc9K;P8hQK{sB-AC#_ZI0G-mrZf@7m>OVV1kgDfhQ*XyS}XV10YrDDSwX(yyP zdD+VqeQa-TO!XP2|NOe_u0K%>$%vQb-Mg_ zeY9v6f6E>d->JnnNHDZuWuUh@W&Ij7fFSQ-W|3155IfuOw?cX%+EW2hwfYH rykGTAxtjhPbW>;azbRG?sbgf&wmtFYQmHgB{24r5{an^LB{Ts5P`I=q literal 0 HcmV?d00001 diff --git a/Core/Core/AvoidingHelpers/Error/CustomError.swift b/Core/Core/AvoidingHelpers/Error/CustomError.swift new file mode 100644 index 000000000..b73f04b0a --- /dev/null +++ b/Core/Core/AvoidingHelpers/Error/CustomError.swift @@ -0,0 +1,21 @@ +// +// CustomError.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation + +public enum CustomError: Error { + case error(text: String) +} + +extension CustomError: LocalizedError { + public var errorDescription: String? { + switch self { + case .error(let text): + return text + } + } +} diff --git a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift new file mode 100644 index 000000000..a759d1251 --- /dev/null +++ b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift @@ -0,0 +1,54 @@ +// +// AppleSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation +import AuthenticationServices + +public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDelegate { + + public override init() {} + + private var completion: ((Result) -> Void)? + private let appleIDProvider = ASAuthorizationAppleIDProvider() + + public func request(completion: ((Result) -> Void)?) { + let request = appleIDProvider.createRequest() + request.requestedScopes = [.fullName, .email] + + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) + authorizationController.delegate = self + authorizationController.performRequests() + + self.completion = completion + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else { + completion?(.failure(CustomError.error(text: "ASAuthorizationAppleIDCredential is nil"))) + return + } + + let userIdentifier = appleIDCredential.user + let fullName = appleIDCredential.fullName + let email = appleIDCredential.email + + debugLog("User id is \(userIdentifier) \n Full Name is \(String(describing: fullName)) \n Email id is \(String(describing: email))") + + completion?(.success(appleIDCredential)) + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: Error + ) { + debugLog(error) + completion?(.failure(error)) + } +} diff --git a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift new file mode 100644 index 000000000..92d8b3f67 --- /dev/null +++ b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift @@ -0,0 +1,31 @@ +// +// FacebookSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation +import FacebookLogin + +public final class FacebookSingInProvider { + + private let loginManager = LoginManager() + + public init() {} + + public func signIn( + withPresenting: UIViewController, + completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void) + ) { + loginManager.logIn( + permissions: ["public_profile"], + from: withPresenting, + handler: completion + ) + } + + public func signOut() { + loginManager.logOut() + } +} diff --git a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift new file mode 100644 index 000000000..e3d76a5fa --- /dev/null +++ b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift @@ -0,0 +1,27 @@ +// +// GoogleSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import GoogleSignIn + +public final class GoogleSingInProvider { + + public init() {} + + public func signIn( + withPresenting: UIViewController, + completion: @escaping ((GIDSignInResult?, Error?) -> Void) + ) { + GIDSignIn.sharedInstance.signIn( + withPresenting: withPresenting, + completion: completion + ) + } + + public func signOut() { + GIDSignIn.sharedInstance.signOut() + } +} diff --git a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift new file mode 100644 index 000000000..f80aca25d --- /dev/null +++ b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift @@ -0,0 +1,86 @@ +// +// MicrosoftSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation +import MSAL + +public typealias MSLoginCompletionHandler = (MSALAccount?, String?, Error?) -> Void + +public final class MicrosoftSingInProvider { + + private let scopes = ["User.Read", "email"] + private var result: MSALResult? + + public init() {} + + public func signIn( + withPresenting: UIViewController, + completion: @escaping MSLoginCompletionHandler + ) { + do { + let clientApplication = try createClientApplication() + + let webParameters = MSALWebviewParameters(authPresentationViewController: withPresenting) + let parameters = MSALInteractiveTokenParameters(scopes: scopes, webviewParameters: webParameters) + clientApplication.acquireToken(with: parameters) { [weak self] result, error in + guard let self = self else { return } + + guard let result = result, error == nil else { + completion(nil, nil, error) + return + } + + self.result = result + let account = result.account + completion(account, result.accessToken, nil) + } + } catch let createApplicationError { + completion(nil, nil, createApplicationError) + } + } + + public func signOut() { + do { + let account = try? currentAccount() + + if let account = account { + let application = try createClientApplication() + try application.remove(account) + } + } catch let error { + debugLog("Logout", "Received error signing user out: \(error)") + } + } + + + private func createClientApplication() throws -> MSALPublicClientApplication { + let configuration = MSALPublicClientApplicationConfig(clientId: "") + + do { + return try MSALPublicClientApplication(configuration: configuration) + } catch { + throw CustomError.error(text: error.localizedDescription) + } + } + + @discardableResult + private func currentAccount() throws -> MSALAccount { + let clientApplication = try createClientApplication() + + guard let account = try clientApplication.allAccounts().first else { + throw CustomError.error(text: "Error") + } + + return account + } + + func getUser(completion: (MSALAccount) -> Void) { + guard let user = result?.account else { return } + completion(user) + } + +} diff --git a/Core/Core/Extensions/DebugLog.swift b/Core/Core/Extensions/DebugLog.swift new file mode 100644 index 000000000..1ceb35482 --- /dev/null +++ b/Core/Core/Extensions/DebugLog.swift @@ -0,0 +1,25 @@ +// +// DebugLog.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation + +public func debugLog( + _ item: Any..., + filename: String = #file, + line: Int = #line, + funcname: String = #function +) { +#if DEBUG + print( + """ + 🕗 \(Date()) + 📄 \(filename.components(separatedBy: "/").last ?? "") \(line) \(funcname) + ℹ️ \(item) + """ + ) +#endif +} diff --git a/Core/Core/Extensions/ResultExtension.swift b/Core/Core/Extensions/ResultExtension.swift new file mode 100644 index 000000000..d9a327768 --- /dev/null +++ b/Core/Core/Extensions/ResultExtension.swift @@ -0,0 +1,23 @@ +// +// ResultExtension.swift +// Core +// +// Created by Eugene Yatsenko on 11.10.2023. +// + +import Foundation + +extension Result { + @discardableResult + public func success(_ handler: (Success) -> Void) -> Self { + guard case let .success(value) = self else { return self } + handler(value) + return self + } + @discardableResult + public func failure(_ handler: (Failure) -> Void) -> Self { + guard case let .failure(error) = self else { return self } + handler(error) + return self + } +} diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index ee82da098..de536d475 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -94,6 +94,10 @@ public enum CoreAssets { public static let logOut = ImageAsset(name: "logOut") public static let noAvatar = ImageAsset(name: "noAvatar") public static let removePhoto = ImageAsset(name: "removePhoto") + public static let iconApple = ImageAsset(name: "icon_apple") + public static let iconFacebookWhite = ImageAsset(name: "icon_facebook_white") + public static let iconGoogleWhite = ImageAsset(name: "icon_google_white") + public static let iconMicrosoftWhite = ImageAsset(name: "icon_microsoft_white") public static let rotateDevice = ImageAsset(name: "rotateDevice") public static let sub = ImageAsset(name: "sub") public static let alarm = ImageAsset(name: "alarm") diff --git a/Core/Core/View/Base/LabelButton.swift b/Core/Core/View/Base/LabelButton.swift new file mode 100644 index 000000000..e61a25949 --- /dev/null +++ b/Core/Core/View/Base/LabelButton.swift @@ -0,0 +1,59 @@ +// +// LabelButton.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import SwiftUI + +public struct LabelButton: View { + + // MARK: - Properties - + + private var image: Image + private var title: String + private var textColor: Color + private var backgroundColor: Color + private var cornerRadius: CGFloat + private var action: () -> Void + + public init( + image: Image, + title: String, + textColor: Color = .white, + backgroundColor: Color = .accentColor, + cornerRadius: CGFloat = 8, + action: @escaping () -> Void + ) { + self.image = image + self.title = title + self.textColor = textColor + self.backgroundColor = backgroundColor + self.cornerRadius = cornerRadius + self.action = action + } + + // MARK: - Views - + + public var body: some View { + Button { + action() + } label: { + Label { + Text(title) + .foregroundStyle(textColor) + .padding(.leading, 10) + Spacer() + } icon: { + image.padding(.leading, 10) + } + } + .frame(height: 44) + .background(backgroundColor) + .clipShape( + RoundedRectangle(cornerRadius: cornerRadius) + ) + + } +} diff --git a/Podfile.lock b/Podfile.lock index 5b998cd5e..dc0017b13 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -182,4 +182,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1639b311802f5d36686512914067b7221ff97a64 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 From 87a826bc6b9f97aa491d79b1ce2639af920330ab Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 19 Oct 2023 10:40:33 +0200 Subject: [PATCH 02/38] chore: remove spacing --- Profile/Profile/Presentation/Profile/ProfileView.swift | 2 +- Profile/Profile/Presentation/Profile/ProfileViewModel.swift | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index 2d61ed18c..8e008dab7 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -18,7 +18,7 @@ public struct ProfileView: View { self._viewModel = StateObject(wrappedValue: { viewModel }()) self._settingsTapped = settingsTapped } - + public var body: some View { ZStack(alignment: .top) { // MARK: - Page Body diff --git a/Profile/Profile/Presentation/Profile/ProfileViewModel.swift b/Profile/Profile/Presentation/Profile/ProfileViewModel.swift index 49e5dd254..5ddbcfee4 100644 --- a/Profile/Profile/Presentation/Profile/ProfileViewModel.swift +++ b/Profile/Profile/Presentation/Profile/ProfileViewModel.swift @@ -10,7 +10,7 @@ import Core import SwiftUI public class ProfileViewModel: ObservableObject { - + @Published public var userModel: UserProfile? @Published public var updatedAvatar: UIImage? @Published private(set) var isShowProgress = false @@ -22,8 +22,7 @@ public class ProfileViewModel: ObservableObject { } } } - - + let router: ProfileRouter let config: Config let connectivity: ConnectivityProtocol From 9d068d16547fd5690fb1b7470690bc3ae93e069c Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 26 Oct 2023 11:43:04 +0200 Subject: [PATCH 03/38] fix: padding bottom social view --- .../Authorization/Presentation/Registration/SignUpView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index fb6a496a0..6b3fbf501 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -103,13 +103,13 @@ public struct SignUpView: View { viewModel.trackCreateAccountClicked() } .padding(.top, 40) - .padding(.bottom, 80) .frame(maxWidth: .infinity) } SocialSignView( signType: .register, onSigned: viewModel.register ) + .padding(.bottom, 30) Spacer() } .padding(.horizontal, 24) From ada811fde565fa174f97ea3f49ac97642a143885 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 31 Oct 2023 15:17:05 +0100 Subject: [PATCH 04/38] chore: result social --- .../Presentation/Login/SignInViewModel.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index c44addd81..f7cb2b291 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -81,9 +81,7 @@ public class SignInViewModel: ObservableObject { } func sign(with result: Result) { - result.success { social in - debugLog(social) - } + result.success(social) result.failure { error in errorMessage = error.localizedDescription } @@ -96,4 +94,8 @@ public class SignInViewModel: ObservableObject { func trackForgotPasswordClicked() { analytics.forgotPasswordClicked() } + + private func social(result: Socials) { + debugLog(social) + } } From bf8ac9b2c2db16ad48443fe6397ff3eff759c6d8 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 1 Nov 2023 13:39:36 +0100 Subject: [PATCH 05/38] chore: add new struct for credentials --- .../Presentation/Login/SignInViewModel.swift | 36 ++++++++++++++++++- .../SocialSign/SocialSignViewModel.swift | 2 +- .../Providers/AppleSingInProvider.swift | 36 ++++++++++++++----- .../Providers/FacebookSingInProvider.swift | 2 +- 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index f7cb2b291..c6e46e9ff 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -9,6 +9,10 @@ import Foundation import Core import SwiftUI import Alamofire +import AuthenticationServices +import FacebookLogin +import GoogleSignIn +import MSAL public class SignInViewModel: ObservableObject { @@ -96,6 +100,36 @@ public class SignInViewModel: ObservableObject { } private func social(result: Socials) { - debugLog(social) + switch result { + case .apple(let credential): + appleLogin(credential) + case .facebook(let loginManagerLoginResult): + facebookLogin(loginManagerLoginResult) + case .google(let gIDSignInResult): + googleLogin(gIDSignInResult) + case .microsoft(let account, let token): + microsoftLogin(account, token) + } + } + + private func appleLogin(_ credentials: AppleCredentials) { + //credentials.email + //credentials.name + //credentials.token } + + private func facebookLogin(_ managerLoginResult: LoginManagerLoginResult) { + //let currentAccessToken = AccessToken.current?.tokenString + } + + private func googleLogin(_ gIDSignInResult: GIDSignInResult) { +// gIDSignInResult.user.accessToken +// gIDSignInResult.user.profile?.email +// gIDSignInResult.user.profile?.name + } + + private func microsoftLogin(_ account: MSALAccount, _ token: String) { + + } + } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift index 9d8c99ea3..07b137bcf 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -13,7 +13,7 @@ import GoogleSignIn import MSAL enum Socials { - case apple(ASAuthorizationAppleIDCredential) + case apple(AppleCredentials) case facebook(LoginManagerLoginResult) case google(GIDSignInResult) case microsoft(MSALAccount, String) diff --git a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift index a759d1251..e1933cdbb 100644 --- a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift @@ -8,14 +8,21 @@ import Foundation import AuthenticationServices +public struct AppleCredentials: Codable { + public var name: String + public var email: String + public var birthYear: String? = nil + public var token: String +} + public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDelegate { public override init() {} - private var completion: ((Result) -> Void)? + private var completion: ((Result) -> Void)? private let appleIDProvider = ASAuthorizationAppleIDProvider() - public func request(completion: ((Result) -> Void)?) { + public func request(completion: ((Result) -> Void)?) { let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] @@ -30,18 +37,31 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { - guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else { + guard let credentials = authorization.credential as? ASAuthorizationAppleIDCredential else { completion?(.failure(CustomError.error(text: "ASAuthorizationAppleIDCredential is nil"))) return } - let userIdentifier = appleIDCredential.user - let fullName = appleIDCredential.fullName - let email = appleIDCredential.email + let firstName = credentials.fullName?.givenName ?? "" + let lastName = credentials.fullName?.familyName ?? "" + let email = credentials.email ?? "" + var name = "\(firstName) \(lastName)" + + guard let data = credentials.identityToken, + let code = String(data: data, encoding: .utf8) else { + completion?(.failure(CustomError.error(text: "Token is nil"))) + return + } + + debugLog("User id is \(data) \n Full Name is \(name) \n Email id is \(email)") - debugLog("User id is \(userIdentifier) \n Full Name is \(String(describing: fullName)) \n Email id is \(String(describing: email))") + let appleCredentials = AppleCredentials( + name: name, + email: email, + token: code + ) - completion?(.success(appleIDCredential)) + completion?(.success(appleCredentials)) } public func authorizationController( diff --git a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift index 92d8b3f67..0af703ddb 100644 --- a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift @@ -19,7 +19,7 @@ public final class FacebookSingInProvider { completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void) ) { loginManager.logIn( - permissions: ["public_profile"], + permissions: ["email", "public_profile"], from: withPresenting, handler: completion ) From 155455d7d4abe33e206610f5ab3c49e1ce00df5f Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 1 Nov 2023 17:36:59 +0100 Subject: [PATCH 06/38] chore: add request social login --- .../Presentation/Login/SignInViewModel.swift | 51 ++++++++++++++++--- .../Core/Data/Repository/AuthRepository.swift | 33 +++++++++++- Core/Core/Domain/AuthInteractor.swift | 7 +++ Core/Core/Network/AuthEndpoint.swift | 13 ++++- 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index c6e46e9ff..4d1b985d6 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -113,23 +113,58 @@ public class SignInViewModel: ObservableObject { } private func appleLogin(_ credentials: AppleCredentials) { - //credentials.email - //credentials.name - //credentials.token + socialLogin( + externalToken: credentials.token, + backend: "apple-id" + ) } private func facebookLogin(_ managerLoginResult: LoginManagerLoginResult) { - //let currentAccessToken = AccessToken.current?.tokenString + guard let currentAccessToken = AccessToken.current?.tokenString else { + return + } + socialLogin( + externalToken: currentAccessToken, + backend: "facebook" + ) } private func googleLogin(_ gIDSignInResult: GIDSignInResult) { -// gIDSignInResult.user.accessToken -// gIDSignInResult.user.profile?.email -// gIDSignInResult.user.profile?.name + socialLogin( + externalToken: gIDSignInResult.user.accessToken.tokenString, + backend: "google-oauth2" + ) } private func microsoftLogin(_ account: MSALAccount, _ token: String) { - + socialLogin( + externalToken: token, + backend: "azuread-oauth2" + ) + } + + private func socialLogin(externalToken: String, backend: String) { + Task { + isShowProgress = true + do { + let user = try await interactor.login(externalToken: externalToken, backend: backend) + analytics.setUserID("\(user.id)") + analytics.userLogin(method: .password) + router.showMainScreen() + } catch let error { + isShowProgress = false + if let validationError = error.validationError, + let value = validationError.data?["error_description"] as? String { + errorMessage = value + } else if case APIError.invalidGrant = error { + errorMessage = CoreLocalization.Error.invalidCredentials + } else if error.isInternetError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else { + errorMessage = CoreLocalization.Error.unknownError + } + } + } } } diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index 1ed62fd68..76070c8d1 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -9,6 +9,7 @@ import Foundation public protocol AuthRepositoryProtocol { func login(username: String, password: String) async throws -> User + func login(externalToken: String, backend: String) async throws -> User func getCookies(force: Bool) async throws func getRegistrationFields() async throws -> [PickerFields] func registerUser(fields: [String: String]) async throws -> User @@ -52,7 +53,31 @@ public class AuthRepository: AuthRepositoryProtocol { appStorage.user = user return user.domain } - + + public func login(externalToken: String, backend: String) async throws -> User { + let endPoint = AuthEndpoint.socialLogin( + externalToken: externalToken, + backend: backend, + clientId: config.oAuthClientId + ) + let authResponse = try await api.requestData(endPoint).mapResponse(DataLayer.AuthResponse.self) + guard let accessToken = authResponse.accessToken, + let refreshToken = authResponse.refreshToken else { + if let error = authResponse.error, error == DataLayer.AuthResponse.invalidGrant { + throw APIError.invalidGrant + } else { + throw APIError.unknown + } + } + + appStorage.accessToken = accessToken + appStorage.refreshToken = refreshToken + + let user = try await api.requestData(AuthEndpoint.getUserInfo).mapResponse(DataLayer.User.self) + appStorage.user = user + return user.domain + } + public func resetPassword(email: String) async throws -> ResetPassword { let response = try await api.requestData(AuthEndpoint.resetPassword(email: email)) .mapResponse(DataLayer.ResetPassword.self) @@ -103,7 +128,11 @@ class AuthRepositoryMock: AuthRepositoryProtocol { func login(username: String, password: String) async throws -> User { User(id: 1, username: "User", email: "email@gmail.com", name: "User Name", userAvatar: "") } - + + public func login(externalToken: String, backend: String) async throws -> User { + User(id: 1, username: "User", email: "email@gmail.com", name: "User Name", userAvatar: "") + } + func resetPassword(email: String) async throws -> ResetPassword { ResetPassword(success: true, responseText: "Success reset") } diff --git a/Core/Core/Domain/AuthInteractor.swift b/Core/Core/Domain/AuthInteractor.swift index 94e202364..a076e7a50 100644 --- a/Core/Core/Domain/AuthInteractor.swift +++ b/Core/Core/Domain/AuthInteractor.swift @@ -11,6 +11,8 @@ import Foundation public protocol AuthInteractorProtocol { @discardableResult func login(username: String, password: String) async throws -> User + @discardableResult + func login(externalToken: String, backend: String) async throws -> User func resetPassword(email: String) async throws -> ResetPassword func getCookies(force: Bool) async throws func getRegistrationFields() async throws -> [PickerFields] @@ -31,6 +33,11 @@ public class AuthInteractor: AuthInteractorProtocol { return try await repository.login(username: username, password: password) } + @discardableResult + public func login(externalToken: String, backend: String) async throws -> User { + return try await repository.login(externalToken: externalToken, backend: backend) + } + public func resetPassword(email: String) async throws -> ResetPassword { try await repository.resetPassword(email: email) } diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index c477c451c..cedb1e9c7 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -10,6 +10,7 @@ import Alamofire enum AuthEndpoint: EndPointType { case getAccessToken(username: String, password: String, clientId: String) + case socialLogin(externalToken: String, backend: String, clientId: String) case getUserInfo case getAuthCookies case getRegisterFields @@ -21,6 +22,8 @@ enum AuthEndpoint: EndPointType { switch self { case .getAccessToken: return "/oauth2/access_token" + case let .socialLogin(_, backend, _): + return "/oauth2/exchange_access_token/\(backend)/" case .getUserInfo: return "/api/mobile/v0.5/my_user_info" case .getAuthCookies: @@ -38,7 +41,7 @@ enum AuthEndpoint: EndPointType { var httpMethod: HTTPMethod { switch self { - case .getAccessToken: + case .getAccessToken, .socialLogin: return .post case .getUserInfo: return .get @@ -69,6 +72,14 @@ enum AuthEndpoint: EndPointType { "password": password ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) + case let .socialLogin(externalToken, _, clientId): + let params: [String: Encodable] = [ + "client_id": clientId, + "jwt": "token_type", + "access_token": externalToken, + "asymmetric_jwt": "true" + ] + return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) case .getUserInfo: return .request case .getAuthCookies: From e51e380e5f5d5d75118f80f07c7938299119ae57 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Mon, 6 Nov 2023 09:40:07 +0100 Subject: [PATCH 07/38] chore: add request and change design buttons --- .../Presentation/AuthorizationAnalytics.swift | 1 + .../Presentation/Login/SignInViewModel.swift | 34 ++++---- .../Registration/SignUpViewModel.swift | 84 +++++++++++++++++-- .../SocialSign/SocialSignView.swift | 19 +++-- .../Providers/FacebookSingInProvider.swift | 2 +- .../Core/Data/Repository/AuthRepository.swift | 11 ++- Core/Core/Domain/AuthInteractor.swift | 7 +- Core/Core/Extensions/CGColorExtension.swift | 6 ++ Core/Core/Network/AuthEndpoint.swift | 6 +- OpenEdX/AppDelegate.swift | 26 +++++- 10 files changed, 154 insertions(+), 42 deletions(-) diff --git a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift index 6c7a60393..67156cd4f 100644 --- a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift +++ b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift @@ -8,6 +8,7 @@ import Foundation public enum LoginMethod: String { + case apple = "Apple" case password = "Password" case facebook = "Facebook" case google = "Google" diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 4d1b985d6..622414a3d 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -91,14 +91,6 @@ public class SignInViewModel: ObservableObject { } } - func trackSignUpClicked() { - analytics.signUpClicked() - } - - func trackForgotPasswordClicked() { - analytics.forgotPasswordClicked() - } - private func social(result: Socials) { switch result { case .apple(let credential): @@ -115,7 +107,8 @@ public class SignInViewModel: ObservableObject { private func appleLogin(_ credentials: AppleCredentials) { socialLogin( externalToken: credentials.token, - backend: "apple-id" + backend: "apple-id", + loginMethod: .apple ) } @@ -125,31 +118,34 @@ public class SignInViewModel: ObservableObject { } socialLogin( externalToken: currentAccessToken, - backend: "facebook" + backend: "facebook", + loginMethod: .facebook ) } private func googleLogin(_ gIDSignInResult: GIDSignInResult) { socialLogin( externalToken: gIDSignInResult.user.accessToken.tokenString, - backend: "google-oauth2" + backend: "google-oauth2", + loginMethod: .google ) } private func microsoftLogin(_ account: MSALAccount, _ token: String) { socialLogin( externalToken: token, - backend: "azuread-oauth2" + backend: "azuread-oauth2", + loginMethod: .microsoft ) } - private func socialLogin(externalToken: String, backend: String) { - Task { + private func socialLogin(externalToken: String, backend: String, loginMethod: LoginMethod) { + Task { @MainActor in isShowProgress = true do { let user = try await interactor.login(externalToken: externalToken, backend: backend) analytics.setUserID("\(user.id)") - analytics.userLogin(method: .password) + analytics.userLogin(method: loginMethod) router.showMainScreen() } catch let error { isShowProgress = false @@ -167,4 +163,12 @@ public class SignInViewModel: ObservableObject { } } + func trackSignUpClicked() { + analytics.signUpClicked() + } + + func trackForgotPasswordClicked() { + analytics.forgotPasswordClicked() + } + } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 1c4d493f4..136af5302 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -8,6 +8,10 @@ import Foundation import Core import SwiftUI +import AuthenticationServices +import FacebookLogin +import GoogleSignIn +import MSAL public class SignUpViewModel: ObservableObject { @@ -78,7 +82,7 @@ public class SignUpViewModel: ObservableObject { } @MainActor - func registerUser() async { + func registerUser(externalToken: String? = nil, backend: String? = nil) async { do { var validateFields: [String: String] = [:] fields.forEach({ @@ -86,10 +90,21 @@ public class SignUpViewModel: ObservableObject { }) validateFields["honor_code"] = "true" validateFields["terms_of_service"] = "true" + if let externalToken = externalToken, let backend = backend { + validateFields["access_token"] = externalToken + validateFields["provider"] = backend + validateFields["client_id"] = config.oAuthClientId + if validateFields.contains(where: {$0.key == "password"}) { + validateFields.removeValue(forKey: "password") + } + } let errors = try await interactor.validateRegistrationFields(fields: validateFields) guard !showErrors(errors: errors) else { return } isShowProgress = true - let user = try await interactor.registerUser(fields: validateFields) + let user = try await interactor.registerUser( + fields: validateFields, + isSocial: externalToken != nil + ) analytics.setUserID("\(user.id)") analytics.registrationSuccess() isShowProgress = false @@ -107,15 +122,74 @@ public class SignUpViewModel: ObservableObject { } } + @MainActor func register(with result: Result) { - result.success { social in - debugLog(social) - } + result.success(social) result.failure { error in errorMessage = error.localizedDescription } } + @MainActor + private func social(result: Socials) { + switch result { + case .apple(let credential): + appleLogin(credential) + case .facebook(let loginManagerLoginResult): + facebookLogin(loginManagerLoginResult) + case .google(let gIDSignInResult): + googleLogin(gIDSignInResult) + case .microsoft(let account, let token): + microsoftLogin(account, token) + } + } + + @MainActor + private func appleLogin(_ credentials: AppleCredentials) { + registerSocial( + externalToken: credentials.token, + backend: "apple-id" + ) + } + + @MainActor + private func facebookLogin(_ managerLoginResult: LoginManagerLoginResult) { + guard let currentAccessToken = AccessToken.current?.tokenString else { + return + } + registerSocial( + externalToken: currentAccessToken, + backend: "facebook" + ) + } + + @MainActor + private func googleLogin(_ gIDSignInResult: GIDSignInResult) { + registerSocial( + externalToken: gIDSignInResult.user.accessToken.tokenString, + backend: "google-oauth2" + ) + } + + @MainActor + private func microsoftLogin(_ account: MSALAccount, _ token: String) { + registerSocial( + externalToken: token, + backend: "azuread-oauth2" + ) + } + + @MainActor + private func registerSocial(externalToken: String, backend: String) { + Task { + await registerUser( + externalToken: externalToken, + backend: backend + ) + } + } + + func trackCreateAccountClicked() { analytics.createAccountClicked() } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index a2d5c75fd..a8b63ee45 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -58,30 +58,31 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - LabelButton( - image: CoreAssets.iconApple.swiftUIImage, - title: "\(title) \(AuthLocalization.apple)", - backgroundColor: .black, - action: viewModel.signInWithApple - ) LabelButton( image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", - backgroundColor: .blue, + textColor: .black, + backgroundColor: UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.00).sui, action: viewModel.signInWithGoogle ) LabelButton( image: CoreAssets.iconFacebookWhite.swiftUIImage, title: "\(title) \(AuthLocalization.facebook)", - backgroundColor: .blue, + backgroundColor: UIColor(red: 0.09, green: 0.46, blue: 0.95, alpha: 1.00).sui, action: viewModel.signInWithFacebook ) LabelButton( image: CoreAssets.iconMicrosoftWhite.swiftUIImage, title: "\(title) \(AuthLocalization.microsoft)", - backgroundColor: .black, + backgroundColor: UIColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00).sui, action: viewModel.signInWithMicrosoft ) + LabelButton( + image: CoreAssets.iconApple.swiftUIImage, + title: "\(title) \(AuthLocalization.apple)", + backgroundColor: .black, + action: viewModel.signInWithApple + ) } } } diff --git a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift index 0af703ddb..a897830ec 100644 --- a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift @@ -19,7 +19,7 @@ public final class FacebookSingInProvider { completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void) ) { loginManager.logIn( - permissions: ["email", "public_profile"], + permissions: [], from: withPresenting, handler: completion ) diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index 76070c8d1..2b2da1706 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -12,7 +12,7 @@ public protocol AuthRepositoryProtocol { func login(externalToken: String, backend: String) async throws -> User func getCookies(force: Bool) async throws func getRegistrationFields() async throws -> [PickerFields] - func registerUser(fields: [String: String]) async throws -> User + func registerUser(fields: [String: String], isSocial: Bool) async throws -> User func validateRegistrationFields(fields: [String: String]) async throws -> [String: String] func resetPassword(email: String) async throws -> ResetPassword } @@ -105,11 +105,14 @@ public class AuthRepository: AuthRepositoryProtocol { } @discardableResult - public func registerUser(fields: [String: String]) async throws -> User { + public func registerUser(fields: [String: String], isSocial: Bool) async throws -> User { try await api.requestData(AuthEndpoint.registerUser(fields)) + if isSocial { + return try await login(externalToken: fields["access_token"] ?? "", backend: fields["provider"] ?? "") + } return try await login(username: fields["username"] ?? "", password: fields["password"] ?? "") } - + public func validateRegistrationFields(fields: [String: String]) async throws -> [String: String] { let result = try await api.requestData(AuthEndpoint.validateRegistrationFields(fields)) if let fieldsResult = try JSONSerialization.jsonObject(with: result, options: []) as? [String: Any] { @@ -172,7 +175,7 @@ class AuthRepositoryMock: AuthRepositoryProtocol { return fields } - func registerUser(fields: [String: String]) async throws -> User { + func registerUser(fields: [String: String], isSocial: Bool) async throws -> User { User(id: 1, username: "User", email: "email@gmail.com", name: "User Name", userAvatar: "") } diff --git a/Core/Core/Domain/AuthInteractor.swift b/Core/Core/Domain/AuthInteractor.swift index a076e7a50..45868cbc9 100644 --- a/Core/Core/Domain/AuthInteractor.swift +++ b/Core/Core/Domain/AuthInteractor.swift @@ -16,12 +16,11 @@ public protocol AuthInteractorProtocol { func resetPassword(email: String) async throws -> ResetPassword func getCookies(force: Bool) async throws func getRegistrationFields() async throws -> [PickerFields] - func registerUser(fields: [String: String]) async throws -> User + func registerUser(fields: [String: String], isSocial: Bool) async throws -> User func validateRegistrationFields(fields: [String: String]) async throws -> [String: String] } public class AuthInteractor: AuthInteractorProtocol { - private let repository: AuthRepositoryProtocol public init(repository: AuthRepositoryProtocol) { @@ -50,8 +49,8 @@ public class AuthInteractor: AuthInteractorProtocol { return try await repository.getRegistrationFields() } - public func registerUser(fields: [String: String]) async throws -> User { - return try await repository.registerUser(fields: fields) + public func registerUser(fields: [String: String], isSocial: Bool) async throws -> User { + return try await repository.registerUser(fields: fields, isSocial: isSocial) } public func validateRegistrationFields(fields: [String: String]) async throws -> [String: String] { diff --git a/Core/Core/Extensions/CGColorExtension.swift b/Core/Core/Extensions/CGColorExtension.swift index 2454e026d..ce5242d9d 100644 --- a/Core/Core/Extensions/CGColorExtension.swift +++ b/Core/Core/Extensions/CGColorExtension.swift @@ -42,3 +42,9 @@ public extension Color { return UIColor(red: r, green: g, blue: b, alpha: a) } } + +public extension UIColor { + var sui: Color { + Color(self) + } +} diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index cedb1e9c7..d51479b4b 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -41,7 +41,9 @@ enum AuthEndpoint: EndPointType { var httpMethod: HTTPMethod { switch self { - case .getAccessToken, .socialLogin: + case .getAccessToken: + return .post + case .socialLogin: return .post case .getUserInfo: return .get @@ -75,7 +77,7 @@ enum AuthEndpoint: EndPointType { case let .socialLogin(externalToken, _, clientId): let params: [String: Encodable] = [ "client_id": clientId, - "jwt": "token_type", + "token_type": "jwt", "access_token": externalToken, "asymmetric_jwt": "true" ] diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index bb7c92a3b..292ed01be 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -12,6 +12,8 @@ import FirebaseCore import FirebaseAnalytics import FirebaseCrashlytics import Profile +import GoogleSignIn +import FacebookCore @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -30,12 +32,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - + ApplicationDelegate.shared.application( + application, + didFinishLaunchingWithOptions: launchOptions + ) + if BuildConfiguration.shared.firebaseOptions.apiKey != "" { FirebaseApp.configure(options: BuildConfiguration.shared.firebaseOptions) Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) } - + initDI() Theme.Fonts.registerFonts() @@ -53,6 +59,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + func application( + _ app: UIApplication, + open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + ApplicationDelegate.shared.application( + app, + open: url, + sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, + annotation: options[UIApplication.OpenURLOptionsKey.annotation] + ) + if GIDSignIn.sharedInstance.handle(url) { + return true + } + return false + } + private func initDI() { let navigation = UINavigationController() navigation.modalPresentationStyle = .fullScreen From 3be7334744666054fa4c6448ba363b59503d3e3d Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 7 Nov 2023 11:47:41 +0100 Subject: [PATCH 08/38] chore: add handle MSAL Response --- .../Presentation/SocialSign/SocialSignView.swift | 1 + OpenEdX/AppDelegate.swift | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index a8b63ee45..a08d18135 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -20,6 +20,7 @@ struct SocialSignView: View { ) { let viewModel: SocialSignViewModel = .init(onSigned: onSigned) self._viewModel = .init(wrappedValue: viewModel) + self.signType = signType } enum SignType { diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 292ed01be..992220340 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -14,6 +14,7 @@ import FirebaseCrashlytics import Profile import GoogleSignIn import FacebookCore +import MSAL @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -69,9 +70,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, annotation: options[UIApplication.OpenURLOptionsKey.annotation] ) + if GIDSignIn.sharedInstance.handle(url) { return true } + + if MSALPublicClientApplication.handleMSALResponse( + url, + sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String + ) { + return true + } + return false } From 42a2ffa6ab8c97acb9c7f1896b982d27cba0ff79 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 21 Nov 2023 17:18:26 +0100 Subject: [PATCH 09/38] chore: add ocial login enabled feature flag --- .../Presentation/Login/SignInView.swift | 4 +++- .../Presentation/Login/SignInViewModel.swift | 8 ++++++-- .../Presentation/Registration/SignUpView.swift | 12 +++++++----- .../Presentation/Registration/SignUpViewModel.swift | 6 +++++- Core/Core/Configuration/Config/FeaturesConfig.swift | 5 ++++- Core/Core/Network/AuthEndpoint.swift | 2 +- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 2785b9ef7..24474747f 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -112,7 +112,9 @@ public struct SignInView: View { .padding(.top, 40) } } - SocialSignView(onSigned: viewModel.sign) + if viewModel.socialLoginEnabled { + SocialSignView(onSigned: viewModel.sign) + } Spacer() } .padding(.horizontal, 24) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 975df564c..0d76733a1 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -39,7 +39,7 @@ public class SignInViewModel: ObservableObject { private let interactor: AuthInteractorProtocol private let analytics: AuthorizationAnalytics private let validator: Validator - + public init( interactor: AuthInteractorProtocol, router: AuthorizationRouter, @@ -53,7 +53,11 @@ public class SignInViewModel: ObservableObject { self.analytics = analytics self.validator = validator } - + + var socialLoginEnabled: Bool { + config.features.socialLoginEnabled + } + @MainActor func login(username: String, password: String) async { guard validator.isValidEmail(username) else { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index 8edbb7ede..4e8b600a5 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -105,11 +105,13 @@ public struct SignUpView: View { .padding(.top, 40) .frame(maxWidth: .infinity) } - SocialSignView( - signType: .register, - onSigned: viewModel.register - ) - .padding(.bottom, 30) + if viewModel.socialLoginEnabled { + SocialSignView( + signType: .register, + onSigned: viewModel.register + ) + .padding(.bottom, 30) + } Spacer() } .padding(.horizontal, 24) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index fec998e3e..e4d8f6989 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -51,7 +51,11 @@ public class SignUpViewModel: ObservableObject { self.cssInjector = cssInjector self.validator = validator } - + + var socialLoginEnabled: Bool { + config.features.socialLoginEnabled + } + private func showErrors(errors: [String: String]) -> Bool { var containsError = false errors.forEach { key, value in diff --git a/Core/Core/Configuration/Config/FeaturesConfig.swift b/Core/Core/Configuration/Config/FeaturesConfig.swift index eb6c6227f..c105652a0 100644 --- a/Core/Core/Configuration/Config/FeaturesConfig.swift +++ b/Core/Core/Configuration/Config/FeaturesConfig.swift @@ -9,13 +9,16 @@ import Foundation private enum FeaturesKeys: String { case whatNewEnabled = "WHATS_NEW_ENABLED" + case socialLoginEnabled = "SOCIAL_LOGIN_ENABLED" } public class FeaturesConfig: NSObject { public var whatNewEnabled: Bool - + public var socialLoginEnabled: Bool + init(dictionary: [String: Any]) { whatNewEnabled = dictionary[FeaturesKeys.whatNewEnabled.rawValue] as? Bool ?? false + socialLoginEnabled = dictionary[FeaturesKeys.socialLoginEnabled.rawValue] as? Bool ?? false super.init() } } diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index c65f954dd..a52524ec1 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -82,7 +82,7 @@ enum AuthEndpoint: EndPointType { "client_id": clientId, "token_type": "jwt", "access_token": externalToken, - "asymmetric_jwt": "true" + "asymmetric_jwt": true ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) case .getUserInfo: From b5189f9683ce6ccb8c20783efe44a6715148315c Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 22 Nov 2023 15:09:19 +0100 Subject: [PATCH 10/38] chore: add configs and flags --- .../Presentation/Login/SignInViewModel.swift | 2 +- .../Registration/SignUpViewModel.swift | 7 ++- .../SocialSign/SocialSignView.swift | 58 +++++++++++-------- .../SocialSign/SocialSignViewModel.swift | 3 + Core/Core.xcodeproj/project.pbxproj | 8 +++ .../Providers/MicrosoftSingInProvider.swift | 7 ++- Core/Core/Configuration/Config/Config.swift | 11 ++++ .../Configuration/Config/FacebookConfig.swift | 38 ++++++++++++ .../Configuration/Config/FeaturesConfig.swift | 3 + .../Configuration/Config/FirebaseConfig.swift | 6 +- .../Configuration/Config/MicrosoftKeys.swift | 35 +++++++++++ 11 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 Core/Core/Configuration/Config/FacebookConfig.swift create mode 100644 Core/Core/Configuration/Config/MicrosoftKeys.swift diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 0d76733a1..ee93e0414 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -55,7 +55,7 @@ public class SignInViewModel: ObservableObject { } var socialLoginEnabled: Bool { - config.features.socialLoginEnabled + config.socialLoginEnabled } @MainActor diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index e4d8f6989..86c3d24dc 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -53,7 +53,11 @@ public class SignUpViewModel: ObservableObject { } var socialLoginEnabled: Bool { - config.features.socialLoginEnabled + config.features.socialLoginEnabled && + config.features.isAppleSigninEnabled && + config.facebook.enabled && + config.microsoft.enabled && + config.firebase.enabled } private func showErrors(errors: [String: String]) -> Bool { @@ -195,7 +199,6 @@ public class SignUpViewModel: ObservableObject { } } - func trackCreateAccountClicked() { analytics.createAccountClicked() } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index a08d18135..6617b0881 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -59,31 +59,39 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - LabelButton( - image: CoreAssets.iconGoogleWhite.swiftUIImage, - title: "\(title) \(AuthLocalization.google)", - textColor: .black, - backgroundColor: UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.00).sui, - action: viewModel.signInWithGoogle - ) - LabelButton( - image: CoreAssets.iconFacebookWhite.swiftUIImage, - title: "\(title) \(AuthLocalization.facebook)", - backgroundColor: UIColor(red: 0.09, green: 0.46, blue: 0.95, alpha: 1.00).sui, - action: viewModel.signInWithFacebook - ) - LabelButton( - image: CoreAssets.iconMicrosoftWhite.swiftUIImage, - title: "\(title) \(AuthLocalization.microsoft)", - backgroundColor: UIColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00).sui, - action: viewModel.signInWithMicrosoft - ) - LabelButton( - image: CoreAssets.iconApple.swiftUIImage, - title: "\(title) \(AuthLocalization.apple)", - backgroundColor: .black, - action: viewModel.signInWithApple - ) + if viewModel.config.firebase.enabled { + LabelButton( + image: CoreAssets.iconGoogleWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.google)", + textColor: .black, + backgroundColor: UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.00).sui, + action: viewModel.signInWithGoogle + ) + } + if viewModel.config.facebook.enabled { + LabelButton( + image: CoreAssets.iconFacebookWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.facebook)", + backgroundColor: UIColor(red: 0.09, green: 0.46, blue: 0.95, alpha: 1.00).sui, + action: viewModel.signInWithFacebook + ) + } + if viewModel.config.microsoft.enabled { + LabelButton( + image: CoreAssets.iconMicrosoftWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.microsoft)", + backgroundColor: UIColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00).sui, + action: viewModel.signInWithMicrosoft + ) + if viewModel.config.features.isAppleSigninEnabled { + LabelButton( + image: CoreAssets.iconApple.swiftUIImage, + title: "\(title) \(AuthLocalization.apple)", + backgroundColor: .black, + action: viewModel.signInWithApple + ) + } + } } } } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift index 07b137bcf..815f20cd0 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -11,6 +11,7 @@ import AuthenticationServices import FacebookLogin import GoogleSignIn import MSAL +import Swinject enum Socials { case apple(AppleCredentials) @@ -38,6 +39,8 @@ final public class SocialSignViewModel: ObservableObject { UIApplication.topViewController() } + private(set) lazy var config = Container.shared.resolve(ConfigProtocol.self)! + // MARK: - Public Intens - func signInWithApple() { diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 9b831cf42..17f340655 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -131,6 +131,8 @@ BADB3F532AD6B3A5004D5CFA /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BADB3F522AD6B3A5004D5CFA /* MSAL */; }; BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; + BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; + BAFB99842B0E282E007D09F9 /* MicrosoftKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftKeys.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */; }; @@ -280,6 +282,8 @@ BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookSingInProvider.swift; sourceTree = ""; }; BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftSingInProvider.swift; sourceTree = ""; }; BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; + BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; + BAFB99832B0E282E007D09F9 /* MicrosoftKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftKeys.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfig.swift; sourceTree = ""; }; @@ -674,6 +678,8 @@ DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, + BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, + BAFB99832B0E282E007D09F9 /* MicrosoftKeys.swift */, ); path = Config; sourceTree = ""; @@ -890,6 +896,7 @@ 0770DE2A28D0929E006D8A5D /* HTTPTask.swift in Sources */, 02284C182A3B1AE00007117F /* UIApplicationExtension.swift in Sources */, 0255D5582936283A004DBC1A /* UploadBodyEncoding.swift in Sources */, + BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */, 027BD3B32909475900392132 /* Publishers+KeyboardState.swift in Sources */, 0727877D28D25212002E9142 /* ProgressBar.swift in Sources */, 0236961F28F9A2F600EEF206 /* AuthEndpoint.swift in Sources */, @@ -953,6 +960,7 @@ 0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */, 02AFCC182AEFDB24000360F0 /* ThirdPartyMailClient.swift in Sources */, 0233D5712AF13EC800BAC8BD /* SelectMailClientView.swift in Sources */, + BAFB99842B0E282E007D09F9 /* MicrosoftKeys.swift in Sources */, 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, 027DB33528D8C8FE002B6862 /* Data_MyCourse.swift in Sources */, 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */, diff --git a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift index f80aca25d..ac079c890 100644 --- a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift @@ -7,6 +7,7 @@ import Foundation import MSAL +import Swinject public typealias MSLoginCompletionHandler = (MSALAccount?, String?, Error?) -> Void @@ -56,9 +57,11 @@ public final class MicrosoftSingInProvider { } } - private func createClientApplication() throws -> MSALPublicClientApplication { - let configuration = MSALPublicClientApplicationConfig(clientId: "") + guard let config = Container.shared.resolve(ConfigProtocol.self), let appID = config.microsoft.appID else { + throw CustomError.error(text: "Configuration error") + } + let configuration = MSALPublicClientApplicationConfig(clientId: appID) do { return try MSALPublicClientApplication(configuration: configuration) diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 49f1ffdc5..548b3a2d8 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -13,8 +13,11 @@ public protocol ConfigProtocol { var tokenType: TokenType { get } var feedbackEmail: String { get } var appStoreLink: String { get } + var socialLoginEnabled: Bool { get } var agreement: AgreementConfig { get } var firebase: FirebaseConfig { get } + var facebook: FacebookConfig { get } + var microsoft: MicrosoftConfig { get } var features: FeaturesConfig { get } } @@ -127,6 +130,14 @@ extension Config: ConfigProtocol { public var appStoreLink: String { "itms-apps://itunes.apple.com/app/id\(appStoreId)?mt=8" } + + public var socialLoginEnabled: Bool { + features.socialLoginEnabled && + (features.isAppleSigninEnabled || + facebook.enabled || + microsoft.enabled || + firebase.enabled) + } } // Mark - For testing and SwiftUI preview diff --git a/Core/Core/Configuration/Config/FacebookConfig.swift b/Core/Core/Configuration/Config/FacebookConfig.swift new file mode 100644 index 000000000..6e6b5130a --- /dev/null +++ b/Core/Core/Configuration/Config/FacebookConfig.swift @@ -0,0 +1,38 @@ +// +// FacebookConfig.swift +// Core +// +// Created by Eugene Yatsenko on 22.11.2023. +// + +import Foundation + +private enum FacebooKeys: String { + case enabled = "ENABLED" + case appID = "FACEBOOK_APP_ID" + case clientToken = "CLIENT_TOKEN" +} + +public final class FacebookConfig: NSObject { + public var enabled: Bool = false + public var appID: String? + public var clientToken: String? + + public var requiredKeysAvailable: Bool { + return appID != nil && clientToken != nil + } + + init(dictionary: [String: AnyObject]) { + appID = dictionary[FacebooKeys.appID.rawValue] as? String + clientToken = dictionary[FacebooKeys.clientToken.rawValue] as? String + super.init() + enabled = requiredKeysAvailable && dictionary[FacebooKeys.enabled.rawValue] as? Bool == true + } +} + +private let facebookKey = "FACEBOOK" +extension Config { + public var facebook: FacebookConfig { + FacebookConfig(dictionary: self[facebookKey] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Config/FeaturesConfig.swift b/Core/Core/Configuration/Config/FeaturesConfig.swift index c105652a0..626bbd502 100644 --- a/Core/Core/Configuration/Config/FeaturesConfig.swift +++ b/Core/Core/Configuration/Config/FeaturesConfig.swift @@ -10,15 +10,18 @@ import Foundation private enum FeaturesKeys: String { case whatNewEnabled = "WHATS_NEW_ENABLED" case socialLoginEnabled = "SOCIAL_LOGIN_ENABLED" + case isAppleSigninEnabled = "APPLE_SIGNIN_ENABLED" } public class FeaturesConfig: NSObject { public var whatNewEnabled: Bool public var socialLoginEnabled: Bool + public var isAppleSigninEnabled: Bool init(dictionary: [String: Any]) { whatNewEnabled = dictionary[FeaturesKeys.whatNewEnabled.rawValue] as? Bool ?? false socialLoginEnabled = dictionary[FeaturesKeys.socialLoginEnabled.rawValue] as? Bool ?? false + isAppleSigninEnabled = dictionary[FeaturesKeys.isAppleSigninEnabled.rawValue] as? Bool ?? false super.init() } } diff --git a/Core/Core/Configuration/Config/FirebaseConfig.swift b/Core/Core/Configuration/Config/FirebaseConfig.swift index d981ff3ce..5bbc30dd1 100644 --- a/Core/Core/Configuration/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Config/FirebaseConfig.swift @@ -29,7 +29,7 @@ enum AnalyticsSource: String { case none } -public class FirebaseConfig: NSObject { +public final class FirebaseConfig: NSObject { public var enabled: Bool = false public var cloudMessagingEnabled: Bool = false public let apiKey: String? @@ -97,9 +97,9 @@ public class FirebaseConfig: NSObject { } } -private let key = "FIREBASE" +private let firebaseKey = "FIREBASE" extension Config { public var firebase: FirebaseConfig { - return FirebaseConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) + FirebaseConfig(dictionary: self[firebaseKey] as? [String: AnyObject] ?? [:]) } } diff --git a/Core/Core/Configuration/Config/MicrosoftKeys.swift b/Core/Core/Configuration/Config/MicrosoftKeys.swift new file mode 100644 index 000000000..f295d18d7 --- /dev/null +++ b/Core/Core/Configuration/Config/MicrosoftKeys.swift @@ -0,0 +1,35 @@ +// +// MicrosoftKeys.swift +// Core +// +// Created by Eugene Yatsenko on 22.11.2023. +// + +import Foundation + +private enum MicrosoftKeys: String { + case enabled = "ENABLED" + case appID = "APP_ID" +} + +public final class MicrosoftConfig: NSObject { + public var enabled: Bool = false + public var appID: String? + + public var requiredKeysAvailable: Bool { + return appID != nil + } + + init(dictionary: [String: AnyObject]) { + appID = dictionary[MicrosoftKeys.appID.rawValue] as? String + super.init() + enabled = requiredKeysAvailable && dictionary[MicrosoftKeys.enabled.rawValue] as? Bool == true + } +} + +private let microsoftKey = "MICROSOFT" +extension Config { + public var microsoft: MicrosoftConfig { + MicrosoftConfig(dictionary: self[microsoftKey] as? [String: AnyObject] ?? [:]) + } +} From 1f0770942371f97272f2415bade1c90305042057 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 23 Nov 2023 09:55:55 +0100 Subject: [PATCH 11/38] chore: rename file --- Core/Core.xcodeproj/project.pbxproj | 8 ++++---- .../Config/{MicrosoftKeys.swift => MicrosoftConfig.swift} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename Core/Core/Configuration/Config/{MicrosoftKeys.swift => MicrosoftConfig.swift} (96%) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 17f340655..bdab48f85 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -132,7 +132,7 @@ BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; - BAFB99842B0E282E007D09F9 /* MicrosoftKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftKeys.swift */; }; + BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */; }; @@ -283,7 +283,7 @@ BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftSingInProvider.swift; sourceTree = ""; }; BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; - BAFB99832B0E282E007D09F9 /* MicrosoftKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftKeys.swift; sourceTree = ""; }; + BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfig.swift; sourceTree = ""; }; @@ -679,7 +679,7 @@ DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, - BAFB99832B0E282E007D09F9 /* MicrosoftKeys.swift */, + BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, ); path = Config; sourceTree = ""; @@ -960,7 +960,7 @@ 0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */, 02AFCC182AEFDB24000360F0 /* ThirdPartyMailClient.swift in Sources */, 0233D5712AF13EC800BAC8BD /* SelectMailClientView.swift in Sources */, - BAFB99842B0E282E007D09F9 /* MicrosoftKeys.swift in Sources */, + BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */, 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, 027DB33528D8C8FE002B6862 /* Data_MyCourse.swift in Sources */, 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */, diff --git a/Core/Core/Configuration/Config/MicrosoftKeys.swift b/Core/Core/Configuration/Config/MicrosoftConfig.swift similarity index 96% rename from Core/Core/Configuration/Config/MicrosoftKeys.swift rename to Core/Core/Configuration/Config/MicrosoftConfig.swift index f295d18d7..f36cf0e6c 100644 --- a/Core/Core/Configuration/Config/MicrosoftKeys.swift +++ b/Core/Core/Configuration/Config/MicrosoftConfig.swift @@ -1,5 +1,5 @@ // -// MicrosoftKeys.swift +// MicrosoftConfig.swift // Core // // Created by Eugene Yatsenko on 22.11.2023. From 38d547e781ed84a227f680bff33f5997b48fd5b7 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 23 Nov 2023 13:44:00 +0100 Subject: [PATCH 12/38] chore: add accessibility, localizable strings uk, and flags for socials --- .../Registration/SignUpViewModel.swift | 6 +--- .../SocialSign/SocialSignView.swift | 30 ++++++++++++------- .../uk.lproj/Localizable.strings | 8 +++++ Core/Core/Configuration/Config/Config.swift | 6 ++-- .../Configuration/Config/FacebookConfig.swift | 11 ++++--- .../Configuration/Config/FirebaseConfig.swift | 5 +++- .../Config/MicrosoftConfig.swift | 3 ++ 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 86c3d24dc..bb9c8fc8e 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -53,11 +53,7 @@ public class SignUpViewModel: ObservableObject { } var socialLoginEnabled: Bool { - config.features.socialLoginEnabled && - config.features.isAppleSigninEnabled && - config.facebook.enabled && - config.microsoft.enabled && - config.firebase.enabled + config.socialLoginEnabled } private func showErrors(errors: [String: String]) -> Bool { diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index 6617b0881..7dcce25ec 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -59,7 +59,7 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - if viewModel.config.firebase.enabled { + if viewModel.config.firebase.googleSignInEnabled { LabelButton( image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", @@ -67,30 +67,38 @@ struct SocialSignView: View { backgroundColor: UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.00).sui, action: viewModel.signInWithGoogle ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.config.facebook.enabled { + if viewModel.config.facebook.facebooSignInEnable { LabelButton( image: CoreAssets.iconFacebookWhite.swiftUIImage, title: "\(title) \(AuthLocalization.facebook)", backgroundColor: UIColor(red: 0.09, green: 0.46, blue: 0.95, alpha: 1.00).sui, action: viewModel.signInWithFacebook ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.config.microsoft.enabled { + if viewModel.config.microsoft.microsoftSignInEnable { LabelButton( image: CoreAssets.iconMicrosoftWhite.swiftUIImage, title: "\(title) \(AuthLocalization.microsoft)", backgroundColor: UIColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00).sui, action: viewModel.signInWithMicrosoft ) - if viewModel.config.features.isAppleSigninEnabled { - LabelButton( - image: CoreAssets.iconApple.swiftUIImage, - title: "\(title) \(AuthLocalization.apple)", - backgroundColor: .black, - action: viewModel.signInWithApple - ) - } + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.microsoft)") + } + if viewModel.config.features.isAppleSigninEnabled { + LabelButton( + image: CoreAssets.iconApple.swiftUIImage, + title: "\(title) \(AuthLocalization.apple)", + backgroundColor: .black, + action: viewModel.signInWithApple + ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.apple)") } } } diff --git a/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings index cc06c89a0..c1887f736 100644 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ b/Authorization/Authorization/uk.lproj/Localizable.strings @@ -28,3 +28,11 @@ "FORGOT.REQUEST" = "Відновити пароль"; "FORGOT.CHECK_TITLE" = "Перевірте свою електронну пошту"; "FORGOT.CHECK_Description" = "Ми надіслали інструкції щодо відновлення пароля на вашу електронну пошту "; + +"SIGN_IN_WITH" = "Sign in with"; +"SIGN_IN_REGISTER" = "Register with"; +"APPLE" = "Apple"; +"GOOGLE" = "Google"; +"FACEBOOK" = "Facebook"; +"MICROSOFT" = "Microsoft"; +"OR" = "Or"; diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 548b3a2d8..5488c26a6 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -134,9 +134,9 @@ extension Config: ConfigProtocol { public var socialLoginEnabled: Bool { features.socialLoginEnabled && (features.isAppleSigninEnabled || - facebook.enabled || - microsoft.enabled || - firebase.enabled) + facebook.facebooSignInEnable || + microsoft.microsoftSignInEnable || + firebase.googleSignInEnabled) } } diff --git a/Core/Core/Configuration/Config/FacebookConfig.swift b/Core/Core/Configuration/Config/FacebookConfig.swift index 6e6b5130a..05b75a528 100644 --- a/Core/Core/Configuration/Config/FacebookConfig.swift +++ b/Core/Core/Configuration/Config/FacebookConfig.swift @@ -7,26 +7,29 @@ import Foundation -private enum FacebooKeys: String { +private enum FacebookKeys: String { case enabled = "ENABLED" case appID = "FACEBOOK_APP_ID" case clientToken = "CLIENT_TOKEN" + case facebooSignInEnable = "FACEBOOK_SIGNIN_ENABLED" } public final class FacebookConfig: NSObject { public var enabled: Bool = false public var appID: String? public var clientToken: String? + public var facebooSignInEnable: Bool = false public var requiredKeysAvailable: Bool { return appID != nil && clientToken != nil } init(dictionary: [String: AnyObject]) { - appID = dictionary[FacebooKeys.appID.rawValue] as? String - clientToken = dictionary[FacebooKeys.clientToken.rawValue] as? String + appID = dictionary[FacebookKeys.appID.rawValue] as? String + clientToken = dictionary[FacebookKeys.clientToken.rawValue] as? String super.init() - enabled = requiredKeysAvailable && dictionary[FacebooKeys.enabled.rawValue] as? Bool == true + enabled = requiredKeysAvailable && dictionary[FacebookKeys.enabled.rawValue] as? Bool == true + facebooSignInEnable = enabled && dictionary[FacebookKeys.facebooSignInEnable.rawValue] as? Bool == true } } diff --git a/Core/Core/Configuration/Config/FirebaseConfig.swift b/Core/Core/Configuration/Config/FirebaseConfig.swift index 5bbc30dd1..fe783205a 100644 --- a/Core/Core/Configuration/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Config/FirebaseConfig.swift @@ -21,6 +21,7 @@ private enum FirebaseKeys: String { case projectID = "PROJECT_ID" case reversedClientID = "REVERSED_CLIENT_ID" case storageBucket = "STORAGE_BUCKET" + case googleSignInEnabled = "GOOGLE_SIGNIN_ENABLED" } enum AnalyticsSource: String { @@ -41,7 +42,8 @@ public final class FirebaseConfig: NSObject { public let projectID: String? public let reversedClientID: String? public let storageBucket: String? - + public var googleSignInEnabled: Bool = false + private let analyticsSource: AnalyticsSource public var requiredKeysAvailable: Bool { @@ -65,6 +67,7 @@ public final class FirebaseConfig: NSObject { super.init() enabled = requiredKeysAvailable && dictionary[FirebaseKeys.enabled.rawValue] as? Bool == true + googleSignInEnabled = enabled && dictionary[FirebaseKeys.googleSignInEnabled.rawValue] as? Bool == true let cloudMessagingEnabled = dictionary[FirebaseKeys.cloudMessagingEnabled.rawValue] as? Bool ?? false self.cloudMessagingEnabled = enabled && cloudMessagingEnabled } diff --git a/Core/Core/Configuration/Config/MicrosoftConfig.swift b/Core/Core/Configuration/Config/MicrosoftConfig.swift index f36cf0e6c..83ee2864a 100644 --- a/Core/Core/Configuration/Config/MicrosoftConfig.swift +++ b/Core/Core/Configuration/Config/MicrosoftConfig.swift @@ -10,11 +10,13 @@ import Foundation private enum MicrosoftKeys: String { case enabled = "ENABLED" case appID = "APP_ID" + case microsoftSignInEnable = "MICROSOFT_SIGNIN_ENABLED" } public final class MicrosoftConfig: NSObject { public var enabled: Bool = false public var appID: String? + public var microsoftSignInEnable: Bool = false public var requiredKeysAvailable: Bool { return appID != nil @@ -24,6 +26,7 @@ public final class MicrosoftConfig: NSObject { appID = dictionary[MicrosoftKeys.appID.rawValue] as? String super.init() enabled = requiredKeysAvailable && dictionary[MicrosoftKeys.enabled.rawValue] as? Bool == true + microsoftSignInEnable = enabled && dictionary[MicrosoftKeys.microsoftSignInEnable.rawValue] as? Bool == true } } From 4d675a95bd87317df74666f4fdc732d3b49cf604 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 23 Nov 2023 17:28:27 +0100 Subject: [PATCH 13/38] chore: remove extra code --- .../Presentation/SocialSign/SocialSignView.swift | 4 ++-- Core/Core/Configuration/Config/Config.swift | 4 ++-- Core/Core/Configuration/Config/FacebookConfig.swift | 3 --- Core/Core/Configuration/Config/MicrosoftConfig.swift | 3 --- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index 7dcce25ec..fd8bbd2bf 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -70,7 +70,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.config.facebook.facebooSignInEnable { + if viewModel.config.facebook.enabled { LabelButton( image: CoreAssets.iconFacebookWhite.swiftUIImage, title: "\(title) \(AuthLocalization.facebook)", @@ -80,7 +80,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.config.microsoft.microsoftSignInEnable { + if viewModel.config.microsoft.enabled { LabelButton( image: CoreAssets.iconMicrosoftWhite.swiftUIImage, title: "\(title) \(AuthLocalization.microsoft)", diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 5488c26a6..fbd3636c3 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -134,8 +134,8 @@ extension Config: ConfigProtocol { public var socialLoginEnabled: Bool { features.socialLoginEnabled && (features.isAppleSigninEnabled || - facebook.facebooSignInEnable || - microsoft.microsoftSignInEnable || + facebook.enabled || + microsoft.enabled || firebase.googleSignInEnabled) } } diff --git a/Core/Core/Configuration/Config/FacebookConfig.swift b/Core/Core/Configuration/Config/FacebookConfig.swift index 05b75a528..7c3e45b34 100644 --- a/Core/Core/Configuration/Config/FacebookConfig.swift +++ b/Core/Core/Configuration/Config/FacebookConfig.swift @@ -11,14 +11,12 @@ private enum FacebookKeys: String { case enabled = "ENABLED" case appID = "FACEBOOK_APP_ID" case clientToken = "CLIENT_TOKEN" - case facebooSignInEnable = "FACEBOOK_SIGNIN_ENABLED" } public final class FacebookConfig: NSObject { public var enabled: Bool = false public var appID: String? public var clientToken: String? - public var facebooSignInEnable: Bool = false public var requiredKeysAvailable: Bool { return appID != nil && clientToken != nil @@ -29,7 +27,6 @@ public final class FacebookConfig: NSObject { clientToken = dictionary[FacebookKeys.clientToken.rawValue] as? String super.init() enabled = requiredKeysAvailable && dictionary[FacebookKeys.enabled.rawValue] as? Bool == true - facebooSignInEnable = enabled && dictionary[FacebookKeys.facebooSignInEnable.rawValue] as? Bool == true } } diff --git a/Core/Core/Configuration/Config/MicrosoftConfig.swift b/Core/Core/Configuration/Config/MicrosoftConfig.swift index 83ee2864a..f36cf0e6c 100644 --- a/Core/Core/Configuration/Config/MicrosoftConfig.swift +++ b/Core/Core/Configuration/Config/MicrosoftConfig.swift @@ -10,13 +10,11 @@ import Foundation private enum MicrosoftKeys: String { case enabled = "ENABLED" case appID = "APP_ID" - case microsoftSignInEnable = "MICROSOFT_SIGNIN_ENABLED" } public final class MicrosoftConfig: NSObject { public var enabled: Bool = false public var appID: String? - public var microsoftSignInEnable: Bool = false public var requiredKeysAvailable: Bool { return appID != nil @@ -26,7 +24,6 @@ public final class MicrosoftConfig: NSObject { appID = dictionary[MicrosoftKeys.appID.rawValue] as? String super.init() enabled = requiredKeysAvailable && dictionary[MicrosoftKeys.enabled.rawValue] as? Bool == true - microsoftSignInEnable = enabled && dictionary[MicrosoftKeys.microsoftSignInEnable.rawValue] as? Bool == true } } From 6e8f76745df064b1c662901e2c96fb604df842a0 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Mon, 27 Nov 2023 12:24:36 +0100 Subject: [PATCH 14/38] chore: add google config --- Core/Core.xcodeproj/project.pbxproj | 20 ++-------- Core/Core/Configuration/Config/Config.swift | 1 + .../Configuration/Config/GoogleConfig.swift | 37 +++++++++++++++++++ 3 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 Core/Core/Configuration/Config/GoogleConfig.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 57419d8b2..305bb2865 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -133,6 +133,7 @@ BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */; }; BAFB998E2B0F70F1007D09F9 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998D2B0F70F1007D09F9 /* CustomError.swift */; }; + BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DB4EBE9E2B1075E100CB4DC4 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */; }; @@ -284,6 +285,7 @@ BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; BAFB998D2B0F70F1007D09F9 /* CustomError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; + BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleConfig.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; @@ -628,22 +630,6 @@ path = Base; sourceTree = ""; }; - 078525AC2B0CBFF4007B4521 /* CoreTests */ = { - isa = PBXGroup; - children = ( - 078525AD2B0CC004007B4521 /* Configuration */, - ); - path = CoreTests; - sourceTree = ""; - }; - 078525AD2B0CC004007B4521 /* Configuration */ = { - isa = PBXGroup; - children = ( - DBF6F2472B01E20A0098414B /* ConfigTests.swift */, - ); - path = Configuration; - sourceTree = ""; - }; BA8FA65F2AD5973500EA029A /* Providers */ = { isa = PBXGroup; children = ( @@ -712,6 +698,7 @@ DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, + BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, ); path = Config; sourceTree = ""; @@ -909,6 +896,7 @@ 02F6EF4A28D9F0A700835477 /* DateExtension.swift in Sources */, DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */, 027BD3AF2909475000392132 /* DismissKeyboardTapHandler.swift in Sources */, + BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */, 02E225B0291D29EB0067769A /* UrlExtension.swift in Sources */, 0770DE7928D0C4A9006D8A5D /* RoundedCorners.swift in Sources */, 02CF46C829546AA200A698EE /* NoCachedDataError.swift in Sources */, diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index fbd3636c3..7bb5b1f38 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -18,6 +18,7 @@ public protocol ConfigProtocol { var firebase: FirebaseConfig { get } var facebook: FacebookConfig { get } var microsoft: MicrosoftConfig { get } + var google: GoogleConfig { get } var features: FeaturesConfig { get } } diff --git a/Core/Core/Configuration/Config/GoogleConfig.swift b/Core/Core/Configuration/Config/GoogleConfig.swift new file mode 100644 index 000000000..91416c987 --- /dev/null +++ b/Core/Core/Configuration/Config/GoogleConfig.swift @@ -0,0 +1,37 @@ +// +// GoogleConfig.swift +// Core +// +// Created by Eugene Yatsenko on 27.11.2023. +// + +import Foundation + +private enum GoogleKeys: String { + case enabled = "ENABLED" + case googlePlusKey = "GOOGLE_PLUS_KEY" + case clientID = "CLIENT_ID" +} + +public final class GoogleConfig: NSObject { + public var enabled: Bool = false + public var googlePlusKey: String? + public var clientID: String? + + public var requiredKeysAvailable: Bool { + return clientID != nil + } + + init(dictionary: [String: AnyObject]) { + clientID = dictionary[GoogleKeys.clientID.rawValue] as? String + super.init() + enabled = requiredKeysAvailable && dictionary[GoogleKeys.enabled.rawValue] as? Bool == true + } +} + +private let googleKey = "GOOGLE" +extension Config { + public var google: GoogleConfig { + GoogleConfig(dictionary: self[googleKey] as? [String: AnyObject] ?? [:]) + } +} From 42b48a0e861461192897d8613ccc366d1505252e Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Mon, 27 Nov 2023 15:12:32 +0100 Subject: [PATCH 15/38] chore: move enabled google sign in to google config --- .../Presentation/SocialSign/SocialSignView.swift | 2 +- Core/Core/Configuration/Config/Config.swift | 2 +- Core/Core/Configuration/Config/FirebaseConfig.swift | 3 --- Core/Core/Configuration/Config/GoogleConfig.swift | 4 ++++ 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index fd8bbd2bf..f18df8ddd 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -59,7 +59,7 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - if viewModel.config.firebase.googleSignInEnabled { + if viewModel.config.google.googleSignInEnabled { LabelButton( image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 7bb5b1f38..041466ce5 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -137,7 +137,7 @@ extension Config: ConfigProtocol { (features.isAppleSigninEnabled || facebook.enabled || microsoft.enabled || - firebase.googleSignInEnabled) + google.googleSignInEnabled) } } diff --git a/Core/Core/Configuration/Config/FirebaseConfig.swift b/Core/Core/Configuration/Config/FirebaseConfig.swift index fe783205a..ba4a6f020 100644 --- a/Core/Core/Configuration/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Config/FirebaseConfig.swift @@ -21,7 +21,6 @@ private enum FirebaseKeys: String { case projectID = "PROJECT_ID" case reversedClientID = "REVERSED_CLIENT_ID" case storageBucket = "STORAGE_BUCKET" - case googleSignInEnabled = "GOOGLE_SIGNIN_ENABLED" } enum AnalyticsSource: String { @@ -42,7 +41,6 @@ public final class FirebaseConfig: NSObject { public let projectID: String? public let reversedClientID: String? public let storageBucket: String? - public var googleSignInEnabled: Bool = false private let analyticsSource: AnalyticsSource @@ -67,7 +65,6 @@ public final class FirebaseConfig: NSObject { super.init() enabled = requiredKeysAvailable && dictionary[FirebaseKeys.enabled.rawValue] as? Bool == true - googleSignInEnabled = enabled && dictionary[FirebaseKeys.googleSignInEnabled.rawValue] as? Bool == true let cloudMessagingEnabled = dictionary[FirebaseKeys.cloudMessagingEnabled.rawValue] as? Bool ?? false self.cloudMessagingEnabled = enabled && cloudMessagingEnabled } diff --git a/Core/Core/Configuration/Config/GoogleConfig.swift b/Core/Core/Configuration/Config/GoogleConfig.swift index 91416c987..01c2b0bf4 100644 --- a/Core/Core/Configuration/Config/GoogleConfig.swift +++ b/Core/Core/Configuration/Config/GoogleConfig.swift @@ -9,15 +9,18 @@ import Foundation private enum GoogleKeys: String { case enabled = "ENABLED" + case googleSignInEnabled = "GOOGLE_SIGNIN_ENABLED" case googlePlusKey = "GOOGLE_PLUS_KEY" case clientID = "CLIENT_ID" } public final class GoogleConfig: NSObject { public var enabled: Bool = false + public var googleSignInEnabled: Bool = false public var googlePlusKey: String? public var clientID: String? + public var requiredKeysAvailable: Bool { return clientID != nil } @@ -26,6 +29,7 @@ public final class GoogleConfig: NSObject { clientID = dictionary[GoogleKeys.clientID.rawValue] as? String super.init() enabled = requiredKeysAvailable && dictionary[GoogleKeys.enabled.rawValue] as? Bool == true + googleSignInEnabled = enabled && dictionary[GoogleKeys.googleSignInEnabled.rawValue] as? Bool == true } } From 4f1c54449cf720f42a7cb183b366ff84ffb35dd1 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Mon, 27 Nov 2023 15:58:24 +0100 Subject: [PATCH 16/38] chore: add new dict social login --- .../SocialSign/SocialSignView.swift | 4 +-- Core/Core.xcodeproj/project.pbxproj | 4 +++ Core/Core/Configuration/Config/Config.swift | 7 ++-- .../Configuration/Config/FeaturesConfig.swift | 6 ---- .../Configuration/Config/GoogleConfig.swift | 3 -- .../Config/SocialLoginConfig.swift | 32 +++++++++++++++++++ 6 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 Core/Core/Configuration/Config/SocialLoginConfig.swift diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index f18df8ddd..99eaca2b4 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -59,7 +59,7 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - if viewModel.config.google.googleSignInEnabled { + if viewModel.config.google.enabled { LabelButton( image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", @@ -90,7 +90,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.microsoft)") } - if viewModel.config.features.isAppleSigninEnabled { + if viewModel.config.socialLogin.appleSigninEnabled { LabelButton( image: CoreAssets.iconApple.swiftUIImage, title: "\(title) \(AuthLocalization.apple)", diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 305bb2865..8ba9ba24e 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -134,6 +134,7 @@ BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */; }; BAFB998E2B0F70F1007D09F9 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998D2B0F70F1007D09F9 /* CustomError.swift */; }; BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */; }; + BAFB99922B14E23D007D09F9 /* SocialLoginConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99912B14E23D007D09F9 /* SocialLoginConfig.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DB4EBE9E2B1075E100CB4DC4 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */; }; @@ -286,6 +287,7 @@ BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; BAFB998D2B0F70F1007D09F9 /* CustomError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleConfig.swift; sourceTree = ""; }; + BAFB99912B14E23D007D09F9 /* SocialLoginConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginConfig.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; @@ -699,6 +701,7 @@ BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, + BAFB99912B14E23D007D09F9 /* SocialLoginConfig.swift */, ); path = Config; sourceTree = ""; @@ -933,6 +936,7 @@ 0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */, 02A4833A29B8A9AB00D33F33 /* DownloadManager.swift in Sources */, 027BD3AE2909475000392132 /* KeyboardScrollerOptions.swift in Sources */, + BAFB99922B14E23D007D09F9 /* SocialLoginConfig.swift in Sources */, 027BD3BE2909478B00392132 /* UIResponder+CurrentResponder.swift in Sources */, 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */, 076F297F2A1F80C800967E7D /* Pagination.swift in Sources */, diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 041466ce5..9ad12c481 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -19,6 +19,7 @@ public protocol ConfigProtocol { var facebook: FacebookConfig { get } var microsoft: MicrosoftConfig { get } var google: GoogleConfig { get } + var socialLogin: SocialLoginConfig { get } var features: FeaturesConfig { get } } @@ -133,11 +134,11 @@ extension Config: ConfigProtocol { } public var socialLoginEnabled: Bool { - features.socialLoginEnabled && - (features.isAppleSigninEnabled || + socialLogin.enable && + (socialLogin.appleSigninEnabled || facebook.enabled || microsoft.enabled || - google.googleSignInEnabled) + google.enabled) } } diff --git a/Core/Core/Configuration/Config/FeaturesConfig.swift b/Core/Core/Configuration/Config/FeaturesConfig.swift index 626bbd502..fa17af6f4 100644 --- a/Core/Core/Configuration/Config/FeaturesConfig.swift +++ b/Core/Core/Configuration/Config/FeaturesConfig.swift @@ -9,19 +9,13 @@ import Foundation private enum FeaturesKeys: String { case whatNewEnabled = "WHATS_NEW_ENABLED" - case socialLoginEnabled = "SOCIAL_LOGIN_ENABLED" - case isAppleSigninEnabled = "APPLE_SIGNIN_ENABLED" } public class FeaturesConfig: NSObject { public var whatNewEnabled: Bool - public var socialLoginEnabled: Bool - public var isAppleSigninEnabled: Bool init(dictionary: [String: Any]) { whatNewEnabled = dictionary[FeaturesKeys.whatNewEnabled.rawValue] as? Bool ?? false - socialLoginEnabled = dictionary[FeaturesKeys.socialLoginEnabled.rawValue] as? Bool ?? false - isAppleSigninEnabled = dictionary[FeaturesKeys.isAppleSigninEnabled.rawValue] as? Bool ?? false super.init() } } diff --git a/Core/Core/Configuration/Config/GoogleConfig.swift b/Core/Core/Configuration/Config/GoogleConfig.swift index 01c2b0bf4..5baa04d87 100644 --- a/Core/Core/Configuration/Config/GoogleConfig.swift +++ b/Core/Core/Configuration/Config/GoogleConfig.swift @@ -9,14 +9,12 @@ import Foundation private enum GoogleKeys: String { case enabled = "ENABLED" - case googleSignInEnabled = "GOOGLE_SIGNIN_ENABLED" case googlePlusKey = "GOOGLE_PLUS_KEY" case clientID = "CLIENT_ID" } public final class GoogleConfig: NSObject { public var enabled: Bool = false - public var googleSignInEnabled: Bool = false public var googlePlusKey: String? public var clientID: String? @@ -29,7 +27,6 @@ public final class GoogleConfig: NSObject { clientID = dictionary[GoogleKeys.clientID.rawValue] as? String super.init() enabled = requiredKeysAvailable && dictionary[GoogleKeys.enabled.rawValue] as? Bool == true - googleSignInEnabled = enabled && dictionary[GoogleKeys.googleSignInEnabled.rawValue] as? Bool == true } } diff --git a/Core/Core/Configuration/Config/SocialLoginConfig.swift b/Core/Core/Configuration/Config/SocialLoginConfig.swift new file mode 100644 index 000000000..9439efc37 --- /dev/null +++ b/Core/Core/Configuration/Config/SocialLoginConfig.swift @@ -0,0 +1,32 @@ +// +// SocialLoginConfig.swift +// Core +// +// Created by Eugene Yatsenko on 27.11.2023. +// + +import Foundation + +private enum SocialLoginKeys: String { + case enable = "ENABLED" + case appleSigninEnabled = "APPLE_SIGNIN_ENABLED" + +} + +public class SocialLoginConfig: NSObject { + public var enable: Bool + public var appleSigninEnabled: Bool + + init(dictionary: [String: Any]) { + enable = dictionary[SocialLoginKeys.enable.rawValue] as? Bool ?? false + appleSigninEnabled = dictionary[SocialLoginKeys.appleSigninEnabled.rawValue] as? Bool ?? false + super.init() + } +} + +private let socialLoginKey = "SOCIAL_LOGINS" +extension Config { + public var socialLogin: SocialLoginConfig { + SocialLoginConfig(dictionary: self[socialLoginKey] as? [String: AnyObject] ?? [:]) + } +} From 57a6c608f8b8a7bc80f3765cd17c6cf356d874d7 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 28 Nov 2023 11:39:11 +0100 Subject: [PATCH 17/38] chore: remove extra code, change names, add colors --- .../Presentation/Login/SignInViewModel.swift | 36 ++++++------ .../Registration/SignUpViewModel.swift | 49 +++++++--------- .../SocialSign/SocialSignView.swift | 10 ++-- .../SocialSign/SocialSignViewModel.swift | 21 +++++-- .../AppleButtonColor.colorset/Contents.json | 38 +++++++++++++ .../Contents.json | 38 +++++++++++++ .../GoogleButtonColor.colorset/Contents.json | 38 +++++++++++++ .../Contents.json | 38 +++++++++++++ .../Core/Data/Repository/AuthRepository.swift | 3 +- Core/Core/Network/AuthEndpoint.swift | 9 ++- Core/Core/SwiftGen/Assets.swift | 4 ++ OpenEdX/AppDelegate.swift | 57 +++++++++++-------- 12 files changed, 255 insertions(+), 86 deletions(-) create mode 100644 Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Colors/FacebookButtonColor.colorset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Colors/GoogleButtonColor.colorset/Contents.json create mode 100644 Core/Core/Assets.xcassets/Colors/MicrosoftButtonColor.colorset/Contents.json diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index ee93e0414..b53c7af19 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -92,57 +92,57 @@ public class SignInViewModel: ObservableObject { } } - func sign(with result: Result) { + func sign(with result: Result) { result.success(social) result.failure { error in errorMessage = error.localizedDescription } } - private func social(result: Socials) { + private func social(result: SocialResult) { switch result { - case .apple(let credential): - appleLogin(credential) - case .facebook(let loginManagerLoginResult): - facebookLogin(loginManagerLoginResult) + case .apple(let appleCredentials): + appleLogin(appleCredentials, backend: result.backend) + case .facebook: + facebookLogin(backend: result.backend) case .google(let gIDSignInResult): - googleLogin(gIDSignInResult) - case .microsoft(let account, let token): - microsoftLogin(account, token) + googleLogin(gIDSignInResult, backend: result.backend) + case .microsoft(_, let token): + microsoftLogin(token, backend: result.backend) } } - private func appleLogin(_ credentials: AppleCredentials) { + private func appleLogin(_ credentials: AppleCredentials, backend: String) { socialLogin( externalToken: credentials.token, - backend: "apple-id", + backend: backend, loginMethod: .apple ) } - private func facebookLogin(_ managerLoginResult: LoginManagerLoginResult) { + private func facebookLogin(backend: String) { guard let currentAccessToken = AccessToken.current?.tokenString else { return } socialLogin( externalToken: currentAccessToken, - backend: "facebook", + backend: backend, loginMethod: .facebook ) } - private func googleLogin(_ gIDSignInResult: GIDSignInResult) { + private func googleLogin(_ result: GIDSignInResult, backend: String) { socialLogin( - externalToken: gIDSignInResult.user.accessToken.tokenString, - backend: "google-oauth2", + externalToken: result.user.accessToken.tokenString, + backend: backend, loginMethod: .google ) } - private func microsoftLogin(_ account: MSALAccount, _ token: String) { + private func microsoftLogin(_ token: String, backend: String) { socialLogin( externalToken: token, - backend: "azuread-oauth2", + backend: backend, loginMethod: .microsoft ) } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index bb9c8fc8e..5cb1c4c79 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -91,9 +91,7 @@ public class SignUpViewModel: ObservableObject { func registerUser(externalToken: String? = nil, backend: String? = nil) async { do { var validateFields: [String: String] = [:] - fields.forEach({ - validateFields[$0.field.name] = $0.text - }) + fields.forEach { validateFields[$0.field.name] = $0.text } validateFields["honor_code"] = "true" validateFields["terms_of_service"] = "true" if let externalToken = externalToken, let backend = backend { @@ -128,66 +126,59 @@ public class SignUpViewModel: ObservableObject { } } - @MainActor - func register(with result: Result) { + func register(with result: Result) { result.success(social) result.failure { error in errorMessage = error.localizedDescription } } - @MainActor - private func social(result: Socials) { + private func social(result: SocialResult) { switch result { - case .apple(let credential): - appleLogin(credential) - case .facebook(let loginManagerLoginResult): - facebookLogin(loginManagerLoginResult) + case .apple(let appleCredentials): + appleLogin(appleCredentials, backend: result.backend) + case .facebook: + facebookLogin(backend: result.backend) case .google(let gIDSignInResult): - googleLogin(gIDSignInResult) - case .microsoft(let account, let token): - microsoftLogin(account, token) + googleLogin(gIDSignInResult, backend: result.backend) + case .microsoft(_, let token): + microsoftLogin(token, backend: result.backend) } } - @MainActor - private func appleLogin(_ credentials: AppleCredentials) { + private func appleLogin(_ credentials: AppleCredentials, backend: String) { registerSocial( externalToken: credentials.token, - backend: "apple-id" + backend: backend ) } - @MainActor - private func facebookLogin(_ managerLoginResult: LoginManagerLoginResult) { + private func facebookLogin(backend: String) { guard let currentAccessToken = AccessToken.current?.tokenString else { return } registerSocial( externalToken: currentAccessToken, - backend: "facebook" + backend: backend ) } - @MainActor - private func googleLogin(_ gIDSignInResult: GIDSignInResult) { + private func googleLogin(_ result: GIDSignInResult, backend: String) { registerSocial( - externalToken: gIDSignInResult.user.accessToken.tokenString, - backend: "google-oauth2" + externalToken: result.user.accessToken.tokenString, + backend: backend ) } - @MainActor - private func microsoftLogin(_ account: MSALAccount, _ token: String) { + private func microsoftLogin(_ token: String, backend: String) { registerSocial( externalToken: token, - backend: "azuread-oauth2" + backend: backend ) } - @MainActor private func registerSocial(externalToken: String, backend: String) { - Task { + Task { @MainActor in await registerUser( externalToken: externalToken, backend: backend diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index 99eaca2b4..937f54b75 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -16,7 +16,7 @@ struct SocialSignView: View { init( signType: SignType = .signIn, - onSigned: @escaping (Result) -> Void + onSigned: @escaping (Result) -> Void ) { let viewModel: SocialSignViewModel = .init(onSigned: onSigned) self._viewModel = .init(wrappedValue: viewModel) @@ -64,7 +64,7 @@ struct SocialSignView: View { image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", textColor: .black, - backgroundColor: UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.00).sui, + backgroundColor: CoreAssets.googleButtonColor.swiftUIColor, action: viewModel.signInWithGoogle ) .accessibilityElement(children: .ignore) @@ -74,7 +74,7 @@ struct SocialSignView: View { LabelButton( image: CoreAssets.iconFacebookWhite.swiftUIImage, title: "\(title) \(AuthLocalization.facebook)", - backgroundColor: UIColor(red: 0.09, green: 0.46, blue: 0.95, alpha: 1.00).sui, + backgroundColor: CoreAssets.facebookButtonColor.swiftUIColor, action: viewModel.signInWithFacebook ) .accessibilityElement(children: .ignore) @@ -84,7 +84,7 @@ struct SocialSignView: View { LabelButton( image: CoreAssets.iconMicrosoftWhite.swiftUIImage, title: "\(title) \(AuthLocalization.microsoft)", - backgroundColor: UIColor(red: 0.18, green: 0.18, blue: 0.18, alpha: 1.00).sui, + backgroundColor: CoreAssets.microsoftButtonColor.swiftUIColor, action: viewModel.signInWithMicrosoft ) .accessibilityElement(children: .ignore) @@ -94,7 +94,7 @@ struct SocialSignView: View { LabelButton( image: CoreAssets.iconApple.swiftUIImage, title: "\(title) \(AuthLocalization.apple)", - backgroundColor: .black, + backgroundColor: CoreAssets.appleButtonColor.swiftUIColor, action: viewModel.signInWithApple ) .accessibilityElement(children: .ignore) diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift index 815f20cd0..6afe61b66 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -13,20 +13,33 @@ import GoogleSignIn import MSAL import Swinject -enum Socials { +enum SocialResult { case apple(AppleCredentials) case facebook(LoginManagerLoginResult) case google(GIDSignInResult) case microsoft(MSALAccount, String) + + var backend: String { + switch self { + case .apple: + return "apple-id" + case .facebook: + return "facebook" + case .google: + return "google-oauth2" + case .microsoft: + return "azuread-oauth2" + } + } } final public class SocialSignViewModel: ObservableObject { // MARK: - Properties - - private var onSigned: ((Result) -> Void) + private var onSigned: ((Result) -> Void) - init(onSigned: @escaping (Result) -> Void) { + init(onSigned: @escaping (Result) -> Void) { self.onSigned = onSigned } @@ -100,7 +113,7 @@ final public class SocialSignViewModel: ObservableObject { } } - private func success(with social: Socials) { + private func success(with social: SocialResult) { onSigned(.success(social)) } diff --git a/Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json new file mode 100644 index 000000000..be9d677bb --- /dev/null +++ b/Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Colors/FacebookButtonColor.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/FacebookButtonColor.colorset/Contents.json new file mode 100644 index 000000000..1b6540850 --- /dev/null +++ b/Core/Core/Assets.xcassets/Colors/FacebookButtonColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0xEA", + "green" : "0x73", + "red" : "0x38" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEA", + "green" : "0x73", + "red" : "0x38" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Colors/GoogleButtonColor.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/GoogleButtonColor.colorset/Contents.json new file mode 100644 index 000000000..8cd6714c9 --- /dev/null +++ b/Core/Core/Assets.xcassets/Colors/GoogleButtonColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0xF2", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF2", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Colors/MicrosoftButtonColor.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/MicrosoftButtonColor.colorset/Contents.json new file mode 100644 index 000000000..45a391bd7 --- /dev/null +++ b/Core/Core/Assets.xcassets/Colors/MicrosoftButtonColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x2E", + "red" : "0x2E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x2E", + "red" : "0x2E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index 62108e7db..44fa91236 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -59,7 +59,8 @@ public class AuthRepository: AuthRepositoryProtocol { let endPoint = AuthEndpoint.socialLogin( externalToken: externalToken, backend: backend, - clientId: config.oAuthClientId + clientId: config.oAuthClientId, + tokenType: config.tokenType.rawValue ) let authResponse = try await api.requestData(endPoint).mapResponse(DataLayer.AuthResponse.self) guard let accessToken = authResponse.accessToken, diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index a52524ec1..b67bbd22b 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -10,8 +10,7 @@ import Alamofire enum AuthEndpoint: EndPointType { case getAccessToken(username: String, password: String, clientId: String, tokenType: String) - - case socialLogin(externalToken: String, backend: String, clientId: String) + case socialLogin(externalToken: String, backend: String, clientId: String, tokenType: String) case getUserInfo case getAuthCookies case getRegisterFields @@ -23,7 +22,7 @@ enum AuthEndpoint: EndPointType { switch self { case .getAccessToken: return "/oauth2/access_token" - case let .socialLogin(_, backend, _): + case let .socialLogin(_, backend, _, _): return "/oauth2/exchange_access_token/\(backend)/" case .getUserInfo: return "/api/mobile/v0.5/my_user_info" @@ -77,10 +76,10 @@ enum AuthEndpoint: EndPointType { "asymmetric_jwt": true ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) - case let .socialLogin(externalToken, _, clientId): + case let .socialLogin(externalToken, _, clientId, tokenType): let params: [String: Encodable] = [ "client_id": clientId, - "token_type": "jwt", + "token_type": tokenType, "access_token": externalToken, "asymmetric_jwt": true ] diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index 9ada28f1d..df647f87b 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -28,6 +28,7 @@ public enum CoreAssets { public static let checkEmail = ImageAsset(name: "checkEmail") public static let accentColor = ColorAsset(name: "AccentColor") public static let alert = ColorAsset(name: "Alert") + public static let appleButtonColor = ColorAsset(name: "AppleButtonColor") public static let avatarStroke = ColorAsset(name: "AvatarStroke") public static let background = ColorAsset(name: "Background") public static let backgroundStroke = ColorAsset(name: "BackgroundStroke") @@ -35,6 +36,9 @@ public enum CoreAssets { public static let cardViewStroke = ColorAsset(name: "CardViewStroke") public static let certificateForeground = ColorAsset(name: "CertificateForeground") public static let commentCellBackground = ColorAsset(name: "CommentCellBackground") + public static let facebookButtonColor = ColorAsset(name: "FacebookButtonColor") + public static let googleButtonColor = ColorAsset(name: "GoogleButtonColor") + public static let microsoftButtonColor = ColorAsset(name: "MicrosoftButtonColor") public static let shadowColor = ColorAsset(name: "ShadowColor") public static let snackbarErrorColor = ColorAsset(name: "SnackbarErrorColor") public static let snackbarErrorTextColor = ColorAsset(name: "SnackbarErrorTextColor") diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 8795c8a3c..371d0de11 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -33,19 +33,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - ApplicationDelegate.shared.application( - application, - didFinishLaunchingWithOptions: launchOptions - ) - initDI() - if let config = Container.shared.resolve(ConfigProtocol.self), - let configuration = config.firebase.firebaseOptions { - FirebaseApp.configure(options: configuration) - Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + if let config = Container.shared.resolve(ConfigProtocol.self) { + + if let configuration = config.firebase.firebaseOptions { + FirebaseApp.configure(options: configuration) + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + } + + if config.facebook.enabled { + ApplicationDelegate.shared.application( + application, + didFinishLaunchingWithOptions: launchOptions + ) + } } - + Theme.Fonts.registerFonts() window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = RouteController() @@ -65,22 +69,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { - ApplicationDelegate.shared.application( - app, - open: url, - sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, - annotation: options[UIApplication.OpenURLOptionsKey.annotation] - ) + if let config = Container.shared.resolve(ConfigProtocol.self), config.socialLoginEnabled { + if config.facebook.enabled { + ApplicationDelegate.shared.application( + app, + open: url, + sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String, + annotation: options[UIApplication.OpenURLOptionsKey.annotation] + ) + } - if GIDSignIn.sharedInstance.handle(url) { - return true - } + if config.google.enabled, GIDSignIn.sharedInstance.handle(url) { + return true + } - if MSALPublicClientApplication.handleMSALResponse( - url, - sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String - ) { - return true + if config.microsoft.enabled, + MSALPublicClientApplication.handleMSALResponse( + url, + sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String + ) { + return true + } } return false From abeeaf5bd5d8c06986e92992f4a44252a72f9215 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 28 Nov 2023 11:45:33 +0100 Subject: [PATCH 18/38] refactor: open URL options key method --- OpenEdX/AppDelegate.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 371d0de11..c0c342e59 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -79,16 +79,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ) } - if config.google.enabled, GIDSignIn.sharedInstance.handle(url) { - return true + if config.google.enabled { + return GIDSignIn.sharedInstance.handle(url) } - if config.microsoft.enabled, - MSALPublicClientApplication.handleMSALResponse( - url, - sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String - ) { - return true + if config.microsoft.enabled { + return MSALPublicClientApplication.handleMSALResponse( + url, + sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String + ) } } From 2761c841c0b3dbb28c74e2bef5f76f8b42d1cc6c Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 28 Nov 2023 12:24:28 +0100 Subject: [PATCH 19/38] refactor: add SocialSignView previews --- .../Presentation/Login/SignInView.swift | 2 +- .../Presentation/Registration/SignUpView.swift | 2 +- .../SocialSign/SocialSignView.swift | 12 ++++++++++-- .../SocialSign/SocialSignViewModel.swift | 10 +++++++--- Core/Core/Configuration/Config/Config.swift | 17 +++++++++++++++++ .../Configuration/Config/GoogleConfig.swift | 1 - Core/Core/View/Base/LabelButton.swift | 13 +++++++++++++ 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 24474747f..39a3e38e8 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -113,7 +113,7 @@ public struct SignInView: View { } } if viewModel.socialLoginEnabled { - SocialSignView(onSigned: viewModel.sign) + SocialSignView(viewModel: .init(onSigned: viewModel.sign)) } Spacer() } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index 4e8b600a5..ed1434638 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -108,7 +108,7 @@ public struct SignUpView: View { if viewModel.socialLoginEnabled { SocialSignView( signType: .register, - onSigned: viewModel.register + viewModel: .init(onSigned: viewModel.register) ) .padding(.bottom, 30) } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index 937f54b75..b5ebce774 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -16,9 +16,8 @@ struct SocialSignView: View { init( signType: SignType = .signIn, - onSigned: @escaping (Result) -> Void + viewModel: SocialSignViewModel ) { - let viewModel: SocialSignViewModel = .init(onSigned: onSigned) self._viewModel = .init(wrappedValue: viewModel) self.signType = signType } @@ -103,3 +102,12 @@ struct SocialSignView: View { } } } + +#if DEBUG +struct SocialSignView_Previews: PreviewProvider { + static var previews: some View { + let vm = SocialSignViewModel(onSigned: { _ in }) + SocialSignView(viewModel: vm).padding() + } +} +#endif diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift index 6afe61b66..e0e9b6ff8 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -39,10 +39,16 @@ final public class SocialSignViewModel: ObservableObject { private var onSigned: ((Result) -> Void) - init(onSigned: @escaping (Result) -> Void) { + init( + config: ConfigProtocol = Container.shared.resolve(ConfigProtocol.self) ?? ConfigMock(), + onSigned: @escaping (Result) -> Void + ) { + self.config = config self.onSigned = onSigned } + let config: ConfigProtocol + private let appleSingInProvider: AppleSingInProvider = .init() private let googleSingInProvider: GoogleSingInProvider = .init() private let facebookSingInProvider: FacebookSingInProvider = .init() @@ -52,8 +58,6 @@ final public class SocialSignViewModel: ObservableObject { UIApplication.topViewController() } - private(set) lazy var config = Container.shared.resolve(ConfigProtocol.self)! - // MARK: - Public Intens - func signInWithApple() { diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 9ad12c481..2e92b5412 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -154,6 +154,23 @@ public class ConfigMock: Config { "AGREEMENT_URLS": [ "PRIVACY_POLICY_URL": "https://www.example.com/privacy", "TOS_URL": "https://www.example.com/tos" + ], + "GOOGLE": [ + "ENABLED": true, + "CLIENT_ID": "CLIENT_ID" + ], + "FACEBOOK": [ + "ENABLED": true, + "FACEBOOK_APP_ID": "FACEBOOK_APP_ID", + "CLIENT_TOKEN": "CLIENT_TOKEN", + ], + "MICROSOFT": [ + "ENABLED": true, + "APP_ID": "APP_ID" + ], + "SOCIAL_LOGINS": [ + "ENABLED": true, + "APPLE_SIGNIN_ENABLED": true ] ] diff --git a/Core/Core/Configuration/Config/GoogleConfig.swift b/Core/Core/Configuration/Config/GoogleConfig.swift index 5baa04d87..91416c987 100644 --- a/Core/Core/Configuration/Config/GoogleConfig.swift +++ b/Core/Core/Configuration/Config/GoogleConfig.swift @@ -18,7 +18,6 @@ public final class GoogleConfig: NSObject { public var googlePlusKey: String? public var clientID: String? - public var requiredKeysAvailable: Bool { return clientID != nil } diff --git a/Core/Core/View/Base/LabelButton.swift b/Core/Core/View/Base/LabelButton.swift index e61a25949..eda2e39f4 100644 --- a/Core/Core/View/Base/LabelButton.swift +++ b/Core/Core/View/Base/LabelButton.swift @@ -57,3 +57,16 @@ public struct LabelButton: View { } } + +#if DEBUG +struct LabelButton_Previews: PreviewProvider { + static var previews: some View { + LabelButton( + image: CoreAssets.iconApple.swiftUIImage, + title: "Apple", + backgroundColor: CoreAssets.appleButtonColor.swiftUIColor, + action: { } + ) + } +} +#endif From 49189c768d68e86ca87f4872dff082cfa7d5dc52 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 28 Nov 2023 14:17:23 +0100 Subject: [PATCH 20/38] chore: add apple sign in config, pr issues --- .../Presentation/Login/SignInView.swift | 7 +++- .../Presentation/Login/SignInViewModel.swift | 15 ++++++--- .../Registration/SignUpView.swift | 5 ++- .../Registration/SignUpViewModel.swift | 9 +++++- .../SocialSign/SocialSignView.swift | 8 ++--- .../SocialSign/SocialSignViewModel.swift | 18 +++++------ Core/Core.xcodeproj/project.pbxproj | 8 ++--- .../Providers/MicrosoftSingInProvider.swift | 2 +- .../Config/AppleSignInConfig.swift | 28 ++++++++++++++++ Core/Core/Configuration/Config/Config.swift | 20 ++++++------ .../Config/SocialLoginConfig.swift | 32 ------------------- .../CoreTests/Configuration/ConfigTests.swift | 19 ++++++++++- 12 files changed, 102 insertions(+), 69 deletions(-) create mode 100644 Core/Core/Configuration/Config/AppleSignInConfig.swift delete mode 100644 Core/Core/Configuration/Config/SocialLoginConfig.swift diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 39a3e38e8..4fa8e2edb 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -113,7 +113,12 @@ public struct SignInView: View { } } if viewModel.socialLoginEnabled { - SocialSignView(viewModel: .init(onSigned: viewModel.sign)) + SocialSignView( + viewModel: .init( + config: viewModel.config, + completion: { viewModel.sign(with: $0) } + ) + ) } Spacer() } diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index b53c7af19..65d07d496 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -15,7 +15,7 @@ import GoogleSignIn import MSAL public class SignInViewModel: ObservableObject { - + @Published private(set) var isShowProgress = false @Published private(set) var showError: Bool = false @Published private(set) var showAlert: Bool = false @@ -35,7 +35,7 @@ public class SignInViewModel: ObservableObject { } let router: AuthorizationRouter - private let config: ConfigProtocol + let config: ConfigProtocol private let interactor: AuthInteractorProtocol private let analytics: AuthorizationAnalytics private let validator: Validator @@ -92,13 +92,15 @@ public class SignInViewModel: ObservableObject { } } + @MainActor func sign(with result: Result) { - result.success(social) + result.success { social(result: $0) } result.failure { error in errorMessage = error.localizedDescription } } + @MainActor private func social(result: SocialResult) { switch result { case .apple(let appleCredentials): @@ -112,6 +114,7 @@ public class SignInViewModel: ObservableObject { } } + @MainActor private func appleLogin(_ credentials: AppleCredentials, backend: String) { socialLogin( externalToken: credentials.token, @@ -120,6 +123,7 @@ public class SignInViewModel: ObservableObject { ) } + @MainActor private func facebookLogin(backend: String) { guard let currentAccessToken = AccessToken.current?.tokenString else { return @@ -131,6 +135,7 @@ public class SignInViewModel: ObservableObject { ) } + @MainActor private func googleLogin(_ result: GIDSignInResult, backend: String) { socialLogin( externalToken: result.user.accessToken.tokenString, @@ -139,6 +144,7 @@ public class SignInViewModel: ObservableObject { ) } + @MainActor private func microsoftLogin(_ token: String, backend: String) { socialLogin( externalToken: token, @@ -147,8 +153,9 @@ public class SignInViewModel: ObservableObject { ) } + @MainActor private func socialLogin(externalToken: String, backend: String, loginMethod: LoginMethod) { - Task { @MainActor in + Task { isShowProgress = true do { let user = try await interactor.login(externalToken: externalToken, backend: backend) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index ed1434638..a07729bf1 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -108,7 +108,10 @@ public struct SignUpView: View { if viewModel.socialLoginEnabled { SocialSignView( signType: .register, - viewModel: .init(onSigned: viewModel.register) + viewModel: .init( + config: viewModel.config, + completion: { viewModel.register(with: $0) } + ) ) .padding(.bottom, 30) } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 5cb1c4c79..c2d0a7f24 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -126,6 +126,7 @@ public class SignUpViewModel: ObservableObject { } } + @MainActor func register(with result: Result) { result.success(social) result.failure { error in @@ -133,6 +134,7 @@ public class SignUpViewModel: ObservableObject { } } + @MainActor private func social(result: SocialResult) { switch result { case .apple(let appleCredentials): @@ -146,6 +148,7 @@ public class SignUpViewModel: ObservableObject { } } + @MainActor private func appleLogin(_ credentials: AppleCredentials, backend: String) { registerSocial( externalToken: credentials.token, @@ -153,6 +156,7 @@ public class SignUpViewModel: ObservableObject { ) } + @MainActor private func facebookLogin(backend: String) { guard let currentAccessToken = AccessToken.current?.tokenString else { return @@ -163,6 +167,7 @@ public class SignUpViewModel: ObservableObject { ) } + @MainActor private func googleLogin(_ result: GIDSignInResult, backend: String) { registerSocial( externalToken: result.user.accessToken.tokenString, @@ -170,6 +175,7 @@ public class SignUpViewModel: ObservableObject { ) } + @MainActor private func microsoftLogin(_ token: String, backend: String) { registerSocial( externalToken: token, @@ -177,8 +183,9 @@ public class SignUpViewModel: ObservableObject { ) } + @MainActor private func registerSocial(externalToken: String, backend: String) { - Task { @MainActor in + Task { await registerUser( externalToken: externalToken, backend: backend diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index b5ebce774..9b22db7d8 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -10,7 +10,7 @@ import Core struct SocialSignView: View { - // MARK: - Properties - + // MARK: - Properties @StateObject var viewModel: SocialSignViewModel @@ -37,7 +37,7 @@ struct SocialSignView: View { } } - // MARK: - Views - + // MARK: - Views var body: some View { VStack(spacing: 10) { @@ -89,7 +89,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.microsoft)") } - if viewModel.config.socialLogin.appleSigninEnabled { + if viewModel.config.appleSignIn.enable { LabelButton( image: CoreAssets.iconApple.swiftUIImage, title: "\(title) \(AuthLocalization.apple)", @@ -106,7 +106,7 @@ struct SocialSignView: View { #if DEBUG struct SocialSignView_Previews: PreviewProvider { static var previews: some View { - let vm = SocialSignViewModel(onSigned: { _ in }) + let vm = SocialSignViewModel(config: ConfigMock(), completion: { _ in }) SocialSignView(viewModel: vm).padding() } } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift index e0e9b6ff8..09726cfe9 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -35,19 +35,19 @@ enum SocialResult { final public class SocialSignViewModel: ObservableObject { - // MARK: - Properties - + // MARK: - Properties - private var onSigned: ((Result) -> Void) + private var completion: ((Result) -> Void) init( - config: ConfigProtocol = Container.shared.resolve(ConfigProtocol.self) ?? ConfigMock(), - onSigned: @escaping (Result) -> Void + config: ConfigProtocol, + completion: @escaping (Result) -> Void ) { self.config = config - self.onSigned = onSigned + self.completion = completion } - let config: ConfigProtocol + let config: ConfigProtocol private let appleSingInProvider: AppleSingInProvider = .init() private let googleSingInProvider: GoogleSingInProvider = .init() @@ -58,7 +58,7 @@ final public class SocialSignViewModel: ObservableObject { UIApplication.topViewController() } - // MARK: - Public Intens - + // MARK: - Public Intens func signInWithApple() { appleSingInProvider.request { [weak self] result in @@ -118,11 +118,11 @@ final public class SocialSignViewModel: ObservableObject { } private func success(with social: SocialResult) { - onSigned(.success(social)) + completion(.success(social)) } private func failure(_ error: Error) { - onSigned(.failure(error)) + completion(.failure(error)) } } diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 8ba9ba24e..1d5fc5b63 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -134,7 +134,7 @@ BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */; }; BAFB998E2B0F70F1007D09F9 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998D2B0F70F1007D09F9 /* CustomError.swift */; }; BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */; }; - BAFB99922B14E23D007D09F9 /* SocialLoginConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99912B14E23D007D09F9 /* SocialLoginConfig.swift */; }; + BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DB4EBE9E2B1075E100CB4DC4 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */; }; @@ -287,7 +287,7 @@ BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; BAFB998D2B0F70F1007D09F9 /* CustomError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleConfig.swift; sourceTree = ""; }; - BAFB99912B14E23D007D09F9 /* SocialLoginConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginConfig.swift; sourceTree = ""; }; + BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSignInConfig.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; @@ -701,7 +701,7 @@ BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, - BAFB99912B14E23D007D09F9 /* SocialLoginConfig.swift */, + BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */, ); path = Config; sourceTree = ""; @@ -936,7 +936,7 @@ 0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */, 02A4833A29B8A9AB00D33F33 /* DownloadManager.swift in Sources */, 027BD3AE2909475000392132 /* KeyboardScrollerOptions.swift in Sources */, - BAFB99922B14E23D007D09F9 /* SocialLoginConfig.swift in Sources */, + BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */, 027BD3BE2909478B00392132 /* UIResponder+CurrentResponder.swift in Sources */, 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */, 076F297F2A1F80C800967E7D /* Pagination.swift in Sources */, diff --git a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift index ac079c890..0a1a053ba 100644 --- a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift @@ -75,7 +75,7 @@ public final class MicrosoftSingInProvider { let clientApplication = try createClientApplication() guard let account = try clientApplication.allAccounts().first else { - throw CustomError.error(text: "Error") + throw CustomError.error(text: "Account not found") } return account diff --git a/Core/Core/Configuration/Config/AppleSignInConfig.swift b/Core/Core/Configuration/Config/AppleSignInConfig.swift new file mode 100644 index 000000000..824cfda73 --- /dev/null +++ b/Core/Core/Configuration/Config/AppleSignInConfig.swift @@ -0,0 +1,28 @@ +// +// SocialLoginConfig.swift +// Core +// +// Created by Eugene Yatsenko on 27.11.2023. +// + +import Foundation + +private enum AppleSignInKeys: String { + case enable = "ENABLED" +} + +public class AppleSignInConfig: NSObject { + public var enable: Bool + + init(dictionary: [String: Any]) { + enable = dictionary[AppleSignInKeys.enable.rawValue] as? Bool ?? false + super.init() + } +} + +private let appleSignInKey = "APPLE_SIGNIN" +extension Config { + public var appleSignIn: AppleSignInConfig { + AppleSignInConfig(dictionary: self[appleSignInKey] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 2e92b5412..84fccabad 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -19,7 +19,7 @@ public protocol ConfigProtocol { var facebook: FacebookConfig { get } var microsoft: MicrosoftConfig { get } var google: GoogleConfig { get } - var socialLogin: SocialLoginConfig { get } + var appleSignIn: AppleSignInConfig { get } var features: FeaturesConfig { get } } @@ -134,11 +134,10 @@ extension Config: ConfigProtocol { } public var socialLoginEnabled: Bool { - socialLogin.enable && - (socialLogin.appleSigninEnabled || + appleSignIn.enable || facebook.enabled || microsoft.enabled || - google.enabled) + google.enabled } } @@ -157,20 +156,19 @@ public class ConfigMock: Config { ], "GOOGLE": [ "ENABLED": true, - "CLIENT_ID": "CLIENT_ID" + "CLIENT_ID": "clientId" ], "FACEBOOK": [ "ENABLED": true, - "FACEBOOK_APP_ID": "FACEBOOK_APP_ID", - "CLIENT_TOKEN": "CLIENT_TOKEN", + "FACEBOOK_APP_ID": "facebookAppId", + "CLIENT_TOKEN": "client_token" ], "MICROSOFT": [ "ENABLED": true, - "APP_ID": "APP_ID" + "APP_ID": "appId" ], - "SOCIAL_LOGINS": [ - "ENABLED": true, - "APPLE_SIGNIN_ENABLED": true + "APPLE_SIGNIN": [ + "ENABLED": true ] ] diff --git a/Core/Core/Configuration/Config/SocialLoginConfig.swift b/Core/Core/Configuration/Config/SocialLoginConfig.swift deleted file mode 100644 index 9439efc37..000000000 --- a/Core/Core/Configuration/Config/SocialLoginConfig.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// SocialLoginConfig.swift -// Core -// -// Created by Eugene Yatsenko on 27.11.2023. -// - -import Foundation - -private enum SocialLoginKeys: String { - case enable = "ENABLED" - case appleSigninEnabled = "APPLE_SIGNIN_ENABLED" - -} - -public class SocialLoginConfig: NSObject { - public var enable: Bool - public var appleSigninEnabled: Bool - - init(dictionary: [String: Any]) { - enable = dictionary[SocialLoginKeys.enable.rawValue] as? Bool ?? false - appleSigninEnabled = dictionary[SocialLoginKeys.appleSigninEnabled.rawValue] as? Bool ?? false - super.init() - } -} - -private let socialLoginKey = "SOCIAL_LOGINS" -extension Config { - public var socialLogin: SocialLoginConfig { - SocialLoginConfig(dictionary: self[socialLoginKey] as? [String: AnyObject] ?? [:]) - } -} diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index 819970fb4..3881864fa 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -32,7 +32,24 @@ class ConfigTests: XCTestCase { "REVERSED_CLIENT_ID": "testReversedClientID", "STORAGE_BUCKET": "testStorageBucket", "ANALYTICS_SOURCE": "firebase", - "CLOUD_MESSAGING_ENABLED": true] + "CLOUD_MESSAGING_ENABLED": true + ], + "GOOGLE": [ + "ENABLED": true, + "CLIENT_ID": "clientId" + ], + "FACEBOOK": [ + "ENABLED": true, + "FACEBOOK_APP_ID": "facebookAppId", + "CLIENT_TOKEN": "client_token" + ], + "MICROSOFT": [ + "ENABLED": true, + "APP_ID": "appId" + ], + "APPLE_SIGNIN": [ + "ENABLED": true + ] ] func testConfigInitialization() { From 98f5b991c2a941b017a266ab4d7aec775aaade7e Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 30 Nov 2023 18:05:08 +0100 Subject: [PATCH 21/38] chore: improvement sign up with third party auth, new error if third party auth cancelled --- .../Registration/SignUpView.swift | 16 ++++- .../Registration/SignUpViewModel.swift | 66 +++++++++++++++---- .../SocialSign/SocialSignView.swift | 8 +-- .../SocialSign/SocialSignViewModel.swift | 26 +++++++- .../Authorization/SwiftGen/Strings.swift | 4 ++ .../en.lproj/Localizable.strings | 2 + .../uk.lproj/Localizable.strings | 2 + .../AvoidingHelpers/Error/CustomError.swift | 6 ++ .../Providers/AppleSingInProvider.swift | 21 ++++-- .../Providers/FacebookSingInProvider.swift | 30 ++++++++- .../Providers/GoogleSingInProvider.swift | 4 +- .../Providers/MicrosoftSingInProvider.swift | 10 ++- Core/Core/SwiftGen/Strings.swift | 4 ++ Core/Core/View/Base/LabelButton.swift | 8 ++- Core/Core/en.lproj/Localizable.strings | 3 + Core/Core/uk.lproj/Localizable.strings | 2 + 16 files changed, 179 insertions(+), 33 deletions(-) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index a07729bf1..b7df88751 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -66,7 +66,17 @@ public struct SignUpView: View { .font(Theme.Fonts.titleSmall) .foregroundColor(Theme.Colors.textPrimary) .padding(.bottom, 20) - + + if viewModel.isThirdPartyAuthSuccess { + Text(AuthLocalization.SignUp.successSignedinLabel) + .font(Theme.Fonts.titleMedium) + .foregroundColor(Theme.Colors.textPrimary) + Text(AuthLocalization.SignUp.successSignedinSublabel) + .font(Theme.Fonts.titleSmall) + .foregroundColor(Theme.Colors.textSecondary) + .padding(.bottom, 20) + } + let requiredFields = viewModel.fields.filter {$0.field.required} let nonRequiredFields = viewModel.fields.filter {!$0.field.required} @@ -97,6 +107,7 @@ public struct SignUpView: View { }.frame(maxWidth: .infinity) } else { StyledButton(AuthLocalization.SignUp.createAccountBtn) { + viewModel.isThirdPartyAuthSuccess = false Task { await viewModel.registerUser() } @@ -105,7 +116,8 @@ public struct SignUpView: View { .padding(.top, 40) .frame(maxWidth: .infinity) } - if viewModel.socialLoginEnabled { + if viewModel.socialLoginEnabled, + !requiredFields.isEmpty { SocialSignView( signType: .register, viewModel: .init( diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index c2d0a7f24..4ba6e97d8 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -18,6 +18,7 @@ public class SignUpViewModel: ObservableObject { @Published var isShowProgress = false @Published var scrollTo: Int? @Published var showError: Bool = false + @Published var isThirdPartyAuthSuccess: Bool = false var errorMessage: String? { didSet { withAnimation { @@ -53,10 +54,15 @@ public class SignUpViewModel: ObservableObject { } var socialLoginEnabled: Bool { - config.socialLoginEnabled + config.socialLoginEnabled && !isThirdPartyAuthSuccess && !isShowProgress } private func showErrors(errors: [String: String]) -> Bool { + if isThirdPartyAuthSuccess, !errors.map({ $0.value }).filter({ !$0.isEmpty }).isEmpty { + scrollTo = 1 + return true + } + var containsError = false errors.forEach { key, value in if let index = fields.firstIndex(where: { $0.field.name == key }) { @@ -86,9 +92,12 @@ public class SignUpViewModel: ObservableObject { } } } - + + private var externalToken: String? + private var backend: String? + @MainActor - func registerUser(externalToken: String? = nil, backend: String? = nil) async { + func registerUser() async { do { var validateFields: [String: String] = [:] fields.forEach { validateFields[$0.field.name] = $0.text } @@ -101,6 +110,7 @@ public class SignUpViewModel: ObservableObject { if validateFields.contains(where: {$0.key == "password"}) { validateFields.removeValue(forKey: "password") } + fields.removeAll { $0.field.type == .password } } let errors = try await interactor.validateRegistrationFields(fields: validateFields) guard !showErrors(errors: errors) else { return } @@ -139,12 +149,12 @@ public class SignUpViewModel: ObservableObject { switch result { case .apple(let appleCredentials): appleLogin(appleCredentials, backend: result.backend) - case .facebook: - facebookLogin(backend: result.backend) + case .facebook(let account): + facebookLogin(backend: result.backend, account: account) case .google(let gIDSignInResult): googleLogin(gIDSignInResult, backend: result.backend) - case .microsoft(_, let token): - microsoftLogin(token, backend: result.backend) + case .microsoft(let account, let token): + microsoftLogin(token, backend: result.backend, account: account) } } @@ -157,18 +167,38 @@ public class SignUpViewModel: ObservableObject { } @MainActor - private func facebookLogin(backend: String) { + private func facebookLogin(backend: String, account: LoginManagerLoginResult) { guard let currentAccessToken = AccessToken.current?.tokenString else { return } + + GraphRequest( + graphPath: "me", + parameters: ["fields": "name, email"] + ).start { [weak self] _, result, _ in + guard let self = self, let userInfo = result as? [String: Any] else { + return + } + self.update( + fullName: userInfo["name"] as? String, + email: userInfo["email"] as? String + ) + } + registerSocial( externalToken: currentAccessToken, backend: backend ) + } @MainActor private func googleLogin(_ result: GIDSignInResult, backend: String) { + update( + fullName: result.user.profile?.name, + email: result.user.profile?.email + ) + registerSocial( externalToken: result.user.accessToken.tokenString, backend: backend @@ -176,7 +206,12 @@ public class SignUpViewModel: ObservableObject { } @MainActor - private func microsoftLogin(_ token: String, backend: String) { + private func microsoftLogin(_ token: String, backend: String, account: MSALAccount) { + update( + fullName: account.accountClaims?["name"] as? String, + email: account.accountClaims?["email"] as? String + ) + registerSocial( externalToken: token, backend: backend @@ -185,14 +220,19 @@ public class SignUpViewModel: ObservableObject { @MainActor private func registerSocial(externalToken: String, backend: String) { + self.externalToken = externalToken + self.backend = backend + isThirdPartyAuthSuccess = true Task { - await registerUser( - externalToken: externalToken, - backend: backend - ) + await registerUser() } } + private func update(fullName: String?, email: String?) { + fields.first(where: { $0.field.type == .email })?.text = email ?? "" + fields.first(where: { $0.field.name == "name" })?.text = fullName ?? "" + } + func trackCreateAccountClicked() { analytics.createAccountClicked() } diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift index 9b22db7d8..6558071f4 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift @@ -58,7 +58,7 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - if viewModel.config.google.enabled { + if viewModel.isGoogleEnabled { LabelButton( image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", @@ -69,7 +69,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.config.facebook.enabled { + if viewModel.isFaceboolEnabled { LabelButton( image: CoreAssets.iconFacebookWhite.swiftUIImage, title: "\(title) \(AuthLocalization.facebook)", @@ -79,7 +79,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.config.microsoft.enabled { + if viewModel.isMicrosoftEnabled { LabelButton( image: CoreAssets.iconMicrosoftWhite.swiftUIImage, title: "\(title) \(AuthLocalization.microsoft)", @@ -89,7 +89,7 @@ struct SocialSignView: View { .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.microsoft)") } - if viewModel.config.appleSignIn.enable { + if viewModel.isAppleSignInEnabled { LabelButton( image: CoreAssets.iconApple.swiftUIImage, title: "\(title) \(AuthLocalization.apple)", diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift index 09726cfe9..13307845f 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift @@ -38,6 +38,7 @@ final public class SocialSignViewModel: ObservableObject { // MARK: - Properties private var completion: ((Result) -> Void) + private let config: ConfigProtocol init( config: ConfigProtocol, @@ -47,8 +48,6 @@ final public class SocialSignViewModel: ObservableObject { self.completion = completion } - let config: ConfigProtocol - private let appleSingInProvider: AppleSingInProvider = .init() private let googleSingInProvider: GoogleSingInProvider = .init() private let facebookSingInProvider: FacebookSingInProvider = .init() @@ -58,6 +57,29 @@ final public class SocialSignViewModel: ObservableObject { UIApplication.topViewController() } + // MARK: - Public Properties + + var isFaceboolEnabled: Bool { + config.facebook.enabled + } + + var isGoogleEnabled: Bool { + config.google.enabled + } + + var isMicrosoftEnabled: Bool { + config.microsoft.enabled + } + + var isAppleSignInEnabled: Bool { + if isFaceboolEnabled || + isGoogleEnabled || + isMicrosoftEnabled { + return true + } + return config.appleSignIn.enable + } + // MARK: - Public Intens func signInWithApple() { diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index 88894cb4d..de2e2298a 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -70,6 +70,10 @@ public enum AuthLocalization { public static let showFields = AuthLocalization.tr("Localizable", "SIGN_UP.SHOW_FIELDS", fallback: "Show optional Fields") /// Create new account. public static let subtitle = AuthLocalization.tr("Localizable", "SIGN_UP.SUBTITLE", fallback: "Create new account.") + /// You've successfully signed in. + public static let successSignedinLabel = AuthLocalization.tr("Localizable", "SIGN_UP.SUCCESS_SIGNEDIN_LABEL", fallback: "You've successfully signed in.") + /// We just need a little more information before you start learning. + public static let successSignedinSublabel = AuthLocalization.tr("Localizable", "SIGN_UP.SUCCESS_SIGNEDIN_SUBLABEL", fallback: "We just need a little more information before you start learning.") /// Sign up public static let title = AuthLocalization.tr("Localizable", "SIGN_UP.TITLE", fallback: "Sign up") } diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index c68796dbf..92e74fcfc 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -23,6 +23,8 @@ "SIGN_UP.CREATE_ACCOUNT_BTN" = "Create account"; "SIGN_UP.HIDE_FIELDS" = "Hide optional Fields"; "SIGN_UP.SHOW_FIELDS" = "Show optional Fields"; +"SIGN_UP.SUCCESS_SIGNEDIN_LABEL" = "You've successfully signed in."; +"SIGN_UP.SUCCESS_SIGNEDIN_SUBLABEL" = "We just need a little more information before you start learning."; "FORGOT.TITLE"= "Forgot password"; "FORGOT.DESCRIPTION" = "Please enter your log-in or recovery email address below and we will send you an email with instructions."; diff --git a/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings index c1887f736..a1f1bf6a5 100644 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ b/Authorization/Authorization/uk.lproj/Localizable.strings @@ -22,6 +22,8 @@ "SIGN_UP.CREATE_ACCOUNT_BTN" = "Створити акаунт"; "SIGN_UP.HIDE_FIELDS" = "Приховати необовʼязкові поля"; "SIGN_UP.SHOW_FIELDS" = "Показати необовʼязкові поля"; +"SIGN_UP.SUCCESS_SIGNEDIN_LABEL" = "You've successfully signed in."; +"SIGN_UP.SUCCESS_SIGNEDIN_SUBLABEL" = "We just need a little more information before you start learning."; "FORGOT.TITLE"= "Відновлення паролю"; "FORGOT.DESCRIPTION" = "Будь ласка, введіть свою адресу електронної пошти для входу або відновлення нижче, і ми надішлемо вам електронний лист з інструкціями."; diff --git a/Core/Core/AvoidingHelpers/Error/CustomError.swift b/Core/Core/AvoidingHelpers/Error/CustomError.swift index b73f04b0a..4dd24c068 100644 --- a/Core/Core/AvoidingHelpers/Error/CustomError.swift +++ b/Core/Core/AvoidingHelpers/Error/CustomError.swift @@ -9,6 +9,8 @@ import Foundation public enum CustomError: Error { case error(text: String) + case socialSignCanceled + case unknownError } extension CustomError: LocalizedError { @@ -16,6 +18,10 @@ extension CustomError: LocalizedError { switch self { case .error(let text): return text + case .socialSignCanceled: + return CoreLocalization.socialSignCanceled + case .unknownError: + return CoreLocalization.Error.unknownError } } } diff --git a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift index e1933cdbb..68d14fd90 100644 --- a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift @@ -37,19 +37,19 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { - guard let credentials = authorization.credential as? ASAuthorizationAppleIDCredential else { - completion?(.failure(CustomError.error(text: "ASAuthorizationAppleIDCredential is nil"))) + guard let credentials = authorization.credential as? ASAuthorizationAppleIDCredential else { + completion?(.failure(CustomError.unknownError)) return } let firstName = credentials.fullName?.givenName ?? "" let lastName = credentials.fullName?.familyName ?? "" let email = credentials.email ?? "" - var name = "\(firstName) \(lastName)" + let name = "\(firstName) \(lastName)" guard let data = credentials.identityToken, let code = String(data: data, encoding: .utf8) else { - completion?(.failure(CustomError.error(text: "Token is nil"))) + completion?(.failure(CustomError.unknownError)) return } @@ -69,6 +69,17 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg didCompleteWithError error: Error ) { debugLog(error) - completion?(.failure(error)) + completion?(.failure(failure(ASAuthorizationError(_nsError: error as NSError)))) + } + + private func failure(_ error: ASAuthorizationError) -> Error { + switch error.code { + case .canceled: + return CustomError.socialSignCanceled + case .failed: + return CustomError.error(text: CoreLocalization.Error.authorizationFailed) + default: + return error + } } } diff --git a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift index a897830ec..faa6353c4 100644 --- a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift @@ -19,13 +19,39 @@ public final class FacebookSingInProvider { completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void) ) { loginManager.logIn( - permissions: [], + permissions: ["name", "email"], from: withPresenting, - handler: completion + handler: { [weak self] result, error in + + if result?.isCancelled == true { + completion(nil, CustomError.socialSignCanceled) + return + } + + if let error = error { + completion(nil, self?.failure(error)) + return + } + + if result?.authenticationToken == nil { + completion(nil, CustomError.error(text: CoreLocalization.Error.unknownError)) + return + } + + completion(result, nil) + } ) } public func signOut() { loginManager.logOut() } + + private func failure(_ error: Error?) -> Error { + if let error = error as? NSError, + let description = error.userInfo[ErrorLocalizedDescriptionKey] as? String { + return CustomError.error(text: description) + } + return error ?? CustomError.error(text: CoreLocalization.Error.unknownError) + } } diff --git a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift index e3d76a5fa..6f71a04cb 100644 --- a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift @@ -17,7 +17,9 @@ public final class GoogleSingInProvider { ) { GIDSignIn.sharedInstance.signIn( withPresenting: withPresenting, - completion: completion + completion: { result, error in + completion(result, error) + } ) } diff --git a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift index 0a1a053ba..4b019723c 100644 --- a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift @@ -31,7 +31,7 @@ public final class MicrosoftSingInProvider { guard let self = self else { return } guard let result = result, error == nil else { - completion(nil, nil, error) + completion(nil, nil, failure(error)) return } @@ -81,6 +81,14 @@ public final class MicrosoftSingInProvider { return account } + private func failure(_ error: Error?) -> Error { + if let error = error as? NSError, + let description = error.userInfo[MSALErrorDescriptionKey] as? String { + return CustomError.error(text: description) + } + return error ?? CustomError.error(text: CoreLocalization.Error.unknownError) + } + func getUser(completion: (MSALAccount) -> Void) { guard let user = result?.account else { return } completion(user) diff --git a/Core/Core/SwiftGen/Strings.swift b/Core/Core/SwiftGen/Strings.swift index fc1a73163..8485925d2 100644 --- a/Core/Core/SwiftGen/Strings.swift +++ b/Core/Core/SwiftGen/Strings.swift @@ -12,6 +12,8 @@ import Foundation public enum CoreLocalization { /// Done public static let done = CoreLocalization.tr("Localizable", "DONE", fallback: "Done") + /// The user canceled the sign-in flow. + public static let socialSignCanceled = CoreLocalization.tr("Localizable", "SOCIAL_SIGN_CANCELED", fallback: "The user canceled the sign-in flow.") public enum Alert { /// ACCEPT public static let accept = CoreLocalization.tr("Localizable", "ALERT.ACCEPT", fallback: "ACCEPT") @@ -93,6 +95,8 @@ public enum CoreLocalization { public static let downloaded = CoreLocalization.tr("Localizable", "DOWNLOAD_MANAGER.DOWNLOADED", fallback: "Downloaded") } public enum Error { + /// Authorization failed. + public static let authorizationFailed = CoreLocalization.tr("Localizable", "ERROR.AUTHORIZATION_FAILED", fallback: "Authorization failed.") /// Invalid credentials public static let invalidCredentials = CoreLocalization.tr("Localizable", "ERROR.INVALID_CREDENTIALS", fallback: "Invalid credentials") /// No cached data for offline mode diff --git a/Core/Core/View/Base/LabelButton.swift b/Core/Core/View/Base/LabelButton.swift index eda2e39f4..2a739af4b 100644 --- a/Core/Core/View/Base/LabelButton.swift +++ b/Core/Core/View/Base/LabelButton.swift @@ -9,8 +9,10 @@ import SwiftUI public struct LabelButton: View { - // MARK: - Properties - + // MARK: - Properties + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + private var image: Image private var title: String private var textColor: Color @@ -34,7 +36,7 @@ public struct LabelButton: View { self.action = action } - // MARK: - Views - + // MARK: - Views public var body: some View { Button { @@ -49,7 +51,7 @@ public struct LabelButton: View { image.padding(.leading, 10) } } - .frame(height: 44) + .frame(maxWidth: idiom == .pad ? 260: .infinity, minHeight: 42) .background(backgroundColor) .clipShape( RoundedRectangle(cornerRadius: cornerRadius) diff --git a/Core/Core/en.lproj/Localizable.strings b/Core/Core/en.lproj/Localizable.strings index 38ca23c6f..555ad1c6a 100644 --- a/Core/Core/en.lproj/Localizable.strings +++ b/Core/Core/en.lproj/Localizable.strings @@ -20,6 +20,7 @@ "ERROR.USER_NOT_ACTIVE" = "User account is not activated. Please activate your account first."; "ERROR.UNKNOWN_ERROR" = "Something went wrong"; "ERROR.WIFI" = "You can only download files over Wi-Fi. You can change this in the settings."; +"ERROR.AUTHORIZATION_FAILED" = "Authorization failed."; "COURSEWARE.COURSE_CONTENT" = "Course content"; "COURSEWARE.COURSE_UNITS" = "Course units"; @@ -92,3 +93,5 @@ "COURSE_DATES.UNRELEASED" = "Unreleased"; "COURSE_DATES.VERIFIED_ONLY" = "Verified Only"; +"SOCIAL_SIGN_CANCELED" = "The user canceled the sign-in flow."; + diff --git a/Core/Core/uk.lproj/Localizable.strings b/Core/Core/uk.lproj/Localizable.strings index e3a75f764..2d56ca83c 100644 --- a/Core/Core/uk.lproj/Localizable.strings +++ b/Core/Core/uk.lproj/Localizable.strings @@ -93,3 +93,5 @@ "COURSE_DATES.UNRELEASED" = "Unreleased"; "COURSE_DATES.VERIFIED_ONLY" = "Verified Only"; +"SOCIAL_SIGN_CANCELED" = "The user canceled the sign-in flow."; +"AUTHORIZATION_FAILED" = "Authorization failed."; From a69d9b85694ccc5bca3c185537a8794fef00d0d6 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Fri, 1 Dec 2023 14:43:30 +0100 Subject: [PATCH 22/38] chore: add not linked acc error when login by third party auth --- .../Presentation/Login/SignInViewModel.swift | 48 +++++++++---------- .../Authorization/SwiftGen/Strings.swift | 4 ++ .../en.lproj/Localizable.strings | 1 + .../uk.lproj/Localizable.strings | 1 + Core/Core/Configuration/Config/Config.swift | 7 ++- 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 65d07d496..7086a4977 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -76,19 +76,7 @@ public class SignInViewModel: ObservableObject { analytics.userLogin(method: .password) router.showMainOrWhatsNewScreen() } catch let error { - isShowProgress = false - if error.isUpdateRequeiredError { - router.showUpdateRequiredView(showAccountLink: false) - } else if let validationError = error.validationError, - let value = validationError.data?["error_description"] as? String { - errorMessage = value - } else if case APIError.invalidGrant = error { - errorMessage = CoreLocalization.Error.invalidCredentials - } else if error.isInternetError { - errorMessage = CoreLocalization.Error.slowOrNoInternetConnection - } else { - errorMessage = CoreLocalization.Error.unknownError - } + failure(error) } } @@ -163,18 +151,30 @@ public class SignInViewModel: ObservableObject { analytics.userLogin(method: loginMethod) router.showMainOrWhatsNewScreen() } catch let error { - isShowProgress = false - if let validationError = error.validationError, - let value = validationError.data?["error_description"] as? String { - errorMessage = value - } else if case APIError.invalidGrant = error { - errorMessage = CoreLocalization.Error.invalidCredentials - } else if error.isInternetError { - errorMessage = CoreLocalization.Error.slowOrNoInternetConnection - } else { - errorMessage = CoreLocalization.Error.unknownError - } + failure(error, loginMethod: loginMethod) + } + } + } + + @MainActor + private func failure(_ error: Error, loginMethod: LoginMethod? = nil) { + isShowProgress = false + if let validationError = error.validationError, + let value = validationError.data?["error_description"] as? String { + if validationError.statusCode == 401, let loginMethod = loginMethod { + errorMessage = AuthLocalization.Error.authProvider( + loginMethod.rawValue, + config.platformName + ) + } else { + errorMessage = value } + } else if case APIError.invalidGrant = error { + errorMessage = CoreLocalization.Error.invalidCredentials + } else if error.isInternetError { + errorMessage = CoreLocalization.Error.slowOrNoInternetConnection + } else { + errorMessage = CoreLocalization.Error.unknownError } } diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index de2e2298a..361415fd6 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -25,6 +25,10 @@ public enum AuthLocalization { /// Sign in with public static let signInWith = AuthLocalization.tr("Localizable", "SIGN_IN_WITH", fallback: "Sign in with") public enum Error { + /// This %@ account is not linked with any %@ account. Please register. + public static func authProvider(_ p1: Any, _ p2: Any) -> String { + return AuthLocalization.tr("Localizable", "ERROR.AUTH_PROVIDER", String(describing: p1), String(describing: p2), fallback: "This %@ account is not linked with any %@ account. Please register.") + } /// Invalid email address public static let invalidEmailAddress = AuthLocalization.tr("Localizable", "ERROR.INVALID_EMAIL_ADDRESS", fallback: "Invalid email address") /// Invalid password lenght diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index 92e74fcfc..0648ef099 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -17,6 +17,7 @@ "ERROR.INVALID_EMAIL_ADDRESS" = "Invalid email address"; "ERROR.INVALID_PASSWORD_LENGHT" = "Invalid password lenght"; +"ERROR.AUTH_PROVIDER" = "This %@ account is not linked with any %@ account. Please register."; "SIGN_UP.TITLE" = "Sign up"; "SIGN_UP.SUBTITLE" = "Create new account."; diff --git a/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings index a1f1bf6a5..9d689a244 100644 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ b/Authorization/Authorization/uk.lproj/Localizable.strings @@ -16,6 +16,7 @@ "ERROR.INVALID_EMAIL_ADDRESS" = "невірна адреса електронної пошти"; "ERROR.INVALID_PASSWORD_LENGHT" = "Пароль занадто короткий або занадто довгий"; +"ERROR.AUTH_PROVIDER" = "This %@ account is not linked with any %@ account. Please register."; "SIGN_UP.TITLE" = "Зареєструватись"; "SIGN_UP.SUBTITLE" = "Cтворити новий акаунт."; diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 84fccabad..027a3925d 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -13,6 +13,7 @@ public protocol ConfigProtocol { var tokenType: TokenType { get } var feedbackEmail: String { get } var appStoreLink: String { get } + var platformName: String { get } var socialLoginEnabled: Bool { get } var agreement: AgreementConfig { get } var firebase: FirebaseConfig { get } @@ -124,7 +125,11 @@ extension Config: ConfigProtocol { public var feedbackEmail: String { return string(for: ConfigKeys.feedbackEmailAddress.rawValue) ?? "" } - + + public var platformName: String { + return string(for: ConfigKeys.platformName.rawValue) ?? "" + } + private var appStoreId: String { return string(for: ConfigKeys.appstoreID.rawValue) ?? "0000000000" } From f2c960f304426e391e8f5b2b2ce40e28e29c46db Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Mon, 4 Dec 2023 10:52:05 +0100 Subject: [PATCH 23/38] chore: add same canceled error --- .../AvoidingHelpers/Providers/GoogleSingInProvider.swift | 4 ++++ .../Providers/MicrosoftSingInProvider.swift | 3 +++ OpenEdX/Info.plist | 8 ++++---- OpenEdX/OpenEdX.entitlements | 6 +++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift index 6f71a04cb..6bf793feb 100644 --- a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift @@ -18,6 +18,10 @@ public final class GoogleSingInProvider { GIDSignIn.sharedInstance.signIn( withPresenting: withPresenting, completion: { result, error in + if let error = error as? NSError, error.code == GIDSignInError.canceled.rawValue { + completion(result, CustomError.socialSignCanceled) + return + } completion(result, error) } ) diff --git a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift index 4b019723c..856ca1ccf 100644 --- a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift @@ -84,6 +84,9 @@ public final class MicrosoftSingInProvider { private func failure(_ error: Error?) -> Error { if let error = error as? NSError, let description = error.userInfo[MSALErrorDescriptionKey] as? String { + if let errorCode = MSALError(rawValue: error.code), case .userCanceled = errorCode { + return CustomError.socialSignCanceled + } return CustomError.error(text: description) } return error ?? CustomError.error(text: CoreLocalization.Error.unknownError) diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index 452ba08ae..e4bdcba95 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -10,10 +10,6 @@ ITSAppUsesNonExemptEncryption - UIAppFonts - - UIViewControllerBasedStatusBarAppearance - LSApplicationQueriesSchemes googlegmail @@ -24,5 +20,9 @@ fastmail protonmail + UIAppFonts + + UIViewControllerBasedStatusBarAppearance + diff --git a/OpenEdX/OpenEdX.entitlements b/OpenEdX/OpenEdX.entitlements index 74c85d2ed..80b5221de 100644 --- a/OpenEdX/OpenEdX.entitlements +++ b/OpenEdX/OpenEdX.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.developer.applesignin + + Default + - \ No newline at end of file + From ab752e1e8a10b78c5fe139df919749aae36f43d5 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Mon, 4 Dec 2023 16:00:52 +0100 Subject: [PATCH 24/38] chore: add error if not register by social auth --- .../Authorization/Presentation/Login/SignInViewModel.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index cf1059514..2de1546f2 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -151,17 +151,17 @@ public class SignInViewModel: ObservableObject { analytics.userLogin(method: loginMethod) router.showMainOrWhatsNewScreen() } catch let error { - failure(error, loginMethod: loginMethod) + failure(error, loginMethod: loginMethod, isExternalToken: true) } } } @MainActor - private func failure(_ error: Error, loginMethod: LoginMethod? = nil) { + private func failure(_ error: Error, loginMethod: LoginMethod? = nil, isExternalToken: Bool = false) { isShowProgress = false if let validationError = error.validationError, let value = validationError.data?["error_description"] as? String { - if validationError.statusCode == 401, let loginMethod = loginMethod { + if isExternalToken, validationError.statusCode == 400, let loginMethod = loginMethod { errorMessage = AuthLocalization.Error.authProvider( loginMethod.rawValue, config.platformName From 9085988f5874822c4dd8461737cc5548f1246097 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 5 Dec 2023 11:14:00 +0100 Subject: [PATCH 25/38] fix: fix msal lib --- Core/Core.xcodeproj/project.pbxproj | 17 ----------------- .../Providers/FacebookSingInProvider.swift | 2 +- Podfile | 1 + Podfile.lock | 8 +++++++- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 1d5fc5b63..e9217e0a5 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -127,7 +127,6 @@ BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; BA8FA66E2AD59E7D00EA029A /* FacebookSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */; }; BA8FA6702AD59EA300EA029A /* MicrosoftSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */; }; - BADB3F532AD6B3A5004D5CFA /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BADB3F522AD6B3A5004D5CFA /* MSAL */; }; BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; @@ -312,7 +311,6 @@ BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */, 025EF2F62971740000B838AB /* YouTubePlayerKit in Frameworks */, C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */, - BADB3F532AD6B3A5004D5CFA /* MSAL in Frameworks */, BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -765,7 +763,6 @@ 025EF2F52971740000B838AB /* YouTubePlayerKit */, BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */, BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */, - BADB3F522AD6B3A5004D5CFA /* MSAL */, ); productName = Core; productReference = 0770DE0828D07831006D8A5D /* Core.framework */; @@ -805,7 +802,6 @@ 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, - BADB3F512AD6B3A5004D5CFA /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, ); productRefGroup = 0770DE0928D07831006D8A5D /* Products */; projectDirPath = ""; @@ -2056,14 +2052,6 @@ minimumVersion = 14.1.0; }; }; - BADB3F512AD6B3A5004D5CFA /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/AzureAD/microsoft-authentication-library-for-objc"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.2.17; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -2077,11 +2065,6 @@ package = BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; productName = GoogleSignIn; }; - BADB3F522AD6B3A5004D5CFA /* MSAL */ = { - isa = XCSwiftPackageProductDependency; - package = BADB3F512AD6B3A5004D5CFA /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */; - productName = MSAL; - }; BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */ = { isa = XCSwiftPackageProductDependency; package = BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; diff --git a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift index faa6353c4..a9f112898 100644 --- a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift @@ -19,7 +19,7 @@ public final class FacebookSingInProvider { completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void) ) { loginManager.logIn( - permissions: ["name", "email"], + permissions: [], from: withPresenting, handler: { [weak self] result, error in diff --git a/Podfile b/Podfile index c3658683d..bca6941be 100644 --- a/Podfile +++ b/Podfile @@ -28,6 +28,7 @@ abstract_target "App" do pod 'SwiftUIIntrospect', '~> 0.8' pod 'Kingfisher', '~> 7.8' pod 'Swinject', '2.8.3' + pod 'MSAL' end target "Authorization" do diff --git a/Podfile.lock b/Podfile.lock index 4bcb22ab6..ebf1bef4a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -92,6 +92,9 @@ PODS: - GoogleUtilities/Logger - KeychainSwift (20.0.0) - Kingfisher (7.9.1) + - MSAL (1.2.18): + - MSAL/app-lib (= 1.2.18) + - MSAL/app-lib (1.2.18) - nanopb (2.30909.0): - nanopb/decode (= 2.30909.0) - nanopb/encode (= 2.30909.0) @@ -116,6 +119,7 @@ DEPENDENCIES: - FirebaseCrashlytics (~> 10.11) - KeychainSwift (~> 20.0) - Kingfisher (~> 7.8) + - MSAL - SwiftGen (~> 6.6) - SwiftLint (~> 0.5) - SwiftUIIntrospect (~> 0.8) @@ -137,6 +141,7 @@ SPEC REPOS: - GoogleUtilities - KeychainSwift - Kingfisher + - MSAL - nanopb - PromisesObjC - PromisesSwift @@ -170,6 +175,7 @@ SPEC CHECKSUMS: GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 KeychainSwift: 0ce6a4d13f7228054d1a71bb1b500448fb2ab837 Kingfisher: 1d14e9f59cbe19389f591c929000332bf70efd32 + MSAL: 75402959845e55146583efbf37ff4ad90b408630 nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 @@ -180,6 +186,6 @@ SPEC CHECKSUMS: SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 -PODFILE CHECKSUM: a44d8de5a5803eb3e3c995134c79c3dad959dbf7 +PODFILE CHECKSUM: ff9a74b7d6d1adcb3a5648e20f9cd9d26c4db3dc COCOAPODS: 1.13.0 From 9397651c8e238286b429ea9760d0ba8859178a56 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 5 Dec 2023 12:59:15 +0100 Subject: [PATCH 26/38] chore: login if can when register if social provider and add name email to field after apple sign in --- .../Registration/SignUpViewModel.swift | 116 +++++++++++++----- 1 file changed, 87 insertions(+), 29 deletions(-) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 4ba6e97d8..dfb9f6260 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -99,19 +99,7 @@ public class SignUpViewModel: ObservableObject { @MainActor func registerUser() async { do { - var validateFields: [String: String] = [:] - fields.forEach { validateFields[$0.field.name] = $0.text } - validateFields["honor_code"] = "true" - validateFields["terms_of_service"] = "true" - if let externalToken = externalToken, let backend = backend { - validateFields["access_token"] = externalToken - validateFields["provider"] = backend - validateFields["client_id"] = config.oAuthClientId - if validateFields.contains(where: {$0.key == "password"}) { - validateFields.removeValue(forKey: "password") - } - fields.removeAll { $0.field.type == .password } - } + let validateFields = configureFields() let errors = try await interactor.validateRegistrationFields(fields: validateFields) guard !showErrors(errors: errors) else { return } isShowProgress = true @@ -136,6 +124,23 @@ public class SignUpViewModel: ObservableObject { } } + private func configureFields() -> [String: String] { + var validateFields: [String: String] = [:] + fields.forEach { validateFields[$0.field.name] = $0.text } + validateFields["honor_code"] = "true" + validateFields["terms_of_service"] = "true" + if let externalToken = externalToken, let backend = backend { + validateFields["access_token"] = externalToken + validateFields["provider"] = backend + validateFields["client_id"] = config.oAuthClientId + if validateFields.contains(where: {$0.key == "password"}) { + validateFields.removeValue(forKey: "password") + } + fields.removeAll { $0.field.type == .password } + } + return validateFields + } + @MainActor func register(with result: Result) { result.success(social) @@ -148,26 +153,39 @@ public class SignUpViewModel: ObservableObject { private func social(result: SocialResult) { switch result { case .apple(let appleCredentials): - appleLogin(appleCredentials, backend: result.backend) + appleLogin(appleCredentials, backend: result.backend, loginMethod: .apple) case .facebook(let account): - facebookLogin(backend: result.backend, account: account) + facebookLogin(backend: result.backend, account: account, loginMethod: .facebook) case .google(let gIDSignInResult): - googleLogin(gIDSignInResult, backend: result.backend) + googleLogin(gIDSignInResult, backend: result.backend, loginMethod: .google) case .microsoft(let account, let token): - microsoftLogin(token, backend: result.backend, account: account) + microsoftLogin(token, backend: result.backend, account: account, loginMethod: .microsoft) } } @MainActor - private func appleLogin(_ credentials: AppleCredentials, backend: String) { + private func appleLogin( + _ credentials: AppleCredentials, + backend: String, + loginMethod: LoginMethod + ) { + update( + fullName: credentials.name, + email: credentials.email + ) registerSocial( externalToken: credentials.token, - backend: backend + backend: backend, + loginMethod: loginMethod ) } @MainActor - private func facebookLogin(backend: String, account: LoginManagerLoginResult) { + private func facebookLogin( + backend: String, + account: LoginManagerLoginResult, + loginMethod: LoginMethod + ) { guard let currentAccessToken = AccessToken.current?.tokenString else { return } @@ -187,13 +205,18 @@ public class SignUpViewModel: ObservableObject { registerSocial( externalToken: currentAccessToken, - backend: backend + backend: backend, + loginMethod: loginMethod ) } @MainActor - private func googleLogin(_ result: GIDSignInResult, backend: String) { + private func googleLogin( + _ result: GIDSignInResult, + backend: String, + loginMethod: LoginMethod + ) { update( fullName: result.user.profile?.name, email: result.user.profile?.email @@ -201,12 +224,18 @@ public class SignUpViewModel: ObservableObject { registerSocial( externalToken: result.user.accessToken.tokenString, - backend: backend + backend: backend, + loginMethod: loginMethod ) } @MainActor - private func microsoftLogin(_ token: String, backend: String, account: MSALAccount) { + private func microsoftLogin( + _ token: String, + backend: String, + account: MSALAccount, + loginMethod: LoginMethod + ) { update( fullName: account.accountClaims?["name"] as? String, email: account.accountClaims?["email"] as? String @@ -214,16 +243,45 @@ public class SignUpViewModel: ObservableObject { registerSocial( externalToken: token, - backend: backend + backend: backend, + loginMethod: loginMethod ) } @MainActor - private func registerSocial(externalToken: String, backend: String) { - self.externalToken = externalToken - self.backend = backend - isThirdPartyAuthSuccess = true + private func registerSocial( + externalToken: String, + backend: String, + loginMethod: LoginMethod + ) { Task { + await loginOrRegister( + externalToken: externalToken, + backend: backend, + loginMethod: loginMethod + ) + } + } + + @MainActor + private func loginOrRegister( + externalToken: String, + backend: String, + loginMethod: LoginMethod + ) async { + do { + isShowProgress = true + let validateFields = configureFields().filter { !$0.value.isEmpty } + let user = try await interactor.login(externalToken: externalToken, backend: backend) + analytics.setUserID("\(user.id)") + analytics.userLogin(method: loginMethod) + isShowProgress = false + router.showMainOrWhatsNewScreen() + } catch { + self.externalToken = externalToken + self.backend = backend + isThirdPartyAuthSuccess = true + isShowProgress = false await registerUser() } } From d7dfc24a602259ca91a7006f8ee0a54fcb3fbc39 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 5 Dec 2023 15:20:03 +0100 Subject: [PATCH 27/38] chore: save full name after login with apple --- .../Providers/AppleSingInProvider.swift | 24 +++++++++-- Core/Core/Data/CoreStorage.swift | 6 +++ OpenEdX/Data/AppStorage.swift | 43 +++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift index 68d14fd90..a03fb28de 100644 --- a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift +++ b/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift @@ -7,6 +7,7 @@ import Foundation import AuthenticationServices +import Swinject public struct AppleCredentials: Codable { public var name: String @@ -42,10 +43,25 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg return } - let firstName = credentials.fullName?.givenName ?? "" - let lastName = credentials.fullName?.familyName ?? "" - let email = credentials.email ?? "" - let name = "\(firstName) \(lastName)" + var storage = Container.shared.resolve(CoreStorage.self) + + let givenName = credentials.fullName?.givenName ?? storage?.appleSignGivenName ?? "" + let familyName = credentials.fullName?.familyName ?? storage?.appleSignFamilyName ?? "" + + let email = credentials.email ?? storage?.appleSignEmail ?? "" + var name: String = "\(givenName) \(familyName)" + + if storage?.appleSignFamilyName == nil, !familyName.isEmpty { + storage?.appleSignFamilyName = familyName + } + + if storage?.appleSignGivenName == nil, !givenName.isEmpty { + storage?.appleSignGivenName = givenName + } + + if storage?.appleSignEmail == nil { + storage?.appleSignEmail = email + } guard let data = credentials.identityToken, let code = String(data: data, encoding: .utf8) else { diff --git a/Core/Core/Data/CoreStorage.swift b/Core/Core/Data/CoreStorage.swift index a2d14dc86..37eecfa19 100644 --- a/Core/Core/Data/CoreStorage.swift +++ b/Core/Core/Data/CoreStorage.swift @@ -10,6 +10,9 @@ import Foundation public protocol CoreStorage { var accessToken: String? {get set} var refreshToken: String? {get set} + var appleSignGivenName: String? {get set} + var appleSignFamilyName: String? {get set} + var appleSignEmail: String? {get set} var cookiesDate: String? {get set} var reviewLastShownVersion: String? {get set} var lastReviewDate: Date? {get set} @@ -22,6 +25,9 @@ public protocol CoreStorage { public class CoreStorageMock: CoreStorage { public var accessToken: String? public var refreshToken: String? + public var appleSignGivenName: String? + public var appleSignFamilyName: String? + public var appleSignEmail: String? public var cookiesDate: String? public var reviewLastShownVersion: String? public var lastReviewDate: Date? diff --git a/OpenEdX/Data/AppStorage.swift b/OpenEdX/Data/AppStorage.swift index 4bbb43eba..1a8cf3cc3 100644 --- a/OpenEdX/Data/AppStorage.swift +++ b/OpenEdX/Data/AppStorage.swift @@ -76,6 +76,46 @@ public class AppStorage: CoreStorage, ProfileStorage, WhatsNewStorage { } } + public var appleSignGivenName: String? { + get { + return keychain.get(KEY_APPLE_SIGN_GIVENNAME) + } + set(newValue) { + if let newValue { + keychain.set(newValue, forKey: KEY_APPLE_SIGN_GIVENNAME) + } else { + keychain.delete(KEY_APPLE_SIGN_GIVENNAME) + } + } + } + + public var appleSignFamilyName: String? { + get { + return keychain.get(KEY_APPLE_SIGN_FAMILYNAME) + } + set(newValue) { + if let newValue { + keychain.set(newValue, forKey: KEY_APPLE_SIGN_FAMILYNAME) + } else { + keychain.delete(KEY_APPLE_SIGN_FAMILYNAME) + } + } + } + + + public var appleSignEmail: String? { + get { + return keychain.get(KEY_APPLE_SIGN_EMAIL) + } + set(newValue) { + if let newValue { + keychain.set(newValue, forKey: KEY_APPLE_SIGN_EMAIL) + } else { + keychain.delete(KEY_APPLE_SIGN_EMAIL) + } + } + } + public var cookiesDate: String? { get { return userDefaults.string(forKey: KEY_COOKIES_DATE) @@ -180,4 +220,7 @@ public class AppStorage: CoreStorage, ProfileStorage, WhatsNewStorage { private let KEY_REVIEW_LAST_SHOWN_VERSION = "reviewLastShownVersion" private let KEY_REVIEW_LAST_REVIEW_DATE = "lastReviewDate" private let KEY_WHATSNEW_VERSION = "whatsNewVersion" + private let KEY_APPLE_SIGN_GIVENNAME = "appleSignGivenName" + private let KEY_APPLE_SIGN_FAMILYNAME = "appleSignFamilyName" + private let KEY_APPLE_SIGN_EMAIL = "appleSignEmail" } From 05cd91b084fa36d0dd9de850527c6df1a7e82978 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Tue, 5 Dec 2023 16:32:45 +0100 Subject: [PATCH 28/38] chore: add microsoft to package --- OpenEdX.xcodeproj/project.pbxproj | 27 +++++++++++++++++++++++++++ Podfile | 1 - Podfile.lock | 8 +------- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 9df9dfe4a..072473af5 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 07D5DA3E28D075AB00752FD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07D5DA3D28D075AB00752FD9 /* Assets.xcassets */; }; 07D5DA4128D075AB00752FD9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 07D5DA3F28D075AB00752FD9 /* LaunchScreen.storyboard */; }; 95C140F3BDF778364986E83B /* Pods_App_OpenEdX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F138C15C3A2515F8F94DAA8B /* Pods_App_OpenEdX.framework */; }; + BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -130,6 +131,7 @@ 028A37362ADFF404008CA604 /* WhatsNew.framework in Frameworks */, 025DE1A428DB4DAE0053E0F4 /* Profile.framework in Frameworks */, 0770DE4B28D0A462006D8A5D /* Authorization.framework in Frameworks */, + BA3042792B1F7147009B64B7 /* MSAL in Frameworks */, 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */, 0218196428F734FA00202564 /* Discussion.framework in Frameworks */, 0219C67728F4347600D64452 /* Course.framework in Frameworks */, @@ -267,6 +269,9 @@ dependencies = ( ); name = OpenEdX; + packageProductDependencies = ( + BA3042782B1F7147009B64B7 /* MSAL */, + ); productName = OpenEdX; productReference = 07D5DA3128D075AA00752FD9 /* OpenEdX.app */; productType = "com.apple.product-type.application"; @@ -296,6 +301,9 @@ uk, ); mainGroup = 07D5DA2828D075AA00752FD9; + packageReferences = ( + BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, + ); productRefGroup = 07D5DA3228D075AA00752FD9 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1041,6 +1049,25 @@ defaultConfigurationName = ReleaseProd; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/AzureAD/microsoft-authentication-library-for-objc"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.2.19; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + BA3042782B1F7147009B64B7 /* MSAL */ = { + isa = XCSwiftPackageProductDependency; + package = BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */; + productName = MSAL; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 07D5DA2928D075AA00752FD9 /* Project object */; } diff --git a/Podfile b/Podfile index f2995e30a..291bcb28e 100644 --- a/Podfile +++ b/Podfile @@ -28,7 +28,6 @@ abstract_target "App" do pod 'SwiftUIIntrospect', '~> 0.8' pod 'Kingfisher', '~> 7.8' pod 'Swinject', '2.8.3' - pod 'MSAL' end target "Authorization" do diff --git a/Podfile.lock b/Podfile.lock index 4683a3215..d9169ef4b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -92,9 +92,6 @@ PODS: - GoogleUtilities/Logger - KeychainSwift (20.0.0) - Kingfisher (7.9.1) - - MSAL (1.2.18): - - MSAL/app-lib (= 1.2.18) - - MSAL/app-lib (1.2.18) - nanopb (2.30909.0): - nanopb/decode (= 2.30909.0) - nanopb/encode (= 2.30909.0) @@ -119,7 +116,6 @@ DEPENDENCIES: - FirebaseCrashlytics (~> 10.11) - KeychainSwift (~> 20.0) - Kingfisher (~> 7.8) - - MSAL - SwiftGen (~> 6.6) - SwiftLint (~> 0.5) - SwiftUIIntrospect (~> 0.8) @@ -141,7 +137,6 @@ SPEC REPOS: - GoogleUtilities - KeychainSwift - Kingfisher - - MSAL - nanopb - PromisesObjC - PromisesSwift @@ -175,7 +170,6 @@ SPEC CHECKSUMS: GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 KeychainSwift: 0ce6a4d13f7228054d1a71bb1b500448fb2ab837 Kingfisher: 1d14e9f59cbe19389f591c929000332bf70efd32 - MSAL: 75402959845e55146583efbf37ff4ad90b408630 nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 @@ -186,6 +180,6 @@ SPEC CHECKSUMS: SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 -PODFILE CHECKSUM: bf3be33bc84295eef4c6b35a1df641d28437eec6 +PODFILE CHECKSUM: 544edab2f9ecc4ac18973fb8865f1d0613ec8a28 COCOAPODS: 1.13.0 From c7a01c6531b20ee5c1df0859ade44534b0c14baa Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 6 Dec 2023 14:56:45 +0100 Subject: [PATCH 29/38] chore: changes from PR feedback --- .../Authorization.xcodeproj/project.pbxproj | 26 +-- .../Presentation/AuthorizationAnalytics.swift | 19 ++- .../Presentation/Login/SignInView.swift | 2 +- .../Presentation/Login/SignInViewModel.swift | 22 +-- .../Registration/SignUpView.swift | 11 +- .../Registration/SignUpViewModel.swift | 24 +-- .../SocialAuthView.swift} | 42 ++--- .../SocialAuth/SocialAuthViewModel.swift | 134 ++++++++++++++++ .../SocialSign/SocialSignViewModel.swift | 150 ------------------ .../Authorization/SwiftGen/Strings.swift | 6 +- .../en.lproj/Localizable.strings | 5 +- .../uk.lproj/Localizable.strings | 5 +- Core/Core.xcodeproj/project.pbxproj | 66 ++++---- .../Providers/FacebookSingInProvider.swift | 57 ------- .../Providers/GoogleSingInProvider.swift | 33 ---- .../Providers/MicrosoftSingInProvider.swift | 100 ------------ .../Configuration/Config/FacebookConfig.swift | 6 +- .../Configuration/Config/GoogleConfig.swift | 6 +- .../Config/MicrosoftConfig.swift | 4 +- Core/Core/Data/CoreStorage.swift | 6 +- .../Core/Data/Repository/AuthRepository.swift | 2 +- Core/Core/Extensions/CGColorExtension.swift | 6 - Core/Core/Network/AuthEndpoint.swift | 10 +- .../SocialAuth/AppleAuthProvider.swift} | 34 ++-- .../SocialAuth/Error/SocialAuthError.swift} | 8 +- .../SocialAuth/FacebookAuthProvider.swift | 63 ++++++++ .../SocialAuth/GoogleAuthProvider.swift | 44 +++++ .../SocialAuth/MicrosoftAuthProvider.swift | 109 +++++++++++++ ...belButton.swift => SocialAuthButton.swift} | 4 +- OpenEdX/AnalyticsManager.swift | 2 +- OpenEdX/AppDelegate.swift | 1 - OpenEdX/Data/AppStorage.swift | 25 +-- 32 files changed, 522 insertions(+), 510 deletions(-) rename Authorization/Authorization/Presentation/{SocialSign/SocialSignView.swift => SocialAuth/SocialAuthView.swift} (73%) create mode 100644 Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift delete mode 100644 Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift delete mode 100644 Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift delete mode 100644 Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift delete mode 100644 Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift rename Core/Core/{AvoidingHelpers/Providers/AppleSingInProvider.swift => Providers/SocialAuth/AppleAuthProvider.swift} (71%) rename Core/Core/{AvoidingHelpers/Error/CustomError.swift => Providers/SocialAuth/Error/SocialAuthError.swift} (75%) create mode 100644 Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift create mode 100644 Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift create mode 100644 Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift rename Core/Core/View/Base/{LabelButton.swift => SocialAuthButton.swift} (96%) diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index d613a7634..8c3380cd1 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -26,8 +26,8 @@ 0770DE6B28D0C035006D8A5D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0770DE6D28D0C035006D8A5D /* Localizable.strings */; }; 0770DE7128D0C0E7006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7028D0C0E7006D8A5D /* Strings.swift */; }; 5FB79D2802949372CDAF08D6 /* Pods_App_Authorization_AuthorizationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FAE9B7FD61FF88C9C4FE1E8 /* Pods_App_Authorization_AuthorizationTests.framework */; }; - BA8B3A322AD5487300D25EF5 /* SocialSignView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A312AD5487300D25EF5 /* SocialSignView.swift */; }; - BADB3F552AD6DFC3004D5CFA /* SocialSignViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F542AD6DFC3004D5CFA /* SocialSignViewModel.swift */; }; + BA8B3A322AD5487300D25EF5 /* SocialAuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */; }; + BADB3F552AD6DFC3004D5CFA /* SocialAuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F542AD6DFC3004D5CFA /* SocialAuthViewModel.swift */; }; DE843D6BB1B9DDA398494890 /* Pods_App_Authorization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47BCFB7C19382EECF15131B6 /* Pods_App_Authorization.framework */; }; E03261642AE64676002CA7EB /* StartupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E03261632AE64676002CA7EB /* StartupViewModel.swift */; }; E03261662AE64AF4002CA7EB /* StartupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E03261652AE64AF4002CA7EB /* StartupView.swift */; }; @@ -80,8 +80,8 @@ 96C85172770225EB81A6D2DA /* Pods-App-Authorization.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Authorization/Pods-App-Authorization.releasedev.xcconfig"; sourceTree = ""; }; 9BF6A1004A955E24527FCF0F /* Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.releaseprod.xcconfig"; sourceTree = ""; }; A99D45203C981893C104053A /* Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Authorization-AuthorizationTests/Pods-App-Authorization-AuthorizationTests.releasestage.xcconfig"; sourceTree = ""; }; - BA8B3A312AD5487300D25EF5 /* SocialSignView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialSignView.swift; sourceTree = ""; }; - BADB3F542AD6DFC3004D5CFA /* SocialSignViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialSignViewModel.swift; sourceTree = ""; }; + BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthView.swift; sourceTree = ""; }; + BADB3F542AD6DFC3004D5CFA /* SocialAuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthViewModel.swift; sourceTree = ""; }; E03261632AE64676002CA7EB /* StartupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupViewModel.swift; sourceTree = ""; }; E03261652AE64AF4002CA7EB /* StartupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupView.swift; sourceTree = ""; }; E03261672AE9F156002CA7EB /* LogistrationBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogistrationBottomView.swift; sourceTree = ""; }; @@ -149,7 +149,7 @@ 071009CC28D1E24000344290 /* Presentation */ = { isa = PBXGroup; children = ( - BA8B3A302AD5485100D25EF5 /* SocialSign */, + BA8B3A302AD5485100D25EF5 /* SocialAuth */, E03261622AE6464A002CA7EB /* Startup */, 020C31BD290AADA700D6DEA2 /* Base */, 071009C528D1D9FA00344290 /* Login */, @@ -270,15 +270,15 @@ path = ../Pods; sourceTree = ""; }; - BA8B3A302AD5485100D25EF5 /* SocialSign */ = { + BA8B3A302AD5485100D25EF5 /* SocialAuth */ = { isa = PBXGroup; children = ( - BA8B3A312AD5487300D25EF5 /* SocialSignView.swift */, - BADB3F542AD6DFC3004D5CFA /* SocialSignViewModel.swift */, + BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */, + BADB3F542AD6DFC3004D5CFA /* SocialAuthViewModel.swift */, ); - path = SocialSign; - sourceTree = ""; - }; + path = SocialAuth; + sourceTree = ""; + }; E03261622AE6464A002CA7EB /* Startup */ = { isa = PBXGroup; children = ( @@ -498,7 +498,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BADB3F552AD6DFC3004D5CFA /* SocialSignViewModel.swift in Sources */, + BADB3F552AD6DFC3004D5CFA /* SocialAuthViewModel.swift in Sources */, 02066B442906D72400F4307E /* SignUpView.swift in Sources */, 0770DE7128D0C0E7006D8A5D /* Strings.swift in Sources */, 025F40E229D360E20064C183 /* ResetPasswordViewModel.swift in Sources */, @@ -512,7 +512,7 @@ 02F3BFE5292533720051930C /* AuthorizationRouter.swift in Sources */, E03261662AE64AF4002CA7EB /* StartupView.swift in Sources */, 071009C728D1DA4F00344290 /* SignInViewModel.swift in Sources */, - BA8B3A322AD5487300D25EF5 /* SocialSignView.swift in Sources */, + BA8B3A322AD5487300D25EF5 /* SocialAuthView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift index 67156cd4f..13ea220ab 100644 --- a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift +++ b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift @@ -7,12 +7,25 @@ import Foundation -public enum LoginMethod: String { - case apple = "Apple" - case password = "Password" +public enum LoginMethod: Equatable { + case password + case socailAuth(SocialAuthMethod) + + public var analyticsValue: String { + switch self { + case .password: + "Password" + case .socailAuth(let socialAuthMethod): + socialAuthMethod.rawValue + } + } +} + +public enum SocialAuthMethod: String { case facebook = "Facebook" case google = "Google" case microsoft = "Microsoft" + case apple = "Apple" } //sourcery: AutoMockable diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 67dcf2dae..d96ad5d4d 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -130,7 +130,7 @@ public struct SignInView: View { } } if viewModel.socialLoginEnabled { - SocialSignView( + SocialAuthView( viewModel: .init( config: viewModel.config, completion: { viewModel.sign(with: $0) } diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 2de1546f2..232040c94 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -81,7 +81,7 @@ public class SignInViewModel: ObservableObject { } @MainActor - func sign(with result: Result) { + func sign(with result: Result) { result.success { social(result: $0) } result.failure { error in errorMessage = error.localizedDescription @@ -89,7 +89,7 @@ public class SignInViewModel: ObservableObject { } @MainActor - private func social(result: SocialResult) { + private func social(result: SocialAuthDetails) { switch result { case .apple(let appleCredentials): appleLogin(appleCredentials, backend: result.backend) @@ -107,7 +107,7 @@ public class SignInViewModel: ObservableObject { socialLogin( externalToken: credentials.token, backend: backend, - loginMethod: .apple + loginMethod: .socailAuth(.apple) ) } @@ -119,7 +119,7 @@ public class SignInViewModel: ObservableObject { socialLogin( externalToken: currentAccessToken, backend: backend, - loginMethod: .facebook + loginMethod: .socailAuth(.facebook) ) } @@ -128,7 +128,7 @@ public class SignInViewModel: ObservableObject { socialLogin( externalToken: result.user.accessToken.tokenString, backend: backend, - loginMethod: .google + loginMethod: .socailAuth(.google) ) } @@ -137,7 +137,7 @@ public class SignInViewModel: ObservableObject { socialLogin( externalToken: token, backend: backend, - loginMethod: .microsoft + loginMethod: .socailAuth(.microsoft) ) } @@ -151,21 +151,23 @@ public class SignInViewModel: ObservableObject { analytics.userLogin(method: loginMethod) router.showMainOrWhatsNewScreen() } catch let error { - failure(error, loginMethod: loginMethod, isExternalToken: true) + failure(error, loginMethod: loginMethod) } } } @MainActor - private func failure(_ error: Error, loginMethod: LoginMethod? = nil, isExternalToken: Bool = false) { + private func failure(_ error: Error, loginMethod: LoginMethod? = nil) { isShowProgress = false if let validationError = error.validationError, let value = validationError.data?["error_description"] as? String { - if isExternalToken, validationError.statusCode == 400, let loginMethod = loginMethod { + if loginMethod != .password, validationError.statusCode == 400, let loginMethod = loginMethod { errorMessage = AuthLocalization.Error.authProvider( - loginMethod.rawValue, + loginMethod.analyticsValue, config.platformName ) + } else if validationError.statusCode == 403 { + errorMessage = AuthLocalization.Error.accountDisabled } else { errorMessage = value } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index 9a2f8a728..4e5cdba68 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -69,10 +69,10 @@ public struct SignUpView: View { .padding(.bottom, 20) if viewModel.isThirdPartyAuthSuccess { - Text(AuthLocalization.SignUp.successSignedinLabel) + Text(AuthLocalization.SignUp.successSigninLabel) .font(Theme.Fonts.titleMedium) .foregroundColor(Theme.Colors.textPrimary) - Text(AuthLocalization.SignUp.successSignedinSublabel) + Text(AuthLocalization.SignUp.successSigninSublabel) .font(Theme.Fonts.titleSmall) .foregroundColor(Theme.Colors.textSecondary) .padding(.bottom, 20) @@ -119,12 +119,11 @@ public struct SignUpView: View { } if viewModel.socialLoginEnabled, !requiredFields.isEmpty { - SocialSignView( + SocialAuthView( signType: .register, viewModel: .init( - config: viewModel.config, - completion: { viewModel.register(with: $0) } - ) + config: viewModel.config + ) { viewModel.register(with: $0) } ) .padding(.bottom, 30) } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index dfb9f6260..bce5e9b43 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -142,31 +142,31 @@ public class SignUpViewModel: ObservableObject { } @MainActor - func register(with result: Result) { - result.success(social) + func register(with result: Result) { + result.success(socialAuth) result.failure { error in errorMessage = error.localizedDescription } } @MainActor - private func social(result: SocialResult) { + private func socialAuth(result: SocialAuthDetails) { switch result { case .apple(let appleCredentials): - appleLogin(appleCredentials, backend: result.backend, loginMethod: .apple) + appleRegister(appleCredentials, backend: result.backend, loginMethod: .socailAuth(.apple)) case .facebook(let account): - facebookLogin(backend: result.backend, account: account, loginMethod: .facebook) + facebookRegister(backend: result.backend, account: account, loginMethod: .socailAuth(.facebook)) case .google(let gIDSignInResult): - googleLogin(gIDSignInResult, backend: result.backend, loginMethod: .google) + googleRegister(gIDSignInResult, backend: result.backend, loginMethod: .socailAuth(.google)) case .microsoft(let account, let token): - microsoftLogin(token, backend: result.backend, account: account, loginMethod: .microsoft) + microsoftRegister(token, backend: result.backend, account: account, loginMethod: .socailAuth(.microsoft)) } } @MainActor - private func appleLogin( + private func appleRegister( _ credentials: AppleCredentials, - backend: String, + backend: String, loginMethod: LoginMethod ) { update( @@ -181,7 +181,7 @@ public class SignUpViewModel: ObservableObject { } @MainActor - private func facebookLogin( + private func facebookRegister( backend: String, account: LoginManagerLoginResult, loginMethod: LoginMethod @@ -212,7 +212,7 @@ public class SignUpViewModel: ObservableObject { } @MainActor - private func googleLogin( + private func googleRegister( _ result: GIDSignInResult, backend: String, loginMethod: LoginMethod @@ -230,7 +230,7 @@ public class SignUpViewModel: ObservableObject { } @MainActor - private func microsoftLogin( + private func microsoftRegister( _ token: String, backend: String, account: MSALAccount, diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift similarity index 73% rename from Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift rename to Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift index 6558071f4..f598cc8e3 100644 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignView.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift @@ -8,32 +8,32 @@ import SwiftUI import Core -struct SocialSignView: View { +struct SocialAuthView: View { // MARK: - Properties - @StateObject var viewModel: SocialSignViewModel + @StateObject var viewModel: SocialAuthViewModel init( - signType: SignType = .signIn, - viewModel: SocialSignViewModel + signType: SocialAuthType = .signIn, + viewModel: SocialAuthViewModel ) { self._viewModel = .init(wrappedValue: viewModel) self.signType = signType } - enum SignType { + enum SocialAuthType { case signIn case register } - var signType: SignType = .signIn + var signType: SocialAuthType = .signIn private var title: String { switch signType { case .signIn: - return AuthLocalization.signInWith + AuthLocalization.signInWith case .register: - return AuthLocalization.signInRegister + AuthLocalization.signInRegister } } @@ -58,39 +58,39 @@ struct SocialSignView: View { private var buttonsView: some View { Group { - if viewModel.isGoogleEnabled { - LabelButton( + if viewModel.googleEnabled { + SocialAuthButton( image: CoreAssets.iconGoogleWhite.swiftUIImage, title: "\(title) \(AuthLocalization.google)", textColor: .black, backgroundColor: CoreAssets.googleButtonColor.swiftUIColor, - action: viewModel.signInWithGoogle + action: { Task { await viewModel.signInWithGoogle() } } ) .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.isFaceboolEnabled { - LabelButton( + if viewModel.faceboolEnabled { + SocialAuthButton( image: CoreAssets.iconFacebookWhite.swiftUIImage, title: "\(title) \(AuthLocalization.facebook)", backgroundColor: CoreAssets.facebookButtonColor.swiftUIColor, - action: viewModel.signInWithFacebook + action: { Task { await viewModel.signInWithFacebook() } } ) .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.facebook)") } - if viewModel.isMicrosoftEnabled { - LabelButton( + if viewModel.microsoftEnabled { + SocialAuthButton( image: CoreAssets.iconMicrosoftWhite.swiftUIImage, title: "\(title) \(AuthLocalization.microsoft)", backgroundColor: CoreAssets.microsoftButtonColor.swiftUIColor, - action: viewModel.signInWithMicrosoft + action: { Task { await viewModel.signInWithMicrosoft() } } ) .accessibilityElement(children: .ignore) .accessibilityLabel("\(title) \(AuthLocalization.microsoft)") } - if viewModel.isAppleSignInEnabled { - LabelButton( + if viewModel.appleSignInEnabled { + SocialAuthButton( image: CoreAssets.iconApple.swiftUIImage, title: "\(title) \(AuthLocalization.apple)", backgroundColor: CoreAssets.appleButtonColor.swiftUIColor, @@ -106,8 +106,8 @@ struct SocialSignView: View { #if DEBUG struct SocialSignView_Previews: PreviewProvider { static var previews: some View { - let vm = SocialSignViewModel(config: ConfigMock(), completion: { _ in }) - SocialSignView(viewModel: vm).padding() + let vm = SocialAuthViewModel(config: ConfigMock(), completion: { _ in }) + SocialAuthView(viewModel: vm).padding() } } #endif diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift new file mode 100644 index 000000000..a4763402c --- /dev/null +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -0,0 +1,134 @@ +// +// SocialSignViewModel.swift +// Authorization +// +// Created by Eugene Yatsenko on 11.10.2023. +// + +import SwiftUI +import Core +import AuthenticationServices +import FacebookLogin +import GoogleSignIn +import MSAL +import Swinject + +enum SocialAuthDetails { + case apple(AppleCredentials) + case facebook(LoginManagerLoginResult) + case google(GIDSignInResult) + case microsoft(MSALAccount, String) + + var backend: String { + switch self { + case .apple: + return "apple-id" + case .facebook: + return "facebook" + case .google: + return "google-oauth2" + case .microsoft: + return "azuread-oauth2" + } + } +} + +final public class SocialAuthViewModel: ObservableObject { + + // MARK: - Properties + + private var completion: ((Result) -> Void) + private let config: ConfigProtocol + + init( + config: ConfigProtocol, + completion: @escaping (Result) -> Void + ) { + self.config = config + self.completion = completion + } + + private lazy var appleAuthProvider: AppleAuthProvider = .init(config: config) + private lazy var googleAuthProvider: GoogleAuthProvider = .init() + private lazy var facebookAuthProvider: FacebookAuthProvider = .init() + private lazy var microsoftAuthProvider: MicrosoftAuthProvider = .init() + + private var topViewController: UIViewController? { + UIApplication.topViewController() + } + + // MARK: - Public Properties + + var faceboolEnabled: Bool { + config.facebook.enabled + } + + var googleEnabled: Bool { + config.google.enabled + } + + var microsoftEnabled: Bool { + config.microsoft.enabled + } + + var appleSignInEnabled: Bool { + if faceboolEnabled || + googleEnabled || + microsoftEnabled { + /// Apps that use a third-party or social login service (such as Facebook Login, Google Sign-In...) + /// to set up or authenticate the user's primary account with the app + /// must also offer Sign in with Apple as an equivalent option + return true + } + return config.appleSignIn.enable + } + + // MARK: - Public Intens + + func signInWithApple() { + appleAuthProvider.request { [weak self] result in + guard let self else { return } + result.success { self.success(with: .apple($0)) } + result.failure(self.failure) + } + } + + @MainActor + func signInWithGoogle() async { + guard let vc = topViewController else { + return + } + let result = await googleAuthProvider.signIn(withPresenting: vc) + result.success { success(with: .google($0)) } + result.failure(failure) + } + + @MainActor + func signInWithFacebook() async { + guard let vc = topViewController else { + return + } + let result = await facebookAuthProvider.signIn(withPresenting: vc) + result.success { success(with: .facebook($0)) } + result.failure(failure) + } + + @MainActor + func signInWithMicrosoft() async { + guard let vc = topViewController else { + return + } + let result = await microsoftAuthProvider.signIn(withPresenting: vc) + result.success { success(with: .microsoft($0.account, $0.token) ) } + result.failure(failure) + } + + private func success(with social: SocialAuthDetails) { + completion(.success(social)) + } + + private func failure(_ error: Error) { + completion(.failure(error)) + } + +} diff --git a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift b/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift deleted file mode 100644 index 13307845f..000000000 --- a/Authorization/Authorization/Presentation/SocialSign/SocialSignViewModel.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// SocialSignViewModel.swift -// Authorization -// -// Created by Eugene Yatsenko on 11.10.2023. -// - -import SwiftUI -import Core -import AuthenticationServices -import FacebookLogin -import GoogleSignIn -import MSAL -import Swinject - -enum SocialResult { - case apple(AppleCredentials) - case facebook(LoginManagerLoginResult) - case google(GIDSignInResult) - case microsoft(MSALAccount, String) - - var backend: String { - switch self { - case .apple: - return "apple-id" - case .facebook: - return "facebook" - case .google: - return "google-oauth2" - case .microsoft: - return "azuread-oauth2" - } - } -} - -final public class SocialSignViewModel: ObservableObject { - - // MARK: - Properties - - private var completion: ((Result) -> Void) - private let config: ConfigProtocol - - init( - config: ConfigProtocol, - completion: @escaping (Result) -> Void - ) { - self.config = config - self.completion = completion - } - - private let appleSingInProvider: AppleSingInProvider = .init() - private let googleSingInProvider: GoogleSingInProvider = .init() - private let facebookSingInProvider: FacebookSingInProvider = .init() - private let microsoftSingInProvider: MicrosoftSingInProvider = .init() - - private var topViewController: UIViewController? { - UIApplication.topViewController() - } - - // MARK: - Public Properties - - var isFaceboolEnabled: Bool { - config.facebook.enabled - } - - var isGoogleEnabled: Bool { - config.google.enabled - } - - var isMicrosoftEnabled: Bool { - config.microsoft.enabled - } - - var isAppleSignInEnabled: Bool { - if isFaceboolEnabled || - isGoogleEnabled || - isMicrosoftEnabled { - return true - } - return config.appleSignIn.enable - } - - // MARK: - Public Intens - - func signInWithApple() { - appleSingInProvider.request { [weak self] result in - guard let self = self else { - return - } - result.success { self.success(with: .apple($0)) } - result.failure(self.failure) - } - } - - func signInWithGoogle() { - topViewController.flatMap { - googleSingInProvider.signIn( - withPresenting: $0 - ) { [weak self] result, error in - guard let self = self else { - return - } - result.flatMap { self.success(with: .google($0)) } - error.flatMap(self.failure) - } - } - } - - func signInWithFacebook() { - topViewController.flatMap { - facebookSingInProvider.signIn( - withPresenting: $0, - completion: { [weak self] result, error in - guard let self = self else { - return - } - result.flatMap { self.success(with: .facebook($0)) } - error.flatMap(self.failure) - } - ) - } - } - - func signInWithMicrosoft() { - topViewController.flatMap { - microsoftSingInProvider.signIn( - withPresenting: $0 - ) { [weak self] account, token, error in - guard let self = self else { - return - } - if let account = account, let token = token { - self.success(with: .microsoft(account, token)) - } - if let error = error { - self.failure(error) - } - } - } - } - - private func success(with social: SocialResult) { - completion(.success(social)) - } - - private func failure(_ error: Error) { - completion(.failure(error)) - } - -} diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index 7da7cd562..e305336e6 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -25,6 +25,8 @@ public enum AuthLocalization { /// Sign in with public static let signInWith = AuthLocalization.tr("Localizable", "SIGN_IN_WITH", fallback: "Sign in with") public enum Error { + /// Your account is disabled. Please contact customer support for assistance. + public static let accountDisabled = AuthLocalization.tr("Localizable", "ERROR.ACCOUNT_DISABLED", fallback: "Your account is disabled. Please contact customer support for assistance.") /// This %@ account is not linked with any %@ account. Please register. public static func authProvider(_ p1: Any, _ p2: Any) -> String { return AuthLocalization.tr("Localizable", "ERROR.AUTH_PROVIDER", String(describing: p1), String(describing: p2), fallback: "This %@ account is not linked with any %@ account. Please register.") @@ -79,9 +81,9 @@ public enum AuthLocalization { /// Create new account. public static let subtitle = AuthLocalization.tr("Localizable", "SIGN_UP.SUBTITLE", fallback: "Create new account.") /// You've successfully signed in. - public static let successSignedinLabel = AuthLocalization.tr("Localizable", "SIGN_UP.SUCCESS_SIGNEDIN_LABEL", fallback: "You've successfully signed in.") + public static let successSigninLabel = AuthLocalization.tr("Localizable", "SIGN_UP.SUCCESS_SIGNIN_LABEL", fallback: "You've successfully signed in.") /// We just need a little more information before you start learning. - public static let successSignedinSublabel = AuthLocalization.tr("Localizable", "SIGN_UP.SUCCESS_SIGNEDIN_SUBLABEL", fallback: "We just need a little more information before you start learning.") + public static let successSigninSublabel = AuthLocalization.tr("Localizable", "SIGN_UP.SUCCESS_SIGNIN_SUBLABEL", fallback: "We just need a little more information before you start learning.") /// Sign up public static let title = AuthLocalization.tr("Localizable", "SIGN_UP.TITLE", fallback: "Sign up") } diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index 2f8ca3b74..29717e3d6 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -19,14 +19,15 @@ "ERROR.INVALID_PASSWORD_LENGHT" = "Invalid password lenght"; "ERROR.AUTH_PROVIDER" = "This %@ account is not linked with any %@ account. Please register."; "ERROR.INVALID_EMAIL_ADDRESS_OR_USERNAME" = "Invalid email or username"; +"ERROR.ACCOUNT_DISABLED" = "Your account is disabled. Please contact customer support for assistance."; "SIGN_UP.TITLE" = "Sign up"; "SIGN_UP.SUBTITLE" = "Create new account."; "SIGN_UP.CREATE_ACCOUNT_BTN" = "Create account"; "SIGN_UP.HIDE_FIELDS" = "Hide optional Fields"; "SIGN_UP.SHOW_FIELDS" = "Show optional Fields"; -"SIGN_UP.SUCCESS_SIGNEDIN_LABEL" = "You've successfully signed in."; -"SIGN_UP.SUCCESS_SIGNEDIN_SUBLABEL" = "We just need a little more information before you start learning."; +"SIGN_UP.SUCCESS_SIGNIN_LABEL" = "You've successfully signed in."; +"SIGN_UP.SUCCESS_SIGNIN_SUBLABEL" = "We just need a little more information before you start learning."; "FORGOT.TITLE"= "Forgot password"; "FORGOT.DESCRIPTION" = "Please enter your log-in or recovery email address below and we will send you an email with instructions."; diff --git a/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings index b357c2bf0..4bc6819ca 100644 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ b/Authorization/Authorization/uk.lproj/Localizable.strings @@ -17,14 +17,15 @@ "ERROR.INVALID_EMAIL_ADDRESS" = "невірна адреса електронної пошти"; "ERROR.INVALID_PASSWORD_LENGHT" = "Пароль занадто короткий або занадто довгий"; "ERROR.AUTH_PROVIDER" = "This %@ account is not linked with any %@ account. Please register."; +"ERROR.ACCOUNT_DISABLED" = "Your account is disabled. Please contact customer support for assistance."; "SIGN_UP.TITLE" = "Зареєструватись"; "SIGN_UP.SUBTITLE" = "Cтворити новий акаунт."; "SIGN_UP.CREATE_ACCOUNT_BTN" = "Створити акаунт"; "SIGN_UP.HIDE_FIELDS" = "Приховати необовʼязкові поля"; "SIGN_UP.SHOW_FIELDS" = "Показати необовʼязкові поля"; -"SIGN_UP.SUCCESS_SIGNEDIN_LABEL" = "You've successfully signed in."; -"SIGN_UP.SUCCESS_SIGNEDIN_SUBLABEL" = "We just need a little more information before you start learning."; +"SIGN_UP.SUCCESS_SIGNIN_LABEL" = "You've successfully signed in."; +"SIGN_UP.SUCCESS_SIGNIN_SUBLABEL" = "We just need a little more information before you start learning."; "FORGOT.TITLE"= "Відновлення паролю"; "FORGOT.DESCRIPTION" = "Будь ласка, введіть свою адресу електронної пошти для входу або відновлення нижче, і ми надішлемо вам електронний лист з інструкціями."; diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 305c09af8..8566344aa 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -117,18 +117,18 @@ 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE5E28D0B22C006D8A5D /* Strings.swift */; }; 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; + BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; - BA8FA6612AD5974300EA029A /* AppleSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */; }; - BA8FA6682AD59A5700EA029A /* LabelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6672AD59A5700EA029A /* LabelButton.swift */; }; - BA8FA66A2AD59B5500EA029A /* GoogleSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */; }; + BA8FA6612AD5974300EA029A /* AppleAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6602AD5974300EA029A /* AppleAuthProvider.swift */; }; + BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */; }; + BA8FA66A2AD59B5500EA029A /* GoogleAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */; }; BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; - BA8FA66E2AD59E7D00EA029A /* FacebookSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */; }; - BA8FA6702AD59EA300EA029A /* MicrosoftSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */; }; + BA8FA66E2AD59E7D00EA029A /* FacebookAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */; }; + BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */; }; BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */; }; - BAFB998E2B0F70F1007D09F9 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998D2B0F70F1007D09F9 /* CustomError.swift */; }; BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */; }; BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; @@ -285,16 +285,16 @@ 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = ""; }; 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; + BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; - BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSingInProvider.swift; sourceTree = ""; }; - BA8FA6672AD59A5700EA029A /* LabelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelButton.swift; sourceTree = ""; }; - BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSingInProvider.swift; sourceTree = ""; }; - BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookSingInProvider.swift; sourceTree = ""; }; - BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftSingInProvider.swift; sourceTree = ""; }; + BA8FA6602AD5974300EA029A /* AppleAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAuthProvider.swift; sourceTree = ""; }; + BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthButton.swift; sourceTree = ""; }; + BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthProvider.swift; sourceTree = ""; }; + BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookAuthProvider.swift; sourceTree = ""; }; + BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftAuthProvider.swift; sourceTree = ""; }; BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; - BAFB998D2B0F70F1007D09F9 /* CustomError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleConfig.swift; sourceTree = ""; }; BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSignInConfig.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; @@ -351,8 +351,6 @@ 027BD3A12909470F00392132 /* AvoidingHelpers */ = { isa = PBXGroup; children = ( - BAFB998C2B0F70F1007D09F9 /* Error */, - BA8FA65F2AD5973500EA029A /* Providers */, 027BD3A22909471900392132 /* Avoider */, 027BD3A32909471F00392132 /* Scroller */, 027BD3A42909472500392132 /* State */, @@ -546,6 +544,7 @@ 0770DE0A28D07831006D8A5D /* Core */ = { isa = PBXGroup; children = ( + BA8FA65F2AD5973500EA029A /* Providers */, 027BD3A12909470F00392132 /* AvoidingHelpers */, 0770DE5528D0B142006D8A5D /* SwiftGen */, 0283347E28D4DCC100C828FC /* Extensions */, @@ -625,31 +624,40 @@ 027BD3C42909707700392132 /* Shake.swift */, 023A1135291432B200D0D354 /* RegistrationTextField.swift */, 023A1137291432FD00D0D354 /* FieldConfiguration.swift */, - BA8FA6672AD59A5700EA029A /* LabelButton.swift */, + BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */, 02E93F862AEBAED4006C4750 /* AppReview */, ); path = Base; sourceTree = ""; }; - BA8FA65F2AD5973500EA029A /* Providers */ = { + BA30427C2B20B235009B64B7 /* SocialAuth */ = { isa = PBXGroup; children = ( - BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */, - BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */, - BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */, - BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */, + BA30427E2B20B299009B64B7 /* Error */, + BA8FA6602AD5974300EA029A /* AppleAuthProvider.swift */, + BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */, + BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */, + BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */, ); - path = Providers; + path = SocialAuth; sourceTree = ""; }; - BAFB998C2B0F70F1007D09F9 /* Error */ = { + BA30427E2B20B299009B64B7 /* Error */ = { isa = PBXGroup; children = ( - BAFB998D2B0F70F1007D09F9 /* CustomError.swift */, + BA30427D2B20B299009B64B7 /* SocialAuthError.swift */, ); path = Error; sourceTree = ""; }; + BA8FA65F2AD5973500EA029A /* Providers */ = { + isa = PBXGroup; + children = ( + BA30427C2B20B235009B64B7 /* SocialAuth */, + ); + path = Providers; + sourceTree = ""; + }; C9DFE47E699CFFA85A77AF2C /* Pods */ = { isa = PBXGroup; children = ( @@ -922,11 +930,10 @@ CFC84952299F8B890055E497 /* Debounce.swift in Sources */, 0236F3B728F4351E0050F09B /* CourseButton.swift in Sources */, 0727878328D31287002E9142 /* DispatchQueue+App.swift in Sources */, - BAFB998E2B0F70F1007D09F9 /* CustomError.swift in Sources */, 028F9F37293A44C700DE65D0 /* Data_ResetPassword.swift in Sources */, 022C64E429AE0191000F532B /* TextWithUrls.swift in Sources */, 0283348028D4DCD200C828FC /* ViewExtension.swift in Sources */, - BA8FA66A2AD59B5500EA029A /* GoogleSingInProvider.swift in Sources */, + BA8FA66A2AD59B5500EA029A /* GoogleAuthProvider.swift in Sources */, 02A4833529B8A73400D33F33 /* CorePersistenceProtocol.swift in Sources */, 0233D5732AF13EEE00BAC8BD /* AppReviewButton.swift in Sources */, 02512FF0299533DF0024D438 /* CoreDataHandlerProtocol.swift in Sources */, @@ -949,8 +956,8 @@ 027BD3A82909474200392132 /* KeyboardAvoidingViewController.swift in Sources */, 02E93F852AEBAEBC006C4750 /* AppReviewViewModel.swift in Sources */, 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */, - BA8FA6612AD5974300EA029A /* AppleSingInProvider.swift in Sources */, - BA8FA6702AD59EA300EA029A /* MicrosoftSingInProvider.swift in Sources */, + BA8FA6612AD5974300EA029A /* AppleAuthProvider.swift in Sources */, + BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */, 020306CC2932C0C4000949EA /* PickerView.swift in Sources */, 027BD3C52909707700392132 /* Shake.swift in Sources */, 027BD39C2908810C00392132 /* RegisterUser.swift in Sources */, @@ -965,6 +972,7 @@ 021D925028DC89D100ACC565 /* UserProfile.swift in Sources */, 071009D028D1E3A600344290 /* Constants.swift in Sources */, 0770DE1928D0847D006D8A5D /* BaseRouter.swift in Sources */, + BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */, 0284DBFE28D48C5300830893 /* CourseItem.swift in Sources */, 0248C92329C075EF00DC8402 /* CourseBlockModel.swift in Sources */, DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */, @@ -976,7 +984,7 @@ 02B2B594295C5C7A00914876 /* Thread.swift in Sources */, 027DB33528D8C8FE002B6862 /* Data_MyCourse.swift in Sources */, 027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */, - BA8FA6682AD59A5700EA029A /* LabelButton.swift in Sources */, + BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */, 02D400612B0678190029D168 /* SKStoreReviewControllerExtension.swift in Sources */, 02A4833C29B8C57800D33F33 /* DownloadView.swift in Sources */, 027BD3AD2909475000392132 /* KeyboardScroller.swift in Sources */, @@ -984,7 +992,7 @@ 024D723529C8BB1A006D36ED /* NavigationBar.swift in Sources */, BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */, 0727877928D23BE0002E9142 /* RequestInterceptor.swift in Sources */, - BA8FA66E2AD59E7D00EA029A /* FacebookSingInProvider.swift in Sources */, + BA8FA66E2AD59E7D00EA029A /* FacebookAuthProvider.swift in Sources */, 0770DE2E28D09743006D8A5D /* API.swift in Sources */, 028CE96929858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift in Sources */, 027BD3A92909474200392132 /* KeyboardAvoidingViewControllerRepr.swift in Sources */, diff --git a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift deleted file mode 100644 index a9f112898..000000000 --- a/Core/Core/AvoidingHelpers/Providers/FacebookSingInProvider.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// FacebookSingInProvider.swift -// Core -// -// Created by Eugene Yatsenko on 10.10.2023. -// - -import Foundation -import FacebookLogin - -public final class FacebookSingInProvider { - - private let loginManager = LoginManager() - - public init() {} - - public func signIn( - withPresenting: UIViewController, - completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void) - ) { - loginManager.logIn( - permissions: [], - from: withPresenting, - handler: { [weak self] result, error in - - if result?.isCancelled == true { - completion(nil, CustomError.socialSignCanceled) - return - } - - if let error = error { - completion(nil, self?.failure(error)) - return - } - - if result?.authenticationToken == nil { - completion(nil, CustomError.error(text: CoreLocalization.Error.unknownError)) - return - } - - completion(result, nil) - } - ) - } - - public func signOut() { - loginManager.logOut() - } - - private func failure(_ error: Error?) -> Error { - if let error = error as? NSError, - let description = error.userInfo[ErrorLocalizedDescriptionKey] as? String { - return CustomError.error(text: description) - } - return error ?? CustomError.error(text: CoreLocalization.Error.unknownError) - } -} diff --git a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift deleted file mode 100644 index 6bf793feb..000000000 --- a/Core/Core/AvoidingHelpers/Providers/GoogleSingInProvider.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// GoogleSingInProvider.swift -// Core -// -// Created by Eugene Yatsenko on 10.10.2023. -// - -import GoogleSignIn - -public final class GoogleSingInProvider { - - public init() {} - - public func signIn( - withPresenting: UIViewController, - completion: @escaping ((GIDSignInResult?, Error?) -> Void) - ) { - GIDSignIn.sharedInstance.signIn( - withPresenting: withPresenting, - completion: { result, error in - if let error = error as? NSError, error.code == GIDSignInError.canceled.rawValue { - completion(result, CustomError.socialSignCanceled) - return - } - completion(result, error) - } - ) - } - - public func signOut() { - GIDSignIn.sharedInstance.signOut() - } -} diff --git a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift b/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift deleted file mode 100644 index 856ca1ccf..000000000 --- a/Core/Core/AvoidingHelpers/Providers/MicrosoftSingInProvider.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// MicrosoftSingInProvider.swift -// Core -// -// Created by Eugene Yatsenko on 10.10.2023. -// - -import Foundation -import MSAL -import Swinject - -public typealias MSLoginCompletionHandler = (MSALAccount?, String?, Error?) -> Void - -public final class MicrosoftSingInProvider { - - private let scopes = ["User.Read", "email"] - private var result: MSALResult? - - public init() {} - - public func signIn( - withPresenting: UIViewController, - completion: @escaping MSLoginCompletionHandler - ) { - do { - let clientApplication = try createClientApplication() - - let webParameters = MSALWebviewParameters(authPresentationViewController: withPresenting) - let parameters = MSALInteractiveTokenParameters(scopes: scopes, webviewParameters: webParameters) - clientApplication.acquireToken(with: parameters) { [weak self] result, error in - guard let self = self else { return } - - guard let result = result, error == nil else { - completion(nil, nil, failure(error)) - return - } - - self.result = result - let account = result.account - completion(account, result.accessToken, nil) - } - } catch let createApplicationError { - completion(nil, nil, createApplicationError) - } - } - - public func signOut() { - do { - let account = try? currentAccount() - - if let account = account { - let application = try createClientApplication() - try application.remove(account) - } - } catch let error { - debugLog("Logout", "Received error signing user out: \(error)") - } - } - - private func createClientApplication() throws -> MSALPublicClientApplication { - guard let config = Container.shared.resolve(ConfigProtocol.self), let appID = config.microsoft.appID else { - throw CustomError.error(text: "Configuration error") - } - let configuration = MSALPublicClientApplicationConfig(clientId: appID) - - do { - return try MSALPublicClientApplication(configuration: configuration) - } catch { - throw CustomError.error(text: error.localizedDescription) - } - } - - @discardableResult - private func currentAccount() throws -> MSALAccount { - let clientApplication = try createClientApplication() - - guard let account = try clientApplication.allAccounts().first else { - throw CustomError.error(text: "Account not found") - } - - return account - } - - private func failure(_ error: Error?) -> Error { - if let error = error as? NSError, - let description = error.userInfo[MSALErrorDescriptionKey] as? String { - if let errorCode = MSALError(rawValue: error.code), case .userCanceled = errorCode { - return CustomError.socialSignCanceled - } - return CustomError.error(text: description) - } - return error ?? CustomError.error(text: CoreLocalization.Error.unknownError) - } - - func getUser(completion: (MSALAccount) -> Void) { - guard let user = result?.account else { return } - completion(user) - } - -} diff --git a/Core/Core/Configuration/Config/FacebookConfig.swift b/Core/Core/Configuration/Config/FacebookConfig.swift index 7c3e45b34..02f082b56 100644 --- a/Core/Core/Configuration/Config/FacebookConfig.swift +++ b/Core/Core/Configuration/Config/FacebookConfig.swift @@ -15,10 +15,10 @@ private enum FacebookKeys: String { public final class FacebookConfig: NSObject { public var enabled: Bool = false - public var appID: String? - public var clientToken: String? + private(set) var appID: String? + private(set) var clientToken: String? - public var requiredKeysAvailable: Bool { + private var requiredKeysAvailable: Bool { return appID != nil && clientToken != nil } diff --git a/Core/Core/Configuration/Config/GoogleConfig.swift b/Core/Core/Configuration/Config/GoogleConfig.swift index 91416c987..e5e45fb04 100644 --- a/Core/Core/Configuration/Config/GoogleConfig.swift +++ b/Core/Core/Configuration/Config/GoogleConfig.swift @@ -15,10 +15,10 @@ private enum GoogleKeys: String { public final class GoogleConfig: NSObject { public var enabled: Bool = false - public var googlePlusKey: String? - public var clientID: String? + private(set) var googlePlusKey: String? + private(set) var clientID: String? - public var requiredKeysAvailable: Bool { + private var requiredKeysAvailable: Bool { return clientID != nil } diff --git a/Core/Core/Configuration/Config/MicrosoftConfig.swift b/Core/Core/Configuration/Config/MicrosoftConfig.swift index f36cf0e6c..16497b09f 100644 --- a/Core/Core/Configuration/Config/MicrosoftConfig.swift +++ b/Core/Core/Configuration/Config/MicrosoftConfig.swift @@ -14,9 +14,9 @@ private enum MicrosoftKeys: String { public final class MicrosoftConfig: NSObject { public var enabled: Bool = false - public var appID: String? + private(set) var appID: String? - public var requiredKeysAvailable: Bool { + private var requiredKeysAvailable: Bool { return appID != nil } diff --git a/Core/Core/Data/CoreStorage.swift b/Core/Core/Data/CoreStorage.swift index 37eecfa19..b3a8faca0 100644 --- a/Core/Core/Data/CoreStorage.swift +++ b/Core/Core/Data/CoreStorage.swift @@ -10,8 +10,7 @@ import Foundation public protocol CoreStorage { var accessToken: String? {get set} var refreshToken: String? {get set} - var appleSignGivenName: String? {get set} - var appleSignFamilyName: String? {get set} + var appleSignFullName: String? {get set} var appleSignEmail: String? {get set} var cookiesDate: String? {get set} var reviewLastShownVersion: String? {get set} @@ -25,8 +24,7 @@ public protocol CoreStorage { public class CoreStorageMock: CoreStorage { public var accessToken: String? public var refreshToken: String? - public var appleSignGivenName: String? - public var appleSignFamilyName: String? + public var appleSignFullName: String? public var appleSignEmail: String? public var cookiesDate: String? public var reviewLastShownVersion: String? diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index 44fa91236..ea0393962 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -56,7 +56,7 @@ public class AuthRepository: AuthRepositoryProtocol { } public func login(externalToken: String, backend: String) async throws -> User { - let endPoint = AuthEndpoint.socialLogin( + let endPoint = AuthEndpoint.getEchangeAccessToken( externalToken: externalToken, backend: backend, clientId: config.oAuthClientId, diff --git a/Core/Core/Extensions/CGColorExtension.swift b/Core/Core/Extensions/CGColorExtension.swift index 3aab30552..f1e871974 100644 --- a/Core/Core/Extensions/CGColorExtension.swift +++ b/Core/Core/Extensions/CGColorExtension.swift @@ -31,9 +31,3 @@ public extension Color { return UIColor(self) } } - -public extension UIColor { - var sui: Color { - Color(self) - } -} diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index b67bbd22b..2138efcab 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -10,7 +10,7 @@ import Alamofire enum AuthEndpoint: EndPointType { case getAccessToken(username: String, password: String, clientId: String, tokenType: String) - case socialLogin(externalToken: String, backend: String, clientId: String, tokenType: String) + case getEchangeAccessToken(externalToken: String, backend: String, clientId: String, tokenType: String) case getUserInfo case getAuthCookies case getRegisterFields @@ -22,7 +22,7 @@ enum AuthEndpoint: EndPointType { switch self { case .getAccessToken: return "/oauth2/access_token" - case let .socialLogin(_, backend, _, _): + case let .getEchangeAccessToken(_, backend, _, _): return "/oauth2/exchange_access_token/\(backend)/" case .getUserInfo: return "/api/mobile/v0.5/my_user_info" @@ -41,9 +41,7 @@ enum AuthEndpoint: EndPointType { var httpMethod: HTTPMethod { switch self { - case .getAccessToken: - return .post - case .socialLogin: + case .getAccessToken, .getEchangeAccessToken: return .post case .getUserInfo: return .get @@ -76,7 +74,7 @@ enum AuthEndpoint: EndPointType { "asymmetric_jwt": true ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) - case let .socialLogin(externalToken, _, clientId, tokenType): + case let .getEchangeAccessToken(externalToken, _, clientId, tokenType): let params: [String: Encodable] = [ "client_id": clientId, "token_type": tokenType, diff --git a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift b/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift similarity index 71% rename from Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift rename to Core/Core/Providers/SocialAuth/AppleAuthProvider.swift index a03fb28de..3f732fb6c 100644 --- a/Core/Core/AvoidingHelpers/Providers/AppleSingInProvider.swift +++ b/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift @@ -12,13 +12,17 @@ import Swinject public struct AppleCredentials: Codable { public var name: String public var email: String - public var birthYear: String? = nil public var token: String } -public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDelegate { +public final class AppleAuthProvider: NSObject, ASAuthorizationControllerDelegate { - public override init() {} + private let config: ConfigProtocol + + public init(config: ConfigProtocol) { + self.config = config + super.init() + } private var completion: ((Result) -> Void)? private let appleIDProvider = ASAuthorizationAppleIDProvider() @@ -39,24 +43,22 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg didCompleteWithAuthorization authorization: ASAuthorization ) { guard let credentials = authorization.credential as? ASAuthorizationAppleIDCredential else { - completion?(.failure(CustomError.unknownError)) + completion?(.failure(SocialAuthError.unknownError)) return } var storage = Container.shared.resolve(CoreStorage.self) + let pncf = PersonNameComponentsFormatter() - let givenName = credentials.fullName?.givenName ?? storage?.appleSignGivenName ?? "" - let familyName = credentials.fullName?.familyName ?? storage?.appleSignFamilyName ?? "" + var name = storage?.appleSignFullName ?? "" + if let fullName = credentials.fullName { + name = pncf.string(from: fullName) + } let email = credentials.email ?? storage?.appleSignEmail ?? "" - var name: String = "\(givenName) \(familyName)" - - if storage?.appleSignFamilyName == nil, !familyName.isEmpty { - storage?.appleSignFamilyName = familyName - } - if storage?.appleSignGivenName == nil, !givenName.isEmpty { - storage?.appleSignGivenName = givenName + if !name.isEmpty { + storage?.appleSignFullName = name } if storage?.appleSignEmail == nil { @@ -65,7 +67,7 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg guard let data = credentials.identityToken, let code = String(data: data, encoding: .utf8) else { - completion?(.failure(CustomError.unknownError)) + completion?(.failure(SocialAuthError.unknownError)) return } @@ -91,9 +93,9 @@ public final class AppleSingInProvider: NSObject, ASAuthorizationControllerDeleg private func failure(_ error: ASAuthorizationError) -> Error { switch error.code { case .canceled: - return CustomError.socialSignCanceled + return SocialAuthError.socialAuthCanceled case .failed: - return CustomError.error(text: CoreLocalization.Error.authorizationFailed) + return SocialAuthError.error(text: CoreLocalization.Error.authorizationFailed) default: return error } diff --git a/Core/Core/AvoidingHelpers/Error/CustomError.swift b/Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift similarity index 75% rename from Core/Core/AvoidingHelpers/Error/CustomError.swift rename to Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift index 4dd24c068..0626897f9 100644 --- a/Core/Core/AvoidingHelpers/Error/CustomError.swift +++ b/Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift @@ -7,18 +7,18 @@ import Foundation -public enum CustomError: Error { +public enum SocialAuthError: Error { case error(text: String) - case socialSignCanceled + case socialAuthCanceled case unknownError } -extension CustomError: LocalizedError { +extension SocialAuthError: LocalizedError { public var errorDescription: String? { switch self { case .error(let text): return text - case .socialSignCanceled: + case .socialAuthCanceled: return CoreLocalization.socialSignCanceled case .unknownError: return CoreLocalization.Error.unknownError diff --git a/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift b/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift new file mode 100644 index 000000000..8068b96e5 --- /dev/null +++ b/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift @@ -0,0 +1,63 @@ +// +// FacebookSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation +import FacebookLogin + +public protocol FacebookAuth { + func signIn(withPresenting: UIViewController, completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void)) +} + +public final class FacebookAuthProvider { + + private let loginManager = LoginManager() + + public init() {} + + @MainActor + public func signIn( + withPresenting: UIViewController + ) async -> Result { + await withCheckedContinuation { continuation in + loginManager.logIn( + permissions: [], + from: withPresenting + ) { result, error in + if let error = error { + continuation.resume(returning: .failure(error)) + return + } + + guard let result = result, let _ = result.authenticationToken else { + continuation.resume( + returning: .failure(SocialAuthError.unknownError) + ) + return + } + + if result.isCancelled { + continuation.resume(returning: .failure(SocialAuthError.socialAuthCanceled)) + return + } + + continuation.resume(returning: .success(result)) + } + } + } + + public func signOut() { + loginManager.logOut() + } + + private func failure(_ error: Error?) -> Error { + if let error = error as? NSError, + let description = error.userInfo[ErrorLocalizedDescriptionKey] as? String { + return SocialAuthError.error(text: description) + } + return error ?? SocialAuthError.unknownError + } +} diff --git a/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift b/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift new file mode 100644 index 000000000..1a04519eb --- /dev/null +++ b/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift @@ -0,0 +1,44 @@ +// +// GoogleSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import GoogleSignIn +import Foundation + +public final class GoogleAuthProvider { + + public init() {} + + @MainActor + public func signIn( + withPresenting: UIViewController + ) async -> Result { + await withCheckedContinuation { continuation in + GIDSignIn.sharedInstance.signIn( + withPresenting: withPresenting, + completion: { result, error in + if let error = error as? NSError, error.code == GIDSignInError.canceled.rawValue { + continuation.resume(returning: .failure(SocialAuthError.socialAuthCanceled)) + return + } + guard let result = result else { + continuation.resume( + returning: .failure( + SocialAuthError.error(text: CoreLocalization.Error.unknownError) + ) + ) + return + } + continuation.resume(returning: .success(result)) + } + ) + } + } + + public func signOut() { + GIDSignIn.sharedInstance.signOut() + } +} diff --git a/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift b/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift new file mode 100644 index 000000000..9a760c2c3 --- /dev/null +++ b/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift @@ -0,0 +1,109 @@ +// +// MicrosoftSingInProvider.swift +// Core +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import Foundation +import MSAL +import Swinject + +public typealias MSLoginCompletionHandler = (account: MSALAccount, token: String) + +public final class MicrosoftAuthProvider { + + private let scopes = ["User.Read", "email"] + private var result: MSALResult? + + public init() {} + + @MainActor + public func signIn( + withPresenting: UIViewController + ) async -> Result { + await withCheckedContinuation { continuation in + do { + let clientApplication = try createClientApplication() + + let webParameters = MSALWebviewParameters(authPresentationViewController: withPresenting) + let parameters = MSALInteractiveTokenParameters(scopes: scopes, webviewParameters: webParameters) + clientApplication.acquireToken(with: parameters) { result, error in + if let error = error { + continuation.resume(returning: .failure(error)) + return + } + + guard let result = result else { + continuation.resume( + returning: .failure( + SocialAuthError.error(text: CoreLocalization.Error.unknownError) + ) + ) + return + } + + self.result = result + let account = result.account + continuation.resume(returning: .success((account, result.accessToken))) + } + } catch let error { + continuation.resume(returning: .failure(error)) + } + } + } + + public func signOut() { + do { + let account = try? currentAccount() + + if let account = account { + let application = try createClientApplication() + try application.remove(account) + } + } catch let error { + debugLog("Logout", "Received error signing user out: \(error)") + } + } + + private func createClientApplication() throws -> MSALPublicClientApplication { + guard let config = Container.shared.resolve(ConfigProtocol.self), let appID = config.microsoft.appID else { + throw SocialAuthError.error(text: "Configuration error") + } + let configuration = MSALPublicClientApplicationConfig(clientId: appID) + + do { + return try MSALPublicClientApplication(configuration: configuration) + } catch { + throw SocialAuthError.error(text: error.localizedDescription) + } + } + + @discardableResult + private func currentAccount() throws -> MSALAccount { + let clientApplication = try createClientApplication() + + guard let account = try clientApplication.allAccounts().first else { + throw SocialAuthError.error(text: "Account not found") + } + + return account + } + + private func failure(_ error: Error?) -> Error { + if let error = error as? NSError, + let description = error.userInfo[MSALErrorDescriptionKey] as? String { + if let errorCode = MSALError(rawValue: error.code), case .userCanceled = errorCode { + return SocialAuthError.socialAuthCanceled + } + return SocialAuthError.error(text: description) + } + return error ?? SocialAuthError.error(text: CoreLocalization.Error.unknownError) + } + + func getUser(completion: (MSALAccount) -> Void) { + guard let user = result?.account else { return } + completion(user) + } + +} diff --git a/Core/Core/View/Base/LabelButton.swift b/Core/Core/View/Base/SocialAuthButton.swift similarity index 96% rename from Core/Core/View/Base/LabelButton.swift rename to Core/Core/View/Base/SocialAuthButton.swift index 2a739af4b..e18451a32 100644 --- a/Core/Core/View/Base/LabelButton.swift +++ b/Core/Core/View/Base/SocialAuthButton.swift @@ -7,7 +7,7 @@ import SwiftUI -public struct LabelButton: View { +public struct SocialAuthButton: View { // MARK: - Properties @@ -63,7 +63,7 @@ public struct LabelButton: View { #if DEBUG struct LabelButton_Previews: PreviewProvider { static var previews: some View { - LabelButton( + SocialAuthButton( image: CoreAssets.iconApple.swiftUIImage, title: "Apple", backgroundColor: CoreAssets.appleButtonColor.swiftUIColor, diff --git a/OpenEdX/AnalyticsManager.swift b/OpenEdX/AnalyticsManager.swift index 598aac053..5a9111fbe 100644 --- a/OpenEdX/AnalyticsManager.swift +++ b/OpenEdX/AnalyticsManager.swift @@ -28,7 +28,7 @@ class AnalyticsManager: AuthorizationAnalytics, } public func userLogin(method: LoginMethod) { - logEvent(.userLogin, parameters: [Key.method: method.rawValue]) + logEvent(.userLogin, parameters: [Key.method: method.analyticsValue]) } public func signUpClicked() { diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 714b16862..42fea0f0e 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -37,7 +37,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { initDI() if let config = Container.shared.resolve(ConfigProtocol.self) { - if let configuration = config.firebase.firebaseOptions { FirebaseApp.configure(options: configuration) Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) diff --git a/OpenEdX/Data/AppStorage.swift b/OpenEdX/Data/AppStorage.swift index 1a8cf3cc3..c0655026a 100644 --- a/OpenEdX/Data/AppStorage.swift +++ b/OpenEdX/Data/AppStorage.swift @@ -76,33 +76,19 @@ public class AppStorage: CoreStorage, ProfileStorage, WhatsNewStorage { } } - public var appleSignGivenName: String? { + public var appleSignFullName: String? { get { - return keychain.get(KEY_APPLE_SIGN_GIVENNAME) + return keychain.get(KEY_APPLE_SIGN_FULLNAME) } set(newValue) { if let newValue { - keychain.set(newValue, forKey: KEY_APPLE_SIGN_GIVENNAME) + keychain.set(newValue, forKey: KEY_APPLE_SIGN_FULLNAME) } else { - keychain.delete(KEY_APPLE_SIGN_GIVENNAME) + keychain.delete(KEY_APPLE_SIGN_FULLNAME) } } } - public var appleSignFamilyName: String? { - get { - return keychain.get(KEY_APPLE_SIGN_FAMILYNAME) - } - set(newValue) { - if let newValue { - keychain.set(newValue, forKey: KEY_APPLE_SIGN_FAMILYNAME) - } else { - keychain.delete(KEY_APPLE_SIGN_FAMILYNAME) - } - } - } - - public var appleSignEmail: String? { get { return keychain.get(KEY_APPLE_SIGN_EMAIL) @@ -220,7 +206,6 @@ public class AppStorage: CoreStorage, ProfileStorage, WhatsNewStorage { private let KEY_REVIEW_LAST_SHOWN_VERSION = "reviewLastShownVersion" private let KEY_REVIEW_LAST_REVIEW_DATE = "lastReviewDate" private let KEY_WHATSNEW_VERSION = "whatsNewVersion" - private let KEY_APPLE_SIGN_GIVENNAME = "appleSignGivenName" - private let KEY_APPLE_SIGN_FAMILYNAME = "appleSignFamilyName" + private let KEY_APPLE_SIGN_FULLNAME = "appleSignFullName" private let KEY_APPLE_SIGN_EMAIL = "appleSignEmail" } From 10bf6472e100d366faf5b9bd373e3f6e2e88ae7c Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 6 Dec 2023 15:42:27 +0100 Subject: [PATCH 30/38] chore: changes from PR feedback --- .../Presentation/Login/SignInViewModel.swift | 5 +++- .../Registration/SignUpViewModel.swift | 6 +++- .../SocialAuth/SocialAuthViewModel.swift | 2 +- .../en.lproj/Localizable.strings | 1 - .../Config/AppleSignInConfig.swift | 10 +++---- Core/Core/Configuration/Config/Config.swift | 8 ------ .../Configuration/Config/FacebookConfig.swift | 4 +-- .../Configuration/Config/GoogleConfig.swift | 4 +-- .../Config/MicrosoftConfig.swift | 4 +-- .../CoreTests/Configuration/ConfigTests.swift | 28 +++++++++++++++++++ OpenEdX/AppDelegate.swift | 2 +- 11 files changed, 50 insertions(+), 24 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 232040c94..410b57606 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -55,7 +55,10 @@ public class SignInViewModel: ObservableObject { } var socialLoginEnabled: Bool { - config.socialLoginEnabled + config.appleSignIn.enabled || + config.facebook.enabled || + config.microsoft.enabled || + config.google.enabled } @MainActor diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index bce5e9b43..fb09e27b3 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -54,7 +54,11 @@ public class SignUpViewModel: ObservableObject { } var socialLoginEnabled: Bool { - config.socialLoginEnabled && !isThirdPartyAuthSuccess && !isShowProgress + let socialLoginEnabled = config.appleSignIn.enabled || + config.facebook.enabled || + config.microsoft.enabled || + config.google.enabled + return socialLoginEnabled && !isThirdPartyAuthSuccess && !isShowProgress } private func showErrors(errors: [String: String]) -> Bool { diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift index a4763402c..ce01fd600 100644 --- a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -80,7 +80,7 @@ final public class SocialAuthViewModel: ObservableObject { /// must also offer Sign in with Apple as an equivalent option return true } - return config.appleSignIn.enable + return config.appleSignIn.enabled } // MARK: - Public Intens diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index 29717e3d6..5cd01832a 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -48,4 +48,3 @@ "STARTUP.SEARCH_TITLE" = "What do you want to learn?"; "STARTUP.SEARCH_PLACEHOLDER" = "Search our 3000+ courses"; "STARTUP.EXPLORE_ALL_COURSES" = "Explore all courses"; - diff --git a/Core/Core/Configuration/Config/AppleSignInConfig.swift b/Core/Core/Configuration/Config/AppleSignInConfig.swift index 824cfda73..8e5817e90 100644 --- a/Core/Core/Configuration/Config/AppleSignInConfig.swift +++ b/Core/Core/Configuration/Config/AppleSignInConfig.swift @@ -8,21 +8,21 @@ import Foundation private enum AppleSignInKeys: String { - case enable = "ENABLED" + case enabled = "ENABLED" } public class AppleSignInConfig: NSObject { - public var enable: Bool + public var enabled: Bool init(dictionary: [String: Any]) { - enable = dictionary[AppleSignInKeys.enable.rawValue] as? Bool ?? false + enabled = dictionary[AppleSignInKeys.enabled.rawValue] as? Bool ?? false super.init() } } -private let appleSignInKey = "APPLE_SIGNIN" +private let key = "APPLE_SIGNIN" extension Config { public var appleSignIn: AppleSignInConfig { - AppleSignInConfig(dictionary: self[appleSignInKey] as? [String: AnyObject] ?? [:]) + AppleSignInConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 027a3925d..0d52790e9 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -14,7 +14,6 @@ public protocol ConfigProtocol { var feedbackEmail: String { get } var appStoreLink: String { get } var platformName: String { get } - var socialLoginEnabled: Bool { get } var agreement: AgreementConfig { get } var firebase: FirebaseConfig { get } var facebook: FacebookConfig { get } @@ -137,13 +136,6 @@ extension Config: ConfigProtocol { public var appStoreLink: String { "itms-apps://itunes.apple.com/app/id\(appStoreId)?mt=8" } - - public var socialLoginEnabled: Bool { - appleSignIn.enable || - facebook.enabled || - microsoft.enabled || - google.enabled - } } // Mark - For testing and SwiftUI preview diff --git a/Core/Core/Configuration/Config/FacebookConfig.swift b/Core/Core/Configuration/Config/FacebookConfig.swift index 02f082b56..e8d7f1ad0 100644 --- a/Core/Core/Configuration/Config/FacebookConfig.swift +++ b/Core/Core/Configuration/Config/FacebookConfig.swift @@ -30,9 +30,9 @@ public final class FacebookConfig: NSObject { } } -private let facebookKey = "FACEBOOK" +private let key = "FACEBOOK" extension Config { public var facebook: FacebookConfig { - FacebookConfig(dictionary: self[facebookKey] as? [String: AnyObject] ?? [:]) + FacebookConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/Core/Core/Configuration/Config/GoogleConfig.swift b/Core/Core/Configuration/Config/GoogleConfig.swift index e5e45fb04..76eeef217 100644 --- a/Core/Core/Configuration/Config/GoogleConfig.swift +++ b/Core/Core/Configuration/Config/GoogleConfig.swift @@ -29,9 +29,9 @@ public final class GoogleConfig: NSObject { } } -private let googleKey = "GOOGLE" +private let key = "GOOGLE" extension Config { public var google: GoogleConfig { - GoogleConfig(dictionary: self[googleKey] as? [String: AnyObject] ?? [:]) + GoogleConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/Core/Core/Configuration/Config/MicrosoftConfig.swift b/Core/Core/Configuration/Config/MicrosoftConfig.swift index 16497b09f..4175a53e2 100644 --- a/Core/Core/Configuration/Config/MicrosoftConfig.swift +++ b/Core/Core/Configuration/Config/MicrosoftConfig.swift @@ -27,9 +27,9 @@ public final class MicrosoftConfig: NSObject { } } -private let microsoftKey = "MICROSOFT" +private let key = "MICROSOFT" extension Config { public var microsoft: MicrosoftConfig { - MicrosoftConfig(dictionary: self[microsoftKey] as? [String: AnyObject] ?? [:]) + MicrosoftConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) } } diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index 3881864fa..694ee6734 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -91,4 +91,32 @@ class ConfigTests: XCTestCase { XCTAssertEqual(config.firebase.isAnalyticsSourceFirebase, true) XCTAssertEqual(config.firebase.cloudMessagingEnabled, true) } + + func testGoogleConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.google.enabled) + XCTAssertEqual(config.google.clientID, "clientId") + } + + func testFacebookConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.facebook.enabled) + XCTAssertEqual(config.facebook.appID, "facebookAppId") + XCTAssertEqual(config.facebook.clientToken, "client_token") + } + + func testMicrosoftConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.microsoft.enabled) + XCTAssertEqual(config.microsoft.appID, "appId") + } + + func testAppleConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.appleSignIn.enabled) + } } diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 42fea0f0e..563d587f1 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -69,7 +69,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { - if let config = Container.shared.resolve(ConfigProtocol.self), config.socialLoginEnabled { + if let config = Container.shared.resolve(ConfigProtocol.self) { if config.facebook.enabled { ApplicationDelegate.shared.application( app, From 19bc1d0a589e3857dff96c2b3be2e0ca05753f01 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Wed, 6 Dec 2023 17:47:05 +0100 Subject: [PATCH 31/38] chore: add tests --- .../AuthorizationMock.generated.swift | 89 ++++++++++++++----- .../Login/SignInViewModelTests.swift | 2 +- .../Register/SignUpViewModelTests.swift | 24 ++--- Course/CourseTests/CourseMock.generated.swift | 89 ++++++++++++++----- .../DashboardMock.generated.swift | 89 ++++++++++++++----- .../DiscoveryMock.generated.swift | 89 ++++++++++++++----- .../DiscussionMock.generated.swift | 89 ++++++++++++++----- .../ProfileTests/ProfileMock.generated.swift | 89 ++++++++++++++----- 8 files changed, 427 insertions(+), 133 deletions(-) diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index 98aae1963..fd5dc338a 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -121,16 +138,16 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } - open func registerUser(fields: [String: String]) throws -> User { - addInvocation(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) - let perform = methodPerformValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void - perform?(`fields`) + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) var __value: User do { - __value = try methodReturnValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for registerUser(fields: [String: String]). Use given") - Failure("Stub return value not specified for registerUser(fields: [String: String]). Use given") + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") } catch { throw error } @@ -156,10 +173,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) case m_getRegistrationFields - case m_registerUser__fields_fields(Parameter<[String: String]>) + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { @@ -170,6 +188,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) @@ -182,9 +206,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { case (.m_getRegistrationFields, .m_getRegistrationFields): return .match - case (.m_registerUser__fields_fields(let lhsFields), .m_registerUser__fields_fields(let rhsFields)): + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) return Matcher.ComparisonResult(results) case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): @@ -198,20 +223,22 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue case .m_getRegistrationFields: return 0 - case let .m_registerUser__fields_fields(p0): return p0.intValue + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" case .m_getRegistrationFields: return ".getRegistrationFields()" - case .m_registerUser__fields_fields: return ".registerUser(fields:)" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" } } @@ -230,14 +257,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func registerUser(fields: Parameter<[String: String]>, willReturn: User...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -254,6 +285,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -284,12 +327,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } - public static func registerUser(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func registerUser(fields: Parameter<[String: String]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (User).self) willProduce(stubber) return given @@ -311,10 +354,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} - public static func registerUser(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_registerUser__fields_fields(`fields`))} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} } @@ -326,6 +371,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) } @@ -335,8 +384,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_getRegistrationFields, performs: perform) } - public static func registerUser(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { - return Perform(method: .m_registerUser__fields_fields(`fields`), performs: perform) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index 872219ba8..744e24ba3 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -61,7 +61,7 @@ final class SignInViewModelTests: XCTestCase { Verify(interactor, 0, .login(username: .any, password: .any)) Verify(router, 0, .showMainOrWhatsNewScreen()) - XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidPasswordLength) + XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidPasswordLenght) XCTAssertEqual(viewModel.isShowProgress, false) } diff --git a/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift index b59519b27..77f9ad85e 100644 --- a/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Register/SignUpViewModelTests.swift @@ -117,7 +117,7 @@ final class SignUpViewModelTests: XCTestCase { validator: validator ) - Given(interactor, .registerUser(fields: .any, willReturn: .init(id: 1, + Given(interactor, .registerUser(fields: .any, isSocial: .any, willReturn: .init(id: 1, username: "Name", email: "mail", name: "name", @@ -127,7 +127,7 @@ final class SignUpViewModelTests: XCTestCase { await viewModel.registerUser() Verify(interactor, 1, .validateRegistrationFields(fields: .any)) - Verify(interactor, 1, .registerUser(fields: .any)) + Verify(interactor, 1, .registerUser(fields: .any, isSocial: .any)) Verify(router, 1, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) @@ -158,12 +158,12 @@ final class SignUpViewModelTests: XCTestCase { ] Given(interactor, .validateRegistrationFields(fields: .any, willReturn: ["email": "invalid email"])) - Given(interactor, .registerUser(fields: .any, willProduce: {_ in})) - + Given(interactor, .registerUser(fields: .any, isSocial: .any, willProduce: {_ in})) + await viewModel.registerUser() Verify(interactor, 1, .validateRegistrationFields(fields: .any)) - Verify(interactor, 0, .registerUser(fields: .any)) + Verify(interactor, 0, .registerUser(fields: .any, isSocial: .any)) Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) @@ -186,12 +186,12 @@ final class SignUpViewModelTests: XCTestCase { ) Given(interactor, .validateRegistrationFields(fields: .any, willReturn: [:])) - Given(interactor, .registerUser(fields: .any, willThrow: APIError.invalidGrant)) - + Given(interactor, .registerUser(fields: .any, isSocial: .any, willThrow: APIError.invalidGrant)) + await viewModel.registerUser() Verify(interactor, 1, .validateRegistrationFields(fields: .any)) - Verify(interactor, 1, .registerUser(fields: .any)) + Verify(interactor, 1, .registerUser(fields: .any, isSocial: .any)) Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) @@ -214,12 +214,12 @@ final class SignUpViewModelTests: XCTestCase { ) Given(interactor, .validateRegistrationFields(fields: .any, willReturn: [:])) - Given(interactor, .registerUser(fields: .any, willThrow: NSError())) + Given(interactor, .registerUser(fields: .any, isSocial: .any, willThrow: NSError())) await viewModel.registerUser() Verify(interactor, 1, .validateRegistrationFields(fields: .any)) - Verify(interactor, 1, .registerUser(fields: .any)) + Verify(interactor, 1, .registerUser(fields: .any, isSocial: .any)) Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) @@ -243,13 +243,13 @@ final class SignUpViewModelTests: XCTestCase { let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) - Given(interactor, .registerUser(fields: .any, willThrow: noInternetError)) + Given(interactor, .registerUser(fields: .any, isSocial: .any, willThrow: noInternetError)) Given(interactor, .validateRegistrationFields(fields: .any, willReturn: [:])) await viewModel.registerUser() Verify(interactor, 1, .validateRegistrationFields(fields: .any)) - Verify(interactor, 1, .registerUser(fields: .any)) + Verify(interactor, 1, .registerUser(fields: .any, isSocial: .any)) Verify(router, 0, .showMainOrWhatsNewScreen()) XCTAssertEqual(viewModel.isShowProgress, false) diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index 0c093e89c..ef2212269 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -121,16 +138,16 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } - open func registerUser(fields: [String: String]) throws -> User { - addInvocation(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) - let perform = methodPerformValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void - perform?(`fields`) + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) var __value: User do { - __value = try methodReturnValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for registerUser(fields: [String: String]). Use given") - Failure("Stub return value not specified for registerUser(fields: [String: String]). Use given") + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") } catch { throw error } @@ -156,10 +173,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) case m_getRegistrationFields - case m_registerUser__fields_fields(Parameter<[String: String]>) + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { @@ -170,6 +188,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) @@ -182,9 +206,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { case (.m_getRegistrationFields, .m_getRegistrationFields): return .match - case (.m_registerUser__fields_fields(let lhsFields), .m_registerUser__fields_fields(let rhsFields)): + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) return Matcher.ComparisonResult(results) case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): @@ -198,20 +223,22 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue case .m_getRegistrationFields: return 0 - case let .m_registerUser__fields_fields(p0): return p0.intValue + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" case .m_getRegistrationFields: return ".getRegistrationFields()" - case .m_registerUser__fields_fields: return ".registerUser(fields:)" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" } } @@ -230,14 +257,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func registerUser(fields: Parameter<[String: String]>, willReturn: User...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -254,6 +285,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -284,12 +327,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } - public static func registerUser(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func registerUser(fields: Parameter<[String: String]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (User).self) willProduce(stubber) return given @@ -311,10 +354,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} - public static func registerUser(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_registerUser__fields_fields(`fields`))} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} } @@ -326,6 +371,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) } @@ -335,8 +384,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_getRegistrationFields, performs: perform) } - public static func registerUser(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { - return Perform(method: .m_registerUser__fields_fields(`fields`), performs: perform) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index ba988cfff..70acae430 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -121,16 +138,16 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } - open func registerUser(fields: [String: String]) throws -> User { - addInvocation(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) - let perform = methodPerformValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void - perform?(`fields`) + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) var __value: User do { - __value = try methodReturnValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for registerUser(fields: [String: String]). Use given") - Failure("Stub return value not specified for registerUser(fields: [String: String]). Use given") + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") } catch { throw error } @@ -156,10 +173,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) case m_getRegistrationFields - case m_registerUser__fields_fields(Parameter<[String: String]>) + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { @@ -170,6 +188,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) @@ -182,9 +206,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { case (.m_getRegistrationFields, .m_getRegistrationFields): return .match - case (.m_registerUser__fields_fields(let lhsFields), .m_registerUser__fields_fields(let rhsFields)): + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) return Matcher.ComparisonResult(results) case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): @@ -198,20 +223,22 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue case .m_getRegistrationFields: return 0 - case let .m_registerUser__fields_fields(p0): return p0.intValue + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" case .m_getRegistrationFields: return ".getRegistrationFields()" - case .m_registerUser__fields_fields: return ".registerUser(fields:)" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" } } @@ -230,14 +257,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func registerUser(fields: Parameter<[String: String]>, willReturn: User...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -254,6 +285,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -284,12 +327,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } - public static func registerUser(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func registerUser(fields: Parameter<[String: String]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (User).self) willProduce(stubber) return given @@ -311,10 +354,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} - public static func registerUser(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_registerUser__fields_fields(`fields`))} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} } @@ -326,6 +371,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) } @@ -335,8 +384,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_getRegistrationFields, performs: perform) } - public static func registerUser(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { - return Perform(method: .m_registerUser__fields_fields(`fields`), performs: perform) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index b95cb9f0d..b4d088a28 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -121,16 +138,16 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } - open func registerUser(fields: [String: String]) throws -> User { - addInvocation(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) - let perform = methodPerformValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void - perform?(`fields`) + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) var __value: User do { - __value = try methodReturnValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for registerUser(fields: [String: String]). Use given") - Failure("Stub return value not specified for registerUser(fields: [String: String]). Use given") + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") } catch { throw error } @@ -156,10 +173,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) case m_getRegistrationFields - case m_registerUser__fields_fields(Parameter<[String: String]>) + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { @@ -170,6 +188,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) @@ -182,9 +206,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { case (.m_getRegistrationFields, .m_getRegistrationFields): return .match - case (.m_registerUser__fields_fields(let lhsFields), .m_registerUser__fields_fields(let rhsFields)): + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) return Matcher.ComparisonResult(results) case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): @@ -198,20 +223,22 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue case .m_getRegistrationFields: return 0 - case let .m_registerUser__fields_fields(p0): return p0.intValue + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" case .m_getRegistrationFields: return ".getRegistrationFields()" - case .m_registerUser__fields_fields: return ".registerUser(fields:)" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" } } @@ -230,14 +257,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func registerUser(fields: Parameter<[String: String]>, willReturn: User...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -254,6 +285,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -284,12 +327,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } - public static func registerUser(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func registerUser(fields: Parameter<[String: String]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (User).self) willProduce(stubber) return given @@ -311,10 +354,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} - public static func registerUser(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_registerUser__fields_fields(`fields`))} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} } @@ -326,6 +371,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) } @@ -335,8 +384,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_getRegistrationFields, performs: perform) } - public static func registerUser(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { - return Perform(method: .m_registerUser__fields_fields(`fields`), performs: perform) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index 5f2e17f42..1afce2ae3 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -121,16 +138,16 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } - open func registerUser(fields: [String: String]) throws -> User { - addInvocation(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) - let perform = methodPerformValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void - perform?(`fields`) + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) var __value: User do { - __value = try methodReturnValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for registerUser(fields: [String: String]). Use given") - Failure("Stub return value not specified for registerUser(fields: [String: String]). Use given") + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") } catch { throw error } @@ -156,10 +173,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) case m_getRegistrationFields - case m_registerUser__fields_fields(Parameter<[String: String]>) + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { @@ -170,6 +188,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) @@ -182,9 +206,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { case (.m_getRegistrationFields, .m_getRegistrationFields): return .match - case (.m_registerUser__fields_fields(let lhsFields), .m_registerUser__fields_fields(let rhsFields)): + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) return Matcher.ComparisonResult(results) case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): @@ -198,20 +223,22 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue case .m_getRegistrationFields: return 0 - case let .m_registerUser__fields_fields(p0): return p0.intValue + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" case .m_getRegistrationFields: return ".getRegistrationFields()" - case .m_registerUser__fields_fields: return ".registerUser(fields:)" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" } } @@ -230,14 +257,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func registerUser(fields: Parameter<[String: String]>, willReturn: User...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -254,6 +285,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -284,12 +327,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } - public static func registerUser(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func registerUser(fields: Parameter<[String: String]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (User).self) willProduce(stubber) return given @@ -311,10 +354,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} - public static func registerUser(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_registerUser__fields_fields(`fields`))} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} } @@ -326,6 +371,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) } @@ -335,8 +384,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_getRegistrationFields, performs: perform) } - public static func registerUser(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { - return Perform(method: .m_registerUser__fields_fields(`fields`), performs: perform) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index 0ebcec11c..69aff547e 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -76,6 +76,23 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } + @discardableResult + open func login(externalToken: String, backend: String) throws -> User { + addInvocation(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) + let perform = methodPerformValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))) as? (String, String) -> Void + perform?(`externalToken`, `backend`) + var __value: User + do { + __value = try methodReturnValue(.m_login__externalToken_externalTokenbackend_backend(Parameter.value(`externalToken`), Parameter.value(`backend`))).casted() + } catch MockError.notStubed { + onFatalFailure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + Failure("Stub return value not specified for login(externalToken: String, backend: String). Use given") + } catch { + throw error + } + return __value + } + open func resetPassword(email: String) throws -> ResetPassword { addInvocation(.m_resetPassword__email_email(Parameter.value(`email`))) let perform = methodPerformValue(.m_resetPassword__email_email(Parameter.value(`email`))) as? (String) -> Void @@ -121,16 +138,16 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { return __value } - open func registerUser(fields: [String: String]) throws -> User { - addInvocation(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) - let perform = methodPerformValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))) as? ([String: String]) -> Void - perform?(`fields`) + open func registerUser(fields: [String: String], isSocial: Bool) throws -> User { + addInvocation(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) + let perform = methodPerformValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))) as? ([String: String], Bool) -> Void + perform?(`fields`, `isSocial`) var __value: User do { - __value = try methodReturnValue(.m_registerUser__fields_fields(Parameter<[String: String]>.value(`fields`))).casted() + __value = try methodReturnValue(.m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>.value(`fields`), Parameter.value(`isSocial`))).casted() } catch MockError.notStubed { - onFatalFailure("Stub return value not specified for registerUser(fields: [String: String]). Use given") - Failure("Stub return value not specified for registerUser(fields: [String: String]). Use given") + onFatalFailure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") + Failure("Stub return value not specified for registerUser(fields: [String: String], isSocial: Bool). Use given") } catch { throw error } @@ -156,10 +173,11 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { fileprivate enum MethodType { case m_login__username_usernamepassword_password(Parameter, Parameter) + case m_login__externalToken_externalTokenbackend_backend(Parameter, Parameter) case m_resetPassword__email_email(Parameter) case m_getCookies__force_force(Parameter) case m_getRegistrationFields - case m_registerUser__fields_fields(Parameter<[String: String]>) + case m_registerUser__fields_fieldsisSocial_isSocial(Parameter<[String: String]>, Parameter) case m_validateRegistrationFields__fields_fields(Parameter<[String: String]>) static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { @@ -170,6 +188,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsPassword, rhs: rhsPassword, with: matcher), lhsPassword, rhsPassword, "password")) return Matcher.ComparisonResult(results) + case (.m_login__externalToken_externalTokenbackend_backend(let lhsExternaltoken, let lhsBackend), .m_login__externalToken_externalTokenbackend_backend(let rhsExternaltoken, let rhsBackend)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsExternaltoken, rhs: rhsExternaltoken, with: matcher), lhsExternaltoken, rhsExternaltoken, "externalToken")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsBackend, rhs: rhsBackend, with: matcher), lhsBackend, rhsBackend, "backend")) + return Matcher.ComparisonResult(results) + case (.m_resetPassword__email_email(let lhsEmail), .m_resetPassword__email_email(let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) @@ -182,9 +206,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { case (.m_getRegistrationFields, .m_getRegistrationFields): return .match - case (.m_registerUser__fields_fields(let lhsFields), .m_registerUser__fields_fields(let rhsFields)): + case (.m_registerUser__fields_fieldsisSocial_isSocial(let lhsFields, let lhsIssocial), .m_registerUser__fields_fieldsisSocial_isSocial(let rhsFields, let rhsIssocial)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFields, rhs: rhsFields, with: matcher), lhsFields, rhsFields, "fields")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsIssocial, rhs: rhsIssocial, with: matcher), lhsIssocial, rhsIssocial, "isSocial")) return Matcher.ComparisonResult(results) case (.m_validateRegistrationFields__fields_fields(let lhsFields), .m_validateRegistrationFields__fields_fields(let rhsFields)): @@ -198,20 +223,22 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { func intValue() -> Int { switch self { case let .m_login__username_usernamepassword_password(p0, p1): return p0.intValue + p1.intValue + case let .m_login__externalToken_externalTokenbackend_backend(p0, p1): return p0.intValue + p1.intValue case let .m_resetPassword__email_email(p0): return p0.intValue case let .m_getCookies__force_force(p0): return p0.intValue case .m_getRegistrationFields: return 0 - case let .m_registerUser__fields_fields(p0): return p0.intValue + case let .m_registerUser__fields_fieldsisSocial_isSocial(p0, p1): return p0.intValue + p1.intValue case let .m_validateRegistrationFields__fields_fields(p0): return p0.intValue } } func assertionName() -> String { switch self { case .m_login__username_usernamepassword_password: return ".login(username:password:)" + case .m_login__externalToken_externalTokenbackend_backend: return ".login(externalToken:backend:)" case .m_resetPassword__email_email: return ".resetPassword(email:)" case .m_getCookies__force_force: return ".getCookies(force:)" case .m_getRegistrationFields: return ".getRegistrationFields()" - case .m_registerUser__fields_fields: return ".registerUser(fields:)" + case .m_registerUser__fields_fieldsisSocial_isSocial: return ".registerUser(fields:isSocial:)" case .m_validateRegistrationFields__fields_fields: return ".validateRegistrationFields(fields:)" } } @@ -230,14 +257,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, willReturn: User...) -> MethodStub { return Given(method: .m_login__username_usernamepassword_password(`username`, `password`), products: willReturn.map({ StubProduct.return($0 as Any) })) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willReturn.map({ StubProduct.return($0 as Any) })) + } public static func resetPassword(email: Parameter, willReturn: ResetPassword...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func getRegistrationFields(willReturn: [PickerFields]...) -> MethodStub { return Given(method: .m_getRegistrationFields, products: willReturn.map({ StubProduct.return($0 as Any) })) } - public static func registerUser(fields: Parameter<[String: String]>, willReturn: User...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willReturn: User...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willReturn.map({ StubProduct.return($0 as Any) })) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, willReturn: [String: String]...) -> MethodStub { return Given(method: .m_validateRegistrationFields__fields_fields(`fields`), products: willReturn.map({ StubProduct.return($0 as Any) })) @@ -254,6 +285,18 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) + } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { + let willThrow: [Error] = [] + let given: Given = { return Given(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), products: willThrow.map({ StubProduct.throw($0) })) }() + let stubber = given.stubThrows(for: (User).self) + willProduce(stubber) + return given + } public static func resetPassword(email: Parameter, willThrow: Error...) -> MethodStub { return Given(method: .m_resetPassword__email_email(`email`), products: willThrow.map({ StubProduct.throw($0) })) } @@ -284,12 +327,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { willProduce(stubber) return given } - public static func registerUser(fields: Parameter<[String: String]>, willThrow: Error...) -> MethodStub { - return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willThrow: Error...) -> MethodStub { + return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) } - public static func registerUser(fields: Parameter<[String: String]>, willProduce: (StubberThrows) -> Void) -> MethodStub { + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, willProduce: (StubberThrows) -> Void) -> MethodStub { let willThrow: [Error] = [] - let given: Given = { return Given(method: .m_registerUser__fields_fields(`fields`), products: willThrow.map({ StubProduct.throw($0) })) }() + let given: Given = { return Given(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), products: willThrow.map({ StubProduct.throw($0) })) }() let stubber = given.stubThrows(for: (User).self) willProduce(stubber) return given @@ -311,10 +354,12 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { @discardableResult public static func login(username: Parameter, password: Parameter) -> Verify { return Verify(method: .m_login__username_usernamepassword_password(`username`, `password`))} + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter) -> Verify { return Verify(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`))} public static func resetPassword(email: Parameter) -> Verify { return Verify(method: .m_resetPassword__email_email(`email`))} public static func getCookies(force: Parameter) -> Verify { return Verify(method: .m_getCookies__force_force(`force`))} public static func getRegistrationFields() -> Verify { return Verify(method: .m_getRegistrationFields)} - public static func registerUser(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_registerUser__fields_fields(`fields`))} + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter) -> Verify { return Verify(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`))} public static func validateRegistrationFields(fields: Parameter<[String: String]>) -> Verify { return Verify(method: .m_validateRegistrationFields__fields_fields(`fields`))} } @@ -326,6 +371,10 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func login(username: Parameter, password: Parameter, perform: @escaping (String, String) -> Void) -> Perform { return Perform(method: .m_login__username_usernamepassword_password(`username`, `password`), performs: perform) } + @discardableResult + public static func login(externalToken: Parameter, backend: Parameter, perform: @escaping (String, String) -> Void) -> Perform { + return Perform(method: .m_login__externalToken_externalTokenbackend_backend(`externalToken`, `backend`), performs: perform) + } public static func resetPassword(email: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_resetPassword__email_email(`email`), performs: perform) } @@ -335,8 +384,8 @@ open class AuthInteractorProtocolMock: AuthInteractorProtocol, Mock { public static func getRegistrationFields(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_getRegistrationFields, performs: perform) } - public static func registerUser(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { - return Perform(method: .m_registerUser__fields_fields(`fields`), performs: perform) + public static func registerUser(fields: Parameter<[String: String]>, isSocial: Parameter, perform: @escaping ([String: String], Bool) -> Void) -> Perform { + return Perform(method: .m_registerUser__fields_fieldsisSocial_isSocial(`fields`, `isSocial`), performs: perform) } public static func validateRegistrationFields(fields: Parameter<[String: String]>, perform: @escaping ([String: String]) -> Void) -> Perform { return Perform(method: .m_validateRegistrationFields__fields_fields(`fields`), performs: perform) From 05b5c80935721c9f2ae3abb2235138355914ffda Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 10:16:48 +0100 Subject: [PATCH 32/38] chore: changes from PR feedback --- .../Presentation/Login/SignInView.swift | 2 +- .../Presentation/Login/SignInViewModel.swift | 42 +++++------ .../Registration/SignUpView.swift | 2 +- .../Registration/SignUpViewModel.swift | 70 +++++++------------ .../SocialAuth/SocialAuthView.swift | 12 ++-- .../SocialAuth/SocialAuthViewModel.swift | 12 ++-- .../Authorization/SwiftGen/Strings.swift | 10 +-- .../en.lproj/Localizable.strings | 7 +- .../uk.lproj/Localizable.strings | 6 +- Core/Core.xcodeproj/project.pbxproj | 4 ++ .../Core/Data/Repository/AuthRepository.swift | 2 +- Core/Core/Network/AuthEndpoint.swift | 8 +-- .../SocialAuth/AppleAuthProvider.swift | 16 ++--- .../SocialAuth/Error/SocialAuthError.swift | 2 +- .../SocialAuth/FacebookAuthProvider.swift | 40 ++++++++--- .../SocialAuth/GoogleAuthProvider.swift | 14 +++- .../SocialAuth/MicrosoftAuthProvider.swift | 15 +++- .../SocialAuth/SocialAuthResponse.swift | 20 ++++++ Core/Core/View/Base/SocialAuthButton.swift | 2 +- 19 files changed, 164 insertions(+), 122 deletions(-) create mode 100644 Core/Core/Providers/SocialAuth/SocialAuthResponse.swift diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index d96ad5d4d..4f055de47 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -133,7 +133,7 @@ public struct SignInView: View { SocialAuthView( viewModel: .init( config: viewModel.config, - completion: { viewModel.sign(with: $0) } + completion: { viewModel.login(with: $0) } ) ) } diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 410b57606..5e96fb0c2 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -84,61 +84,61 @@ public class SignInViewModel: ObservableObject { } @MainActor - func sign(with result: Result) { - result.success { social(result: $0) } + func login(with result: Result) { + result.success { socialAuth(result: $0) } result.failure { error in errorMessage = error.localizedDescription } } @MainActor - private func social(result: SocialAuthDetails) { + private func socialAuth(result: SocialAuthDetails) { switch result { - case .apple(let appleCredentials): - appleLogin(appleCredentials, backend: result.backend) - case .facebook: - facebookLogin(backend: result.backend) - case .google(let gIDSignInResult): - googleLogin(gIDSignInResult, backend: result.backend) - case .microsoft(_, let token): - microsoftLogin(token, backend: result.backend) + case .apple(let response): + appleLogin(response, backend: result.backend) + case .facebook(let response): + facebookLogin(response, backend: result.backend) + case .google(let response): + googleLogin(response, backend: result.backend) + case .microsoft(let response): + microsoftLogin(response, backend: result.backend) } } @MainActor - private func appleLogin(_ credentials: AppleCredentials, backend: String) { + private func appleLogin(_ response: SocialAuthResponse, backend: String) { socialLogin( - externalToken: credentials.token, + externalToken: response.token, backend: backend, loginMethod: .socailAuth(.apple) ) } @MainActor - private func facebookLogin(backend: String) { + private func facebookLogin(_ response: SocialAuthResponse, backend: String) { guard let currentAccessToken = AccessToken.current?.tokenString else { return } socialLogin( - externalToken: currentAccessToken, + externalToken: response.token, backend: backend, loginMethod: .socailAuth(.facebook) ) } @MainActor - private func googleLogin(_ result: GIDSignInResult, backend: String) { + private func googleLogin(_ response: SocialAuthResponse, backend: String) { socialLogin( - externalToken: result.user.accessToken.tokenString, + externalToken: response.token, backend: backend, loginMethod: .socailAuth(.google) ) } @MainActor - private func microsoftLogin(_ token: String, backend: String) { + private func microsoftLogin(_ response: SocialAuthResponse, backend: String) { socialLogin( - externalToken: token, + externalToken: response.token, backend: backend, loginMethod: .socailAuth(.microsoft) ) @@ -165,12 +165,12 @@ public class SignInViewModel: ObservableObject { if let validationError = error.validationError, let value = validationError.data?["error_description"] as? String { if loginMethod != .password, validationError.statusCode == 400, let loginMethod = loginMethod { - errorMessage = AuthLocalization.Error.authProvider( + errorMessage = AuthLocalization.Error.accountNotRegistered( loginMethod.analyticsValue, config.platformName ) } else if validationError.statusCode == 403 { - errorMessage = AuthLocalization.Error.accountDisabled + errorMessage = AuthLocalization.Error.disabledAccount } else { errorMessage = value } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index 4e5cdba68..fd4e9b070 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -120,7 +120,7 @@ public struct SignUpView: View { if viewModel.socialLoginEnabled, !requiredFields.isEmpty { SocialAuthView( - signType: .register, + authType: .register, viewModel: .init( config: viewModel.config ) { viewModel.register(with: $0) } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index fb09e27b3..a1e497ca9 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -156,29 +156,29 @@ public class SignUpViewModel: ObservableObject { @MainActor private func socialAuth(result: SocialAuthDetails) { switch result { - case .apple(let appleCredentials): - appleRegister(appleCredentials, backend: result.backend, loginMethod: .socailAuth(.apple)) - case .facebook(let account): - facebookRegister(backend: result.backend, account: account, loginMethod: .socailAuth(.facebook)) - case .google(let gIDSignInResult): - googleRegister(gIDSignInResult, backend: result.backend, loginMethod: .socailAuth(.google)) - case .microsoft(let account, let token): - microsoftRegister(token, backend: result.backend, account: account, loginMethod: .socailAuth(.microsoft)) + case .apple(let response): + appleRegister(response, backend: result.backend, loginMethod: .socailAuth(.apple)) + case .facebook(let response): + facebookRegister(response, backend: result.backend, loginMethod: .socailAuth(.facebook)) + case .google(let response): + googleRegister(response, backend: result.backend, loginMethod: .socailAuth(.google)) + case .microsoft(let response): + microsoftRegister(response, backend: result.backend, loginMethod: .socailAuth(.microsoft)) } } @MainActor private func appleRegister( - _ credentials: AppleCredentials, + _ response: SocialAuthResponse, backend: String, loginMethod: LoginMethod ) { update( - fullName: credentials.name, - email: credentials.email + fullName: response.name, + email: response.email ) registerSocial( - externalToken: credentials.token, + externalToken: response.token, backend: backend, loginMethod: loginMethod ) @@ -186,29 +186,16 @@ public class SignUpViewModel: ObservableObject { @MainActor private func facebookRegister( + _ response: SocialAuthResponse, backend: String, - account: LoginManagerLoginResult, loginMethod: LoginMethod ) { - guard let currentAccessToken = AccessToken.current?.tokenString else { - return - } - - GraphRequest( - graphPath: "me", - parameters: ["fields": "name, email"] - ).start { [weak self] _, result, _ in - guard let self = self, let userInfo = result as? [String: Any] else { - return - } - self.update( - fullName: userInfo["name"] as? String, - email: userInfo["email"] as? String - ) - } - + update( + fullName: response.name, + email: response.email + ) registerSocial( - externalToken: currentAccessToken, + externalToken: response.token, backend: backend, loginMethod: loginMethod ) @@ -217,17 +204,16 @@ public class SignUpViewModel: ObservableObject { @MainActor private func googleRegister( - _ result: GIDSignInResult, + _ response: SocialAuthResponse, backend: String, loginMethod: LoginMethod ) { update( - fullName: result.user.profile?.name, - email: result.user.profile?.email + fullName: response.name, + email: response.email ) - registerSocial( - externalToken: result.user.accessToken.tokenString, + externalToken: response.token, backend: backend, loginMethod: loginMethod ) @@ -235,19 +221,17 @@ public class SignUpViewModel: ObservableObject { @MainActor private func microsoftRegister( - _ token: String, + _ response: SocialAuthResponse, backend: String, - account: MSALAccount, loginMethod: LoginMethod ) { update( - fullName: account.accountClaims?["name"] as? String, - email: account.accountClaims?["email"] as? String + fullName: response.name, + email: response.email ) - registerSocial( - externalToken: token, - backend: backend, + externalToken: response.token, + backend: backend, loginMethod: loginMethod ) } diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift index f598cc8e3..2e91ebf82 100644 --- a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift @@ -1,5 +1,5 @@ // -// SocialSignView.swift +// SocialAuthView.swift // Authorization // // Created by Eugene Yatsenko on 10.10.2023. @@ -15,25 +15,25 @@ struct SocialAuthView: View { @StateObject var viewModel: SocialAuthViewModel init( - signType: SocialAuthType = .signIn, + authType: SocialAuthType = .signIn, viewModel: SocialAuthViewModel ) { self._viewModel = .init(wrappedValue: viewModel) - self.signType = signType + self.authType = authType } enum SocialAuthType { case signIn case register } - var signType: SocialAuthType = .signIn + var authType: SocialAuthType = .signIn private var title: String { - switch signType { + switch authType { case .signIn: AuthLocalization.signInWith case .register: - AuthLocalization.signInRegister + AuthLocalization.registerWith } } diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift index ce01fd600..75aa6b718 100644 --- a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -1,5 +1,5 @@ // -// SocialSignViewModel.swift +// SocialAuthViewModel.swift // Authorization // // Created by Eugene Yatsenko on 11.10.2023. @@ -14,10 +14,10 @@ import MSAL import Swinject enum SocialAuthDetails { - case apple(AppleCredentials) - case facebook(LoginManagerLoginResult) - case google(GIDSignInResult) - case microsoft(MSALAccount, String) + case apple(SocialAuthResponse) + case facebook(SocialAuthResponse) + case google(SocialAuthResponse) + case microsoft(SocialAuthResponse) var backend: String { switch self { @@ -119,7 +119,7 @@ final public class SocialAuthViewModel: ObservableObject { return } let result = await microsoftAuthProvider.signIn(withPresenting: vc) - result.success { success(with: .microsoft($0.account, $0.token) ) } + result.success { success(with: .microsoft($0)) } result.failure(failure) } diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index e305336e6..a2cd9accf 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -21,16 +21,16 @@ public enum AuthLocalization { /// Or public static let or = AuthLocalization.tr("Localizable", "OR", fallback: "Or") /// Register with - public static let signInRegister = AuthLocalization.tr("Localizable", "SIGN_IN_REGISTER", fallback: "Register with") + public static let registerWith = AuthLocalization.tr("Localizable", "REGISTER_WITH", fallback: "Register with") /// Sign in with public static let signInWith = AuthLocalization.tr("Localizable", "SIGN_IN_WITH", fallback: "Sign in with") public enum Error { - /// Your account is disabled. Please contact customer support for assistance. - public static let accountDisabled = AuthLocalization.tr("Localizable", "ERROR.ACCOUNT_DISABLED", fallback: "Your account is disabled. Please contact customer support for assistance.") /// This %@ account is not linked with any %@ account. Please register. - public static func authProvider(_ p1: Any, _ p2: Any) -> String { - return AuthLocalization.tr("Localizable", "ERROR.AUTH_PROVIDER", String(describing: p1), String(describing: p2), fallback: "This %@ account is not linked with any %@ account. Please register.") + public static func accountNotRegistered(_ p1: Any, _ p2: Any) -> String { + return AuthLocalization.tr("Localizable", "ERROR.ACCOUNT_NOT_REGISTERED", String(describing: p1), String(describing: p2), fallback: "This %@ account is not linked with any %@ account. Please register.") } + /// Your account is disabled. Please contact customer support for assistance. + public static let disabledAccount = AuthLocalization.tr("Localizable", "ERROR.DISABLED_ACCOUNT", fallback: "Your account is disabled. Please contact customer support for assistance.") /// Invalid email address public static let invalidEmailAddress = AuthLocalization.tr("Localizable", "ERROR.INVALID_EMAIL_ADDRESS", fallback: "Invalid email address") /// Invalid email or username diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index 5cd01832a..50807ea57 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -17,9 +17,9 @@ "ERROR.INVALID_EMAIL_ADDRESS" = "Invalid email address"; "ERROR.INVALID_PASSWORD_LENGHT" = "Invalid password lenght"; -"ERROR.AUTH_PROVIDER" = "This %@ account is not linked with any %@ account. Please register."; +"ERROR.ACCOUNT_NOT_REGISTERED" = "This %@ account is not linked with any %@ account. Please register."; "ERROR.INVALID_EMAIL_ADDRESS_OR_USERNAME" = "Invalid email or username"; -"ERROR.ACCOUNT_DISABLED" = "Your account is disabled. Please contact customer support for assistance."; +"ERROR.DISABLED_ACCOUNT" = "Your account is disabled. Please contact customer support for assistance."; "SIGN_UP.TITLE" = "Sign up"; "SIGN_UP.SUBTITLE" = "Create new account."; @@ -35,9 +35,8 @@ "FORGOT.CHECK_TITLE" = "Check your email"; "FORGOT.CHECK_Description" = "We have sent a password recover instructions to your email "; - "SIGN_IN_WITH" = "Sign in with"; -"SIGN_IN_REGISTER" = "Register with"; +"REGISTER_WITH" = "Register with"; "APPLE" = "Apple"; "GOOGLE" = "Google"; "FACEBOOK" = "Facebook"; diff --git a/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings index 4bc6819ca..5cbcbb2d8 100644 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ b/Authorization/Authorization/uk.lproj/Localizable.strings @@ -16,8 +16,8 @@ "ERROR.INVALID_EMAIL_ADDRESS" = "невірна адреса електронної пошти"; "ERROR.INVALID_PASSWORD_LENGHT" = "Пароль занадто короткий або занадто довгий"; -"ERROR.AUTH_PROVIDER" = "This %@ account is not linked with any %@ account. Please register."; -"ERROR.ACCOUNT_DISABLED" = "Your account is disabled. Please contact customer support for assistance."; +"ERROR.ACCOUNT_NOT_REGISTERED" = "This %@ account is not linked with any %@ account. Please register."; +"ERROR.DISABLED_ACCOUNT" = "Your account is disabled. Please contact customer support for assistance."; "SIGN_UP.TITLE" = "Зареєструватись"; "SIGN_UP.SUBTITLE" = "Cтворити новий акаунт."; @@ -34,7 +34,7 @@ "FORGOT.CHECK_Description" = "Ми надіслали інструкції щодо відновлення пароля на вашу електронну пошту "; "SIGN_IN_WITH" = "Sign in with"; -"SIGN_IN_REGISTER" = "Register with"; +"REGISTER_WITH" = "Register with"; "APPLE" = "Apple"; "GOOGLE" = "Google"; "FACEBOOK" = "Facebook"; diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 8566344aa..6d9e449a4 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -118,6 +118,7 @@ 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; + BA76135C2B21BC7300B599B7 /* SocialAuthResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */; }; BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; BA8FA6612AD5974300EA029A /* AppleAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6602AD5974300EA029A /* AppleAuthProvider.swift */; }; BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */; }; @@ -286,6 +287,7 @@ 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; + BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthResponse.swift; sourceTree = ""; }; BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; BA8FA6602AD5974300EA029A /* AppleAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAuthProvider.swift; sourceTree = ""; }; BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthButton.swift; sourceTree = ""; }; @@ -638,6 +640,7 @@ BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */, BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */, BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */, + BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */, ); path = SocialAuth; sourceTree = ""; @@ -958,6 +961,7 @@ 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */, BA8FA6612AD5974300EA029A /* AppleAuthProvider.swift in Sources */, BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */, + BA76135C2B21BC7300B599B7 /* SocialAuthResponse.swift in Sources */, 020306CC2932C0C4000949EA /* PickerView.swift in Sources */, 027BD3C52909707700392132 /* Shake.swift in Sources */, 027BD39C2908810C00392132 /* RegisterUser.swift in Sources */, diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index ea0393962..178d9fcf2 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -56,7 +56,7 @@ public class AuthRepository: AuthRepositoryProtocol { } public func login(externalToken: String, backend: String) async throws -> User { - let endPoint = AuthEndpoint.getEchangeAccessToken( + let endPoint = AuthEndpoint.echangeAccessToken( externalToken: externalToken, backend: backend, clientId: config.oAuthClientId, diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index 2138efcab..318b1cdda 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -10,7 +10,7 @@ import Alamofire enum AuthEndpoint: EndPointType { case getAccessToken(username: String, password: String, clientId: String, tokenType: String) - case getEchangeAccessToken(externalToken: String, backend: String, clientId: String, tokenType: String) + case echangeAccessToken(externalToken: String, backend: String, clientId: String, tokenType: String) case getUserInfo case getAuthCookies case getRegisterFields @@ -22,7 +22,7 @@ enum AuthEndpoint: EndPointType { switch self { case .getAccessToken: return "/oauth2/access_token" - case let .getEchangeAccessToken(_, backend, _, _): + case let .echangeAccessToken(_, backend, _, _): return "/oauth2/exchange_access_token/\(backend)/" case .getUserInfo: return "/api/mobile/v0.5/my_user_info" @@ -41,7 +41,7 @@ enum AuthEndpoint: EndPointType { var httpMethod: HTTPMethod { switch self { - case .getAccessToken, .getEchangeAccessToken: + case .getAccessToken, .echangeAccessToken: return .post case .getUserInfo: return .get @@ -74,7 +74,7 @@ enum AuthEndpoint: EndPointType { "asymmetric_jwt": true ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) - case let .getEchangeAccessToken(externalToken, _, clientId, tokenType): + case let .echangeAccessToken(externalToken, _, clientId, tokenType): let params: [String: Encodable] = [ "client_id": clientId, "token_type": tokenType, diff --git a/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift b/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift index 3f732fb6c..99e7fa4d5 100644 --- a/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift @@ -1,5 +1,5 @@ // -// AppleSingInProvider.swift +// AppleAuthProvider.swift // Core // // Created by Eugene Yatsenko on 10.10.2023. @@ -9,12 +9,6 @@ import Foundation import AuthenticationServices import Swinject -public struct AppleCredentials: Codable { - public var name: String - public var email: String - public var token: String -} - public final class AppleAuthProvider: NSObject, ASAuthorizationControllerDelegate { private let config: ConfigProtocol @@ -24,10 +18,10 @@ public final class AppleAuthProvider: NSObject, ASAuthorizationControllerDelegat super.init() } - private var completion: ((Result) -> Void)? + private var completion: ((Result) -> Void)? private let appleIDProvider = ASAuthorizationAppleIDProvider() - public func request(completion: ((Result) -> Void)?) { + public func request(completion: ((Result) -> Void)?) { let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] @@ -61,7 +55,7 @@ public final class AppleAuthProvider: NSObject, ASAuthorizationControllerDelegat storage?.appleSignFullName = name } - if storage?.appleSignEmail == nil { + if storage?.appleSignEmail == nil, !email.isEmpty { storage?.appleSignEmail = email } @@ -73,7 +67,7 @@ public final class AppleAuthProvider: NSObject, ASAuthorizationControllerDelegat debugLog("User id is \(data) \n Full Name is \(name) \n Email id is \(email)") - let appleCredentials = AppleCredentials( + let appleCredentials = SocialAuthResponse( name: name, email: email, token: code diff --git a/Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift b/Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift index 0626897f9..a9167451e 100644 --- a/Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift +++ b/Core/Core/Providers/SocialAuth/Error/SocialAuthError.swift @@ -1,5 +1,5 @@ // -// CustomError.swift +// SocialAuthError.swift // Core // // Created by Eugene Yatsenko on 10.10.2023. diff --git a/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift b/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift index 8068b96e5..a87f03944 100644 --- a/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift @@ -1,5 +1,5 @@ // -// FacebookSingInProvider.swift +// FacebookAuthProvider.swift // Core // // Created by Eugene Yatsenko on 10.10.2023. @@ -8,10 +8,6 @@ import Foundation import FacebookLogin -public protocol FacebookAuth { - func signIn(withPresenting: UIViewController, completion: @escaping ((LoginManagerLoginResult?, Error?) -> Void)) -} - public final class FacebookAuthProvider { private let loginManager = LoginManager() @@ -21,7 +17,7 @@ public final class FacebookAuthProvider { @MainActor public func signIn( withPresenting: UIViewController - ) async -> Result { + ) async -> Result { await withCheckedContinuation { continuation in loginManager.logIn( permissions: [], @@ -32,7 +28,9 @@ public final class FacebookAuthProvider { return } - guard let result = result, let _ = result.authenticationToken else { + guard let result = result, + let _ = result.authenticationToken, + let tokenString = AccessToken.current?.tokenString else { continuation.resume( returning: .failure(SocialAuthError.unknownError) ) @@ -44,7 +42,33 @@ public final class FacebookAuthProvider { return } - continuation.resume(returning: .success(result)) + GraphRequest( + graphPath: "me", + parameters: ["fields": "name, email"] + ).start { [weak self] _, result, _ in + guard let self else { return } + guard let userInfo = result as? [String: Any] else { + continuation.resume( + returning: .success( + SocialAuthResponse( + name: "", + email: "", + token: tokenString + ) + ) + ) + return + } + continuation.resume( + returning: .success( + SocialAuthResponse( + name: userInfo["name"] as? String ?? "", + email: userInfo["email"] as? String ?? "", + token: tokenString + ) + ) + ) + } } } } diff --git a/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift b/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift index 1a04519eb..c600c1735 100644 --- a/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/GoogleAuthProvider.swift @@ -1,5 +1,5 @@ // -// GoogleSingInProvider.swift +// GoogleAuthProvider.swift // Core // // Created by Eugene Yatsenko on 10.10.2023. @@ -15,7 +15,7 @@ public final class GoogleAuthProvider { @MainActor public func signIn( withPresenting: UIViewController - ) async -> Result { + ) async -> Result { await withCheckedContinuation { continuation in GIDSignIn.sharedInstance.signIn( withPresenting: withPresenting, @@ -32,7 +32,15 @@ public final class GoogleAuthProvider { ) return } - continuation.resume(returning: .success(result)) + continuation.resume( + returning: .success( + SocialAuthResponse( + name: result.user.profile?.name ?? "", + email: result.user.profile?.email ?? "", + token: result.user.accessToken.tokenString + ) + ) + ) } ) } diff --git a/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift b/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift index 9a760c2c3..bae77a4e1 100644 --- a/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift @@ -1,5 +1,5 @@ // -// MicrosoftSingInProvider.swift +// MicrosoftAuthProvider.swift // Core // // Created by Eugene Yatsenko on 10.10.2023. @@ -21,7 +21,7 @@ public final class MicrosoftAuthProvider { @MainActor public func signIn( withPresenting: UIViewController - ) async -> Result { + ) async -> Result { await withCheckedContinuation { continuation in do { let clientApplication = try createClientApplication() @@ -45,7 +45,16 @@ public final class MicrosoftAuthProvider { self.result = result let account = result.account - continuation.resume(returning: .success((account, result.accessToken))) + + continuation.resume( + returning: .success( + SocialAuthResponse( + name: account.accountClaims?["name"] as? String ?? "" , + email: account.accountClaims?["email"] as? String ?? "", + token: result.accessToken + ) + ) + ) } } catch let error { continuation.resume(returning: .failure(error)) diff --git a/Core/Core/Providers/SocialAuth/SocialAuthResponse.swift b/Core/Core/Providers/SocialAuth/SocialAuthResponse.swift new file mode 100644 index 000000000..55589d476 --- /dev/null +++ b/Core/Core/Providers/SocialAuth/SocialAuthResponse.swift @@ -0,0 +1,20 @@ +// +// SocialAuthResponse.swift +// Core +// +// Created by Eugene Yatsenko on 07.12.2023. +// + +import Foundation + +public struct SocialAuthResponse { + public var name: String + public var email: String + public var token: String + + public init(name: String, email: String, token: String) { + self.name = name + self.email = email + self.token = token + } +} diff --git a/Core/Core/View/Base/SocialAuthButton.swift b/Core/Core/View/Base/SocialAuthButton.swift index e18451a32..71ea25dbc 100644 --- a/Core/Core/View/Base/SocialAuthButton.swift +++ b/Core/Core/View/Base/SocialAuthButton.swift @@ -1,5 +1,5 @@ // -// LabelButton.swift +// SocialAuthButton.swift // Core // // Created by Eugene Yatsenko on 10.10.2023. From c25bcf76f22be4176e1df6ea339f7c49db26eb28 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 10:22:09 +0100 Subject: [PATCH 33/38] chore: remove extra code --- Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift | 1 - Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift b/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift index a87f03944..66ae46b6d 100644 --- a/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/FacebookAuthProvider.swift @@ -29,7 +29,6 @@ public final class FacebookAuthProvider { } guard let result = result, - let _ = result.authenticationToken, let tokenString = AccessToken.current?.tokenString else { continuation.resume( returning: .failure(SocialAuthError.unknownError) diff --git a/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift b/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift index bae77a4e1..16178b17c 100644 --- a/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/MicrosoftAuthProvider.swift @@ -29,7 +29,7 @@ public final class MicrosoftAuthProvider { let webParameters = MSALWebviewParameters(authPresentationViewController: withPresenting) let parameters = MSALInteractiveTokenParameters(scopes: scopes, webviewParameters: webParameters) clientApplication.acquireToken(with: parameters) { result, error in - if let error = error { + if let error = error { continuation.resume(returning: .failure(error)) return } From 39ff6b38d5718188fc69f6e9d33c5942b6dd6487 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 14:53:53 +0100 Subject: [PATCH 34/38] chore: remove extra code and add tests --- .../Presentation/Login/SignInView.swift | 11 +- .../Presentation/Login/SignInViewModel.swift | 98 ++++++------- .../Registration/SignUpView.swift | 10 +- .../Registration/SignUpViewModel.swift | 131 +++++------------- .../Login/SignInViewModelTests.swift | 68 ++++++++- Core/Core/Configuration/Config/Config.swift | 1 + .../Core/Data/Repository/AuthRepository.swift | 2 +- Core/Core/Network/AuthEndpoint.swift | 8 +- 8 files changed, 157 insertions(+), 172 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 4f055de47..0fece9b53 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -129,12 +129,13 @@ public struct SignInView: View { .padding(.top, 40) } } - if viewModel.socialLoginEnabled { + if viewModel.socialAuthEnabled { SocialAuthView( viewModel: .init( - config: viewModel.config, - completion: { viewModel.login(with: $0) } - ) + config: viewModel.config + ) { result in + Task { await viewModel.login(with: result) } + } ) } Spacer() @@ -188,7 +189,7 @@ struct SignInView_Previews: PreviewProvider { static var previews: some View { let vm = SignInViewModel( interactor: AuthInteractor.mock, - router: AuthorizationRouterMock(), + router: AuthorizationRouterMock(), config: ConfigMock(), analytics: AuthorizationAnalyticsMock(), validator: Validator() diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 5e96fb0c2..e1ee9d073 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -54,7 +54,7 @@ public class SignInViewModel: ObservableObject { self.validator = validator } - var socialLoginEnabled: Bool { + var socialAuthEnabled: Bool { config.appleSignIn.enabled || config.facebook.enabled || config.microsoft.enabled || @@ -84,79 +84,61 @@ public class SignInViewModel: ObservableObject { } @MainActor - func login(with result: Result) { - result.success { socialAuth(result: $0) } - result.failure { error in + func login(with result: Result) async { + switch result { + case .success(let result): + await socialAuth(result: result) + case .failure(let error): errorMessage = error.localizedDescription } } @MainActor - private func socialAuth(result: SocialAuthDetails) { + private func socialAuth(result: SocialAuthDetails) async { switch result { case .apple(let response): - appleLogin(response, backend: result.backend) + await socialLogin( + externalToken: response.token, + backend: result.backend, + loginMethod: .socailAuth(.apple) + ) case .facebook(let response): - facebookLogin(response, backend: result.backend) + await socialLogin( + externalToken: response.token, + backend: result.backend, + loginMethod: .socailAuth(.facebook) + ) case .google(let response): - googleLogin(response, backend: result.backend) + await socialLogin( + externalToken: response.token, + backend: result.backend, + loginMethod: .socailAuth(.google) + ) case .microsoft(let response): - microsoftLogin(response, backend: result.backend) + await socialLogin( + externalToken: response.token, + backend: result.backend, + loginMethod: .socailAuth(.microsoft) + ) } } @MainActor - private func appleLogin(_ response: SocialAuthResponse, backend: String) { - socialLogin( - externalToken: response.token, - backend: backend, - loginMethod: .socailAuth(.apple) - ) - } - - @MainActor - private func facebookLogin(_ response: SocialAuthResponse, backend: String) { - guard let currentAccessToken = AccessToken.current?.tokenString else { - return + private func socialLogin( + externalToken: String, + backend: String, + loginMethod: LoginMethod + ) async { + isShowProgress = true + do { + let user = try await interactor.login(externalToken: externalToken, backend: backend) + analytics.setUserID("\(user.id)") + analytics.userLogin(method: loginMethod) + router.showMainOrWhatsNewScreen() + } catch let error { + failure(error, loginMethod: loginMethod) } - socialLogin( - externalToken: response.token, - backend: backend, - loginMethod: .socailAuth(.facebook) - ) - } - - @MainActor - private func googleLogin(_ response: SocialAuthResponse, backend: String) { - socialLogin( - externalToken: response.token, - backend: backend, - loginMethod: .socailAuth(.google) - ) - } - - @MainActor - private func microsoftLogin(_ response: SocialAuthResponse, backend: String) { - socialLogin( - externalToken: response.token, - backend: backend, - loginMethod: .socailAuth(.microsoft) - ) - } - @MainActor - private func socialLogin(externalToken: String, backend: String, loginMethod: LoginMethod) { - Task { - isShowProgress = true - do { - let user = try await interactor.login(externalToken: externalToken, backend: backend) - analytics.setUserID("\(user.id)") - analytics.userLogin(method: loginMethod) - router.showMainOrWhatsNewScreen() - } catch let error { - failure(error, loginMethod: loginMethod) - } - } } @MainActor diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index fd4e9b070..13320ee9b 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -68,7 +68,7 @@ public struct SignUpView: View { .foregroundColor(Theme.Colors.textPrimary) .padding(.bottom, 20) - if viewModel.isThirdPartyAuthSuccess { + if viewModel.thirdPartyAuthSuccess { Text(AuthLocalization.SignUp.successSigninLabel) .font(Theme.Fonts.titleMedium) .foregroundColor(Theme.Colors.textPrimary) @@ -108,7 +108,7 @@ public struct SignUpView: View { }.frame(maxWidth: .infinity) } else { StyledButton(AuthLocalization.SignUp.createAccountBtn) { - viewModel.isThirdPartyAuthSuccess = false + viewModel.thirdPartyAuthSuccess = false Task { await viewModel.registerUser() } @@ -117,13 +117,15 @@ public struct SignUpView: View { .padding(.top, 40) .frame(maxWidth: .infinity) } - if viewModel.socialLoginEnabled, + if viewModel.socialAuthEnabled, !requiredFields.isEmpty { SocialAuthView( authType: .register, viewModel: .init( config: viewModel.config - ) { viewModel.register(with: $0) } + ) { result in + Task { await viewModel.register(with: result) } + } ) .padding(.bottom, 30) } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index a1e497ca9..2b2f4eeba 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -18,7 +18,7 @@ public class SignUpViewModel: ObservableObject { @Published var isShowProgress = false @Published var scrollTo: Int? @Published var showError: Bool = false - @Published var isThirdPartyAuthSuccess: Bool = false + @Published var thirdPartyAuthSuccess: Bool = false var errorMessage: String? { didSet { withAnimation { @@ -53,16 +53,16 @@ public class SignUpViewModel: ObservableObject { self.validator = validator } - var socialLoginEnabled: Bool { + var socialAuthEnabled: Bool { let socialLoginEnabled = config.appleSignIn.enabled || config.facebook.enabled || config.microsoft.enabled || config.google.enabled - return socialLoginEnabled && !isThirdPartyAuthSuccess && !isShowProgress + return socialLoginEnabled && !thirdPartyAuthSuccess && !isShowProgress } private func showErrors(errors: [String: String]) -> Bool { - if isThirdPartyAuthSuccess, !errors.map({ $0.value }).filter({ !$0.isEmpty }).isEmpty { + if thirdPartyAuthSuccess, !errors.map({ $0.value }).filter({ !$0.isEmpty }).isEmpty { scrollTo = 1 return true } @@ -146,129 +146,64 @@ public class SignUpViewModel: ObservableObject { } @MainActor - func register(with result: Result) { - result.success(socialAuth) - result.failure { error in + func register(with result: Result) async { + switch result { + case .success(let result): + await socialAuth(result: result) + case .failure(let error): errorMessage = error.localizedDescription } } @MainActor - private func socialAuth(result: SocialAuthDetails) { + private func socialAuth(result: SocialAuthDetails) async { switch result { case .apple(let response): - appleRegister(response, backend: result.backend, loginMethod: .socailAuth(.apple)) + await loginOrRegister( + response, + backend: result.backend, + loginMethod: .socailAuth(.apple) + ) case .facebook(let response): - facebookRegister(response, backend: result.backend, loginMethod: .socailAuth(.facebook)) + await loginOrRegister( + response, + backend: result.backend, + loginMethod: .socailAuth(.facebook) + ) case .google(let response): - googleRegister(response, backend: result.backend, loginMethod: .socailAuth(.google)) + await loginOrRegister( + response, + backend: result.backend, + loginMethod: .socailAuth(.google) + ) case .microsoft(let response): - microsoftRegister(response, backend: result.backend, loginMethod: .socailAuth(.microsoft)) - } - } - - @MainActor - private func appleRegister( - _ response: SocialAuthResponse, - backend: String, - loginMethod: LoginMethod - ) { - update( - fullName: response.name, - email: response.email - ) - registerSocial( - externalToken: response.token, - backend: backend, - loginMethod: loginMethod - ) - } - - @MainActor - private func facebookRegister( - _ response: SocialAuthResponse, - backend: String, - loginMethod: LoginMethod - ) { - update( - fullName: response.name, - email: response.email - ) - registerSocial( - externalToken: response.token, - backend: backend, - loginMethod: loginMethod - ) - - } - - @MainActor - private func googleRegister( - _ response: SocialAuthResponse, - backend: String, - loginMethod: LoginMethod - ) { - update( - fullName: response.name, - email: response.email - ) - registerSocial( - externalToken: response.token, - backend: backend, - loginMethod: loginMethod - ) - } - - @MainActor - private func microsoftRegister( - _ response: SocialAuthResponse, - backend: String, - loginMethod: LoginMethod - ) { - update( - fullName: response.name, - email: response.email - ) - registerSocial( - externalToken: response.token, - backend: backend, - loginMethod: loginMethod - ) - } - - @MainActor - private func registerSocial( - externalToken: String, - backend: String, - loginMethod: LoginMethod - ) { - Task { await loginOrRegister( - externalToken: externalToken, - backend: backend, - loginMethod: loginMethod + response, + backend: result.backend, + loginMethod: .socailAuth(.microsoft) ) } } @MainActor private func loginOrRegister( - externalToken: String, + _ response: SocialAuthResponse, backend: String, loginMethod: LoginMethod ) async { do { isShowProgress = true let validateFields = configureFields().filter { !$0.value.isEmpty } - let user = try await interactor.login(externalToken: externalToken, backend: backend) + let user = try await interactor.login(externalToken: response.token, backend: backend) analytics.setUserID("\(user.id)") analytics.userLogin(method: loginMethod) isShowProgress = false router.showMainOrWhatsNewScreen() } catch { - self.externalToken = externalToken + update(fullName: response.name, email: response.email) + self.externalToken = response.token self.backend = backend - isThirdPartyAuthSuccess = true + thirdPartyAuthSuccess = true isShowProgress = false await registerUser() } diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index 744e24ba3..8204c5629 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -90,7 +90,71 @@ final class SignInViewModelTests: XCTestCase { XCTAssertEqual(viewModel.errorMessage, nil) XCTAssertEqual(viewModel.isShowProgress, true) } - + + func testSocialLoginSuccess() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let analytics = AuthorizationAnalyticsMock() + let viewModel = SignInViewModel( + interactor: interactor, + router: router, + config: ConfigMock(), + analytics: analytics, + validator: validator + ) + + let result: Result = .success(.apple( + .init(name: "name", email: "email", token: "239i2oi3jrf2jflkj23lf2f")) + ) + let user = User(id: 1, username: "username", email: "edxUser@edx.com", name: "Name", userAvatar: "") + + Given(interactor, .login(externalToken: .any, backend: .any, willReturn: user)) + + await viewModel.login(with: result) + + Verify(interactor, 1, .login(externalToken: .any, backend: .any)) + Verify(analytics, .userLogin(method: .any)) + Verify(router, 1, .showMainOrWhatsNewScreen()) + + XCTAssertEqual(viewModel.errorMessage, nil) + XCTAssertEqual(viewModel.isShowProgress, true) + } + + func testSocialLoginErrorValidation() async throws { + let interactor = AuthInteractorProtocolMock() + let router = AuthorizationRouterMock() + let validator = Validator() + let analytics = AuthorizationAnalyticsMock() + let viewModel = SignInViewModel( + interactor: interactor, + router: router, + config: ConfigMock(), + analytics: analytics, + validator: validator + ) + + let result: Result = .success( + .apple(.init(name: "name", email: "email", token: "239i2oi3jrf2jflkj23lf2f")) + ) + let validationErrorMessage = AuthLocalization.Error.accountNotRegistered( + LoginMethod.socailAuth(.apple).analyticsValue, + viewModel.config.platformName + ) + let validationError = CustomValidationError(statusCode: 400, data: ["error_description": validationErrorMessage]) + let error = AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.customValidationFailed(error: validationError)) + + Given(interactor, .login(externalToken: .any, backend: .any, willThrow: error)) + + await viewModel.login(with: result) + + Verify(interactor, 1, .login(externalToken: .any, backend: .any)) + Verify(router, 0, .showMainOrWhatsNewScreen()) + + XCTAssertEqual(viewModel.errorMessage, validationErrorMessage) + XCTAssertEqual(viewModel.isShowProgress, false) + } + func testLoginErrorValidation() async throws { let interactor = AuthInteractorProtocolMock() let router = AuthorizationRouterMock() @@ -118,7 +182,7 @@ final class SignInViewModelTests: XCTestCase { XCTAssertEqual(viewModel.errorMessage, validationErrorMessage) XCTAssertEqual(viewModel.isShowProgress, false) } - + func testLoginErrorInvalidGrant() async throws { let interactor = AuthInteractorProtocolMock() let router = AuthorizationRouterMock() diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 0d52790e9..8468cd673 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -145,6 +145,7 @@ public class ConfigMock: Config { "API_HOST_URL": "https://www.example.com", "OAUTH_CLIENT_ID": "oauth_client_id", "FEEDBACK_EMAIL_ADDRESS": "example@mail.com", + "PLATFORM_NAME": "OpenEdx", "TOKEN_TYPE": "JWT", "WHATS_NEW_ENABLED": false, "AGREEMENT_URLS": [ diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index 178d9fcf2..e4adf93ea 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -56,7 +56,7 @@ public class AuthRepository: AuthRepositoryProtocol { } public func login(externalToken: String, backend: String) async throws -> User { - let endPoint = AuthEndpoint.echangeAccessToken( + let endPoint = AuthEndpoint.exchangeAccessToken( externalToken: externalToken, backend: backend, clientId: config.oAuthClientId, diff --git a/Core/Core/Network/AuthEndpoint.swift b/Core/Core/Network/AuthEndpoint.swift index 318b1cdda..e93e5c860 100644 --- a/Core/Core/Network/AuthEndpoint.swift +++ b/Core/Core/Network/AuthEndpoint.swift @@ -10,7 +10,7 @@ import Alamofire enum AuthEndpoint: EndPointType { case getAccessToken(username: String, password: String, clientId: String, tokenType: String) - case echangeAccessToken(externalToken: String, backend: String, clientId: String, tokenType: String) + case exchangeAccessToken(externalToken: String, backend: String, clientId: String, tokenType: String) case getUserInfo case getAuthCookies case getRegisterFields @@ -22,7 +22,7 @@ enum AuthEndpoint: EndPointType { switch self { case .getAccessToken: return "/oauth2/access_token" - case let .echangeAccessToken(_, backend, _, _): + case let .exchangeAccessToken(_, backend, _, _): return "/oauth2/exchange_access_token/\(backend)/" case .getUserInfo: return "/api/mobile/v0.5/my_user_info" @@ -41,7 +41,7 @@ enum AuthEndpoint: EndPointType { var httpMethod: HTTPMethod { switch self { - case .getAccessToken, .echangeAccessToken: + case .getAccessToken, .exchangeAccessToken: return .post case .getUserInfo: return .get @@ -74,7 +74,7 @@ enum AuthEndpoint: EndPointType { "asymmetric_jwt": true ] return .requestParameters(parameters: params, encoding: URLEncoding.httpBody) - case let .echangeAccessToken(externalToken, _, clientId, tokenType): + case let .exchangeAccessToken(externalToken, _, clientId, tokenType): let params: [String: Encodable] = [ "client_id": clientId, "token_type": tokenType, From 9f000cb5c37ac7377af871ebc7a603fdf789a73c Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 15:43:32 +0100 Subject: [PATCH 35/38] chore: changes for saving apple info to appstorage --- .../Providers/SocialAuth/AppleAuthProvider.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift b/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift index 99e7fa4d5..1ed3d4c06 100644 --- a/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift +++ b/Core/Core/Providers/SocialAuth/AppleAuthProvider.swift @@ -45,18 +45,15 @@ public final class AppleAuthProvider: NSObject, ASAuthorizationControllerDelegat let pncf = PersonNameComponentsFormatter() var name = storage?.appleSignFullName ?? "" - if let fullName = credentials.fullName { - name = pncf.string(from: fullName) - } - - let email = credentials.email ?? storage?.appleSignEmail ?? "" - - if !name.isEmpty { + if let components = credentials.fullName, !pncf.string(from: components).isEmpty { + name = pncf.string(from: components) storage?.appleSignFullName = name } - if storage?.appleSignEmail == nil, !email.isEmpty { - storage?.appleSignEmail = email + var email = storage?.appleSignEmail ?? "" + if let appleEmail = credentials.email, !appleEmail.isEmpty { + email = appleEmail + storage?.appleSignEmail = appleEmail } guard let data = credentials.identityToken, From a9816efbb53b98f80e4e77d15ca017e4542ba1a3 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 15:58:22 +0100 Subject: [PATCH 36/38] chore: remove extra code and change social auth details --- .../Presentation/Login/SignInViewModel.swift | 35 +++---------------- .../Registration/SignUpViewModel.swift | 34 +++--------------- .../SocialAuth/SocialAuthViewModel.swift | 31 +++++++++++++--- 3 files changed, 35 insertions(+), 65 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index e1ee9d073..505732b2b 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -87,39 +87,13 @@ public class SignInViewModel: ObservableObject { func login(with result: Result) async { switch result { case .success(let result): - await socialAuth(result: result) - case .failure(let error): - errorMessage = error.localizedDescription - } - } - - @MainActor - private func socialAuth(result: SocialAuthDetails) async { - switch result { - case .apple(let response): - await socialLogin( - externalToken: response.token, - backend: result.backend, - loginMethod: .socailAuth(.apple) - ) - case .facebook(let response): await socialLogin( - externalToken: response.token, + externalToken: result.response.token, backend: result.backend, - loginMethod: .socailAuth(.facebook) - ) - case .google(let response): - await socialLogin( - externalToken: response.token, - backend: result.backend, - loginMethod: .socailAuth(.google) - ) - case .microsoft(let response): - await socialLogin( - externalToken: response.token, - backend: result.backend, - loginMethod: .socailAuth(.microsoft) + loginMethod: result.loginMethod ) + case .failure(let error): + errorMessage = error.localizedDescription } } @@ -138,7 +112,6 @@ public class SignInViewModel: ObservableObject { } catch let error { failure(error, loginMethod: loginMethod) } - } @MainActor diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 2b2f4eeba..9ef20078c 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -149,39 +149,13 @@ public class SignUpViewModel: ObservableObject { func register(with result: Result) async { switch result { case .success(let result): - await socialAuth(result: result) - case .failure(let error): - errorMessage = error.localizedDescription - } - } - - @MainActor - private func socialAuth(result: SocialAuthDetails) async { - switch result { - case .apple(let response): - await loginOrRegister( - response, - backend: result.backend, - loginMethod: .socailAuth(.apple) - ) - case .facebook(let response): await loginOrRegister( - response, + result.response, backend: result.backend, - loginMethod: .socailAuth(.facebook) - ) - case .google(let response): - await loginOrRegister( - response, - backend: result.backend, - loginMethod: .socailAuth(.google) - ) - case .microsoft(let response): - await loginOrRegister( - response, - backend: result.backend, - loginMethod: .socailAuth(.microsoft) + loginMethod: result.loginMethod ) + case .failure(let error): + errorMessage = error.localizedDescription } } diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift index 75aa6b718..c7f5b4d20 100644 --- a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -22,13 +22,36 @@ enum SocialAuthDetails { var backend: String { switch self { case .apple: - return "apple-id" + "apple-id" case .facebook: - return "facebook" + "facebook" case .google: - return "google-oauth2" + "google-oauth2" case .microsoft: - return "azuread-oauth2" + "azuread-oauth2" + } + } + + var loginMethod: LoginMethod { + switch self { + case .apple: + LoginMethod.socailAuth(.apple) + case .facebook: + LoginMethod.socailAuth(.facebook) + case .google: + LoginMethod.socailAuth(.google) + case .microsoft: + LoginMethod.socailAuth(.microsoft) + } + } + + var response: SocialAuthResponse { + switch self { + case .apple(let response), + .facebook(let response), + .google(let response), + .microsoft(let response): + return response } } } From 5a0e83895bb4dac3796b18c1254b45bd12a16b25 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 16:19:30 +0100 Subject: [PATCH 37/38] chore: change LoginMethod -> AuthMethod and remove extra code --- .../Presentation/AuthorizationAnalytics.swift | 6 +++--- .../Presentation/Login/SignInViewModel.swift | 4 ++-- .../Presentation/Registration/SignUpViewModel.swift | 3 +-- .../SocialAuth/SocialAuthViewModel.swift | 10 +++++----- .../AuthorizationMock.generated.swift | 12 ++++++------ .../Presentation/Login/SignInViewModelTests.swift | 2 +- OpenEdX/AnalyticsManager.swift | 2 +- 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift index 13ea220ab..d73799435 100644 --- a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift +++ b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift @@ -7,7 +7,7 @@ import Foundation -public enum LoginMethod: Equatable { +public enum AuthMethod: Equatable { case password case socailAuth(SocialAuthMethod) @@ -31,7 +31,7 @@ public enum SocialAuthMethod: String { //sourcery: AutoMockable public protocol AuthorizationAnalytics { func setUserID(_ id: String) - func userLogin(method: LoginMethod) + func userLogin(method: AuthMethod) func signUpClicked() func createAccountClicked() func registrationSuccess() @@ -42,7 +42,7 @@ public protocol AuthorizationAnalytics { #if DEBUG class AuthorizationAnalyticsMock: AuthorizationAnalytics { public func setUserID(_ id: String) {} - public func userLogin(method: LoginMethod) {} + public func userLogin(method: AuthMethod) {} public func signUpClicked() {} public func createAccountClicked() {} public func registrationSuccess() {} diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 505732b2b..123da5c35 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -101,7 +101,7 @@ public class SignInViewModel: ObservableObject { private func socialLogin( externalToken: String, backend: String, - loginMethod: LoginMethod + loginMethod: AuthMethod ) async { isShowProgress = true do { @@ -115,7 +115,7 @@ public class SignInViewModel: ObservableObject { } @MainActor - private func failure(_ error: Error, loginMethod: LoginMethod? = nil) { + private func failure(_ error: Error, loginMethod: AuthMethod? = nil) { isShowProgress = false if let validationError = error.validationError, let value = validationError.data?["error_description"] as? String { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 9ef20078c..8905a7b29 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -163,11 +163,10 @@ public class SignUpViewModel: ObservableObject { private func loginOrRegister( _ response: SocialAuthResponse, backend: String, - loginMethod: LoginMethod + loginMethod: AuthMethod ) async { do { isShowProgress = true - let validateFields = configureFields().filter { !$0.value.isEmpty } let user = try await interactor.login(externalToken: response.token, backend: backend) analytics.setUserID("\(user.id)") analytics.userLogin(method: loginMethod) diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift index c7f5b4d20..8c22c8b9d 100644 --- a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -32,16 +32,16 @@ enum SocialAuthDetails { } } - var loginMethod: LoginMethod { + var loginMethod: AuthMethod { switch self { case .apple: - LoginMethod.socailAuth(.apple) + .socailAuth(.apple) case .facebook: - LoginMethod.socailAuth(.facebook) + .socailAuth(.facebook) case .google: - LoginMethod.socailAuth(.google) + .socailAuth(.google) case .microsoft: - LoginMethod.socailAuth(.microsoft) + .socailAuth(.microsoft) } } diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index fd5dc338a..d6a968438 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -515,9 +515,9 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { perform?(`id`) } - open func userLogin(method: LoginMethod) { - addInvocation(.m_userLogin__method_method(Parameter.value(`method`))) - let perform = methodPerformValue(.m_userLogin__method_method(Parameter.value(`method`))) as? (LoginMethod) -> Void + open func userLogin(method: AuthMethod) { + addInvocation(.m_userLogin__method_method(Parameter.value(`method`))) + let perform = methodPerformValue(.m_userLogin__method_method(Parameter.value(`method`))) as? (AuthMethod) -> Void perform?(`method`) } @@ -554,7 +554,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { fileprivate enum MethodType { case m_setUserID__id(Parameter) - case m_userLogin__method_method(Parameter) + case m_userLogin__method_method(Parameter) case m_signUpClicked case m_createAccountClicked case m_registrationSuccess @@ -628,7 +628,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { fileprivate var method: MethodType public static func setUserID(_ id: Parameter) -> Verify { return Verify(method: .m_setUserID__id(`id`))} - public static func userLogin(method: Parameter) -> Verify { return Verify(method: .m_userLogin__method_method(`method`))} + public static func userLogin(method: Parameter) -> Verify { return Verify(method: .m_userLogin__method_method(`method`))} public static func signUpClicked() -> Verify { return Verify(method: .m_signUpClicked)} public static func createAccountClicked() -> Verify { return Verify(method: .m_createAccountClicked)} public static func registrationSuccess() -> Verify { return Verify(method: .m_registrationSuccess)} @@ -643,7 +643,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { public static func setUserID(_ id: Parameter, perform: @escaping (String) -> Void) -> Perform { return Perform(method: .m_setUserID__id(`id`), performs: perform) } - public static func userLogin(method: Parameter, perform: @escaping (LoginMethod) -> Void) -> Perform { + public static func userLogin(method: Parameter, perform: @escaping (AuthMethod) -> Void) -> Perform { return Perform(method: .m_userLogin__method_method(`method`), performs: perform) } public static func signUpClicked(perform: @escaping () -> Void) -> Perform { diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index 8204c5629..484807136 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -138,7 +138,7 @@ final class SignInViewModelTests: XCTestCase { .apple(.init(name: "name", email: "email", token: "239i2oi3jrf2jflkj23lf2f")) ) let validationErrorMessage = AuthLocalization.Error.accountNotRegistered( - LoginMethod.socailAuth(.apple).analyticsValue, + AuthMethod.socailAuth(.apple).analyticsValue, viewModel.config.platformName ) let validationError = CustomValidationError(statusCode: 400, data: ["error_description": validationErrorMessage]) diff --git a/OpenEdX/AnalyticsManager.swift b/OpenEdX/AnalyticsManager.swift index 5a9111fbe..ecb74b036 100644 --- a/OpenEdX/AnalyticsManager.swift +++ b/OpenEdX/AnalyticsManager.swift @@ -27,7 +27,7 @@ class AnalyticsManager: AuthorizationAnalytics, Analytics.setUserID(id) } - public func userLogin(method: LoginMethod) { + public func userLogin(method: AuthMethod) { logEvent(.userLogin, parameters: [Key.method: method.analyticsValue]) } From c6f7f043c36979e86da82b3408802a2f0d9dc3e7 Mon Sep 17 00:00:00 2001 From: Eugene Yatsenko Date: Thu, 7 Dec 2023 16:27:02 +0100 Subject: [PATCH 38/38] chore: change names --- .../Presentation/Login/SignInViewModel.swift | 14 +++++++------- .../Registration/SignUpViewModel.swift | 6 +++--- .../SocialAuth/SocialAuthViewModel.swift | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 123da5c35..a2439a10b 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -90,7 +90,7 @@ public class SignInViewModel: ObservableObject { await socialLogin( externalToken: result.response.token, backend: result.backend, - loginMethod: result.loginMethod + authMethod: result.authMethod ) case .failure(let error): errorMessage = error.localizedDescription @@ -101,27 +101,27 @@ public class SignInViewModel: ObservableObject { private func socialLogin( externalToken: String, backend: String, - loginMethod: AuthMethod + authMethod: AuthMethod ) async { isShowProgress = true do { let user = try await interactor.login(externalToken: externalToken, backend: backend) analytics.setUserID("\(user.id)") - analytics.userLogin(method: loginMethod) + analytics.userLogin(method: authMethod) router.showMainOrWhatsNewScreen() } catch let error { - failure(error, loginMethod: loginMethod) + failure(error, authMethod: authMethod) } } @MainActor - private func failure(_ error: Error, loginMethod: AuthMethod? = nil) { + private func failure(_ error: Error, authMethod: AuthMethod? = nil) { isShowProgress = false if let validationError = error.validationError, let value = validationError.data?["error_description"] as? String { - if loginMethod != .password, validationError.statusCode == 400, let loginMethod = loginMethod { + if authMethod != .password, validationError.statusCode == 400, let authMethod = authMethod { errorMessage = AuthLocalization.Error.accountNotRegistered( - loginMethod.analyticsValue, + authMethod.analyticsValue, config.platformName ) } else if validationError.statusCode == 403 { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 8905a7b29..927651b7d 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -152,7 +152,7 @@ public class SignUpViewModel: ObservableObject { await loginOrRegister( result.response, backend: result.backend, - loginMethod: result.loginMethod + authMethod: result.authMethod ) case .failure(let error): errorMessage = error.localizedDescription @@ -163,13 +163,13 @@ public class SignUpViewModel: ObservableObject { private func loginOrRegister( _ response: SocialAuthResponse, backend: String, - loginMethod: AuthMethod + authMethod: AuthMethod ) async { do { isShowProgress = true let user = try await interactor.login(externalToken: response.token, backend: backend) analytics.setUserID("\(user.id)") - analytics.userLogin(method: loginMethod) + analytics.userLogin(method: authMethod) isShowProgress = false router.showMainOrWhatsNewScreen() } catch { diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift index 8c22c8b9d..24e8ca5b8 100644 --- a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -31,8 +31,8 @@ enum SocialAuthDetails { "azuread-oauth2" } } - - var loginMethod: AuthMethod { + + var authMethod: AuthMethod { switch self { case .apple: .socailAuth(.apple)