Make CurrentUser and User coins system cleaner
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 1 year ago
parent a35e8a1d6b
commit 9bf19b70a2

@ -1,20 +0,0 @@
package fr.iut.alldev.allin.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.repository.UserRepository
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AllInCurrentUser
@Module
@InstallIn(SingletonComponent::class)
internal object CurrentUserModule {
@AllInCurrentUser
@Provides
fun provideUser(userRepository: UserRepository) = userRepository.currentUser
}

@ -5,11 +5,10 @@ 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.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.data.repository.UserRepository
import fr.iut.alldev.allin.ext.FieldErrorState import fr.iut.alldev.allin.ext.FieldErrorState
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -19,9 +18,9 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class BetCreationViewModel @Inject constructor( class BetCreationViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository, private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager private val keystoreManager: AllInKeystoreManager,
private val userRepository: UserRepository
) : ViewModel() { ) : ViewModel() {
private var hasError = mutableStateOf(false) private var hasError = mutableStateOf(false)
@ -98,6 +97,7 @@ class BetCreationViewModel @Inject constructor(
if (!hasError.value) { if (!hasError.value) {
try { try {
userRepository.currentUser.value?.let { currentUser ->
val bet = BetFactory.createBet( val bet = BetFactory.createBet(
id = "", id = "",
betType = selectedBetType.value, betType = selectedBetType.value,
@ -113,6 +113,7 @@ class BetCreationViewModel @Inject constructor(
) )
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty()) betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess() onSuccess()
} ?: onError()
} catch (e: AllInAPIException) { } catch (e: AllInAPIException) {
Timber.e(e) Timber.e(e)
onError() onError()

@ -4,13 +4,11 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -37,7 +35,7 @@ fun BetStatusBottomSheet(
sheetVisibility: Boolean, sheetVisibility: Boolean,
sheetBackVisibility: Boolean, sheetBackVisibility: Boolean,
betDetail: BetDetail?, betDetail: BetDetail?,
userCoinAmount: MutableIntState, userCoinAmount: Int,
onParticipate: (stake: Int, response: String) -> Unit, onParticipate: (stake: Int, response: String) -> Unit,
onDismiss: () -> Unit, onDismiss: () -> Unit,
participateSheetVisibility: Boolean, participateSheetVisibility: Boolean,
@ -83,7 +81,7 @@ fun BetStatusBottomSheet(
state.hasExpandedState, state.hasExpandedState,
odds = betDetail.answers.getOrNull(selectedAnswer)?.odds ?: 1f, odds = betDetail.answers.getOrNull(selectedAnswer)?.odds ?: 1f,
betPhrase = betDetail.bet.phrase, betPhrase = betDetail.bet.phrase,
coinAmount = userCoinAmount.intValue, coinAmount = userCoinAmount,
onDismiss = { setParticipateSheetVisibility(false) }, onDismiss = { setParticipateSheetVisibility(false) },
state = rememberModalBottomSheetState(skipPartiallyExpanded = true), state = rememberModalBottomSheetState(skipPartiallyExpanded = true),
elements = elements, elements = elements,
@ -92,7 +90,7 @@ fun BetStatusBottomSheet(
setStake = { stake = it }, setStake = { stake = it },
setElement = { idx -> selectedAnswer = idx }, setElement = { idx -> selectedAnswer = idx },
enabled = stake != null && enabled = stake != null &&
(stake ?: 0) <= userCoinAmount.intValue (stake ?: 0) <= userCoinAmount
) { ) {
stake?.let { stake -> stake?.let { stake ->
onParticipate( onParticipate(

@ -23,6 +23,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@ -58,7 +59,8 @@ fun MainScreen(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val (loading, setLoading) = remember { mainViewModel.loading } val (loading, setLoading) = remember { mainViewModel.loading }
val currentUser = remember { mainViewModel.currentUserState } val currentUser by mainViewModel.currentUser.collectAsStateWithLifecycle()
val userCoins by mainViewModel.userCoins.collectAsStateWithLifecycle()
val selectedBet by remember { mainViewModel.selectedBet } val selectedBet by remember { mainViewModel.selectedBet }
val statusVisibility = remember { mutableStateOf(false) } val statusVisibility = remember { mutableStateOf(false) }
val sheetBackVisibility = remember { mutableStateOf(false) } val sheetBackVisibility = remember { mutableStateOf(false) }
@ -113,7 +115,7 @@ fun MainScreen(
drawerState = drawerState, drawerState = drawerState,
destinations = topLevelDestinations, destinations = topLevelDestinations,
scope = scope, scope = scope,
username = currentUser.user.username, username = currentUser?.username ?: "",
nbFriends = 5, nbFriends = 5,
nbBets = 35, nbBets = 35,
bestWin = 362, bestWin = 362,
@ -122,13 +124,13 @@ fun MainScreen(
navController.popUpTo(route, startDestination) navController.popUpTo(route, startDestination)
}, },
logout = { logout = {
mainViewModel.deleteToken() mainViewModel.onLogout()
navigateToWelcomeScreen() navigateToWelcomeScreen()
} }
) { ) {
AllInScaffold( AllInScaffold(
onMenuClicked = { scope.launch { drawerState.open() } }, onMenuClicked = { scope.launch { drawerState.open() } },
coinAmount = currentUser.userCoins.intValue, coinAmount = userCoins ?: 0,
drawerState = drawerState, drawerState = drawerState,
snackbarHostState = snackbarHostState snackbarHostState = snackbarHostState
) { ) {
@ -174,7 +176,7 @@ fun MainScreen(
onDismiss = { setStatusVisibility(false) }, onDismiss = { setStatusVisibility(false) },
betDetail = selectedBet, betDetail = selectedBet,
displayBet = { betStatusDisplayer.DisplayBet(it) }, displayBet = { betStatusDisplayer.DisplayBet(it) },
userCoinAmount = mainViewModel.currentUserState.userCoins, userCoinAmount = userCoins ?: 0,
onParticipate = { stake, response -> mainViewModel.participateToBet(stake, response) }, onParticipate = { stake, response -> mainViewModel.participateToBet(stake, response) },
participateSheetVisibility = participateSheetVisibility, participateSheetVisibility = participateSheetVisibility,
setParticipateSheetVisibility = setParticipateSheetVisibility setParticipateSheetVisibility = setParticipateSheetVisibility

@ -1,42 +1,38 @@
package fr.iut.alldev.allin.ui.main package fr.iut.alldev.allin.ui.main
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf 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.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.data.model.bet.Participation import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
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.data.repository.UserRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType
import fr.iut.alldev.allin.ui.main.event.AllInEvent import fr.iut.alldev.allin.ui.main.event.AllInEvent
import fr.iut.alldev.allin.ui.main.event.ToConfirmBet import fr.iut.alldev.allin.ui.main.event.ToConfirmBet
import fr.iut.alldev.allin.ui.main.event.WonBet import fr.iut.alldev.allin.ui.main.event.WonBet
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
class UserState(val user: User) {
val userCoins = mutableIntStateOf(user.coins)
}
@HiltViewModel @HiltViewModel
class MainViewModel @Inject constructor( class MainViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User, private val userRepository: UserRepository,
private val betRepository: BetRepository, private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
var loading = mutableStateOf(false) var loading = mutableStateOf(false)
val currentUserState = UserState(currentUser) val currentUser = userRepository.currentUser.asStateFlow()
val userCoins = userRepository.userCoins.asStateFlow()
val selectedBet = mutableStateOf<BetDetail?>(null) val selectedBet = mutableStateOf<BetDetail?>(null)
val dismissedEvents = mutableStateListOf<AllInEvent>() val dismissedEvents = mutableStateListOf<AllInEvent>()
val events = mutableStateListOf<AllInEvent>() val events = mutableStateListOf<AllInEvent>()
@ -67,11 +63,13 @@ class MainViewModel @Inject constructor(
} }
) )
}) })
addAll(betRepository.getWon(token).map { result -> addAll(betRepository.getWon(token).mapNotNull { result ->
currentUser.value?.let {
WonBet( WonBet(
user = currentUser, user = it,
betResult = result betResult = result
) )
}
}) })
}.filter { it !in dismissedEvents } }.filter { it !in dismissedEvents }
) )
@ -84,9 +82,21 @@ class MainViewModel @Inject constructor(
} }
} }
fun deleteToken() { fun onLogout() {
viewModelScope.launch { viewModelScope.launch {
keystoreManager.deleteToken() keystoreManager.deleteToken()
userRepository.currentUser.emit(null)
userRepository.userCoins.emit(null)
}
}
private fun decreaseCoins(amount: Int) {
viewModelScope.launch {
userRepository.userCoins.value?.let {
val newAmount = it - amount
userRepository.userCoins.emit(newAmount)
}
} }
} }
@ -94,16 +104,18 @@ class MainViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
loading.value = true loading.value = true
currentUserState.userCoins.intValue -= stake currentUser.value?.let { user ->
decreaseCoins(stake)
selectedBet.value?.bet?.let { selectedBet.value?.bet?.let {
val participation = Participation( val participation = Participation(
betId = it.id, betId = it.id,
username = currentUser.username, username = user.username,
response = response, response = response,
stake = stake stake = stake
) )
betRepository.participateToBet(participation, keystoreManager.getTokenOrEmpty()) betRepository.participateToBet(participation, keystoreManager.getTokenOrEmpty())
} }
}
loading.value = false loading.value = false
} }
} }

