diff --git a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndCreateEvent.kt b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndCreateEvent.kt index dce768a2b..188ecb521 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndCreateEvent.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndCreateEvent.kt @@ -3,6 +3,7 @@ package com.android.streetworkapp.end2end import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextContains import androidx.compose.ui.test.click @@ -38,7 +39,9 @@ import com.android.streetworkapp.model.user.UserRepository import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.model.workout.WorkoutViewModel import com.android.streetworkapp.ui.navigation.Screen +import com.android.streetworkapp.utils.GoogleAuthService import com.google.firebase.Timestamp +import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.FirebaseFirestore import okhttp3.OkHttpClient import org.junit.Before @@ -46,6 +49,7 @@ import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.RETURNS_DEFAULTS import org.mockito.Mockito.mock @RunWith(AndroidJUnit4::class) @@ -139,6 +143,10 @@ class End2EndCreateEvent { mock(TextModerationViewModel::class.java), mock(ImageViewModel::class.java), mock(PreferencesViewModel::class.java), + GoogleAuthService( + "abc", + mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), + context = LocalContext.current), true) } } diff --git a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndGeneral.kt b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndGeneral.kt index a7d4babaf..8ab84deb6 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndGeneral.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndGeneral.kt @@ -1,5 +1,6 @@ package com.android.streetworkapp.end2end +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals @@ -32,11 +33,14 @@ import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.model.workout.WorkoutRepository import com.android.streetworkapp.model.workout.WorkoutViewModel import com.android.streetworkapp.ui.navigation.Route +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.FirebaseAuth import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.InjectMocks import org.mockito.Mock +import org.mockito.Mockito.RETURNS_DEFAULTS import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.wheneverBlocking @@ -124,7 +128,9 @@ class End2EndGeneral { WorkoutViewModel(mock(WorkoutRepository::class.java)), TextModerationViewModel(mock(TextModerationRepository::class.java)), ImageViewModel(mock(ImageRepository::class.java)), - PreferencesViewModel(mock(PreferencesRepository::class.java))) + PreferencesViewModel(mock(PreferencesRepository::class.java)), + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current)) } composeTestRule.waitForIdle() diff --git a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndParks.kt b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndParks.kt index 84ccbf395..b4d1c89cd 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndParks.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndParks.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.click @@ -42,6 +43,8 @@ import com.android.streetworkapp.model.progression.ProgressionViewModel import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.model.workout.WorkoutViewModel import com.android.streetworkapp.ui.navigation.Route +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.FirebaseAuth import io.mockk.every import io.mockk.mockk import org.junit.After @@ -51,6 +54,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.RETURNS_DEFAULTS import org.mockito.Mockito.mock @RunWith(AndroidJUnit4::class) @@ -118,7 +122,9 @@ class End2EndParks { WorkoutViewModel(mockk()), TextModerationViewModel(mockk()), ImageViewModel(mockk()), - PreferencesViewModel(mock(PreferencesRepository::class.java))) + PreferencesViewModel(mock(PreferencesRepository::class.java)), + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current)) // setup so as we're already on the MAP route } diff --git a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndWorkoutTraining.kt b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndWorkoutTraining.kt index 2ec07e318..8779f00ef 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndWorkoutTraining.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/end2end/End2EndWorkoutTraining.kt @@ -1,5 +1,6 @@ package com.android.streetworkapp.end2end +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextEquals @@ -36,6 +37,8 @@ import com.android.streetworkapp.model.workout.WorkoutSession import com.android.streetworkapp.model.workout.WorkoutViewModel import com.android.streetworkapp.ui.navigation.NavigationActions import com.android.streetworkapp.ui.navigation.Route +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.FirebaseAuth import org.junit.Before import org.junit.Ignore import org.junit.Rule @@ -122,7 +125,8 @@ class End2EndWorkoutTraining { workoutViewModel, TextModerationViewModel(mock(TextModerationRepository::class.java)), mock(ImageViewModel::class.java), - PreferencesViewModel(mock(PreferencesRepository::class.java))) + PreferencesViewModel(mock(PreferencesRepository::class.java)), + GoogleAuthService("abc", mock(FirebaseAuth::class.java), LocalContext.current)) } NavigationActions(testNavController).apply { composeTestRule.waitForIdle() diff --git a/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/BottomNavigationTest.kt b/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/BottomNavigationTest.kt index 9bc2fcebc..aa5c0999d 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/BottomNavigationTest.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/BottomNavigationTest.kt @@ -5,6 +5,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed @@ -34,6 +35,8 @@ import com.android.streetworkapp.model.user.UserRepository import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.model.workout.WorkoutRepository import com.android.streetworkapp.model.workout.WorkoutViewModel +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.FirebaseAuth import org.junit.Assert import org.junit.Rule import org.junit.Test @@ -128,6 +131,8 @@ class BottomNavigationTest { TextModerationViewModel(mock(TextModerationRepository::class.java, RETURNS_DEFAULTS)), ImageViewModel(mock(ImageRepository::class.java)), PreferencesViewModel(mock(PreferencesRepository::class.java)), + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current), true) } diff --git a/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/TopAppBarTest.kt b/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/TopAppBarTest.kt index f3520d96e..c9c9f2f33 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/TopAppBarTest.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/ui/navigation/TopAppBarTest.kt @@ -1,6 +1,7 @@ package com.android.streetworkapp.ui.navigation import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertTextEquals @@ -25,6 +26,8 @@ import com.android.streetworkapp.model.user.UserRepository import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.model.workout.WorkoutRepository import com.android.streetworkapp.model.workout.WorkoutViewModel +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.FirebaseAuth import io.mockk.mockk import org.junit.Rule import org.junit.Test @@ -65,6 +68,8 @@ class TopAppBarTest { TextModerationViewModel(mock(TextModerationRepository::class.java)), ImageViewModel(mock(ImageRepository::class.java)), PreferencesViewModel(mock(PreferencesRepository::class.java)), + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current), true) } diff --git a/app/src/androidTest/java/com/android/streetworkapp/ui/profile/SettingsTest.kt b/app/src/androidTest/java/com/android/streetworkapp/ui/profile/SettingsTest.kt index 1a34404b4..84790c6f1 100644 --- a/app/src/androidTest/java/com/android/streetworkapp/ui/profile/SettingsTest.kt +++ b/app/src/androidTest/java/com/android/streetworkapp/ui/profile/SettingsTest.kt @@ -12,23 +12,31 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.sample.R +import com.android.streetworkapp.model.preferences.PreferencesRepository +import com.android.streetworkapp.model.preferences.PreferencesViewModel import com.android.streetworkapp.model.user.User import com.android.streetworkapp.model.user.UserRepository import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.ui.navigation.NavigationActions +import com.android.streetworkapp.ui.navigation.Route +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.FirebaseAuth import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito +import org.mockito.Mockito.RETURNS_DEFAULTS +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class SettingsTest { @get:Rule val composeTestRule = createComposeRule() - private val navigationActions = Mockito.mock(NavigationActions::class.java) - private val userRepository = Mockito.mock(UserRepository::class.java) + private val navigationActions = mock(NavigationActions::class.java) + private val userRepository = mock(UserRepository::class.java) private val userViewModel = UserViewModel(userRepository) + private val preferencesViewModel = PreferencesViewModel(mock(PreferencesRepository::class.java)) @Test fun isSettingsContentDisplayedForNullUser() { @@ -37,7 +45,11 @@ class SettingsTest { var context: Context? = null composeTestRule.setContent { - SettingsContent(navigationActions, userViewModel, showSettingDialog) + val authService = + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current) + SettingsContent( + navigationActions, userViewModel, preferencesViewModel, authService, showSettingDialog) context = LocalContext.current } @@ -70,7 +82,11 @@ class SettingsTest { userViewModel.setCurrentUser(alice) composeTestRule.setContent { - SettingsContent(navigationActions, userViewModel, showSettingDialog) + val authService = + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current) + SettingsContent( + navigationActions, userViewModel, preferencesViewModel, authService, showSettingDialog) context = LocalContext.current } @@ -89,4 +105,28 @@ class SettingsTest { composeTestRule.onNodeWithTag("deleteAccountDialog").assertIsDisplayed() } + + @Test + fun testLogoutButtonNavigatesToAuthScreen() { + val showSettingDialog = mutableStateOf(false) + var context: Context? = null + val alice = User("uid-alice", "Alice", "alice@gmail.com", 42, emptyList(), "") + userViewModel.setCurrentUser(alice) + + composeTestRule.setContent { + val authService = + GoogleAuthService( + "abc", mock(FirebaseAuth::class.java, RETURNS_DEFAULTS), LocalContext.current) + SettingsContent( + navigationActions, userViewModel, preferencesViewModel, authService, showSettingDialog) + context = LocalContext.current + } + + // Click the logout button + composeTestRule.onNodeWithTag("LogOutButton").performClick() + composeTestRule.waitForIdle() + + // Verify that the navigation action to the AUTH screen was called + verify(navigationActions).navigateTo(Route.AUTH) + } } diff --git a/app/src/main/java/com/android/streetworkapp/MainActivity.kt b/app/src/main/java/com/android/streetworkapp/MainActivity.kt index 2101ba278..1aa811068 100644 --- a/app/src/main/java/com/android/streetworkapp/MainActivity.kt +++ b/app/src/main/java/com/android/streetworkapp/MainActivity.kt @@ -20,12 +20,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import androidx.navigation.navigation +import com.android.sample.R import com.android.streetworkapp.device.network.isInternetAvailable import com.android.streetworkapp.model.event.EventRepositoryFirestore import com.android.streetworkapp.model.event.EventViewModel @@ -80,8 +82,10 @@ import com.android.streetworkapp.ui.tutorial.TutorialEvent import com.android.streetworkapp.ui.utils.CustomDialog import com.android.streetworkapp.ui.utils.DialogType import com.android.streetworkapp.ui.utils.trainComposable +import com.android.streetworkapp.utils.GoogleAuthService +import com.google.firebase.auth.ktx.auth import com.google.firebase.firestore.FirebaseFirestore -import kotlinx.serialization.json.JsonNull.content +import com.google.firebase.ktx.Firebase import okhttp3.OkHttpClient class MainActivity : ComponentActivity() { @@ -146,6 +150,10 @@ fun StreetWorkAppMain( val imageRepository = ImageRepositoryFirestore(firestoreDB, parkRepository, userRepository) val imageViewModel = ImageViewModel(imageRepository) + // Instantiate Google Auth Service + val token = stringResource(R.string.default_web_client_id) + val authService = GoogleAuthService(token, Firebase.auth, LocalContext.current) + // Get the preferences cached parameters val loginState by preferencesViewModel.loginState.collectAsState() val uid by preferencesViewModel.uid.collectAsState() @@ -199,6 +207,7 @@ fun StreetWorkAppMain( textModerationViewModel, imageViewModel, preferencesViewModel, + authService, startDestination = resolvedStartDestination!!) } } @@ -218,6 +227,7 @@ fun StreetWorkApp( textModerationViewModel: TextModerationViewModel, imageViewModel: ImageViewModel, preferencesViewModel: PreferencesViewModel, + authService: GoogleAuthService, navTestInvokationOnEachRecompose: Boolean = false, e2eEventTesting: Boolean = false, startDestination: String = Route.AUTH @@ -305,7 +315,7 @@ fun StreetWorkApp( route = Route.AUTH, ) { composable(Screen.AUTH) { - SignInScreen(navigationActions, userViewModel, preferencesViewModel) + SignInScreen(navigationActions, userViewModel, preferencesViewModel, authService) } } navigation(startDestination = Screen.PROGRESSION, route = Route.PROGRESSION) { @@ -389,7 +399,12 @@ fun StreetWorkApp( tag = "Settings", title = "Settings", Content = { - SettingsContent(navigationActions, userViewModel, showSettingsDialog) + SettingsContent( + navigationActions, + userViewModel, + preferencesViewModel, + authService, + showSettingsDialog) }, ) } diff --git a/app/src/main/java/com/android/streetworkapp/model/user/UserViewModel.kt b/app/src/main/java/com/android/streetworkapp/model/user/UserViewModel.kt index b79e5b4d4..6e7a1aa50 100644 --- a/app/src/main/java/com/android/streetworkapp/model/user/UserViewModel.kt +++ b/app/src/main/java/com/android/streetworkapp/model/user/UserViewModel.kt @@ -27,6 +27,15 @@ open class UserViewModel(private val repository: UserRepository) : ViewModel() { val userList: StateFlow> get() = _userList + /** + * Sets the user to the provided User object. + * + * @param user The User object to set as the user. + */ + fun setUser(user: User?) { + _user.value = user + } + /** * Sets the current user to the provided User object. * diff --git a/app/src/main/java/com/android/streetworkapp/ui/authentication/GoogleAuthButton.kt b/app/src/main/java/com/android/streetworkapp/ui/authentication/GoogleAuthButton.kt index 40fb9d270..257430a95 100644 --- a/app/src/main/java/com/android/streetworkapp/ui/authentication/GoogleAuthButton.kt +++ b/app/src/main/java/com/android/streetworkapp/ui/authentication/GoogleAuthButton.kt @@ -33,14 +33,13 @@ import com.android.streetworkapp.utils.GoogleAuthService @Composable fun GoogleAuthButton( authService: GoogleAuthService, - context: android.content.Context, launcher: ManagedActivityResultLauncher ) { Button( onClick = { Log.d("SignInScreen", "Start sign-in") - authService.launchSignIn(context, launcher) + authService.launchSignIn(launcher) }, modifier = Modifier.width(250.dp) diff --git a/app/src/main/java/com/android/streetworkapp/ui/authentication/SignIn.kt b/app/src/main/java/com/android/streetworkapp/ui/authentication/SignIn.kt index 5f5005b49..111e4aba2 100644 --- a/app/src/main/java/com/android/streetworkapp/ui/authentication/SignIn.kt +++ b/app/src/main/java/com/android/streetworkapp/ui/authentication/SignIn.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -53,7 +52,8 @@ import com.google.firebase.ktx.Firebase fun SignInScreen( navigationActions: NavigationActions, userViewModel: UserViewModel, - preferencesViewModel: PreferencesViewModel + preferencesViewModel: PreferencesViewModel, + authService: GoogleAuthService ) { val user by userViewModel.user.collectAsState() @@ -61,12 +61,8 @@ fun SignInScreen( // This part of the code handles google sign-in : var firebaseUser by remember { mutableStateOf(Firebase.auth.currentUser) } - val token = stringResource(R.string.default_web_client_id) val context = LocalContext.current - // Create an instance of GoogleAuthService (helper class for authentication) : - val authService = remember { GoogleAuthService(token, Firebase.auth) } - // Instantiate the launcher for the sign-in process : val launcher = authService.rememberFirebaseAuthLauncher( @@ -121,7 +117,7 @@ fun SignInScreen( .height(96.dp) .testTag("loginScreenGoogleAuthButtonContainer"), contentAlignment = Alignment.Center) { - GoogleAuthButton(authService, context, launcher) + GoogleAuthButton(authService, launcher) } } } diff --git a/app/src/main/java/com/android/streetworkapp/ui/profile/Settings.kt b/app/src/main/java/com/android/streetworkapp/ui/profile/Settings.kt index f7ee3f605..77c669f77 100644 --- a/app/src/main/java/com/android/streetworkapp/ui/profile/Settings.kt +++ b/app/src/main/java/com/android/streetworkapp/ui/profile/Settings.kt @@ -17,16 +17,21 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import com.android.sample.R +import com.android.streetworkapp.model.preferences.PreferencesViewModel import com.android.streetworkapp.model.user.UserViewModel import com.android.streetworkapp.ui.navigation.NavigationActions +import com.android.streetworkapp.ui.navigation.Route import com.android.streetworkapp.ui.theme.ColorPalette import com.android.streetworkapp.ui.utils.CustomDialog import com.android.streetworkapp.ui.utils.DialogType +import com.android.streetworkapp.utils.GoogleAuthService @Composable fun SettingsContent( navigationActions: NavigationActions, userViewModel: UserViewModel, + preferencesViewModel: PreferencesViewModel, + authService: GoogleAuthService, showParentDialog: MutableState ) { @@ -52,11 +57,15 @@ fun SettingsContent( Button( onClick = { showParentDialog.value = false - Toast.makeText(context, "Not yet implemented", Toast.LENGTH_SHORT).show() + logout(authService, userViewModel, preferencesViewModel) + Toast.makeText( + context, context.getString(R.string.LogoutSuccess), Toast.LENGTH_SHORT) + .show() + navigationActions.navigateTo(Route.AUTH) }, colors = ColorPalette.BUTTON_COLOR, modifier = Modifier.testTag("LogOutButton")) { - Text("Log-out") + Text(context.getString(R.string.LogoutTitle)) } Button( @@ -87,3 +96,30 @@ fun SettingsContent( // navigationActions.navigateTo(Screen.AUTH) }) } + +/** + * Logs out the user by signing out from Google and the app. + * + * @param authService the Google authentication service + * @param userViewModel the user viewmodel + * @param preferencesViewModel the preferences viewmodel + */ +fun logout( + authService: GoogleAuthService, + userViewModel: UserViewModel, + preferencesViewModel: PreferencesViewModel +) { + // Sign out and revoke access from Google Service (disable auto-login) + authService.signOut() + authService.revokeAccess() + + // Clear user viewmodel data + userViewModel.setUser(null) + userViewModel.setCurrentUser(null) + + // Clear preferences parameters + preferencesViewModel.setLoginState(false) + preferencesViewModel.setUid("") + preferencesViewModel.setName("") + preferencesViewModel.setScore(0) +} diff --git a/app/src/main/java/com/android/streetworkapp/utils/AuthService.kt b/app/src/main/java/com/android/streetworkapp/utils/AuthService.kt index cafc458f6..fbb2338fa 100644 --- a/app/src/main/java/com/android/streetworkapp/utils/AuthService.kt +++ b/app/src/main/java/com/android/streetworkapp/utils/AuthService.kt @@ -1,6 +1,5 @@ package com.android.streetworkapp.utils -import android.content.Context import android.content.Intent import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult @@ -10,13 +9,15 @@ import com.google.firebase.auth.FirebaseUser interface AuthService { // Trigger the sign-in process : fun launchSignIn( - context: Context, launcher: ManagedActivityResultLauncher, ) // Handle the sign-out process fun signOut() + // Revoke access to the app + fun revokeAccess() + // Returns the current signed-in user (null if none) fun getCurrentUser(): FirebaseUser? } diff --git a/app/src/main/java/com/android/streetworkapp/utils/GoogleAuthService.kt b/app/src/main/java/com/android/streetworkapp/utils/GoogleAuthService.kt index 26c85c4e8..de471efba 100644 --- a/app/src/main/java/com/android/streetworkapp/utils/GoogleAuthService.kt +++ b/app/src/main/java/com/android/streetworkapp/utils/GoogleAuthService.kt @@ -2,6 +2,7 @@ package com.android.streetworkapp.utils +import android.content.Context import android.content.Intent import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult @@ -10,6 +11,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInClient import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.common.api.ApiException import com.google.firebase.auth.AuthResult @@ -21,29 +23,66 @@ import com.google.firebase.ktx.Firebase import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await -class GoogleAuthService(private val token: String, private val auth: FirebaseAuth) : AuthService { +/** + * A class that handles Google authentication. + * + * @param token The token to use for authentication. + * @param auth The Firebase authentication instance. + * @param context The context to use for authentication. + */ +class GoogleAuthService( + private val token: String, + private val auth: FirebaseAuth, + private val context: Context +) : AuthService { - override fun launchSignIn( - context: android.content.Context, - launcher: ManagedActivityResultLauncher - ) { + private val mGoogleSignInClient: GoogleSignInClient + + init { val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(token) .requestEmail() .build() - val googleSignInClient = GoogleSignIn.getClient(context, gso) - launcher.launch(googleSignInClient.signInIntent) + mGoogleSignInClient = GoogleSignIn.getClient(context, gso) + } + + /** + * Launches the sign-in activity. + * + * @param launcher The launcher to use for the activity result. + */ + override fun launchSignIn(launcher: ManagedActivityResultLauncher) { + launcher.launch(mGoogleSignInClient.signInIntent) } + /** Signs out the user. */ override fun signOut() { auth.signOut() + mGoogleSignInClient.signOut() + } + + /** Revokes access to the app. */ + override fun revokeAccess() { + mGoogleSignInClient.revokeAccess() } + /** + * Gets the auth current user. + * + * @return The auth current user. + */ override fun getCurrentUser(): FirebaseUser? { return auth.currentUser } + /** + * Remembers the Firebase authentication launcher. + * + * @param onAuthComplete The function to call when authentication is complete. + * @param onAuthError The function to call when authentication fails. + * @return The Firebase authentication launcher. + */ @Composable fun rememberFirebaseAuthLauncher( onAuthComplete: (AuthResult) -> Unit, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88f52ed04..6bb8ed4d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,9 @@ Are you sure ? Do you really want to delete your account ? \n\nYou will loose all of your progression. \nThis action is irreversible. + Log-out + You are logged out + // Friend removal : Remove friend diff --git a/app/src/test/java/com/android/streetworkapp/model/user/UserViewModelTest.kt b/app/src/test/java/com/android/streetworkapp/model/user/UserViewModelTest.kt index 4ff48a18e..dfc916c4b 100644 --- a/app/src/test/java/com/android/streetworkapp/model/user/UserViewModelTest.kt +++ b/app/src/test/java/com/android/streetworkapp/model/user/UserViewModelTest.kt @@ -172,6 +172,14 @@ class UserViewModelTest { assertEquals(user, observedUser) } + @Test + fun setUserUpdatesUser() = runTest { + val user = User("user123", "John Doe", "john@example.com", 100, emptyList(), picture = "") + userViewModel.setUser(user) + val observedUser = userViewModel.user.first() + assertEquals(user, observedUser) + } + @Test fun getUserUpdatesUser() = runTest { val user = User("user123", "Jane Doe", "jane@example.com", 50, emptyList(), picture = "")