Sprint_3 #3

Merged
arthur.valin merged 3 commits from Sprint_3 into master 1 year ago

@ -10,7 +10,7 @@ plugins {
}
// Keystore
val keystorePropertiesFile = rootProject.file("app/keys/keystore.properties")
val keystorePropertiesFile = rootProject.file("keys/keystore.properties")
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
@ -33,11 +33,16 @@ android {
}
}
signingConfigs {
named(BuildType.DEBUG.name) {
storeFile = file(keystoreProperties["debugStoreFile"].toString())
}
register(BuildType.RELEASE.name) {
storeFile = file(keystoreProperties["store"].toString())
storePassword = keystoreProperties["password"].toString()
keyPassword = keystoreProperties["password"].toString()
storeFile = file(keystoreProperties["releaseStoreFile"].toString())
storePassword = keystoreProperties["passwordRelease"].toString()
keyAlias = keystoreProperties["aliasRelease"].toString()
keyPassword = keystoreProperties["passwordRelease"].toString()
}
}
@ -102,5 +107,6 @@ dependencies {
androidTestImplementation(libs.test.androidx.junit)
androidTestImplementation(libs.hilt.androidTesting)
kaptAndroidTest(libs.hilt.androidCompiler)
androidTestImplementation(libs.ui.test.junit)
debugImplementation(libs.ui.test.manifest)
}

Binary file not shown.

@ -1,2 +0,0 @@
store=keys/keystore.jks
password=placeYourBets

Binary file not shown.

@ -18,4 +18,19 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile
# Android.Security.Crypto
-dontwarn com.google.api.client.http.**
-dontwarn com.google.errorprone.**
-dontwarn org.joda.time.**
-keep,allowoptimization class com.google.crypto.tink.** { *; }
-keep,allowoptimization class com.google.errorprone.** { *; }
-keep,allowoptimization class com.google.api.client.http.** { *; }
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation

@ -1,6 +1,7 @@
package fr.iut.alldev.allin.test.mock
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.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import java.time.ZonedDateTime
@ -14,7 +15,8 @@ object Bets {
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.InProgress
betStatus = BetStatus.InProgress,
creator = "creator",
),
MatchBet(
theme = "Theme",
@ -24,7 +26,23 @@ object Bets {
isPublic = true,
betStatus = BetStatus.InProgress,
nameTeam1 = "Team_1",
nameTeam2 = "Team_2"
nameTeam2 = "Team_2",
creator = "creator"
),
CustomBet(
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.InProgress,
creator = "creator",
possibleAnswers = listOf(
"Answer 1",
"Answer 2",
"Answer 3",
"Answer 4"
)
),
)
}

@ -9,8 +9,7 @@ import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.test.mock.Bets
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.vo.bet.factory.toBetVO
import fr.iut.alldev.allin.vo.bet.visitor.BetTestVisitor
import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -30,37 +29,55 @@ class BetVOTest {
}
companion object {
val visitor = BetTestVisitor()
val displayer = BetTestDisplayer()
}
@Test
fun testVisitor_shouldDisplayYesNoBetUI(){
fun testDisplayer_shouldDisplayYesNoBetUI(){
//Given
//When
composeTestRule.activity.setContent {
AllInTheme{
Bets.bets[0].toBetVO()?.Accept(v = visitor)
displayer.DisplayBet(Bets.bets[0])
}
}
//Expect
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertExists()
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertDoesNotExist()
composeTestRule.onNodeWithTag(TestTags.CUSTOM_BET.tag).assertDoesNotExist()
}
@Test
fun testVisitor_shouldDisplayMatchUI(){
fun testDisplayer_shouldDisplayMatchUI(){
//Given
//When
composeTestRule.activity.setContent {
AllInTheme{
Bets.bets[1].toBetVO()?.Accept(v = visitor)
displayer.DisplayBet(Bets.bets[1])
}
}
//Expect
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertExists()
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertDoesNotExist()
composeTestRule.onNodeWithTag(TestTags.CUSTOM_BET.tag).assertDoesNotExist()
}
@Test
fun testDisplayer_shouldDisplayCustomBetUI(){
//Given
//When
composeTestRule.activity.setContent {
AllInTheme{
displayer.DisplayBet(Bets.bets[2])
}
}
//Expect
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertDoesNotExist()
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertDoesNotExist()
composeTestRule.onNodeWithTag(TestTags.CUSTOM_BET.tag).assertExists()
}
}

@ -1,21 +1,28 @@
package fr.iut.alldev.allin.vo.bet.visitor
package fr.iut.alldev.allin.vo.bet.displayer
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetTestVisitor : DisplayBetVisitor {
class BetTestDisplayer : BetDisplayer {
@Composable
override fun VisitYesNoBet(b: YesNoBet) {
override fun DisplayYesNoBet(bet: YesNoBet) {
Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
}
@Composable
override fun VisitMatchBet(b: MatchBet) {
override fun DisplayMatchBet(bet: MatchBet) {
Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag))
}
@Composable
override fun DisplayCustomBet(bet: CustomBet) {
Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag))
}
}

@ -16,7 +16,5 @@ annotation class AllInCurrentUser
internal object CurrentUserModule {
@AllInCurrentUser
@Provides
fun provideUser(
userRepository: UserRepository
) = userRepository.currentUser
fun provideUser(userRepository: UserRepository) = userRepository.currentUser
}

@ -12,7 +12,7 @@ import fr.iut.alldev.allin.data.model.bet.BetType
@StringRes
fun BetType.getTitleId(): Int {
return when (this) {
BetType.YES_NO -> R.string.yes_no
BetType.YES_NO -> R.string.yes_no
BetType.MATCH -> R.string.sport_match
BetType.CUSTOM -> R.string.custom_answers
}

@ -0,0 +1,9 @@
package fr.iut.alldev.allin.ext
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
fun Float.formatToSimple(locale: Locale): String {
return DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale)).format(this)
}

@ -0,0 +1,21 @@
package fr.iut.alldev.allin.ext
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
fun String.verifyIsFloat(locale: Locale): String? {
val pattern = Regex("^\\d+(\\.|\\,)?\\d*\$")
val decimalSeparator = DecimalFormatSymbols.getInstance(locale).decimalSeparator.toString()
return if (this.matches(pattern)) {
this.replace(Regex("[.,]"), decimalSeparator).format()
} else if (this.isEmpty()) {
this
} else null
}
fun String.toFloatOrNull(locale: Locale): Float? {
val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale))
return format.parse(this)?.toFloat()
}

@ -1,10 +1,9 @@
package fr.iut.alldev.allin.keystore
import androidx.security.crypto.MasterKeys
import androidx.security.crypto.MasterKey
abstract class AllInKeystoreManager {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
protected abstract val masterKey: MasterKey
abstract fun createKeystore()
abstract fun putToken(token: String)
abstract fun getToken(): String?

@ -3,10 +3,12 @@ package fr.iut.alldev.allin.keystore.impl
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
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"
@ -14,13 +16,18 @@ class AllInKeystoreManagerImpl @Inject constructor(
@ApplicationContext private val context: Context,
) : AllInKeystoreManager() {
override val masterKey = MasterKey
.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private var sharedPreferences: SharedPreferences? = null
override fun createKeystore() {
if (sharedPreferences == null) {
sharedPreferences = EncryptedSharedPreferences.create(
PREFS_FILE_NAME,
masterKeyAlias,
context,
PREFS_FILE_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

@ -2,5 +2,6 @@ package fr.iut.alldev.allin.test
enum class TestTags(val tag: String) {
YES_NO_BET("YES_NO"),
MATCH_BET("MATCH_BET")
MATCH_BET("MATCH_BET"),
CUSTOM_BET("CUSTOM_BET")
}

@ -36,6 +36,7 @@ data class AllInColors(
val allInBetInProgressText: Color,
val allInBetWaitingText: Color,
val allInMainGradient: Brush,
val allInMainGradientReverse: Brush,
val allInBar1stGradient: Brush,
val allInBar2ndGradient: Brush,
val allInTextGradient: Brush,
@ -73,6 +74,7 @@ internal val LocalColors = staticCompositionLocalOf {
allInBetInProgressText = Color.Unspecified,
allInBetWaitingText = Color.Unspecified,
allInMainGradient = SolidColor(Color.Unspecified),
allInMainGradientReverse = SolidColor(Color.Unspecified),
allInBar1stGradient = SolidColor(Color.Unspecified),
allInBar2ndGradient = SolidColor(Color.Unspecified),
allInTextGradient = SolidColor(Color.Unspecified),

@ -9,7 +9,7 @@ import androidx.compose.ui.graphics.painter.Painter
@Immutable
data class AllInIcons(
val allCoins: @Composable ()->Painter,
val allCoins: @Composable () -> Painter,
)
internal val LocalIcons = staticCompositionLocalOf {

@ -55,6 +55,13 @@ fun AllInTheme(
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
),
allInMainGradientReverse = Brush.linearGradient(
0.0f to Color(0xFF199fee),
0.5f to Color(0xFFaa7ef3),
1.0f to Color(0xFFf951a8),
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
),
allInBar1stGradient = Brush.horizontalGradient(
0.0f to Color(0xFF2599F8),
1.0f to Color(0xFF846AC9)
@ -82,32 +89,32 @@ fun AllInTheme(
val customTypography = AllInTypography(
h1 = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.ExtraBold,
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.ExtraBold
),
h2 = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.Bold,
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.Bold
),
h3 = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.SemiBold,
sm1 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.SemiBold
),
m = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.Medium,
sm2 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.Medium
),
r = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.Normal,
p1 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.Normal
),
s = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.Light,
p2 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.Light
),
xs = TextStyle(
fontFamily = PlusJakartaSans,
fontWeight = FontWeight.ExtraLight,
l1 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans,
fontWeight = FontWeight.ExtraLight
)
)

@ -3,40 +3,50 @@ package fr.iut.alldev.allin.theme
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.googlefonts.Font
import androidx.compose.ui.text.googlefonts.GoogleFont
import fr.iut.alldev.allin.R
val PlusJakartaSans = FontFamily(
Font(R.font.plusjakartasans_extralight, weight = FontWeight.ExtraLight),
Font(R.font.plusjakartasans_light, weight = FontWeight.Light),
Font(R.font.plusjakartasans_regular, weight = FontWeight.Normal),
Font(R.font.plusjakartasans_medium, weight = FontWeight.Medium),
Font(R.font.plusjakartasans_semibold, weight = FontWeight.SemiBold),
Font(R.font.plusjakartasans_bold, weight = FontWeight.Bold),
Font(R.font.plusjakartasans_extrabold, weight = FontWeight.ExtraBold)
)
private val provider = GoogleFont.Provider(
providerAuthority = "com.google.android.gms.fonts",
providerPackage = "com.google.android.gms",
certificates = R.array.com_google_android_gms_fonts_certs
)
private val fontNamePlusJakartaSans = GoogleFont(name = "Plus Jakarta Sans")
val fontFamilyPlusJakartaSans = FontFamily(
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.ExtraLight),
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.Light),
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.Normal),
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.Medium),
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.SemiBold),
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.Bold),
Font(googleFont = fontNamePlusJakartaSans, fontProvider = provider, weight = FontWeight.ExtraBold),
)
@Immutable
data class AllInTypography(
val h1: TextStyle,
val h2: TextStyle,
val h3: TextStyle,
val m: TextStyle,
val r: TextStyle,
val s: TextStyle,
val xs: TextStyle
val sm1: TextStyle,
val sm2: TextStyle,
val p1: TextStyle,
val p2: TextStyle,
val l1: TextStyle
)
internal val LocalTypography = staticCompositionLocalOf {
AllInTypography(
h1 = TextStyle.Default,
h2 = TextStyle.Default,
h3 = TextStyle.Default,
m = TextStyle.Default,
r = TextStyle.Default,
s = TextStyle.Default,
xs = TextStyle.Default
sm1 = TextStyle.Default,
sm2 = TextStyle.Default,
p1 = TextStyle.Default,
p2 = TextStyle.Default,
l1 = TextStyle.Default
)
}

@ -118,8 +118,8 @@ fun BetScreen(
creator = it.creator,
category = it.theme,
title = it.phrase,
date = it.endBetDate.formatToMediumDateNoYear(),
time = it.endBetDate.formatToTime(),
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
players = List(3) { null },
onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) },

@ -79,7 +79,7 @@ fun BetScreenCard(
players.size,
players.size
),
style = AllInTheme.typography.m,
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2
)
}

@ -100,13 +100,13 @@ fun BetScreenPopularCard(
color = AllInTheme.colors.allInPink
),
color = AllInTheme.colors.white,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 15.sp
)
Text(
text = " - ",
color = AllInTheme.colors.white,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 15.sp
)
val pointsText = if (points % 1 == 0f) {
@ -126,7 +126,7 @@ fun BetScreenPopularCard(
color = AllInTheme.colors.allInPink
),
color = AllInTheme.colors.white,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 15.sp
)
}

