Automatic login with JWT and Logout
continuous-integration/drone/push Build is passing Details

pull/3/head
Arthur VALIN 1 year ago
parent 304508fa8c
commit b575f6e157

@ -7,11 +7,11 @@
<deviceKey> <deviceKey>
<Key> <Key>
<type value="VIRTUAL_DEVICE_PATH" /> <type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\Siph9\.android\avd\pixel_5_-_api_33.avd" /> <value value="C:\Users\Siph9\.android\avd\Pixel_4_XL_API_33.avd" />
</Key> </Key>
</deviceKey> </deviceKey>
</Target> </Target>
</targetSelectedWithDropDown> </targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-09-27T20:38:30.723570500Z" /> <timeTargetWasSelectedWithDropDown value="2024-01-09T09:46:59.648294500Z" />
</component> </component>
</project> </project>

@ -100,4 +100,7 @@ dependencies {
androidTestImplementation(libs.test.junit) androidTestImplementation(libs.test.junit)
androidTestImplementation(libs.test.espresso) androidTestImplementation(libs.test.espresso)
androidTestImplementation(libs.test.androidx.junit) androidTestImplementation(libs.test.androidx.junit)
androidTestImplementation(libs.hilt.androidTesting)
kaptAndroidTest(libs.hilt.androidCompiler)
} }

@ -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
}

@ -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()
}

@ -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()
}
}

