diff --git a/.github/workflows/add-depr-ticket-to-depr-board.yml b/.github/workflows/add-depr-ticket-to-depr-board.yml new file mode 100644 index 000000000..250e394ab --- /dev/null +++ b/.github/workflows/add-depr-ticket-to-depr-board.yml @@ -0,0 +1,19 @@ +# Run the workflow that adds new tickets that are either: +# - labelled "DEPR" +# - title starts with "[DEPR]" +# - body starts with "Proposal Date" (this is the first template field) +# to the org-wide DEPR project board + +name: Add newly created DEPR issues to the DEPR project board + +on: + issues: + types: [opened] + +jobs: + routeissue: + uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master + secrets: + GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }} + GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }} + SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }} diff --git a/.github/workflows/add-remove-label-on-comment.yml b/.github/workflows/add-remove-label-on-comment.yml new file mode 100644 index 000000000..0f369db7d --- /dev/null +++ b/.github/workflows/add-remove-label-on-comment.yml @@ -0,0 +1,20 @@ +# This workflow runs when a comment is made on the ticket +# If the comment starts with "label: " it tries to apply +# the label indicated in rest of comment. +# If the comment starts with "remove label: ", it tries +# to remove the indicated label. +# Note: Labels are allowed to have spaces and this script does +# not parse spaces (as often a space is legitimate), so the command +# "label: really long lots of words label" will apply the +# label "really long lots of words label" + +name: Allows for the adding and removing of labels via comment + +on: + issue_comment: + types: [created] + +jobs: + add_remove_labels: + uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master + diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 000000000..fec11d6c2 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,10 @@ +# Run commitlint on the commit messages in a pull request. + +name: Lint Commit Messages + +on: + - pull_request + +jobs: + commitlint: + uses: openedx/.github/.github/workflows/commitlint.yml@master diff --git a/.github/workflows/self-assign-issue.yml b/.github/workflows/self-assign-issue.yml new file mode 100644 index 000000000..37522fd57 --- /dev/null +++ b/.github/workflows/self-assign-issue.yml @@ -0,0 +1,12 @@ +# This workflow runs when a comment is made on the ticket +# If the comment starts with "assign me" it assigns the author to the +# ticket (case insensitive) + +name: Assign comment author to ticket if they say "assign me" +on: + issue_comment: + types: [created] + +jobs: + self_assign_by_comment: + uses: openedx/.github/.github/workflows/self-assign-issue.yml@master diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index fba945920..8c3380cd1 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -26,7 +26,12 @@ 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 /* 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 */; }; + E03261682AE9F156002CA7EB /* LogistrationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E03261672AE9F156002CA7EB /* LogistrationBottomView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -75,6 +80,11 @@ 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 /* 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 = ""; }; 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 +149,8 @@ 071009CC28D1E24000344290 /* Presentation */ = { isa = PBXGroup; children = ( + BA8B3A302AD5485100D25EF5 /* SocialAuth */, + E03261622AE6464A002CA7EB /* Startup */, 020C31BD290AADA700D6DEA2 /* Base */, 071009C528D1D9FA00344290 /* Login */, 07169462296D93E000E3DED6 /* Registration */, @@ -258,6 +270,25 @@ path = ../Pods; sourceTree = ""; }; + BA8B3A302AD5485100D25EF5 /* SocialAuth */ = { + isa = PBXGroup; + children = ( + BA8B3A312AD5487300D25EF5 /* SocialAuthView.swift */, + BADB3F542AD6DFC3004D5CFA /* SocialAuthViewModel.swift */, + ); + path = SocialAuth; + sourceTree = ""; + }; + E03261622AE6464A002CA7EB /* Startup */ = { + isa = PBXGroup; + children = ( + E03261632AE64676002CA7EB /* StartupViewModel.swift */, + E03261652AE64AF4002CA7EB /* StartupView.swift */, + E03261672AE9F156002CA7EB /* LogistrationBottomView.swift */, + ); + path = Startup; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -467,16 +498,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BADB3F552AD6DFC3004D5CFA /* SocialAuthViewModel.swift in Sources */, 02066B442906D72400F4307E /* SignUpView.swift in Sources */, 0770DE7128D0C0E7006D8A5D /* Strings.swift in Sources */, 025F40E229D360E20064C183 /* ResetPasswordViewModel.swift in Sources */, 02066B462906D72F00F4307E /* SignUpViewModel.swift in Sources */, + E03261642AE64676002CA7EB /* StartupViewModel.swift in Sources */, 02A2ACDB2A4B016100FBBBBB /* AuthorizationAnalytics.swift in Sources */, + E03261682AE9F156002CA7EB /* LogistrationBottomView.swift in Sources */, 025F40E029D1E2FC0064C183 /* ResetPasswordView.swift in Sources */, 020C31CB290BF49900D6DEA2 /* FieldsView.swift in Sources */, 0770DE4E28D0A677006D8A5D /* SignInView.swift in Sources */, 02F3BFE5292533720051930C /* AuthorizationRouter.swift in Sources */, + E03261662AE64AF4002CA7EB /* StartupView.swift in Sources */, 071009C728D1DA4F00344290 /* SignInViewModel.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 6c7a60393..d73799435 100644 --- a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift +++ b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift @@ -7,17 +7,31 @@ import Foundation -public enum LoginMethod: String { - case password = "Password" +public enum AuthMethod: 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 public protocol AuthorizationAnalytics { func setUserID(_ id: String) - func userLogin(method: LoginMethod) + func userLogin(method: AuthMethod) func signUpClicked() func createAccountClicked() func registrationSuccess() @@ -28,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/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 8ad4a9949..0fece9b53 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -7,6 +7,8 @@ import SwiftUI import Core +import Theme +import Swinject public struct SignInView: View { @@ -25,13 +27,26 @@ public struct SignInView: View { public var body: some View { ZStack(alignment: .top) { VStack { - CoreAssets.authBackground.swiftUIImage + ThemeAssets.authBackground.swiftUIImage .resizable() .edgesIgnoringSafeArea(.top) }.frame(maxWidth: .infinity, maxHeight: 200) + if viewModel.config.features.startupScreenEnabled { + VStack { + Button(action: { viewModel.router.back() }, label: { + CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) + .backButtonStyle(color: .white) + }) + .foregroundColor(Theme.Colors.styledButtonText) + .padding(.leading, isHorizontal ? 48 : 0) + .padding(.top, 11) + + }.frame(maxWidth: .infinity, alignment: .topLeading) + .padding(.top, isHorizontal ? 20 : 0) + } VStack(alignment: .center) { - CoreAssets.appLogo.swiftUIImage + ThemeAssets.appLogo.swiftUIImage .resizable() .frame(maxWidth: 189, maxHeight: 54) .padding(.top, isHorizontal ? 20 : 40) @@ -49,10 +64,10 @@ public struct SignInView: View { .foregroundColor(Theme.Colors.textPrimary) .padding(.bottom, 20) - Text(AuthLocalization.SignIn.email) + Text(AuthLocalization.SignIn.emailOrUsername) .font(Theme.Fonts.labelLarge) .foregroundColor(Theme.Colors.textPrimary) - TextField(AuthLocalization.SignIn.email, text: $email) + TextField(AuthLocalization.SignIn.emailOrUsername, text: $email) .keyboardType(.emailAddress) .textContentType(.emailAddress) .autocapitalization(.none) @@ -83,21 +98,23 @@ public struct SignInView: View { .stroke(lineWidth: 1) .fill(Theme.Colors.textInputStroke) ) - HStack { - Button(AuthLocalization.SignIn.registerBtn) { - viewModel.trackSignUpClicked() - viewModel.router.showRegisterScreen() - }.foregroundColor(Theme.Colors.accentColor) - - Spacer() - + if !viewModel.config.features.startupScreenEnabled { + Button(AuthLocalization.SignIn.registerBtn) { + viewModel.trackSignUpClicked() + viewModel.router.showRegisterScreen() + }.foregroundColor(Theme.Colors.accentColor) + + Spacer() + } + Button(AuthLocalization.SignIn.forgotPassBtn) { viewModel.trackForgotPasswordClicked() viewModel.router.showForgotPasswordScreen() }.foregroundColor(Theme.Colors.accentColor) + .padding(.top, 0) } - .padding(.top, 10) + if viewModel.isShowProgress { HStack(alignment: .center) { ProgressBar(size: 40, lineWidth: 8) @@ -112,6 +129,15 @@ public struct SignInView: View { .padding(.top, 40) } } + if viewModel.socialAuthEnabled { + SocialAuthView( + viewModel: .init( + config: viewModel.config + ) { result in + Task { await viewModel.login(with: result) } + } + ) + } Spacer() } .padding(.horizontal, 24) @@ -126,7 +152,7 @@ public struct SignInView: View { VStack { Text(viewModel.alertMessage ?? "") .shadowCardStyle(bgColor: Theme.Colors.accentColor, - textColor: .white) + textColor: Theme.Colors.white) .padding(.top, 80) Spacer() @@ -153,8 +179,6 @@ public struct SignInView: View { } } .hideNavigationBar() - .navigationBarBackButtonHidden(true) - .navigationBarHidden(true) .ignoresSafeArea(.all, edges: .horizontal) .background(Theme.Colors.background.ignoresSafeArea(.all)) } @@ -165,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 350d09af0..a2439a10b 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -9,9 +9,13 @@ import Foundation import Core import SwiftUI import Alamofire +import AuthenticationServices +import FacebookLogin +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 @@ -31,11 +35,11 @@ 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 - + public init( interactor: AuthInteractorProtocol, router: AuthorizationRouter, @@ -49,14 +53,21 @@ public class SignInViewModel: ObservableObject { self.analytics = analytics self.validator = validator } - + + var socialAuthEnabled: Bool { + config.appleSignIn.enabled || + config.facebook.enabled || + config.microsoft.enabled || + config.google.enabled + } + @MainActor func login(username: String, password: String) async { - guard validator.isValidEmail(username) else { - errorMessage = AuthLocalization.Error.invalidEmailAddress + guard validator.isValidUsername(username) else { + errorMessage = AuthLocalization.Error.invalidEmailAddressOrUsername return } - guard validator.isValidPassword(password) else { + guard !password.isEmpty else { errorMessage = AuthLocalization.Error.invalidPasswordLenght return } @@ -68,27 +79,71 @@ 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 + failure(error) + } + } + + @MainActor + func login(with result: Result) async { + switch result { + case .success(let result): + await socialLogin( + externalToken: result.response.token, + backend: result.backend, + authMethod: result.authMethod + ) + case .failure(let error): + errorMessage = error.localizedDescription + } + } + + @MainActor + private func socialLogin( + externalToken: String, + backend: String, + authMethod: AuthMethod + ) async { + isShowProgress = true + do { + let user = try await interactor.login(externalToken: externalToken, backend: backend) + analytics.setUserID("\(user.id)") + analytics.userLogin(method: authMethod) + router.showMainOrWhatsNewScreen() + } catch let error { + failure(error, authMethod: authMethod) + } + } + + @MainActor + private func failure(_ error: Error, authMethod: AuthMethod? = nil) { + isShowProgress = false + if let validationError = error.validationError, + let value = validationError.data?["error_description"] as? String { + if authMethod != .password, validationError.statusCode == 400, let authMethod = authMethod { + errorMessage = AuthLocalization.Error.accountNotRegistered( + authMethod.analyticsValue, + config.platformName + ) + } else if validationError.statusCode == 403 { + errorMessage = AuthLocalization.Error.disabledAccount } else { - errorMessage = CoreLocalization.Error.unknownError + 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 } } - + func trackSignUpClicked() { analytics.signUpClicked() } - + func trackForgotPasswordClicked() { analytics.forgotPasswordClicked() } + } diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index bc4bd7dee..13320ee9b 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -7,6 +7,7 @@ import SwiftUI import Core +import Theme public struct SignUpView: View { @@ -28,7 +29,7 @@ public struct SignUpView: View { public var body: some View { ZStack(alignment: .top) { VStack { - CoreAssets.authBackground.swiftUIImage + ThemeAssets.authBackground.swiftUIImage .resizable() .edgesIgnoringSafeArea(.top) }.frame(maxWidth: .infinity, maxHeight: 200) @@ -38,12 +39,12 @@ public struct SignUpView: View { ZStack { HStack { Text(AuthLocalization.SignIn.registerBtn) - .titleSettings(color: .white) + .titleSettings(color: Theme.Colors.white) } VStack { Button(action: { viewModel.router.back() }, label: { CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) - .backButtonStyle(color: .white) + .backButtonStyle(color: Theme.Colors.white) }) .foregroundColor(Theme.Colors.styledButtonText) .padding(.leading, isHorizontal ? 48 : 0) @@ -66,7 +67,17 @@ public struct SignUpView: View { .font(Theme.Fonts.titleSmall) .foregroundColor(Theme.Colors.textPrimary) .padding(.bottom, 20) - + + if viewModel.thirdPartyAuthSuccess { + Text(AuthLocalization.SignUp.successSigninLabel) + .font(Theme.Fonts.titleMedium) + .foregroundColor(Theme.Colors.textPrimary) + Text(AuthLocalization.SignUp.successSigninSublabel) + .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,15 +108,27 @@ public struct SignUpView: View { }.frame(maxWidth: .infinity) } else { StyledButton(AuthLocalization.SignUp.createAccountBtn) { + viewModel.thirdPartyAuthSuccess = false Task { await viewModel.registerUser() } viewModel.trackCreateAccountClicked() } .padding(.top, 40) - .padding(.bottom, 80) .frame(maxWidth: .infinity) } + if viewModel.socialAuthEnabled, + !requiredFields.isEmpty { + SocialAuthView( + authType: .register, + viewModel: .init( + config: viewModel.config + ) { result in + Task { await viewModel.register(with: result) } + } + ) + .padding(.bottom, 30) + } Spacer() } .padding(.horizontal, 24) diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index ff3691c05..927651b7d 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -8,12 +8,17 @@ import Foundation import Core import SwiftUI +import AuthenticationServices +import FacebookLogin +import GoogleSignIn +import MSAL public class SignUpViewModel: ObservableObject { @Published var isShowProgress = false @Published var scrollTo: Int? @Published var showError: Bool = false + @Published var thirdPartyAuthSuccess: Bool = false var errorMessage: String? { didSet { withAnimation { @@ -47,8 +52,21 @@ public class SignUpViewModel: ObservableObject { self.cssInjector = cssInjector self.validator = validator } - + + var socialAuthEnabled: Bool { + let socialLoginEnabled = config.appleSignIn.enabled || + config.facebook.enabled || + config.microsoft.enabled || + config.google.enabled + return socialLoginEnabled && !thirdPartyAuthSuccess && !isShowProgress + } + private func showErrors(errors: [String: String]) -> Bool { + if thirdPartyAuthSuccess, !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 }) { @@ -78,20 +96,21 @@ public class SignUpViewModel: ObservableObject { } } } - + + private var externalToken: String? + private var backend: String? + @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" + let validateFields = configureFields() 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 @@ -108,7 +127,66 @@ 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) async { + switch result { + case .success(let result): + await loginOrRegister( + result.response, + backend: result.backend, + authMethod: result.authMethod + ) + case .failure(let error): + errorMessage = error.localizedDescription + } + } + + @MainActor + private func loginOrRegister( + _ response: SocialAuthResponse, + backend: String, + authMethod: AuthMethod + ) async { + do { + isShowProgress = true + let user = try await interactor.login(externalToken: response.token, backend: backend) + analytics.setUserID("\(user.id)") + analytics.userLogin(method: authMethod) + isShowProgress = false + router.showMainOrWhatsNewScreen() + } catch { + update(fullName: response.name, email: response.email) + self.externalToken = response.token + self.backend = backend + thirdPartyAuthSuccess = true + isShowProgress = false + 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/Reset Password/ResetPasswordView.swift b/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift index ef4d1c7fb..acb2a6df3 100644 --- a/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift +++ b/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift @@ -7,6 +7,7 @@ import SwiftUI import Core +import Theme public struct ResetPasswordView: View { @@ -26,15 +27,15 @@ public struct ResetPasswordView: View { public var body: some View { ZStack(alignment: .top) { VStack { - CoreAssets.authBackground.swiftUIImage + ThemeAssets.authBackground.swiftUIImage .resizable() .edgesIgnoringSafeArea(.top) }.frame(maxWidth: .infinity, maxHeight: 200) VStack(alignment: .center) { NavigationBar(title: AuthLocalization.Forgot.title, - titleColor: .white, - leftButtonColor: .white, + titleColor: Theme.Colors.white, + leftButtonColor: Theme.Colors.white, leftButtonAction: { viewModel.router.back() }).padding(.leading, isHorizontal ? 48 : 0) @@ -125,7 +126,7 @@ public struct ResetPasswordView: View { VStack { Text(viewModel.alertMessage ?? "") .shadowCardStyle(bgColor: Theme.Colors.accentColor, - textColor: .white) + textColor: Theme.Colors.white) .padding(.top, 80) Spacer() diff --git a/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift new file mode 100644 index 000000000..2e91ebf82 --- /dev/null +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthView.swift @@ -0,0 +1,113 @@ +// +// SocialAuthView.swift +// Authorization +// +// Created by Eugene Yatsenko on 10.10.2023. +// + +import SwiftUI +import Core + +struct SocialAuthView: View { + + // MARK: - Properties + + @StateObject var viewModel: SocialAuthViewModel + + init( + authType: SocialAuthType = .signIn, + viewModel: SocialAuthViewModel + ) { + self._viewModel = .init(wrappedValue: viewModel) + self.authType = authType + } + + enum SocialAuthType { + case signIn + case register + } + var authType: SocialAuthType = .signIn + + private var title: String { + switch authType { + case .signIn: + AuthLocalization.signInWith + case .register: + AuthLocalization.registerWith + } + } + + // 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 { + if viewModel.googleEnabled { + SocialAuthButton( + image: CoreAssets.iconGoogleWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.google)", + textColor: .black, + backgroundColor: CoreAssets.googleButtonColor.swiftUIColor, + action: { Task { await viewModel.signInWithGoogle() } } + ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.facebook)") + } + if viewModel.faceboolEnabled { + SocialAuthButton( + image: CoreAssets.iconFacebookWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.facebook)", + backgroundColor: CoreAssets.facebookButtonColor.swiftUIColor, + action: { Task { await viewModel.signInWithFacebook() } } + ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.facebook)") + } + if viewModel.microsoftEnabled { + SocialAuthButton( + image: CoreAssets.iconMicrosoftWhite.swiftUIImage, + title: "\(title) \(AuthLocalization.microsoft)", + backgroundColor: CoreAssets.microsoftButtonColor.swiftUIColor, + action: { Task { await viewModel.signInWithMicrosoft() } } + ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.microsoft)") + } + if viewModel.appleSignInEnabled { + SocialAuthButton( + image: CoreAssets.iconApple.swiftUIImage, + title: "\(title) \(AuthLocalization.apple)", + backgroundColor: CoreAssets.appleButtonColor.swiftUIColor, + action: viewModel.signInWithApple + ) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(title) \(AuthLocalization.apple)") + } + } + } +} + +#if DEBUG +struct SocialSignView_Previews: PreviewProvider { + static var previews: some View { + 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..24e8ca5b8 --- /dev/null +++ b/Authorization/Authorization/Presentation/SocialAuth/SocialAuthViewModel.swift @@ -0,0 +1,157 @@ +// +// SocialAuthViewModel.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(SocialAuthResponse) + case facebook(SocialAuthResponse) + case google(SocialAuthResponse) + case microsoft(SocialAuthResponse) + + var backend: String { + switch self { + case .apple: + "apple-id" + case .facebook: + "facebook" + case .google: + "google-oauth2" + case .microsoft: + "azuread-oauth2" + } + } + + var authMethod: AuthMethod { + switch self { + case .apple: + .socailAuth(.apple) + case .facebook: + .socailAuth(.facebook) + case .google: + .socailAuth(.google) + case .microsoft: + .socailAuth(.microsoft) + } + } + + var response: SocialAuthResponse { + switch self { + case .apple(let response), + .facebook(let response), + .google(let response), + .microsoft(let response): + return response + } + } +} + +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.enabled + } + + // 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)) } + 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/Startup/LogistrationBottomView.swift b/Authorization/Authorization/Presentation/Startup/LogistrationBottomView.swift new file mode 100644 index 000000000..8bd82e445 --- /dev/null +++ b/Authorization/Authorization/Presentation/Startup/LogistrationBottomView.swift @@ -0,0 +1,66 @@ +// +// LogistrationBottomView.swift +// Authorization +// +// Created by SaeedBashir on 10/26/23. +// + +import Foundation +import SwiftUI +import Core +import Theme + +public struct LogistrationBottomView: View { + @ObservedObject + private var viewModel: StartupViewModel + + @Environment(\.isHorizontal) private var isHorizontal + + public init(viewModel: StartupViewModel) { + self.viewModel = viewModel + } + + public var body: some View { + VStack(alignment: .leading) { + HStack(spacing: 24) { + StyledButton(AuthLocalization.SignIn.registerBtn) { + viewModel.router.showRegisterScreen() + viewModel.tracksignUpClicked() + } + .frame(maxWidth: .infinity) + + StyledButton( + AuthLocalization.SignIn.logInTitle, + action: { viewModel.router.showLoginScreen() }, + color: .white, + textColor: Theme.Colors.accentColor, + borderColor: Theme.Colors.textInputStroke + ) + .frame(width: 100) + } + .padding(.horizontal, isHorizontal ? 0 : 0) + } + .padding(.horizontal, isHorizontal ? 10 : 24) + } +} + +#if DEBUG +struct LogistrationBottomView_Previews: PreviewProvider { + static var previews: some View { + let vm = StartupViewModel( + interactor: AuthInteractor.mock, + router: AuthorizationRouterMock(), + analytics: AuthorizationAnalyticsMock() + ) + LogistrationBottomView(viewModel: vm) + .preferredColorScheme(.light) + .previewDisplayName("StartupView Light") + .loadFonts() + + LogistrationBottomView(viewModel: vm) + .preferredColorScheme(.dark) + .previewDisplayName("StartupView Dark") + .loadFonts() + } +} +#endif diff --git a/Authorization/Authorization/Presentation/Startup/StartupView.swift b/Authorization/Authorization/Presentation/Startup/StartupView.swift new file mode 100644 index 000000000..8cfc50347 --- /dev/null +++ b/Authorization/Authorization/Presentation/Startup/StartupView.swift @@ -0,0 +1,125 @@ +// +// StartupView.swift +// Authorization +// +// Created by SaeedBashir on 10/23/23. +// + +import Foundation +import SwiftUI +import Core +import Theme + +public struct StartupView: View { + + @State private var searchQuery: String = "" + + @Environment(\.isHorizontal) private var isHorizontal + + @ObservedObject + private var viewModel: StartupViewModel + + public init(viewModel: StartupViewModel) { + self.viewModel = viewModel + } + + public var body: some View { + ZStack(alignment: .top) { + VStack(alignment: .leading) { + ThemeAssets.appLogo.swiftUIImage + .resizable() + .frame(maxWidth: 189, maxHeight: 54) + .padding(.top, isHorizontal ? 20 : 40) + .padding(.bottom, isHorizontal ? 0 : 20) + .padding(.horizontal, isHorizontal ? 10 : 24) + .colorMultiply(Theme.Colors.accentColor) + + VStack { + VStack(alignment: .leading) { + Text(AuthLocalization.Startup.infoMessage) + .font(Theme.Fonts.titleLarge) + .foregroundColor(Theme.Colors.textPrimary) + .padding(.bottom, isHorizontal ? 10 : 20 ) + + Text(AuthLocalization.Startup.searchTitle) + .font(Theme.Fonts.bodyLarge) + .bold() + .foregroundColor(Theme.Colors.textPrimary) + .padding(.top, isHorizontal ? 0 : 24) + + HStack(spacing: 11) { + Image(systemName: "magnifyingglass") + .padding(.leading, 16) + .padding(.top, 1) + TextField(AuthLocalization.Startup.searchPlaceholder, text: $searchQuery, onCommit: { + if searchQuery.isEmpty { return } + viewModel.router.showDiscoveryScreen(searchQuery: searchQuery, fromStartupScreen: true) + }) + .autocapitalization(.none) + .autocorrectionDisabled() + .frame(minHeight: 50) + .submitLabel(.search) + + }.overlay( + Theme.Shapes.textInputShape + .stroke(lineWidth: 1) + .fill(Theme.Colors.textInputStroke) + ) + .background( + Theme.Shapes.textInputShape + .fill(Theme.Colors.textInputBackground) + ) + + Button { + viewModel.router.showDiscoveryScreen(searchQuery: searchQuery, fromStartupScreen: true) + } label: { + Text(AuthLocalization.Startup.exploreAllCourses) + .underline() + .foregroundColor(Theme.Colors.accentColor) + .font(Theme.Fonts.bodyLarge) + } + .padding(.top, isHorizontal ? 0 : 5) + Spacer() + } + .padding(.horizontal, isHorizontal ? 10 : 24) + + LogistrationBottomView(viewModel: viewModel) + } + .padding(.top, 10) + .padding(.bottom, 2) + } + .onDisappear { + searchQuery = "" + } + } + .hideNavigationBar() + .padding(.all, isHorizontal ? 1 : 0) + .background(Theme.Colors.background.ignoresSafeArea(.all)) + .ignoresSafeArea(.keyboard, edges: .bottom) + .onTapGesture { + UIApplication.shared.endEditing() + } + } +} + +#if DEBUG +struct StartupView_Previews: PreviewProvider { + static var previews: some View { + let vm = StartupViewModel( + interactor: AuthInteractor.mock, + router: AuthorizationRouterMock(), + analytics: AuthorizationAnalyticsMock() + ) + + StartupView(viewModel: vm) + .preferredColorScheme(.light) + .previewDisplayName("StartupView Light") + .loadFonts() + + StartupView(viewModel: vm) + .preferredColorScheme(.dark) + .previewDisplayName("StartupView Dark") + .loadFonts() + } +} +#endif diff --git a/Authorization/Authorization/Presentation/Startup/StartupViewModel.swift b/Authorization/Authorization/Presentation/Startup/StartupViewModel.swift new file mode 100644 index 000000000..1a13aaea9 --- /dev/null +++ b/Authorization/Authorization/Presentation/Startup/StartupViewModel.swift @@ -0,0 +1,30 @@ +// +// StartupViewModel.swift +// Authorization +// +// Created by SaeedBashir on 10/23/23. +// + +import Foundation +import Core + +public class StartupViewModel: ObservableObject { + let router: AuthorizationRouter + private let interactor: AuthInteractorProtocol + private let analytics: AuthorizationAnalytics + @Published var searchQuery: String? + + public init( + interactor: AuthInteractorProtocol, + router: AuthorizationRouter, + analytics: AuthorizationAnalytics + ) { + self.interactor = interactor + self.router = router + self.analytics = analytics + } + + func tracksignUpClicked() { + analytics.signUpClicked() + } +} diff --git a/Authorization/Authorization/SwiftGen/Strings.swift b/Authorization/Authorization/SwiftGen/Strings.swift index 9d7d92b4e..a2cd9accf 100644 --- a/Authorization/Authorization/SwiftGen/Strings.swift +++ b/Authorization/Authorization/SwiftGen/Strings.swift @@ -10,9 +10,31 @@ 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 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 { + /// 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 + public static let invalidEmailAddressOrUsername = AuthLocalization.tr("Localizable", "ERROR.INVALID_EMAIL_ADDRESS_OR_USERNAME", fallback: "Invalid email or username") /// Invalid password lenght public static let invalidPasswordLenght = AuthLocalization.tr("Localizable", "ERROR.INVALID_PASSWORD_LENGHT", fallback: "Invalid password lenght") } @@ -31,6 +53,8 @@ public enum AuthLocalization { public enum SignIn { /// Email public static let email = AuthLocalization.tr("Localizable", "SIGN_IN.EMAIL", fallback: "Email") + /// Email or username + public static let emailOrUsername = AuthLocalization.tr("Localizable", "SIGN_IN.EMAIL_OR_USERNAME", fallback: "Email or username") /// Forgot password? public static let forgotPassBtn = AuthLocalization.tr("Localizable", "SIGN_IN.FORGOT_PASS_BTN", fallback: "Forgot password?") /// Sign in @@ -56,9 +80,23 @@ 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 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 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") } + public enum Startup { + /// Explore all courses + public static let exploreAllCourses = AuthLocalization.tr("Localizable", "STARTUP.EXPLORE_ALL_COURSES", fallback: "Explore all courses") + /// Courses and programs from the world's best universities in your pocket. + public static let infoMessage = AuthLocalization.tr("Localizable", "STARTUP.INFO_MESSAGE", fallback: "Courses and programs from the world's best universities in your pocket.") + /// Search our 3000+ courses + public static let searchPlaceholder = AuthLocalization.tr("Localizable", "STARTUP.SEARCH_PLACEHOLDER", fallback: "Search our 3000+ courses") + /// What do you want to learn? + public static let searchTitle = AuthLocalization.tr("Localizable", "STARTUP.SEARCH_TITLE", fallback: "What do you want to learn?") + } } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces diff --git a/Authorization/Authorization/en.lproj/Localizable.strings b/Authorization/Authorization/en.lproj/Localizable.strings index 88365042c..50807ea57 100644 --- a/Authorization/Authorization/en.lproj/Localizable.strings +++ b/Authorization/Authorization/en.lproj/Localizable.strings @@ -9,23 +9,41 @@ "SIGN_IN.LOG_IN_TITLE" = "Sign in"; "SIGN_IN.WELCOME_BACK" = "Welcome back! Please authorize to continue."; "SIGN_IN.EMAIL" = "Email"; +"SIGN_IN.EMAIL_OR_USERNAME" = "Email or username"; "SIGN_IN.PASSWORD" = "Password"; "SIGN_IN.REGISTER_BTN" = "Register"; "SIGN_IN.FORGOT_PASS_BTN" = "Forgot password?"; "SIGN_IN.LOG_IN_BTN" = "Sign in"; - "ERROR.INVALID_EMAIL_ADDRESS" = "Invalid email address"; "ERROR.INVALID_PASSWORD_LENGHT" = "Invalid password lenght"; +"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.DISABLED_ACCOUNT" = "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_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."; "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"; +"REGISTER_WITH" = "Register with"; +"APPLE" = "Apple"; +"GOOGLE" = "Google"; +"FACEBOOK" = "Facebook"; +"MICROSOFT" = "Microsoft"; +"OR" = "Or"; + +"STARTUP.INFO_MESSAGE" = "Courses and programs from the world's best universities in your pocket."; +"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/Authorization/Authorization/uk.lproj/Localizable.strings b/Authorization/Authorization/uk.lproj/Localizable.strings index cc06c89a0..5cbcbb2d8 100644 --- a/Authorization/Authorization/uk.lproj/Localizable.strings +++ b/Authorization/Authorization/uk.lproj/Localizable.strings @@ -16,15 +16,32 @@ "ERROR.INVALID_EMAIL_ADDRESS" = "невірна адреса електронної пошти"; "ERROR.INVALID_PASSWORD_LENGHT" = "Пароль занадто короткий або занадто довгий"; +"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творити новий акаунт."; "SIGN_UP.CREATE_ACCOUNT_BTN" = "Створити акаунт"; "SIGN_UP.HIDE_FIELDS" = "Приховати необовʼязкові поля"; "SIGN_UP.SHOW_FIELDS" = "Показати необовʼязкові поля"; +"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" = "Будь ласка, введіть свою адресу електронної пошти для входу або відновлення нижче, і ми надішлемо вам електронний лист з інструкціями."; "FORGOT.REQUEST" = "Відновити пароль"; "FORGOT.CHECK_TITLE" = "Перевірте свою електронну пошту"; "FORGOT.CHECK_Description" = "Ми надіслали інструкції щодо відновлення пароля на вашу електронну пошту "; + +"SIGN_IN_WITH" = "Sign in with"; +"REGISTER_WITH" = "Register with"; +"APPLE" = "Apple"; +"GOOGLE" = "Google"; +"FACEBOOK" = "Facebook"; +"MICROSOFT" = "Microsoft"; +"OR" = "Or"; + +"STARTUP.INFO_MESSAGE" = "Courses and programs from the world's best universities in your pocket."; +"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/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index 2afc95ada..d6a968438 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) @@ -466,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`) } @@ -505,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 @@ -579,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)} @@ -594,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 { @@ -773,6 +822,12 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { perform?() } + open func showStartupScreen() { + addInvocation(.m_showStartupScreen) + let perform = methodPerformValue(.m_showStartupScreen) as? () -> Void + perform?() + } + open func showLoginScreen() { addInvocation(.m_showLoginScreen) let perform = methodPerformValue(.m_showLoginScreen) as? () -> Void @@ -791,6 +846,12 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { perform?() } + open func showDiscoveryScreen(searchQuery: String?, fromStartupScreen: Bool) { + addInvocation(.m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(Parameter.value(`searchQuery`), Parameter.value(`fromStartupScreen`))) + let perform = methodPerformValue(.m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(Parameter.value(`searchQuery`), Parameter.value(`fromStartupScreen`))) as? (String?, Bool) -> Void + perform?(`searchQuery`, `fromStartupScreen`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -824,9 +885,11 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) case m_showMainOrWhatsNewScreen + case m_showStartupScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen + case m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(Parameter, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -863,12 +926,20 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match + case (.m_showStartupScreen, .m_showStartupScreen): return .match + case (.m_showLoginScreen, .m_showLoginScreen): return .match case (.m_showRegisterScreen, .m_showRegisterScreen): return .match case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(let lhsSearchquery, let lhsFromstartupscreen), .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(let rhsSearchquery, let rhsFromstartupscreen)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSearchquery, rhs: rhsSearchquery, with: matcher), lhsSearchquery, rhsSearchquery, "searchQuery")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFromstartupscreen, rhs: rhsFromstartupscreen, with: matcher), lhsFromstartupscreen, rhsFromstartupscreen, "fromStartupScreen")) + return Matcher.ComparisonResult(results) + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -915,9 +986,11 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue case .m_showMainOrWhatsNewScreen: return 0 + case .m_showStartupScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 + case let .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(p0, p1): return p0.intValue + p1.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -933,9 +1006,11 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" + case .m_showStartupScreen: return ".showStartupScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" + case .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen: return ".showDiscoveryScreen(searchQuery:fromStartupScreen:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -965,9 +1040,11 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} + public static func showStartupScreen() -> Verify { return Verify(method: .m_showStartupScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} + public static func showDiscoveryScreen(searchQuery: Parameter, fromStartupScreen: Parameter) -> Verify { return Verify(method: .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(`searchQuery`, `fromStartupScreen`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -999,6 +1076,9 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } + public static func showStartupScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showStartupScreen, performs: perform) + } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) } @@ -1008,6 +1088,9 @@ open class AuthorizationRouterMock: AuthorizationRouter, Mock { public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showForgotPasswordScreen, performs: perform) } + public static func showDiscoveryScreen(searchQuery: Parameter, fromStartupScreen: Parameter, perform: @escaping (String?, Bool) -> Void) -> Perform { + return Perform(method: .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(`searchQuery`, `fromStartupScreen`), performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter, perform: @escaping (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void) -> Perform { return Perform(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } @@ -1175,6 +1258,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showStartupScreen() { + addInvocation(.m_showStartupScreen) + let perform = methodPerformValue(.m_showStartupScreen) as? () -> Void + perform?() + } + open func showLoginScreen() { addInvocation(.m_showLoginScreen) let perform = methodPerformValue(.m_showLoginScreen) as? () -> Void @@ -1193,6 +1282,12 @@ open class BaseRouterMock: BaseRouter, Mock { perform?() } + open func showDiscoveryScreen(searchQuery: String?, fromStartupScreen: Bool) { + addInvocation(.m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(Parameter.value(`searchQuery`), Parameter.value(`fromStartupScreen`))) + let perform = methodPerformValue(.m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(Parameter.value(`searchQuery`), Parameter.value(`fromStartupScreen`))) as? (String?, Bool) -> Void + perform?(`searchQuery`, `fromStartupScreen`) + } + open func presentAlert(alertTitle: String, alertMessage: String, positiveAction: String, onCloseTapped: @escaping () -> Void, okTapped: @escaping () -> Void, type: AlertViewType) { addInvocation(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) let perform = methodPerformValue(.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter.value(`alertTitle`), Parameter.value(`alertMessage`), Parameter.value(`positiveAction`), Parameter<() -> Void>.value(`onCloseTapped`), Parameter<() -> Void>.value(`okTapped`), Parameter.value(`type`))) as? (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void @@ -1225,9 +1320,11 @@ open class BaseRouterMock: BaseRouter, Mock { case m_dismiss__animated_animated(Parameter) case m_removeLastView__controllers_controllers(Parameter) case m_showMainOrWhatsNewScreen + case m_showStartupScreen case m_showLoginScreen case m_showRegisterScreen case m_showForgotPasswordScreen + case m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(Parameter, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter) case m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(Parameter, Parameter, Parameter, Parameter, Parameter, Parameter<() -> Void>, Parameter<() -> Void>, Parameter<() -> Void>) case m_presentView__transitionStyle_transitionStyleview_view(Parameter, Parameter) @@ -1259,12 +1356,20 @@ open class BaseRouterMock: BaseRouter, Mock { case (.m_showMainOrWhatsNewScreen, .m_showMainOrWhatsNewScreen): return .match + case (.m_showStartupScreen, .m_showStartupScreen): return .match + case (.m_showLoginScreen, .m_showLoginScreen): return .match case (.m_showRegisterScreen, .m_showRegisterScreen): return .match case (.m_showForgotPasswordScreen, .m_showForgotPasswordScreen): return .match + case (.m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(let lhsSearchquery, let lhsFromstartupscreen), .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(let rhsSearchquery, let rhsFromstartupscreen)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsSearchquery, rhs: rhsSearchquery, with: matcher), lhsSearchquery, rhsSearchquery, "searchQuery")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsFromstartupscreen, rhs: rhsFromstartupscreen, with: matcher), lhsFromstartupscreen, rhsFromstartupscreen, "fromStartupScreen")) + return Matcher.ComparisonResult(results) + case (.m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let lhsAlerttitle, let lhsAlertmessage, let lhsPositiveaction, let lhsOnclosetapped, let lhsOktapped, let lhsType), .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(let rhsAlerttitle, let rhsAlertmessage, let rhsPositiveaction, let rhsOnclosetapped, let rhsOktapped, let rhsType)): var results: [Matcher.ParameterComparisonResult] = [] results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsAlerttitle, rhs: rhsAlerttitle, with: matcher), lhsAlerttitle, rhsAlerttitle, "alertTitle")) @@ -1310,9 +1415,11 @@ open class BaseRouterMock: BaseRouter, Mock { case let .m_dismiss__animated_animated(p0): return p0.intValue case let .m_removeLastView__controllers_controllers(p0): return p0.intValue case .m_showMainOrWhatsNewScreen: return 0 + case .m_showStartupScreen: return 0 case .m_showLoginScreen: return 0 case .m_showRegisterScreen: return 0 case .m_showForgotPasswordScreen: return 0 + case let .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(p0, p1): return p0.intValue + p1.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(p0, p1, p2, p3, p4, p5): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue case let .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(p0, p1, p2, p3, p4, p5, p6, p7): return p0.intValue + p1.intValue + p2.intValue + p3.intValue + p4.intValue + p5.intValue + p6.intValue + p7.intValue case let .m_presentView__transitionStyle_transitionStyleview_view(p0, p1): return p0.intValue + p1.intValue @@ -1327,9 +1434,11 @@ open class BaseRouterMock: BaseRouter, Mock { case .m_dismiss__animated_animated: return ".dismiss(animated:)" case .m_removeLastView__controllers_controllers: return ".removeLastView(controllers:)" case .m_showMainOrWhatsNewScreen: return ".showMainOrWhatsNewScreen()" + case .m_showStartupScreen: return ".showStartupScreen()" case .m_showLoginScreen: return ".showLoginScreen()" case .m_showRegisterScreen: return ".showRegisterScreen()" case .m_showForgotPasswordScreen: return ".showForgotPasswordScreen()" + case .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen: return ".showDiscoveryScreen(searchQuery:fromStartupScreen:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type: return ".presentAlert(alertTitle:alertMessage:positiveAction:onCloseTapped:okTapped:type:)" case .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped: return ".presentAlert(alertTitle:alertMessage:nextSectionName:action:image:onCloseTapped:okTapped:nextSectionTapped:)" case .m_presentView__transitionStyle_transitionStyleview_view: return ".presentView(transitionStyle:view:)" @@ -1358,9 +1467,11 @@ open class BaseRouterMock: BaseRouter, Mock { public static func dismiss(animated: Parameter) -> Verify { return Verify(method: .m_dismiss__animated_animated(`animated`))} public static func removeLastView(controllers: Parameter) -> Verify { return Verify(method: .m_removeLastView__controllers_controllers(`controllers`))} public static func showMainOrWhatsNewScreen() -> Verify { return Verify(method: .m_showMainOrWhatsNewScreen)} + public static func showStartupScreen() -> Verify { return Verify(method: .m_showStartupScreen)} public static func showLoginScreen() -> Verify { return Verify(method: .m_showLoginScreen)} public static func showRegisterScreen() -> Verify { return Verify(method: .m_showRegisterScreen)} public static func showForgotPasswordScreen() -> Verify { return Verify(method: .m_showForgotPasswordScreen)} + public static func showDiscoveryScreen(searchQuery: Parameter, fromStartupScreen: Parameter) -> Verify { return Verify(method: .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(`searchQuery`, `fromStartupScreen`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`))} public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, nextSectionName: Parameter, action: Parameter, image: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, nextSectionTapped: Parameter<() -> Void>) -> Verify { return Verify(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagenextSectionName_nextSectionNameaction_actionimage_imageonCloseTapped_onCloseTappedokTapped_okTappednextSectionTapped_nextSectionTapped(`alertTitle`, `alertMessage`, `nextSectionName`, `action`, `image`, `onCloseTapped`, `okTapped`, `nextSectionTapped`))} public static func presentView(transitionStyle: Parameter, view: Parameter) -> Verify { return Verify(method: .m_presentView__transitionStyle_transitionStyleview_view(`transitionStyle`, `view`))} @@ -1389,6 +1500,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showMainOrWhatsNewScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showMainOrWhatsNewScreen, performs: perform) } + public static func showStartupScreen(perform: @escaping () -> Void) -> Perform { + return Perform(method: .m_showStartupScreen, performs: perform) + } public static func showLoginScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showLoginScreen, performs: perform) } @@ -1398,6 +1512,9 @@ open class BaseRouterMock: BaseRouter, Mock { public static func showForgotPasswordScreen(perform: @escaping () -> Void) -> Perform { return Perform(method: .m_showForgotPasswordScreen, performs: perform) } + public static func showDiscoveryScreen(searchQuery: Parameter, fromStartupScreen: Parameter, perform: @escaping (String?, Bool) -> Void) -> Perform { + return Perform(method: .m_showDiscoveryScreen__searchQuery_searchQueryfromStartupScreen_fromStartupScreen(`searchQuery`, `fromStartupScreen`), performs: perform) + } public static func presentAlert(alertTitle: Parameter, alertMessage: Parameter, positiveAction: Parameter, onCloseTapped: Parameter<() -> Void>, okTapped: Parameter<() -> Void>, type: Parameter, perform: @escaping (String, String, String, @escaping () -> Void, @escaping () -> Void, AlertViewType) -> Void) -> Perform { return Perform(method: .m_presentAlert__alertTitle_alertTitlealertMessage_alertMessagepositiveAction_positiveActiononCloseTapped_onCloseTappedokTapped_okTappedtype_type(`alertTitle`, `alertMessage`, `positiveAction`, `onCloseTapped`, `okTapped`, `type`), performs: perform) } diff --git a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift index 036da9d10..484807136 100644 --- a/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift +++ b/Authorization/AuthorizationTests/Presentation/Login/SignInViewModelTests.swift @@ -35,12 +35,12 @@ final class SignInViewModelTests: XCTestCase { validator: validator ) - await viewModel.login(username: "email", password: "") + await viewModel.login(username: "", password: "") Verify(interactor, 0, .login(username: .any, password: .any)) Verify(router, 0, .showMainOrWhatsNewScreen()) - XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidEmailAddress) + XCTAssertEqual(viewModel.errorMessage, AuthLocalization.Error.invalidEmailAddressOrUsername) XCTAssertEqual(viewModel.isShowProgress, false) } @@ -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( + AuthMethod.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/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/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index f17d7acb6..a44ee85d7 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -63,7 +63,6 @@ 028CE96929858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028CE96829858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift */; }; 028F9F37293A44C700DE65D0 /* Data_ResetPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028F9F36293A44C700DE65D0 /* Data_ResetPassword.swift */; }; 028F9F39293A452B00DE65D0 /* ResetPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028F9F38293A452B00DE65D0 /* ResetPassword.swift */; }; - 0295B1DC297FF114003B0C65 /* SF-Pro.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0295B1DA297FF0E9003B0C65 /* SF-Pro.ttf */; }; 0295C885299B99DD00ABE571 /* RefreshableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0295C884299B99DD00ABE571 /* RefreshableScrollView.swift */; }; 02A463112AEA966C00331037 /* AppReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A463102AEA966C00331037 /* AppReviewView.swift */; }; 02A4833529B8A73400D33F33 /* CorePersistenceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A4833429B8A73400D33F33 /* CorePersistenceProtocol.swift */; }; @@ -86,6 +85,7 @@ 02F6EF3B28D9B8EC00835477 /* CourseCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F6EF3A28D9B8EC00835477 /* CourseCellView.swift */; }; 02F6EF4A28D9F0A700835477 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F6EF4928D9F0A700835477 /* DateExtension.swift */; }; 02F98A7F28F81EE900DE94C0 /* Container+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F98A7E28F81EE900DE94C0 /* Container+App.swift */; }; + 0604C9AA2B22FACF00AD5DBF /* UIComponentsConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0604C9A92B22FACF00AD5DBF /* UIComponentsConfig.swift */; }; 070019A528F6F17900D5FC78 /* Data_Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070019A428F6F17900D5FC78 /* Data_Media.swift */; }; 070019AC28F6FD0100D5FC78 /* CourseDetailBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070019AB28F6FD0100D5FC78 /* CourseDetailBlock.swift */; }; 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 070019AD28F701B200D5FC78 /* Certificate.swift */; }; @@ -117,17 +117,32 @@ 0770DE5B28D0B209006D8A5D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0770DE5D28D0B209006D8A5D /* Localizable.strings */; }; 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE5E28D0B22C006D8A5D /* Strings.swift */; }; 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; - 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 */; }; BA593F1C2AF8E498009ADB51 /* ScrollSlidingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */; }; BA593F1E2AF8E4A0009ADB51 /* FrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1D2AF8E4A0009ADB51 /* FrameReader.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 */; }; + BA8FA66A2AD59B5500EA029A /* GoogleAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */; }; + BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; + 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 */; }; + 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 */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */; }; DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */; }; - DBF6F2482B01E20A0098414B /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2472B01E20A0098414B /* ConfigTests.swift */; }; DBF6F24A2B0380E00098414B /* FeaturesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2492B0380E00098414B /* FeaturesConfig.swift */; }; + E055A5392B18DC95008D9E5E /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; }; + E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E09179FD2B0F204E002AB695 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09179FC2B0F204D002AB695 /* ConfigTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -140,6 +155,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + E055A53B2B18DC95008D9E5E /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 020306CB2932C0C4000949EA /* PickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = ""; }; 02066B472906F73400F4307E /* PickerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerMenu.swift; sourceTree = ""; }; @@ -196,7 +225,6 @@ 028CE96829858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexibleKeyboardInputView.swift; sourceTree = ""; }; 028F9F36293A44C700DE65D0 /* Data_ResetPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_ResetPassword.swift; sourceTree = ""; }; 028F9F38293A452B00DE65D0 /* ResetPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPassword.swift; sourceTree = ""; }; - 0295B1DA297FF0E9003B0C65 /* SF-Pro.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro.ttf"; sourceTree = ""; }; 0295C884299B99DD00ABE571 /* RefreshableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshableScrollView.swift; sourceTree = ""; }; 02A463102AEA966C00331037 /* AppReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewView.swift; sourceTree = ""; }; 02A4833429B8A73400D33F33 /* CorePersistenceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorePersistenceProtocol.swift; sourceTree = ""; }; @@ -220,6 +248,7 @@ 02F6EF3A28D9B8EC00835477 /* CourseCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseCellView.swift; sourceTree = ""; }; 02F6EF4928D9F0A700835477 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; 02F98A7E28F81EE900DE94C0 /* Container+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Container+App.swift"; sourceTree = ""; }; + 0604C9A92B22FACF00AD5DBF /* UIComponentsConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIComponentsConfig.swift; sourceTree = ""; }; 070019A428F6F17900D5FC78 /* Data_Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_Media.swift; sourceTree = ""; }; 070019AB28F6FD0100D5FC78 /* CourseDetailBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailBlock.swift; sourceTree = ""; }; 070019AD28F701B200D5FC78 /* Certificate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Certificate.swift; sourceTree = ""; }; @@ -253,8 +282,6 @@ 0770DE5C28D0B209006D8A5D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 0770DE5E28D0B22C006D8A5D /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 0770DE6028D0B2CB006D8A5D /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; - 0770DE7828D0C4A9006D8A5D /* RoundedCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCorners.swift; sourceTree = ""; }; - 0770DE7A28D0C78C006D8A5D /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Animation.swift"; sourceTree = ""; }; 0E13E9173C9C4CFC19F8B6F2 /* Pods-App-Core.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugstage.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugstage.xcconfig"; sourceTree = ""; }; 1A154A95AF4EE85A4A1C083B /* Pods-App-Core.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releasedev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releasedev.xcconfig"; sourceTree = ""; }; @@ -265,12 +292,26 @@ 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 = ""; }; BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollSlidingTabBar.swift; sourceTree = ""; }; BA593F1D2AF8E4A0009ADB51 /* FrameReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameReader.swift; 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 = ""; }; + 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 = ""; }; + 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 = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConfig.swift; sourceTree = ""; }; DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementConfig.swift; sourceTree = ""; }; - DBF6F2472B01E20A0098414B /* ConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; DBF6F2492B0380E00098414B /* FeaturesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesConfig.swift; sourceTree = ""; }; + E055A5382B18DC95008D9E5E /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E09179FC2B0F204D002AB695 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -286,8 +327,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */, 025EF2F62971740000B838AB /* YouTubePlayerKit in Frameworks */, C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */, + BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */, + E055A5392B18DC95008D9E5E /* Theme.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,19 +423,13 @@ 0727878228D31287002E9142 /* DispatchQueue+App.swift */, 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */, 02284C172A3B1AE00007117F /* UIApplicationExtension.swift */, + BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */, + BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */, 02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */, ); path = Extensions; sourceTree = ""; }; - 0295B1DB297FF0E9003B0C65 /* Fonts */ = { - isa = PBXGroup; - children = ( - 0295B1DA297FF0E9003B0C65 /* SF-Pro.ttf */, - ); - path = Fonts; - sourceTree = ""; - }; 02AFCC162AEFDB0F000360F0 /* ThirdPartyMailer */ = { isa = PBXGroup; children = ( @@ -495,7 +533,7 @@ children = ( 0770DE5328D0B00C006D8A5D /* swiftgen.yml */, 0770DE0A28D07831006D8A5D /* Core */, - 078525AC2B0CBFF4007B4521 /* CoreTests */, + E09179FA2B0F204D002AB695 /* CoreTests */, 0770DE0928D07831006D8A5D /* Products */, C9DFE47E699CFFA85A77AF2C /* Pods */, F1620A3A2C8B0699EAA61B57 /* Frameworks */, @@ -514,7 +552,7 @@ 0770DE0A28D07831006D8A5D /* Core */ = { isa = PBXGroup; children = ( - 0295B1DB297FF0E9003B0C65 /* Fonts */, + BA8FA65F2AD5973500EA029A /* Providers */, 027BD3A12909470F00392132 /* AvoidingHelpers */, 0770DE5528D0B142006D8A5D /* SwiftGen */, 0283347E28D4DCC100C828FC /* Extensions */, @@ -523,7 +561,6 @@ 0727876E28D233EC002E9142 /* Configuration */, 0770DE2828D0928B006D8A5D /* Network */, 0770DE7628D0C491006D8A5D /* View */, - 0770DE7A28D0C78C006D8A5D /* Theme.swift */, 0770DE5D28D0B209006D8A5D /* Localizable.strings */, 0770DE5128D0ADFF006D8A5D /* Assets.xcassets */, 071009CF28D1E3A600344290 /* Constants.swift */, @@ -569,7 +606,6 @@ 0770DE7728D0C49E006D8A5D /* Base */ = { isa = PBXGroup; children = ( - 0770DE7828D0C4A9006D8A5D /* RoundedCorners.swift */, 02A4833B29B8C57800D33F33 /* DownloadView.swift */, 02D800CB29348F460099CF16 /* ImagePicker.swift */, 024D723429C8BB1A006D36ED /* NavigationBar.swift */, @@ -597,25 +633,39 @@ 023A1135291432B200D0D354 /* RegistrationTextField.swift */, 023A1137291432FD00D0D354 /* FieldConfiguration.swift */, BA593F1A2AF8E487009ADB51 /* ScrollSlidingTabBar */, + BA8FA6672AD59A5700EA029A /* SocialAuthButton.swift */, 02E93F862AEBAED4006C4750 /* AppReview */, ); path = Base; sourceTree = ""; }; - 078525AC2B0CBFF4007B4521 /* CoreTests */ = { + BA30427C2B20B235009B64B7 /* SocialAuth */ = { isa = PBXGroup; children = ( - 078525AD2B0CC004007B4521 /* Configuration */, + BA30427E2B20B299009B64B7 /* Error */, + BA8FA6602AD5974300EA029A /* AppleAuthProvider.swift */, + BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */, + BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */, + BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */, + BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */, ); - path = CoreTests; + path = SocialAuth; sourceTree = ""; }; - 078525AD2B0CC004007B4521 /* Configuration */ = { + BA30427E2B20B299009B64B7 /* Error */ = { isa = PBXGroup; children = ( - DBF6F2472B01E20A0098414B /* ConfigTests.swift */, + BA30427D2B20B299009B64B7 /* SocialAuthError.swift */, ); - path = Configuration; + path = Error; + sourceTree = ""; + }; + BA8FA65F2AD5973500EA029A /* Providers */ = { + isa = PBXGroup; + children = ( + BA30427C2B20B235009B64B7 /* SocialAuth */, + ); + path = Providers; sourceTree = ""; }; BA593F1A2AF8E487009ADB51 /* ScrollSlidingTabBar */ = { @@ -654,17 +704,39 @@ DBF6F2422B014AF30098414B /* Config */ = { isa = PBXGroup; children = ( + 0604C9A92B22FACF00AD5DBF /* UIComponentsConfig.swift */, 0727876F28D23411002E9142 /* Config.swift */, DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, + BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, + BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, + BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, + BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */, ); path = Config; sourceTree = ""; }; + E09179FA2B0F204D002AB695 /* CoreTests */ = { + isa = PBXGroup; + children = ( + E09179FB2B0F204D002AB695 /* Configuration */, + ); + path = CoreTests; + sourceTree = ""; + }; + E09179FB2B0F204D002AB695 /* Configuration */ = { + isa = PBXGroup; + children = ( + E09179FC2B0F204D002AB695 /* ConfigTests.swift */, + ); + path = Configuration; + sourceTree = ""; + }; F1620A3A2C8B0699EAA61B57 /* Frameworks */ = { isa = PBXGroup; children = ( + E055A5382B18DC95008D9E5E /* Theme.framework */, 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */, ); name = Frameworks; @@ -711,6 +783,7 @@ 0770DE0428D07831006D8A5D /* Sources */, 0770DE0528D07831006D8A5D /* Frameworks */, 0770DE0628D07831006D8A5D /* Resources */, + E055A53B2B18DC95008D9E5E /* Embed Frameworks */, ); buildRules = ( ); @@ -719,6 +792,8 @@ name = Core; packageProductDependencies = ( 025EF2F52971740000B838AB /* YouTubePlayerKit */, + BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */, + BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */, ); productName = Core; productReference = 0770DE0828D07831006D8A5D /* Core.framework */; @@ -756,6 +831,8 @@ mainGroup = 0770DDFE28D07831006D8A5D; packageReferences = ( 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, + BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, + BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, ); productRefGroup = 0770DE0928D07831006D8A5D /* Products */; projectDirPath = ""; @@ -780,7 +857,6 @@ buildActionMask = 2147483647; files = ( 0770DE5228D0ADFF006D8A5D /* Assets.xcassets in Resources */, - 0295B1DC297FF114003B0C65 /* SF-Pro.ttf in Resources */, 0770DE5B28D0B209006D8A5D /* Localizable.strings in Resources */, 0770DE5428D0B00C006D8A5D /* swiftgen.yml in Resources */, ); @@ -837,7 +913,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DBF6F2482B01E20A0098414B /* ConfigTests.swift in Sources */, + E09179FD2B0F204E002AB695 /* ConfigTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -849,8 +925,8 @@ 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 */, 0727877728D23847002E9142 /* DataLayer.swift in Sources */, 0241666B28F5A78B00082765 /* HTMLFormattedText.swift in Sources */, @@ -860,6 +936,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 */, @@ -878,12 +955,14 @@ 028F9F37293A44C700DE65D0 /* Data_ResetPassword.swift in Sources */, 022C64E429AE0191000F532B /* TextWithUrls.swift in Sources */, 0283348028D4DCD200C828FC /* ViewExtension.swift in Sources */, + BA8FA66A2AD59B5500EA029A /* GoogleAuthProvider.swift in Sources */, 02A4833529B8A73400D33F33 /* CorePersistenceProtocol.swift in Sources */, 0233D5732AF13EEE00BAC8BD /* AppReviewButton.swift in Sources */, 02512FF0299533DF0024D438 /* CoreDataHandlerProtocol.swift in Sources */, 0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */, 02A4833A29B8A9AB00D33F33 /* DownloadManager.swift in Sources */, 027BD3AE2909475000392132 /* KeyboardScrollerOptions.swift in Sources */, + BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */, 027BD3BE2909478B00392132 /* UIResponder+CurrentResponder.swift in Sources */, 070019AE28F701B200D5FC78 /* Certificate.swift in Sources */, 076F297F2A1F80C800967E7D /* Pagination.swift in Sources */, @@ -897,9 +976,11 @@ 023A4DD4299E66BD006C0E48 /* OfflineSnackBarView.swift in Sources */, 021D925728DCF12900ACC565 /* AlertView.swift in Sources */, 027BD3A82909474200392132 /* KeyboardAvoidingViewController.swift in Sources */, - 0770DE7B28D0C78C006D8A5D /* Theme.swift in Sources */, 02E93F852AEBAEBC006C4750 /* AppReviewViewModel.swift in Sources */, 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 */, @@ -914,6 +995,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 */, @@ -921,15 +1003,19 @@ 0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */, 02AFCC182AEFDB24000360F0 /* ThirdPartyMailClient.swift in Sources */, 0233D5712AF13EC800BAC8BD /* SelectMailClientView.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 */, + BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */, 02D400612B0678190029D168 /* SKStoreReviewControllerExtension.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 /* FacebookAuthProvider.swift in Sources */, 0770DE2E28D09743006D8A5D /* API.swift in Sources */, 028CE96929858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift in Sources */, 027BD3A92909474200392132 /* KeyboardAvoidingViewControllerRepr.swift in Sources */, @@ -945,6 +1031,7 @@ DBF6F24A2B0380E00098414B /* FeaturesConfig.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 */, @@ -952,6 +1039,7 @@ 025B36752A13B7D5001A640E /* UnitButtonView.swift in Sources */, 028F9F39293A452B00DE65D0 /* ResetPassword.swift in Sources */, 0233D56F2AF13EB200BAC8BD /* StarRatingView.swift in Sources */, + 0604C9AA2B22FACF00AD5DBF /* UIComponentsConfig.swift in Sources */, 027BD3B82909476200392132 /* DismissKeyboardTapViewModifier.swift in Sources */, 024BE3DF29B2615500BCDEE2 /* CGColorExtension.swift in Sources */, 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */, @@ -1980,6 +2068,22 @@ 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; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1988,6 +2092,16 @@ package = 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */; productName = YouTubePlayerKit; }; + BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */ = { + isa = XCSwiftPackageProductDependency; + package = BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignIn; + }; + 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/Colors/TextColor/TextSecondary.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json similarity index 76% rename from Core/Core/Assets.xcassets/Colors/TextColor/TextSecondary.colorset/Contents.json rename to Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json index 096098758..be9d677bb 100644 --- a/Core/Core/Assets.xcassets/Colors/TextColor/TextSecondary.colorset/Contents.json +++ b/Core/Core/Assets.xcassets/Colors/AppleButtonColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xBB", - "green" : "0xA5", - "red" : "0x97" + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x9F", - "green" : "0x88", - "red" : "0x79" + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" } }, "idiom" : "universal" 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/Assets.xcassets/Auth/Contents.json b/Core/Core/Assets.xcassets/Socials/Contents.json similarity index 100% rename from Core/Core/Assets.xcassets/Auth/Contents.json rename to Core/Core/Assets.xcassets/Socials/Contents.json 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 000000000..6ca46033a Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@2x.png differ diff --git a/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@3x.png b/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@3x.png new file mode 100644 index 000000000..87995a5ef Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_apple.imageset/icon_apple@3x.png differ 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 000000000..109a67604 Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@2x.png differ diff --git a/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@3x.png b/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@3x.png new file mode 100644 index 000000000..1623f36fd Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_facebook_white.imageset/icon_facebook_white@3x.png differ 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 000000000..64c2157f9 Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@2x.png differ diff --git a/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@3x.png b/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@3x.png new file mode 100644 index 000000000..56b7b28d3 Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_google_white.imageset/icon_google_white@3x.png differ diff --git a/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/Contents.json b/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/Contents.json new file mode 100644 index 000000000..be9b6142b --- /dev/null +++ b/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_microsoft_white@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_microsoft_white@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@2x.png b/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@2x.png new file mode 100644 index 000000000..bc596b581 Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@2x.png differ diff --git a/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@3x.png b/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@3x.png new file mode 100644 index 000000000..586c06636 Binary files /dev/null and b/Core/Core/Assets.xcassets/Socials/icon_microsoft_white.imageset/icon_microsoft_white@3x.png differ diff --git a/Core/Core/Assets.xcassets/Auth/checkEmail.imageset/Contents.json b/Core/Core/Assets.xcassets/checkEmail.imageset/Contents.json similarity index 100% rename from Core/Core/Assets.xcassets/Auth/checkEmail.imageset/Contents.json rename to Core/Core/Assets.xcassets/checkEmail.imageset/Contents.json diff --git a/Core/Core/Assets.xcassets/Auth/checkEmail.imageset/_1-2.svg b/Core/Core/Assets.xcassets/checkEmail.imageset/_1-2.svg similarity index 100% rename from Core/Core/Assets.xcassets/Auth/checkEmail.imageset/_1-2.svg rename to Core/Core/Assets.xcassets/checkEmail.imageset/_1-2.svg diff --git a/Core/Core/Assets.xcassets/Auth/checkEmail.imageset/_1.svg b/Core/Core/Assets.xcassets/checkEmail.imageset/_1.svg similarity index 100% rename from Core/Core/Assets.xcassets/Auth/checkEmail.imageset/_1.svg rename to Core/Core/Assets.xcassets/checkEmail.imageset/_1.svg diff --git a/Core/Core/Configuration/BaseRouter.swift b/Core/Core/Configuration/BaseRouter.swift index 034855d87..a153aa067 100644 --- a/Core/Core/Configuration/BaseRouter.swift +++ b/Core/Core/Configuration/BaseRouter.swift @@ -22,12 +22,16 @@ public protocol BaseRouter { func removeLastView(controllers: Int) func showMainOrWhatsNewScreen() - + + func showStartupScreen() + func showLoginScreen() - + func showRegisterScreen() func showForgotPasswordScreen() + + func showDiscoveryScreen(searchQuery: String?, fromStartupScreen: Bool) func presentAlert( alertTitle: String, @@ -74,13 +78,17 @@ open class BaseRouterMock: BaseRouter { public func dismiss(animated: Bool) {} public func showMainOrWhatsNewScreen() {} + + public func showStartupScreen() {} public func showLoginScreen() {} - + public func showRegisterScreen() {} public func showForgotPasswordScreen() {} + public func showDiscoveryScreen(searchQuery: String?, fromStartupScreen: Bool) {} + public func backToRoot(animated: Bool) {} public func back(animated: Bool) {} diff --git a/Core/Core/Configuration/CSSInjector.swift b/Core/Core/Configuration/CSSInjector.swift index 57a2c88b0..59beef4cd 100644 --- a/Core/Core/Configuration/CSSInjector.swift +++ b/Core/Core/Configuration/CSSInjector.swift @@ -7,6 +7,7 @@ import Foundation import SwiftUI +import Theme public class CSSInjector { @@ -114,7 +115,7 @@ public class CSSInjector {