Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions ComposeAuth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
id(libs.plugins.kotlin.multiplatform.get().pluginId)
id("io.github.frankois944.spmForKmp") version "1.0.1"
id(libs.plugins.android.library.get().pluginId)
id(libs.plugins.compose.plugin.get().pluginId)
alias(libs.plugins.compose.compiler)
Expand Down Expand Up @@ -32,6 +33,20 @@ kotlin {
}
jvmToolchain(11)
composeTargets(JvmTarget.JVM_11)
iosTargets()
targets.forEach {
if (it is org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
it.binaries.framework {
baseName = "common"
isStatic = true
}
it.compilations {
val main by getting {
cinterops.create("nativeBridge")
}
}
}
}
sourceSets {
commonMain {
dependencies {
Expand Down Expand Up @@ -62,4 +77,24 @@ tasks.withType<LintModelMetadataTask> {
}
tasks.withType<AndroidLintAnalysisTask> {
dependsOn("generateResourceAccessorsForAndroidUnitTest")
}

swiftPackageConfig {
create("nativeBridge") {
dependency {
linkerOpts =
listOf("-ObjC", "-fObjC")
// Google Sign-In SDK
remotePackageVersion(
url = uri("https://github.com/google/GoogleSignIn-iOS.git"),
products = {
add("GoogleSignIn", exportToKotlin = true)
},
version = "9.0.0",
)
exportedPackageSettings {
includeProduct = listOf("GoogleSignIn")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ package io.github.jan.supabase.compose.auth.composable
import androidx.compose.runtime.Composable
import io.github.jan.supabase.compose.auth.ComposeAuth
import io.github.jan.supabase.compose.auth.IdTokenCallback
import io.github.jan.supabase.compose.auth.defaultLoginBehavior
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import io.github.jan.supabase.auth.providers.Google
import io.github.jan.supabase.logging.d
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import nativeBridge.GoogleSignInController

/**
* Composable function that implements Native Google Auth.
Expand All @@ -15,12 +23,72 @@ import io.github.jan.supabase.compose.auth.defaultLoginBehavior
* @param fallback Fallback function for unsupported platforms
* @return [NativeSignInState]
*/
@OptIn(ExperimentalForeignApi::class)
@Composable
actual fun ComposeAuth.rememberSignInWithGoogle(
onResult: (NativeSignInResult) -> Unit,
onIdToken: IdTokenCallback,
type: GoogleDialogType,
fallback: suspend () -> Unit
): NativeSignInState = defaultLoginBehavior(fallback)
): NativeSignInState {
val state = remember { NativeSignInState(this.serializer) }
val scope = rememberCoroutineScope()

internal actual suspend fun handleGoogleSignOut() = Unit
val googleSignInController = remember {
GoogleSignInController()
}

LaunchedEffect(key1 = state.status) {
if (state.status is NativeSignInStatus.Started) {
ComposeAuth.logger.d { "Start Oauth flow"}
try {
if (config.googleLoginConfig != null) {
ComposeAuth.logger.d { "Config is available"}
val currentNonce = (state.status as? NativeSignInStatus.Started)?.nonce
val currentExtraData = (state.status as? NativeSignInStatus.Started)?.extraData
// Call the signIn method on the Swift controller, passing a lambda for completion
googleSignInController.signInCompletion { idToken, errorMessage, isCancelled ->
scope.launch {
if (isCancelled) {
ComposeAuth.logger.d { "Flow is canceled"}
onResult.invoke(NativeSignInResult.ClosedByUser)
} else if (idToken != null) {
ComposeAuth.logger.d { "Id token available"}

onIdToken.invoke(
composeAuth = this@rememberSignInWithGoogle,
result = IdTokenCallback.Result(
idToken = idToken,
provider = Google,
nonce = currentNonce,
extraData = currentExtraData
)
)
onResult.invoke(NativeSignInResult.Success)
} else if (errorMessage != null) {
ComposeAuth.logger.d { "Error happens"}
onResult.invoke(NativeSignInResult.Error(errorMessage))
} else {
// Fallback for unexpected cases
onResult.invoke(NativeSignInResult.Error("Unknown Google sign-in error"))
}
}
}
} else {
fallback.invoke()
}
} catch (e: Exception) {
coroutineContext.ensureActive()
onResult.invoke(NativeSignInResult.Error(e.message ?: "error"))
} finally {
state.reset()
}
}
}
return state
}

@OptIn(ExperimentalForeignApi::class)
internal actual suspend fun handleGoogleSignOut() {
GoogleSignInController.signOutGoogle()
}
40 changes: 40 additions & 0 deletions ComposeAuth/swift/nativeBridge/GoogleSignInController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

import Foundation
import GoogleSignIn
import UIKit // Needed for UIViewController

// Define a public typealias for the completion handler closure
public typealias GoogleSignInCompletionHandler = (String?, String?, Bool) -> Void

@objcMembers public class GoogleSignInController: NSObject {

public override init() {
super.init()
}

public func signIn(completion: @escaping GoogleSignInCompletionHandler) {
guard let presentingViewController = UIApplication.shared.keyWindow?.rootViewController else {
completion(nil, "No root view controller found", false)
return
}
GoogleSignIn.GIDSignIn.sharedInstance.signIn(withPresenting: presentingViewController) { result, error in
if let error = error {
if let nsError = error as NSError?, nsError.code == -5 {
completion(nil, nil, true)
} else {
completion(nil, error.localizedDescription, false)
}
} else if let idToken = result?.user.idToken?.tokenString {
completion(idToken, nil, false)
} else {
completion(nil, "Unknown sign-in error or no ID token", false)
}
}


}

@objc public static func signOutGoogle() {
GoogleSignIn.GIDSignIn.sharedInstance.signOut()
}
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.native.ignoreDisabledTargets=true
org.gradle.parallel=true
kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning
kotlin.mpp.enableCInteropCommonization=true

org.jetbrains.compose.experimental.uikit.enabled=true
org.jetbrains.compose.experimental.jscanvas.enabled=true
Expand Down
Loading