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

pull/5/head
avalin 9 months ago
parent a35e8a1d6b
commit 9bf19b70a2

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".AllInApplication"

@ -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 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.data.repository.UserRepository
import fr.iut.alldev.allin.ext.FieldErrorState
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.launch
@ -19,9 +18,9 @@ import javax.inject.Inject
@HiltViewModel
class BetCreationViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager
private val keystoreManager: AllInKeystoreManager,
private val userRepository: UserRepository
) : ViewModel() {
private var hasError = mutableStateOf(false)
@ -98,21 +97,23 @@ class BetCreationViewModel @Inject constructor(
if (!hasError.value) {
try {
val bet = BetFactory.createBet(
id = "",
betType = selectedBetType.value,
theme = theme.value,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
nameTeam1 = "",
nameTeam2 = "",
possibleAnswers = listOf(),
creator = currentUser.username
)
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess()
userRepository.currentUser.value?.let { currentUser ->
val bet = BetFactory.createBet(
id = "",
betType = selectedBetType.value,
theme = theme.value,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
nameTeam1 = "",
nameTeam2 = "",
possibleAnswers = listOf(),
creator = currentUser.username
)
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess()
} ?: onError()
} catch (e: AllInAPIException) {
Timber.e(e)
onError()

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

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

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

@ -1,17 +1,12 @@
package fr.iut.alldev.allin.data.di
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import javax.inject.Qualifier
internal val json by lazy {
@ -33,13 +28,4 @@ internal object NetworkModule {
@Provides
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
import fr.iut.alldev.allin.data.model.User
import kotlinx.coroutines.flow.MutableStateFlow
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(token: String): String?

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

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