diff --git a/src/.idea/deploymentTargetDropDown.xml b/src/.idea/deploymentTargetDropDown.xml index ad2890c..27a5c42 100644 --- a/src/.idea/deploymentTargetDropDown.xml +++ b/src/.idea/deploymentTargetDropDown.xml @@ -7,11 +7,11 @@ - + - + \ No newline at end of file diff --git a/src/app/build.gradle.kts b/src/app/build.gradle.kts index 5875172..d9b3b9f 100644 --- a/src/app/build.gradle.kts +++ b/src/app/build.gradle.kts @@ -100,4 +100,7 @@ dependencies { androidTestImplementation(libs.test.junit) androidTestImplementation(libs.test.espresso) androidTestImplementation(libs.test.androidx.junit) + androidTestImplementation(libs.hilt.androidTesting) + kaptAndroidTest(libs.hilt.androidCompiler) + } \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/di/KeystoreModule.kt b/src/app/src/main/java/fr/iut/alldev/allin/di/KeystoreModule.kt new file mode 100644 index 0000000..f2d2c0c --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/di/KeystoreModule.kt @@ -0,0 +1,17 @@ +package fr.iut.alldev.allin.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import fr.iut.alldev.allin.keystore.AllInKeystoreManager +import fr.iut.alldev.allin.keystore.impl.AllInKeystoreManagerImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class KeystoreModule { + @Singleton + @Binds + abstract fun provideKeystoreManager(allInKeystoreManagerImpl: AllInKeystoreManagerImpl): AllInKeystoreManager +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/keystore/AllInKeystoreManager.kt b/src/app/src/main/java/fr/iut/alldev/allin/keystore/AllInKeystoreManager.kt new file mode 100644 index 0000000..5d26ecf --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/keystore/AllInKeystoreManager.kt @@ -0,0 +1,12 @@ +package fr.iut.alldev.allin.keystore + +import androidx.security.crypto.MasterKeys + +abstract class AllInKeystoreManager { + val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) + + abstract fun createKeystore() + abstract fun putToken(token: String) + abstract fun getToken(): String? + abstract fun deleteToken() +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/keystore/impl/AllInKeystoreManagerImpl.kt b/src/app/src/main/java/fr/iut/alldev/allin/keystore/impl/AllInKeystoreManagerImpl.kt new file mode 100644 index 0000000..899fd7e --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/keystore/impl/AllInKeystoreManagerImpl.kt @@ -0,0 +1,42 @@ +package fr.iut.alldev.allin.keystore.impl + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import dagger.hilt.android.qualifiers.ApplicationContext +import fr.iut.alldev.allin.keystore.AllInKeystoreManager +import javax.inject.Inject + +private const val AUTH_TOKEN_KEY = "auth_token" +private const val PREFS_FILE_NAME = "secured_shared_prefs" + +class AllInKeystoreManagerImpl @Inject constructor( + @ApplicationContext private val context: Context, +) : AllInKeystoreManager() { + + private var sharedPreferences: SharedPreferences? = null + override fun createKeystore() { + if (sharedPreferences == null) { + sharedPreferences = EncryptedSharedPreferences.create( + PREFS_FILE_NAME, + masterKeyAlias, + context, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } + } + + override fun putToken(token: String) { + sharedPreferences?.edit()?.putString(AUTH_TOKEN_KEY, token)?.apply() + } + + + override fun getToken(): String? { + return sharedPreferences?.getString(AUTH_TOKEN_KEY, null) + } + + override fun deleteToken() { + sharedPreferences?.edit()?.putString(AUTH_TOKEN_KEY, null)?.apply() + } +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt index b8be456..8d0149b 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt @@ -32,6 +32,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import fr.iut.alldev.allin.R +import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear +import fr.iut.alldev.allin.data.ext.formatToTime import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus import fr.iut.alldev.allin.data.model.bet.BetStatus @@ -50,7 +52,8 @@ private val bets = listOf( endRegisterDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(), isPublic = true, - betStatus = BetStatus.Waiting + betStatus = BetStatus.Waiting, + creator = "Lucas" ), YesNoBet( theme = "Études", @@ -58,7 +61,8 @@ private val bets = listOf( endRegisterDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(), isPublic = true, - betStatus = BetStatus.InProgress + betStatus = BetStatus.InProgress, + creator = "Lucas" ), YesNoBet( theme = "Études", @@ -66,7 +70,8 @@ private val bets = listOf( endRegisterDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(), isPublic = true, - betStatus = BetStatus.Finished(BetFinishedStatus.WON) + betStatus = BetStatus.Finished(BetFinishedStatus.WON), + creator = "Lucas" ), MatchBet( theme = "Études", @@ -76,7 +81,8 @@ private val bets = listOf( isPublic = true, betStatus = BetStatus.Waiting, nameTeam1 = "Team 1", - nameTeam2 = "Team 2" + nameTeam2 = "Team 2", + creator = "Lucas" ), ) @@ -153,11 +159,11 @@ fun BetScreen( } items(bets) { BetScreenCard( - creator = "Lucas", - category = "Études", - title = "Emre va réussir son TP de CI/CD mercredi?", - date = "11 Sept.", - time = "13:00", + creator = it.creator, + category = it.theme, + title = it.phrase, + date = it.endBetDate.formatToMediumDateNoYear(), + time = it.endBetDate.formatToTime(), players = List(3) { null }, onClickParticipate = { selectBet(it, true) }, onClickCard = { selectBet(it, false) }, diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationScreen.kt index f9477e1..6fea697 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationScreen.kt @@ -24,6 +24,7 @@ import fr.iut.alldev.allin.ext.getIcon import fr.iut.alldev.allin.ext.getTitleId import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenAnswerTab import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenQuestionTab +import fr.iut.alldev.allin.ui.core.AllInAlertDialog import fr.iut.alldev.allin.ui.core.AllInDatePicker import fr.iut.alldev.allin.ui.core.AllInSections import fr.iut.alldev.allin.ui.core.AllInTimePicker @@ -42,6 +43,7 @@ fun BetCreationScreen( ) { val interactionSource = remember { MutableInteractionSource() } val betTypes = remember { BetType.values().toList() } + var hasError by remember { mutableStateOf(false) } var theme by remember { viewModel.theme } var phrase by remember { viewModel.phrase } @@ -151,8 +153,10 @@ fun BetCreationScreen( themeFieldName = themeFieldName, phraseFieldName = phraseFieldName, registerDateFieldName = registerDateFieldName, - betDateFieldName = betDateFieldName - ) { mainViewModel.loading.value = it } + betDateFieldName = betDateFieldName, + setLoading = { mainViewModel.loading.value = it }, + onError = { hasError = true } + ) } ) } @@ -182,6 +186,14 @@ fun BetCreationScreen( } ) } + + AllInAlertDialog( + enabled = hasError, + title = stringResource(id = R.string.generic_error), + text = stringResource(id = R.string.bet_creation_error), + onDismiss = { hasError = false } + ) + if (showRegisterTimePicker || showEndTimePicker) { val timeToEdit = if (showRegisterTimePicker) registerDate else betDate AllInTimePicker( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationViewModel.kt index af102cb..67fbdbf 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betCreation/BetCreationViewModel.kt @@ -4,11 +4,15 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException +import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.bet.BetFactory import fr.iut.alldev.allin.data.model.bet.BetType import fr.iut.alldev.allin.data.repository.BetRepository +import fr.iut.alldev.allin.di.AllInCurrentUser import fr.iut.alldev.allin.ext.FieldErrorState import kotlinx.coroutines.launch +import timber.log.Timber import java.time.ZonedDateTime import javax.inject.Inject @@ -17,6 +21,7 @@ const val PHRASE_MIN_SIZE = 5 @HiltViewModel class BetCreationViewModel @Inject constructor( + @AllInCurrentUser val currentUser: User, private val betRepository: BetRepository, ) : ViewModel() { @@ -33,7 +38,7 @@ class BetCreationViewModel @Inject constructor( val registerDateError = mutableStateOf(FieldErrorState.NoError) val betDateError = mutableStateOf(FieldErrorState.NoError) - private fun initErrorField(){ + private fun initErrorField() { themeError.value = FieldErrorState.NoError phraseError.value = FieldErrorState.NoError registerDateError.value = FieldErrorState.NoError @@ -46,30 +51,30 @@ class BetCreationViewModel @Inject constructor( phraseFieldName: String, registerDateFieldName: String, betDateFieldName: String, - ){ - if(theme.value.length < THEME_MIN_SIZE){ + ) { + if (theme.value.length < THEME_MIN_SIZE) { themeError.value = FieldErrorState.TooShort(themeFieldName.lowercase(), THEME_MIN_SIZE) hasError.value = true } - if(phrase.value.length < PHRASE_MIN_SIZE){ + if (phrase.value.length < PHRASE_MIN_SIZE) { phraseError.value = FieldErrorState.TooShort(phraseFieldName.lowercase(), PHRASE_MIN_SIZE) hasError.value = true } - if(registerDate.value <= ZonedDateTime.now()){ + if (registerDate.value <= ZonedDateTime.now()) { registerDateError.value = FieldErrorState.PastDate(registerDateFieldName.lowercase()) hasError.value = true } - if(betDate.value <= ZonedDateTime.now()){ + if (betDate.value <= ZonedDateTime.now()) { betDateError.value = FieldErrorState.PastDate(betDateFieldName.lowercase()) hasError.value = true - }else if(betDate.value < registerDate.value){ + } else if (betDate.value < registerDate.value) { betDateError.value = FieldErrorState.DateOrder( registerDateFieldName.lowercase(), @@ -85,7 +90,8 @@ class BetCreationViewModel @Inject constructor( phraseFieldName: String, registerDateFieldName: String, betDateFieldName: String, - setLoading: (Boolean)->Unit + onError: () -> Unit, + setLoading: (Boolean) -> Unit, ) { viewModelScope.launch { setLoading(true) @@ -98,19 +104,25 @@ class BetCreationViewModel @Inject constructor( betDateFieldName, ) - if(!hasError.value){ - val bet = BetFactory.createBet( - betType = selectedBetType.value, - theme = theme.value, - phrase = phrase.value, - endRegisterDate = registerDate.value, - endBetDate = betDate.value, - isPublic = isPublic.value, - nameTeam1 = "", - nameTeam2 = "", - possibleAnswers = setOf() - ) - betRepository.createBet(bet) + if (!hasError.value) { + try { + val bet = BetFactory.createBet( + betType = selectedBetType.value, + theme = theme.value, + phrase = phrase.value, + endRegisterDate = registerDate.value, + endBetDate = betDate.value, + isPublic = isPublic.value, + nameTeam1 = "", + nameTeam2 = "", + possibleAnswers = setOf(), + creator = currentUser.username + ) + betRepository.createBet(bet) + } catch (e: AllInAPIException) { + Timber.e(e) + onError() + } } setLoading(false) } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt index 89b4c6a..82d73f5 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betHistory/BetHistoryScreen.kt @@ -52,7 +52,7 @@ fun BetHistoryScreen( items(bets) { BetHistoryScreenCard( title = it.phrase, - creator = "creator", + creator = it.creator, category = it.theme, date = it.endBetDate.formatToMediumDateNoYear(), time = it.endBetDate.formatToTime(), diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt index 5acdde5..62c1fd0 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betStatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt @@ -176,7 +176,8 @@ private fun YesNoBetPreview() { endRegisterDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(), isPublic = true, - betStatus = BetStatus.InProgress + betStatus = BetStatus.InProgress, + creator = "creator" ).toBetVO()?.Accept( BetStatusBottomSheetDisplayBetVisitor( userCoinAmount = coins, @@ -198,7 +199,8 @@ private fun YesNoBetFinishedPreview() { endRegisterDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(), isPublic = true, - betStatus = BetStatus.Finished(BetFinishedStatus.WON) + betStatus = BetStatus.Finished(BetFinishedStatus.WON), + creator = "creator" ).toBetVO()?.Accept( BetStatusBottomSheetDisplayBetVisitor( userCoinAmount = coins, @@ -222,7 +224,8 @@ private fun MatchBetPreview() { isPublic = true, betStatus = BetStatus.InProgress, nameTeam1 = "Team 1", - nameTeam2 = "Team 2" + nameTeam2 = "Team 2", + creator = "creator" ).toBetVO()?.Accept( BetStatusBottomSheetDisplayBetVisitor( userCoinAmount = coins, diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt index ae6264e..9bd14c1 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException import fr.iut.alldev.allin.data.repository.UserRepository +import fr.iut.alldev.allin.keystore.AllInKeystoreManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -13,7 +14,8 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val keystoreManager: AllInKeystoreManager ) : ViewModel() { var loading = mutableStateOf(false) @@ -22,19 +24,21 @@ class LoginViewModel @Inject constructor( val username = mutableStateOf("") val password = mutableStateOf("") fun onLogin( - navigateToDashboard: ()->Unit - ){ + navigateToDashboard: () -> Unit + ) { viewModelScope.launch { loading.value = true withContext(Dispatchers.IO) { - try{ - userRepository.login(username.value, password.value) - } catch (e: AllInAPIException){ + try { + userRepository + .login(username.value, password.value) + ?.let { token -> keystoreManager.putToken(token) } + } catch (e: AllInAPIException) { hasError.value = true } } - if(!hasError.value){ + if (!hasError.value) { navigateToDashboard() } loading.value = false diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt index 8a3e62d..6abdb46 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt @@ -60,6 +60,7 @@ fun MainScreen( drawerState: DrawerState = rememberDrawerState(initialValue = DrawerValue.Closed), startDestination: String = Routes.PUBLIC_BETS, mainViewModel: MainViewModel = hiltViewModel(), + navigateToWelcomeScreen: () -> Unit ) { val loading by remember { mainViewModel.loading } @@ -105,6 +106,10 @@ fun MainScreen( bestWin = 362, navigateTo = { route -> navController.popUpTo(route, startDestination) + }, + logout = { + mainViewModel.deleteToken() + navigateToWelcomeScreen() } ) { AllInScaffold( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt index 925f6a9..7ee0273 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt @@ -8,18 +8,20 @@ import dagger.hilt.android.lifecycle.HiltViewModel import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.di.AllInCurrentUser +import fr.iut.alldev.allin.keystore.AllInKeystoreManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject -class UserState(val user: User){ +class UserState(val user: User) { val userCoins = mutableIntStateOf(user.coins) } @HiltViewModel class MainViewModel @Inject constructor( - @AllInCurrentUser val currentUser: User + @AllInCurrentUser val currentUser: User, + private val keystoreManager: AllInKeystoreManager ) : ViewModel() { var loading = mutableStateOf(false) @@ -27,9 +29,13 @@ class MainViewModel @Inject constructor( val currentUserState = UserState(currentUser) val selectedBet = mutableStateOf(null) - fun participateToBet( - stake: Int - ){ + fun deleteToken() { + viewModelScope.launch { + keystoreManager.deleteToken() + } + } + + fun participateToBet(stake: Int) { viewModelScope.launch { withContext(Dispatchers.IO) { loading.value = true diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt index 3d1b2c1..d7d0040 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt @@ -46,15 +46,12 @@ object NavArguments { const val ARG_BET_HISTORY_IS_CURRENT = "ARG_BET_HISTORY_IS_CURRENT" } - internal fun NavHostController.popUpTo(route: String, baseRoute: String) { this.navigate(route) { launchSingleTop = true popUpTo(baseRoute) { - saveState = true inclusive = true } - restoreState = true } } @@ -67,21 +64,19 @@ fun AllInNavHost( NavHost( navController = navController, startDestination = startDestination, - enterTransition = - { - if (navController.currentDestination?.route != Routes.DASHBOARD) + enterTransition = { + if (navController.currentDestination?.route != Routes.DASHBOARD && + navController.currentDestination?.route != Routes.WELCOME + ) { slideInHorizontally(initialOffsetX = { it }) - else - fadeIn(animationSpec = tween(1500)) + } else fadeIn(animationSpec = tween(1500)) }, - exitTransition = - { - if (navController.currentDestination?.route != Routes.DASHBOARD) + exitTransition = { + if (navController.currentDestination?.route != Routes.DASHBOARD && + navController.currentDestination?.route != Routes.WELCOME + ) { slideOutHorizontally(targetOffsetX = { -it / 2 }) - else - fadeOut( - animationSpec = tween(1500) - ) + } else fadeOut(animationSpec = tween(1500)) }, modifier = modifier .fillMaxSize() @@ -90,7 +85,7 @@ fun AllInNavHost( allInWelcomeScreen(navController) allInRegisterScreen(navController) allInLoginScreen(navController) - allInDashboard() + allInDashboard(navController) } } @@ -149,14 +144,15 @@ private fun NavGraphBuilder.allInWelcomeScreen( }, navigateToLogin = { navController.popUpTo(Routes.LOGIN, Routes.WELCOME) + }, + navigateToDashboard = { + navController.popUpTo(Routes.DASHBOARD, Routes.WELCOME) } ) } } -private fun NavGraphBuilder.allInRegisterScreen( - navController: NavHostController, -) { +private fun NavGraphBuilder.allInRegisterScreen(navController: NavHostController) { composable(route = Routes.REGISTER) { RegisterScreen( navigateToDashboard = { @@ -169,9 +165,7 @@ private fun NavGraphBuilder.allInRegisterScreen( } } -private fun NavGraphBuilder.allInLoginScreen( - navController: NavHostController, -) { +private fun NavGraphBuilder.allInLoginScreen(navController: NavHostController) { composable(route = Routes.LOGIN) { LoginScreen( navigateToRegister = { @@ -184,10 +178,14 @@ private fun NavGraphBuilder.allInLoginScreen( } } -private fun NavGraphBuilder.allInDashboard() { +private fun NavGraphBuilder.allInDashboard(navController: NavHostController) { composable( route = Routes.DASHBOARD, ) { - MainScreen() + MainScreen( + navigateToWelcomeScreen = { + navController.popUpTo(Routes.WELCOME, Routes.DASHBOARD) + } + ) } } \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/drawer/AllInDrawer.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/drawer/AllInDrawer.kt index a57b701..a86410f 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/drawer/AllInDrawer.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/drawer/AllInDrawer.kt @@ -11,6 +11,8 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,6 +20,8 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import fr.iut.alldev.allin.R import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.ui.navigation.TopLevelDestination import fr.iut.alldev.allin.ui.navigation.drawer.components.DrawerCell @@ -35,7 +39,8 @@ fun AllInDrawer( bestWin: Int, nbFriends: Int, navigateTo: (String) -> Unit, - content: @Composable () -> Unit, + logout: () -> Unit, + content: @Composable () -> Unit ) { ModalNavigationDrawer( drawerState = drawerState, @@ -63,6 +68,17 @@ fun AllInDrawer( modifier = Modifier.padding(vertical = 5.dp, horizontal = 13.dp) ) } + TextButton( + onClick = logout, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text( + text = stringResource(id = R.string.Logout), + style = AllInTheme.typography.h3, + color = AllInTheme.colors.allInDarkGrey50, + fontSize = 16.sp + ) + } Box( Modifier .fillMaxSize() diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt index 6c17b78..80f46d8 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt @@ -10,6 +10,7 @@ import fr.iut.alldev.allin.ext.ALLOWED_SYMBOLS import fr.iut.alldev.allin.ext.FieldErrorState import fr.iut.alldev.allin.ext.containsCharacter import fr.iut.alldev.allin.ext.isEmail +import fr.iut.alldev.allin.keystore.AllInKeystoreManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -20,11 +21,12 @@ private const val MIN_USERNAME_SIZE = 3 @HiltViewModel class RegisterViewModel @Inject constructor( - private val userRepository: UserRepository + private val userRepository: UserRepository, + private val keystoreManager: AllInKeystoreManager ) : ViewModel() { var loading = mutableStateOf(false) - var hasError = mutableStateOf(false) + private var hasError = mutableStateOf(false) val username = mutableStateOf("") val email = mutableStateOf("") @@ -36,78 +38,80 @@ class RegisterViewModel @Inject constructor( val passwordError = mutableStateOf(FieldErrorState.NoError) val passwordValidationError = mutableStateOf(FieldErrorState.NoError) - private fun initErrorField(){ + private fun initErrorField() { usernameError.value = FieldErrorState.NoError emailError.value = FieldErrorState.NoError passwordError.value = FieldErrorState.NoError passwordValidationError.value = FieldErrorState.NoError hasError.value = false } + private fun verifyField( - usernameFieldName:String, - emailFieldName:String, - passwordFieldName:String - ){ - if(username.value.length < MIN_USERNAME_SIZE){ + usernameFieldName: String, + emailFieldName: String, + passwordFieldName: String + ) { + if (username.value.length < MIN_USERNAME_SIZE) { usernameError.value = FieldErrorState.TooShort(usernameFieldName.lowercase(), MIN_USERNAME_SIZE) hasError.value = true } - if(password.value.length < MIN_PASSWORD_SIZE){ + if (password.value.length < MIN_PASSWORD_SIZE) { passwordError.value = FieldErrorState.TooShort(passwordFieldName.lowercase(), MIN_PASSWORD_SIZE) hasError.value = true - }else if(!password.value.containsCharacter(ALLOWED_SYMBOLS)){ + } else if (!password.value.containsCharacter(ALLOWED_SYMBOLS)) { passwordError.value = FieldErrorState.NoSpecialCharacter(passwordFieldName.lowercase()) hasError.value = true } - if(!email.value.isEmail()){ + if (!email.value.isEmail()) { emailError.value = FieldErrorState.BadFormat(emailFieldName.lowercase(), "john@doe.com") hasError.value = true } - if(passwordValidation.value != password.value){ + if (passwordValidation.value != password.value) { passwordValidationError.value = FieldErrorState.NotIdentical hasError.value = true } } fun onRegister( - usernameFieldName:String, + usernameFieldName: String, emailFieldName: String, - passwordFieldName:String, - navigateToDashboard: ()->Unit - ){ + passwordFieldName: String, + navigateToDashboard: () -> Unit + ) { viewModelScope.launch { loading.value = true withContext(Dispatchers.IO) { - initErrorField() verifyField( usernameFieldName, emailFieldName, passwordFieldName ) - if(!hasError.value) { + if (!hasError.value) { try { - userRepository.register( - username.value, - email.value, - password.value - ) - }catch(e : AllInAPIException){ + userRepository + .register( + username.value, + email.value, + password.value + ) + ?.let { token -> keystoreManager.putToken(token) } + } catch (e: AllInAPIException) { usernameError.value = FieldErrorState.AlreadyUsed(username.value) emailError.value = FieldErrorState.AlreadyUsed(email.value) hasError.value = true } } } - if(!hasError.value){ + if (!hasError.value) { navigateToDashboard() } loading.value = false diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreen.kt index 4cf77fa..99f3ae4 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreen.kt @@ -1,12 +1,23 @@ package fr.iut.alldev.allin.ui.welcome import android.content.res.Configuration -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush @@ -17,16 +28,25 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import fr.iut.alldev.allin.R import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.ui.core.AllInButton +import fr.iut.alldev.allin.ui.core.AllInLoading -@OptIn(ExperimentalFoundationApi::class) @Composable fun WelcomeScreen( navigateToRegister: () -> Unit, navigateToLogin: () -> Unit, + navigateToDashboard: () -> Unit, + viewModel: WelcomeScreenViewModel = hiltViewModel() ) { + val loading by remember { viewModel.loading } + + LaunchedEffect(viewModel) { + viewModel.tryAutoLogin(navigateToDashboard) + } + Box( Modifier .fillMaxWidth() @@ -102,6 +122,9 @@ fun WelcomeScreen( } } } + + AllInLoading(visible = loading) + } @Preview @@ -109,6 +132,10 @@ fun WelcomeScreen( @Composable private fun WelcomeScreenPreview() { AllInTheme { - WelcomeScreen(navigateToRegister = {}, navigateToLogin = {}) + WelcomeScreen( + navigateToRegister = {}, + navigateToLogin = {}, + navigateToDashboard = {} + ) } } \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreenViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreenViewModel.kt new file mode 100644 index 0000000..9c9c07e --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/welcome/WelcomeScreenViewModel.kt @@ -0,0 +1,38 @@ +package fr.iut.alldev.allin.ui.welcome + + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import fr.iut.alldev.allin.data.repository.UserRepository +import fr.iut.alldev.allin.keystore.AllInKeystoreManager +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class WelcomeScreenViewModel @Inject constructor( + private val keystoreManager: AllInKeystoreManager, + private val userRepository: UserRepository +) : ViewModel() { + + var loading = mutableStateOf(false) + + fun tryAutoLogin(onSuccess: () -> Unit) { + viewModelScope.launch { + loading.value = true + keystoreManager.createKeystore() + keystoreManager.getToken()?.let { token -> + runCatching { + userRepository + .login(token) + ?.let { newToken -> + keystoreManager.putToken(newToken) + } + onSuccess() + } + } + loading.value = false + } + } +} \ No newline at end of file diff --git a/src/app/src/main/res/values-fr/strings.xml b/src/app/src/main/res/values-fr/strings.xml index f52edb8..204d9ef 100644 --- a/src/app/src/main/res/values-fr/strings.xml +++ b/src/app/src/main/res/values-fr/strings.xml @@ -6,6 +6,7 @@ Mot de passe Confirmation du mot de passe Se connecter + Déconnexion Tu as déjà un compte ? Mot de passe oublié ? Pas encore inscrit ? @@ -25,6 +26,7 @@ Détails Mise Gains possibles + Erreur Bets @@ -79,6 +81,8 @@ Les utilisateurs devront répondre au pari avec OUI ou NON. Aucune autre réponse ne sera acceptée. Réponses personnalisées + Erreur lors de la création du bet, veuillez rééssayer. + Populaire Public diff --git a/src/app/src/main/res/values/strings.xml b/src/app/src/main/res/values/strings.xml index 8787b48..e54b9c9 100644 --- a/src/app/src/main/res/values/strings.xml +++ b/src/app/src/main/res/values/strings.xml @@ -8,6 +8,7 @@ Password Confirm password Login + Logout Already have an account ? Forgot password ? Don\'t have an account ? @@ -27,6 +28,7 @@ Details Stake Possible winnings + Error Bets @@ -82,6 +84,8 @@ No other answer will be accepted. Sport match Custom answers + Error while creating the bet. Please try again. + Popular Public @@ -107,6 +111,7 @@ %s point at stake %s points at stake + Finished ! In progress… diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt index be18a76..98a19d6 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt @@ -2,18 +2,23 @@ package fr.iut.alldev.allin.data.api import fr.iut.alldev.allin.data.api.model.CheckUser import fr.iut.alldev.allin.data.api.model.RequestUser +import fr.iut.alldev.allin.data.api.model.ResponseBet import fr.iut.alldev.allin.data.api.model.ResponseUser import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.POST interface AllInApi { @POST("users/login") - suspend fun login( - @Body body: CheckUser - ): ResponseUser + suspend fun login(@Body body: CheckUser): ResponseUser @POST("users/register") - suspend fun register( - @Body body: RequestUser - ): ResponseUser + suspend fun register(@Body body: RequestUser): ResponseUser + + @GET("users/token") + suspend fun login(@Header("Authorization") token: String): ResponseUser + + @POST("bets/add") + suspend fun createBet(@Body body: ResponseBet) } \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseBet.kt new file mode 100644 index 0000000..d500386 --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseBet.kt @@ -0,0 +1,48 @@ +package fr.iut.alldev.allin.data.api.model + +import androidx.annotation.Keep +import fr.iut.alldev.allin.data.model.bet.Bet +import fr.iut.alldev.allin.data.model.bet.BetStatus +import fr.iut.alldev.allin.data.model.bet.CustomBet +import fr.iut.alldev.allin.data.model.bet.YesNoBet +import fr.iut.alldev.allin.data.serialization.ZonedDateTimeSerializer +import kotlinx.serialization.Serializable +import java.time.ZonedDateTime + +@Keep +@Serializable +data class ResponseBet( + val id: Int?, + val theme: String, + val sentenceBet: String, + @Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime, + @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime, + var isPrivate: Boolean, + var response: List, + val createdBy: String, +) { + fun toBet(): Bet { + if (response.toSet() == setOf("Yes", "No")) { + return YesNoBet( + theme = theme, + phrase = sentenceBet, + endRegisterDate = endRegistration, + endBetDate = endBet, + isPublic = !isPrivate, + betStatus = BetStatus.Waiting, + creator = createdBy + ) + } else { + return CustomBet( + theme = theme, + phrase = sentenceBet, + endRegisterDate = endRegistration, + endBetDate = endBet, + isPublic = !isPrivate, + betStatus = BetStatus.Waiting, + creator = createdBy, + possibleAnswers = response.toSet() + ) + } + } +} diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt index e29b6a1..37af616 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt @@ -19,7 +19,8 @@ data class ResponseUser( val username: String, val email: String, var nbCoins: Int, -){ + var token: String? = null, +) { fun toUser() = User( username = username, email = email, diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt index 7110e8c..0d92ede 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt @@ -1,12 +1,29 @@ package fr.iut.alldev.allin.data.model.bet +import fr.iut.alldev.allin.data.api.model.ResponseBet import java.time.ZonedDateTime abstract class Bet( + open val id: Int? = null, + open val creator: String, open val theme: String, open val phrase: String, open val endRegisterDate: ZonedDateTime, open val endBetDate: ZonedDateTime, open val isPublic: Boolean, open val betStatus: BetStatus, -) \ No newline at end of file +) { + abstract fun getResponses(): List + fun toResponseBet(): ResponseBet { + return ResponseBet( + id = id, + theme = theme, + sentenceBet = phrase, + endRegistration = endRegisterDate, + endBet = endBetDate, + isPrivate = !isPublic, + response = getResponses(), + createdBy = creator + ) + } +} \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetFactory.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetFactory.kt index 9d570f6..f48a5d3 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetFactory.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetFactory.kt @@ -6,6 +6,7 @@ class BetFactory { companion object { fun createBet( betType: BetType, + creator: String, theme: String, phrase: String, endRegisterDate: ZonedDateTime, @@ -20,6 +21,7 @@ class BetFactory { BetType.YES_NO -> { YesNoBet( theme = theme, + creator = creator, phrase = phrase, endRegisterDate = endRegisterDate, endBetDate = endBetDate, @@ -31,6 +33,7 @@ class BetFactory { BetType.MATCH -> { MatchBet( theme = theme, + creator = creator, phrase = phrase, endRegisterDate = endRegisterDate, endBetDate = endBetDate, @@ -45,6 +48,7 @@ class BetFactory { BetType.CUSTOM -> { CustomBet( theme = theme, + creator = creator, phrase = phrase, endRegisterDate = endRegisterDate, endBetDate = endBetDate, diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/CustomBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/CustomBet.kt index a9f27f0..4a8320d 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/CustomBet.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/CustomBet.kt @@ -3,18 +3,24 @@ package fr.iut.alldev.allin.data.model.bet import java.time.ZonedDateTime data class CustomBet( + override val id: Int? = null, + override val creator: String, override val theme: String, - override val phrase: String, - override val endRegisterDate: ZonedDateTime, - override val endBetDate: ZonedDateTime, - override val isPublic: Boolean, - override val betStatus: BetStatus, - val possibleAnswers: Set + override val phrase: String, + override val endRegisterDate: ZonedDateTime, + override val endBetDate: ZonedDateTime, + override val isPublic: Boolean, + override val betStatus: BetStatus, + val possibleAnswers: Set, ) : Bet( + id, + creator, theme, phrase, endRegisterDate, endBetDate, isPublic, betStatus -) +) { + override fun getResponses(): List = possibleAnswers.toList() +} diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt index 43c21cd..e0a4211 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt @@ -3,6 +3,8 @@ package fr.iut.alldev.allin.data.model.bet import java.time.ZonedDateTime data class MatchBet( + override val id: Int? = null, + override val creator: String, override val theme: String, override val phrase: String, override val endRegisterDate: ZonedDateTime, @@ -10,12 +12,17 @@ data class MatchBet( override val isPublic: Boolean, override val betStatus: BetStatus, val nameTeam1: String, - val nameTeam2: String + val nameTeam2: String, ) : Bet( + id, + creator, theme, phrase, endRegisterDate, endBetDate, isPublic, betStatus -) \ No newline at end of file +) { + override fun getResponses(): List = listOf(nameTeam1, nameTeam2) + +} \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt index f9c09db..8252dab 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt @@ -3,17 +3,23 @@ package fr.iut.alldev.allin.data.model.bet import java.time.ZonedDateTime data class YesNoBet( + override val id: Int? = null, + override val creator: String, override val theme: String, override val phrase: String, override val endRegisterDate: ZonedDateTime, override val endBetDate: ZonedDateTime, override val isPublic: Boolean, - override val betStatus: BetStatus + override val betStatus: BetStatus, ) : Bet( + id, + creator, theme, phrase, endRegisterDate, endBetDate, isPublic, betStatus -) \ No newline at end of file +) { + override fun getResponses(): List = listOf("Yes", "No") +} \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/BetRepository.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/BetRepository.kt index b11ec0b..e846158 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/BetRepository.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/BetRepository.kt @@ -4,10 +4,7 @@ import fr.iut.alldev.allin.data.model.bet.Bet import kotlinx.coroutines.flow.Flow abstract class BetRepository { - abstract suspend fun createBet( - bet: Bet, - ) - + abstract suspend fun createBet(bet: Bet) abstract suspend fun getHistory(): Flow> abstract suspend fun getCurrentBets(): Flow> } \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt index bb2001c..338b8aa 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/UserRepository.kt @@ -7,10 +7,13 @@ abstract class UserRepository { abstract suspend fun login( username: String, password: String - ) + ): String? + + abstract suspend fun login(token: String): String? + abstract suspend fun register( username: String, email: String, password: String - ) + ): String? } \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt index ccdbc6e..425cfdd 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/BetRepositoryImpl.kt @@ -8,24 +8,22 @@ import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.repository.BetRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import timber.log.Timber import java.time.ZonedDateTime import javax.inject.Inject class BetRepositoryImpl @Inject constructor( - private val api: AllInApi, + private val api: AllInApi ) : BetRepository() { override suspend fun createBet(bet: Bet) { - // TODO - Timber.d("$bet") + api.createBet(bet.toResponseBet()) } override suspend fun getHistory(): Flow> { // TODO return flowOf( listOf( - YesNoBet( + creator = "Lucas", theme = "Theme", phrase = "Bet phrase 1", endRegisterDate = ZonedDateTime.now().minusDays(4), @@ -34,6 +32,7 @@ class BetRepositoryImpl @Inject constructor( betStatus = BetStatus.Finished(BetFinishedStatus.WON) ), YesNoBet( + creator = "Lucas", theme = "Theme", phrase = "Bet phrase 2", endRegisterDate = ZonedDateTime.now().minusDays(3), @@ -42,6 +41,7 @@ class BetRepositoryImpl @Inject constructor( betStatus = BetStatus.Finished(BetFinishedStatus.LOST) ), YesNoBet( + creator = "Lucas", theme = "Theme", phrase = "Bet phrase 3", endRegisterDate = ZonedDateTime.now().minusDays(15), @@ -58,6 +58,7 @@ class BetRepositoryImpl @Inject constructor( return flowOf( listOf( YesNoBet( + creator = "Lucas", theme = "Theme", phrase = "Bet phrase 1", endRegisterDate = ZonedDateTime.now().plusDays(5), @@ -66,6 +67,7 @@ class BetRepositoryImpl @Inject constructor( betStatus = BetStatus.InProgress ), YesNoBet( + creator = "Lucas", theme = "Theme", phrase = "Bet phrase 2", endRegisterDate = ZonedDateTime.now().plusDays(1), @@ -74,6 +76,7 @@ class BetRepositoryImpl @Inject constructor( betStatus = BetStatus.InProgress ), YesNoBet( + creator = "Lucas", theme = "Theme", phrase = "Bet phrase 3", endRegisterDate = ZonedDateTime.now().plusDays(3), diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt index 3380105..54f3889 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt @@ -10,25 +10,34 @@ class UserRepositoryImpl @Inject constructor( private val api: AllInApi, ) : UserRepository() { - override suspend fun login(username: String, password: String) { - currentUser = api.login( + override suspend fun login(username: String, password: String): String? { + val response = api.login( CheckUser( login = username, password = password ) - ).toUser() + ) + currentUser = response.toUser() + return response.token + } + + override suspend fun login(token: String): String? { + val response = api.login(token = "Bearer $token") + currentUser = response.toUser() + return response.token } - override suspend fun register(username: String, email: String, password: String) { - currentUser = api.register( + override suspend fun register(username: String, email: String, password: String): String? { + val response = api.register( RequestUser( username = username, email = email, password = password, nbCoins = 0 ) - ).toUser() - + ) + currentUser = response.toUser() + return response.token } } \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/serialization/ZonedDateTimeSerializer.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/serialization/ZonedDateTimeSerializer.kt new file mode 100644 index 0000000..1a20345 --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/serialization/ZonedDateTimeSerializer.kt @@ -0,0 +1,25 @@ +package fr.iut.alldev.allin.data.serialization + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime + +object ZonedDateTimeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ZonedDateTime", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: ZonedDateTime) { + encoder.encodeLong(value.toEpochSecond()) + } + + override fun deserialize(decoder: Decoder): ZonedDateTime { + val epoch = decoder.decodeLong() + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epoch), ZoneId.systemDefault()) + } +} \ No newline at end of file diff --git a/src/gradle/libs.versions.toml b/src/gradle/libs.versions.toml index 989a6c5..cb615ee 100644 --- a/src/gradle/libs.versions.toml +++ b/src/gradle/libs.versions.toml @@ -10,6 +10,7 @@ kotlin = "1.9.20" androidxCore = "1.12.0" androidxActivity = "1.8.2" +androidxSecurity = "1.0.0" composeBom = "2023.10.01" composePreview = "1.6.0-beta03" @@ -39,6 +40,7 @@ resgenPlugin = "2.5" # Android androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidxCore" } androidx-activity = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" } +androidx-security = { module = "androidx.security:security-crypto", version.ref = "androidxSecurity" } # Lifecycle androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } @@ -50,7 +52,8 @@ androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-ru test-junit = { group = "junit", name = "junit", version.ref = "junit" } test-androidx-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExtJunit" } test-espresso = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } - +hilt-androidTesting = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } +hilt-androidCompiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } # Compose compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } @@ -95,6 +98,6 @@ plugin-hilt = { module = "com.google.dagger:hilt-android-gradle-plugin", version [bundles] -android = ["androidx-core", "androidx-activity"] +android = ["androidx-core", "androidx-activity", "androidx-security"] androidx-lifecycle = ["androidx-lifecycle-runtime", "androidx-lifecycle-viewmodel", "androidx-lifecycle-process", "androidx-lifecycle-runtime-compose"] compose = ["compose-ui", "compose-ui-graphics", "compose-tooling-preview", "compose-ui-tooling", "compose-foundation", "compose-material", "compose-material3", "compose-material-icons", "compose-material-icons-extended", "compose-navigation", "compose-ui-googlefonts"]