11package cz.muni.fi.rpg.ui.startup
22
33import androidx.activity.compose.rememberLauncherForActivityResult
4- import androidx.compose.material.AlertDialog
4+ import androidx.compose.foundation.Image
5+ import androidx.compose.foundation.layout.Arrangement
6+ import androidx.compose.foundation.layout.Box
7+ import androidx.compose.foundation.layout.Row
8+ import androidx.compose.material.Button
59import androidx.compose.material.Text
6- import androidx.compose.material.TextButton
710import androidx.compose.runtime.Composable
811import androidx.compose.runtime.LaunchedEffect
912import androidx.compose.runtime.getValue
@@ -13,65 +16,96 @@ import androidx.compose.runtime.remember
1316import androidx.compose.runtime.rememberCoroutineScope
1417import androidx.compose.runtime.saveable.rememberSaveable
1518import androidx.compose.runtime.setValue
19+ import androidx.compose.ui.Alignment
1620import androidx.compose.ui.platform.LocalContext
21+ import androidx.compose.ui.unit.dp
1722import com.google.android.gms.auth.api.signin.GoogleSignIn
1823import cz.frantisekmasa.wfrp_master.common.Str
1924import cz.frantisekmasa.wfrp_master.common.auth.AuthenticationManager
2025import cz.frantisekmasa.wfrp_master.common.auth.LocalWebClientId
21- import cz.frantisekmasa.wfrp_master.common.core.shared.SettingsStorage
22- import cz.frantisekmasa.wfrp_master.common.core.shared.edit
26+ import cz.frantisekmasa.wfrp_master.common.core.shared.Resources
27+ import cz.frantisekmasa.wfrp_master.common.core.shared.drawableResource
2328import cz.frantisekmasa.wfrp_master.common.core.ui.flow.collectWithLifecycle
24- import cz.frantisekmasa.wfrp_master.common.settings.AppSettings
29+ import cz.frantisekmasa.wfrp_master.common.core.ui.primitives.VisualOnlyIconDescription
2530import cz.frantisekmasa.wfrp_master.common.shell.SplashScreen
2631import dev.icerock.moko.resources.compose.stringResource
2732import io.github.aakira.napier.Napier
2833import kotlinx.coroutines.Dispatchers
2934import kotlinx.coroutines.launch
3035import kotlinx.coroutines.tasks.await
3136import kotlinx.coroutines.withContext
32- import org.kodein.di.compose.localDI
33- import org.kodein.di.instance
3437
3538@Composable
36- fun StartupScreen (authenticationManager : AuthenticationManager ) {
37- SplashScreen ()
39+ fun StartupScreen (authenticationManager : AuthenticationManager ): Unit = Box {
40+
41+ var signInButtonVisible by rememberSaveable { mutableStateOf(false ) }
3842
3943 val authenticated by authenticationManager.authenticated.collectWithLifecycle()
4044
4145 Napier .d(" Authenticated: $authenticated " )
4246
4347 if (authenticated != false ) {
48+ SplashScreen ()
4449 // We could not determine whether user is logged in yet or there is delay between
4550 // recompositions (user is already authenticated, but startup screen is still visible)
4651 return
4752 }
4853
49- val coroutineScope = rememberCoroutineScope()
54+ val webClientId = LocalWebClientId .current
55+ val context = LocalContext .current
5056
51- var showAnonymousAuthenticationDialog by rememberSaveable { mutableStateOf(false ) }
57+ val startGoogleSignInFlow = rememberGoogleSignInLauncher(
58+ authenticationManager = authenticationManager,
59+ onFailure = { signInButtonVisible = true },
60+ )
5261
53- if (showAnonymousAuthenticationDialog) {
54- val settings: SettingsStorage by localDI().instance()
62+ LaunchedEffect (Unit ) {
63+ withContext(Dispatchers .Default ) {
64+ // If user signed in via Google before, try to sign in him directly
65+ if (authenticationManager.attemptToRestoreExistingGoogleSignIn(context, webClientId)) {
66+ return @withContext
67+ }
68+
69+ withContext(Dispatchers .Main ) { startGoogleSignInFlow() }
70+ }
71+ }
5572
56- AnonymousAuthenticationExplanationDialog (
57- onDismissRequest = {
58- coroutineScope.launch {
59- settings.edit(AppSettings .GOOGLE_SIGN_IN_DISMISSED , true )
60- authenticationManager.authenticateAnonymously()
73+ if (signInButtonVisible) {
74+ SplashScreen {
75+ Button (
76+ onClick = startGoogleSignInFlow
77+ ) {
78+ Row (
79+ verticalAlignment = Alignment .CenterVertically ,
80+ horizontalArrangement = Arrangement .spacedBy(4 .dp)
81+ ) {
82+ Image (
83+ drawableResource(Resources .Drawable .GoogleLogo ),
84+ VisualOnlyIconDescription ,
85+ )
86+ Text (stringResource(Str .authentication_button_sign_in))
6187 }
62- showAnonymousAuthenticationDialog = false
6388 }
64- )
89+ }
90+ } else {
91+ SplashScreen ()
6592 }
93+ }
6694
67- val context = LocalContext .current
95+ @Composable
96+ private fun rememberGoogleSignInLauncher (
97+ authenticationManager : AuthenticationManager ,
98+ onFailure : () -> Unit ,
99+ ): () -> Unit {
68100 val webClientId = LocalWebClientId .current
69101 val contract = remember(authenticationManager, webClientId) { authenticationManager.googleSignInContract(webClientId) }
70- val googleSignInLauncher = key(contract, coroutineScope) {
71- rememberLauncherForActivityResult(contract) { result ->
102+ val coroutineScope = rememberCoroutineScope()
103+
104+ return key(contract, coroutineScope) {
105+ val launcher = rememberLauncherForActivityResult(contract) { result ->
72106 if (result.resultCode == 0 ) {
73107 Napier .d(" Google Sign-In dialog was dismissed" )
74- showAnonymousAuthenticationDialog = true
108+ onFailure()
75109 return @rememberLauncherForActivityResult
76110 }
77111
@@ -82,35 +116,21 @@ fun StartupScreen(authenticationManager: AuthenticationManager) {
82116 .idToken
83117 ?.let { idToken -> authenticationManager.signInWithGoogleToken(idToken) }
84118 } catch (e: Throwable ) {
85- showAnonymousAuthenticationDialog = true
119+ onFailure()
86120 }
87121 }
88122 }
89- }
90123
91- LaunchedEffect (null ) {
92- withContext(Dispatchers .Default ) {
93- // If user signed in via Google before, try to sign in him directly
94- if (authenticationManager.attemptToRestoreExistingGoogleSignIn(context, webClientId)) {
95- return @withContext
124+ return @key remember(launcher, onFailure) {
125+ {
126+ try {
127+ launcher.launch(GoogleSignInCode )
128+ } catch (e: Throwable ) {
129+ onFailure()
130+ }
96131 }
97-
98- withContext(Dispatchers .Main ) { googleSignInLauncher.launch(GoogleSignInCode ) }
99132 }
100133 }
101134}
102135
103- @Composable
104- private fun AnonymousAuthenticationExplanationDialog (onDismissRequest : () -> Unit ) {
105- AlertDialog (
106- onDismissRequest = onDismissRequest,
107- text = { Text (stringResource(Str .authentication_startup_google_sign_in_failed)) },
108- confirmButton = {
109- TextButton (onClick = onDismissRequest) {
110- Text (stringResource(Str .common_ui_button_ok).uppercase())
111- }
112- }
113- )
114- }
115-
116136private const val GoogleSignInCode = 100
0 commit comments