@ -32,6 +32,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import fr.iut.alldev.allin.R 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.Bet
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
@ -50,7 +52,8 @@ private val bets = listOf(
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.Waiting betStatus = BetStatus.Waiting,
creator = "Lucas"
), ),
YesNoBet( YesNoBet(
theme = "Études", theme = "Études",
@ -58,7 +61,8 @@ private val bets = listOf(
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress betStatus = BetStatus.InProgress,
creator = "Lucas"
), ),
YesNoBet( YesNoBet(
theme = "Études", theme = "Études",
@ -66,7 +70,8 @@ private val bets = listOf(
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON) betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "Lucas"
), ),
MatchBet( MatchBet(
theme = "Études", theme = "Études",
@ -76,7 +81,8 @@ private val bets = listOf(
isPublic = true, isPublic = true,
betStatus = BetStatus.Waiting, betStatus = BetStatus.Waiting,
nameTeam1 = "Team 1", nameTeam1 = "Team 1",
nameTeam2 = "Team 2" nameTeam2 = "Team 2",
creator = "Lucas"
), ),
) )
@ -153,11 +159,11 @@ fun BetScreen(
} }
items(bets) { items(bets) {
BetScreenCard( BetScreenCard(
creator = "Lucas", creator = it.creator,
category = "Études", category = it.theme,
title = "Emre va réussir son TP de CI/CD mercredi?", title = it.phrase,
date = "11 Sept.", date = it.endBetDate.formatToMediumDateNoYear(),
time = "13:00", time = it.endBetDate.formatToTime(),
players = List(3) { null }, players = List(3) { null },
onClickParticipate = { selectBet(it, true) }, onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) }, onClickCard = { selectBet(it, false) },

@ -24,6 +24,7 @@ import fr.iut.alldev.allin.ext.getIcon
import fr.iut.alldev.allin.ext.getTitleId import fr.iut.alldev.allin.ext.getTitleId
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenAnswerTab import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenAnswerTab
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenQuestionTab 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.AllInDatePicker
import fr.iut.alldev.allin.ui.core.AllInSections import fr.iut.alldev.allin.ui.core.AllInSections
import fr.iut.alldev.allin.ui.core.AllInTimePicker import fr.iut.alldev.allin.ui.core.AllInTimePicker
@ -42,6 +43,7 @@ fun BetCreationScreen(
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val betTypes = remember { BetType.values().toList() } val betTypes = remember { BetType.values().toList() }
var hasError by remember { mutableStateOf(false) }
var theme by remember { viewModel.theme } var theme by remember { viewModel.theme }
var phrase by remember { viewModel.phrase } var phrase by remember { viewModel.phrase }
@ -151,8 +153,10 @@ fun BetCreationScreen(
themeFieldName = themeFieldName, themeFieldName = themeFieldName,
phraseFieldName = phraseFieldName, phraseFieldName = phraseFieldName,
registerDateFieldName = registerDateFieldName, registerDateFieldName = registerDateFieldName,
betDateFieldName = betDateFieldName betDateFieldName = betDateFieldName,
) { mainViewModel.loading.value = it } 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) { if (showRegisterTimePicker || showEndTimePicker) {
val timeToEdit = if (showRegisterTimePicker) registerDate else betDate val timeToEdit = if (showRegisterTimePicker) registerDate else betDate
AllInTimePicker( AllInTimePicker(

@ -4,11 +4,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel 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.BetFactory
import fr.iut.alldev.allin.data.model.bet.BetType import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.di.AllInCurrentUser
import fr.iut.alldev.allin.ext.FieldErrorState import fr.iut.alldev.allin.ext.FieldErrorState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.ZonedDateTime import java.time.ZonedDateTime
import javax.inject.Inject import javax.inject.Inject
@ -17,6 +21,7 @@ const val PHRASE_MIN_SIZE = 5
@HiltViewModel @HiltViewModel
class BetCreationViewModel @Inject constructor( class BetCreationViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository, private val betRepository: BetRepository,
) : ViewModel() { ) : ViewModel() {
@ -85,7 +90,8 @@ class BetCreationViewModel @Inject constructor(
phraseFieldName: String, phraseFieldName: String,
registerDateFieldName: String, registerDateFieldName: String,
betDateFieldName: String, betDateFieldName: String,
setLoading: (Boolean)->Unit onError: () -> Unit,
setLoading: (Boolean) -> Unit,
) { ) {
viewModelScope.launch { viewModelScope.launch {
setLoading(true) setLoading(true)
@ -99,6 +105,7 @@ class BetCreationViewModel @Inject constructor(
) )
if (!hasError.value) { if (!hasError.value) {
try {
val bet = BetFactory.createBet( val bet = BetFactory.createBet(
betType = selectedBetType.value, betType = selectedBetType.value,
theme = theme.value, theme = theme.value,
@ -108,9 +115,14 @@ class BetCreationViewModel @Inject constructor(
isPublic = isPublic.value, isPublic = isPublic.value,
nameTeam1 = "", nameTeam1 = "",
nameTeam2 = "", nameTeam2 = "",
possibleAnswers = setOf() possibleAnswers = setOf(),
creator = currentUser.username
) )
betRepository.createBet(bet) betRepository.createBet(bet)
} catch (e: AllInAPIException) {
Timber.e(e)
onError()
}
} }
setLoading(false) setLoading(false)
} }

@ -52,7 +52,7 @@ fun BetHistoryScreen(
items(bets) { items(bets) {
BetHistoryScreenCard( BetHistoryScreenCard(
title = it.phrase, title = it.phrase,
creator = "creator", creator = it.creator,
category = it.theme, category = it.theme,
date = it.endBetDate.formatToMediumDateNoYear(), date = it.endBetDate.formatToMediumDateNoYear(),
time = it.endBetDate.formatToTime(), time = it.endBetDate.formatToTime(),

@ -176,7 +176,8 @@ private fun YesNoBetPreview() {
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress betStatus = BetStatus.InProgress,
creator = "creator"
).toBetVO()?.Accept( ).toBetVO()?.Accept(
BetStatusBottomSheetDisplayBetVisitor( BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = coins, userCoinAmount = coins,
@ -198,7 +199,8 @@ private fun YesNoBetFinishedPreview() {
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON) betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator"
).toBetVO()?.Accept( ).toBetVO()?.Accept(
BetStatusBottomSheetDisplayBetVisitor( BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = coins, userCoinAmount = coins,
@ -222,7 +224,8 @@ private fun MatchBetPreview() {
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress, betStatus = BetStatus.InProgress,
nameTeam1 = "Team 1", nameTeam1 = "Team 1",
nameTeam2 = "Team 2" nameTeam2 = "Team 2",
creator = "creator"
).toBetVO()?.Accept( ).toBetVO()?.Accept(
BetStatusBottomSheetDisplayBetVisitor( BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = coins, userCoinAmount = coins,

@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException
import fr.iut.alldev.allin.data.repository.UserRepository import fr.iut.alldev.allin.data.repository.UserRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -13,7 +14,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class LoginViewModel @Inject constructor( class LoginViewModel @Inject constructor(
private val userRepository: UserRepository private val userRepository: UserRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
var loading = mutableStateOf(false) var loading = mutableStateOf(false)
@ -29,7 +31,9 @@ class LoginViewModel @Inject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
userRepository.login(username.value, password.value) userRepository
.login(username.value, password.value)
?.let { token -> keystoreManager.putToken(token) }
} catch (e: AllInAPIException) { } catch (e: AllInAPIException) {
hasError.value = true hasError.value = true
} }

@ -60,6 +60,7 @@ fun MainScreen(
drawerState: DrawerState = rememberDrawerState(initialValue = DrawerValue.Closed), drawerState: DrawerState = rememberDrawerState(initialValue = DrawerValue.Closed),
startDestination: String = Routes.PUBLIC_BETS, startDestination: String = Routes.PUBLIC_BETS,
mainViewModel: MainViewModel = hiltViewModel(), mainViewModel: MainViewModel = hiltViewModel(),
navigateToWelcomeScreen: () -> Unit
) { ) {
val loading by remember { mainViewModel.loading } val loading by remember { mainViewModel.loading }
@ -105,6 +106,10 @@ fun MainScreen(
bestWin = 362, bestWin = 362,
navigateTo = { route -> navigateTo = { route ->
navController.popUpTo(route, startDestination) navController.popUpTo(route, startDestination)
},
logout = {
mainViewModel.deleteToken()
navigateToWelcomeScreen()
} }
) { ) {
AllInScaffold( AllInScaffold(

@ -8,6 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.di.AllInCurrentUser import fr.iut.alldev.allin.di.AllInCurrentUser
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -19,7 +20,8 @@ class UserState(val user: User){
@HiltViewModel @HiltViewModel
class MainViewModel @Inject constructor( class MainViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User @AllInCurrentUser val currentUser: User,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
var loading = mutableStateOf(false) var loading = mutableStateOf(false)
@ -27,9 +29,13 @@ class MainViewModel @Inject constructor(
val currentUserState = UserState(currentUser) val currentUserState = UserState(currentUser)
val selectedBet = mutableStateOf<Bet?>(null) val selectedBet = mutableStateOf<Bet?>(null)
fun participateToBet( fun deleteToken() {
stake: Int viewModelScope.launch {
){ keystoreManager.deleteToken()
}
}
fun participateToBet(stake: Int) {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
loading.value = true loading.value = true

@ -46,15 +46,12 @@ object NavArguments {
const val ARG_BET_HISTORY_IS_CURRENT = "ARG_BET_HISTORY_IS_CURRENT" const val ARG_BET_HISTORY_IS_CURRENT = "ARG_BET_HISTORY_IS_CURRENT"
} }
internal fun NavHostController.popUpTo(route: String, baseRoute: String) { internal fun NavHostController.popUpTo(route: String, baseRoute: String) {
this.navigate(route) { this.navigate(route) {
launchSingleTop = true launchSingleTop = true
popUpTo(baseRoute) { popUpTo(baseRoute) {
saveState = true
inclusive = true inclusive = true
} }
restoreState = true
} }
} }
@ -67,21 +64,19 @@ fun AllInNavHost(
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = startDestination, startDestination = startDestination,
enterTransition = enterTransition = {
{ if (navController.currentDestination?.route != Routes.DASHBOARD &&
if (navController.currentDestination?.route != Routes.DASHBOARD) navController.currentDestination?.route != Routes.WELCOME
) {
slideInHorizontally(initialOffsetX = { it }) slideInHorizontally(initialOffsetX = { it })
else } else fadeIn(animationSpec = tween(1500))
fadeIn(animationSpec = tween(1500))
}, },
exitTransition = exitTransition = {
{ if (navController.currentDestination?.route != Routes.DASHBOARD &&
if (navController.currentDestination?.route != Routes.DASHBOARD) navController.currentDestination?.route != Routes.WELCOME
) {
slideOutHorizontally(targetOffsetX = { -it / 2 }) slideOutHorizontally(targetOffsetX = { -it / 2 })
else } else fadeOut(animationSpec = tween(1500))
fadeOut(
animationSpec = tween(1500)
)
}, },
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@ -90,7 +85,7 @@ fun AllInNavHost(
allInWelcomeScreen(navController) allInWelcomeScreen(navController)
allInRegisterScreen(navController) allInRegisterScreen(navController)
allInLoginScreen(navController) allInLoginScreen(navController)
allInDashboard() allInDashboard(navController)
} }
} }
@ -149,14 +144,15 @@ private fun NavGraphBuilder.allInWelcomeScreen(
}, },
navigateToLogin = { navigateToLogin = {
navController.popUpTo(Routes.LOGIN, Routes.WELCOME) navController.popUpTo(Routes.LOGIN, Routes.WELCOME)
},
navigateToDashboard = {
navController.popUpTo(Routes.DASHBOARD, Routes.WELCOME)
} }
) )
} }
} }
private fun NavGraphBuilder.allInRegisterScreen( private fun NavGraphBuilder.allInRegisterScreen(navController: NavHostController) {
navController: NavHostController,
) {
composable(route = Routes.REGISTER) { composable(route = Routes.REGISTER) {
RegisterScreen( RegisterScreen(
navigateToDashboard = { navigateToDashboard = {
@ -169,9 +165,7 @@ private fun NavGraphBuilder.allInRegisterScreen(
} }
} }
private fun NavGraphBuilder.allInLoginScreen( private fun NavGraphBuilder.allInLoginScreen(navController: NavHostController) {
navController: NavHostController,
) {
composable(route = Routes.LOGIN) { composable(route = Routes.LOGIN) {
LoginScreen( LoginScreen(
navigateToRegister = { navigateToRegister = {
@ -184,10 +178,14 @@ private fun NavGraphBuilder.allInLoginScreen(
} }
} }
private fun NavGraphBuilder.allInDashboard() { private fun NavGraphBuilder.allInDashboard(navController: NavHostController) {
composable( composable(
route = Routes.DASHBOARD, route = Routes.DASHBOARD,
) { ) {
MainScreen() MainScreen(
navigateToWelcomeScreen = {
navController.popUpTo(Routes.WELCOME, Routes.DASHBOARD)
}
)
} }
} }

@ -11,6 +11,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.navigation.TopLevelDestination import fr.iut.alldev.allin.ui.navigation.TopLevelDestination
import fr.iut.alldev.allin.ui.navigation.drawer.components.DrawerCell import fr.iut.alldev.allin.ui.navigation.drawer.components.DrawerCell
@ -35,7 +39,8 @@ fun AllInDrawer(
bestWin: Int, bestWin: Int,
nbFriends: Int, nbFriends: Int,
navigateTo: (String) -> Unit, navigateTo: (String) -> Unit,
content: @Composable () -> Unit, logout: () -> Unit,
content: @Composable () -> Unit
) { ) {
ModalNavigationDrawer( ModalNavigationDrawer(
drawerState = drawerState, drawerState = drawerState,
@ -63,6 +68,17 @@ fun AllInDrawer(
modifier = Modifier.padding(vertical = 5.dp, horizontal = 13.dp) 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( Box(
Modifier Modifier
.fillMaxSize() .fillMaxSize()

@ -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.FieldErrorState
import fr.iut.alldev.allin.ext.containsCharacter import fr.iut.alldev.allin.ext.containsCharacter
import fr.iut.alldev.allin.ext.isEmail import fr.iut.alldev.allin.ext.isEmail
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -20,11 +21,12 @@ private const val MIN_USERNAME_SIZE = 3
@HiltViewModel @HiltViewModel
class RegisterViewModel @Inject constructor( class RegisterViewModel @Inject constructor(
private val userRepository: UserRepository private val userRepository: UserRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
var loading = mutableStateOf(false) var loading = mutableStateOf(false)
var hasError = mutableStateOf(false) private var hasError = mutableStateOf(false)
val username = mutableStateOf("") val username = mutableStateOf("")
val email = mutableStateOf("") val email = mutableStateOf("")
@ -43,6 +45,7 @@ class RegisterViewModel @Inject constructor(
passwordValidationError.value = FieldErrorState.NoError passwordValidationError.value = FieldErrorState.NoError
hasError.value = false hasError.value = false
} }
private fun verifyField( private fun verifyField(
usernameFieldName: String, usernameFieldName: String,
emailFieldName: String, emailFieldName: String,
@ -86,7 +89,6 @@ class RegisterViewModel @Inject constructor(
loading.value = true loading.value = true
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
initErrorField() initErrorField()
verifyField( verifyField(
usernameFieldName, usernameFieldName,
@ -95,11 +97,13 @@ class RegisterViewModel @Inject constructor(
) )
if (!hasError.value) { if (!hasError.value) {
try { try {
userRepository.register( userRepository
.register(
username.value, username.value,
email.value, email.value,
password.value password.value
) )
?.let { token -> keystoreManager.putToken(token) }
} catch (e: AllInAPIException) { } catch (e: AllInAPIException) {
usernameError.value = FieldErrorState.AlreadyUsed(username.value) usernameError.value = FieldErrorState.AlreadyUsed(username.value)
emailError.value = FieldErrorState.AlreadyUsed(email.value) emailError.value = FieldErrorState.AlreadyUsed(email.value)

@ -1,12 +1,23 @@
package fr.iut.alldev.allin.ui.welcome package fr.iut.alldev.allin.ui.welcome
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background 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.foundation.text.ClickableText
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInButton import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.AllInLoading
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun WelcomeScreen( fun WelcomeScreen(
navigateToRegister: () -> Unit, navigateToRegister: () -> Unit,
navigateToLogin: () -> Unit, navigateToLogin: () -> Unit,
navigateToDashboard: () -> Unit,
viewModel: WelcomeScreenViewModel = hiltViewModel()
) { ) {
val loading by remember { viewModel.loading }
LaunchedEffect(viewModel) {
viewModel.tryAutoLogin(navigateToDashboard)
}
Box( Box(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
@ -102,6 +122,9 @@ fun WelcomeScreen(
} }
} }
} }
AllInLoading(visible = loading)
} }
@Preview @Preview
@ -109,6 +132,10 @@ fun WelcomeScreen(
@Composable @Composable
private fun WelcomeScreenPreview() { private fun WelcomeScreenPreview() {
AllInTheme { AllInTheme {
WelcomeScreen(navigateToRegister = {}, navigateToLogin = {}) WelcomeScreen(
navigateToRegister = {},
navigateToLogin = {},
navigateToDashboard = {}
)
} }
} }

@ -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
}
}
}

@ -6,6 +6,7 @@
<string name="password">Mot de passe</string> <string name="password">Mot de passe</string>
<string name="confirm_password">Confirmation du mot de passe</string> <string name="confirm_password">Confirmation du mot de passe</string>
<string name="Login">Se connecter</string> <string name="Login">Se connecter</string>
<string name="Logout">Déconnexion</string>
<string name="already_have_account">Tu as déjà un compte ?</string> <string name="already_have_account">Tu as déjà un compte ?</string>
<string name="forgot_password">Mot de passe oublié ?</string> <string name="forgot_password">Mot de passe oublié ?</string>
<string name="no_account">Pas encore inscrit ?</string> <string name="no_account">Pas encore inscrit ?</string>
@ -25,6 +26,7 @@
<string name="Details">Détails</string> <string name="Details">Détails</string>
<string name="Stake">Mise</string> <string name="Stake">Mise</string>
<string name="Possible_winnings">Gains possibles</string> <string name="Possible_winnings">Gains possibles</string>
<string name="generic_error">Erreur</string>
<!--Drawer--> <!--Drawer-->
<string name="bets">Bets</string> <string name="bets">Bets</string>
@ -79,6 +81,8 @@
<string name="yes_no_bottom_text_1">Les utilisateurs devront répondre au pari avec OUI ou NON.</string> <string name="yes_no_bottom_text_1">Les utilisateurs devront répondre au pari avec OUI ou NON.</string>
<string name="yes_no_bottom_text_2">Aucune autre réponse ne sera acceptée.</string> <string name="yes_no_bottom_text_2">Aucune autre réponse ne sera acceptée.</string>
<string name="custom_answers">Réponses personnalisées</string> <string name="custom_answers">Réponses personnalisées</string>
<string name="bet_creation_error">Erreur lors de la création du bet, veuillez rééssayer.</string>
<!--Bet Page--> <!--Bet Page-->
<string name="Popular">Populaire</string> <string name="Popular">Populaire</string>
<string name="Public">Public</string> <string name="Public">Public</string>

@ -8,6 +8,7 @@
<string name="password">Password</string> <string name="password">Password</string>
<string name="confirm_password">Confirm password</string> <string name="confirm_password">Confirm password</string>
<string name="Login">Login</string> <string name="Login">Login</string>
<string name="Logout">Logout</string>
<string name="already_have_account">Already have an account ?</string> <string name="already_have_account">Already have an account ?</string>
<string name="forgot_password">Forgot password ?</string> <string name="forgot_password">Forgot password ?</string>
<string name="no_account">Don\'t have an account ?</string> <string name="no_account">Don\'t have an account ?</string>
@ -27,6 +28,7 @@
<string name="Details">Details</string> <string name="Details">Details</string>
<string name="Stake">Stake</string> <string name="Stake">Stake</string>
<string name="Possible_winnings">Possible winnings</string> <string name="Possible_winnings">Possible winnings</string>
<string name="generic_error">Error</string>
<!--Drawer--> <!--Drawer-->
<string name="bets">Bets</string> <string name="bets">Bets</string>
@ -82,6 +84,8 @@
<string name="yes_no_bottom_text_2">No other answer will be accepted.</string> <string name="yes_no_bottom_text_2">No other answer will be accepted.</string>
<string name="sport_match">Sport match</string> <string name="sport_match">Sport match</string>
<string name="custom_answers">Custom answers</string> <string name="custom_answers">Custom answers</string>
<string name="bet_creation_error">Error while creating the bet. Please try again.</string>
<!--Bet Page--> <!--Bet Page-->
<string name="Popular">Popular</string> <string name="Popular">Popular</string>
<string name="Public">Public</string> <string name="Public">Public</string>
@ -107,6 +111,7 @@
<item quantity="one">%s point at stake</item> <item quantity="one">%s point at stake</item>
<item quantity="other">%s points at stake</item> <item quantity="other">%s points at stake</item>
</plurals> </plurals>
<!--Bet status--> <!--Bet status-->
<string name="bet_status_finished">Finished !</string> <string name="bet_status_finished">Finished !</string>
<string name="bet_status_in_progress">In progress…</string> <string name="bet_status_in_progress">In progress…</string>

@ -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.CheckUser
import fr.iut.alldev.allin.data.api.model.RequestUser 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 fr.iut.alldev.allin.data.api.model.ResponseUser
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
interface AllInApi { interface AllInApi {
@POST("users/login") @POST("users/login")
suspend fun login( suspend fun login(@Body body: CheckUser): ResponseUser
@Body body: CheckUser
): ResponseUser
@POST("users/register") @POST("users/register")
suspend fun register( suspend fun register(@Body body: RequestUser): ResponseUser
@Body body: RequestUser
): ResponseUser @GET("users/token")
suspend fun login(@Header("Authorization") token: String): ResponseUser
@POST("bets/add")
suspend fun createBet(@Body body: ResponseBet)
} }

@ -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<String>,
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()
)
}
}
}

@ -19,6 +19,7 @@ data class ResponseUser(
val username: String, val username: String,
val email: String, val email: String,
var nbCoins: Int, var nbCoins: Int,
var token: String? = null,
) { ) {
fun toUser() = User( fun toUser() = User(
username = username, username = username,

@ -1,12 +1,29 @@
package fr.iut.alldev.allin.data.model.bet package fr.iut.alldev.allin.data.model.bet
import fr.iut.alldev.allin.data.api.model.ResponseBet
import java.time.ZonedDateTime import java.time.ZonedDateTime
abstract class Bet( abstract class Bet(
open val id: Int? = null,
open val creator: String,
open val theme: String, open val theme: String,
open val phrase: String, open val phrase: String,
open val endRegisterDate: ZonedDateTime, open val endRegisterDate: ZonedDateTime,
open val endBetDate: ZonedDateTime, open val endBetDate: ZonedDateTime,
open val isPublic: Boolean, open val isPublic: Boolean,
open val betStatus: BetStatus, open val betStatus: BetStatus,
) {
abstract fun getResponses(): List<String>
fun toResponseBet(): ResponseBet {
return ResponseBet(
id = id,
theme = theme,
sentenceBet = phrase,
endRegistration = endRegisterDate,
endBet = endBetDate,
isPrivate = !isPublic,
response = getResponses(),
createdBy = creator
) )
}
}

@ -6,6 +6,7 @@ class BetFactory {
companion object { companion object {
fun createBet( fun createBet(
betType: BetType, betType: BetType,
creator: String,
theme: String, theme: String,
phrase: String, phrase: String,
endRegisterDate: ZonedDateTime, endRegisterDate: ZonedDateTime,
@ -20,6 +21,7 @@ class BetFactory {
BetType.YES_NO -> { BetType.YES_NO -> {
YesNoBet( YesNoBet(
theme = theme, theme = theme,
creator = creator,
phrase = phrase, phrase = phrase,
endRegisterDate = endRegisterDate, endRegisterDate = endRegisterDate,
endBetDate = endBetDate, endBetDate = endBetDate,
@ -31,6 +33,7 @@ class BetFactory {
BetType.MATCH -> { BetType.MATCH -> {
MatchBet( MatchBet(
theme = theme, theme = theme,
creator = creator,
phrase = phrase, phrase = phrase,
endRegisterDate = endRegisterDate, endRegisterDate = endRegisterDate,
endBetDate = endBetDate, endBetDate = endBetDate,
@ -45,6 +48,7 @@ class BetFactory {
BetType.CUSTOM -> { BetType.CUSTOM -> {
CustomBet( CustomBet(
theme = theme, theme = theme,
creator = creator,
phrase = phrase, phrase = phrase,
endRegisterDate = endRegisterDate, endRegisterDate = endRegisterDate,
endBetDate = endBetDate, endBetDate = endBetDate,

@ -3,18 +3,24 @@ package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime import java.time.ZonedDateTime
data class CustomBet( data class CustomBet(
override val id: Int? = null,
override val creator: String,
override val theme: String, override val theme: String,
override val phrase: String, override val phrase: String,
override val endRegisterDate: ZonedDateTime, override val endRegisterDate: ZonedDateTime,
override val endBetDate: ZonedDateTime, override val endBetDate: ZonedDateTime,
override val isPublic: Boolean, override val isPublic: Boolean,
override val betStatus: BetStatus, override val betStatus: BetStatus,
val possibleAnswers: Set<String> val possibleAnswers: Set<String>,
) : Bet( ) : Bet(
id,
creator,
theme, theme,
phrase, phrase,
endRegisterDate, endRegisterDate,
endBetDate, endBetDate,
isPublic, isPublic,
betStatus betStatus
) ) {
override fun getResponses(): List<String> = possibleAnswers.toList()
}

@ -3,6 +3,8 @@ package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime import java.time.ZonedDateTime
data class MatchBet( data class MatchBet(
override val id: Int? = null,
override val creator: String,
override val theme: String, override val theme: String,
override val phrase: String, override val phrase: String,
override val endRegisterDate: ZonedDateTime, override val endRegisterDate: ZonedDateTime,
@ -10,12 +12,17 @@ data class MatchBet(
override val isPublic: Boolean, override val isPublic: Boolean,
override val betStatus: BetStatus, override val betStatus: BetStatus,
val nameTeam1: String, val nameTeam1: String,
val nameTeam2: String val nameTeam2: String,
) : Bet( ) : Bet(
id,
creator,
theme, theme,
phrase, phrase,
endRegisterDate, endRegisterDate,
endBetDate, endBetDate,
isPublic, isPublic,
betStatus betStatus
) ) {
override fun getResponses(): List<String> = listOf(nameTeam1, nameTeam2)
}

@ -3,17 +3,23 @@ package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime import java.time.ZonedDateTime
data class YesNoBet( data class YesNoBet(
override val id: Int? = null,
override val creator: String,
override val theme: String, override val theme: String,
override val phrase: String, override val phrase: String,
override val endRegisterDate: ZonedDateTime, override val endRegisterDate: ZonedDateTime,
override val endBetDate: ZonedDateTime, override val endBetDate: ZonedDateTime,
override val isPublic: Boolean, override val isPublic: Boolean,
override val betStatus: BetStatus override val betStatus: BetStatus,
) : Bet( ) : Bet(
id,
creator,
theme, theme,
phrase, phrase,
endRegisterDate, endRegisterDate,
endBetDate, endBetDate,
isPublic, isPublic,
betStatus betStatus
) ) {
override fun getResponses(): List<String> = listOf("Yes", "No")
}

@ -4,10 +4,7 @@ import fr.iut.alldev.allin.data.model.bet.Bet
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
abstract class BetRepository { abstract class BetRepository {
abstract suspend fun createBet( abstract suspend fun createBet(bet: Bet)
bet: Bet,
)
abstract suspend fun getHistory(): Flow<List<Bet>> abstract suspend fun getHistory(): Flow<List<Bet>>
abstract suspend fun getCurrentBets(): Flow<List<Bet>> abstract suspend fun getCurrentBets(): Flow<List<Bet>>
} }

@ -7,10 +7,13 @@ abstract class UserRepository {
abstract suspend fun login( abstract suspend fun login(
username: String, username: String,
password: String password: String
) ): String?
abstract suspend fun login(token: String): String?
abstract suspend fun register( abstract suspend fun register(
username: String, username: String,
email: String, email: String,
password: String password: String
) ): String?
} }

@ -8,24 +8,22 @@ import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import timber.log.Timber
import java.time.ZonedDateTime import java.time.ZonedDateTime
import javax.inject.Inject import javax.inject.Inject
class BetRepositoryImpl @Inject constructor( class BetRepositoryImpl @Inject constructor(
private val api: AllInApi, private val api: AllInApi
) : BetRepository() { ) : BetRepository() {
override suspend fun createBet(bet: Bet) { override suspend fun createBet(bet: Bet) {
// TODO api.createBet(bet.toResponseBet())
Timber.d("$bet")
} }
override suspend fun getHistory(): Flow<List<Bet>> { override suspend fun getHistory(): Flow<List<Bet>> {
// TODO // TODO
return flowOf( return flowOf(
listOf( listOf(
YesNoBet( YesNoBet(
creator = "Lucas",
theme = "Theme", theme = "Theme",
phrase = "Bet phrase 1", phrase = "Bet phrase 1",
endRegisterDate = ZonedDateTime.now().minusDays(4), endRegisterDate = ZonedDateTime.now().minusDays(4),
@ -34,6 +32,7 @@ class BetRepositoryImpl @Inject constructor(
betStatus = BetStatus.Finished(BetFinishedStatus.WON) betStatus = BetStatus.Finished(BetFinishedStatus.WON)
), ),
YesNoBet( YesNoBet(
creator = "Lucas",
theme = "Theme", theme = "Theme",
phrase = "Bet phrase 2", phrase = "Bet phrase 2",
endRegisterDate = ZonedDateTime.now().minusDays(3), endRegisterDate = ZonedDateTime.now().minusDays(3),
@ -42,6 +41,7 @@ class BetRepositoryImpl @Inject constructor(
betStatus = BetStatus.Finished(BetFinishedStatus.LOST) betStatus = BetStatus.Finished(BetFinishedStatus.LOST)
), ),
YesNoBet( YesNoBet(
creator = "Lucas",
theme = "Theme", theme = "Theme",
phrase = "Bet phrase 3", phrase = "Bet phrase 3",
endRegisterDate = ZonedDateTime.now().minusDays(15), endRegisterDate = ZonedDateTime.now().minusDays(15),
@ -58,6 +58,7 @@ class BetRepositoryImpl @Inject constructor(
return flowOf( return flowOf(
listOf( listOf(
YesNoBet( YesNoBet(
creator = "Lucas",
theme = "Theme", theme = "Theme",
phrase = "Bet phrase 1", phrase = "Bet phrase 1",
endRegisterDate = ZonedDateTime.now().plusDays(5), endRegisterDate = ZonedDateTime.now().plusDays(5),
@ -66,6 +67,7 @@ class BetRepositoryImpl @Inject constructor(
betStatus = BetStatus.InProgress betStatus = BetStatus.InProgress
), ),
YesNoBet( YesNoBet(
creator = "Lucas",
theme = "Theme", theme = "Theme",
phrase = "Bet phrase 2", phrase = "Bet phrase 2",
endRegisterDate = ZonedDateTime.now().plusDays(1), endRegisterDate = ZonedDateTime.now().plusDays(1),
@ -74,6 +76,7 @@ class BetRepositoryImpl @Inject constructor(
betStatus = BetStatus.InProgress betStatus = BetStatus.InProgress
), ),
YesNoBet( YesNoBet(
creator = "Lucas",
theme = "Theme", theme = "Theme",
phrase = "Bet phrase 3", phrase = "Bet phrase 3",
endRegisterDate = ZonedDateTime.now().plusDays(3), endRegisterDate = ZonedDateTime.now().plusDays(3),

@ -10,25 +10,34 @@ class UserRepositoryImpl @Inject constructor(
private val api: AllInApi, private val api: AllInApi,
) : UserRepository() { ) : UserRepository() {
override suspend fun login(username: String, password: String) { override suspend fun login(username: String, password: String): String? {
currentUser = api.login( val response = api.login(
CheckUser( CheckUser(
login = username, login = username,
password = password 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) { override suspend fun register(username: String, email: String, password: String): String? {
currentUser = api.register( val response = api.register(
RequestUser( RequestUser(
username = username, username = username,
email = email, email = email,
password = password, password = password,
nbCoins = 0 nbCoins = 0
) )
).toUser() )
currentUser = response.toUser()
return response.token
} }
} }

@ -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<ZonedDateTime> {
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())
}
}

@ -10,6 +10,7 @@ kotlin = "1.9.20"
androidxCore = "1.12.0" androidxCore = "1.12.0"
androidxActivity = "1.8.2" androidxActivity = "1.8.2"
androidxSecurity = "1.0.0"
composeBom = "2023.10.01" composeBom = "2023.10.01"
composePreview = "1.6.0-beta03" composePreview = "1.6.0-beta03"
@ -39,6 +40,7 @@ resgenPlugin = "2.5"
# Android # Android
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidxCore" } androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
androidx-activity = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" } androidx-activity = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" }
androidx-security = { module = "androidx.security:security-crypto", version.ref = "androidxSecurity" }
# Lifecycle # Lifecycle
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "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-junit = { group = "junit", name = "junit", version.ref = "junit" }
test-androidx-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExtJunit" } 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" } 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
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } 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] [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"] 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"] 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"]

Loading…
Cancel
Save