Filters on bets screen
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 9 months ago
parent 4ce266860f
commit 4612f88c69

@ -0,0 +1,18 @@
package fr.iut.alldev.allin.ext
import androidx.annotation.StringRes
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.model.bet.BetFilter.FINISHED
import fr.iut.alldev.allin.data.model.bet.BetFilter.INVITATION
import fr.iut.alldev.allin.data.model.bet.BetFilter.IN_PROGRESS
import fr.iut.alldev.allin.data.model.bet.BetFilter.PUBLIC
@StringRes
fun BetFilter.textId() =
when (this) {
PUBLIC -> R.string.Public
INVITATION -> R.string.Invitation
IN_PROGRESS -> R.string.Current
FINISHED -> R.string.Finished
}

@ -23,10 +23,9 @@ import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
@ -35,10 +34,11 @@ import androidx.compose.ui.platform.LocalDensity
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.BetFilter
import fr.iut.alldev.allin.ext.textId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
@ -51,6 +51,7 @@ fun BetScreen(
selectBet: (Bet, Boolean) -> Unit,
) {
val bets by viewModel.bets.collectAsState()
val filters by viewModel.filters.collectAsState()
val refreshing by viewModel.isRefreshing.collectAsState()
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() })
@ -62,7 +63,7 @@ fun BetScreen(
.padding(top = with(LocalDensity.current) {
progressAnimation.toDp()
}),
contentPadding = WindowInsets.navigationBars.asPaddingValues()
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
item {
Box(
@ -98,18 +99,24 @@ fun BetScreen(
horizontalArrangement = Arrangement.spacedBy(9.dp),
contentPadding = PaddingValues(horizontal = 23.dp)
) {
items(items) {
var isSelected by remember { mutableStateOf(false) }
items(BetFilter.entries) {
val isSelected by remember {
derivedStateOf {
filters.contains(it)
}
}
AllInChip(
text = stringResource(id = it),
text = stringResource(id = it.textId()),
isSelected = isSelected,
onClick = {
isSelected = !isSelected
})
onClick = { viewModel.toggleFilter(it) }
)
}
}
}
itemsIndexed(bets) { idx, it ->
itemsIndexed(
items = bets,
key = { _, it -> it.id }
) { idx, it ->
BetScreenCard(
creator = it.creator,
category = it.theme,
@ -119,18 +126,13 @@ fun BetScreen(
players = List(3) { null },
onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) },
modifier = Modifier.padding(horizontal = 23.dp)
modifier = Modifier
.animateItemPlacement()
.padding(horizontal = 23.dp)
)
if (idx != bets.lastIndex) {
Spacer(modifier = Modifier.height(24.dp))
}
}
}
}
val items = listOf(
R.string.Public,
R.string.Invitation,
R.string.Current,
R.string.Finished
)
}

@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@ -39,22 +41,48 @@ class BetViewModel @Inject constructor(
)
}
private val _filters: MutableStateFlow<List<BetFilter>> by lazy {
MutableStateFlow(BetFilter.entries)
}
val filters by lazy { _filters.asStateFlow() }
init {
viewModelScope.launch {
refreshData()
refreshBets()
filters.collect { refreshBets() }
}
}
private suspend fun refreshData() {
runCatching {
_bets.emit(betRepository.getAllBets(keystoreManager.getTokenOrEmpty()))
fun toggleFilter(filter: BetFilter) {
viewModelScope.launch {
_filters.emit(
if (_filters.value.contains(filter)) {
_filters.value - filter
} else {
_filters.value + filter
}
)
}
}
private suspend fun refreshBets() {
try {
_bets.emit(
betRepository.getAllBets(
token = keystoreManager.getTokenOrEmpty(),
filters = filters.value
)
)
} catch (e: Exception) {
Timber.e(e)
}
}
fun refresh() {
viewModelScope.launch {
_isRefreshing.emit(true)
refreshData()
refreshBets()
_isRefreshing.emit(false)
}
}

@ -178,7 +178,7 @@ private fun BetStatusParticipationBottomSheetContent(
color = AllInTheme.colors.onBackground
)
AllInCoinCount(
amount = stake?.let { (it + (it * odds)).roundToInt() } ?: 0,
amount = stake?.let { (it * odds).roundToInt() } ?: 0,
color = AllInTheme.colors.onBackground
)
}

@ -33,7 +33,10 @@ class LoginViewModel @Inject constructor(
userRepository
.login(username.value, password.value)
?.let { token -> keystoreManager.putToken(token) }
navigateToDashboard()
withContext(Dispatchers.Main) {
navigateToDashboard()
}
} catch (e: Exception) {
hasError.value = true

@ -106,6 +106,10 @@ class RegisterViewModel @Inject constructor(
password.value
)
?.let { token -> keystoreManager.putToken(token) }
withContext(Dispatchers.Main) {
navigateToDashboard()
}
} catch (e: Exception) {
when {
e is HttpException && e.code() == 409 -> {
@ -122,9 +126,6 @@ class RegisterViewModel @Inject constructor(
}
}
}
if (!hasError.value) {
navigateToDashboard()
}
loading.value = false
}
}