@ -110,6 +110,7 @@ class BetCreationViewModel @Inject constructor(
if (!hasError.value) {
try {
val bet = BetFactory.createBet(
id = "",
betType = selectedBetType.value,
theme = theme.value,
phrase = phrase.value,
@ -118,7 +119,7 @@ class BetCreationViewModel @Inject constructor(
isPublic = isPublic.value,
nameTeam1 = "",
nameTeam2 = "",
possibleAnswers = setOf(),
possibleAnswers = listOf(),
creator = currentUser.username
)
betRepository.createBet(bet, keystoreManager.getToken() ?: "")

@ -1,10 +1,10 @@
package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.ext.formatToMediumDate
@ -14,32 +14,30 @@ import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabPrivacySectio
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
import java.time.ZonedDateTime
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BetCreationScreenQuestionTab(
modifier: Modifier = Modifier,
nbFriends: Int,
betTheme: String,
betThemeError: String?,
setBetTheme: (String)->Unit,
setBetTheme: (String) -> Unit,
betPhrase: String,
betPhraseError: String?,
setBetPhrase: (String)->Unit,
setBetPhrase: (String) -> Unit,
isPublic: Boolean,
setIsPublic: (Boolean)->Unit,
setIsPublic: (Boolean) -> Unit,
registerDate: ZonedDateTime,
registerDateError: String?,
betDate: ZonedDateTime,
betDateError: String?,
selectedFriends: MutableList<Int>,
setRegisterDateDialog: (Boolean)->Unit,
setEndDateDialog: (Boolean)->Unit,
setRegisterTimeDialog: (Boolean)->Unit,
setEndTimeDialog: (Boolean)->Unit,
setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean) -> Unit,
interactionSource: MutableInteractionSource
) {
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(modifier){
Column(modifier) {
QuestionTabThemePhraseSection(
betTheme = betTheme,
betThemeError = betThemeError,
@ -47,7 +45,6 @@ fun BetCreationScreenQuestionTab(
betPhrase = betPhrase,
betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase,
bringIntoViewRequester = bringIntoViewRequester,
interactionSource = interactionSource
)
Spacer(modifier = Modifier.height(35.dp))

@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.runtime.Composable
@ -24,11 +23,10 @@ import fr.iut.alldev.allin.ui.core.AllInTitleInfo
internal fun QuestionTabThemePhraseSection(
betTheme: String,
betThemeError: String?,
setBetTheme: (String)->Unit,
setBetTheme: (String) -> Unit,
betPhrase: String,
betPhraseError: String?,
setBetPhrase: (String)->Unit,
bringIntoViewRequester: BringIntoViewRequester,
setBetPhrase: (String) -> Unit,
interactionSource: MutableInteractionSource
) {
AllInTitleInfo(
@ -41,13 +39,12 @@ internal fun QuestionTabThemePhraseSection(
AllInTextField(
placeholder = stringResource(id = R.string.Theme_placeholder),
value = betTheme,
onValueChange = setBetTheme,
bringIntoViewRequester = bringIntoViewRequester,
borderColor = AllInTheme.colors.white,
modifier = Modifier.fillMaxWidth(),
maxChar = 20,
placeholderFontSize = 13.sp,
onValueChange = setBetTheme,
errorText = betThemeError,
modifier = Modifier.fillMaxWidth()
borderColor = AllInTheme.colors.white
)
Spacer(modifier = Modifier.height(10.dp))
AllInTitleInfo(
@ -60,15 +57,14 @@ internal fun QuestionTabThemePhraseSection(
AllInTextField(
placeholder = stringResource(id = R.string.Bet_Phrase_placeholder),
value = betPhrase,
borderColor = AllInTheme.colors.white,
onValueChange = setBetPhrase,
bringIntoViewRequester = bringIntoViewRequester,
multiLine = true,
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
maxChar = 100,
placeholderFontSize = 13.sp,
multiLine = true,
onValueChange = setBetPhrase,
errorText = betPhraseError,
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
borderColor = AllInTheme.colors.white
)
}

@ -54,8 +54,8 @@ fun BetHistoryScreen(
title = it.phrase,
creator = it.creator,
category = it.theme,
date = it.endBetDate.formatToMediumDateNoYear(),
time = it.endBetDate.formatToTime(),
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
status = it.betStatus,
nbCoins = 230
)

@ -1,24 +1,13 @@
package fr.iut.alldev.allin.ui.betHistory.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.core.bet.BetDateTimeRow
import fr.iut.alldev.allin.ui.core.bet.BetTitleHeader
import fr.iut.alldev.allin.ui.core.bet.BetCard
import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider
@Composable
@ -32,30 +21,15 @@ fun BetHistoryScreenCard(
status: BetStatus,
nbCoins: Int,
) {
AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 16.dp
BetCard(
title = title,
creator = creator,
category = category,
date = date,
time = time,
status = status,
modifier = modifier
) {
Column(
Modifier.padding(horizontal = 19.dp, vertical = 11.dp)
) {
BetTitleHeader(
title = title,
category = category,
creator = creator,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(11.dp))
BetDateTimeRow(
label = stringResource(id = status.getDateStartLabelId()),
date = date,
time = time
)
}
HorizontalDivider(
thickness = 1.dp,
color = AllInTheme.themeColors.border
)
BetHistoryBetStatus(
status = status,
nbCoins = nbCoins

@ -0,0 +1,173 @@
package fr.iut.alldev.allin.ui.betResult
import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.BetStatus
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetBetCard
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCoinAmount
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCongratulations
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import java.time.ZonedDateTime
@Composable
fun BetResultBottomSheet(
state: SheetState,
sheetVisibility: Boolean,
username: String,
coinAmount: Int,
bet: Bet,
stake: Int,
winnings: Int,
odds: Float,
onDismiss: () -> Unit
) {
AllInBottomSheet(
sheetVisibility = sheetVisibility,
onDismiss = onDismiss,
state = state,
dragHandle = null
) {
BetResultBottomSheetContent(
username = username,
coinAmount = coinAmount,
bet = bet,
stake = stake,
winnings = winnings,
odds = odds,
onClose = onDismiss
)
}
}
@Composable
fun BetResultBottomSheetContent(
username: String,
coinAmount: Int,
bet: Bet,
stake: Int,
winnings: Int,
odds: Float,
onClose: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AllInTheme.colors.allInMainGradientReverse),
) {
Icon(
painter = painterResource(id = R.drawable.allin_marquee),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.rotate(11f)
.scale(1.2f)
.offset(x = (-24).dp)
.basicMarquee(spacing = MarqueeSpacing(0.dp)),
tint = AllInTheme.colors.white.copy(alpha = .05f)
)
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
IconButton(
onClick = onClose,
modifier = Modifier
.size(24.dp)
.align(Alignment.TopStart)
) {
Icon(
imageVector = Icons.Default.Close,
tint = AllInTheme.colors.white,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
Icon(
painter = painterResource(R.drawable.allin),
contentDescription = null,
tint = AllInTheme.colors.white,
modifier = Modifier
.size(40.dp)
.align(Alignment.TopCenter)
)
Column(
modifier = Modifier.align(Alignment.Center),
verticalArrangement = Arrangement.spacedBy(48.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
BetResultBottomSheetContentCongratulations(username = username)
BetResultBottomSheetContentCoinAmount(amount = coinAmount)
BetResultBottomSheetBetCard(
title = bet.phrase,
creator = bet.creator,
category = bet.theme,
date = bet.endBetDate.formatToMediumDateNoYear(),
time = bet.endBetDate.formatToTime(),
status = bet.betStatus,
stake = stake,
winnings = winnings,
odds = odds
)
}
}
}
}
@Preview
@Preview(widthDp = 800, heightDp = 1280)
@Composable
private fun BetResultBottomSheetContentPreview() {
AllInTheme {
BetResultBottomSheetContent(
username = "Pseudo",
coinAmount = 3976,
bet = YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.InProgress,
creator = "creator",
),
stake = 4175,
winnings = 2600,
odds = 6.7f
) {
}
}
}

@ -0,0 +1,60 @@
package fr.iut.alldev.allin.ui.betResult.components
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.bet.BetCard
@Composable
fun BetResultBottomSheetBetCard(
modifier: Modifier = Modifier,
title: String,
creator: String,
category: String,
date: String,
time: String,
status: BetStatus,
stake: Int,
winnings: Int,
odds: Float
) {
BetCard(
title = title,
creator = creator,
category = category,
date = date,
time = time,
status = status,
modifier = modifier
) {
BetResultBottomSheetBetCardStats(
stake = stake,
winnings = winnings,
odds = odds
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetResultBottomSheetBetCardPreview() {
AllInTheme {
BetResultBottomSheetBetCard(
creator = "Creator",
category = "Category",
title = "Title",
date = "Date",
time = "Time",
status = BetStatus.Finished(BetFinishedStatus.WON),
stake = 2446,
winnings = 6930,
odds = 2.3f
)
}
}

@ -0,0 +1,111 @@
package fr.iut.alldev.allin.ui.betResult.components
import android.content.res.Configuration
import androidx.compose.foundation.background
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.IconPosition
@Composable
fun BetResultBottomSheetBetCardStats(
stake: Int,
winnings: Int,
odds: Float
) {
Column(
Modifier
.fillMaxWidth()
.background(AllInTheme.themeColors.background2)
.padding(horizontal = 19.dp, vertical = 11.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(id = R.string.bet_result_stake),
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onMainSurface
)
AllInCoinCount(
amount = stake,
color = AllInTheme.colors.allInPurple,
textStyle = AllInTheme.typography.sm1,
position = IconPosition.TRAILING
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(id = R.string.bet_result_winnings),
style = AllInTheme.typography.sm2,
color = AllInTheme.colors.allInPurple
)
AllInCoinCount(
amount = winnings,
textStyle = AllInTheme.typography.sm1,
brush = AllInTheme.colors.allInMainGradient,
position = IconPosition.TRAILING
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(id = R.string.bet_result_odds),
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2
)
AllInCard(
radius = 8.dp,
backgroundBrush = AllInTheme.colors.allInMainGradient
) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text(
text = "$odds",
style = AllInTheme.typography.sm1,
color = AllInTheme.colors.white
)
}
}
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetResultBottomSheetBetCardStatsPreview() {
AllInTheme {
BetResultBottomSheetBetCardStats(
stake = 2446,
winnings = 6930,
odds = 2.3f
)
}
}

@ -0,0 +1,71 @@
package fr.iut.alldev.allin.ui.betResult.components
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
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
@Composable
fun BetResultBottomSheetContentCongratulations(
modifier: Modifier = Modifier,
username: String
) {
val infiniteTransition = rememberInfiniteTransition(label = "")
val rotation by infiniteTransition.animateFloat(
initialValue = -11f,
targetValue = -5f,
animationSpec = infiniteRepeatable(
animation = tween(900),
repeatMode = RepeatMode.Reverse
), label = ""
)
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = .9f,
animationSpec = infiniteRepeatable(
animation = tween(900),
repeatMode = RepeatMode.Reverse
), label = ""
)
Row(
modifier = modifier
.rotate(rotation)
.scale(scale),
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text= stringResource(id = R.string.bet_result_congratulations),
style = AllInTheme.typography.h1,
fontSize = 25.sp,
fontStyle = FontStyle.Italic,
color = AllInTheme.colors.white
)
Text(
text = "${username.uppercase()} !",
style = AllInTheme.typography.h1,
fontSize = 30.sp,
fontStyle = FontStyle.Italic,
color = AllInTheme.colors.white
)
}
}

@ -0,0 +1,79 @@
package fr.iut.alldev.allin.ui.betResult.components
import android.content.res.Configuration
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
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.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.IconPosition
@Composable
fun BetResultBottomSheetContentCoinAmount(
modifier: Modifier = Modifier,
amount: Int
) {
Column(
verticalArrangement = Arrangement.spacedBy(14.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.bet_result_you_win),
style = AllInTheme.typography.sm2,
color = AllInTheme.colors.white,
fontSize = 20.sp
)
Box(
modifier
.border(
width = 2.dp,
shape = RoundedCornerShape(100.dp),
color = AllInTheme.colors.white
)
.padding(vertical = 22.dp, horizontal = 33.dp)
) {
AllInCoinCount(
amount = amount,
textStyle = AllInTheme.typography.h1,
position = IconPosition.TRAILING,
color = AllInTheme.colors.white,
size = 60,
modifier = Modifier
.align(Alignment.Center)
.offset(y = (-6).dp)
)
AllInCoinCount(
amount = amount,
textStyle = AllInTheme.typography.h1,
position = IconPosition.TRAILING,
color = AllInTheme.colors.white.copy(alpha = .32f),
size = 60,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetResultBottomSheetContentCoinAmountPreview() {
AllInTheme {
BetResultBottomSheetContentCoinAmount(amount = 1572)
}
}

@ -6,11 +6,13 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.vo.BetDetail
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusBottomSheetBack
import fr.iut.alldev.allin.ui.betStatus.visitor.BetStatusBottomSheetDisplayBetVisitor
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusParticipationBottomSheet
import fr.iut.alldev.allin.ui.betStatus.components.getAnswerFromParticipationIdx
import fr.iut.alldev.allin.ui.betStatus.components.getParticipationAnswers
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.vo.bet.BetVO
internal const val SHEET_HEIGHT = .85f
@ -22,9 +24,14 @@ fun BetStatusBottomSheet(
state: SheetState,
sheetVisibility: Boolean,
sheetBackVisibility: Boolean,
bet: BetVO<Bet>?,
onDismiss: ()->Unit,
visitor: BetStatusBottomSheetDisplayBetVisitor
betDetail: BetDetail?,
paddingValues: PaddingValues,
userCoinAmount: MutableIntState,
onParticipate: (stake: Int, response: String) -> Unit,
onDismiss: () -> Unit,
participateSheetVisibility: Boolean,
setParticipateSheetVisibility: (Boolean) -> Unit,
displayBet: @Composable (BetDetail) -> Unit
) {
AnimatedVisibility(
visible = sheetBackVisibility,
@ -35,7 +42,7 @@ fun BetStatusBottomSheet(
targetOffsetY = { it }
)
) {
bet?.let {
betDetail?.let {
BetStatusBottomSheetBack(
status = it.bet.betStatus
)
@ -47,11 +54,45 @@ fun BetStatusBottomSheet(
onDismiss = onDismiss,
state = state,
scrimColor = Color.Transparent
){
) {
var selectedAnswer by remember { mutableStateOf(0) }
var stake by remember { mutableStateOf<Int?>(null) }
Column(
Modifier.fillMaxHeight(SHEET_HEIGHT)
) {
bet?.Accept(visitor)
betDetail?.let {
val elements = betDetail.getParticipationAnswers()
displayBet(it)
BetStatusParticipationBottomSheet(
sheetVisibility = participateSheetVisibility && betDetail.bet.betStatus == BetStatus.Waiting && state.hasExpandedState,
safeBottomPadding = paddingValues.calculateBottomPadding(),
odds = betDetail.answers.getOrNull(selectedAnswer)?.odds ?: 1f,
betPhrase = betDetail.bet.phrase,
coinAmount = userCoinAmount.intValue,
onDismiss = { setParticipateSheetVisibility(false) },
state = rememberModalBottomSheetState(skipPartiallyExpanded = true),
elements = elements,
selectedElement = elements.getOrNull(selectedAnswer),
stake = stake,
setStake = { stake = it },
setElement = { idx -> selectedAnswer = idx },
enabled = stake != null &&
(stake ?: 0) <= userCoinAmount.intValue
) {
stake?.let { stake ->
onParticipate(
stake,
betDetail.bet.getAnswerFromParticipationIdx(selectedAnswer)
)
}
}
}
}
}
}

@ -0,0 +1,166 @@
package fr.iut.alldev.allin.ui.betStatus.components
import android.content.res.Configuration
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.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.preview.BetDetailPreviewProvider
import java.util.Locale
private val participationAnswerFontSize = 25.sp
@Composable
fun BetDetail.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> {
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
return when (this.bet) {
is CustomBet -> (this.bet as CustomBet).possibleAnswers.map {
{
this@getParticipationAnswers.getAnswerOfResponse(it)?.let {
ParticipationAnswerLine(
text = it.response,
odds = it.odds,
locale = locale
)
}
}
}
is MatchBet -> buildList {
val bet = (this@getParticipationAnswers.bet as MatchBet)
add {
this@getParticipationAnswers.getAnswerOfResponse(bet.nameTeam1)?.let {
ParticipationAnswerLine(
text = it.response,
odds = it.odds,
locale = locale
)
}
}
add {
this@getParticipationAnswers.getAnswerOfResponse(bet.nameTeam2)?.let {
ParticipationAnswerLine(
text = it.response,
color = AllInTheme.colors.allInBarPink,
odds = it.odds,
locale = locale
)
}
}
}
is YesNoBet -> buildList {
add {
this@getParticipationAnswers.getAnswerOfResponse(YES_VALUE)?.let {
ParticipationAnswerLine(
text = it.response,
odds = it.odds,
locale = locale
)
}
}
add {
this@getParticipationAnswers.getAnswerOfResponse(NO_VALUE)?.let {
ParticipationAnswerLine(
text = it.response,
color = AllInTheme.colors.allInBarPink,
odds = it.odds,
locale = locale
)
}
}
}
}
}
@Composable
private fun ParticipationAnswerLine(
text: String,
color: Color = AllInTheme.colors.allInBlue,
locale: Locale,
odds: Float
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = text.uppercase(),
color = color,
style = AllInTheme.typography.h1,
fontSize = participationAnswerFontSize
)
AllInCard(
radius = 50.dp,
backgroundColor = AllInTheme.colors.allInPurple
) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text(
text = "x${odds.formatToSimple(locale)}",
color = AllInTheme.colors.white,
style = AllInTheme.typography.h2
)
}
}
}
}
fun Bet.getAnswerFromParticipationIdx(idx: Int) =
when (this) {
is CustomBet -> this.possibleAnswers.getOrElse(idx) { "" }
is MatchBet -> when (idx) {
0 -> this.nameTeam1
1 -> this.nameTeam2
else -> ""
}
is YesNoBet -> when (idx) {
0 -> YES_VALUE
1 -> NO_VALUE
else -> ""
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ParticipationAnswersPreview(
@PreviewParameter(BetDetailPreviewProvider::class) bet: BetDetail,
) {
AllInTheme {
Column {
bet.getParticipationAnswers().forEach {
Row(Modifier.fillMaxWidth()) { it() }
}
}
}
}

@ -5,6 +5,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -18,8 +20,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInIntTextField
import fr.iut.alldev.allin.ui.core.AllInSelectionBox
import fr.iut.alldev.allin.ui.core.topbar.AllInTopBarCoinCounter
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -30,7 +35,14 @@ fun BetStatusParticipationBottomSheet(
coinAmount: Int,
onDismiss: () -> Unit,
state: SheetState,
onParticipate: () -> Unit,
enabled: Boolean,
odds: Float,
stake: Int?,
setStake: (Int?) -> Unit,
elements: List<@Composable RowScope.() -> Unit>,
selectedElement: (@Composable RowScope.() -> Unit)?,
setElement: (Int) -> Unit,
onParticipate: () -> Unit
) {
val scope = rememberCoroutineScope()
AllInBottomSheet(
@ -38,6 +50,98 @@ fun BetStatusParticipationBottomSheet(
onDismiss = onDismiss,
state = state,
containerColor = AllInTheme.themeColors.background2
) {
BetStatusParticipationBottomSheetContent(
safeBottomPadding = safeBottomPadding,
betPhrase = betPhrase,
coinAmount = coinAmount,
elements = elements,
selectedElement = selectedElement,
setElement = setElement,
enabled = enabled,
stake = stake,
odds = odds,
setStake = setStake
) {
scope.launch {
onParticipate()
state.hide()
onDismiss()
}
}
}
}
@Composable
private fun ColumnScope.BetStatusParticipationBottomSheetContent(
safeBottomPadding: Dp,
betPhrase: String,
coinAmount: Int,
enabled: Boolean,
stake: Int?,
odds: Float,
setStake: (Int?) -> Unit,
selectedElement: (@Composable RowScope.() -> Unit)?,
elements: List<@Composable RowScope.() -> Unit>,
setElement: (Int) -> Unit,
onButtonClick: () -> Unit
) {
val (answersBoxIsOpen, setAnswersBoxIsOpen) = remember { mutableStateOf(false) }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.place_your_bets),
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
fontSize = 20.sp,
modifier = Modifier.padding(start = 18.dp)
)
AllInTopBarCoinCounter(
amount = coinAmount,
backgroundColor = AllInTheme.colors.allInBlue,
textColor = AllInTheme.colors.white,
iconColor = AllInTheme.colors.white,
)
}
Column(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Text(
text = betPhrase,
style = AllInTheme.typography.p2,
color = AllInTheme.themeColors.onMainSurface,
modifier = Modifier.padding(vertical = 30.dp)
)
AllInSelectionBox(
isOpen = answersBoxIsOpen,
setIsOpen = setAnswersBoxIsOpen,
selected = selectedElement,
setSelected = { setElement(elements.indexOf(it)) },
elements = elements,
borderWidth = 1.dp
)
Spacer(modifier = Modifier.height(8.dp))
AllInIntTextField(
value = stake,
setValue = setStake,
placeholder = stringResource(id = R.string.bet_result_stake),
trailingIcon = AllInTheme.icons.allCoins(),
modifier = Modifier.fillMaxWidth(),
maxChar = null
)
}
Spacer(modifier = Modifier.height(100.dp))
HorizontalDivider(color = AllInTheme.themeColors.border)
Column(
modifier = Modifier
.background(AllInTheme.themeColors.background)
.padding(horizontal = 7.dp)
.padding(bottom = safeBottomPadding, top = 7.dp),
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
@ -45,81 +149,45 @@ fun BetStatusParticipationBottomSheet(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.place_your_bets),
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
fontSize = 20.sp,
modifier = Modifier.padding(start = 18.dp)
text = stringResource(id = R.string.Possible_winnings),
style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onBackground
)
AllInTopBarCoinCounter(
amount = coinAmount,
backgroundColor = AllInTheme.colors.allInBlue,
textColor = AllInTheme.colors.white,
iconColor = AllInTheme.colors.white,
AllInCoinCount(
amount = stake?.let { (it + (it * odds)).roundToInt() } ?: 0,
color = AllInTheme.themeColors.onBackground
)
}
Spacer(modifier = Modifier.height(30.dp))
Text(
text = betPhrase,
style = AllInTheme.typography.s,
color = AllInTheme.themeColors.onMainSurface,
modifier = Modifier.padding(horizontal = 18.dp)
AllInButton(
enabled = enabled,
color = AllInTheme.colors.allInPurple,
text = stringResource(id = R.string.Participate),
textColor = AllInTheme.colors.white,
radius = 5.dp,
onClick = onButtonClick
)
Spacer(modifier = Modifier.height(100.dp))
HorizontalDivider(color = AllInTheme.themeColors.border)
Column(
modifier = Modifier
.background(AllInTheme.themeColors.background)
.padding(horizontal = 7.dp)
.padding(bottom = safeBottomPadding, top = 7.dp),
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.Possible_winnings),
style = AllInTheme.typography.r,
color = AllInTheme.themeColors.onBackground
)
AllInCoinCount(
amount = 121,
color = AllInTheme.themeColors.onBackground
)
}
AllInButton(
color = AllInTheme.colors.allInPurple,
text = stringResource(id = R.string.Participate),
textColor = AllInTheme.colors.white,
radius = 5.dp
) {
scope.launch {
onParticipate()
state.hide()
onDismiss()
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetStatusParticipationBottomSheetPreview() {
private fun BetStatusParticipationBottomSheetContentPreview() {
AllInTheme {
BetStatusParticipationBottomSheet(
sheetVisibility = true,
safeBottomPadding = 5.dp,
betPhrase = "Lorem Ipsum",
coinAmount = 125,
onDismiss = {},
state = rememberModalBottomSheetState(),
onParticipate = {}
)
Column {
BetStatusParticipationBottomSheetContent(
safeBottomPadding = 0.dp,
betPhrase = "Bet phrase",
coinAmount = 3620,
onButtonClick = {},
elements = emptyList(),
setElement = {},
selectedElement = null,
enabled = true,
stake = 123,
odds = 0.42f,
setStake = {}
)
}
}
}

@ -15,6 +15,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInTheme
@ -41,7 +42,7 @@ fun BetStatusWinner(
) {
AllInTextIcon(
text = answer,
icon = Icons.Filled.EmojiEvents,
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
color = color,
size = 50,
iconSize = 60,
@ -57,13 +58,13 @@ fun BetStatusWinner(
)
AllInTextIcon(
text = username,
icon = Icons.Filled.People,
icon = rememberVectorPainter(image = Icons.Filled.People),
color = color,
position = IconPosition.LEADING
)
AllInTextIcon(
text = "x" + String.format("%.1f", multiplier),
icon = Icons.Filled.EmojiEvents,
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
color = color,
position = IconPosition.LEADING
)

@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextIcon
@ -26,7 +27,7 @@ fun YesNoDetailsLine(
AllInTextIcon(
text = yesText,
color = AllInTheme.colors.allInBlue,
icon = icon,
icon = rememberVectorPainter(image = icon),
position = IconPosition.LEADING,
size = 10,
iconSize = 15
@ -34,7 +35,7 @@ fun YesNoDetailsLine(
AllInTextIcon(
text = noText,
color = AllInTheme.colors.allInBarPink,
icon = icon,
icon = rememberVectorPainter(image = icon),
position = IconPosition.TRAILING,
size = 10,
iconSize = 15

@ -4,27 +4,27 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.toPercentageString
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.PercentagePositionnedElement
import fr.iut.alldev.allin.ui.core.StatBar
@Composable
fun YesNoStatBar(
yesPercentage: Float,
fun BinaryStatBar(
response1Percentage: Float,
response1: String,
response2: String,
modifier: Modifier = Modifier,
) {
Column(modifier) {
Row {
Text(
text = stringResource(id = R.string.Yes).uppercase(),
text = response1,
color = AllInTheme.colors.allInBlue,
style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic,
@ -32,20 +32,20 @@ fun YesNoStatBar(
modifier = Modifier.weight(1f)
)
Text(
text = stringResource(id = R.string.No).uppercase(),
text = response2,
style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic,
fontSize = 30.sp,
color = AllInTheme.colors.allInBarPink
)
}
StatBar(percentage = yesPercentage)
StatBar(percentage = response1Percentage)
PercentagePositionnedElement(
percentage = yesPercentage
percentage = response1Percentage
) {
Text(
text = yesPercentage.toPercentageString(),
style = AllInTheme.typography.h3,
text = response1Percentage.toPercentageString(),
style = AllInTheme.typography.sm1,
color = AllInTheme.colors.allInBarPurple
)
}
@ -64,6 +64,10 @@ private fun YesNoStatBarPreview(
@PreviewParameter(YesNoStatBarPreviewProvider::class) percentage: Float,
) {
AllInTheme {
YesNoStatBar(percentage)
BinaryStatBar(
percentage,
"Answer 1",
"Answer 2"
)
}
}

@ -1,236 +0,0 @@
package fr.iut.alldev.allin.ui.betStatus.visitor
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EmojiEvents
import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.BetFinishedStatus
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.ext.getDateEndLabelId
import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusParticipationBottomSheet
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner
import fr.iut.alldev.allin.ui.betStatus.components.YesNoDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.YesNoStatBar
import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer
import fr.iut.alldev.allin.ui.core.RainbowButton
import fr.iut.alldev.allin.ui.core.bet.BetDateTimeRow
import fr.iut.alldev.allin.ui.core.bet.BetTitleHeader
import fr.iut.alldev.allin.vo.bet.factory.toBetVO
import fr.iut.alldev.allin.vo.bet.visitor.DisplayBetVisitor
import java.time.ZonedDateTime
class BetStatusBottomSheetDisplayBetVisitor(
val userCoinAmount: MutableIntState,
val onParticipate: (Int) -> Unit,
) : DisplayBetVisitor {
val participateBottomSheetVisibility = mutableStateOf(false)
val paddingValues = mutableStateOf(PaddingValues())
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun VisitYesNoBet(b: YesNoBet) {
val (participateSheetVisibility, setParticipateSheetVisibility) = remember {
this.participateBottomSheetVisibility
}
val safeBottomPadding = paddingValues.value.calculateBottomPadding()
Box(
Modifier
.padding(bottom = safeBottomPadding)
) {
Column {
Column(Modifier.padding(horizontal = 20.dp)) {
BetTitleHeader(
title = b.phrase,
category = b.theme,
creator = "Lucas" /*TODO : Creator*/,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(20.dp))
Column(
horizontalAlignment = Alignment.End
) {
BetDateTimeRow(
label = stringResource(id = b.betStatus.getDateStartLabelId()),
date = b.endRegisterDate.formatToMediumDateNoYear(),
time = b.endRegisterDate.formatToTime(),
modifier = Modifier.width(IntrinsicSize.Max)
)
Spacer(modifier = Modifier.height(15.dp))
BetDateTimeRow(
label = stringResource(id = b.betStatus.getDateEndLabelId()),
date = b.endBetDate.formatToMediumDateNoYear(),
time = b.endBetDate.formatToTime(),
modifier = Modifier.width(IntrinsicSize.Max)
)
}
Spacer(modifier = Modifier.height(20.dp))
}
if (b.betStatus is BetStatus.Finished) {
BetStatusWinner(
answer = stringResource(id = R.string.Yes),
color = AllInTheme.colors.allInBlue,
coinAmount = 442,
username = "Imri",
multiplier = 1.2f
)
} else {
HorizontalDivider(color = AllInTheme.themeColors.border)
}
Column(
Modifier
.fillMaxHeight()
.background(AllInTheme.themeColors.background2)
.padding(horizontal = 20.dp)
) {
Spacer(modifier = Modifier.height(20.dp))
YesNoStatBar(yesPercentage = .86f)
AllInDetailsDrawer {
YesNoDetailsLine(
icon = AllInTheme.icons.allCoins(),
yesText = "550",
noText = "330",
)
YesNoDetailsLine(
icon = Icons.Filled.People,
yesText = "12",
noText = "5"
)
YesNoDetailsLine(
icon = Icons.Filled.WorkspacePremium,
yesText = "45",
noText = "45"
)
YesNoDetailsLine(
icon = Icons.Filled.EmojiEvents,
yesText = "x1.2",
noText = "x1.45"
)
}
}
}
if (b.betStatus !is BetStatus.Finished) {
RainbowButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(horizontal = 7.dp),
text = stringResource(id = R.string.Participate),
enabled = b.betStatus == BetStatus.Waiting,
onClick = {
setParticipateSheetVisibility(true)
}
)
}
}
BetStatusParticipationBottomSheet(
sheetVisibility = participateSheetVisibility && b.betStatus == BetStatus.Waiting,
safeBottomPadding = safeBottomPadding,
betPhrase = b.phrase,
coinAmount = userCoinAmount.intValue,
onDismiss = { setParticipateSheetVisibility(false) },
state = rememberModalBottomSheetState()
) {
onParticipate(100)
}
}
@Composable
override fun VisitMatchBet(b: MatchBet) {
Text("This is a MATCH BET")
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun YesNoBetPreview() {
AllInTheme {
val coins = remember { mutableIntStateOf(100) }
YesNoBet(
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.InProgress,
creator = "creator"
).toBetVO()?.Accept(
BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = coins,
onParticipate = {}
)
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun YesNoBetFinishedPreview() {
AllInTheme {
val coins = remember { mutableIntStateOf(100) }
YesNoBet(
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator"
).toBetVO()?.Accept(
BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = coins,
onParticipate = {}
)
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MatchBetPreview() {
AllInTheme {
val coins = remember { mutableIntStateOf(100) }
MatchBet(
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.InProgress,
nameTeam1 = "Team 1",
nameTeam2 = "Team 2",
creator = "creator"
).toBetVO()?.Accept(
BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = coins,
onParticipate = {}
)
)
}
}

@ -0,0 +1,265 @@
package fr.iut.alldev.allin.ui.betStatus.vo
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EmojiEvents
import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
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.BetStatus
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.ext.getDateEndLabelId
import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner
import fr.iut.alldev.allin.ui.betStatus.components.BinaryStatBar
import fr.iut.alldev.allin.ui.betStatus.components.YesNoDetailsLine
import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer
import fr.iut.alldev.allin.ui.core.ProfilePicture
import fr.iut.alldev.allin.ui.core.RainbowButton
import fr.iut.alldev.allin.ui.core.bet.BetDateTimeRow
import fr.iut.alldev.allin.ui.core.bet.BetTitleHeader
import fr.iut.alldev.allin.ui.preview.BetDetailPreviewProvider
import fr.iut.alldev.allin.vo.bet.BetDisplayer
import java.util.Locale
class BetStatusBottomSheetBetDisplayer(
val openParticipateSheet: () -> Unit
) : BetDisplayer {
val paddingValues = mutableStateOf(PaddingValues())
@Composable
private fun DisplayBinaryBet(
betDetail: BetDetail,
response1: String,
response2: String,
response1Display: String = response1,
response2Display: String = response2
) {
val safeBottomPadding = paddingValues.value.calculateBottomPadding()
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val response1Answer = remember { betDetail.getAnswerOfResponse(response1) }
val response2Answer = remember { betDetail.getAnswerOfResponse(response2) }
Box(Modifier.padding(bottom = safeBottomPadding)) {
Column {
Column(Modifier.padding(horizontal = 20.dp)) {
BetTitleHeader(
title = betDetail.bet.phrase,
category = betDetail.bet.theme,
creator = betDetail.bet.creator,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(20.dp))
Column(
horizontalAlignment = Alignment.End
) {
BetDateTimeRow(
label = stringResource(id = betDetail.bet.betStatus.getDateStartLabelId()),
date = betDetail.bet.endRegisterDate.formatToMediumDateNoYear(),
time = betDetail.bet.endRegisterDate.formatToTime(),
modifier = Modifier.width(IntrinsicSize.Max)
)
Spacer(modifier = Modifier.height(15.dp))
BetDateTimeRow(
label = stringResource(id = betDetail.bet.betStatus.getDateEndLabelId()),
date = betDetail.bet.endBetDate.formatToMediumDateNoYear(),
time = betDetail.bet.endBetDate.formatToTime(),
modifier = Modifier.width(IntrinsicSize.Max)
)
}
Spacer(modifier = Modifier.height(20.dp))
}
if (betDetail.bet.betStatus is BetStatus.Finished) {
BetStatusWinner(
answer = response1Display,
color = AllInTheme.colors.allInBlue,
coinAmount = 442,
username = "Imri",
multiplier = 1.2f
)
} else {
HorizontalDivider(color = AllInTheme.themeColors.border)
}
Column(
Modifier
.fillMaxHeight()
.background(AllInTheme.themeColors.background2)
.padding(horizontal = 20.dp)
) {
Spacer(modifier = Modifier.height(20.dp))
BinaryStatBar(
response1Percentage = remember {
val total = (response1Answer?.totalParticipants ?: 0) + (response2Answer?.totalParticipants ?: 0)
if (total == 0) .5f else (response1Answer?.totalParticipants ?: 0) / total.toFloat()
},
response1 = response1Display,
response2 = response2Display
)
AllInDetailsDrawer {
YesNoDetailsLine(
icon = AllInTheme.icons.allCoins(),
yesText = response1Answer?.totalStakes?.toString() ?: "0",
noText = response2Answer?.totalStakes?.toString() ?: "0"
)
YesNoDetailsLine(
icon = Icons.Filled.People,
yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0"
)
YesNoDetailsLine(
icon = Icons.Filled.WorkspacePremium,
yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0"
)
YesNoDetailsLine(
icon = Icons.Filled.EmojiEvents,
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
)
}
Text(
text = stringResource(id = R.string.bet_status_participants_list),
fontSize = 20.sp,
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.h1,
modifier = Modifier.padding(vertical = 36.dp)
)
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
contentPadding = PaddingValues(horizontal = 13.dp, vertical = 8.dp),
modifier = Modifier.fillMaxHeight()
) {
betDetail.userParticipation?.let {
item {
BetStatusParticipant(
username = it.username,
allCoinsAmount = it.stake
)
HorizontalDivider(
color = AllInTheme.themeColors.border,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 25.dp)
)
}
}
items(betDetail.participations) {
if (it.username != betDetail.userParticipation?.username) {
BetStatusParticipant(
username = it.username,
allCoinsAmount = it.stake
)
}
}
}
}
}
if (betDetail.bet.betStatus !is BetStatus.Finished && betDetail.userParticipation == null) {
RainbowButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(horizontal = 7.dp),
text = stringResource(id = R.string.Participate),
enabled = betDetail.bet.betStatus == BetStatus.Waiting,
onClick = openParticipateSheet
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun DisplayYesNoBet(betDetail: BetDetail) {
DisplayBinaryBet(
betDetail = betDetail,
response1 = YES_VALUE,
response2 = NO_VALUE,
response1Display = stringResource(id = R.string.Yes).uppercase(),
response2Display = stringResource(id = R.string.No).uppercase()
)
}
@Composable
override fun DisplayMatchBet(betDetail: BetDetail) {
val bet = remember { betDetail.bet as MatchBet }
DisplayBinaryBet(
betDetail = betDetail,
response1 = bet.nameTeam1,
response2 = bet.nameTeam2
)
}
@Composable
override fun DisplayCustomBet(betDetail: BetDetail) {
Text("This is a CUSTOM BET")
}
}
@Composable
fun BetStatusParticipant(
username: String,
allCoinsAmount: Int
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically
) {
ProfilePicture(modifier = Modifier.size(25.dp))
Text(
text = username,
fontWeight = FontWeight.Bold,
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
AllInCoinCount(
amount = allCoinsAmount,
color = AllInTheme.colors.allInPurple
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetStatusBottomSheetPreview(
@PreviewParameter(BetDetailPreviewProvider::class) bet: BetDetail
) {
AllInTheme {
BetStatusBottomSheetBetDisplayer {
}.DisplayBet(bet)
}
}

@ -32,7 +32,7 @@ fun AllInAlertDialog(
text = {
Text(
text = text,
style = AllInTheme.typography.r
style = AllInTheme.typography.p1
)
},
confirmButton = {

@ -18,6 +18,7 @@ fun AllInBottomSheet(
state: SheetState,
scrimColor: Color = BottomSheetDefaults.ScrimColor,
containerColor: Color = AllInTheme.themeColors.background,
dragHandle: (@Composable ()->Unit)? = { BottomSheetDefaults.DragHandle() },
content: @Composable ColumnScope.()->Unit
) {
val localDensity = LocalDensity.current
@ -38,6 +39,7 @@ fun AllInBottomSheet(
bottom = 0,
)
},
dragHandle = dragHandle,
content = content
)
}

@ -15,10 +15,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
color: Color,
text: String,
textColor: Color,
modifier: Modifier = Modifier,
radius: Dp = 15.dp,
onClick: () -> Unit,
) {
@ -26,13 +27,14 @@ fun AllInButton(
onClick = onClick,
modifier = modifier,
radius = radius,
backgroundColor = color
backgroundColor = color,
enabled = enabled
) {
Text(
text = text,
textAlign = TextAlign.Center,
style = AllInTheme.typography.h2,
color = textColor,
color = if(enabled) textColor else AllInTheme.themeColors.disabledBorder,
fontSize = 20.sp,
modifier = Modifier
.padding(vertical = 15.dp)
@ -51,6 +53,21 @@ private fun AllInButtonPreview() {
textColor = Color.White
) {
}
}
}
@Preview
@Composable
private fun AllInButtonDisabledPreview() {
AllInTheme {
AllInButton(
color = AllInTheme.colors.allInLoginPurple,
text = "Connexion",
textColor = Color.White,
enabled = false
) {
}
}
}

@ -44,7 +44,7 @@ fun AllInChip(
.padding(vertical = 8.dp, horizontal = 22.dp)
.alpha(if (isSelected) 0f else 1f),
textAlign = TextAlign.Center,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onBackground2
)
if (isSelected) {

@ -3,7 +3,9 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInTheme
@ -11,14 +13,20 @@ import fr.iut.alldev.allin.theme.AllInTheme
fun AllInCoinCount(
modifier: Modifier = Modifier,
amount: Int,
color: Color,
position: IconPosition = IconPosition.TRAILING,
color: Color? = null,
brush: Brush?= null,
size: Int = 15,
textStyle: TextStyle = AllInTheme.typography.h1,
position: IconPosition = IconPosition.TRAILING
) {
AllInTextIcon(
text = amount.toString(),
icon = AllInTheme.icons.allCoins(),
color = color,
color = color ?: Color.Black,
brush = brush,
position = position,
textStyle = textStyle,
size = size,
modifier = modifier
)
}

@ -51,7 +51,7 @@ fun AllInDatePicker(
Text(
text = stringResource(id = R.string.Cancel),
color = AllInTheme.themeColors.onBackground2,
style = AllInTheme.typography.h3
style = AllInTheme.typography.sm1
)
}
},

@ -58,7 +58,7 @@ fun AllInDetailsDrawer(
) {
Text(
text = text,
style = AllInTheme.typography.s,
style = AllInTheme.typography.p2,
color = textColor,
fontSize = 15.sp
)

@ -12,7 +12,7 @@ import fr.iut.alldev.allin.theme.AllInTheme
fun AllInErrorLine(text: String) {
Text(
text = text,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
color = Color.Red,
fontSize = 10.sp,
overflow = TextOverflow.Ellipsis

@ -64,7 +64,7 @@ fun AllInRetractableCard(
fontStyle = AllInTheme.typography.h2.fontStyle
),
color = AllInTheme.themeColors.onBackground2,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 16.sp,
modifier = Modifier.weight(1f)
)

@ -20,14 +20,14 @@ fun AllInSectionButton(
onClick: (Int) -> Unit,
) {
val style = if (isSelected) {
AllInTheme.typography.h3.copy(
AllInTheme.typography.sm1.copy(
color = AllInTheme.themeColors.onMainSurface,
fontSize = 15.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.ExtraBold
)
} else {
AllInTheme.typography.h3.copy(
AllInTheme.typography.sm1.copy(
color = AllInTheme.themeColors.onBackground2,
fontSize = 15.sp,
textAlign = TextAlign.Center,

@ -6,8 +6,10 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -29,6 +31,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
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.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInTheme
@ -36,11 +39,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
class SelectionElement(
val textId: Int,
val imageVector: ImageVector,
val imageVector: ImageVector
)
@Composable
fun AllInSelectionLine(
private fun AllInSelectionLine(
text: String,
iconVector: ImageVector?,
modifier: Modifier = Modifier,
@ -91,13 +94,19 @@ fun AllInSelectionLine(
fun AllInSelectionBox(
modifier: Modifier = Modifier,
isOpen: Boolean,
borderWidth: Dp? = null,
setIsOpen: (Boolean) -> Unit,
selected: SelectionElement?,
setSelected: (SelectionElement) -> Unit,
elements: List<SelectionElement>,
) {
val interactionSource = remember { MutableInteractionSource() }
AllInCard(modifier.fillMaxWidth()) {
AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
) {
Column(
Modifier.animateContentSize()
) {
@ -154,6 +163,84 @@ private fun AllInSelectionBoxClosedPreview() {
}
}
@Composable
private fun AllInSelectionLine(
element: @Composable RowScope.() -> Unit,
modifier: Modifier = Modifier,
onClick: () -> Unit,
trailingIcon: ImageVector? = null,
interactionSource: MutableInteractionSource
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onClick
)
.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
element()
trailingIcon?.let {
Icon(
imageVector = trailingIcon,
contentDescription = null,
tint = AllInTheme.colors.allInPurple,
modifier = Modifier
.size(30.dp)
)
}
}
}
@Composable
fun AllInSelectionBox(
modifier: Modifier = Modifier,
isOpen: Boolean,
setIsOpen: (Boolean) -> Unit,
borderWidth: Dp? = null,
selected: (@Composable RowScope.() -> Unit)?,
setSelected: (@Composable RowScope.() -> Unit) -> Unit,
elements: List<@Composable RowScope.() -> Unit>,
) {
val interactionSource = remember { MutableInteractionSource() }
AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
) {
Column(Modifier.animateContentSize()) {
AllInSelectionLine(
element = selected ?: { Box { } },
onClick = { setIsOpen(!isOpen) },
interactionSource = interactionSource,
trailingIcon = with(Icons.Default) {
if (isOpen) ExpandLess else ExpandMore
}
)
AnimatedVisibility(isOpen) {
Column {
HorizontalDivider(color = AllInTheme.themeColors.border)
elements.filter { it != selected }.forEach { element ->
AllInSelectionLine(
element = element,
interactionSource = interactionSource,
onClick = {
setSelected(element)
setIsOpen(false)
}
)
}
}
}
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable

@ -2,7 +2,6 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
@ -11,35 +10,37 @@ import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.input.*
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
import androidx.core.text.isDigitsOnly
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.ext.toFloatOrNull
import fr.iut.alldev.allin.ext.verifyIsFloat
import fr.iut.alldev.allin.theme.AllInTheme
import kotlinx.coroutines.launch
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
import java.util.Locale
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun AllInTextField(
placeholder: String,
value: String,
modifier: Modifier = Modifier,
placeholder: String? = null,
value: String,
maxChar: Int? = null,
enabled: Boolean = true,
trailingIcon: ImageVector? = null,
trailingContent: @Composable (() -> Unit)? = null,
trailingIcon: Painter? = null,
trailingContent: @Composable() (() -> Unit)? = null,
placeholderFontSize: TextUnit = 18.sp,
multiLine: Boolean = false,
onValueChange: (String) -> Unit,
errorText: String? = null,
bringIntoViewRequester: BringIntoViewRequester,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Default,
@ -48,64 +49,44 @@ fun AllInTextField(
containerColor: Color = AllInTheme.themeColors.background,
textColor: Color = AllInTheme.themeColors.onMainSurface,
placeholderColor: Color = AllInTheme.themeColors.onBackground2,
onValueChange: (String) -> Unit,
) {
val scope = rememberCoroutineScope()
var hasFocus by remember { mutableStateOf(false) }
var textFieldValue by remember {
mutableStateOf(TextFieldValue(text = value, selection = TextRange(value.length)))
}
LaunchedEffect(key1 = value, block = {
textFieldValue = TextFieldValue(
text = value,
selection = textFieldValue.selection
)
})
OutlinedTextField(
value = textFieldValue,
value = value,
isError = errorText != null,
modifier = modifier
.onFocusChanged {
hasFocus = it.hasFocus
if (it.isFocused) {
scope.launch {
bringIntoViewRequester.bringIntoView()
}
}
},
modifier = modifier,
supportingText = errorText?.let {
{ AllInErrorLine(text = it) }
},
visualTransformation = visualTransformation,
singleLine = !multiLine,
onValueChange = {
if (maxChar == null || it.text.length <= maxChar) {
textFieldValue = it
onValueChange(it.text)
if (maxChar == null || it.length <= maxChar) {
onValueChange(it)
}
},
placeholder = {
Text(
text = placeholder,
fontSize = placeholderFontSize,
style = AllInTheme.typography.r,
color = placeholderColor,
maxLines = if (multiLine) 3 else 1,
overflow = TextOverflow.Ellipsis
)
placeholder = placeholder?.let {
{
Text(
text = placeholder,
fontSize = placeholderFontSize,
style = AllInTheme.typography.p1,
color = placeholderColor,
maxLines = if (multiLine) 3 else 1,
overflow = TextOverflow.Ellipsis
)
}
},
trailingIcon = trailingContent ?: trailingIcon?.let {
@Composable {
Icon(
imageVector = it,
painter = it,
contentDescription = null,
tint = AllInTheme.colors.allInLightGrey300
)
}
},
textStyle = AllInTheme.typography.r,
textStyle = AllInTheme.typography.p1,
enabled = enabled,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = imeAction),
keyboardActions = keyboardActions,
@ -135,22 +116,13 @@ fun AllInPasswordField(
keyboardActions: KeyboardActions = KeyboardActions.Default,
errorText: String? = null,
onValueChange: (String) -> Unit,
bringIntoViewRequester: BringIntoViewRequester,
isHiddenByDefault: Boolean = true,
) {
var hidden by remember {
mutableStateOf(isHiddenByDefault)
}
var hidden by remember { mutableStateOf(isHiddenByDefault) }
AllInTextField(
modifier = modifier,
errorText = errorText,
placeholder = placeholder,
imeAction = imeAction,
keyboardActions = keyboardActions,
visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None,
value = value,
onValueChange = onValueChange,
bringIntoViewRequester = bringIntoViewRequester,
modifier = modifier,
trailingContent = {
IconButton(
onClick = { hidden = !hidden }
@ -162,10 +134,73 @@ fun AllInPasswordField(
)
}
},
keyboardType = keyboardType
onValueChange = onValueChange,
errorText = errorText,
visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None,
keyboardType = keyboardType,
imeAction = imeAction,
keyboardActions = keyboardActions
)
}
@Composable
fun AllInFloatTextfield(
modifier: Modifier = Modifier,
value: Float?,
setValue: (Float?) -> Unit
) {
val configuration = LocalConfiguration.current
val locale = remember {
ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault()
}
var stringValue by remember(value) { mutableStateOf(value?.formatToSimple(locale) ?: "") }
AllInTextField(
value = stringValue,
modifier = modifier,
maxChar = 5,
keyboardType = KeyboardType.Number
) {
it.verifyIsFloat(locale)?.let {
stringValue = it
if (it.isNotEmpty()) {
if (it.lastOrNull() !in setOf(',', '.')) {
it.toFloatOrNull(locale)?.let {
setValue(it)
}
}
} else setValue(null)
}
}
}
@Composable
fun AllInIntTextField(
modifier: Modifier = Modifier,
placeholder: String? = null,
maxChar: Int? = 3,
value: Int?,
trailingIcon: Painter? = null,
setValue: (Int?) -> Unit
) {
AllInTextField(
value = value?.toString() ?: "",
placeholder = placeholder,
modifier = modifier,
maxChar = maxChar,
trailingIcon = trailingIcon,
keyboardType = KeyboardType.NumberPassword
) {
if (it.isEmpty()) setValue(null)
else if (it.isDigitsOnly()) {
it.toIntOrNull()?.let {
setValue(it)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
@ -174,8 +209,7 @@ private fun AllInTextFieldPlaceholderPreview() {
AllInTextField(
placeholder = "Email",
value = "",
onValueChange = { },
bringIntoViewRequester = BringIntoViewRequester()
onValueChange = { }
)
}
}
@ -190,8 +224,7 @@ private fun AllInTextFieldValuePreview() {
AllInTextField(
placeholder = "Email",
value = "JohnDoe@mail.com",
onValueChange = { },
bringIntoViewRequester = BringIntoViewRequester()
onValueChange = { }
)
}
}
@ -204,9 +237,8 @@ private fun AllInTextFieldErrorPreview() {
AllInTextField(
placeholder = "Email",
value = "JohnDoe@mail.com",
errorText = "This is an error.",
onValueChange = { },
bringIntoViewRequester = BringIntoViewRequester()
errorText = "This is an error."
)
}
}
@ -220,13 +252,11 @@ private fun AllInTextFieldPasswordPreview() {
AllInPasswordField(
placeholder = "Password",
value = value,
onValueChange = setValue,
bringIntoViewRequester = BringIntoViewRequester()
onValueChange = setValue
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
private fun AllInTextFieldMultilinePreview() {
@ -234,9 +264,8 @@ private fun AllInTextFieldMultilinePreview() {
AllInTextField(
placeholder = "David sera il absent le Lundi matin en cours ?",
value = "",
onValueChange = { },
multiLine = true,
bringIntoViewRequester = BringIntoViewRequester()
onValueChange = { }
)
}
}

@ -6,16 +6,17 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Fireplace
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@ -34,6 +35,8 @@ fun AllInTextIcon(
text: String,
icon: Painter,
color: Color,
brush: Brush? = null,
textStyle: TextStyle = AllInTheme.typography.h1,
position: IconPosition = IconPosition.TRAILING,
size: Int = 15,
iconSize: Int = size,
@ -52,54 +55,15 @@ fun AllInTextIcon(
Text(
text = text,
color = color,
style = AllInTheme.typography.h1,
style = brush?.let { textStyle.copy(brush = it) } ?: textStyle,
fontSize = size.sp
)
Icon(
painter = icon,
contentDescription = null,
modifier = Modifier.size(iconSize.dp),
tint = color
)
}
}
}
}
@Composable
fun AllInTextIcon(
modifier: Modifier = Modifier,
text: String,
icon: ImageVector,
color: Color,
position: IconPosition = IconPosition.TRAILING,
size: Int = 15,
iconSize: Int = size,
) {
val direction =
if (position == IconPosition.LEADING) LayoutDirection.Rtl
else LayoutDirection.Ltr
Box(modifier) {
CompositionLocalProvider(
LocalLayoutDirection provides direction
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(7.dp)
) {
Text(
text = text,
color = color,
style = AllInTheme.typography.h1,
fontSize = size.sp
)
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(iconSize.dp),
tint = color
tint = color,
brush = brush
)
}
}
@ -112,7 +76,7 @@ private fun AllInTextIconPreview() {
AllInTheme {
AllInTextIcon(
text = "value",
icon = Icons.Default.Fireplace,
icon = rememberVectorPainter(image = Icons.Default.Fireplace),
color = AllInTheme.colors.allInBlue
)
}
@ -126,6 +90,7 @@ private fun AllInTextIconReversePreview() {
text = "value",
icon = AllInTheme.icons.allCoins(),
color = AllInTheme.colors.allInBlue,
brush = AllInTheme.colors.allInMainGradient,
position = IconPosition.LEADING
)
}

@ -46,7 +46,7 @@ fun AllInTimePicker(
Text(
text = stringResource(id = R.string.Cancel),
color = AllInTheme.themeColors.onBackground2,
style = AllInTheme.typography.h3
style = AllInTheme.typography.sm1
)
}
},

@ -31,7 +31,7 @@ fun AllInTooltip(
Text(
text = text,
color = AllInTheme.colors.allInLightGrey200,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 12.sp
)
}
@ -121,7 +121,7 @@ private fun AllInTooltipPreview() {
Text(
text = "Généralement une question qui sera répondu par les utilisateurs.",
color = AllInTheme.colors.allInLightGrey200,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 10.sp
)
})

@ -0,0 +1,38 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
@Composable
fun Icon(
painter: Painter,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current,
brush: Brush? = null,
) {
Icon(
painter = painter,
contentDescription = contentDescription,
modifier = brush?.let {
modifier
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
.drawWithCache {
onDrawWithContent {
drawContent()
drawRect(brush, blendMode = BlendMode.SrcAtop)
}
}
} ?: modifier,
tint = tint
)
}

@ -0,0 +1,76 @@
package fr.iut.alldev.allin.ui.core.bet
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
@Composable
fun BetCard(
modifier: Modifier = Modifier,
title: String,
creator: String,
category: String,
date: String,
time: String,
status: BetStatus,
content: @Composable () -> Unit
) {
AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 16.dp
) {
Column(
Modifier.padding(horizontal = 19.dp, vertical = 11.dp)
) {
BetTitleHeader(
title = title,
category = category,
creator = creator,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(11.dp))
BetDateTimeRow(
label = stringResource(id = status.getDateStartLabelId()),
date = date,
time = time
)
}
HorizontalDivider(
thickness = 1.dp,
color = AllInTheme.themeColors.border
)
content()
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetCardPreview() {
AllInTheme {
BetCard(
creator = "Creator",
category = "Category",
title = "Title",
date = "Date",
time = "Time",
status = BetStatus.Waiting
){
Text("Content")
}
}
}

@ -29,7 +29,7 @@ fun BetDateTimeChip(
Text(
text = text,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
style = AllInTheme.typography.h3,
style = AllInTheme.typography.sm1,
textAlign = TextAlign.Center,
color = AllInTheme.colors.allInPurple
)

@ -27,7 +27,7 @@ fun BetDateTimeRow(
Text(
text = label,
fontSize = 15.sp,
style = AllInTheme.typography.m,
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2
)
BetDateTimeChip(date)

@ -38,7 +38,7 @@ fun BetTitleHeader(
color = AllInTheme.themeColors.onMainSurface
),
fontSize = 12.sp,
style = AllInTheme.typography.s,
style = AllInTheme.typography.p2,
color = AllInTheme.themeColors.onBackground2
)
}
@ -47,7 +47,7 @@ fun BetTitleHeader(
text = category,
fontSize = 15.sp,
color = AllInTheme.themeColors.onBackground2,
style = AllInTheme.typography.m
style = AllInTheme.typography.sm2
)
Text(
text = title,

@ -105,7 +105,7 @@ fun AllInSnackbarContent(
Text(
text = text,
color = contentColor,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
overflow = TextOverflow.Ellipsis,
maxLines = 5,
modifier = Modifier.weight(1f)

@ -1,6 +1,5 @@
package fr.iut.alldev.allin.ui.login
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -11,12 +10,10 @@ 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.relocation.BringIntoViewRequester
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -42,7 +39,6 @@ import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.AllInPasswordField
import fr.iut.alldev.allin.ui.core.AllInTextField
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(
navigateToDashboard: () -> Unit,
@ -51,7 +47,6 @@ fun LoginScreen(
) {
val focusManager = LocalFocusManager.current
val bringIntoViewRequester = BringIntoViewRequester()
val loading by remember { loginViewModel.loading }
var hasLoginError by remember { loginViewModel.hasError }
@ -84,7 +79,7 @@ fun LoginScreen(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.Login_title),
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.h3,
style = AllInTheme.typography.sm1,
textAlign = TextAlign.Center,
fontSize = 40.sp
)
@ -93,7 +88,7 @@ fun LoginScreen(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.Login_subtitle),
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
textAlign = TextAlign.Center,
fontSize = 23.sp
)
@ -102,27 +97,25 @@ fun LoginScreen(
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.username),
value = username,
modifier = Modifier.fillMaxWidth(),
onValueChange = setUsername,
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
)
AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.password),
value = password,
onValueChange = setPassword,
bringIntoViewRequester = bringIntoViewRequester,
modifier = Modifier.fillMaxWidth(),
imeAction = ImeAction.Done,
keyboardActions = keyboardActions
keyboardActions = keyboardActions,
onValueChange = setPassword
)
}
ClickableText(
text = AnnotatedString(stringResource(id = R.string.forgot_password)),
style = AllInTheme.typography.m.copy(
style = AllInTheme.typography.sm2.copy(
color = AllInTheme.themeColors.onMainSurface,
fontSize = 15.sp,
),
@ -156,12 +149,12 @@ fun LoginScreen(
text = stringResource(id = R.string.no_account),
color = AllInTheme.themeColors.onMainSurface,
fontSize = 15.sp,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
modifier = Modifier.padding(end = 5.dp)
)
ClickableText(
text = AnnotatedString(stringResource(id = R.string.Register)),
style = AllInTheme.typography.r.copy(
style = AllInTheme.typography.p1.copy(
color = AllInTheme.colors.allInPurple,
fontSize = 15.sp,
fontWeight = FontWeight.Bold

@ -11,8 +11,9 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betResult.BetResultBottomSheet
import fr.iut.alldev.allin.ui.betStatus.BetStatusBottomSheet
import fr.iut.alldev.allin.ui.betStatus.visitor.BetStatusBottomSheetDisplayBetVisitor
import fr.iut.alldev.allin.ui.betStatus.vo.BetStatusBottomSheetBetDisplayer
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.snackbar.AllInSnackbarVisualsImpl
import fr.iut.alldev.allin.ui.main.components.AllInScaffold
@ -21,7 +22,6 @@ import fr.iut.alldev.allin.ui.navigation.Routes
import fr.iut.alldev.allin.ui.navigation.TopLevelDestination
import fr.iut.alldev.allin.ui.navigation.drawer.AllInDrawer
import fr.iut.alldev.allin.ui.navigation.popUpTo
import fr.iut.alldev.allin.vo.bet.factory.toBetVO
import kotlinx.coroutines.launch
private val topLevelDestinations = listOf(
@ -67,15 +67,18 @@ fun MainScreen(
var loading by remember { mainViewModel.loading }
val currentUser = remember { mainViewModel.currentUserState }
val (selectedBet, setSelectedBet) = remember { mainViewModel.selectedBet }
val selectedBet by remember { mainViewModel.selectedBet }
val (wonBet, setWonBet) = remember { mainViewModel.wonBet }
val (statusVisibility, sheetBackVisibility, setStatusVisibility) = rememberBetStatusVisibilities()
val (participateSheetVisibility, setParticipateSheetVisibility) = remember {
mutableStateOf(false)
}
val betStatusDisplayVisitor = remember {
BetStatusBottomSheetDisplayBetVisitor(
userCoinAmount = currentUser.userCoins,
onParticipate = {
mainViewModel.participateToBet(it)
}
val (displayResult, setDisplayResult) = remember { mutableStateOf(true) }
val betStatusDisplayer = remember {
BetStatusBottomSheetBetDisplayer(
openParticipateSheet = { setParticipateSheetVisibility(true) }
)
}
@ -100,7 +103,7 @@ fun MainScreen(
}
}
val bottomSheetState = rememberModalBottomSheetState(
val statusBottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
confirmValueChange = {
if (it == SheetValue.Hidden) {
@ -110,6 +113,10 @@ fun MainScreen(
}
)
val resultBottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
AllInDrawer(
drawerState = drawerState,
destinations = topLevelDestinations,
@ -133,7 +140,7 @@ fun MainScreen(
snackbarHostState = snackbarHostState
) {
LaunchedEffect(key1 = it) {
betStatusDisplayVisitor.paddingValues.value = it
betStatusDisplayer.paddingValues.value = it
}
Column(
modifier = Modifier
@ -145,8 +152,8 @@ fun MainScreen(
AllInDrawerNavHost(
navController = navController,
selectBet = { bet, participate ->
setSelectedBet(bet)
betStatusDisplayVisitor.participateBottomSheetVisibility.value = participate
mainViewModel.openBetDetail(bet)
setParticipateSheetVisibility(participate)
setStatusVisibility(true)
},
setLoading = { loading = it },
@ -156,15 +163,32 @@ fun MainScreen(
}
}
wonBet?.let {
BetResultBottomSheet(
state = resultBottomSheetState,
sheetVisibility = displayResult,
onDismiss = { setDisplayResult(false) },
bet = wonBet,
username = currentUser.user.username,
coinAmount = 1630,
stake = 1630,
winnings = 1630,
odds = 3.62f
)
}
BetStatusBottomSheet(
state = bottomSheetState,
state = statusBottomSheetState,
sheetVisibility = statusVisibility.value,
sheetBackVisibility = sheetBackVisibility.value,
onDismiss = {
setStatusVisibility(false)
},
bet = selectedBet?.toBetVO(),
visitor = betStatusDisplayVisitor
onDismiss = { setStatusVisibility(false) },
betDetail = selectedBet,
paddingValues = betStatusDisplayer.paddingValues.value,
displayBet = { betStatusDisplayer.DisplayBet(it) },
userCoinAmount = mainViewModel.currentUserState.userCoins,
onParticipate = { stake, response -> mainViewModel.participateToBet(stake, response) },
participateSheetVisibility = participateSheetVisibility,
setParticipateSheetVisibility = setParticipateSheetVisibility
)
AllInLoading(visible = loading)
BackHandler(

@ -8,6 +8,9 @@ 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.keystore.AllInKeystoreManager
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType
@ -23,19 +26,38 @@ class UserState(val user: User) {
@HiltViewModel
class MainViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() {
var loading = mutableStateOf(false)
val currentUserState = UserState(currentUser)
val selectedBet = mutableStateOf<Bet?>(null)
val selectedBet = mutableStateOf<BetDetail?>(null)
val wonBet = mutableStateOf<Bet?>(
null
/* YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator"
)*/
)
val snackbarContent: MutableState<SnackbarContent?> by lazy { mutableStateOf(null) }
fun putSnackbarContent(content: SnackbarContent) {
snackbarContent.value = content
}
fun openBetDetail(bet: Bet) {
viewModelScope.launch {
selectedBet.value = betRepository.getBet(bet.id, keystoreManager.getToken() ?: "")
}
}
fun deleteToken() {
viewModelScope.launch {
@ -43,12 +65,20 @@ class MainViewModel @Inject constructor(
}
}
fun participateToBet(stake: Int) {
fun participateToBet(stake: Int, response: String) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
loading.value = true
currentUserState.userCoins.intValue += 50
Thread.sleep(1000)
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.getToken() ?: "")
}
loading.value = false
}
}

@ -74,7 +74,7 @@ fun AllInDrawer(
) {
Text(
text = stringResource(id = R.string.Logout),
style = AllInTheme.typography.h3,
style = AllInTheme.typography.sm1,
color = AllInTheme.colors.allInDarkGrey50,
fontSize = 16.sp
)

@ -56,7 +56,7 @@ fun DrawerCell(
)
Text(
text = subtitle,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
fontSize = 9.sp,
color = AllInTheme.colors.allInLightGrey300,
)

@ -23,7 +23,7 @@ fun DrawerHeaderStat(
Text(
text = label,
color = AllInTheme.colors.allInLightGrey300,
style = AllInTheme.typography.r
style = AllInTheme.typography.p1
)
}
}

@ -0,0 +1,58 @@
package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
class BetDetailPreviewProvider : PreviewParameterProvider<BetDetail> {
override val values = BetWithStatusPreviewProvider().values.map {
BetDetail(
bet = it,
answers = listOf(
BetAnswerDetail(
response = YES_VALUE,
totalStakes = 300,
totalParticipants = 2,
highestStake = 200,
odds = 1.0f
),
BetAnswerDetail(
response = NO_VALUE,
totalStakes = 150,
totalParticipants = 1,
highestStake = 150,
odds = 2.0f
),
),
participations = listOf(
Participation(
betId = it.id,
username = "User1",
response = YES_VALUE,
stake = 200
),
Participation(
betId = it.id,
username = "User2",
response = YES_VALUE,
stake = 100
),
Participation(
betId = it.id,
username = "MyUser",
response = NO_VALUE,
stake = 150
)
),
userParticipation = Participation(
betId = it.id,
username = "MyUser",
response = NO_VALUE,
stake = 150
)
)
}
}

@ -0,0 +1,54 @@
package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
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
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import java.time.ZonedDateTime
class BetPreviewProvider : PreviewParameterProvider<Bet> {
override val values = sequenceOf(
YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator"
),
MatchBet(
id = "2",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator",
nameTeam1 = "The Monarchs",
nameTeam2 = "Climate Change"
),
CustomBet(
id = "3",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.Finished(BetFinishedStatus.WON),
creator = "creator",
possibleAnswers = listOf(
"Answer 1",
"Answer 2",
"Answer 3",
"Answer 4"
)
),
)
}

@ -1,14 +0,0 @@
package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import fr.iut.alldev.allin.data.model.bet.BetFinishedStatus
import fr.iut.alldev.allin.data.model.bet.BetStatus
class BetStatusPreviewProvider: PreviewParameterProvider<BetStatus> {
override val values = sequenceOf(
BetStatus.InProgress,
BetStatus.Waiting,
BetStatus.Finished(BetFinishedStatus.WON),
BetStatus.Finished(BetFinishedStatus.LOST)
)
}

@ -0,0 +1,8 @@
package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import fr.iut.alldev.allin.data.model.bet.BetStatus
class BetStatusPreviewProvider : PreviewParameterProvider<BetStatus> {
override val values = BetStatus.entries
}

@ -0,0 +1,56 @@
package fr.iut.alldev.allin.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
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.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import java.time.ZonedDateTime
class BetWithStatusPreviewProvider : PreviewParameterProvider<Bet> {
override val values = BetStatus.entries.flatMap { status ->
sequenceOf(
YesNoBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = status,
creator = "creator"
),
MatchBet(
id = "2",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = status,
creator = "creator",
nameTeam1 = "The Monarchs",
nameTeam2 = "Climate Change"
),
CustomBet(
id = "3",
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = status,
creator = "creator",
possibleAnswers = listOf(
"Answer 1",
"Answer 2",
"Answer 3",
"Answer 4"
)
)
)
}
}

@ -2,7 +2,6 @@ package fr.iut.alldev.allin.ui.register
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.Text
@ -49,7 +48,6 @@ fun RegisterScreen(
val (password, setPassword) = remember { registerViewModel.password }
val (passwordValidation, setPasswordValidation) = remember { registerViewModel.passwordValidation }
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val scrollState = rememberScrollState()
val usernameFieldName = stringResource(id = R.string.username)
@ -89,7 +87,7 @@ fun RegisterScreen(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.Hello_x, username),
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.h3,
style = AllInTheme.typography.sm1,
textAlign = TextAlign.Center,
fontSize = 40.sp,
overflow = TextOverflow.Ellipsis,
@ -99,7 +97,7 @@ fun RegisterScreen(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.Register_title),
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.h3,
style = AllInTheme.typography.sm1,
textAlign = TextAlign.Center,
fontSize = 40.sp
)
@ -108,7 +106,7 @@ fun RegisterScreen(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.Register_subtitle),
color = AllInTheme.themeColors.onMainSurface,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
textAlign = TextAlign.Center,
fontSize = 23.sp
)
@ -117,46 +115,42 @@ fun RegisterScreen(
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = usernameFieldName,
value = username,
onValueChange = setUsername,
modifier = Modifier.fillMaxWidth(),
maxChar = 20,
onValueChange = setUsername,
errorText = usernameError.errorResource(),
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
)
AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = emailFieldName,
value = email,
modifier = Modifier.fillMaxWidth(),
onValueChange = setEmail,
errorText = emailError.errorResource(),
keyboardType = KeyboardType.Email,
bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
)
AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = passwordFieldName,
value = password,
errorText = passwordError.errorResource(),
onValueChange = setPassword,
bringIntoViewRequester = bringIntoViewRequester,
modifier = Modifier.fillMaxWidth(),
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
keyboardActions = keyboardActions,
errorText = passwordError.errorResource(),
onValueChange = setPassword
)
AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.confirm_password),
value = passwordValidation,
errorText = passwordValidationError.errorResource(),
onValueChange = setPasswordValidation,
bringIntoViewRequester = bringIntoViewRequester,
modifier = Modifier.fillMaxWidth(),
imeAction = ImeAction.Done,
keyboardActions = keyboardActions
keyboardActions = keyboardActions,
errorText = passwordValidationError.errorResource(),
onValueChange = setPasswordValidation
)
}
}
@ -184,12 +178,12 @@ fun RegisterScreen(
text = stringResource(id = R.string.already_have_account),
color = AllInTheme.themeColors.onMainSurface,
fontSize = 15.sp,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
modifier = Modifier.padding(end = 5.dp)
)
ClickableText(
text = AnnotatedString(stringResource(id = R.string.Login)),
style = AllInTheme.typography.r.copy(
style = AllInTheme.typography.p1.copy(
color = AllInTheme.colors.allInPurple,
fontSize = 15.sp,
fontWeight = FontWeight.Bold

@ -88,7 +88,7 @@ fun WelcomeScreen(
text = stringResource(id = R.string.welcome_subtitle),
color = AllInTheme.themeColors.onBackground,
fontSize = 15.sp,
style = AllInTheme.typography.r
style = AllInTheme.typography.p1
)
Spacer(modifier = Modifier.height(78.dp))
AllInButton(
@ -106,12 +106,12 @@ fun WelcomeScreen(
text = stringResource(id = R.string.already_have_account),
color = AllInTheme.themeColors.tint1,
fontSize = 15.sp,
style = AllInTheme.typography.r,
style = AllInTheme.typography.p1,
modifier = Modifier.padding(end = 5.dp)
)
ClickableText(
text = AnnotatedString(stringResource(id = R.string.Login)),
style = AllInTheme.typography.r.copy(
style = AllInTheme.typography.p1.copy(
color = AllInTheme.themeColors.tint1,
fontSize = 15.sp,
fontWeight = FontWeight.Bold

@ -1,12 +0,0 @@
package fr.iut.alldev.allin.utils
import java.io.Serializable
data class Quadruple<out A, out B, out C, out D>(
val first: A,
val second: B,
val third: C,
val fourth: D
) : Serializable {
override fun toString(): String = "($first, $second, $third, $fourth)"
}

@ -1,8 +0,0 @@
package fr.iut.alldev.allin.vo
import androidx.compose.runtime.Composable
interface ViewObject<V : Visitor>{
@Composable
fun Accept(v: V)
}

@ -1,3 +0,0 @@
package fr.iut.alldev.allin.vo
interface Visitor

@ -0,0 +1,27 @@
package fr.iut.alldev.allin.vo.bet
import androidx.compose.runtime.Composable
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
interface BetDisplayer {
@Composable
fun DisplayBet(betDetail: BetDetail) {
when (betDetail.bet) {
is CustomBet -> DisplayCustomBet(betDetail)
is MatchBet -> DisplayMatchBet(betDetail)
is YesNoBet -> DisplayYesNoBet(betDetail)
}
}
@Composable
fun DisplayYesNoBet(betDetail: BetDetail)
@Composable
fun DisplayMatchBet(betDetail: BetDetail)
@Composable
fun DisplayCustomBet(betDetail: BetDetail)
}

@ -1,28 +0,0 @@
package fr.iut.alldev.allin.vo.bet
import androidx.compose.runtime.Composable
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.vo.ViewObject
import fr.iut.alldev.allin.vo.bet.visitor.DisplayBetVisitor
abstract class BetVO<T: Bet>(val bet: T)
: ViewObject<DisplayBetVisitor> {
@Composable
abstract override fun Accept(v: DisplayBetVisitor)
}
class YesNoBetVO(bet: YesNoBet) : BetVO<YesNoBet>(bet){
@Composable
override fun Accept(v: DisplayBetVisitor){
v.VisitYesNoBet(b = bet)
}
}
class MatchBetVO(bet: MatchBet) : BetVO<MatchBet>(bet){
@Composable
override fun Accept(v: DisplayBetVisitor){
v.VisitMatchBet(b = bet)
}
}

@ -1,30 +0,0 @@
package fr.iut.alldev.allin.vo.bet.factory
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.vo.bet.BetVO
import fr.iut.alldev.allin.vo.bet.MatchBetVO
import fr.iut.alldev.allin.vo.bet.YesNoBetVO
private val betTypeToVOMap = mapOf(
YesNoBet::class.java to YesNoBetVOFactory(),
MatchBet::class.java to MatchBetVOFactory()
)
abstract class BetVOFactory<out T : Bet> {
abstract fun create(bet: @UnsafeVariance T): BetVO<@UnsafeVariance T>
}
class YesNoBetVOFactory : BetVOFactory<YesNoBet>() {
override fun create(bet: YesNoBet) =
YesNoBetVO(bet)
}
class MatchBetVOFactory : BetVOFactory<MatchBet>() {
override fun create(bet: MatchBet) =
MatchBetVO(bet)
}
fun Bet.toBetVO() = betTypeToVOMap[this.javaClass]?.create(this)

@ -1,15 +0,0 @@
package fr.iut.alldev.allin.vo.bet.visitor
import androidx.compose.runtime.Composable
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.vo.Visitor
interface DisplayBetVisitor : Visitor {
@Composable
fun VisitYesNoBet(b: YesNoBet)
@Composable
fun VisitMatchBet(b: MatchBet)
}

File diff suppressed because it is too large Load Diff

@ -118,6 +118,7 @@
<string name="bet_status_in_progress">En cours…</string>
<string name="bet_status_waiting">En attente…</string>
<string name="place_your_bets">Faites vos paris</string>
<string name="bet_status_participants_list">Liste des participants</string>
<!--Bet history-->
<string name="bet_history_current_title">En cours</string>
@ -126,4 +127,10 @@
<string name="bet_history_status_won">%s [icon] gagnés !</string>
<string name="bet_history_status_lost">%s [icon] perdus !</string>
<!--Bet result-->
<string name="bet_result_congratulations">FÉLICITATIONS</string>
<string name="bet_result_you_win">Vous remportez</string>
<string name="bet_result_odds">Cote totale</string>
<string name="bet_result_stake">Mise</string>
<string name="bet_result_winnings">Gains</string>
</resources>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

@ -118,6 +118,7 @@
<string name="bet_status_in_progress">In progress…</string>
<string name="bet_status_waiting">Waiting…</string>
<string name="place_your_bets">Place your bets</string>
<string name="bet_status_participants_list">Participants</string>
<!--Bet history-->
<string name="bet_history_current_title">Current</string>
@ -126,4 +127,12 @@
<string name="bet_history_status_won">%s [icon] won !</string>
<string name="bet_history_status_lost">%s [icon] lost !</string>
<!--Bet result-->
<string name="bet_result_congratulations">CONGRATULATIONS</string>
<string name="bet_result_you_win">You win</string>
<string name="bet_result_odds">Total odds</string>
<string name="bet_result_stake">Stake</string>
<string name="bet_result_winnings">Winnings</string>
</resources>

@ -2,15 +2,22 @@ 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.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.ResponseUser
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
interface AllInApi {
companion object {
fun String.formatBearerToken() = "Bearer $this"
}
@POST("users/login")
suspend fun login(@Body body: CheckUser): ResponseUser
@ -21,8 +28,14 @@ interface AllInApi {
suspend fun login(@Header("Authorization") token: String): ResponseUser
@POST("bets/add")
suspend fun createBet(@Body body: RequestBet)
suspend fun createBet(@Header("Authorization") token: String, @Body body: RequestBet)
@GET("bets/gets")
suspend fun getAllBets(): List<ResponseBet>
@GET("betdetail/get/{id}")
suspend fun getBet(@Header("Authorization") token: String, @Path("id") id: String): ResponseBetDetail
@POST("participations/add")
suspend fun participateToBet(@Header("Authorization") token: String, @Body body: RequestParticipation)
}

@ -0,0 +1,239 @@
package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException
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.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.ResponseBetAnswerDetail
import fr.iut.alldev.allin.data.api.model.ResponseBetDetail
import fr.iut.alldev.allin.data.api.model.ResponseParticipation
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import java.time.ZonedDateTime
import java.util.UUID
class MockAllInApi : AllInApi {
private fun getUserFromToken(token: String) =
mockUsers.find { it.first.token == token }
private fun getAnswerDetails(bet: ResponseBet, participations: List<ResponseParticipation>): List<ResponseBetAnswerDetail> {
return bet.response.map { response ->
val responseParticipations = participations.filter { it.answer == response }
ResponseBetAnswerDetail(
response = response,
totalStakes = responseParticipations.sumOf { it.stake },
totalParticipants = responseParticipations.size,
highestStake = responseParticipations.maxOfOrNull { it.stake } ?: 0,
odds = if (participations.isEmpty()) 0.0f else responseParticipations.size / participations.size.toFloat()
)
}
}
override suspend fun login(body: CheckUser): ResponseUser {
return mockUsers.find { it.first.username == body.login && it.second == body.password }?.first
?: throw AllInAPIException("Invalid login/password.")
}
override suspend fun login(token: String): ResponseUser {
return getUserFromToken(token)?.first
?: throw AllInAPIException("Invalid token")
}
override suspend fun register(body: RequestUser): ResponseUser {
val response = ResponseUser(
id = UUID.randomUUID().toString(),
username = body.username,
email = body.email,
nbCoins = 500,
token = "${body.username} ${mockUsers.size}"
) to body.password
mockUsers.add(response)
return response.first
}
override suspend fun createBet(token: String, body: RequestBet) {
mockBets.add(
ResponseBet(
id = UUID.randomUUID().toString(),
theme = body.theme,
sentenceBet = body.sentenceBet,
endRegistration = body.endRegistration,
endBet = body.endBet,
isPrivate = body.isPrivate,
response = body.response,
createdBy = ""
)
)
}
override suspend fun getAllBets(): List<ResponseBet> = mockBets.toList()
override suspend fun getBet(token: String, id: String): ResponseBetDetail {
val bet = mockBets.find { it.id == id } ?: throw AllInAPIException("Bet not found")
val user = getUserFromToken(token) ?: throw AllInAPIException("Invalid login/password.")
val betParticipations = mockParticipations.filter { it.betId == bet.id }
val userParticipation = betParticipations.find { it.username == user.first.username }
return ResponseBetDetail(
bet = bet,
answers = getAnswerDetails(bet, betParticipations),
participations = betParticipations,
userParticipation = userParticipation
)
}
override suspend fun participateToBet(token: String, body: RequestParticipation) {
getUserFromToken(token)?.let {
mockParticipations.add(
ResponseParticipation(
id = "",
betId = body.betId,
username = it.first.username,
answer = body.answer,
stake = body.stake
)
)
} ?: throw AllInAPIException("Invalid token")
}
}
private val mockUsers = mutableListOf(
ResponseUser(
id = "UUID 1",
username = "User 1",
email = "john@doe.fr",
nbCoins = 250,
token = "token 1"
) to "12345",
ResponseUser(
id = "UUID 2",
username = "User 2",
email = "john@doe.fr",
nbCoins = 250,
token = "token 2"
) to "12345",
ResponseUser(
id = "UUID 3",
username = "User 3",
email = "john@doe.fr",
nbCoins = 250,
token = "token 3"
) to "12345",
ResponseUser(
id = "UUID 4",
username = "User 4",
email = "john@doe.fr",
nbCoins = 250,
token = "token 4"
) to "12345",
ResponseUser(
id = "UUID 5",
username = "User 5",
email = "john@doe.fr",
nbCoins = 250,
token = "token 5"
) to "12345",
ResponseUser(
id = "UUID 6",
username = "User 6",
email = "john@doe.fr",
nbCoins = 250,
token = "token 6"
) to "12345",
ResponseUser(
id = "UUID 7",
username = "User 7",
email = "john@doe.fr",
nbCoins = 250,
token = "token 7"
) to "12345"
)
private val mockParticipations = mutableListOf(
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[0].first.username,
answer = YES_VALUE,
stake = 200
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[1].first.username,
answer = NO_VALUE,
stake = 1500
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[2].first.username,
answer = YES_VALUE,
stake = 300
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[3].first.username,
answer = YES_VALUE,
stake = 25
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[4].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[5].first.username,
answer = NO_VALUE,
stake = 222
),
ResponseParticipation(
id = "",
betId = "UUID1",
username = mockUsers[6].first.username,
answer = NO_VALUE,
stake = 222
)
)
private val mockBets = mutableListOf(
ResponseBet(
id = "UUID1",
theme = "Études",
sentenceBet = "Dave va arriver en retard demain matin ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure"
),
ResponseBet(
id = "UUID2",
theme = "Études",
sentenceBet = "Dave va arriver en retard demain matin ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf("Answer 1", "Answer 2", "Answer 3", "Answer 4"),
createdBy = "User 2"
),
ResponseBet(
id = "UUID3",
theme = "Sport",
sentenceBet = "Nouveau record du monde ?",
endRegistration = ZonedDateTime.now().plusDays(3),
endBet = ZonedDateTime.now().plusDays(4),
isPrivate = false,
response = listOf(YES_VALUE, NO_VALUE),
createdBy = "Armure"
)
)

@ -14,14 +14,15 @@ class ErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if(!response.isSuccessful){
if (!response.isSuccessful) {
when (response.code) {
404 -> throw AllInNotFoundException(response.message)
401 -> throw AllInUnauthorizedException(response.message)
else -> throw AllInUnsuccessfulException(response.message)
}
}
if (response.body?.contentType()?.subtype != "json") {
response.body?.contentType()?.subtype?.takeIf { it != "json" }?.let {
throw AllInAPIException(response.message)
}

@ -4,26 +4,31 @@ 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.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.data.serialization.SimpleDateSerializer
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
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 id: String?,
val theme: String,
val sentenceBet: String,
@Serializable(SimpleDateSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(SimpleDateSerializer::class) var endBet: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
var isPrivate: Boolean,
var response: List<String>,
val createdBy: String
) {
fun toBet(): Bet {
if (response.toSet() == setOf("Yes", "No")) {
if (response.toSet() == setOf(YES_VALUE, NO_VALUE)) {
return YesNoBet(
id = id ?: "",
theme = theme,
phrase = sentenceBet,
endRegisterDate = endRegistration,
@ -34,6 +39,7 @@ data class ResponseBet(
)
} else {
return CustomBet(
id = id ?: "",
theme = theme,
phrase = sentenceBet,
endRegisterDate = endRegistration,
@ -41,7 +47,7 @@ data class ResponseBet(
isPublic = !isPrivate,
betStatus = BetStatus.Waiting,
creator = createdBy,
possibleAnswers = response.toSet()
possibleAnswers = response
)
}
}
@ -50,11 +56,48 @@ data class ResponseBet(
@Keep
@Serializable
data class RequestBet(
val id: String = "",
val theme: String,
val sentenceBet: String,
@Serializable(SimpleDateSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(SimpleDateSerializer::class) var endBet: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
var isPrivate: Boolean,
var response: List<String>,
val createdBy: String
var response: List<String>
)
@Keep
@Serializable
data class ResponseBetAnswerDetail(
val response: String,
val totalStakes: Int,
val totalParticipants: Int,
val highestStake: Int,
val odds: Float
) {
fun toAnswerDetail() =
BetAnswerDetail(
response = response,
totalStakes = totalStakes,
totalParticipants = totalParticipants,
highestStake = highestStake,
odds = odds
)
}
@Keep
@Serializable
data class ResponseBetDetail(
val bet: ResponseBet,
val answers: List<ResponseBetAnswerDetail>,
val participations: List<ResponseParticipation>,
val userParticipation: ResponseParticipation? = null
) {
fun toBetDetail() =
BetDetail(
bet = bet.toBet(),
answers = answers.map { it.toAnswerDetail() },
participations = participations.map { it.toParticipation() },
userParticipation = userParticipation?.toParticipation()
)
}

@ -0,0 +1,31 @@
package fr.iut.alldev.allin.data.api.model
import androidx.annotation.Keep
import fr.iut.alldev.allin.data.model.bet.Participation
import kotlinx.serialization.Serializable
@Keep
@Serializable
data class ResponseParticipation(
val id: String,
val betId: String,
val username: String,
val answer: String,
val stake: Int
) {
fun toParticipation() =
Participation(
betId = betId,
username = username,
response = answer,
stake = stake
)
}
@Keep
@Serializable
data class RequestParticipation(
val betId: String,
val answer: String,
val stake: Int
)

@ -9,23 +9,23 @@ import kotlinx.serialization.Serializable
data class RequestUser(
val username: String,
val email: String,
val password: String,
var nbCoins: Int,
val password: String
)
@Keep
@Serializable
data class ResponseUser(
val id: String = "",
val username: String,
val email: String,
var nbCoins: Int,
var token: String? = null,
var token: String? = null
) {
fun toUser() = User(
id = id,
username = username,
email = email,
id = "",
coins = nbCoins
coins = nbCoins.toInt()
)
}

@ -5,18 +5,25 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.api.MockAllInApi
import fr.iut.alldev.allin.data.di.NetworkModule.createRetrofit
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Singleton
const val mock = false
@Module
@InstallIn(SingletonComponent::class)
class ApiModule {
@Provides
@Singleton
fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi {
val retrofit = createRetrofit(url = url, okHttpClient = okHttpClient)
return retrofit.create(AllInApi::class.java)
return if (mock) {
MockAllInApi()
} else {
val retrofit = createRetrofit(url = url, okHttpClient = okHttpClient)
retrofit.create(AllInApi::class.java)
}
}
}

@ -17,7 +17,7 @@ import javax.inject.Qualifier
internal val json by lazy {
Json {
ignoreUnknownKeys = true
encodeDefaults = false
encodeDefaults = true
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save