Last active
November 12, 2022 20:00
-
-
Save bmc08gt/ff9610a20dcd43c6e6dd5cbd1797e73a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@OptIn(ExperimentalComposeApi::class) | |
@Composable | |
fun ActivityResultRegistry.googleSignIn(userPrefs: UserPreferences): GoogleSignInState { | |
val context = ContextAmbient.current | |
val options: GoogleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).apply { | |
requestIdToken(context.getString(R.string.google_client_id)) | |
requestEmail() | |
}.build() | |
val signInState: MutableState<UserState<*>> = remember { | |
val currentUser = userPrefs.user.value | |
mutableStateOf( | |
if (currentUser != null) { | |
UserState.LoggedIn(currentUser) | |
} else { | |
UserState.LoggedOut | |
} | |
) | |
} | |
val launcher = activityResultLauncher(SignInActivityContract()) { | |
GlobalScope.launch { | |
val result = runCatching { firebaseAuthWithGoogle(it) } | |
signInState.value = if (result.isSuccess) { | |
val user = result.getOrNull() | |
if (user != null) { | |
UserState.LoggedIn(user) | |
} else { | |
UserState.LoginError(Throwable("User is null")) | |
} | |
} else { | |
Log.d("sign-in", "googleSignIn: ${result.exceptionOrNull()}") | |
UserState.LoginError(result.exceptionOrNull()) | |
} | |
} | |
} | |
return remember(launcher) { | |
GoogleSignInState( | |
userPrefs, | |
GoogleSignIn.getClient(context, options), | |
signInState, | |
launcher | |
) | |
} | |
} | |
private suspend fun firebaseAuthWithGoogle(account: GoogleSignInAccount?) = suspendCoroutine<FirebaseUser> { cont -> | |
val credential = GoogleAuthProvider.getCredential(account?.idToken, null) | |
FirebaseAuth.getInstance().signInWithCredential(credential) | |
.addOnCompleteListener { task -> | |
if (task.isSuccessful) { | |
// Sign in success, update UI with the signed-in user's information | |
val user = FirebaseAuth.getInstance().currentUser | |
if (user == null) { | |
cont.resumeWithException(Throwable("User was null")) | |
return@addOnCompleteListener | |
} | |
Log.d("sign-in", "firebaseAuthWithGoogle: ${user.metadata}") | |
cont.resume(user) | |
} else { | |
// If sign in fails, display a message to the user. | |
Log.e("sign-in", "signInWithCredential:failure", task.exception) | |
cont.resumeWithException(task.exception ?: Throwable("Failure")) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun AppContent( | |
appComponent: AppComponent, | |
authComponent: AuthComponent, | |
navigationViewModel: NavigationViewModel | |
) { | |
val signInState = ContextAmbient.current.findActivity()?.activityResultRegistry?.googleSignIn(authComponent.userPreferences) | |
Surface(color = MaterialTheme.colors.background) { | |
signInState?.let { state -> | |
when { | |
state.isLoggedIn() -> navigationViewModel.navigateTo(Screen.Home) | |
else -> navigationViewModel.navigateTo(Screen.Login) | |
} | |
} | |
when (val screen = navigationViewModel.currentScreen) { | |
Screen.Home -> HomeContent(signInState = signInState) | |
Screen.Login -> LoginScreen(signInState) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed class UserState<T> { | |
object LoggedOut: UserState<Nothing>() | |
object LoginRequested: UserState<Nothing>() | |
object LoggingIn : UserState<Nothing>() | |
data class LoggedIn(val user: FirebaseUser): UserState<FirebaseUser>() | |
data class LoginError(val exception: Throwable?): UserState<Throwable?>() | |
} | |
class GoogleSignInState( | |
private val userPreferences: UserPreferences, | |
private val googleSignInClient: GoogleSignInClient, | |
private var userState: MutableState<UserState<*>>, | |
private val launcher: ActivityResultLauncher<GoogleSignInClient> | |
) { | |
fun launchSignInRequest() { | |
userState.value = UserState.LoggingIn | |
launcher.launch(googleSignInClient) | |
} | |
fun login() { | |
userState.value = UserState.LoginRequested | |
} | |
fun logOut() { | |
userPreferences.logOut() | |
userState.value = UserState.LoggedOut | |
} | |
@Composable | |
fun isLoggedIn() = userState.value is UserState.LoggedIn | |
@Composable | |
fun isLoggingIn() = userState.value is UserState.LoggingIn | |
@Composable | |
fun isLoggedOut() = userState.value is UserState.LoggedOut | |
@Composable | |
fun wasLoginRequested() = userState.value is UserState.LoginRequested | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun LoginScreen(signInState: GoogleSignInState?) { | |
signInState?.let { state -> | |
if (state.wasLoginRequested()) { | |
signInState.launchSignInRequest() | |
} | |
if (state.isLoggedOut()) { | |
LoginSplash(onLogin = { state.login() }) | |
} else { | |
LoginInProgress() | |
} | |
} | |
} | |
@Composable | |
fun LoginRootContent(children: @Composable ConstraintLayoutScope.() -> Unit) { | |
Surface( | |
color = MaterialTheme.colors.secondary, | |
modifier = Modifier.fillMaxSize() | |
) { | |
ConstraintLayout { | |
val (logo, title, description) = createRefs() | |
Image( | |
modifier = Modifier.constrainAs(logo) { | |
start.linkTo(parent.start) | |
end.linkTo(parent.end) | |
top.linkTo(parent.top) | |
bottom.linkTo(title.top) | |
} + Modifier.padding(start = 64.dp, end = 64.dp, top = 64.dp), | |
contentScale = ContentScale.Inside, | |
asset = imageResource(R.drawable.waffle) | |
) | |
Text( | |
modifier = Modifier.constrainAs(title) { | |
start.linkTo(parent.start) | |
end.linkTo(parent.end) | |
top.linkTo(parent.top) | |
bottom.linkTo(parent.bottom) | |
}, | |
text = AnnotatedString("Waffle"), | |
style = ThemeTypography.h5 | |
) | |
children(this) | |
} | |
} | |
} | |
@Composable | |
fun LoginSplash(onLogin: () -> Unit) { | |
LoginRootContent { | |
val button = createRef() | |
Button( | |
modifier = Modifier.constrainAs(button) { | |
start.linkTo(parent.start) | |
end.linkTo(parent.end) | |
bottom.linkTo(parent.bottom, 32.dp) | |
}, | |
backgroundColor = MaterialTheme.colors.surface, | |
contentColor = MaterialTheme.colors.onSurface, | |
onClick = { onLogin() } | |
) { | |
Image(asset = vectorResource(R.drawable.ic_google)) | |
Text(text = "Sign in with Google") | |
} | |
} | |
} | |
@Composable | |
fun LoginInProgress() { | |
LoginRootContent { | |
val progress = createRef() | |
CircularProgressIndicator(modifier = Modifier.constrainAs(progress) { | |
start.linkTo(parent.start) | |
end.linkTo(parent.end) | |
bottom.linkTo(parent.bottom, 32.dp) | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SignInActivityContract : ActivityResultContract<GoogleSignInClient, GoogleSignInAccount?>(), | |
CoroutineScope by CoroutineScope(Dispatchers.Main) { | |
val auth = FirebaseAuth.getInstance() | |
override fun createIntent(context: Context, input: GoogleSignInClient): Intent { | |
return input.signInIntent | |
} | |
override fun parseResult(resultCode: Int, intent: Intent?): GoogleSignInAccount? { | |
val task = GoogleSignIn.getSignedInAccountFromIntent(intent) | |
try { | |
val account = task.getResult(ApiException::class.java) ?: return null | |
return account | |
} catch (e: ApiException) { | |
Log.w("sign-in", "Google sign in failed", e) | |
return null | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment