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"]