Skip to content

Commit

Permalink
feat: Support web-based LMS OAuth.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kelketek committed Aug 21, 2023
1 parent 630f9b7 commit ee46d1a
Show file tree
Hide file tree
Showing 15 changed files with 315 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum LoginMethod: String {
case facebook = "Facebook"
case google = "Google"
case microsoft = "Microsoft"
case oauth2 = "Custom OAuth2"
}

//sourcery: AutoMockable
Expand Down
269 changes: 152 additions & 117 deletions Authorization/Authorization/Presentation/Login/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,137 +16,171 @@ public struct SignInView: View {
@ObservedObject
private var viewModel: SignInViewModel

public init(viewModel: SignInViewModel) {
private var navigationController: UINavigationController

public init(viewModel: SignInViewModel, navigationController: UINavigationController) {
self.viewModel = viewModel
self.navigationController = navigationController
}

public func webLogin() async {
await viewModel.login(viewController: self.navigationController)
}

public var body: some View {
ZStack(alignment: .top) {
VStack {
CoreAssets.authBackground.swiftUIImage
.resizable()
.edgesIgnoringSafeArea(.top)
}.frame(maxWidth: .infinity, maxHeight: 200)

VStack(alignment: .center) {
CoreAssets.appLogo.swiftUIImage
.resizable()
.frame(maxWidth: 189, maxHeight: 54)
.padding(.vertical, 40)
if viewModel.forceWebLogin {
// On first load, we should bring the user right to the web login
// interface rather than showing this view.
//
// If that login fails or the user escapes back, they'll be brought
// back to the view where any error message will be shown.
Task {
await webLogin()
}
}
return ZStack(alignment: .top) {
if viewModel.forceWebLogin {
Text("")
// Is there an idiomatic way of doing an early return here
// rather than using this big indented else clause?
// Using a return statement seems to break whatever magic
// happens here.
} else {
VStack {
CoreAssets.authBackground.swiftUIImage
.resizable()
.edgesIgnoringSafeArea(.top)
}.frame(maxWidth: .infinity, maxHeight: 200)

ScrollView {
VStack {
VStack(alignment: .leading) {
Text(AuthLocalization.SignIn.logInTitle)
.font(Theme.Fonts.displaySmall)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.bottom, 4)
Text(AuthLocalization.SignIn.welcomeBack)
.font(Theme.Fonts.titleSmall)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.bottom, 20)

Text(AuthLocalization.SignIn.email)
.font(Theme.Fonts.labelLarge)
.foregroundColor(Theme.Colors.textPrimary)
TextField(AuthLocalization.SignIn.email, text: $email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.autocapitalization(.none)
.autocorrectionDisabled()
.padding(.all, 14)
.background(
Theme.Shapes.textInputShape
.fill(Theme.Colors.textInputBackground)
)
.overlay(
Theme.Shapes.textInputShape
.stroke(lineWidth: 1)
.fill(Theme.Colors.textInputStroke)
)

Text(AuthLocalization.SignIn.password)
.font(Theme.Fonts.labelLarge)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.top, 18)
SecureField(AuthLocalization.SignIn.password, text: $password)
.padding(.all, 14)
.background(
Theme.Shapes.textInputShape
.fill(Theme.Colors.textInputBackground)
)
.overlay(
Theme.Shapes.textInputShape
.stroke(lineWidth: 1)
.fill(Theme.Colors.textInputStroke)
)

HStack {
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, 10)
if viewModel.isShowProgress {
HStack(alignment: .center) {
ProgressBar(size: 40, lineWidth: 8)
.padding(20)
}.frame(maxWidth: .infinity)
} else {
StyledButton(AuthLocalization.SignIn.logInBtn) {
Task {
await viewModel.login(username: email, password: password)
VStack(alignment: .center) {
CoreAssets.appLogo.swiftUIImage
.resizable()
.frame(maxWidth: 189, maxHeight: 54)
.padding(.vertical, 40)

ScrollView {
VStack {
VStack(alignment: .leading) {
Text(AuthLocalization.SignIn.logInTitle)
.font(Theme.Fonts.displaySmall)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.bottom, 4)
Text(AuthLocalization.SignIn.welcomeBack)
.font(Theme.Fonts.titleSmall)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.bottom, 20)
if viewModel.config.webLogin {
StyledButton(AuthLocalization.SignIn.logInBtn) {
Task {
await self.webLogin()
}
}.frame(maxWidth: .infinity)
.padding(.top, 40)
} else {
Text(AuthLocalization.SignIn.email)
.font(Theme.Fonts.labelLarge)
.foregroundColor(Theme.Colors.textPrimary)
TextField(AuthLocalization.SignIn.email, text: $email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.autocapitalization(.none)
.autocorrectionDisabled()
.padding(.all, 14)
.background(
Theme.Shapes.textInputShape
.fill(Theme.Colors.textInputBackground)
)
.overlay(
Theme.Shapes.textInputShape
.stroke(lineWidth: 1)
.fill(Theme.Colors.textInputStroke)
)

Text(AuthLocalization.SignIn.password)
.font(Theme.Fonts.labelLarge)
.foregroundColor(Theme.Colors.textPrimary)
.padding(.top, 18)
SecureField(AuthLocalization.SignIn.password, text: $password)
.padding(.all, 14)
.background(
Theme.Shapes.textInputShape
.fill(Theme.Colors.textInputBackground)
)
.overlay(
Theme.Shapes.textInputShape
.stroke(lineWidth: 1)
.fill(Theme.Colors.textInputStroke)
)

HStack {
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)
}
}.frame(maxWidth: .infinity)
.padding(.top, 40)
.padding(.top, 10)
}
if viewModel.isShowProgress {
HStack(alignment: .center) {
ProgressBar(size: 40, lineWidth: 8)
.padding(20)
}.frame(maxWidth: .infinity)
} else if !viewModel.config.webLogin {
StyledButton(AuthLocalization.SignIn.logInBtn) {
Task {
await viewModel.login(username: email, password: password)
}
}.frame(maxWidth: .infinity)
.padding(.top, 40)
}
}
Spacer()
}
Spacer()
}
.padding(.horizontal, 24)
.padding(.top, 50)
}.roundedBackground(Theme.Colors.background)
.scrollAvoidKeyboard(dismissKeyboardByTap: true)

}

// MARK: - Alert
if viewModel.showAlert {
VStack {
Text(viewModel.alertMessage ?? "")
.shadowCardStyle(bgColor: Theme.Colors.accentColor,
textColor: .white)
.padding(.top, 80)
Spacer()
.padding(.horizontal, 24)
.padding(.top, 50)
}.roundedBackground(Theme.Colors.background)
.scrollAvoidKeyboard(dismissKeyboardByTap: true)

}
.transition(.move(edge: .top))
.onAppear {
doAfter(Theme.Timeout.snackbarMessageLongTimeout) {
viewModel.alertMessage = nil

// MARK: - Alert
if viewModel.showAlert {
VStack {
Text(viewModel.alertMessage ?? "")
.shadowCardStyle(bgColor: Theme.Colors.accentColor,
textColor: .white)
.padding(.top, 80)
Spacer()

}
}
}

// MARK: - Show error
if viewModel.showError {
VStack {
Spacer()
SnackBarView(message: viewModel.errorMessage)
}.transition(.move(edge: .bottom))
.transition(.move(edge: .top))
.onAppear {
doAfter(Theme.Timeout.snackbarMessageLongTimeout) {
viewModel.errorMessage = nil
viewModel.alertMessage = nil
}
}
}

// MARK: - Show error
if viewModel.showError {
VStack {
Spacer()
SnackBarView(message: viewModel.errorMessage)
}.transition(.move(edge: .bottom))
.onAppear {
doAfter(Theme.Timeout.snackbarMessageLongTimeout) {
viewModel.errorMessage = nil
}
}
}
}
}
.hideNavigationBar()
Expand All @@ -163,15 +197,16 @@ struct SignInView_Previews: PreviewProvider {
interactor: AuthInteractor.mock,
router: AuthorizationRouterMock(),
analytics: AuthorizationAnalyticsMock(),
config: ConfigMock(),
validator: Validator()
)

SignInView(viewModel: vm)
SignInView(viewModel: vm, navigationController: UINavigationController())
.preferredColorScheme(.light)
.previewDisplayName("SignInView Light")
.loadFonts()

SignInView(viewModel: vm)
SignInView(viewModel: vm, navigationController: UINavigationController())
.preferredColorScheme(.dark)
.previewDisplayName("SignInView Dark")
.loadFonts()
Expand Down
Loading

0 comments on commit ee46d1a

Please sign in to comment.