Merge pull request 'Sprint_3' (#3) from Sprint_3 into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #3
pull/4/head V3
Arthur VALIN 1 year ago
commit 8c073bdec0

@ -10,7 +10,7 @@ plugins {
} }
// Keystore // Keystore
val keystorePropertiesFile = rootProject.file("app/keys/keystore.properties") val keystorePropertiesFile = rootProject.file("keys/keystore.properties")
val keystoreProperties = Properties() val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile)) keystoreProperties.load(FileInputStream(keystorePropertiesFile))
@ -33,11 +33,16 @@ android {
} }
} }
signingConfigs { signingConfigs {
named(BuildType.DEBUG.name) {
storeFile = file(keystoreProperties["debugStoreFile"].toString())
}
register(BuildType.RELEASE.name) { register(BuildType.RELEASE.name) {
storeFile = file(keystoreProperties["store"].toString()) storeFile = file(keystoreProperties["releaseStoreFile"].toString())
storePassword = keystoreProperties["password"].toString() storePassword = keystoreProperties["passwordRelease"].toString()
keyPassword = keystoreProperties["password"].toString() keyAlias = keystoreProperties["aliasRelease"].toString()
keyPassword = keystoreProperties["passwordRelease"].toString()
} }
} }
@ -102,5 +107,6 @@ dependencies {
androidTestImplementation(libs.test.androidx.junit) androidTestImplementation(libs.test.androidx.junit)
androidTestImplementation(libs.hilt.androidTesting) androidTestImplementation(libs.hilt.androidTesting)
kaptAndroidTest(libs.hilt.androidCompiler) 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 # If you keep the line number information, uncomment this to
# hide the original source file name. # 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 package fr.iut.alldev.allin.test.mock
import fr.iut.alldev.allin.data.model.bet.BetStatus 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.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.YesNoBet
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -14,7 +15,8 @@ object Bets {
endRegisterDate = ZonedDateTime.now(), endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(), endBetDate = ZonedDateTime.now(),
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress betStatus = BetStatus.InProgress,
creator = "creator",
), ),
MatchBet( MatchBet(
theme = "Theme", theme = "Theme",
@ -24,7 +26,23 @@ object Bets {
isPublic = true, isPublic = true,
betStatus = BetStatus.InProgress, betStatus = BetStatus.InProgress,
nameTeam1 = "Team_1", 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.test.mock.Bets
import fr.iut.alldev.allin.ui.MainActivity import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.vo.bet.factory.toBetVO import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer
import fr.iut.alldev.allin.vo.bet.visitor.BetTestVisitor
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -30,37 +29,55 @@ class BetVOTest {
} }
companion object { companion object {
val visitor = BetTestVisitor() val displayer = BetTestDisplayer()
} }
@Test @Test
fun testVisitor_shouldDisplayYesNoBetUI(){ fun testDisplayer_shouldDisplayYesNoBetUI(){
//Given //Given
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme{
Bets.bets[0].toBetVO()?.Accept(v = visitor) displayer.DisplayBet(Bets.bets[0])
} }
} }
//Expect //Expect
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertExists() composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertExists()
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertDoesNotExist() composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertDoesNotExist()
composeTestRule.onNodeWithTag(TestTags.CUSTOM_BET.tag).assertDoesNotExist()
} }
@Test @Test
fun testVisitor_shouldDisplayMatchUI(){ fun testDisplayer_shouldDisplayMatchUI(){
//Given //Given
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme{
Bets.bets[1].toBetVO()?.Accept(v = visitor) displayer.DisplayBet(Bets.bets[1])
} }
} }
//Expect //Expect
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertExists() composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertExists()
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertDoesNotExist() 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.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag 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.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.test.TestTags import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetTestVisitor : DisplayBetVisitor { class BetTestDisplayer : BetDisplayer {
@Composable @Composable
override fun VisitYesNoBet(b: YesNoBet) { override fun DisplayYesNoBet(bet: YesNoBet) {
Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag)) Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
} }
@Composable @Composable
override fun VisitMatchBet(b: MatchBet) { override fun DisplayMatchBet(bet: MatchBet) {
Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag)) 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 { internal object CurrentUserModule {
@AllInCurrentUser @AllInCurrentUser
@Provides @Provides
fun provideUser( fun provideUser(userRepository: UserRepository) = userRepository.currentUser
userRepository: UserRepository
) = userRepository.currentUser
} }