@ -2,6 +2,7 @@ package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.model.CheckUser
import fr.iut.alldev.allin.data.api.model.RequestBet
import fr.iut.alldev.allin.data.api.model.RequestBetFilters
import fr.iut.alldev.allin.data.api.model.RequestParticipation
import fr.iut.alldev.allin.data.api.model.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet
@ -19,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.RequestBody
import java.time.Duration
import java.time.ZonedDateTime
import java.util.UUID
@ -133,7 +135,7 @@ class MockAllInApi : AllInApi {
} else throw MockAllInApiException("Gift already taken today")
}
override suspend fun getAllBets(token: String): List<ResponseBet> {
override suspend fun getAllBets(token: String, body: RequestBetFilters): List<ResponseBet> {
getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
return mockBets
}
@ -155,13 +157,13 @@ class MockAllInApi : AllInApi {
}
}
override suspend fun confirmBet(token: String, id: String, value: String) {
override suspend fun confirmBet(token: String, id: String, value: RequestBody) {
getUserFromToken(token) ?: throw MockAllInApiException("Invalid login/password.")
val bet = mockBets.find { it.id == id } ?: throw MockAllInApiException("Unauthorized")
mockResults.add(
ResponseBetResult(
betId = id,
result = value
result = value.toString()
)
)
mockBets[mockBets.indexOf(bet)] = bet.copy(status = BetStatus.FINISHED)

@ -2,12 +2,16 @@ package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.model.CheckUser
import fr.iut.alldev.allin.data.api.model.RequestBet
import fr.iut.alldev.allin.data.api.model.RequestBetFilters
import fr.iut.alldev.allin.data.api.model.RequestParticipation
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.ResponseBetDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetResultDetail
import fr.iut.alldev.allin.data.api.model.ResponseUser
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
@ -17,6 +21,9 @@ import retrofit2.http.Path
interface AllInApi {
companion object {
fun String.formatBearerToken() = "Bearer $this"
fun String.asRequestBody() =
this.toRequestBody("text/plain".toMediaTypeOrNull())
}
@POST("users/login")
@ -34,8 +41,11 @@ interface AllInApi {
@GET("users/gift")
suspend fun dailyGift(@Header("Authorization") token: String): Int
@GET("bets/gets")
suspend fun getAllBets(@Header("Authorization") token: String): List<ResponseBet>
@POST("bets/gets")
suspend fun getAllBets(
@Header("Authorization") token: String,
@Body body: RequestBetFilters
): List<ResponseBet>
@GET("bets/toConfirm")
suspend fun getToConfirm(@Header("Authorization") token: String): List<ResponseBetDetail>
@ -44,7 +54,7 @@ interface AllInApi {
suspend fun confirmBet(
@Header("Authorization") token: String,
@Path("id") id: String,
@Body value: String
@Body value: RequestBody
)
@GET("betdetail/get/{id}")
@ -53,7 +63,7 @@ interface AllInApi {
@Path("id") id: String
): ResponseBetDetail
@GET("bets/getCurrent")
@GET("bets/current")
suspend fun getBetCurrent(
@Header("Authorization") token: String
): List<ResponseBetDetail>

@ -2,6 +2,7 @@ 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.BetFilter
import fr.iut.alldev.allin.data.model.bet.BetResult
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.BetStatus
@ -156,4 +157,9 @@ data class ResponseBetResultDetail(
amount = amount,
won = won
)
}
}
@Serializable
data class RequestBetFilters(
val filters: List<BetFilter>
)

@ -0,0 +1,8 @@
package fr.iut.alldev.allin.data.model.bet
enum class BetFilter {
PUBLIC,
INVITATION,
IN_PROGRESS,
FINISHED
}

@ -1,6 +1,7 @@
package fr.iut.alldev.allin.data.repository
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
@ -11,7 +12,7 @@ abstract class BetRepository {
abstract suspend fun getCurrentBets(token: String): List<BetDetail>
abstract suspend fun getBet(id: String, token: String): BetDetail
abstract suspend fun participateToBet(participation: Participation, token: String)
abstract suspend fun getAllBets(token: String): List<Bet>
abstract suspend fun getAllBets(token: String, filters: List<BetFilter>): List<Bet>
abstract suspend fun confirmBet(token: String, id: String, response: String)
abstract suspend fun getToConfirm(token: String): List<BetDetail>
abstract suspend fun getWon(token: String): List<BetResultDetail>

@ -1,8 +1,11 @@
package fr.iut.alldev.allin.data.repository.impl
import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.api.AllInApi.Companion.asRequestBody
import fr.iut.alldev.allin.data.api.AllInApi.Companion.formatBearerToken
import fr.iut.alldev.allin.data.api.model.RequestBetFilters
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.model.bet.BetResultDetail
import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
@ -45,8 +48,11 @@ class BetRepositoryImpl @Inject constructor(
)
}
override suspend fun getAllBets(token: String): List<Bet> =
api.getAllBets(token.formatBearerToken()).map { it.toBet() }
override suspend fun getAllBets(token: String, filters: List<BetFilter>): List<Bet> =
api.getAllBets(
token.formatBearerToken(),
RequestBetFilters(filters)
).map { it.toBet() }
override suspend fun getToConfirm(token: String): List<BetDetail> =
api.getToConfirm(token.formatBearerToken()).map { it.toBetDetail() }
@ -55,7 +61,7 @@ class BetRepositoryImpl @Inject constructor(
api.getWon(token.formatBearerToken()).map { it.toBetResultDetail() }
override suspend fun confirmBet(token: String, id: String, response: String) {
api.confirmBet(token.formatBearerToken(), id, response)
api.confirmBet(token.formatBearerToken(), id, response.asRequestBody())
}
}

@ -12,7 +12,7 @@ androidxCore = "1.12.0"
androidxActivity = "1.8.2"
androidxSecurity = "1.1.0-alpha06"
composeBom = "2024.04.00"
composeBom = "2024.05.00"
composeCompiler = "1.5.5"
composePreview = "1.6.5"
composeNavigation = "2.7.6"

Loading…
Cancel
Save