Skip to content

Commit

Permalink
Merge pull request #3 from carissafarry/feat/firebase-integration
Browse files Browse the repository at this point in the history
feat: add login via email and google account
  • Loading branch information
carissafarry committed Jan 7, 2024
2 parents 85193eb + aadaa17 commit b9abee0
Show file tree
Hide file tree
Showing 16 changed files with 500 additions and 12 deletions.
4 changes: 4 additions & 0 deletions GoogleService-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http:https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>447234467571-4ek3ib1q7hiq3cb97dkqdnhhh3b7rk43.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.447234467571-4ek3ib1q7hiq3cb97dkqdnhhh3b7rk43</string>
<key>API_KEY</key>
<string>AIzaSyAjPNYmjB7efyIJ8UjSz4VEfZA89EWVRr4</string>
<key>GCM_SENDER_ID</key>
Expand Down
133 changes: 127 additions & 6 deletions StudyFora.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"version" : "1.2022062300.0"
}
},
{
"identity" : "appauth-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/openid/AppAuth-iOS.git",
"state" : {
"revision" : "71cde449f13d453227e687458144bde372d30fc7",
"version" : "1.6.2"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -36,6 +45,15 @@
"version" : "9.2.5"
}
},
{
"identity" : "googlesignin-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleSignIn-iOS",
"state" : {
"revision" : "7932d33686c1dc4d7df7a919aae47361d1cdfda4",
"version" : "7.0.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -63,6 +81,15 @@
"version" : "3.1.1"
}
},
{
"identity" : "gtmappauth",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GTMAppAuth.git",
"state" : {
"revision" : "cee3c709307912d040bd1e06ca919875a92339c6",
"version" : "2.0.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
Expand Down
18 changes: 12 additions & 6 deletions StudyFora/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@

import Foundation
import Firebase
import GoogleSignIn

class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()

return true
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}

func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance.handle(url)
}
}
5 changes: 5 additions & 0 deletions StudyFora/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ struct ContentView: View {
router.navigate(to: .register)
}
.padding(.top, 12)

Button("**Login Page**") {
router.navigate(to: .login)
}
.padding(.top, 12)
}
.padding()
}
Expand Down
12 changes: 12 additions & 0 deletions StudyFora/Core/Enum/ErrorMessageEnum.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// ErrorMessageEnum.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 07/01/24.
//

import Foundation

enum ErrorMessageEnum: Swift.Error {
case errorMessage(message:String)
}
3 changes: 3 additions & 0 deletions StudyFora/Core/Router/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ struct Routes: View {
switch route {
case .register:
RegisterView()
case .login:
LoginView()
case .profile:
ProfileView()
}
Expand All @@ -24,6 +26,7 @@ final class Router: ObservableObject {

public enum Destination: Codable, Hashable {
case register
case login
case profile
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// LoginRepositoryImpl.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 06/01/24.
//

import Foundation
import FirebaseAuth
import GoogleSignIn

struct LoginRepositoryImpl: LoginRepository {
func login(withEmail data: RegisterRequestModel, completion: @escaping (Result<AuthDataResult, Error>) -> Void) {
Auth.auth().signIn(withEmail: data.email, password: data.password) { authResult, error in
if let error = error {
completion(.failure(error))
} else if let authResult = authResult {
completion(.success(authResult))
} else {
fatalError("Unexpected nil value in authResult")
}
}
}

func login(withCredential credential: AuthCredential, completion: @escaping (Result<AuthDataResult, Error>) -> Void) {
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
completion(.failure(error))
} else if let authResult = authResult {
completion(.success(authResult))
} else {
fatalError("Unexpected nil value in authResult")
}
}
}

func signIn(config: GIDConfiguration, presentedUI: UIViewController, completion: @escaping (Result<AuthCredential, Error>) -> Void) {

GIDSignIn.sharedInstance.signIn(withPresenting: presentedUI) { result, error in
guard error == nil else {
fatalError(error!.localizedDescription)
}

guard let user = result?.user,
let idToken = user.idToken?.tokenString
else {
fatalError("Error loading user token")
}

let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: user.accessToken.tokenString)

completion(.success(credential))
}
}
}
15 changes: 15 additions & 0 deletions StudyFora/Features/Login/Domain/Repository/LoginRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// LoginRepository.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 06/01/24.
//