@ -1,17 +1,12 @@
package fr.iut.alldev.allin.data.di package fr.iut.alldev.allin.data.di
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import javax.inject.Qualifier import javax.inject.Qualifier
internal val json by lazy { internal val json by lazy {
@ -33,13 +28,4 @@ internal object NetworkModule {
@Provides @Provides
fun provideUrl(): HttpUrl = "https://codefirst.iut.uca.fr/containers/AllDev-api/".toHttpUrl() fun provideUrl(): HttpUrl = "https://codefirst.iut.uca.fr/containers/AllDev-api/".toHttpUrl()
@OptIn(ExperimentalSerializationApi::class)
fun createRetrofit(url: HttpUrl, okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(
json.asConverterFactory("application/json".toMediaType())
)
.baseUrl(url)
.build()
} }

@ -1,9 +1,18 @@
package fr.iut.alldev.allin.data.repository package fr.iut.alldev.allin.data.repository
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import kotlinx.coroutines.flow.MutableStateFlow
abstract class UserRepository { abstract class UserRepository {
lateinit var currentUser: User
val currentUser by lazy { MutableStateFlow<User?>(null) }
val userCoins by lazy { MutableStateFlow<Int?>(null) }
internal suspend fun updateUser(user: User) {
currentUser.emit(user)
userCoins.emit(user.coins)
}
abstract suspend fun login(username: String, password: String): String? abstract suspend fun login(username: String, password: String): String?
abstract suspend fun login(token: String): String? abstract suspend fun login(token: String): String?

@ -8,7 +8,7 @@ import fr.iut.alldev.allin.data.repository.UserRepository
import javax.inject.Inject import javax.inject.Inject
class UserRepositoryImpl @Inject constructor( class UserRepositoryImpl @Inject constructor(
private val api: AllInApi, private val api: AllInApi
) : UserRepository() { ) : UserRepository() {
override suspend fun login(username: String, password: String): String? { override suspend fun login(username: String, password: String): String? {
@ -18,13 +18,13 @@ class UserRepositoryImpl @Inject constructor(
password = password password = password
) )
) )
currentUser = response.toUser() updateUser(response.toUser())
return response.token return response.token
} }
override suspend fun login(token: String): String? { override suspend fun login(token: String): String? {
val response = api.login(token = token.formatBearerToken()) val response = api.login(token = token.formatBearerToken())
currentUser = response.toUser() updateUser(response.toUser())
return response.token return response.token
} }
@ -37,7 +37,7 @@ class UserRepositoryImpl @Inject constructor(
password = password password = password
) )
) )
currentUser = response.toUser() updateUser(response.toUser())
return response.token return response.token
} }
} }

@ -1,12 +1,17 @@
package fr.iut.alldev.allin.data.di package fr.iut.alldev.allin.data.di
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.api.AllInApi import fr.iut.alldev.allin.data.api.AllInApi
import kotlinx.serialization.ExperimentalSerializationApi
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.create
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -14,8 +19,11 @@ import javax.inject.Singleton
internal object ApiModule { internal object ApiModule {
@Provides @Provides
@Singleton @Singleton
fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi { @OptIn(ExperimentalSerializationApi::class)
val retrofit = NetworkModule.createRetrofit(url = url, okHttpClient = okHttpClient) fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi = Retrofit.Builder()
return retrofit.create(AllInApi::class.java) .client(okHttpClient)
} .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.baseUrl(url)
.build()
.create<AllInApi>()
} }
Loading…
Cancel
Save