@ -12,7 +12,7 @@ import fr.iut.alldev.allin.data.model.bet.BetType
@StringRes @StringRes
fun BetType.getTitleId(): Int { fun BetType.getTitleId(): Int {
return when (this) { return when (this) {
BetType.YES_NO -> R.string.yes_no BetType.YES_NO -> R.string.yes_no
BetType.MATCH -> R.string.sport_match BetType.MATCH -> R.string.sport_match
BetType.CUSTOM -> R.string.custom_answers 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 package fr.iut.alldev.allin.keystore
import androidx.security.crypto.MasterKeys import androidx.security.crypto.MasterKey
abstract class AllInKeystoreManager { abstract class AllInKeystoreManager {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) protected abstract val masterKey: MasterKey
abstract fun createKeystore() abstract fun createKeystore()
abstract fun putToken(token: String) abstract fun putToken(token: String)
abstract fun getToken(): String? abstract fun getToken(): String?

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

@ -2,5 +2,6 @@ package fr.iut.alldev.allin.test
enum class TestTags(val tag: String) { enum class TestTags(val tag: String) {
YES_NO_BET("YES_NO"), 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 allInBetInProgressText: Color,
val allInBetWaitingText: Color, val allInBetWaitingText: Color,
val allInMainGradient: Brush, val allInMainGradient: Brush,
val allInMainGradientReverse: Brush,
val allInBar1stGradient: Brush, val allInBar1stGradient: Brush,
val allInBar2ndGradient: Brush, val allInBar2ndGradient: Brush,
val allInTextGradient: Brush, val allInTextGradient: Brush,
@ -73,6 +74,7 @@ internal val LocalColors = staticCompositionLocalOf {
allInBetInProgressText = Color.Unspecified, allInBetInProgressText = Color.Unspecified,
allInBetWaitingText = Color.Unspecified, allInBetWaitingText = Color.Unspecified,
allInMainGradient = SolidColor(Color.Unspecified), allInMainGradient = SolidColor(Color.Unspecified),
allInMainGradientReverse = SolidColor(Color.Unspecified),
allInBar1stGradient = SolidColor(Color.Unspecified), allInBar1stGradient = SolidColor(Color.Unspecified),
allInBar2ndGradient = SolidColor(Color.Unspecified), allInBar2ndGradient = SolidColor(Color.Unspecified),
allInTextGradient = SolidColor(Color.Unspecified), allInTextGradient = SolidColor(Color.Unspecified),

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

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

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

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

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

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

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

@ -1,10 +1,10 @@
package fr.iut.alldev.allin.ui.betCreation.tabs package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.* import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.ext.formatToMediumDate 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 fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
import java.time.ZonedDateTime import java.time.ZonedDateTime
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BetCreationScreenQuestionTab( fun BetCreationScreenQuestionTab(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
nbFriends: Int, nbFriends: Int,
betTheme: String, betTheme: String,
betThemeError: String?, betThemeError: String?,
setBetTheme: (String)->Unit, setBetTheme: (String) -> Unit,
betPhrase: String, betPhrase: String,
betPhraseError: String?, betPhraseError: String?,
setBetPhrase: (String)->Unit, setBetPhrase: (String) -> Unit,
isPublic: Boolean, isPublic: Boolean,
setIsPublic: (Boolean)->Unit, setIsPublic: (Boolean) -> Unit,
registerDate: ZonedDateTime, registerDate: ZonedDateTime,
registerDateError: String?, registerDateError: String?,
betDate: ZonedDateTime, betDate: ZonedDateTime,
betDateError: String?, betDateError: String?,
selectedFriends: MutableList<Int>, selectedFriends: MutableList<Int>,
setRegisterDateDialog: (Boolean)->Unit, setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean)->Unit, setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean)->Unit, setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean)->Unit, setEndTimeDialog: (Boolean) -> Unit,
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
val bringIntoViewRequester = remember { BringIntoViewRequester() } Column(modifier) {
Column(modifier){
QuestionTabThemePhraseSection( QuestionTabThemePhraseSection(
betTheme = betTheme, betTheme = betTheme,
betThemeError = betThemeError, betThemeError = betThemeError,
@ -47,7 +45,6 @@ fun BetCreationScreenQuestionTab(
betPhrase = betPhrase, betPhrase = betPhrase,
betPhraseError = betPhraseError, betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase, setBetPhrase = setBetPhrase,
bringIntoViewRequester = bringIntoViewRequester,
interactionSource = interactionSource interactionSource = interactionSource
) )
Spacer(modifier = Modifier.height(35.dp)) 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -24,11 +23,10 @@ import fr.iut.alldev.allin.ui.core.AllInTitleInfo
internal fun QuestionTabThemePhraseSection( internal fun QuestionTabThemePhraseSection(
betTheme: String, betTheme: String,
betThemeError: String?, betThemeError: String?,
setBetTheme: (String)->Unit, setBetTheme: (String) -> Unit,
betPhrase: String, betPhrase: String,
betPhraseError: String?, betPhraseError: String?,
setBetPhrase: (String)->Unit, setBetPhrase: (String) -> Unit,
bringIntoViewRequester: BringIntoViewRequester,
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
AllInTitleInfo( AllInTitleInfo(
@ -41,13 +39,12 @@ internal fun QuestionTabThemePhraseSection(
AllInTextField( AllInTextField(
placeholder = stringResource(id = R.string.Theme_placeholder), placeholder = stringResource(id = R.string.Theme_placeholder),
value = betTheme, value = betTheme,
onValueChange = setBetTheme, modifier = Modifier.fillMaxWidth(),
bringIntoViewRequester = bringIntoViewRequester,
borderColor = AllInTheme.colors.white,
maxChar = 20, maxChar = 20,
placeholderFontSize = 13.sp, placeholderFontSize = 13.sp,
onValueChange = setBetTheme,
errorText = betThemeError, errorText = betThemeError,
modifier = Modifier.fillMaxWidth() borderColor = AllInTheme.colors.white
) )
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
AllInTitleInfo( AllInTitleInfo(
@ -60,15 +57,14 @@ internal fun QuestionTabThemePhraseSection(
AllInTextField( AllInTextField(
placeholder = stringResource(id = R.string.Bet_Phrase_placeholder), placeholder = stringResource(id = R.string.Bet_Phrase_placeholder),
value = betPhrase, value = betPhrase,
borderColor = AllInTheme.colors.white, modifier = Modifier
onValueChange = setBetPhrase, .fillMaxWidth()
bringIntoViewRequester = bringIntoViewRequester, .height(100.dp),
multiLine = true,
maxChar = 100, maxChar = 100,
placeholderFontSize = 13.sp, placeholderFontSize = 13.sp,
multiLine = true,
onValueChange = setBetPhrase,
errorText = betPhraseError, errorText = betPhraseError,
modifier = Modifier borderColor = AllInTheme.colors.white
.fillMaxWidth()
.height(100.dp)
) )
} }

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

@ -1,24 +1,13 @@
package fr.iut.alldev.allin.ui.betHistory.components package fr.iut.alldev.allin.ui.betHistory.components
import android.content.res.Configuration 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.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter 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.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard import fr.iut.alldev.allin.ui.core.bet.BetCard
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.BetStatusPreviewProvider import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider
@Composable @Composable
@ -32,30 +21,15 @@ fun BetHistoryScreenCard(
status: BetStatus, status: BetStatus,
nbCoins: Int, nbCoins: Int,
) { ) {
AllInCard( BetCard(
modifier = modifier.fillMaxWidth(), title = title,
radius = 16.dp 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( BetHistoryBetStatus(
status = status, status = status,
nbCoins = nbCoins 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.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.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.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.vo.bet.BetVO
internal const val SHEET_HEIGHT = .85f internal const val SHEET_HEIGHT = .85f
@ -22,9 +24,14 @@ fun BetStatusBottomSheet(
state: SheetState, state: SheetState,
sheetVisibility: Boolean, sheetVisibility: Boolean,
sheetBackVisibility: Boolean, sheetBackVisibility: Boolean,
bet: BetVO<Bet>?, betDetail: BetDetail?,
onDismiss: ()->Unit, paddingValues: PaddingValues,
visitor: BetStatusBottomSheetDisplayBetVisitor userCoinAmount: MutableIntState,
onParticipate: (stake: Int, response: String) -> Unit,
onDismiss: () -> Unit,
participateSheetVisibility: Boolean,
setParticipateSheetVisibility: (Boolean) -> Unit,
displayBet: @Composable (BetDetail) -> Unit
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = sheetBackVisibility, visible = sheetBackVisibility,
@ -35,7 +42,7 @@ fun BetStatusBottomSheet(
targetOffsetY = { it } targetOffsetY = { it }
) )
) { ) {
bet?.let { betDetail?.let {
BetStatusBottomSheetBack( BetStatusBottomSheetBack(
status = it.bet.betStatus status = it.bet.betStatus
) )
@ -47,11 +54,45 @@ fun BetStatusBottomSheet(
onDismiss = onDismiss, onDismiss = onDismiss,
state = state, state = state,
scrimColor = Color.Transparent scrimColor = Color.Transparent
){ ) {
var selectedAnswer by remember { mutableStateOf(0) }
var stake by remember { mutableStateOf<Int?>(null) }
Column( Column(
Modifier.fillMaxHeight(SHEET_HEIGHT) 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.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.AllInCoinCount 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 fr.iut.alldev.allin.ui.core.topbar.AllInTopBarCoinCounter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -30,7 +35,14 @@ fun BetStatusParticipationBottomSheet(
coinAmount: Int, coinAmount: Int,
onDismiss: () -> Unit, onDismiss: () -> Unit,
state: SheetState, 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() val scope = rememberCoroutineScope()
AllInBottomSheet( AllInBottomSheet(
@ -38,6 +50,98 @@ fun BetStatusParticipationBottomSheet(
onDismiss = onDismiss, onDismiss = onDismiss,
state = state, state = state,
containerColor = AllInTheme.themeColors.background2 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( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -45,81 +149,45 @@ fun BetStatusParticipationBottomSheet(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = stringResource(id = R.string.place_your_bets), text = stringResource(id = R.string.Possible_winnings),
style = AllInTheme.typography.h2, style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onMainSurface, color = AllInTheme.themeColors.onBackground
fontSize = 20.sp,
modifier = Modifier.padding(start = 18.dp)
) )
AllInTopBarCoinCounter( AllInCoinCount(
amount = coinAmount, amount = stake?.let { (it + (it * odds)).roundToInt() } ?: 0,
backgroundColor = AllInTheme.colors.allInBlue, color = AllInTheme.themeColors.onBackground
textColor = AllInTheme.colors.white,
iconColor = AllInTheme.colors.white,
) )
} }
Spacer(modifier = Modifier.height(30.dp)) AllInButton(
Text( enabled = enabled,
text = betPhrase, color = AllInTheme.colors.allInPurple,
style = AllInTheme.typography.s, text = stringResource(id = R.string.Participate),
color = AllInTheme.themeColors.onMainSurface, textColor = AllInTheme.colors.white,
modifier = Modifier.padding(horizontal = 18.dp) 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
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
private fun BetStatusParticipationBottomSheetPreview() { private fun BetStatusParticipationBottomSheetContentPreview() {
AllInTheme { AllInTheme {
BetStatusParticipationBottomSheet( Column {
sheetVisibility = true, BetStatusParticipationBottomSheetContent(
safeBottomPadding = 5.dp, safeBottomPadding = 0.dp,
betPhrase = "Lorem Ipsum", betPhrase = "Bet phrase",
coinAmount = 125, coinAmount = 3620,
onDismiss = {}, onButtonClick = {},
state = rememberModalBottomSheetState(), elements = emptyList(),
onParticipate = {} 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@ -41,7 +42,7 @@ fun BetStatusWinner(
) { ) {
AllInTextIcon( AllInTextIcon(
text = answer, text = answer,
icon = Icons.Filled.EmojiEvents, icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
color = color, color = color,
size = 50, size = 50,
iconSize = 60, iconSize = 60,
@ -57,13 +58,13 @@ fun BetStatusWinner(
) )
AllInTextIcon( AllInTextIcon(
text = username, text = username,
icon = Icons.Filled.People, icon = rememberVectorPainter(image = Icons.Filled.People),
color = color, color = color,
position = IconPosition.LEADING position = IconPosition.LEADING
) )
AllInTextIcon( AllInTextIcon(
text = "x" + String.format("%.1f", multiplier), text = "x" + String.format("%.1f", multiplier),
icon = Icons.Filled.EmojiEvents, icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
color = color, color = color,
position = IconPosition.LEADING position = IconPosition.LEADING
) )

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

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

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

@ -15,10 +15,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
fun AllInButton( fun AllInButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
color: Color, color: Color,
text: String, text: String,
textColor: Color, textColor: Color,
modifier: Modifier = Modifier,
radius: Dp = 15.dp, radius: Dp = 15.dp,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
@ -26,13 +27,14 @@ fun AllInButton(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier,
radius = radius, radius = radius,
backgroundColor = color backgroundColor = color,
enabled = enabled
) { ) {
Text( Text(
text = text, text = text,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
color = textColor, color = if(enabled) textColor else AllInTheme.themeColors.disabledBorder,
fontSize = 20.sp, fontSize = 20.sp,
modifier = Modifier modifier = Modifier
.padding(vertical = 15.dp) .padding(vertical = 15.dp)
@ -51,6 +53,21 @@ private fun AllInButtonPreview() {
textColor = Color.White 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) .padding(vertical = 8.dp, horizontal = 22.dp)
.alpha(if (isSelected) 0f else 1f), .alpha(if (isSelected) 0f else 1f),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.r, style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onBackground2 color = AllInTheme.themeColors.onBackground2
) )
if (isSelected) { if (isSelected) {

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

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

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

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

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

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

@ -6,8 +6,10 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size 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.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@ -36,11 +39,11 @@ import fr.iut.alldev.allin.theme.AllInTheme
class SelectionElement( class SelectionElement(
val textId: Int, val textId: Int,
val imageVector: ImageVector, val imageVector: ImageVector
) )
@Composable @Composable
fun AllInSelectionLine( private fun AllInSelectionLine(
text: String, text: String,
iconVector: ImageVector?, iconVector: ImageVector?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -91,13 +94,19 @@ fun AllInSelectionLine(
fun AllInSelectionBox( fun AllInSelectionBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isOpen: Boolean, isOpen: Boolean,
borderWidth: Dp? = null,
setIsOpen: (Boolean) -> Unit, setIsOpen: (Boolean) -> Unit,
selected: SelectionElement?, selected: SelectionElement?,
setSelected: (SelectionElement) -> Unit, setSelected: (SelectionElement) -> Unit,
elements: List<SelectionElement>, elements: List<SelectionElement>,
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
AllInCard(modifier.fillMaxWidth()) { AllInCard(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
) {
Column( Column(
Modifier.animateContentSize() 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
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable

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

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

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

@ -31,7 +31,7 @@ fun AllInTooltip(
Text( Text(
text = text, text = text,
color = AllInTheme.colors.allInLightGrey200, color = AllInTheme.colors.allInLightGrey200,
style = AllInTheme.typography.r, style = AllInTheme.typography.p1,
fontSize = 12.sp fontSize = 12.sp
) )
} }
@ -121,7 +121,7 @@ private fun AllInTooltipPreview() {
Text( Text(
text = "Généralement une question qui sera répondu par les utilisateurs.", text = "Généralement une question qui sera répondu par les utilisateurs.",
color = AllInTheme.colors.allInLightGrey200, color = AllInTheme.colors.allInLightGrey200,
style = AllInTheme.typography.r, style = AllInTheme.typography.p1,
fontSize = 10.sp 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 = text, text = text,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp), modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
style = AllInTheme.typography.h3, style = AllInTheme.typography.sm1,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = AllInTheme.colors.allInPurple color = AllInTheme.colors.allInPurple
) )

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

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

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

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

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

@ -8,6 +8,9 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.Participation
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.di.AllInCurrentUser import fr.iut.alldev.allin.di.AllInCurrentUser
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType
@ -23,19 +26,38 @@ class UserState(val user: User) {
@HiltViewModel @HiltViewModel
class MainViewModel @Inject constructor( class MainViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User, @AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager private val keystoreManager: AllInKeystoreManager
) : ViewModel() { ) : ViewModel() {
var loading = mutableStateOf(false) var loading = mutableStateOf(false)
val currentUserState = UserState(currentUser) val 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) } val snackbarContent: MutableState<SnackbarContent?> by lazy { mutableStateOf(null) }
fun putSnackbarContent(content: SnackbarContent) { fun putSnackbarContent(content: SnackbarContent) {
snackbarContent.value = content snackbarContent.value = content
} }
fun openBetDetail(bet: Bet) {
viewModelScope.launch {
selectedBet.value = betRepository.getBet(bet.id, keystoreManager.getToken() ?: "")
}
}
fun deleteToken() { fun deleteToken() {
viewModelScope.launch { viewModelScope.launch {
@ -43,12 +65,20 @@ class MainViewModel @Inject constructor(
} }
} }
fun participateToBet(stake: Int) { fun participateToBet(stake: Int, response: String) {
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
loading.value = true loading.value = true
currentUserState.userCoins.intValue += 50 currentUserState.userCoins.intValue -= stake
Thread.sleep(1000) 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 loading.value = false
} }
} }

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

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

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

@ -88,7 +88,7 @@ fun WelcomeScreen(
text = stringResource(id = R.string.welcome_subtitle), text = stringResource(id = R.string.welcome_subtitle),
color = AllInTheme.themeColors.onBackground, color = AllInTheme.themeColors.onBackground,
fontSize = 15.sp, fontSize = 15.sp,
style = AllInTheme.typography.r style = AllInTheme.typography.p1
) )
Spacer(modifier = Modifier.height(78.dp)) Spacer(modifier = Modifier.height(78.dp))
AllInButton( AllInButton(
@ -106,12 +106,12 @@ fun WelcomeScreen(
text = stringResource(id = R.string.already_have_account), text = stringResource(id = R.string.already_have_account),
color = AllInTheme.themeColors.tint1, color = AllInTheme.themeColors.tint1,
fontSize = 15.sp, fontSize = 15.sp,
style = AllInTheme.typography.r, style = AllInTheme.typography.p1,
modifier = Modifier.padding(end = 5.dp) modifier = Modifier.padding(end = 5.dp)
) )
ClickableText( ClickableText(
text = AnnotatedString(stringResource(id = R.string.Login)), text = AnnotatedString(stringResource(id = R.string.Login)),
style = AllInTheme.typography.r.copy( style = AllInTheme.typography.p1.copy(
color = AllInTheme.themeColors.tint1, color = AllInTheme.themeColors.tint1,
fontSize = 15.sp, fontSize = 15.sp,
fontWeight = FontWeight.Bold 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_in_progress">En cours…</string>
<string name="bet_status_waiting">En attente…</string> <string name="bet_status_waiting">En attente…</string>
<string name="place_your_bets">Faites vos paris</string> <string name="place_your_bets">Faites vos paris</string>
<string name="bet_status_participants_list">Liste des participants</string>
<!--Bet history--> <!--Bet history-->
<string name="bet_history_current_title">En cours</string> <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_won">%s [icon] gagnés !</string>
<string name="bet_history_status_lost">%s [icon] perdus !</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> </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_in_progress">In progress…</string>
<string name="bet_status_waiting">Waiting…</string> <string name="bet_status_waiting">Waiting…</string>
<string name="place_your_bets">Place your bets</string> <string name="place_your_bets">Place your bets</string>
<string name="bet_status_participants_list">Participants</string>
<!--Bet history--> <!--Bet history-->
<string name="bet_history_current_title">Current</string> <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_won">%s [icon] won !</string>
<string name="bet_history_status_lost">%s [icon] lost !</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> </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.CheckUser
import fr.iut.alldev.allin.data.api.model.RequestBet 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.RequestUser
import fr.iut.alldev.allin.data.api.model.ResponseBet 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 fr.iut.alldev.allin.data.api.model.ResponseUser
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path
interface AllInApi { interface AllInApi {
companion object {
fun String.formatBearerToken() = "Bearer $this"
}
@POST("users/login") @POST("users/login")
suspend fun login(@Body body: CheckUser): ResponseUser suspend fun login(@Body body: CheckUser): ResponseUser
@ -21,8 +28,14 @@ interface AllInApi {
suspend fun login(@Header("Authorization") token: String): ResponseUser suspend fun login(@Header("Authorization") token: String): ResponseUser
@POST("bets/add") @POST("bets/add")
suspend fun createBet(@Body body: RequestBet) suspend fun createBet(@Header("Authorization") token: String, @Body body: RequestBet)
@GET("bets/gets") @GET("bets/gets")
suspend fun getAllBets(): List<ResponseBet> 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 { override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() val request = chain.request()
val response = chain.proceed(request) val response = chain.proceed(request)
if(!response.isSuccessful){ if (!response.isSuccessful) {
when (response.code) { when (response.code) {
404 -> throw AllInNotFoundException(response.message) 404 -> throw AllInNotFoundException(response.message)
401 -> throw AllInUnauthorizedException(response.message) 401 -> throw AllInUnauthorizedException(response.message)
else -> throw AllInUnsuccessfulException(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) 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.Bet
import fr.iut.alldev.allin.data.model.bet.BetStatus 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.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.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 kotlinx.serialization.Serializable
import java.time.ZonedDateTime import java.time.ZonedDateTime
@Keep @Keep
@Serializable @Serializable
data class ResponseBet( data class ResponseBet(
val id: Int?, val id: String?,
val theme: String, val theme: String,
val sentenceBet: String, val sentenceBet: String,
@Serializable(SimpleDateSerializer::class) val endRegistration: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(SimpleDateSerializer::class) var endBet: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
var isPrivate: Boolean, var isPrivate: Boolean,
var response: List<String>, var response: List<String>,
val createdBy: String val createdBy: String
) { ) {
fun toBet(): Bet { fun toBet(): Bet {
if (response.toSet() == setOf("Yes", "No")) { if (response.toSet() == setOf(YES_VALUE, NO_VALUE)) {
return YesNoBet( return YesNoBet(
id = id ?: "",
theme = theme, theme = theme,
phrase = sentenceBet, phrase = sentenceBet,
endRegisterDate = endRegistration, endRegisterDate = endRegistration,
@ -34,6 +39,7 @@ data class ResponseBet(
) )
} else { } else {
return CustomBet( return CustomBet(
id = id ?: "",
theme = theme, theme = theme,
phrase = sentenceBet, phrase = sentenceBet,
endRegisterDate = endRegistration, endRegisterDate = endRegistration,
@ -41,7 +47,7 @@ data class ResponseBet(
isPublic = !isPrivate, isPublic = !isPrivate,
betStatus = BetStatus.Waiting, betStatus = BetStatus.Waiting,
creator = createdBy, creator = createdBy,
possibleAnswers = response.toSet() possibleAnswers = response
) )
} }
} }
@ -50,11 +56,48 @@ data class ResponseBet(
@Keep @Keep
@Serializable @Serializable
data class RequestBet( data class RequestBet(
val id: String = "",
val theme: String, val theme: String,
val sentenceBet: String, val sentenceBet: String,
@Serializable(SimpleDateSerializer::class) val endRegistration: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
@Serializable(SimpleDateSerializer::class) var endBet: ZonedDateTime, @Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
var isPrivate: Boolean, var isPrivate: Boolean,
var response: List<String>, var response: List<String>
val createdBy: 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( data class RequestUser(
val username: String, val username: String,
val email: String, val email: String,
val password: String, val password: String
var nbCoins: Int,
) )
@Keep @Keep
@Serializable @Serializable
data class ResponseUser( data class ResponseUser(
val id: String = "",
val username: String, val username: String,
val email: String, val email: String,
var nbCoins: Int, var nbCoins: Int,
var token: String? = null, var token: String? = null
) { ) {
fun toUser() = User( fun toUser() = User(
id = id,
username = username, username = username,
email = email, email = email,
id = "", coins = nbCoins.toInt()
coins = nbCoins
) )
} }

@ -5,18 +5,25 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.api.AllInApi import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.api.MockAllInApi
import fr.iut.alldev.allin.data.di.NetworkModule.createRetrofit import fr.iut.alldev.allin.data.di.NetworkModule.createRetrofit
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import javax.inject.Singleton import javax.inject.Singleton
const val mock = false
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
class ApiModule { class ApiModule {
@Provides @Provides
@Singleton @Singleton
fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi { fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi {
val retrofit = createRetrofit(url = url, okHttpClient = okHttpClient) return if (mock) {
return retrofit.create(AllInApi::class.java) 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 { internal val json by lazy {
Json { Json {
ignoreUnknownKeys = true 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