import FirebaseAuth
import GoogleSignIn

protocol LoginRepository{
func login(withEmail data: RegisterRequestModel, completion: @escaping (Result<AuthDataResult, Error>) -> Void)
func login(withCredential credential: AuthCredential, completion: @escaping (Result<AuthDataResult, Error>) -> Void)
func signIn(config: GIDConfiguration, presentedUI: UIViewController, completion: @escaping (Result<AuthCredential, Error>) -> Void)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// CredentialLoginUseCase.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 07/01/24.
//

import Foundation
import Combine
import FirebaseAuth

struct CredentialLoginUseCase {
var repository: LoginRepository

func execute(credential: AuthCredential) async throws -> AuthDataResult? {
return try await withCheckedThrowingContinuation { continuation in
repository.login(withCredential: credential) { result in
do {
let authResult = try result.get()
print("User logined with credential successfully!")
continuation.resume(returning: authResult)
} catch {
print("Error login user with credential: \(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
}
29 changes: 29 additions & 0 deletions StudyFora/Features/Login/Domain/UseCase/EmailLoginUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// EmailLoginUseCase.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 06/01/24.
//

import Foundation
import Combine
import FirebaseAuth

struct EmailLoginUseCase {
var repository: LoginRepository

func execute(data: RegisterRequestModel) async throws -> AuthDataResult? {
return try await withCheckedThrowingContinuation { continuation in
repository.login(withEmail: data) { result in
do {
let authResult = try result.get()
print("User logined with email successfully!")
continuation.resume(returning: authResult)
} catch {
print("Error login user with email: \(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
}
43 changes: 43 additions & 0 deletions StudyFora/Features/Login/Domain/UseCase/GoogleLoginUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// GoogleLoginUseCase.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 07/01/24.
//

import Foundation
import FirebaseAuth
import GoogleSignIn
import FirebaseCore

struct GoogleLoginUseCase {
var repository: LoginRepository

func execute() async throws -> AuthCredential? {
guard
let clientID = FirebaseApp.app()?.options.clientID,
let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = await windowScene.windows.first?.rootViewController
// let rootViewController = await UIApplication.shared.windows.first?.rootViewController
else {
throw ErrorMessageEnum.errorMessage(message: "No view controller available")
}

// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = config

return try await withCheckedThrowingContinuation { continuation in
repository.signIn(config: config, presentedUI: rootViewController) { credential in
do {
let credential = try credential.get()
print("User logined with Google successfully!")
continuation.resume(returning: credential)
} catch {
print("Error login user with Google: \(error.localizedDescription)")
continuation.resume(throwing: error)
}
}
}
}
}
50 changes: 50 additions & 0 deletions StudyFora/Features/Login/Presentation/View/LoginView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// LoginView.swift
// StudyFora
//
// Created by Carissa Farry Hilmi Az Zahra on 06/01/24.
//

import SwiftUI
import GoogleSignInSwift

struct LoginView: View {
@EnvironmentObject var router: Router

@StateObject var loginViewModel = LoginViewModel(loginRepository: LoginRepositoryImpl())

@State var email = ""
@State var password = ""

var body: some View {
VStack {
Text("Login")
TextField("Email", text: $email)
SecureField("Password", text: $password)
Button(action: {
Task {
await loginViewModel.emailLogin(data: RegisterRequestModel(email: email, password: password))
print(email)
}
}) {
Text("Log in")
}

GoogleSignInButton(viewModel: GoogleSignInButtonViewModel(scheme: .light, style: .wide, state: .normal), action: {
Task {
await loginViewModel.googleLogin()
print(email)
}
})

Button("Back") {
router.navigateBack()
}
}
.padding()
}
}

#Preview {
LoginView()
}
Loading

0 comments on commit b9abee0

Please sign in to comment.