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

Reviewed-on: #5
master
Arthur VALIN 5 months ago
commit a75ba9451a

@ -7,6 +7,7 @@ plugins {
id("kotlin-android")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
id("com.starter.easylauncher")
}
// Keystore
@ -74,6 +75,33 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
flavorDimensions += listOf("env")
productFlavors {
create("dev") {
dimension = "env"
versionNameSuffix = "-dev"
applicationIdSuffix = ".dev"
}
create("prod") {
dimension = "env"
}
}
}
easylauncher {
buildTypes {
register(BuildType.DEBUG.name).configure {
filters(redRibbonFilter())
}
}
productFlavors {
register("dev") {
filters(chromeLike())
}
}
}
dependencies {
@ -100,6 +128,9 @@ dependencies {
// Squircle
implementation(libs.smoothCornerRect)
// Coil
implementation(libs.coil.compose)
// Tests
testImplementation(libs.test.junit)
androidTestImplementation(libs.test.junit)

@ -5,10 +5,11 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.test.mock.Bets
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer
import org.junit.Before
import org.junit.Rule
@ -33,13 +34,22 @@ class BetVOTest {
}
@Test
fun testDisplayer_shouldDisplayYesNoBetUI(){
fun testDisplayer_shouldDisplayYesNoBetUI() {
//Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When
composeTestRule.activity.setContent {
AllInTheme{
displayer.DisplayBet(Bets.bets[0])
AllInTheme {
displayer.DisplayBet(
Bets.bets[0],
currentUser
)
}
}
//Expect
@ -49,13 +59,22 @@ class BetVOTest {
}
@Test
fun testDisplayer_shouldDisplayMatchUI(){
fun testDisplayer_shouldDisplayMatchUI() {
//Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When
composeTestRule.activity.setContent {
AllInTheme{
displayer.DisplayBet(Bets.bets[1])
AllInTheme {
displayer.DisplayBet(
Bets.bets[1],
currentUser
)
}
}
//Expect
@ -65,13 +84,22 @@ class BetVOTest {
}
@Test
fun testDisplayer_shouldDisplayCustomBetUI(){
fun testDisplayer_shouldDisplayCustomBetUI() {
//Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When
composeTestRule.activity.setContent {
AllInTheme{
displayer.DisplayBet(Bets.bets[2])
AllInTheme {
displayer.DisplayBet(
Bets.bets[2],
currentUser
)
}
}
//Expect

@ -4,23 +4,24 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.vo.bet.BetDisplayer
class BetTestDisplayer : BetDisplayer {
@Composable
override fun DisplayYesNoBet(betDetail: BetDetail) {
Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
override fun DisplayBinaryBet(betDetail: BetDetail, currentUser: User) {
Text("This is a Binary Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
}
@Composable
override fun DisplayMatchBet(betDetail: BetDetail) {
override fun DisplayMatchBet(betDetail: BetDetail, currentUser: User) {
Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag))
}
@Composable
override fun DisplayCustomBet(betDetail: BetDetail) {
override fun DisplayCustomBet(betDetail: BetDetail, currentUser: User) {
Text("This is a Custom Bet", Modifier.testTag(TestTags.CUSTOM_BET.tag))
}
}

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Allin_DEBUG</string>
<string name="app_name" translatable="false">Allin DEBUG</string>
</resources>

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".AllInApplication"
@ -17,12 +17,24 @@
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustPan"
android:theme="@style/Theme.Allin">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>

@ -5,11 +5,11 @@ import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class AllInApplication : Application(){
class AllInApplication : Application() {
override fun onCreate() {
super.onCreate()
if(BuildConfig.DEBUG){
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}

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

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

@ -7,7 +7,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.theme.AllInColorToken
@StringRes
fun BetStatus.getTitleId(): Int {
@ -23,36 +23,36 @@ fun BetStatus.getTitleId(): Int {
@StringRes
fun BetStatus.getDateStartLabelId(): Int {
return when (this) {
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.Started
else -> R.string.Starting
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.bet_started
else -> R.string.bet_starting
}
}
@StringRes
fun BetStatus.getDateEndLabelId(): Int {
return when (this) {
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.Ended
else -> R.string.Ends
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.bet_ended
else -> R.string.bet_ends
}
}
@Composable
fun BetStatus.getColor(): Color {
return when (this) {
BetStatus.FINISHED -> AllInTheme.colors.allInBetFinish
BetStatus.IN_PROGRESS -> AllInTheme.colors.allInBetInProgress
BetStatus.WAITING -> AllInTheme.colors.allInBetWaiting
else -> AllInTheme.colors.allInBetFinish // TODO
BetStatus.FINISHED -> AllInColorToken.allInBetFinish
BetStatus.IN_PROGRESS -> AllInColorToken.allInBetInProgress
BetStatus.WAITING -> AllInColorToken.allInBetWaiting
else -> AllInColorToken.allInBetFinish // TODO
}
}
@Composable
fun BetStatus.getTextColor(): Color {
return when (this) {
BetStatus.FINISHED -> AllInTheme.colors.allInBetFinishText
BetStatus.IN_PROGRESS -> AllInTheme.colors.allInBetInProgressText
BetStatus.WAITING -> AllInTheme.colors.allInBetWaitingText
else -> AllInTheme.colors.allInBetFinishText // TODO
BetStatus.FINISHED -> AllInColorToken.allInBetFinishText
BetStatus.IN_PROGRESS -> AllInColorToken.allInBetInProgressText
BetStatus.WAITING -> AllInColorToken.allInBetWaitingText
else -> AllInColorToken.allInBetFinishText // TODO
}
}
@ -70,8 +70,8 @@ fun BetStatus.getBetHistoryPhrase(won: Boolean): Int {
fun BetStatus.getBetHistoryStatusColor(won: Boolean): Brush {
return when (this) {
BetStatus.FINISHED ->
if (won) AllInTheme.colors.allInMainGradient else AllInTheme.colors.allInDarkGradient
if (won) AllInColorToken.allInMainGradient else AllInColorToken.allInDarkGradient
else -> SolidColor(AllInTheme.colors.allInDarkGrey100)
else -> SolidColor(AllInColorToken.allInDarkGrey100)
}
}

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

@ -14,29 +14,31 @@ sealed class FieldErrorState(
) {
data object NoError : FieldErrorState()
data object Mandatory : FieldErrorState(R.string.FieldError_Mandatory)
data object Mandatory : FieldErrorState(R.string.field_error_mandatory)
data class TooShort(val fieldName: String, val minChar: Int) :
FieldErrorState(R.string.FieldError_TooShort, fieldName, minChar)
FieldErrorState(R.string.field_error_too_short, fieldName, minChar)
data class BadFormat(val fieldName: String, val format: String) :
FieldErrorState(R.string.FieldError_BadFormat, fieldName, format)
FieldErrorState(R.string.field_error_bad_format, fieldName, format)
data object NotIdentical : FieldErrorState(R.string.FieldError_NotIdentical)
data object NotIdentical : FieldErrorState(R.string.field_error_not_identical)
data class NoSpecialCharacter(val fieldName: String, val characters: String = ALLOWED_SYMBOLS) :
FieldErrorState(R.string.FieldError_NoSpecialCharacter, fieldName, characters)
FieldErrorState(R.string.field_error_no_special_character, fieldName, characters)
data class AlreadyUsed(val value: String) :
FieldErrorState(R.string.FieldError_AlreadyUsed, value)
FieldErrorState(R.string.field_error_already_used, value)
data class PastDate(val fieldName: String) :
FieldErrorState(R.string.FieldError_PastDate, fieldName)
FieldErrorState(R.string.field_error_past_date, fieldName)
data class DateOrder(val fieldName1: String, val fieldName2: String) :
FieldErrorState(R.string.FieldError_DateOrder, fieldName1, fieldName2)
FieldErrorState(R.string.field_error_date_order, fieldName1, fieldName2)
data object NoResponse :
FieldErrorState(R.string.field_error_no_response)
@Composable
fun errorResource() = stringResourceOrNull(id = messageId, *messageArgs)

@ -1,17 +1,26 @@
package fr.iut.alldev.allin.ext
import android.graphics.BlurMaskFilter
import androidx.compose.foundation.ScrollState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.min
@Composable
fun Modifier.shadow(
@ -31,7 +40,7 @@ fun Modifier.shadow(
BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL)
}
frameworkPaint.color = color.toArgb()
frameworkPaint.alpha = (255*alpha).toInt()
frameworkPaint.alpha = (255 * alpha).toInt()
val leftPixel = offsetX.toPx()
val topPixel = offsetY.toPx()
val rightPixel = size.width + topPixel
@ -71,7 +80,7 @@ fun Modifier.shadow(
Offset(size.width, 0f),
colors
)
frameworkPaint.alpha = (255*alpha).toInt()
frameworkPaint.alpha = (255 * alpha).toInt()
val leftPixel = offsetX.toPx()
val topPixel = offsetY.toPx()
val rightPixel = size.width + topPixel
@ -88,4 +97,50 @@ fun Modifier.shadow(
)
}
}
)
fun Modifier.nonLinkedScroll() =
this.nestedScroll(object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
) = available.copy(x = 0f)
})
fun Modifier.fadingEdges(
scrollState: ScrollState,
topEdgeHeight: Dp = 16.dp,
bottomEdgeHeight: Dp = 16.dp
): Modifier = this.then(
Modifier
// adding layer fixes issue with blending gradient and content
.graphicsLayer { alpha = 0.99F }
.drawWithContent {
drawContent()
val topColors = listOf(Color.Transparent, Color.Black)
val topStartY = scrollState.value.toFloat()
val topGradientHeight = min(topEdgeHeight.toPx(), topStartY)
drawRect(
brush = Brush.verticalGradient(
colors = topColors,
startY = topStartY,
endY = topStartY + topGradientHeight
),
blendMode = BlendMode.DstIn
)
val bottomColors = listOf(Color.Black, Color.Transparent)
val bottomEndY = size.height - scrollState.maxValue + scrollState.value
val bottomGradientHeight = min(bottomEdgeHeight.toPx(), scrollState.maxValue.toFloat() - scrollState.value)
if (bottomGradientHeight != 0f) drawRect(
brush = Brush.verticalGradient(
colors = bottomColors,
startY = bottomEndY - bottomGradientHeight,
endY = bottomEndY
),
blendMode = BlendMode.DstIn
)
}
)

@ -0,0 +1,72 @@
package fr.iut.alldev.allin.ext
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.safeContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ReadOnlyComposable
@Composable
operator fun PaddingValues.plus(paddingValues: PaddingValues): PaddingValues {
val direction = LocalLayoutDirection.current
return PaddingValues(
top = this.calculateTopPadding() + paddingValues.calculateTopPadding(),
bottom = this.calculateBottomPadding() + paddingValues.calculateBottomPadding(),
start = this.calculateStartPadding(direction) + paddingValues.calculateStartPadding(direction),
end = this.calculateEndPadding(direction) + paddingValues.calculateEndPadding(direction),
)
}
@ReadOnlyComposable
@Composable
fun WindowInsets.asPaddingValues(top: Dp = 0.dp, bottom: Dp = 0.dp, start: Dp = 0.dp, end: Dp = 0.dp): PaddingValues =
this.asPaddingValues() + PaddingValues(start, top, end, bottom)
@ReadOnlyComposable
@Composable
fun WindowInsets.asPaddingValues(horizontal: Dp = 0.dp, vertical: Dp = 0.dp): PaddingValues =
this.asPaddingValues() + PaddingValues(horizontal, vertical)
@ReadOnlyComposable
@Composable
fun WindowInsets.asPaddingValues(all: Dp = 0.dp): PaddingValues = this.asPaddingValues() + PaddingValues(all)
@ReadOnlyComposable
@Composable
fun WindowInsets.takeBottomOnly(): WindowInsets {
val density = LocalDensity.current
return WindowInsets(bottom = this.getBottom(density))
}
@ReadOnlyComposable
@Composable
fun WindowInsets.takeTopOnly(): WindowInsets {
val density = LocalDensity.current
return WindowInsets(top = this.getTop(density))
}
@Composable
fun bottomSheetNavigationBarsInsets(): WindowInsets {
val density = LocalDensity.current
val navBar = WindowInsets.navigationBars
if (navBar.getBottom(density) == 0) {
val safeContent = WindowInsets.safeContent
if (navBar.getBottom(density) == 0) {
return WindowInsets(bottom = 40.dp)
}
return safeContent.takeBottomOnly()
}
return navBar
}
@Composable
fun bottomSheetNavigationBarsPadding(): PaddingValues = bottomSheetNavigationBarsInsets().asPaddingValues()

@ -18,4 +18,10 @@ fun String.verifyIsFloat(locale: Locale): String? {
fun String.toFloatOrNull(locale: Locale): Float? {
val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale))
return format.parse(this)?.toFloat()
}
fun String.asFallbackProfileUsername() = buildString {
this@asFallbackProfileUsername.trim().split("\\s+".toRegex(), limit = 2).forEach {
append(it.first().uppercase())
}
}

@ -2,89 +2,12 @@ package fr.iut.alldev.allin.theme
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
@Immutable
data class AllInColors(
val allInDark: Color,
val allInDarkGrey300: Color,
val allInDarkGrey200: Color,
val allInDarkGrey100: Color,
val allInDarkGrey50: Color,
val allInGrey: Color,
val allInLightGrey300: Color,
val allInLightGrey200: Color,
val allInLightGrey100: Color,
val allInLightGrey50: Color,
val allInWhite: Color,
val white: Color,
val black: Color,
val allInPink: Color,
val allInMint: Color,
val allInPurple: Color,
val allInLoginPurple: Color,
val allInBlue: Color,
val allInDarkBlue: Color,
val allInBarPurple: Color,
val allInBarPink: Color,
val allInBetFinish: Color,
val allInBetInProgress: Color,
val allInBetWaiting: Color,
val allInBetFinishText: Color,
val allInBetInProgressText: Color,
val allInBetWaitingText: Color,
val allInMainGradient: Brush,
val allInMainGradientReverse: Brush,
val allInBar1stGradient: Brush,
val allInBar2ndGradient: Brush,
val allInTextGradient: Brush,
val allInLoginGradient: Brush,
val allInDarkGradient: Brush,
)
internal val LocalColors = staticCompositionLocalOf {
AllInColors(
allInDark = Color.Unspecified,
allInDarkGrey300 = Color.Unspecified,
allInDarkGrey200 = Color.Unspecified,
allInDarkGrey100 = Color.Unspecified,
allInDarkGrey50 = Color.Unspecified,
allInGrey = Color.Unspecified,
allInLightGrey300 = Color.Unspecified,
allInLightGrey200 = Color.Unspecified,
allInLightGrey100 = Color.Unspecified,
allInLightGrey50 = Color.Unspecified,
allInWhite = Color.Unspecified,
white = Color.Unspecified,
black = Color.Unspecified,
allInPink = Color.Unspecified,
allInPurple = Color.Unspecified,
allInLoginPurple = Color.Unspecified,
allInBarPurple = Color.Unspecified,
allInBarPink = Color.Unspecified,
allInBlue = Color.Unspecified,
allInMint = Color.Unspecified,
allInDarkBlue = Color.Unspecified,
allInBetFinish = Color.Unspecified,
allInBetInProgress = Color.Unspecified,
allInBetWaiting = Color.Unspecified,
allInBetFinishText = Color.Unspecified,
allInBetInProgressText = Color.Unspecified,
allInBetWaitingText = Color.Unspecified,
allInMainGradient = SolidColor(Color.Unspecified),
allInMainGradientReverse = SolidColor(Color.Unspecified),
allInBar1stGradient = SolidColor(Color.Unspecified),
allInBar2ndGradient = SolidColor(Color.Unspecified),
allInTextGradient = SolidColor(Color.Unspecified),
allInLoginGradient = SolidColor(Color.Unspecified),
allInDarkGradient = SolidColor(Color.Unspecified)
)
}
@Immutable
data class AllInThemeColors(
val mainSurface: Color,
val onMainSurface: Color,
val background: Color,
@ -97,8 +20,8 @@ data class AllInThemeColors(
val disabledBorder: Color,
)
internal val LocalThemeColors = staticCompositionLocalOf {
AllInThemeColors(
internal val LocalColors = staticCompositionLocalOf {
AllInColors(
mainSurface = Color.Unspecified,
onMainSurface = Color.Unspecified,
background = Color.Unspecified,
@ -110,4 +33,76 @@ internal val LocalThemeColors = staticCompositionLocalOf {
disabled = Color.Unspecified,
disabledBorder = Color.Unspecified
)
}
internal object AllInColorToken {
val black = Color(0xFF000000)
val allInDark = Color(0xFF2A2A2A)
val allInDarkGrey300 = Color(0xFF1c1c1c)
val allInDarkGrey200 = Color(0xFF262626)
val allInDarkGrey100 = Color(0xFF393939)
val allInDarkGrey50 = Color(0xFF454545)
val allInGrey = Color(0xFF525252)
val allInLightGrey300 = Color(0XFFAAAAAA)
val allInLightGrey200 = Color(0XFFC5C5C5)
val allInLightGrey100 = Color(0XFFEBEBEB)
val allInLightGrey50 = Color(0XFFF7F7F7)
val allInWhite = Color(0xFFEBEBF6)
val white = Color(0xFFFFFFFF)
val allInDarkBlue = Color(0xFF323078)
val allInBlue = Color(0xFF6a89fa)
val allInPurple = Color(0xFF7D79FF)
val allInLoginPurple = Color(0xFF7F7BFB)
val allInBarPurple = Color(0xFF846AC9)
val allInPink = Color(0xFFFF2A89)
val allInBarPink = Color(0xFFFE2B8A)
val allInBarViolet = Color(0xFFC249A8)
val allInMint = Color(0xFFC4DEE9)
val allInBetFinish = Color(0xFF353535)
val allInBetInProgress = Color(0xFF604BDB)
val allInBetWaiting = Color(0xFFDF3B9A)
val allInBetFinishText = Color(0xFFA7A7A7)
val allInBetInProgressText = Color(0xFF4636A3)
val allInBetWaitingText = Color(0xFF852E6C)
val allInMainGradient = Brush.linearGradient(
0.0f to Color(0xFFf951a8),
0.5f to Color(0xFFaa7ef3),
1.0f to Color(0xFF199fee),
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
)
val 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)
)
val allInBar1stGradient = Brush.horizontalGradient(
0.0f to Color(0xFF2599F8),
1.0f to Color(0xFF846AC9)
)
val allInBar2ndGradient = Brush.horizontalGradient(
0.0f to Color(0xFFFE2B8A),
1.0f to Color(0xFFC249A8)
)
val allInTextGradient = Brush.horizontalGradient(
0.0f to Color(0xFFF876C1),
1.0f to Color(0xFF2399F8)
)
val allInLoginGradient = Brush.linearGradient(
0.0f to Color(0xFFEC1794),
0.5f to Color(0xFFaa7ef3),
1.0f to Color(0xFF00EEEE),
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
)
val allInDarkGradient = Brush.horizontalGradient(
0.0f to Color(0xFF595959),
1.0f to Color(0xFF000000)
)
}

@ -10,10 +10,12 @@ import androidx.compose.ui.graphics.painter.Painter
@Immutable
data class AllInIcons(
val allCoins: @Composable () -> Painter,
val logo: @Composable () -> Painter
)
internal val LocalIcons = staticCompositionLocalOf {
AllInIcons(
allCoins = { ColorPainter(Color.Unspecified) }
allCoins = { ColorPainter(Color.Unspecified) },
logo = { ColorPainter(Color.Unspecified) }
)
}

@ -6,8 +6,6 @@ import androidx.compose.material.ripple.RippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
@ -19,74 +17,6 @@ import fr.iut.alldev.allin.R
fun AllInTheme(
content: @Composable () -> Unit,
) {
val customColors = AllInColors(
allInDark = Color(0xFF2A2A2A),
allInDarkGrey300 = Color(0xFF1c1c1c),
allInDarkGrey200 = Color(0xFF262626),
allInDarkGrey100 = Color(0xFF393939),
allInDarkGrey50 = Color(0xFF454545),
allInGrey = Color(0xFF525252),
allInLightGrey300 = Color(0XFFAAAAAA),
allInLightGrey200 = Color(0XFFC5C5C5),
allInLightGrey100 = Color(0XFFEBEBEB),
allInLightGrey50 = Color(0XFFF7F7F7),
allInWhite = Color(0xFFEBEBF6),
white = Color(0xFFFFFFFF),
black = Color(0xFF000000),
allInPink = Color(0xFFFF2A89),
allInPurple = Color(0xFF7D79FF),
allInLoginPurple = Color(0xFF7F7BFB),
allInBarPurple = Color(0xFF846AC9),
allInBarPink = Color(0xFFFE2B8A),
allInBlue = Color(0xFF6a89fa),
allInMint = Color(0xFFC4DEE9),
allInDarkBlue = Color(0xFF323078),
allInBetFinish = Color(0xFF353535),
allInBetInProgress = Color(0xFF604BDB),
allInBetWaiting = Color(0xFFDF3B9A),
allInBetFinishText = Color(0xFFA7A7A7),
allInBetInProgressText = Color(0xFF4636A3),
allInBetWaitingText = Color(0xFF852E6C),
allInMainGradient = Brush.linearGradient(
0.0f to Color(0xFFf951a8),
0.5f to Color(0xFFaa7ef3),
1.0f to Color(0xFF199fee),
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
),
allInMainGradientReverse = Brush.linearGradient(
0.0f to Color(0xFF199fee),
0.5f to Color(0xFFaa7ef3),
1.0f to Color(0xFFf951a8),
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
),
allInBar1stGradient = Brush.horizontalGradient(
0.0f to Color(0xFF2599F8),
1.0f to Color(0xFF846AC9)
),
allInBar2ndGradient = Brush.horizontalGradient(
0.0f to Color(0xFFFE2B8A),
1.0f to Color(0xFFC249A8)
),
allInTextGradient = Brush.horizontalGradient(
0.0f to Color(0xFFF876C1),
1.0f to Color(0xFF2399F8)
),
allInLoginGradient = Brush.linearGradient(
0.0f to Color(0xFFEC1794),
0.5f to Color(0xFFaa7ef3),
1.0f to Color(0xFF00EEEE),
start = Offset(0f, Float.POSITIVE_INFINITY),
end = Offset(Float.POSITIVE_INFINITY, 0f)
),
allInDarkGradient = Brush.horizontalGradient(
0.0f to Color(0xFF595959),
1.0f to Color(0xFF000000)
)
)
val customTypography = AllInTypography(
h1 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans,
@ -119,54 +49,49 @@ fun AllInTheme(
)
val customTheme = if (isSystemInDarkTheme()) {
AllInThemeColors(
mainSurface = customColors.allInDarkGrey300,
onMainSurface = customColors.allInWhite,
background = customColors.allInDarkGrey200,
onBackground = customColors.white,
tint1 = customColors.white,
background2 = customColors.allInDark,
onBackground2 = customColors.allInLightGrey200,
border = customColors.allInDarkGrey100,
disabled = customColors.allInDarkGrey200,
disabledBorder = customColors.allInDarkGrey100
AllInColors(
mainSurface = AllInColorToken.allInDarkGrey300,
onMainSurface = AllInColorToken.allInWhite,
background = AllInColorToken.allInDarkGrey200,
onBackground = AllInColorToken.white,
tint1 = AllInColorToken.white,
background2 = AllInColorToken.allInDark,
onBackground2 = AllInColorToken.allInLightGrey200,
border = AllInColorToken.allInDarkGrey100,
disabled = AllInColorToken.allInDarkGrey200,
disabledBorder = AllInColorToken.allInDarkGrey100
)
} else {
AllInThemeColors(
mainSurface = customColors.allInWhite,
onMainSurface = customColors.allInDark,
background = customColors.white,
onBackground = customColors.allInDarkBlue,
tint1 = customColors.allInLoginPurple,
background2 = customColors.allInLightGrey50,
onBackground2 = customColors.allInLightGrey300,
border = customColors.allInLightGrey100,
disabled = customColors.allInLightGrey100,
disabledBorder = customColors.allInLightGrey200
AllInColors(
mainSurface = AllInColorToken.allInWhite,
onMainSurface = AllInColorToken.allInDark,
background = AllInColorToken.white,
onBackground = AllInColorToken.allInDarkBlue,
tint1 = AllInColorToken.allInLoginPurple,
background2 = AllInColorToken.allInLightGrey50,
onBackground2 = AllInColorToken.allInLightGrey300,
border = AllInColorToken.allInLightGrey100,
disabled = AllInColorToken.allInLightGrey100,
disabledBorder = AllInColorToken.allInLightGrey200
)
}
val customIcons = AllInIcons(
allCoins = { painterResource(id = R.drawable.allcoin) }
allCoins = { painterResource(id = R.drawable.allcoin) },
logo = { painterResource(id = R.drawable.allin) }
)
CompositionLocalProvider(
LocalColors provides customColors,
LocalIcons provides customIcons,
LocalTypography provides customTypography,
LocalThemeColors provides customTheme
LocalColors provides customTheme
) {
content()
}
}
object AllInTheme {
val colors: AllInColors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
val icons: AllInIcons
@Composable
@ReadOnlyComposable
@ -177,10 +102,10 @@ object AllInTheme {
@ReadOnlyComposable
get() = LocalTypography.current
val themeColors: AllInThemeColors
val colors: AllInColors
@Composable
@ReadOnlyComposable
get() = LocalThemeColors.current
get() = LocalColors.current
}

@ -1,42 +1,29 @@
package fr.iut.alldev.allin.ui
import android.app.Activity
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.compose.runtime.SideEffect
import androidx.activity.enableEdgeToEdge
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import dagger.hilt.android.AndroidEntryPoint
import fr.iut.alldev.allin.ui.navigation.AllInNavHost
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.navigation.AllInNavHost
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.light(
scrim = Color.Transparent.toArgb(),
darkScrim = Color.Transparent.toArgb()
)
)
super.onCreate(savedInstanceState)
setContent {
AllInTheme{
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
with((view.context as Activity)) {
window.statusBarColor = Color.Transparent.toArgb()
window.navigationBarColor = Color.Transparent.toArgb()
if (Build.VERSION.SDK_INT > 30) {
window.insetsController?.hide(WindowInsetsCompat.Type.statusBars())
window.insetsController?.hide(WindowInsetsCompat.Type.navigationBars())
WindowCompat.setDecorFitsSystemWindows(window, false)
}
}
}
}
AllInTheme {
AllInNavHost()
}
}

@ -1,138 +1,37 @@
package fr.iut.alldev.allin.ui.bet
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
import fr.iut.alldev.allin.ui.core.AllInChip
import fr.iut.alldev.allin.ui.bet.components.BetScreenLoadedContent
import fr.iut.alldev.allin.ui.core.AllInLoading
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
fun BetScreen(
viewModel: BetViewModel = hiltViewModel(),
selectBet: (Bet, Boolean) -> Unit,
) {
val bets by viewModel.bets.collectAsState()
val horizontalPadding = 23.dp
val refreshing by viewModel.isRefreshing.collectAsState()
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() })
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
LazyColumn(
Modifier
.pullRefresh(pullRefreshState)
.padding(top = with(LocalDensity.current) {
progressAnimation.toDp()
})
) {
item {
Box(
Modifier.fillMaxWidth()
) {
BetScreenPopularCard(
modifier = Modifier
.padding(top = 13.dp, bottom = 10.dp)
.padding(horizontal = 13.dp),
nbPlayers = 12,
points = 2.35f,
pointUnit = "k",
title = "Emre va réussir son TP de CI/CD mercredi?"
)
PullRefreshIndicator(
modifier = Modifier
.align(Alignment.TopCenter),
refreshing = refreshing,
state = pullRefreshState
)
}
}
stickyHeader {
LazyRow(
modifier = Modifier
.background(
Brush.verticalGradient(
0.5f to AllInTheme.themeColors.mainSurface,
1f to Color.Transparent
)
)
.padding(top = 5.dp, bottom = 19.dp),
horizontalArrangement = Arrangement.spacedBy(9.dp)
) {
item {
Spacer(modifier = Modifier.width(horizontalPadding))
}
items(items) {
var isSelected by remember { mutableStateOf(false) }
AllInChip(
text = stringResource(id = it),
isSelected = isSelected,
onClick = {
isSelected = !isSelected
})
}
item {
Spacer(modifier = Modifier.width(horizontalPadding))
}
}
}
items(bets) {
BetScreenCard(
creator = it.creator,
category = it.theme,
title = it.phrase,
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
players = List(3) { null },
onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) },
modifier = Modifier.padding(horizontal = horizontalPadding)
val state by viewModel.state.collectAsStateWithLifecycle()
val filters by viewModel.filters.collectAsStateWithLifecycle()
val isRefreshing by viewModel.refreshing.collectAsStateWithLifecycle()
when (val s = state) {
is BetViewModel.State.Loaded -> {
BetScreenLoadedContent(
popularBet = s.popularBet,
filters = filters,
bets = s.bets,
isRefreshing = isRefreshing,
selectBet = selectBet,
toggleFilter = { viewModel.toggleFilter(it) },
refreshData = { viewModel.refreshData() }
)
Spacer(modifier = Modifier.height(24.dp))
}
}
}
val items = listOf(
R.string.Public,
R.string.Invitation,
R.string.Current,
R.string.Finished
)
BetViewModel.State.Loading -> {
AllInLoading(visible = true)
}
}
}

@ -4,16 +4,17 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel
class BetViewModel @Inject constructor(
@ -21,42 +22,70 @@ class BetViewModel @Inject constructor(
private val betRepository: BetRepository
) : ViewModel() {
private val _isRefreshing by lazy { MutableStateFlow(false) }
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow()
private val _state: MutableStateFlow<State> by lazy { MutableStateFlow(State.Loading) }
val state: StateFlow<State> by lazy { _state.asStateFlow() }
private val _bets: MutableStateFlow<List<Bet>> by lazy {
MutableStateFlow(emptyList())
}
private val _filters: MutableStateFlow<List<BetFilter>> by lazy { MutableStateFlow(emptyList()) }
val filters get() = _filters.asStateFlow()
val bets: StateFlow<List<Bet>> by lazy {
_bets.asStateFlow()
.filterNotNull()
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
emptyList()
)
}
private val _refreshing by lazy { MutableStateFlow(false) }
val refreshing by lazy { _refreshing.asStateFlow() }
init {
viewModelScope.launch {
refreshData()
refreshBets()
filters
.debounce(1.seconds)
.collect {
refreshBets()
}
}
}
private suspend fun refreshData() {
runCatching {
_bets.emit(betRepository.getAllBets(keystoreManager.getTokenOrEmpty()))
fun toggleFilter(filter: BetFilter) {
viewModelScope.launch {
_filters.emit(
if (_filters.value.contains(filter)) {
_filters.value - filter
} else {
_filters.value + filter
}
)
}
}
fun refresh() {
private suspend fun refreshBets() {
try {
val token = keystoreManager.getTokenOrEmpty()
_state.emit(
State.Loaded(
bets = betRepository.getAllBets(
token = token,
filters = filters.value
),
popularBet = try {
betRepository.getPopularBet(token)
} catch (e: Exception) {
null
}
)
)
} catch (e: Exception) {
Timber.e(e)
}
}
fun refreshData() {
viewModelScope.launch {
_isRefreshing.emit(true)
refreshData()
_isRefreshing.emit(false)
_refreshing.emit(true)
refreshBets()
_refreshing.emit(false)
}
}
sealed interface State {
data object Loading : State
data class Loaded(val bets: List<Bet>, val popularBet: Bet?) : State
}
}

@ -15,12 +15,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.pluralStringResource
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.model.User
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.RainbowButton
@ -35,15 +35,18 @@ fun BetScreenCard(
title: String,
date: String,
time: String,
players: List<Painter?>,
modifier: Modifier = Modifier,
players: List<User>,
totalParticipants: Int,
onClickParticipate: () -> Unit,
onClickCard: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true
) {
AllInBouncyCard(
modifier = modifier.fillMaxWidth(),
radius = 16.dp,
onClick = onClickCard
onClick = onClickCard,
enabled = enabled
) {
Column(
Modifier.padding(horizontal = 19.dp, vertical = 11.dp)
@ -55,15 +58,15 @@ fun BetScreenCard(
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(11.dp))
BetDateTimeRow(label = stringResource(id = R.string.Starting), date = date, time = time)
BetDateTimeRow(label = stringResource(id = R.string.bet_starting), date = date, time = time)
}
HorizontalDivider(
thickness = 1.dp,
color = AllInTheme.themeColors.border
color = AllInTheme.colors.border
)
Column(
Modifier
.background(AllInTheme.themeColors.background2)
.background(AllInTheme.colors.background2)
) {
Row(
modifier = Modifier
@ -71,22 +74,25 @@ fun BetScreenCard(
.padding(7.dp),
verticalAlignment = Alignment.CenterVertically
) {
BetProfilePictureRow(pictures = players)
Spacer(modifier = Modifier.width(12.dp))
if (players.isNotEmpty()) {
BetProfilePictureRow(pictures = players.map { it.username to it.image })
Spacer(modifier = Modifier.width(12.dp))
}
Text(
text = pluralStringResource(
id = R.plurals.n_players_waiting,
players.size,
players.size
id = R.plurals.bet_players_waiting_format,
totalParticipants,
totalParticipants
),
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2
color = AllInTheme.colors.onBackground2
)
}
RainbowButton(
modifier = Modifier.padding(6.dp),
text = stringResource(id = R.string.Participate),
onClick = onClickParticipate
text = stringResource(id = R.string.bet_participate),
onClick = onClickParticipate,
enabled = enabled
)
}
}
@ -103,9 +109,59 @@ private fun BetScreenCardPreview() {
title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.",
time = "13:00",
players = List(3) { null },
totalParticipants = 25,
players = listOf(
User(
id = "",
username = "Lucas D",
email = "",
coins = 0,
nbBets = 0,
nbFriends = 0,
bestWin = 0,
)
),
onClickParticipate = {},
onClickCard = {}
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetScreenCardNoPlayersPreview() {
AllInTheme {
BetScreenCard(
creator = "Lucas",
category = "Études",
title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.",
time = "13:00",
totalParticipants = 25,
players = emptyList(),
onClickParticipate = {},
onClickCard = {}
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetScreenCardDisabledPreview() {
AllInTheme {
BetScreenCard(
creator = "Lucas",
category = "Études",
title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.",
time = "13:00",
totalParticipants = 25,
players = emptyList(),
onClickParticipate = {},
onClickCard = {},
enabled = false,
)
}
}

@ -0,0 +1,205 @@
package fr.iut.alldev.allin.ui.bet.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.ext.textId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInChip
import fr.iut.alldev.allin.ui.core.bet.AllInEmptyView
import java.time.ZonedDateTime
private const val DISABLED_OPACITY = .5f
@Composable
fun BetScreenLoadedContent(
popularBet: Bet?,
filters: List<BetFilter>,
bets: List<Bet>,
isRefreshing: Boolean,
selectBet: (Bet, Boolean) -> Unit,
toggleFilter: (BetFilter) -> Unit,
refreshData: () -> Unit
) {
val pullRefreshState = rememberPullRefreshState(isRefreshing, refreshData)
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
Box(Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier
.pullRefresh(pullRefreshState)
.padding(top = with(LocalDensity.current) {
progressAnimation.toDp()
}),
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
popularBet?.let {
item {
Box(Modifier.fillMaxWidth()) {
BetScreenPopularCard(
nbPlayers = it.totalParticipants,
points = it.totalStakes,
title = it.phrase,
onClick = { selectBet(it, false) },
enabled = !isRefreshing,
modifier = Modifier
.padding(top = 13.dp, bottom = 10.dp)
.padding(horizontal = 13.dp)
.let {
if (isRefreshing) it.alpha(DISABLED_OPACITY)
else it
}
)
}
}
}
stickyHeader {
LazyRow(
modifier = Modifier
.background(
Brush.verticalGradient(
0.5f to AllInTheme.colors.mainSurface,
1f to Color.Transparent
)
)
.zIndex(1f),
horizontalArrangement = Arrangement.spacedBy(9.dp),
contentPadding = PaddingValues(horizontal = 23.dp)
) {
items(BetFilter.entries) {
val isSelected by remember(filters) {
derivedStateOf {
filters.contains(it)
}
}
AllInChip(
text = stringResource(id = it.textId()),
isSelected = isSelected,
onClick = { toggleFilter(it) },
enabled = !isRefreshing,
modifier = Modifier.let {
if (isRefreshing) it.alpha(DISABLED_OPACITY)
else it
}
)
}
}
}
itemsIndexed(
items = bets,
key = { _, it -> it.id }
) { idx, it ->
BetScreenCard(
creator = it.creator,
category = it.theme,
title = it.phrase,
date = it.endRegisterDate.formatToMediumDateNoYear(),
time = it.endRegisterDate.formatToTime(),
players = emptyList(), // TODO : Players
totalParticipants = it.totalParticipants,
onClickParticipate = { selectBet(it, true) },
onClickCard = { selectBet(it, false) },
enabled = !isRefreshing,
modifier = Modifier
.animateItemPlacement()
.padding(horizontal = 23.dp)
.let {
if (isRefreshing) it.alpha(DISABLED_OPACITY)
else it
}
)
if (idx != bets.lastIndex) {
Spacer(modifier = Modifier.height(24.dp))
}
}
if (bets.isEmpty()) {
item {
AllInEmptyView(
text = stringResource(id = R.string.bet_empty_text),
subtext = null,
image = painterResource(id = R.drawable.video_game),
modifier = Modifier
.fillMaxWidth()
.fillParentMaxHeight(.5f)
)
}
}
}
PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = isRefreshing,
state = pullRefreshState
)
}
}
@Preview
@Composable
private fun BetScreenLoadedContentPreview() {
AllInTheme {
BetScreenLoadedContent(
popularBet = BinaryBet(
id = "Arleen",
creator = "Omar",
theme = "Kyli",
phrase = "Leigha",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = false,
betStatus = BetStatus.IN_PROGRESS,
totalParticipants = 200,
totalStakes = 2500
),
filters = emptyList(),
bets = emptyList(),
isRefreshing = true,
selectBet = { _, _ -> },
toggleFilter = { },
refreshData = { }
)
}
}

@ -1,5 +1,6 @@
package fr.iut.alldev.allin.ui.bet.components
import android.icu.text.CompactDecimalFormat
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -11,9 +12,11 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
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.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
@ -22,29 +25,37 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.shadow
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.core.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.HighlightedText
import kotlin.math.ceil
@Composable
fun BetScreenPopularCard(
nbPlayers: Int,
points: Float,
pointUnit: String,
points: Int,
title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true
) {
AllInCard(
val configuration = LocalConfiguration.current
val numberFormat = remember(configuration) {
val locale = ConfigurationCompat.getLocales(configuration).get(0) ?: java.util.Locale.getDefault()
CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT)
}
AllInBouncyCard(
modifier = modifier
.let {
if (isSystemInDarkTheme()) {
it.shadow(
colors = listOf(
AllInTheme.colors.allInPink,
AllInTheme.colors.allInBlue
AllInColorToken.allInPink,
AllInColorToken.allInBlue
),
blurRadius = 10.dp,
alpha = .5f,
@ -60,9 +71,11 @@ fun BetScreenPopularCard(
}
}
.fillMaxWidth(),
backgroundColor = AllInTheme.colors.allInDark,
backgroundColor = AllInColorToken.allInDark,
borderWidth = 2.dp,
borderBrush = AllInTheme.colors.allInMainGradient
borderBrush = AllInColorToken.allInMainGradient,
onClick = onClick,
enabled = enabled
) {
Column(modifier = Modifier.padding(13.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -70,19 +83,19 @@ fun BetScreenPopularCard(
painter = painterResource(id = R.drawable.allin_fire),
modifier = Modifier.size(15.dp),
contentDescription = null,
tint = AllInTheme.colors.allInPink
tint = AllInColorToken.allInPink
)
Spacer(modifier = Modifier.width(3.dp))
Text(
text = stringResource(id = R.string.Popular),
color = AllInTheme.colors.allInPink,
text = stringResource(id = R.string.bet_popular),
color = AllInColorToken.allInPink,
fontSize = 17.sp,
style = AllInTheme.typography.h2
)
}
Text(
text = title,
color = AllInTheme.colors.white,
color = AllInColorToken.white,
fontSize = 20.sp,
style = AllInTheme.typography.h1,
modifier = Modifier.padding(vertical = 22.dp)
@ -90,42 +103,38 @@ fun BetScreenPopularCard(
Row(modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) {
HighlightedText(
text = pluralStringResource(
id = R.plurals.n_players,
id = R.plurals.bet_players_format,
nbPlayers,
nbPlayers
),
query = nbPlayers.toString(),
highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold,
color = AllInTheme.colors.allInPink
color = AllInColorToken.allInPink
),
color = AllInTheme.colors.white,
color = AllInColorToken.white,
style = AllInTheme.typography.p1,
fontSize = 15.sp
)
Text(
text = " - ",
color = AllInTheme.colors.white,
color = AllInColorToken.white,
style = AllInTheme.typography.p1,
fontSize = 15.sp
)
val pointsText = if (points % 1 == 0f) {
stringResource(id = R.string.int_and_unit, points.toInt(), pointUnit)
} else {
stringResource(id = R.string.float_and_unit, points, pointUnit)
}
val pointsText = numberFormat.format(points)
HighlightedText(
text = pluralStringResource(
id = R.plurals.n_points_at_stake,
if (pointUnit.isEmpty()) ceil(points).toInt() else 2,
id = R.plurals.bet_points_at_stake_format,
points,
pointsText
),
query = pointsText,
highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold,
color = AllInTheme.colors.allInPink
color = AllInColorToken.allInPink
),
color = AllInTheme.colors.white,
color = AllInColorToken.white,
style = AllInTheme.typography.p1,
fontSize = 15.sp
)
@ -140,9 +149,9 @@ private fun BetScreenPopularCardPreview() {
AllInTheme {
BetScreenPopularCard(
nbPlayers = 12,
points = 2.35f,
pointUnit = "k",
title = "Emre va réussir son TP de CI/CD mercredi?"
points = 2350,
title = "Emre va réussir son TP de CI/CD mercredi?",
onClick = {}
)
}
}
@ -153,9 +162,9 @@ private fun BetScreenPopularCardSingularPreview() {
AllInTheme {
BetScreenPopularCard(
nbPlayers = 1,
points = 1.0f,
pointUnit = "",
title = "Emre va réussir son TP de CI/CD mercredi?"
points = 150,
title = "Emre va réussir son TP de CI/CD mercredi?",
onClick = {}
)
}
}

@ -7,13 +7,17 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
@ -28,12 +32,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -42,14 +46,20 @@ 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.BinaryBet
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.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsInsets
import fr.iut.alldev.allin.ext.fadingEdges
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.ext.nonLinkedScroll
import fr.iut.alldev.allin.ext.takeTopOnly
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton
@ -89,13 +99,13 @@ fun BetConfirmationBottomSheetAnswer(
text: String,
odds: Float,
modifier: Modifier = Modifier,
color: Color = AllInTheme.colors.allInBlue,
color: Color = AllInColorToken.allInBlue,
isSelected: Boolean,
locale: Locale,
onClick: () -> Unit
) {
val backColor = if (isSelected) AllInTheme.colors.allInPurple else AllInTheme.colors.white
val contentColor = if (isSelected) AllInTheme.colors.white else null
val backColor = if (isSelected) AllInColorToken.allInPurple else AllInColorToken.white
val contentColor = if (isSelected) AllInColorToken.white else null
AllInCard(
backgroundColor = backColor,
@ -111,22 +121,27 @@ fun BetConfirmationBottomSheetAnswer(
text = text.uppercase(),
color = contentColor ?: color,
style = AllInTheme.typography.h1,
fontSize = 40.sp,
modifier = Modifier.align(Alignment.Center)
fontSize = 35.sp,
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.align(Alignment.Center)
.padding(horizontal = 64.dp)
)
AllInCard(
radius = 50.dp,
backgroundColor = contentColor ?: AllInTheme.colors.allInPurple,
modifier = Modifier.align(Alignment.CenterEnd)
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.clip(RoundedCornerShape(50.dp))
.background(contentColor ?: AllInColorToken.allInPurple)
.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text(
text = "x${odds.formatToSimple(locale)}",
color = backColor,
style = AllInTheme.typography.h2
)
}
Text(
text = "x${odds.formatToSimple(locale)}",
color = backColor,
style = AllInTheme.typography.h2
)
}
}
}
@ -139,123 +154,49 @@ fun ConfirmationAnswers(
onClick: (String) -> Unit
) {
val configuration = LocalConfiguration.current
val locale =
remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
when (betDetail.bet) {
is CustomBet -> items((betDetail.bet as CustomBet).possibleAnswers) {
betDetail.getAnswerOfResponse(it)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
label = ""
)
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier.alpha(opacity)
)
}
}
is MatchBet -> {
val bet = (betDetail.bet as MatchBet)
item {
betDetail.getAnswerOfResponse(bet.nameTeam1)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
label = ""
)
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier.alpha(opacity)
)
}
}
item {
betDetail.getAnswerOfResponse(bet.nameTeam2)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
label = ""
)
BetConfirmationBottomSheetAnswer(
text = it.response,
color = AllInTheme.colors.allInBarPink,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier
.alpha(opacity)
)
}
}
}
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val scrollState = rememberScrollState()
is YesNoBet -> {
item {
betDetail.getAnswerOfResponse(YES_VALUE)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
label = ""
)
val scale by animateFloatAsState(
targetValue = if (selectedAnswer == null) 1f
else if (selectedAnswer != it.response) .95f else 1.05f,
label = ""
)
val possibleAnswers = remember {
when (val bet = betDetail.bet) {
is CustomBet -> bet.possibleAnswers
is MatchBet -> listOf(bet.nameTeam1, bet.nameTeam2)
is BinaryBet -> listOf(YES_VALUE, NO_VALUE)
}
}
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier
.alpha(opacity)
.scale(scale)
)
}
}
item {
betDetail.getAnswerOfResponse(NO_VALUE)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
label = ""
)
val scale by animateFloatAsState(
targetValue = if (selectedAnswer == null) 1f
else if (selectedAnswer != it.response) .95f else 1f,
label = ""
)
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.nonLinkedScroll()
.verticalScroll(scrollState)
.fadingEdges(scrollState)
) {
possibleAnswers.forEachIndexed { idx, it ->
betDetail.getAnswerOfResponse(it)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
label = ""
)
BetConfirmationBottomSheetAnswer(
text = it.response,
color = AllInTheme.colors.allInBarPink,
odds = it.odds,
locale = locale,
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier
.alpha(opacity)
.scale(scale)
)
}
}
BetConfirmationBottomSheetAnswer(
text = it.response,
odds = it.odds,
locale = locale,
color = if (possibleAnswers.size == 2 && idx == 1) {
AllInColorToken.allInBarPink
} else {
AllInColorToken.allInBlue
},
onClick = { onClick(it.response) },
isSelected = selectedAnswer == it.response,
modifier = Modifier.alpha(opacity)
)
}
}
Spacer(
modifier = Modifier.padding(bottomSheetNavigationBarsInsets().asPaddingValues(bottom = 56.dp))
)
}
}
@ -267,10 +208,15 @@ fun BetConfirmationBottomSheetContent(
) {
var selectedAnswer by remember { mutableStateOf<String?>(null) }
AllInMarqueeBox(backgroundColor = AllInTheme.colors.allInDarkGrey300) {
AllInMarqueeBox(backgroundColor = AllInColorToken.allInDarkGrey300) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(
WindowInsets.safeContent
.takeTopOnly()
.asPaddingValues()
)
.padding(16.dp)
) {
IconButton(
@ -281,16 +227,16 @@ fun BetConfirmationBottomSheetContent(
) {
Icon(
imageVector = Icons.Default.Close,
tint = AllInTheme.colors.white,
tint = AllInColorToken.white,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
Icon(
painter = painterResource(R.drawable.allin),
painter = AllInTheme.icons.logo(),
contentDescription = null,
tint = AllInTheme.colors.white,
tint = AllInColorToken.white,
modifier = Modifier
.size(40.dp)
.align(Alignment.TopCenter)
@ -312,22 +258,22 @@ fun BetConfirmationBottomSheetContent(
Row(
modifier = Modifier
.fillMaxWidth()
.background(AllInTheme.colors.allInMainGradient)
.background(AllInColorToken.allInMainGradient)
.padding(16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.Finished),
color = AllInTheme.colors.white,
text = stringResource(id = R.string.bet_finished),
color = AllInColorToken.white,
style = AllInTheme.typography.h1,
fontSize = 24.sp
)
}
}
Text(
text = "Ce bet est arrivé à la date de fin. Vous devez à présent distribuer les gains en validant le pari gagnant.",
color = AllInTheme.colors.allInLightGrey200,
text = stringResource(id = R.string.bet_confirmation_text),
color = AllInColorToken.allInLightGrey200,
style = AllInTheme.typography.p2,
textAlign = TextAlign.Center
)
@ -335,9 +281,9 @@ fun BetConfirmationBottomSheetContent(
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Veuillez choisir la réponse finale :",
fontSize = 20.sp,
color = AllInTheme.colors.white,
text = stringResource(id = R.string.bet_confirmation_choose_response),
fontSize = 18.sp,
color = AllInColorToken.white,
style = AllInTheme.typography.h1,
modifier = Modifier.fillMaxWidth()
)
@ -348,12 +294,15 @@ fun BetConfirmationBottomSheetContent(
}
if (selectedAnswer != null) {
AllInButton(
color = AllInTheme.colors.allInPurple,
text = stringResource(id = R.string.Validate),
textColor = AllInTheme.colors.white,
color = AllInColorToken.allInPurple,
text = stringResource(id = R.string.generic_validate),
textColor = AllInColorToken.white,
radius = 5.dp,
onClick = { selectedAnswer?.let(onConfirm) },
modifier = Modifier.align(Alignment.BottomCenter)
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.safeContentPadding()
)
}
}
@ -366,7 +315,7 @@ private fun BetConfirmationBottomSheetContentPreview() {
AllInTheme {
BetConfirmationBottomSheetContent(
betDetail = BetDetail(
bet = YesNoBet(
bet = BinaryBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
@ -375,6 +324,8 @@ private fun BetConfirmationBottomSheetContentPreview() {
isPublic = true,
betStatus = BetStatus.FINISHED,
creator = "creator",
totalStakes = 0,
totalParticipants = 0
),
answers = listOf(
BetAnswerDetail(
@ -389,11 +340,12 @@ private fun BetConfirmationBottomSheetContentPreview() {
totalStakes = 150,
totalParticipants = 1,
highestStake = 150,
odds = 2.0f
odds = 2.255f
)
),
participations = emptyList(),
userParticipation = null
userParticipation = null,
wonParticipation = null
),
onConfirm = { }
) {

@ -1,35 +1,23 @@
package fr.iut.alldev.allin.ui.betCreation
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.ext.getIcon
import fr.iut.alldev.allin.ext.getTitleId
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenAnswerTab
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenQuestionTab
import fr.iut.alldev.allin.ui.betCreation.BetCreationViewModel.BetTypeState.Companion.typeState
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenContent
import fr.iut.alldev.allin.ui.core.AllInAlertDialog
import fr.iut.alldev.allin.ui.core.AllInDatePicker
import fr.iut.alldev.allin.ui.core.AllInSections
import fr.iut.alldev.allin.ui.core.AllInTimePicker
import fr.iut.alldev.allin.ui.core.RainbowButton
import fr.iut.alldev.allin.ui.core.SectionElement
import fr.iut.alldev.allin.ui.core.SelectionElement
import java.time.Instant
import java.time.ZoneId
@ -41,29 +29,30 @@ fun BetCreationScreen(
setLoading: (Boolean) -> Unit,
onCreation: () -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val betTypes = remember { BetType.values().toList() }
val betTypes = remember { BetType.entries }
var hasError by remember { mutableStateOf(false) }
var theme by remember { viewModel.theme }
var phrase by remember { viewModel.phrase }
val (registerDate, setRegisterDate) = remember { viewModel.registerDate }
val (betDate, setBetDate) = remember { viewModel.betDate }
var isPublic by remember { viewModel.isPublic }
var isPrivate by remember { viewModel.isPrivate }
var selectedBetType by remember { viewModel.selectedBetType }
val themeError by remember { viewModel.themeError }
val phraseError by remember { viewModel.phraseError }
val registerDateError by remember { viewModel.registerDateError }
val betDateError by remember { viewModel.betDateError }
val typeError by remember { viewModel.typeError }
val selectedFriends = remember { mutableListOf<Int>() }
val friends by viewModel.friends.collectAsStateWithLifecycle()
val selectedFriends by viewModel.selectedFriends.collectAsStateWithLifecycle()
var selectionElements by remember { mutableStateOf(listOf<SelectionElement>()) }
var selectedBetTypeElement by remember { mutableStateOf<SelectionElement?>(null) }
val focus = LocalFocusManager.current
val themeFieldName = stringResource(id = R.string.Theme)
val phraseFieldName = stringResource(id = R.string.Bet_Phrase)
val themeFieldName = stringResource(id = R.string.bet_creation_theme)
val phraseFieldName = stringResource(id = R.string.bet_creation_bet_phrase)
LaunchedEffect(key1 = betTypes) {
selectionElements = betTypes.map {
@ -76,7 +65,7 @@ fun BetCreationScreen(
}
LaunchedEffect(key1 = selectedBetTypeElement) {
selectedBetType = betTypes[selectionElements.indexOf(selectedBetTypeElement)]
selectedBetType = betTypes[selectionElements.indexOf(selectedBetTypeElement)].typeState()
}
val (showRegisterDatePicker, setRegisterDatePicker) = remember { mutableStateOf(false) }
@ -85,80 +74,46 @@ fun BetCreationScreen(
val (showRegisterTimePicker, setRegisterTimePicker) = remember { mutableStateOf(false) }
val (showEndTimePicker, setEndTimePicker) = remember { mutableStateOf(false) }
Box(
Modifier
.fillMaxSize()
.padding(top = 20.dp)
) {
AllInSections(
onLoadSection = {
focus.clearFocus()
},
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxSize()
.padding(bottom = 90.dp),
sections = listOf(
SectionElement(stringResource(id = R.string.Question)) {
BetCreationScreenQuestionTab(
isPublic = isPublic,
setIsPublic = { isPublic = it },
betPhrase = phrase,
setBetPhrase = { phrase = it },
betTheme = theme,
setBetTheme = { theme = it },
nbFriends = 42,
selectedFriends = selectedFriends,
registerDate = registerDate,
betDate = betDate,
setRegisterDateDialog = { setRegisterDatePicker(it) },
setEndDateDialog = { setEndDatePicker(it) },
setRegisterTimeDialog = { setRegisterTimePicker(it) },
setEndTimeDialog = { setEndTimePicker(it) },
interactionSource = interactionSource,
betThemeError = themeError.errorResource(),
betPhraseError = phraseError.errorResource(),
registerDateError = registerDateError.errorResource(),
betDateError = betDateError.errorResource(),
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(vertical = 12.dp, horizontal = 20.dp)
)
},
SectionElement(stringResource(id = R.string.Answer)) {
BetCreationScreenAnswerTab(
selectedBetType = selectedBetType,
selected = selectedBetTypeElement,
setSelected = { selectedBetTypeElement = it },
elements = selectionElements,
modifier = Modifier
.fillMaxSize()
.padding(vertical = 12.dp, horizontal = 20.dp)
)
BetCreationScreenContent(
betTheme = theme,
betThemeError = themeError.errorResource(),
setBetTheme = { theme = it },
betPhrase = phrase,
betPhraseError = phraseError.errorResource(),
setBetPhrase = { phrase = it },
isPrivate = isPrivate,
setIsPrivate = { isPrivate = it },
registerDate = registerDate,
registerDateError = registerDateError.errorResource(),
betDate = betDate,
betDateError = betDateError.errorResource(),
typeError = typeError.errorResource(),
friends = friends,
selectedFriends = selectedFriends.toList(),
setRegisterDateDialog = setRegisterDatePicker,
setEndDateDialog = setEndDatePicker,
setRegisterTimeDialog = setRegisterTimePicker,
setEndTimeDialog = setEndTimePicker,
selectedBetTypeElement = selectedBetTypeElement,
selectedBetType = selectedBetType,
setSelectedBetTypeElement = { selectedBetTypeElement = it },
selectionBetType = selectionElements,
addAnswer = { viewModel.addAnswer(it) },
deleteAnswer = { viewModel.deleteAnswer(it) },
onCreateBet = {
viewModel.createBet(
themeFieldName = themeFieldName,
phraseFieldName = phraseFieldName,
setLoading = setLoading,
onError = { hasError = true },
onSuccess = {
onCreation()
}
)
)
},
toggleFriend = { viewModel.toggleFriend(it) }
)
RainbowButton(
text = stringResource(id = R.string.Publish),
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 14.dp)
.padding(horizontal = 20.dp),
onClick = {
viewModel.createBet(
themeFieldName = themeFieldName,
phraseFieldName = phraseFieldName,
setLoading = setLoading,
onError = { hasError = true },
onSuccess = {
onCreation()
}
)
}
)
}
if (showRegisterDatePicker || showEndDatePicker) {
val dateToEdit = if (showRegisterDatePicker) registerDate else betDate
AllInDatePicker(

@ -4,14 +4,19 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException
import fr.iut.alldev.allin.data.api.model.RequestBet
import fr.iut.alldev.allin.data.model.FriendStatus
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.model.bet.BetFactory
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.di.AllInCurrentUser
import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.data.repository.UserRepository
import fr.iut.alldev.allin.ext.FieldErrorState
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.ZonedDateTime
@ -19,9 +24,10 @@ import javax.inject.Inject
@HiltViewModel
class BetCreationViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager
private val keystoreManager: AllInKeystoreManager,
private val userRepository: UserRepository,
private val friendRepository: FriendRepository
) : ViewModel() {
private var hasError = mutableStateOf(false)
@ -29,19 +35,37 @@ class BetCreationViewModel @Inject constructor(
var phrase = mutableStateOf("")
val registerDate = mutableStateOf(ZonedDateTime.now())
val betDate = mutableStateOf(ZonedDateTime.now())
var isPublic = mutableStateOf(true)
var selectedBetType = mutableStateOf(BetType.BINARY)
var isPrivate = mutableStateOf(false)
var selectedBetType = mutableStateOf<BetTypeState>(BetTypeState.Binary)
val themeError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val phraseError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val betDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val typeError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
private val _friends by lazy { MutableStateFlow<List<User>>(emptyList()) }
val friends get() = _friends.asStateFlow()
private val _selectedFriends by lazy { MutableStateFlow<Set<String>>(emptySet()) }
val selectedFriends get() = _selectedFriends.asStateFlow()
init {
viewModelScope.launch {
_friends.emit(
friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
).filter { it.friendStatus == FriendStatus.FRIEND }
)
}
}
private fun initErrorField() {
themeError.value = FieldErrorState.NoError
phraseError.value = FieldErrorState.NoError
registerDateError.value = FieldErrorState.NoError
betDateError.value = FieldErrorState.NoError
typeError.value = FieldErrorState.NoError
hasError.value = false
}
@ -77,6 +101,19 @@ class BetCreationViewModel @Inject constructor(
)
hasError.value = true
}
when (val state = selectedBetType.value) {
BetTypeState.Binary -> Unit
is BetTypeState.Custom -> if (state.answers.size < 2) {
typeError.value = FieldErrorState.NoResponse
hasError.value = true
}
is BetTypeState.Match -> if (state.team1.isBlank() || state.team2.isBlank()) {
typeError.value = FieldErrorState.Mandatory
hasError.value = true
}
}
}
@ -98,22 +135,23 @@ class BetCreationViewModel @Inject constructor(
if (!hasError.value) {
try {
val bet = BetFactory.createBet(
id = "",
betType = selectedBetType.value,
theme = theme.value,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
nameTeam1 = "",
nameTeam2 = "",
possibleAnswers = listOf(),
creator = currentUser.username
)
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess()
} catch (e: AllInAPIException) {
userRepository.currentUserState.value?.let { _ ->
val bet = RequestBet(
theme = theme.value,
type = selectedBetType.value.type,
sentenceBet = phrase.value,
endRegistration = registerDate.value,
endBet = betDate.value,
isPrivate = isPrivate.value,
response = selectedBetType.value.getPossibleAnswers(),
userInvited = selectedFriends.value.toList()
)
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess()
} ?: onError()
} catch (e: Exception) {
Timber.e(e)
onError()
}
@ -121,4 +159,64 @@ class BetCreationViewModel @Inject constructor(
setLoading(false)
}
}
fun addAnswer(value: String) {
viewModelScope.launch {
selectedBetType.value.let {
if (it is BetTypeState.Custom) {
selectedBetType.value = BetTypeState.Custom(answers = it.answers + value)
}
}
}
}
fun deleteAnswer(value: String) {
viewModelScope.launch {
selectedBetType.value.let {
if (it is BetTypeState.Custom) {
selectedBetType.value = BetTypeState.Custom(answers = it.answers - value)
}
}
}
}
fun toggleFriend(value: String) {
viewModelScope.launch {
_selectedFriends.value.let { itSelectedFriends ->
if (itSelectedFriends.contains(value)) {
_selectedFriends.emit(itSelectedFriends - value)
} else {
_selectedFriends.emit(itSelectedFriends + value)
}
}
}
}
sealed class BetTypeState(val type: BetType) {
data object Binary : BetTypeState(type = BetType.BINARY) {
override fun getPossibleAnswers(): List<String> = listOf(YES_VALUE, NO_VALUE)
}
data class Match(val team1: String, val team2: String) : BetTypeState(type = BetType.MATCH) {
override fun getPossibleAnswers(): List<String> = listOf(team1, team2)
}
data class Custom(val answers: List<String>) : BetTypeState(type = BetType.CUSTOM) {
override fun getPossibleAnswers(): List<String> = answers
}
abstract fun getPossibleAnswers(): List<String>
companion object {
fun BetType.typeState() =
when (this) {
BetType.BINARY -> Binary
BetType.MATCH -> Match(team1 = "", team2 = "")
BetType.CUSTOM -> Custom(emptyList())
}
}
}
}

@ -8,6 +8,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -17,7 +18,7 @@ fun BetCreationScreenBottomText(
) {
Text(
text = text,
color = AllInTheme.colors.allInPurple,
color = AllInColorToken.allInPurple,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
style = AllInTheme.typography.h2,

@ -0,0 +1,164 @@
package fr.iut.alldev.allin.ui.betCreation.components
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
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.model.User
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betCreation.BetCreationViewModel
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenAnswerTab
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenQuestionTab
import fr.iut.alldev.allin.ui.core.AllInSections
import fr.iut.alldev.allin.ui.core.RainbowButton
import fr.iut.alldev.allin.ui.core.SectionElement
import fr.iut.alldev.allin.ui.core.SelectionElement
import java.time.ZonedDateTime
@Composable
fun BetCreationScreenContent(
betTheme: String,
betThemeError: String?,
setBetTheme: (String) -> Unit,
betPhrase: String,
betPhraseError: String?,
setBetPhrase: (String) -> Unit,
isPrivate: Boolean,
setIsPrivate: (Boolean) -> Unit,
registerDate: ZonedDateTime,
registerDateError: String?,
betDate: ZonedDateTime,
betDateError: String?,
friends: List<User>,
selectedFriends: List<String>,
setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean) -> Unit,
selectedBetTypeElement: SelectionElement?,
selectedBetType: BetCreationViewModel.BetTypeState,
typeError: String?,
setSelectedBetTypeElement: (SelectionElement) -> Unit,
selectionBetType: List<SelectionElement>,
addAnswer: (String) -> Unit,
deleteAnswer: (String) -> Unit,
onCreateBet: () -> Unit,
toggleFriend: (String) -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val focus = LocalFocusManager.current
Box(Modifier.fillMaxSize()) {
AllInSections(
onLoadSection = { focus.clearFocus() },
modifier = Modifier.align(Alignment.TopCenter),
sections = listOf(
SectionElement(stringResource(id = R.string.bet_creation_question)) {
BetCreationScreenQuestionTab(
isPrivate = isPrivate,
setIsPrivate = setIsPrivate,
betPhrase = betPhrase,
setBetPhrase = setBetPhrase,
betTheme = betTheme,
setBetTheme = setBetTheme,
friends = friends,
selectedFriends = selectedFriends,
registerDate = registerDate,
betDate = betDate,
setRegisterDateDialog = setRegisterDateDialog,
setEndDateDialog = setEndDateDialog,
setRegisterTimeDialog = setRegisterTimeDialog,
setEndTimeDialog = setEndTimeDialog,
interactionSource = interactionSource,
betThemeError = betThemeError,
betPhraseError = betPhraseError,
registerDateError = registerDateError,
betDateError = betDateError,
toggleFriend = toggleFriend
)
},
SectionElement(stringResource(id = R.string.bet_creation_answer)) {
BetCreationScreenAnswerTab(
selectedBetType = selectedBetType,
selected = selectedBetTypeElement,
setSelected = setSelectedBetTypeElement,
elements = selectionBetType,
addAnswer = addAnswer,
deleteAnswer = deleteAnswer,
typeError = typeError
)
}
)
)
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.background(
Brush.verticalGradient(
0f to Color.Transparent,
0.50f to AllInTheme.colors.mainSurface
)
),
) {
RainbowButton(
text = stringResource(id = R.string.bet_creation_publish),
modifier = Modifier
.padding(bottom = 14.dp)
.padding(horizontal = 20.dp)
.safeContentPadding(),
onClick = onCreateBet,
enabled = !isPrivate || selectedFriends.isNotEmpty()
)
}
}
}
@Preview
@Composable
private fun BetCreationScreenContentPreview() {
AllInTheme {
BetCreationScreenContent(
friends = emptyList(),
betTheme = "Ina",
betThemeError = null,
setBetTheme = { },
betPhrase = "Bryon",
typeError = null,
betPhraseError = null,
setBetPhrase = { },
isPrivate = false,
setIsPrivate = { },
registerDate = ZonedDateTime.now(),
registerDateError = null,
betDate = ZonedDateTime.now(),
betDateError = null,
selectedFriends = mutableListOf(),
setRegisterDateDialog = { },
setEndDateDialog = { },
setRegisterTimeDialog = { },
setEndTimeDialog = { },
selectedBetTypeElement = null,
selectedBetType = BetCreationViewModel.BetTypeState.Binary,
setSelectedBetTypeElement = { },
selectionBetType = listOf(),
onCreateBet = { },
addAnswer = { },
deleteAnswer = { },
toggleFriend = { }
)
}
}

@ -0,0 +1,72 @@
package fr.iut.alldev.allin.ui.betCreation.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
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.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@Composable
fun BetCreationScreenCustomAnswer(
text: String,
onDelete: () -> Unit,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
shape = AbsoluteSmoothCornerShape(15.dp, 100),
color = AllInColorToken.allInPurple,
contentColor = AllInColorToken.white
) {
Row(
modifier = Modifier
.padding(vertical = 4.dp)
.padding(start = 8.dp, end = 2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = text,
textAlign = TextAlign.Center,
style = AllInTheme.typography.h1
)
IconButton(
onClick = onDelete,
modifier = Modifier.size(24.dp)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
}
}
}
}
@Preview
@Composable
private fun BetCreationScreenCustomAnswerPreview() {
AllInTheme {
BetCreationScreenCustomAnswer(
text = "Text",
onDelete = {}
)
}
}

@ -0,0 +1,74 @@
package fr.iut.alldev.allin.ui.betCreation.components
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.R
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.AllInTextField
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@Composable
fun BetCreationScreenCustomAnswerTextField(
value: String,
setValue: (String) -> Unit,
enabled: Boolean,
buttonEnabled: Boolean,
onAdd: () -> Unit,
modifier: Modifier = Modifier
) {
AllInTextField(
value = value,
enabled = enabled,
modifier = modifier,
onValueChange = setValue,
maxChar = 15,
trailingContent = {
AllInButton(
color = AllInColorToken.allInPurple,
enabled = enabled && buttonEnabled,
text = stringResource(id = R.string.generic_add),
textColor = AllInColorToken.white,
shape = AbsoluteSmoothCornerShape(
cornerRadiusTR = 10.dp,
cornerRadiusBR = 10.dp,
smoothnessAsPercentTR = 100,
smoothnessAsPercentBR = 100
),
onClick = onAdd
)
}
)
}
@Preview
@Composable
private fun BetCreationScreenCustomAnswerTextFieldPreview() {
AllInTheme {
BetCreationScreenCustomAnswerTextField(
onAdd = {},
enabled = true,
buttonEnabled = true,
value = "Test",
setValue = { }
)
}
}
@Preview
@Composable
private fun BetCreationScreenCustomAnswerDisabledTextFieldPreview() {
AllInTheme {
BetCreationScreenCustomAnswerTextField(
onAdd = {},
enabled = false,
buttonEnabled = false,
value = "Test",
setValue = { }
)
}
}

@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
@ -24,7 +25,7 @@ fun BetCreationScreenDateTimeButton(
) {
Text(
text = text,
color = AllInTheme.colors.allInPurple,
color = AllInColorToken.allInPurple,
style = AllInTheme.typography.h2,
fontSize = 16.sp,
modifier = Modifier.padding(horizontal = 23.dp, vertical = 9.dp)

@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -16,6 +15,8 @@ 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.unit.dp
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInRadioButton
@ -24,6 +25,7 @@ import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable
fun BetCreationScreenFriendLine(
username: String,
image: String?,
allCoinsAmount: Int,
isSelected: Boolean,
onClick: () -> Unit,
@ -33,8 +35,8 @@ fun BetCreationScreenFriendLine(
.fillMaxWidth()
.clickable(onClick = onClick)
.background(
if (isSelected) AllInTheme.colors.allInPurple.copy(alpha = .13f)
else AllInTheme.themeColors.background
if (isSelected) AllInColorToken.allInPurple.copy(alpha = .13f)
else AllInTheme.colors.background
)
.padding(15.dp),
horizontalArrangement = Arrangement.spacedBy(7.dp),
@ -44,19 +46,23 @@ fun BetCreationScreenFriendLine(
checked = isSelected,
modifier = Modifier.padding(end = 7.dp)
)
ProfilePicture(modifier = Modifier.size(25.dp))
ProfilePicture(
image = image,
fallback = username.asFallbackProfileUsername(),
size = 25.dp
)
Text(
text = username,
fontWeight = FontWeight.Bold,
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.onMainSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
AllInCoinCount(
amount = allCoinsAmount,
color = AllInTheme.colors.allInPurple
color = AllInColorToken.allInPurple
)
}
}
@ -68,6 +74,7 @@ private fun BetCreationScreenFriendLinePreview() {
AllInTheme {
BetCreationScreenFriendLine(
username = "David",
image = null,
allCoinsAmount = 542,
isSelected = false
) {
@ -83,6 +90,7 @@ private fun BetCreationScreenFriendLineSelectedPreview() {
AllInTheme {
BetCreationScreenFriendLine(
username = "David",
image = null,
allCoinsAmount = 542,
isSelected = true
) {

@ -2,68 +2,154 @@ package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.ext.getTitleId
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betCreation.BetCreationViewModel
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenBottomText
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenCustomAnswer
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenCustomAnswerTextField
import fr.iut.alldev.allin.ui.core.AllInErrorLine
import fr.iut.alldev.allin.ui.core.AllInSelectionBox
import fr.iut.alldev.allin.ui.core.SelectionElement
private const val BET_MAX_ANSWERS = 4
@Composable
fun BetCreationScreenAnswerTab(
typeError: String?,
modifier: Modifier = Modifier,
selected: SelectionElement?,
selectedBetType: BetType,
selectedBetType: BetCreationViewModel.BetTypeState,
setSelected: (SelectionElement) -> Unit,
elements: List<SelectionElement>,
addAnswer: (String) -> Unit,
deleteAnswer: (String) -> Unit
) {
var isOpen by remember {
mutableStateOf(false)
}
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
verticalArrangement = Arrangement.spacedBy(35.dp)
) {
item {
AllInSelectionBox(
isOpen = isOpen,
setIsOpen = { isOpen = it },
selected = selected,
setSelected = setSelected,
elements = elements
)
Spacer(modifier = Modifier.height(26.dp))
Column(modifier) {
AllInSelectionBox(
isOpen = isOpen,
setIsOpen = { isOpen = it },
selected = selected,
setSelected = setSelected,
elements = elements
)
Spacer(modifier = Modifier.height(26.dp))
when (selectedBetType) {
BetType.BINARY -> {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
BetCreationScreenBottomText(text = stringResource(id = R.string.yes_no_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.yes_no_bottom_text_2))
}
}
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
when (selectedBetType) {
BetCreationViewModel.BetTypeState.Binary -> {
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_yes_no_bottom_text_2))
}
is BetCreationViewModel.BetTypeState.Match -> {
BetCreationScreenBottomText(text = stringResource(selectedBetType.type.getTitleId()))
}
is BetCreationViewModel.BetTypeState.Custom -> {
val (currentAnswer, setCurrentAnswer) = remember { mutableStateOf("") }
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_custom_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_custom_bottom_text_2))
BetCreationScreenCustomAnswerTextField(
value = currentAnswer,
setValue = setCurrentAnswer,
onAdd = { addAnswer(currentAnswer) },
enabled = selectedBetType.answers.size < BET_MAX_ANSWERS,
buttonEnabled = currentAnswer.isNotBlank() && (currentAnswer !in selectedBetType.answers),
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(
id = R.string.bet_creation_max_answers,
BET_MAX_ANSWERS - selectedBetType.answers.size
),
color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.sm1,
modifier = Modifier.align(Alignment.End)
)
BetType.MATCH -> {
BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId())
)
}
BetType.CUSTOM -> {
BetCreationScreenBottomText(
text = stringResource(selectedBetType.getTitleId())
)
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
selectedBetType.answers.fastForEach {
BetCreationScreenCustomAnswer(
text = it,
onDelete = { deleteAnswer(it) }
)
}
}
}
}
typeError?.let { AllInErrorLine(text = it) }
}
}
}
}
@Preview
@Composable
private fun BetCreationScreenAnswerTabPreview() {
AllInTheme {
BetCreationScreenAnswerTab(
selected = null,
selectedBetType = BetCreationViewModel.BetTypeState.Binary,
setSelected = { },
elements = listOf(),
addAnswer = { },
deleteAnswer = { },
typeError = "Error"
)
}
}
@Preview
@Composable
private fun BetCreationScreenAnswerTabCustomPreview() {
AllInTheme {
BetCreationScreenAnswerTab(
selected = null,
selectedBetType = BetCreationViewModel.BetTypeState.Custom(listOf("Lorem ipsum", "Lorem iiiipsum", "Looooorem")),
setSelected = { },
elements = listOf(),
addAnswer = { },
deleteAnswer = { },
typeError = null
)
}
}

@ -1,73 +1,117 @@
package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.ext.formatToMediumDate
import fr.iut.alldev.allin.data.ext.formatToTime
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabDateTimeSection
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabPrivacySection
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
import java.time.ZonedDateTime
import fr.iut.alldev.allin.data.ext.formatToTime as formatToTime1
@Composable
fun BetCreationScreenQuestionTab(
modifier: Modifier = Modifier,
nbFriends: Int,
friends: List<User>,
betTheme: String,
betThemeError: String?,
setBetTheme: (String) -> Unit,
betPhrase: String,
betPhraseError: String?,
setBetPhrase: (String) -> Unit,
isPublic: Boolean,
setIsPublic: (Boolean) -> Unit,
isPrivate: Boolean,
setIsPrivate: (Boolean) -> Unit,
registerDate: ZonedDateTime,
registerDateError: String?,
betDate: ZonedDateTime,
betDateError: String?,
selectedFriends: MutableList<Int>,
selectedFriends: List<String>,
setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean) -> Unit,
toggleFriend: (String) -> Unit,
interactionSource: MutableInteractionSource
) {
Column(modifier) {
QuestionTabThemePhraseSection(
betTheme = betTheme,
betThemeError = betThemeError,
setBetTheme = setBetTheme,
betPhrase = betPhrase,
betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase,
interactionSource = interactionSource
)
Spacer(modifier = Modifier.height(35.dp))
QuestionTabDateTimeSection(
registerDate = registerDate.formatToMediumDate(),
registerTime = registerDate.formatToTime(),
registerDateError = registerDateError,
betDateError = betDateError,
endDate = betDate.formatToMediumDate(),
endTime = betDate.formatToTime(),
setEndDateDialog = setEndDateDialog,
setRegisterDateDialog = setRegisterDateDialog,
setRegisterTimeDialog = setRegisterTimeDialog,
setEndTimeDialog = setEndTimeDialog,
interactionSource = interactionSource,
)
Spacer(modifier = Modifier.height(35.dp))
QuestionTabPrivacySection(
isPublic = isPublic,
setIsPublic = setIsPublic,
nbFriends = nbFriends,
selectedFriends = selectedFriends,
interactionSource = interactionSource
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
verticalArrangement = Arrangement.spacedBy(35.dp)
) {
item {
QuestionTabThemePhraseSection(
betTheme = betTheme,
betThemeError = betThemeError,
setBetTheme = setBetTheme,
betPhrase = betPhrase,
betPhraseError = betPhraseError,
setBetPhrase = setBetPhrase,
interactionSource = interactionSource
)
}
item {
QuestionTabDateTimeSection(
registerDate = registerDate.formatToMediumDate(),
registerTime = registerDate.formatToTime1(),
registerDateError = registerDateError,
betDateError = betDateError,
endDate = betDate.formatToMediumDate(),
endTime = betDate.formatToTime1(),
setEndDateDialog = setEndDateDialog,
setRegisterDateDialog = setRegisterDateDialog,
setRegisterTimeDialog = setRegisterTimeDialog,
setEndTimeDialog = setEndTimeDialog,
interactionSource = interactionSource,
)
}
item {
QuestionTabPrivacySection(
isPrivate = isPrivate,
setIsPrivate = setIsPrivate,
friends = friends,
selectedFriends = selectedFriends,
toggleFriend = toggleFriend,
interactionSource = interactionSource
)
}
}
}
@Preview
@Composable
private fun BetCreationScreenQuestionTabPreview() {
AllInTheme {
BetCreationScreenQuestionTab(
friends = emptyList(),
betTheme = "Elly",
betThemeError = null,
setBetTheme = { },
betPhrase = "Trinh",
betPhraseError = null,
setBetPhrase = { },
isPrivate = true,
setIsPrivate = { },
registerDate = ZonedDateTime.now(),
registerDateError = null,
betDate = ZonedDateTime.now(),
betDateError = null,
selectedFriends = mutableListOf(),
setRegisterDateDialog = { },
setEndDateDialog = { },
setRegisterTimeDialog = { },
setEndTimeDialog = { },
toggleFriend = { },
interactionSource = remember { MutableInteractionSource() }
)
}
}

@ -17,10 +17,10 @@ import fr.iut.alldev.allin.ui.core.AllInTitleInfo
@Composable
internal fun QuestionTabDateTimeSection(
setRegisterDateDialog: (Boolean)->Unit,
setEndDateDialog: (Boolean)->Unit,
setRegisterTimeDialog: (Boolean)->Unit,
setEndTimeDialog: (Boolean)->Unit,
setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean) -> Unit,
registerDateError: String?,
betDateError: String?,
registerDate: String,
@ -30,10 +30,10 @@ internal fun QuestionTabDateTimeSection(
interactionSource: MutableInteractionSource
) {
AllInTitleInfo(
text = stringResource(id = R.string.End_registration_date),
text = stringResource(id = R.string.bet_creation_end_registration_date),
icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp),
tooltipText = stringResource(id = R.string.Register_tooltip),
tooltipText = stringResource(id = R.string.bet_creation_register_end_date_tooltip),
interactionSource = interactionSource
)
BetCreationScreenDateTimeRow(
@ -42,15 +42,15 @@ internal fun QuestionTabDateTimeSection(
onClickDate = { setRegisterDateDialog(true) },
onClickTime = { setRegisterTimeDialog(true) },
)
registerDateError?.let{
registerDateError?.let {
AllInErrorLine(text = it)
}
Spacer(modifier = Modifier.height(12.dp))
AllInTitleInfo(
text = stringResource(id = R.string.End_bet_date),
text = stringResource(id = R.string.bet_creation_end_bet_date),
icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp),
tooltipText = stringResource(id = R.string.BetEnd_tooltip),
tooltipText = stringResource(id = R.string.bet_creation_bet_end_date_tooltip),
interactionSource = interactionSource
)
BetCreationScreenDateTimeRow(
@ -59,7 +59,7 @@ internal fun QuestionTabDateTimeSection(
onClickDate = { setEndDateDialog(true) },
onClickTime = { setEndTimeDialog(true) },
)
betDateError?.let{
betDateError?.let {
AllInErrorLine(text = it)
}
}

@ -2,11 +2,14 @@ package fr.iut.alldev.allin.ui.betCreation.tabs.sections
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.filled.Lock
@ -17,30 +20,37 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.ext.nonLinkedScroll
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenBottomText
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenFriendLine
import fr.iut.alldev.allin.ui.core.AllInIconChip
import fr.iut.alldev.allin.ui.core.AllInRetractableCard
import fr.iut.alldev.allin.ui.core.AllInTitleInfo
import fr.iut.alldev.allin.ui.core.bet.AllInEmptyView
@Composable
fun QuestionTabPrivacySection(
isPublic: Boolean,
setIsPublic: (Boolean) -> Unit,
nbFriends: Int,
selectedFriends: MutableList<Int>,
isPrivate: Boolean,
setIsPrivate: (Boolean) -> Unit,
friends: List<User>,
selectedFriends: List<String>,
interactionSource: MutableInteractionSource,
toggleFriend: (String) -> Unit
) {
AllInTitleInfo(
text = stringResource(id = R.string.Bet_privacy),
text = stringResource(id = R.string.bet_creation_bet_privacy),
icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp),
tooltipText = stringResource(id = R.string.Privacy_tooltip),
tooltipText = stringResource(id = R.string.bet_creation_privacy_tooltip),
interactionSource = interactionSource
)
Row(
@ -48,83 +58,90 @@ fun QuestionTabPrivacySection(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
AllInIconChip(
text = stringResource(id = R.string.Public),
text = stringResource(id = R.string.bet_public),
leadingIcon = Icons.Default.Public,
onClick = {
setIsPublic(true)
},
isSelected = isPublic
onClick = { setIsPrivate(false) },
isSelected = !isPrivate
)
AllInIconChip(
text = stringResource(id = R.string.Private),
text = stringResource(id = R.string.bet_private),
leadingIcon = Icons.Default.Lock,
onClick = {
setIsPublic(false)
},
isSelected = !isPublic
onClick = { setIsPrivate(true) },
isSelected = isPrivate
)
}
Column(
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
var isOpen by remember {
mutableStateOf(false)
}
var isOpen by remember { mutableStateOf(false) }
if (isPublic) {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
BetCreationScreenBottomText(text = stringResource(id = R.string.public_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.public_bottom_text_2))
}
} else {
if (isPrivate) {
AllInRetractableCard(
text = stringResource(
id = R.string.n_friends_available,
nbFriends,
nbFriends
text = pluralStringResource(
id = R.plurals.bet_creation_friends_available_format,
friends.size,
friends.size
),
borderWidth = 1.dp,
boldText = nbFriends.toString(),
boldText = friends.size.toString(),
isOpen = isOpen,
setIsOpen = { isOpen = it }
) {
LazyColumn(
modifier = Modifier.height(165.dp)
) {
items(nbFriends) {
var isSelected by remember {
mutableStateOf(selectedFriends.contains(it))
}
Box {
LazyColumn(
modifier = Modifier
.heightIn(max = 440.dp)
.nonLinkedScroll()
) {
itemsIndexed(friends, key = { _, it -> it.id }) { idx, it ->
var isSelected by remember {
mutableStateOf(selectedFriends.contains(it.id))
}
if (it != 0) {
HorizontalDivider(color = AllInTheme.themeColors.border)
}
BetCreationScreenFriendLine(
username = "Dave",
allCoinsAmount = 542,
isSelected = isSelected
) {
if (isSelected) {
selectedFriends.remove(it)
} else {
selectedFriends.add(it)
if (idx != 0) {
HorizontalDivider(color = AllInTheme.colors.border)
}
BetCreationScreenFriendLine(
username = it.username,
allCoinsAmount = it.coins,
image = it.image,
isSelected = isSelected
) {
toggleFriend(it.id)
isSelected = !isSelected
}
isSelected = !isSelected
}
}
if (friends.isEmpty()) {
AllInEmptyView(
text = stringResource(id = R.string.friends_empty_text),
subtext = stringResource(id = R.string.friends_empty_subtext),
image = painterResource(id = R.drawable.silhouettes),
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center)
)
}
}
}
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_private_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_private_bottom_text_2))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_private_bottom_text_3))
}
} else {
Column(
modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp)
) {
BetCreationScreenBottomText(text = stringResource(id = R.string.private_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.private_bottom_text_2))
BetCreationScreenBottomText(text = stringResource(id = R.string.private_bottom_text_3))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_public_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_public_bottom_text_2))
}
}
}

@ -14,7 +14,7 @@ import androidx.compose.ui.res.stringResource
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.theme.AllInColorToken
import fr.iut.alldev.allin.ui.core.AllInTextField
import fr.iut.alldev.allin.ui.core.AllInTitleInfo
@ -30,32 +30,32 @@ internal fun QuestionTabThemePhraseSection(
interactionSource: MutableInteractionSource
) {
AllInTitleInfo(
text = stringResource(id = R.string.Theme),
text = stringResource(id = R.string.bet_creation_theme),
icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp),
tooltipText = stringResource(id = R.string.Theme_tooltip),
tooltipText = stringResource(id = R.string.bet_creation_theme_tooltip),
interactionSource = interactionSource
)
AllInTextField(
placeholder = stringResource(id = R.string.Theme_placeholder),
placeholder = stringResource(id = R.string.bet_creation_theme_placeholder),
value = betTheme,
modifier = Modifier.fillMaxWidth(),
maxChar = 20,
placeholderFontSize = 13.sp,
onValueChange = setBetTheme,
errorText = betThemeError,
borderColor = AllInTheme.colors.white
borderColor = AllInColorToken.white
)
Spacer(modifier = Modifier.height(10.dp))
AllInTitleInfo(
text = stringResource(id = R.string.Bet_Phrase),
text = stringResource(id = R.string.bet_creation_bet_phrase),
icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp),
tooltipText = stringResource(id = R.string.Phrase_tooltip),
tooltipText = stringResource(id = R.string.bet_creation_phrase_tooltip),
interactionSource = interactionSource
)
AllInTextField(
placeholder = stringResource(id = R.string.Bet_Phrase_placeholder),
placeholder = stringResource(id = R.string.bet_creation_bet_phrase_placeholder),
value = betPhrase,
modifier = Modifier
.fillMaxWidth()
@ -65,6 +65,6 @@ internal fun QuestionTabThemePhraseSection(
multiLine = true,
onValueChange = setBetPhrase,
errorText = betPhraseError,
borderColor = AllInTheme.colors.white
borderColor = AllInColorToken.white
)
}

@ -1,20 +1,22 @@
package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.ui.betHistory.components.GenericHistory
@Composable
fun BetCurrentScreen(
selectBet: (Bet, Boolean) -> Unit,
viewModel: BetCurrentViewModel = hiltViewModel()
) {
val bets by viewModel.bets.collectAsState()
val bets by viewModel.bets.collectAsStateWithLifecycle()
GenericHistory(
title = stringResource(id = R.string.bet_history_current_title),
bets = bets,
@ -25,6 +27,7 @@ fun BetCurrentScreen(
getEndBetTime = { it.bet.endBetDate.formatToTime() },
getStatus = { it.bet.betStatus },
getNbCoins = { it.userParticipation?.stake ?: 0 },
getWon = { true }
getWon = { true },
onClick = { selectBet(it.bet, false) },
)
}

@ -37,7 +37,7 @@ class BetCurrentViewModel @Inject constructor(
init {
viewModelScope.launch {
_bets.emit(
betRepository.getToConfirm(keystoreManager.getTokenOrEmpty())
betRepository.getCurrentBets(keystoreManager.getTokenOrEmpty())
)
}
}

@ -1,20 +1,22 @@
package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.ui.betHistory.components.GenericHistory
@Composable
fun BetHistoryScreen(
selectBet: (Bet, Boolean) -> Unit,
viewModel: BetHistoryViewModel = hiltViewModel()
) {
val bets by viewModel.bets.collectAsState()
val bets by viewModel.bets.collectAsStateWithLifecycle()
GenericHistory(
title = stringResource(id = R.string.bet_history_title),
bets = bets,
@ -25,6 +27,7 @@ fun BetHistoryScreen(
getEndBetTime = { it.bet.endBetDate.formatToTime() },
getStatus = { it.bet.betStatus },
getNbCoins = { it.amount },
getWon = { it.won }
getWon = { it.won },
onClick = { selectBet(it.bet, false) }
)
}

@ -24,6 +24,7 @@ import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getBetHistoryPhrase
import fr.iut.alldev.allin.ext.getBetHistoryStatusColor
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider
@ -40,7 +41,7 @@ val betHistoryStatusInlineContent = mapOf(
) {
Icon(
painter = AllInTheme.icons.allCoins(),
tint = AllInTheme.colors.white,
tint = AllInColorToken.white,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
@ -70,7 +71,7 @@ fun BetHistoryBetStatus(
appendInlineContent(betHistoryStatusModId, "[icon]")
append(betHistoryPhrase.substringAfter("[icon]"))
},
color = AllInTheme.colors.white,
color = AllInColorToken.white,
inlineContent = betHistoryStatusInlineContent,
style = AllInTheme.typography.h1,
fontSize = 24.sp

@ -13,6 +13,7 @@ import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider
@Composable
fun BetHistoryScreenCard(
modifier: Modifier = Modifier,
onClick: () -> Unit,
title: String,
creator: String,
category: String,
@ -29,6 +30,7 @@ fun BetHistoryScreenCard(
date = date,
time = time,
status = status,
onClick = onClick,
modifier = modifier
) {
BetHistoryBetStatus(
@ -47,6 +49,7 @@ private fun BetHistoryScreenCardPreview(
) {
AllInTheme {
BetHistoryScreenCard(
onClick = {},
creator = "Creator",
category = "Category",
title = "Title",

@ -1,9 +1,10 @@
package fr.iut.alldev.allin.ui.betHistory.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
@ -13,6 +14,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -27,17 +30,18 @@ fun <T> GenericHistory(
getStatus: (T) -> BetStatus,
getNbCoins: (T) -> Int,
getWon: (T) -> Boolean,
onClick: (T) -> Unit,
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 18.dp),
contentPadding = WindowInsets.navigationBars.asPaddingValues(horizontal = 24.dp, vertical = 18.dp),
verticalArrangement = Arrangement.spacedBy(18.dp),
) {
item {
Text(
text = title,
style = AllInTheme.typography.h1,
color = AllInTheme.colors.allInGrey,
color = AllInColorToken.allInGrey,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
@ -53,7 +57,8 @@ fun <T> GenericHistory(
time = getEndBetTime(it),
status = getStatus(it),
nbCoins = getNbCoins(it),
won = getWon(it)
won = getWon(it),
onClick = { onClick(it) }
)
}
}

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
@ -14,15 +15,14 @@ import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.data.model.bet.BinaryBet
import fr.iut.alldev.allin.theme.AllInColorToken
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
@ -71,10 +71,11 @@ fun BetResultBottomSheetContent(
odds: Float,
onClose: () -> Unit
) {
AllInMarqueeBox(backgroundBrush = AllInTheme.colors.allInMainGradientReverse) {
AllInMarqueeBox(backgroundBrush = AllInColorToken.allInMainGradientReverse) {
Box(
modifier = Modifier
.fillMaxSize()
.safeContentPadding()
.padding(16.dp)
) {
IconButton(
@ -85,16 +86,16 @@ fun BetResultBottomSheetContent(
) {
Icon(
imageVector = Icons.Default.Close,
tint = AllInTheme.colors.white,
tint = AllInColorToken.white,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
Icon(
painter = painterResource(R.drawable.allin),
painter = AllInTheme.icons.logo(),
contentDescription = null,
tint = AllInTheme.colors.white,
tint = AllInColorToken.white,
modifier = Modifier
.size(40.dp)
.align(Alignment.TopCenter)
@ -132,7 +133,7 @@ private fun BetResultBottomSheetContentPreview() {
BetResultBottomSheetContent(
username = "Pseudo",
coinAmount = 3976,
bet = YesNoBet(
bet = BinaryBet(
id = "1",
theme = "Theme",
phrase = "Phrase",
@ -141,6 +142,8 @@ private fun BetResultBottomSheetContentPreview() {
isPublic = true,
betStatus = BetStatus.IN_PROGRESS,
creator = "creator",
totalStakes = 0,
totalParticipants = 0
),
stake = 4175,
winnings = 2600,

@ -16,6 +16,7 @@ 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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.core.AllInCoinCount
@ -30,7 +31,7 @@ fun BetResultBottomSheetBetCardStats(
Column(
Modifier
.fillMaxWidth()
.background(AllInTheme.themeColors.background2)
.background(AllInTheme.colors.background2)
.padding(horizontal = 19.dp, vertical = 11.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
@ -42,12 +43,12 @@ fun BetResultBottomSheetBetCardStats(
Text(
text = stringResource(id = R.string.bet_result_stake),
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onMainSurface
color = AllInTheme.colors.onMainSurface
)
AllInCoinCount(
amount = stake,
color = AllInTheme.colors.allInPurple,
color = AllInColorToken.allInPurple,
textStyle = AllInTheme.typography.sm1,
position = IconPosition.TRAILING
)
@ -60,12 +61,12 @@ fun BetResultBottomSheetBetCardStats(
Text(
text = stringResource(id = R.string.bet_result_winnings),
style = AllInTheme.typography.sm2,
color = AllInTheme.colors.allInPurple
color = AllInColorToken.allInPurple
)
AllInCoinCount(
amount = winnings,
textStyle = AllInTheme.typography.sm1,
brush = AllInTheme.colors.allInMainGradient,
brush = AllInColorToken.allInMainGradient,
position = IconPosition.TRAILING
)
}
@ -78,17 +79,17 @@ fun BetResultBottomSheetBetCardStats(
Text(
text = stringResource(id = R.string.bet_result_odds),
style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2
color = AllInTheme.colors.onBackground2
)
AllInCard(
radius = 8.dp,
backgroundBrush = AllInTheme.colors.allInMainGradient
backgroundBrush = AllInColorToken.allInMainGradient
) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text(
text = "$odds",
style = AllInTheme.typography.sm1,
color = AllInTheme.colors.white
color = AllInColorToken.white
)
}
}

@ -19,6 +19,7 @@ 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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -54,18 +55,18 @@ fun BetResultBottomSheetContentCongratulations(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text= stringResource(id = R.string.bet_result_congratulations),
text = stringResource(id = R.string.bet_result_congratulations),
style = AllInTheme.typography.h1,
fontSize = 25.sp,
fontStyle = FontStyle.Italic,
color = AllInTheme.colors.white
color = AllInColorToken.white
)
Text(
text = "${username.uppercase()} !",
style = AllInTheme.typography.h1,
fontSize = 30.sp,
fontStyle = FontStyle.Italic,
color = AllInTheme.colors.white
color = AllInColorToken.white
)
}
}

@ -17,6 +17,7 @@ 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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.IconPosition
@ -33,7 +34,7 @@ fun BetResultBottomSheetContentCoinAmount(
Text(
text = stringResource(id = R.string.bet_result_you_win),
style = AllInTheme.typography.sm2,
color = AllInTheme.colors.white,
color = AllInColorToken.white,
fontSize = 20.sp
)
@ -42,7 +43,7 @@ fun BetResultBottomSheetContentCoinAmount(
.border(
width = 2.dp,
shape = RoundedCornerShape(100.dp),
color = AllInTheme.colors.white
color = AllInColorToken.white
)
.padding(vertical = 22.dp, horizontal = 33.dp)
@ -51,7 +52,7 @@ fun BetResultBottomSheetContentCoinAmount(
amount = amount,
textStyle = AllInTheme.typography.h1,
position = IconPosition.TRAILING,
color = AllInTheme.colors.white,
color = AllInColorToken.white,
size = 60,
modifier = Modifier
.align(Alignment.Center)
@ -61,7 +62,7 @@ fun BetResultBottomSheetContentCoinAmount(
amount = amount,
textStyle = AllInTheme.typography.h1,
position = IconPosition.TRAILING,
color = AllInTheme.colors.white.copy(alpha = .32f),
color = AllInColorToken.white.copy(alpha = .32f),
size = 60,
modifier = Modifier.align(Alignment.Center)
)

@ -4,13 +4,11 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@ -37,8 +35,7 @@ fun BetStatusBottomSheet(
sheetVisibility: Boolean,
sheetBackVisibility: Boolean,
betDetail: BetDetail?,
paddingValues: PaddingValues,
userCoinAmount: MutableIntState,
userCoinAmount: Int,
onParticipate: (stake: Int, response: String) -> Unit,
onDismiss: () -> Unit,
participateSheetVisibility: Boolean,
@ -55,9 +52,7 @@ fun BetStatusBottomSheet(
)
) {
betDetail?.let {
BetStatusBottomSheetBack(
status = it.bet.betStatus
)
BetStatusBottomSheetBack(status = it.bet.betStatus)
}
}
@ -82,10 +77,9 @@ fun BetStatusBottomSheet(
sheetVisibility = participateSheetVisibility &&
betDetail.bet.betStatus == BetStatus.IN_PROGRESS &&
state.hasExpandedState,
safeBottomPadding = paddingValues.calculateBottomPadding(),
odds = betDetail.answers.getOrNull(selectedAnswer)?.odds ?: 1f,
betPhrase = betDetail.bet.phrase,
coinAmount = userCoinAmount.intValue,
coinAmount = userCoinAmount,
onDismiss = { setParticipateSheetVisibility(false) },
state = rememberModalBottomSheetState(skipPartiallyExpanded = true),
elements = elements,
@ -93,8 +87,7 @@ fun BetStatusBottomSheet(
stake = stake,
setStake = { stake = it },
setElement = { idx -> selectedAnswer = idx },
enabled = stake != null &&
(stake ?: 0) <= userCoinAmount.intValue
enabled = (stake ?: 0) != 0 && (stake ?: 0) <= userCoinAmount
) {
stake?.let { stake ->
onParticipate(
@ -103,7 +96,6 @@ fun BetStatusBottomSheet(
)
}
}
}
}

@ -1,7 +1,11 @@
package fr.iut.alldev.allin.ui.betStatus.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -19,6 +23,7 @@ import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getColor
import fr.iut.alldev.allin.ext.getTextColor
import fr.iut.alldev.allin.ext.getTitleId
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.SHEET_HEIGHT
import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider
@ -28,7 +33,6 @@ fun BetStatusBottomSheetBack(
status: BetStatus,
modifier: Modifier = Modifier,
) {
Box(
modifier
.fillMaxSize()
@ -37,7 +41,9 @@ fun BetStatusBottomSheetBack(
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxHeight(1 - SHEET_HEIGHT)
modifier = Modifier
.background(status.getColor())
.fillMaxHeight(1 - SHEET_HEIGHT)
) {
Text(
text = stringResource(id = status.getTitleId()),
@ -50,7 +56,7 @@ fun BetStatusBottomSheetBack(
)
Icon(
painter = painterResource(id = R.drawable.allin_exit),
tint = AllInTheme.colors.white,
tint = AllInColorToken.white,
contentDescription = null
)
}

@ -1,6 +1,5 @@
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
@ -21,13 +20,14 @@ 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.BinaryBet
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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.preview.BetDetailPreviewProvider
@ -68,7 +68,7 @@ fun BetDetail.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> {
this@getParticipationAnswers.getAnswerOfResponse(bet.nameTeam2)?.let {
ParticipationAnswerLine(
text = it.response,
color = AllInTheme.colors.allInBarPink,
color = AllInColorToken.allInBarPink,
odds = it.odds,
locale = locale
)
@ -76,7 +76,7 @@ fun BetDetail.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> {
}
}
is YesNoBet -> buildList {
is BinaryBet -> buildList {
add {
this@getParticipationAnswers.getAnswerOfResponse(YES_VALUE)?.let {
ParticipationAnswerLine(
@ -90,7 +90,7 @@ fun BetDetail.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> {
this@getParticipationAnswers.getAnswerOfResponse(NO_VALUE)?.let {
ParticipationAnswerLine(
text = it.response,
color = AllInTheme.colors.allInBarPink,
color = AllInColorToken.allInBarPink,
odds = it.odds,
locale = locale
)
@ -103,7 +103,7 @@ fun BetDetail.getParticipationAnswers(): List<@Composable RowScope.() -> Unit> {
@Composable
private fun ParticipationAnswerLine(
text: String,
color: Color = AllInTheme.colors.allInBlue,
color: Color = AllInColorToken.allInBlue,
locale: Locale,
odds: Float
) {
@ -121,12 +121,12 @@ private fun ParticipationAnswerLine(
AllInCard(
radius = 50.dp,
backgroundColor = AllInTheme.colors.allInPurple
backgroundColor = AllInColorToken.allInPurple
) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text(
text = "x${odds.formatToSimple(locale)}",
color = AllInTheme.colors.white,
color = AllInColorToken.white,
style = AllInTheme.typography.h2
)
}
@ -143,7 +143,7 @@ fun Bet.getAnswerFromParticipationIdx(idx: Int) =
else -> ""
}
is YesNoBet -> when (idx) {
is BinaryBet -> when (idx) {
0 -> YES_VALUE
1 -> NO_VALUE
else -> ""
@ -151,7 +151,6 @@ fun Bet.getAnswerFromParticipationIdx(idx: Int) =
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ParticipationAnswersPreview(
@PreviewParameter(BetDetailPreviewProvider::class) bet: BetDetail,

@ -1,10 +1,22 @@
package fr.iut.alldev.allin.ui.betStatus.components
import android.content.res.Configuration
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
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.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -12,10 +24,11 @@ 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.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsPadding
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton
@ -24,13 +37,13 @@ import fr.iut.alldev.allin.ui.core.AllInIntTextField
import fr.iut.alldev.allin.ui.core.AllInSelectionBox
import fr.iut.alldev.allin.ui.core.topbar.AllInTopBarCoinCounter
import kotlinx.coroutines.launch
import kotlin.math.ln
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BetStatusParticipationBottomSheet(
sheetVisibility: Boolean,
safeBottomPadding: Dp,
betPhrase: String,
coinAmount: Int,
onDismiss: () -> Unit,
@ -42,17 +55,17 @@ fun BetStatusParticipationBottomSheet(
elements: List<@Composable RowScope.() -> Unit>,
selectedElement: (@Composable RowScope.() -> Unit)?,
setElement: (Int) -> Unit,
onParticipate: () -> Unit
) {
onParticipate: () -> Unit,
) {
val scope = rememberCoroutineScope()
AllInBottomSheet(
sheetVisibility = sheetVisibility,
onDismiss = onDismiss,
state = state,
containerColor = AllInTheme.themeColors.background2
containerColor = AllInTheme.colors.background2
) {
BetStatusParticipationBottomSheetContent(
safeBottomPadding = safeBottomPadding,
betPhrase = betPhrase,
coinAmount = coinAmount,
elements = elements,
@ -73,8 +86,7 @@ fun BetStatusParticipationBottomSheet(
}
@Composable
private fun ColumnScope.BetStatusParticipationBottomSheetContent(
safeBottomPadding: Dp,
private fun BetStatusParticipationBottomSheetContent(
betPhrase: String,
coinAmount: Int,
enabled: Boolean,
@ -87,6 +99,12 @@ private fun ColumnScope.BetStatusParticipationBottomSheetContent(
onButtonClick: () -> Unit
) {
val (answersBoxIsOpen, setAnswersBoxIsOpen) = remember { mutableStateOf(false) }
val betStrength by animateFloatAsState(
targetValue = if (enabled) {
(ln(stake?.toFloat() ?: 0f) / ln(coinAmount.toFloat())).coerceIn(.42f..1f)
} else .42f,
label = ""
)
Row(
modifier = Modifier.fillMaxWidth(),
@ -94,26 +112,27 @@ private fun ColumnScope.BetStatusParticipationBottomSheetContent(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.place_your_bets),
text = stringResource(id = R.string.bet_status_place_your_bets),
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.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,
backgroundColor = AllInColorToken.allInBlue,
textColor = AllInColorToken.white,
iconColor = AllInColorToken.white,
)
}
Column(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Text(
text = betPhrase,
style = AllInTheme.typography.p2,
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.onMainSurface,
modifier = Modifier.padding(vertical = 30.dp)
)
AllInSelectionBox(
@ -127,20 +146,25 @@ private fun ColumnScope.BetStatusParticipationBottomSheetContent(
Spacer(modifier = Modifier.height(8.dp))
AllInIntTextField(
value = stake,
setValue = setStake,
setValue = { setStake(it?.coerceAtMost(coinAmount)) },
textStyle = AllInTheme.typography.h1.copy(
fontSize = 20.sp,
color = AllInColorToken.allInPurple
),
placeholder = stringResource(id = R.string.bet_result_stake),
trailingIcon = AllInTheme.icons.allCoins(),
trailingIconColor = if (enabled) AllInColorToken.allInPurple else null,
borderColor = if (enabled) AllInColorToken.allInPurple.copy(alpha = betStrength) else AllInTheme.colors.border,
modifier = Modifier.fillMaxWidth(),
maxChar = null
)
}
Spacer(modifier = Modifier.height(100.dp))
HorizontalDivider(color = AllInTheme.themeColors.border)
HorizontalDivider(color = AllInTheme.colors.border)
Column(
modifier = Modifier
.background(AllInTheme.themeColors.background)
.padding(horizontal = 7.dp)
.padding(bottom = safeBottomPadding, top = 7.dp),
.background(AllInTheme.colors.background)
.padding(7.dp),
verticalArrangement = Arrangement.spacedBy(7.dp)
) {
Row(
@ -149,23 +173,26 @@ private fun ColumnScope.BetStatusParticipationBottomSheetContent(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.Possible_winnings),
text = stringResource(id = R.string.participation_possible_winnings),
style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onBackground
color = AllInTheme.colors.onBackground
)
AllInCoinCount(
amount = stake?.let { (it + (it * odds)).roundToInt() } ?: 0,
color = AllInTheme.themeColors.onBackground
amount = stake?.let { (it * odds).roundToInt() } ?: 0,
color = AllInTheme.colors.onBackground
)
}
AllInButton(
enabled = enabled,
color = AllInTheme.colors.allInPurple,
text = stringResource(id = R.string.Participate),
textColor = AllInTheme.colors.white,
color = AllInColorToken.allInPurple,
text = stringResource(id = R.string.bet_participate),
textColor = AllInColorToken.white,
radius = 5.dp,
onClick = onButtonClick
onClick = onButtonClick,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.padding(bottomSheetNavigationBarsPadding()))
}
}
@ -176,7 +203,6 @@ private fun BetStatusParticipationBottomSheetContentPreview() {
AllInTheme {
Column {
BetStatusParticipationBottomSheetContent(
safeBottomPadding = 0.dp,
betPhrase = "Bet phrase",
coinAmount = 3620,
onButtonClick = {},
@ -190,4 +216,26 @@ private fun BetStatusParticipationBottomSheetContentPreview() {
)
}
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BetStatusParticipationBottomSheetContentEmptyPreview() {
AllInTheme {
Column {
BetStatusParticipationBottomSheetContent(
betPhrase = "Bet phrase",
coinAmount = 3620,
onButtonClick = {},
elements = emptyList(),
setElement = {},
selectedElement = null,
enabled = true,
stake = null,
odds = 0.42f,
setStake = {}
)
}
}
}

@ -29,7 +29,7 @@ fun BetStatusWinner(
coinAmount: Int,
username: String,
multiplier: Float,
color: Color = AllInTheme.themeColors.onMainSurface,
color: Color = AllInTheme.colors.onMainSurface,
) {
Column {
HorizontalDivider(color = color.copy(alpha = .4f))
@ -38,7 +38,7 @@ fun BetStatusWinner(
modifier = Modifier
.fillMaxWidth()
.background(color.copy(alpha = .2f))
.padding(vertical = 20.dp)
.padding(20.dp)
) {
AllInTextIcon(
text = answer,

@ -8,12 +8,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextIcon
import fr.iut.alldev.allin.ui.core.IconPosition
@Composable
fun YesNoDetailsLine(
fun BinaryDetailsLine(
icon: Painter,
yesText: String,
noText: String,
@ -24,7 +25,7 @@ fun YesNoDetailsLine(
) {
AllInTextIcon(
text = yesText,
color = AllInTheme.colors.allInBlue,
color = AllInColorToken.allInBlue,
icon = icon,
position = IconPosition.LEADING,
size = 15,
@ -32,7 +33,7 @@ fun YesNoDetailsLine(
)
AllInTextIcon(
text = noText,
color = AllInTheme.colors.allInBarPink,
color = AllInColorToken.allInBarPink,
icon = icon,
position = IconPosition.TRAILING,
size = 15,
@ -46,7 +47,7 @@ fun YesNoDetailsLine(
@Composable
private fun YesNoDetailsLinePreview() {
AllInTheme {
YesNoDetailsLine(
BinaryDetailsLine(
icon = AllInTheme.icons.allCoins(),
yesText = "550",
noText = "330"

@ -5,11 +5,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.data.ext.toPercentageString
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.PercentagePositionnedElement
import fr.iut.alldev.allin.ui.core.StatBar
@ -25,7 +27,7 @@ fun BinaryStatBar(
Row {
Text(
text = response1,
color = AllInTheme.colors.allInBlue,
color = AllInColorToken.allInBlue,
style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic,
fontSize = 30.sp,
@ -36,7 +38,9 @@ fun BinaryStatBar(
style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic,
fontSize = 30.sp,
color = AllInTheme.colors.allInBarPink
color = AllInColorToken.allInBarPink,
textAlign = TextAlign.End,
modifier = Modifier.weight(1f)
)
}
StatBar(percentage = response1Percentage)
@ -46,22 +50,22 @@ fun BinaryStatBar(
Text(
text = response1Percentage.toPercentageString(),
style = AllInTheme.typography.sm1,
color = AllInTheme.colors.allInBarPurple
color = AllInColorToken.allInBarPurple
)
}
}
}
private class YesNoStatBarPreviewProvider : PreviewParameterProvider<Float> {
private class BinaryStatBarPreviewProvider : PreviewParameterProvider<Float> {
override val values = sequenceOf(0f, .33f, .5f, .66f, 1f)
}
@Preview
@Composable
private fun YesNoStatBarPreview(
@PreviewParameter(YesNoStatBarPreviewProvider::class) percentage: Float,
private fun BinaryStatBarPreview(
@PreviewParameter(BinaryStatBarPreviewProvider::class) percentage: Float,
) {
AllInTheme {
BinaryStatBar(

@ -0,0 +1,62 @@
package fr.iut.alldev.allin.ui.betStatus.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInTextIcon
import fr.iut.alldev.allin.ui.core.IconPosition
@Composable
fun SimpleDetailsLine(
icon: Painter,
text: String,
isWin: Boolean
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
AllInTextIcon(
text = text,
color = if (isWin) {
AllInColorToken.allInBarViolet
} else {
AllInColorToken.allInBlue
},
icon = icon,
position = IconPosition.TRAILING,
size = 15,
iconSize = 15
)
}
}
@Preview
@Composable
private fun SimpleDetailsLineWinPreview() {
AllInTheme {
SimpleDetailsLine(
icon = AllInTheme.icons.allCoins(),
text = "550",
isWin = true
)
}
}
@Preview
@Composable
private fun SimpleDetailsLinePreview() {
AllInTheme {
SimpleDetailsLine(
icon = AllInTheme.icons.allCoins(),
text = "550",
isWin = false
)
}
}

@ -0,0 +1,124 @@
package fr.iut.alldev.allin.ui.betStatus.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle
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.data.ext.toPercentageString
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun SimpleStatBar(
percentage: Float,
response: String,
isWin: Boolean,
modifier: Modifier = Modifier,
) {
Column(modifier) {
Text(
text = response,
color = if (isWin) {
AllInColorToken.allInBarPink
} else {
AllInColorToken.allInBlue
},
style = AllInTheme.typography.sm2,
fontStyle = FontStyle.Italic,
fontSize = 18.sp
)
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.height(20.dp)
.let { itModifier ->
if (percentage != 0f && !percentage.isNaN()) itModifier.weight(percentage)
else itModifier
}
.clip(
AbsoluteRoundedCornerShape(
topLeftPercent = 50,
bottomLeftPercent = 50,
topRightPercent = 0,
bottomRightPercent = 0
)
)
.background(
if (isWin) {
AllInColorToken.allInBar2ndGradient
} else {
AllInColorToken.allInBar1stGradient
}
)
)
Icon(
painter = painterResource(id = R.drawable.fire_solid),
tint = if (isWin) {
AllInColorToken.allInBarViolet
} else {
AllInColorToken.allInBarPurple
},
contentDescription = null,
modifier = Modifier
.size(32.dp)
.offset((-7).dp)
)
Text(
modifier = Modifier.let { itModifier ->
if (percentage != 1f && !percentage.isNaN()) itModifier.weight(1 - percentage)
else itModifier
},
text = percentage.toPercentageString(),
style = AllInTheme.typography.h1.copy(
fontSize = if (isWin) 24.sp else 16.sp
),
color = if (isWin) {
AllInColorToken.allInBarViolet
} else {
AllInColorToken.allInBarPurple
}
)
}
}
}
@Preview
@Composable
private fun SimpleStatBarBarWinPreview() {
AllInTheme {
SimpleStatBar(
percentage = .8f,
response = "Answer",
isWin = true
)
}
}
@Preview
@Composable
private fun SimpleStatBarBarPreview() {
AllInTheme {
SimpleStatBar(
percentage = .4f,
response = "Answer",
isWin = false
)
}
}

@ -6,29 +6,29 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
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.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
@ -42,18 +42,28 @@ 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.User
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.NO_VALUE
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
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsInsets
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.ext.nonLinkedScroll
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner
import fr.iut.alldev.allin.ui.betStatus.components.BinaryDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.BinaryStatBar
import fr.iut.alldev.allin.ui.betStatus.components.YesNoDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.SimpleDetailsLine
import fr.iut.alldev.allin.ui.betStatus.components.SimpleStatBar
import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer
import fr.iut.alldev.allin.ui.core.ProfilePicture
@ -65,27 +75,17 @@ import fr.iut.alldev.allin.vo.bet.BetDisplayer
import java.util.Locale
class BetStatusBottomSheetBetDisplayer(
val openParticipateSheet: () -> Unit
val openParticipateSheet: () -> Unit,
val getImageUrl: (id: String) -> String
) : BetDisplayer {
val paddingValues = mutableStateOf(PaddingValues())
@Composable
private fun DisplayBinaryBet(
private fun DisplayBetDail(
betDetail: BetDetail,
response1: String,
response2: String,
response1Display: String = response1,
response2Display: String = response2
currentUser: User,
winnerColor: @Composable () -> Color,
statBar: LazyListScope.() -> Unit
) {
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)) {
Box(Modifier) {
Column {
Column(Modifier.padding(horizontal = 20.dp)) {
BetTitleHeader(
@ -115,98 +115,88 @@ class BetStatusBottomSheetBetDisplayer(
Spacer(modifier = Modifier.height(20.dp))
}
if (betDetail.bet.betStatus == BetStatus.FINISHED) {
BetStatusWinner(
answer = response1Display,
color = AllInTheme.colors.allInBlue,
coinAmount = 442,
username = "Imri",
multiplier = 1.2f
)
betDetail.wonParticipation?.let { wonParticipation ->
BetStatusWinner(
answer = wonParticipation.response,
color = winnerColor(),
coinAmount = wonParticipation.stake,
username = wonParticipation.username,
multiplier = betDetail.getAnswerOfResponse(wonParticipation.response)
?.odds ?: .5f
)
}
} else {
HorizontalDivider(color = AllInTheme.themeColors.border)
HorizontalDivider(color = AllInTheme.colors.border)
}
Column(
Modifier
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.background(AllInTheme.themeColors.background2)
.padding(horizontal = 20.dp)
.background(AllInTheme.colors.background2)
.nonLinkedScroll(),
contentPadding = bottomSheetNavigationBarsInsets().asPaddingValues(top = 20.dp, start = 20.dp, end = 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 = rememberVectorPainter(image = Icons.Filled.People),
yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0"
)
YesNoDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0"
)
YesNoDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
)
statBar(this)
if (betDetail.participations.isNotEmpty() || betDetail.userParticipation != null) {
item {
Text(
text = stringResource(id = R.string.bet_status_participants_list),
fontSize = 20.sp,
color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.h1,
modifier = Modifier.padding(vertical = 36.dp)
)
}
}
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)
)
}
betDetail.userParticipation?.let {
item {
BetStatusParticipant(
username = it.username,
allCoinsAmount = it.stake,
image = getImageUrl(it.userId)
)
HorizontalDivider(
color = AllInTheme.colors.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
)
}
}
items(betDetail.participations) {
if (it.username != betDetail.userParticipation?.username) {
BetStatusParticipant(
username = it.username,
allCoinsAmount = it.stake,
image = getImageUrl(it.userId)
)
Spacer(modifier = Modifier.height(8.dp))
}
}
item {
if (betDetail.bet.betStatus != BetStatus.FINISHED && betDetail.userParticipation == null) {
Spacer(modifier = Modifier.height(75.dp))
}
}
}
}
if (betDetail.bet.betStatus != BetStatus.FINISHED && betDetail.userParticipation == null) {
if (
betDetail.bet.betStatus != BetStatus.FINISHED &&
betDetail.userParticipation == null &&
betDetail.bet.creator != currentUser.username
) {
RainbowButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(horizontal = 7.dp),
text = stringResource(id = R.string.Participate),
.background(
Brush.verticalGradient(
.2f to Color.Transparent,
.5f to AllInTheme.colors.background2
)
)
.padding(bottomSheetNavigationBarsInsets().asPaddingValues(7.dp)),
text = stringResource(id = R.string.bet_participate),
enabled = betDetail.bet.betStatus == BetStatus.IN_PROGRESS,
onClick = openParticipateSheet
)
@ -214,37 +204,180 @@ class BetStatusBottomSheetBetDisplayer(
}
}
@OptIn(ExperimentalMaterial3Api::class)
private fun LazyListScope.displayBinaryStatBar(
betDetail: BetDetail,
response1: String,
response2: String,
response1Display: @Composable () -> String = { response1 },
response2Display: @Composable () -> String = { response2 }
) {
item {
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val response1Answer = remember(betDetail) { betDetail.getAnswerOfResponse(response1) }
val response2Answer = remember(betDetail) { betDetail.getAnswerOfResponse(response2) }
BinaryStatBar(
response1Percentage = remember(betDetail) {
response1Answer?.let { betDetail.getPercentageOfAnswer(response1Answer) } ?: 0f
},
response1 = response1Display(),
response2 = response2Display()
)
AllInDetailsDrawer {
BinaryDetailsLine(
icon = AllInTheme.icons.allCoins(),
yesText = response1Answer?.totalStakes?.toString() ?: "0",
noText = response2Answer?.totalStakes?.toString() ?: "0"
)
BinaryDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.People),
yesText = response1Answer?.totalParticipants?.toString() ?: "0",
noText = response2Answer?.totalParticipants?.toString() ?: "0"
)
BinaryDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
yesText = response1Answer?.highestStake?.toString() ?: "0",
noText = response2Answer?.highestStake?.toString() ?: "0"
)
BinaryDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
yesText = "x${response1Answer?.odds?.formatToSimple(locale) ?: "1.00"}",
noText = "x${response2Answer?.odds?.formatToSimple(locale) ?: "1.00"}"
)
}
}
}
private fun LazyListScope.displayMultiStatBar(
responsesWithDetail: List<Pair<BetAnswerDetail, Float>>,
locale: Locale
) {
itemsIndexed(responsesWithDetail) { idx, (answer, percentage) ->
val isWin = remember { idx == 0 }
SimpleStatBar(
percentage = percentage,
response = answer.response,
isWin = isWin
)
AllInDetailsDrawer {
SimpleDetailsLine(
icon = AllInTheme.icons.allCoins(),
text = answer.totalStakes.toString(),
isWin = isWin
)
SimpleDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.People),
text = answer.totalParticipants.toString(),
isWin = isWin
)
SimpleDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.WorkspacePremium),
text = answer.highestStake.toString(),
isWin = isWin
)
SimpleDetailsLine(
icon = rememberVectorPainter(image = Icons.Filled.EmojiEvents),
text = "x${answer.odds.formatToSimple(locale)}",
isWin = isWin
)
}
}
}
@Composable
override fun DisplayYesNoBet(betDetail: BetDetail) {
DisplayBinaryBet(
override fun DisplayBinaryBet(betDetail: BetDetail, currentUser: User) {
DisplayBetDail(
betDetail = betDetail,
response1 = YES_VALUE,
response2 = NO_VALUE,
response1Display = stringResource(id = R.string.Yes).uppercase(),
response2Display = stringResource(id = R.string.No).uppercase()
)
currentUser = currentUser,
winnerColor = {
if (betDetail.wonParticipation?.response == YES_VALUE) AllInColorToken.allInBlue
else AllInColorToken.allInPink
}
) {
displayBinaryStatBar(
betDetail = betDetail,
response1 = YES_VALUE,
response2 = NO_VALUE,
response1Display = { stringResource(id = R.string.generic_yes).uppercase() },
response2Display = { stringResource(id = R.string.generic_no).uppercase() },
)
}
}
@Composable
override fun DisplayMatchBet(betDetail: BetDetail) {
val bet = remember { betDetail.bet as MatchBet }
DisplayBinaryBet(
override fun DisplayMatchBet(betDetail: BetDetail, currentUser: User) {
val matchBet = remember { betDetail.bet as MatchBet }
DisplayBetDail(
betDetail = betDetail,
response1 = bet.nameTeam1,
response2 = bet.nameTeam2
)
currentUser = currentUser,
winnerColor = {
if (betDetail.wonParticipation?.response == matchBet.nameTeam1) AllInColorToken.allInBlue
else AllInColorToken.allInPink
}
) {
displayBinaryStatBar(
betDetail = betDetail,
response1 = matchBet.nameTeam1,
response2 = matchBet.nameTeam2
)
}
}
@Composable
override fun DisplayCustomBet(betDetail: BetDetail) {
Text("This is a CUSTOM BET")
override fun DisplayCustomBet(betDetail: BetDetail, currentUser: User) {
val customBet = remember { betDetail.bet as CustomBet }
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val responsesWithDetail = remember(betDetail) {
if (customBet.possibleAnswers.size > 2) {
customBet.getResponses().mapNotNull {
betDetail.getAnswerOfResponse(it)
}.associateWith {
betDetail.getPercentageOfAnswer(it)
}
.toList()
.sortedByDescending { it.second }
} else emptyList()
}
DisplayBetDail(
betDetail = betDetail,
currentUser = currentUser,
winnerColor = {
val isBinary = remember { customBet.possibleAnswers.size == 2 }
if (isBinary) {
if (betDetail.wonParticipation?.response == customBet.possibleAnswers.first()) AllInColorToken.allInBlue
else AllInColorToken.allInPink
} else AllInTheme.colors.onMainSurface
}
) {
if (customBet.possibleAnswers.size == 2) {
displayBinaryStatBar(
betDetail = betDetail,
response1 = customBet.possibleAnswers.first(),
response2 = customBet.possibleAnswers.last()
)
} else {
displayMultiStatBar(
responsesWithDetail = responsesWithDetail,
locale = locale
)
}
}
}
}
@Composable
fun BetStatusParticipant(
username: String,
image: String?,
allCoinsAmount: Int
) {
Row(
@ -252,19 +385,23 @@ fun BetStatusParticipant(
horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically
) {
ProfilePicture(modifier = Modifier.size(25.dp))
ProfilePicture(
image = image,
fallback = username.asFallbackProfileUsername(),
size = 25.dp
)
Text(
text = username,
fontWeight = FontWeight.Bold,
style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.onMainSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
AllInCoinCount(
amount = allCoinsAmount,
color = AllInTheme.colors.allInPurple
color = AllInColorToken.allInPurple
)
}
}
@ -276,8 +413,20 @@ private fun BetStatusBottomSheetPreview(
@PreviewParameter(BetDetailPreviewProvider::class) bet: BetDetail
) {
AllInTheme {
BetStatusBottomSheetBetDisplayer {
}.DisplayBet(bet)
BetStatusBottomSheetBetDisplayer(
openParticipateSheet = {},
getImageUrl = { "" }
).DisplayBet(
betDetail = bet,
currentUser = User(
id = "x",
username = "aaa",
email = "aaa",
coins = 150,
nbBets = 0,
nbFriends = 0,
bestWin = 0,
)
)
}
}

@ -9,6 +9,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -16,7 +17,7 @@ fun AllInAlertDialog(
enabled: Boolean = true,
title: String,
text: String,
confirmText: String = stringResource(id = R.string.Ok),
confirmText: String = stringResource(id = R.string.generic_ok),
onDismiss: () -> Unit,
onConfirm: () -> Unit = onDismiss,
) {
@ -43,16 +44,16 @@ fun AllInAlertDialog(
text = confirmText,
fontSize = 15.sp,
style = AllInTheme.typography.h1.copy(
brush = AllInTheme.colors.allInMainGradient
brush = AllInColorToken.allInMainGradient
)
)
}
},
onDismissRequest = onDismiss,
containerColor = AllInTheme.themeColors.mainSurface,
titleContentColor = AllInTheme.themeColors.onMainSurface,
textContentColor = AllInTheme.themeColors.onBackground2,
containerColor = AllInTheme.colors.mainSurface,
titleContentColor = AllInTheme.colors.onMainSurface,
textContentColor = AllInTheme.colors.onBackground2,
)
}
}

@ -1,7 +1,11 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
@ -14,17 +18,17 @@ import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@Composable
fun AllInBottomSheet(
sheetVisibility: Boolean,
onDismiss: ()->Unit,
onDismiss: () -> Unit,
state: SheetState,
scrimColor: Color = BottomSheetDefaults.ScrimColor,
containerColor: Color = AllInTheme.themeColors.background,
dragHandle: (@Composable ()->Unit)? = { BottomSheetDefaults.DragHandle() },
content: @Composable ColumnScope.()->Unit
containerColor: Color = AllInTheme.colors.background,
dragHandle: (@Composable () -> Unit)? = { BottomSheetDefaults.DragHandle() },
content: @Composable ColumnScope.() -> Unit
) {
val localDensity = LocalDensity.current
val localLayoutDirection = LocalLayoutDirection.current
if(sheetVisibility) {
if (sheetVisibility) {
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = state,
@ -35,7 +39,7 @@ fun AllInBottomSheet(
WindowInsets(
left = it.getLeft(localDensity, localLayoutDirection),
right = it.getRight(localDensity, localLayoutDirection),
top = it.getTop(localDensity),
top = 0,
bottom = 0,
)
},

@ -1,44 +1,57 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@Composable
fun AllInButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
color: Color,
text: String,
textColor: Color,
radius: Dp = 15.dp,
onClick: () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = AllInTheme.typography.h2,
enabled: Boolean = true,
isSmall: Boolean = false,
radius: Dp = 10.dp,
shape: Shape = AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100)
) {
AllInCard(
onClick = onClick,
Button(
shape = shape,
colors = ButtonDefaults.buttonColors(
containerColor = color,
disabledContainerColor = AllInTheme.colors.disabled
),
modifier = modifier,
radius = radius,
backgroundColor = color,
enabled = enabled
enabled = enabled,
onClick = onClick,
contentPadding = if (isSmall) {
PaddingValues(horizontal = 12.dp)
} else ButtonDefaults.ContentPadding
) {
Text(
text = text,
textAlign = TextAlign.Center,
style = AllInTheme.typography.h2,
color = if(enabled) textColor else AllInTheme.themeColors.disabledBorder,
fontSize = 20.sp,
modifier = Modifier
.padding(vertical = 15.dp)
.fillMaxWidth(),
style = textStyle,
color = if (enabled) textColor else AllInTheme.colors.disabledBorder,
fontSize = if (isSmall) 12.sp else 20.sp,
modifier = Modifier.padding(vertical = if (isSmall) 0.dp else 8.dp)
)
}
}
@ -48,12 +61,11 @@ fun AllInButton(
private fun AllInButtonPreview() {
AllInTheme {
AllInButton(
color = AllInTheme.colors.allInLoginPurple,
color = AllInColorToken.allInLoginPurple,
text = "Connexion",
textColor = Color.White
) {
}
textColor = Color.White,
onClick = { }
)
}
}
@ -62,12 +74,40 @@ private fun AllInButtonPreview() {
private fun AllInButtonDisabledPreview() {
AllInTheme {
AllInButton(
color = AllInTheme.colors.allInLoginPurple,
color = AllInColorToken.allInLoginPurple,
text = "Connexion",
textColor = Color.White,
enabled = false
) {
enabled = false,
onClick = {}
)
}
}
}
@Preview
@Composable
private fun AllInButtonSmallPreview() {
AllInTheme {
AllInButton(
color = AllInColorToken.allInLoginPurple,
text = "Connexion",
textColor = Color.White,
isSmall = true,
onClick = { }
)
}
}
@Preview
@Composable
private fun AllInButtonDisabledSmallPreview() {
AllInTheme {
AllInButton(
color = AllInColorToken.allInLoginPurple,
text = "Connexion",
textColor = Color.White,
enabled = false,
isSmall = true,
onClick = {}
)
}
}

@ -11,8 +11,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@ -20,29 +20,32 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AllInCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
radius: Dp = 15.dp,
shape: Shape? = null,
enabled: Boolean = true,
backgroundColor: Color = AllInTheme.themeColors.background,
disabledBackgroundColor: Color = AllInTheme.themeColors.disabled,
backgroundColor: Color = AllInTheme.colors.background,
disabledBackgroundColor: Color = AllInTheme.colors.disabled,
backgroundBrush: Brush? = null,
borderWidth: Dp? = null,
borderColor: Color = AllInTheme.themeColors.border,
disabledBorderColor: Color = AllInTheme.themeColors.disabledBorder,
borderColor: Color = AllInTheme.colors.border,
disabledBorderColor: Color = AllInTheme.colors.disabledBorder,
borderBrush: Brush? = null,
content: @Composable () -> Unit,
) {
val cardShape = AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100)
val cardShape = shape ?: AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100)
val cardModifier = modifier
.run {
backgroundBrush?.let {
@ -89,15 +92,16 @@ fun AllInBouncyCard(
onClick: (() -> Unit)? = null,
radius: Dp = 15.dp,
enabled: Boolean = true,
backgroundColor: Color = AllInTheme.themeColors.background,
disabledBackgroundColor: Color = AllInTheme.themeColors.disabled,
backgroundColor: Color = AllInTheme.colors.background,
disabledBackgroundColor: Color = AllInTheme.colors.disabled,
backgroundBrush: Brush? = null,
borderWidth: Dp? = null,
borderColor: Color = AllInTheme.themeColors.border,
disabledBorderColor: Color = AllInTheme.themeColors.disabledBorder,
borderColor: Color = AllInTheme.colors.border,
disabledBorderColor: Color = AllInTheme.colors.disabledBorder,
borderBrush: Brush? = null,
content: @Composable () -> Unit,
) {
val haptic = LocalHapticFeedback.current
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
@ -106,15 +110,24 @@ fun AllInBouncyCard(
animationSpec = spring(
Spring.DampingRatioHighBouncy,
Spring.StiffnessMediumLow
)
), label = ""
)
LaunchedEffect(key1 = scale < .97f) {
if (scale < .97f) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
AllInCard(
modifier = modifier
.combinedClickable(
interactionSource = interactionSource,
indication = null,
onClick = { onClick?.let { it() } }
)
.let {
if (enabled) it.combinedClickable(
interactionSource = interactionSource,
indication = null,
onClick = { onClick?.let { it() } }
) else it
}
.scale(scale),
onClick = null,
radius = radius,

@ -2,8 +2,11 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -13,30 +16,33 @@ import androidx.compose.ui.text.style.TextAlign
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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AllInChip(
text: String,
modifier: Modifier = Modifier,
isSelected: Boolean = false,
enabled: Boolean = true,
onClick: () -> Unit = {},
radius: Dp = 50.dp,
selectedColor: Color = AllInTheme.colors.allInPurple,
unselectedColor: Color = AllInTheme.themeColors.background,
selectedColor: Color = AllInColorToken.allInPurple,
unselectedColor: Color = AllInTheme.colors.background,
) {
Card(
modifier = modifier,
shape = AbsoluteSmoothCornerShape(radius, 100),
onClick = onClick,
border = if (!isSelected) BorderStroke(1.dp, AllInTheme.themeColors.border) else null,
border = if (!isSelected) BorderStroke(1.dp, AllInTheme.colors.border) else null,
colors = CardDefaults.cardColors(
containerColor = if (isSelected) selectedColor else unselectedColor
)
containerColor = if (isSelected) selectedColor else unselectedColor,
disabledContainerColor = if (isSelected) selectedColor else unselectedColor
),
enabled = enabled
) {
Box {
Box{
Text(
text = text,
modifier = Modifier
@ -45,7 +51,7 @@ fun AllInChip(
.alpha(if (isSelected) 0f else 1f),
textAlign = TextAlign.Center,
style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onBackground2
color = AllInTheme.colors.onBackground2
)
if (isSelected) {
Text(
@ -53,8 +59,7 @@ fun AllInChip(
modifier = modifier.align(Alignment.Center),
textAlign = TextAlign.Center,
style = AllInTheme.typography.h1,
color = AllInTheme.colors.white
color = AllInColorToken.white
)
}
}

@ -7,6 +7,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -14,7 +15,7 @@ fun AllInCoinCount(
modifier: Modifier = Modifier,
amount: Int,
color: Color? = null,
brush: Brush?= null,
brush: Brush? = null,
size: Int = 15,
textStyle: TextStyle = AllInTheme.typography.h1,
position: IconPosition = IconPosition.TRAILING
@ -36,6 +37,6 @@ fun AllInCoinCount(
@Composable
private fun AllInCoinCountPreview() {
AllInTheme {
AllInCoinCount(amount = 542, color = AllInTheme.colors.allInPurple)
AllInCoinCount(amount = 542, color = AllInColorToken.allInPurple)
}
}

@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import java.time.ZonedDateTime
import java.util.*
@ -39,9 +40,9 @@ fun AllInDatePicker(
}
) {
Text(
text = stringResource(id = R.string.Validate),
text = stringResource(id = R.string.generic_validate),
style = AllInTheme.typography.h1.copy(
brush = AllInTheme.colors.allInMainGradient
brush = AllInColorToken.allInMainGradient
)
)
}
@ -49,37 +50,37 @@ fun AllInDatePicker(
dismissButton = {
TextButton(onClick = onDismiss) {
Text(
text = stringResource(id = R.string.Cancel),
color = AllInTheme.themeColors.onBackground2,
text = stringResource(id = R.string.generic_cancel),
color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.sm1
)
}
},
colors = DatePickerDefaults.colors(
containerColor = AllInTheme.themeColors.mainSurface
containerColor = AllInTheme.colors.mainSurface
)
) {
DatePicker(
state = datePickerState,
colors = DatePickerDefaults.colors(
todayDateBorderColor = AllInTheme.colors.allInBlue,
selectedDayContainerColor = AllInTheme.colors.allInBlue,
todayContentColor = AllInTheme.colors.allInBlue,
dayContentColor = AllInTheme.colors.allInBlue,
dividerColor = AllInTheme.themeColors.border,
containerColor = AllInTheme.themeColors.background,
titleContentColor = AllInTheme.themeColors.onMainSurface,
headlineContentColor = AllInTheme.themeColors.onMainSurface,
subheadContentColor = AllInTheme.themeColors.onBackground2,
todayDateBorderColor = AllInColorToken.allInBlue,
selectedDayContainerColor = AllInColorToken.allInBlue,
todayContentColor = AllInColorToken.allInBlue,
dayContentColor = AllInColorToken.allInBlue,
dividerColor = AllInTheme.colors.border,
containerColor = AllInTheme.colors.background,
titleContentColor = AllInTheme.colors.onMainSurface,
headlineContentColor = AllInTheme.colors.onMainSurface,
subheadContentColor = AllInTheme.colors.onBackground2,
dateTextFieldColors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = AllInTheme.themeColors.mainSurface,
unfocusedContainerColor = AllInTheme.themeColors.mainSurface,
focusedBorderColor = AllInTheme.colors.allInPurple,
unfocusedBorderColor = AllInTheme.themeColors.border,
focusedTextColor = AllInTheme.themeColors.onMainSurface,
unfocusedTextColor = AllInTheme.themeColors.onMainSurface,
focusedLabelColor = AllInTheme.colors.allInPurple,
unfocusedLabelColor = AllInTheme.themeColors.onMainSurface,
focusedContainerColor = AllInTheme.colors.mainSurface,
unfocusedContainerColor = AllInTheme.colors.mainSurface,
focusedBorderColor = AllInColorToken.allInPurple,
unfocusedBorderColor = AllInTheme.colors.border,
focusedTextColor = AllInTheme.colors.onMainSurface,
unfocusedTextColor = AllInTheme.colors.onMainSurface,
focusedLabelColor = AllInColorToken.allInPurple,
unfocusedLabelColor = AllInTheme.colors.onMainSurface,
)
)
)

@ -32,8 +32,8 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInDetailsDrawer(
text: String = stringResource(id = R.string.Details),
textColor: Color = AllInTheme.themeColors.onBackground2,
text: String = stringResource(id = R.string.bet_status_details_drawer),
textColor: Color = AllInTheme.colors.onBackground2,
content: @Composable ColumnScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }

@ -10,6 +10,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.ext.shadow
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -23,8 +24,8 @@ fun AllInGradientButton(
modifier = modifier
.shadow(
colors = listOf(
AllInTheme.colors.allInPink,
AllInTheme.colors.allInBlue
AllInColorToken.allInPink,
AllInColorToken.allInBlue
),
blurRadius = 20.dp,
alpha = .5f,
@ -32,13 +33,13 @@ fun AllInGradientButton(
)
.fillMaxWidth(),
radius = 10.dp,
backgroundBrush = AllInTheme.colors.allInMainGradient
backgroundBrush = AllInColorToken.allInMainGradient
) {
Text(
text = text,
textAlign = TextAlign.Center,
style = AllInTheme.typography.h2,
color = AllInTheme.colors.white,
color = AllInColorToken.white,
fontSize = 20.sp,
modifier = Modifier
.padding(vertical = 15.dp)

@ -18,10 +18,10 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AllInIconChip(
text: String,
@ -29,16 +29,16 @@ fun AllInIconChip(
isSelected: Boolean = false,
onClick: () -> Unit = {},
radius: Dp = 15.dp,
selectedColor: Color = AllInTheme.colors.allInPurple,
unselectedColor: Color = AllInTheme.themeColors.background,
selectedColor: Color = AllInColorToken.allInPurple,
unselectedColor: Color = AllInTheme.colors.background,
leadingIcon: ImageVector,
) {
val contentColor = if (isSelected) AllInTheme.colors.white else selectedColor
val contentColor = if (isSelected) AllInColorToken.white else selectedColor
Card(
modifier = modifier,
shape = AbsoluteSmoothCornerShape(radius, 100),
onClick = onClick,
border = if (!isSelected) BorderStroke(1.dp, AllInTheme.themeColors.border) else null,
border = if (!isSelected) BorderStroke(1.dp, AllInTheme.colors.border) else null,
colors = CardDefaults.cardColors(
containerColor = if (isSelected) selectedColor else unselectedColor
)

@ -34,13 +34,10 @@ import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import kotlin.math.PI
import kotlin.math.abs
@ -50,7 +47,7 @@ import kotlin.math.max
fun AllInLoading(
modifier: Modifier = Modifier,
visible: Boolean,
brush: Brush = AllInTheme.colors.allInMainGradient,
brush: Brush = AllInColorToken.allInMainGradient,
) {
val interactionSource = remember { MutableInteractionSource() }
AnimatedVisibility(
@ -58,34 +55,23 @@ fun AllInLoading(
enter = fadeIn(),
exit = fadeOut()
) {
Dialog(
onDismissRequest = {},
properties = DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false,
decorFitsSystemWindows = false,
usePlatformDefaultWidth = false
)
) {
(LocalView.current.parent as DialogWindowProvider).window.setDimAmount(0f)
Box(
modifier = modifier
.fillMaxSize()
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {}
)
.background(AllInTheme.themeColors.mainSurface.copy(alpha = .4f))
) {
AllInCircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(50.dp),
brush = brush,
strokeWidth = 7.dp
Box(
modifier = modifier
.fillMaxSize()
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {}
)
}
.background(AllInTheme.colors.mainSurface.copy(alpha = .4f))
) {
AllInCircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(50.dp),
brush = brush,
strokeWidth = 7.dp
)
}
}
}
@ -93,7 +79,7 @@ fun AllInLoading(
@Composable
fun AllInCircularProgressIndicator(
modifier: Modifier = Modifier,
brush: Brush = AllInTheme.colors.allInMainGradient,
brush: Brush = AllInColorToken.allInMainGradient,
strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
) {
@ -111,7 +97,7 @@ fun AllInCircularProgressIndicator(
durationMillis = RotationDuration * RotationsPerCycle,
easing = LinearEasing
)
)
), label = ""
)
val baseRotation = transition.animateFloat(
0f,

@ -19,18 +19,20 @@ 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.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInMarqueeBox(
backgroundColor: Color = AllInTheme.themeColors.mainSurface,
backgroundColor: Color = AllInTheme.colors.mainSurface,
backgroundBrush: Brush? = null,
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize().let { itModifier ->
.fillMaxSize()
.let { itModifier ->
backgroundBrush?.let {
itModifier.background(it)
} ?: itModifier.background(backgroundColor)
@ -45,7 +47,7 @@ fun AllInMarqueeBox(
.scale(1.2f)
.rotate(11f)
.basicMarquee(spacing = MarqueeSpacing(0.dp)),
tint = AllInTheme.colors.white.copy(alpha = .05f)
tint = AllInColorToken.white.copy(alpha = .05f)
)
content()
}
@ -57,7 +59,7 @@ fun AllInMarqueeBox(
private fun AllInMarqueeBoxPreview() {
AllInTheme {
AllInMarqueeBox(
backgroundBrush = AllInTheme.colors.allInMainGradient,
backgroundBrush = AllInColorToken.allInMainGradient,
) {
Text("CONTENT")
}

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -16,9 +17,9 @@ fun AllInRadioButton(
) {
AllInCard(
radius = 100.dp,
borderColor = AllInTheme.colors.allInMint,
borderColor = AllInColorToken.allInMint,
borderWidth = if (!checked) 1.dp else null,
backgroundColor = if (checked) AllInTheme.colors.allInPurple else unCheckedColor,
backgroundColor = if (checked) AllInColorToken.allInPurple else unCheckedColor,
modifier = modifier.size(12.dp)
) {}
}
@ -35,7 +36,7 @@ private fun AllInRadioButtonNotCheckedPreview() {
@Composable
private fun AllInRadioButtonNotCheckedFilledPreview() {
AllInTheme {
AllInRadioButton(checked = false, unCheckedColor = AllInTheme.colors.allInMint)
AllInRadioButton(checked = false, unCheckedColor = AllInColorToken.allInMint)
}
}

@ -23,6 +23,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -39,7 +40,7 @@ fun AllInRetractableCard(
AllInCard(
modifier = modifier.fillMaxWidth(),
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(.5f)
borderColor = AllInColorToken.allInPurple.copy(.5f)
) {
Column(
Modifier.animateContentSize()
@ -60,10 +61,10 @@ fun AllInRetractableCard(
query = boldText,
highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold,
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.onMainSurface,
fontStyle = AllInTheme.typography.h2.fontStyle
),
color = AllInTheme.themeColors.onBackground2,
color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.p1,
fontSize = 16.sp,
modifier = Modifier.weight(1f)
@ -73,13 +74,13 @@ fun AllInRetractableCard(
if (isOpen) ExpandLess else ExpandMore
},
contentDescription = null,
tint = AllInTheme.colors.allInPurple,
tint = AllInColorToken.allInPurple,
modifier = Modifier.size(30.dp)
)
}
AnimatedVisibility(isOpen) {
Column {
HorizontalDivider(color = AllInTheme.themeColors.border)
HorizontalDivider(color = AllInTheme.colors.border)
content()
}
}

@ -21,14 +21,14 @@ fun AllInSectionButton(
) {
val style = if (isSelected) {
AllInTheme.typography.sm1.copy(
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.onMainSurface,
fontSize = 15.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.ExtraBold
)
} else {
AllInTheme.typography.sm1.copy(
color = AllInTheme.themeColors.onBackground2,
color = AllInTheme.colors.onBackground2,
fontSize = 15.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold

@ -2,16 +2,24 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -20,27 +28,47 @@ import kotlinx.coroutines.launch
class SectionElement(
val text: String,
val content: @Composable ()->Unit
val content: @Composable () -> Unit
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AllInSections(
sections: List<SectionElement>,
interSectionsPadding: Dp = 56.dp,
modifier: Modifier = Modifier,
onLoadSection: ()->Unit = {}
interSectionsPadding: Dp = 56.dp,
onLoadSection: () -> Unit = { }
) {
val pagerState = rememberPagerState(pageCount = {
sections.size
})
val pagerState = rememberPagerState(pageCount = { sections.size })
val scope = rememberCoroutineScope()
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
){
LazyRow(
horizontalArrangement = Arrangement.spacedBy(interSectionsPadding)
Box(modifier = modifier) {
HorizontalPager(state = pagerState) { page ->
LaunchedEffect(key1 = page) { onLoadSection() }
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 40.dp)
) {
sections[page].content()
}
}
Box(
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxWidth()
.background(
Brush.verticalGradient(
0.75f to AllInTheme.colors.mainSurface,
1f to Color.Transparent
)
),
contentAlignment = Alignment.Center
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(interSectionsPadding),
modifier = Modifier.padding(vertical = 12.dp)
) {
itemsIndexed(sections) { index, section ->
AllInSectionButton(
@ -54,13 +82,6 @@ fun AllInSections(
)
}
}
HorizontalPager(state = pagerState) { page ->
LaunchedEffect(key1 = page){
onLoadSection()
}
sections[page].content()
}
}
}

@ -34,6 +34,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@ -66,13 +67,13 @@ private fun AllInSelectionLine(
Icon(
imageVector = it,
contentDescription = null,
tint = AllInTheme.colors.allInPurple,
tint = AllInColorToken.allInPurple,
modifier = Modifier.size(20.dp)
)
}
Text(
text = text,
color = AllInTheme.colors.allInPurple,
color = AllInColorToken.allInPurple,
style = AllInTheme.typography.h2,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
@ -81,7 +82,7 @@ private fun AllInSelectionLine(
Icon(
imageVector = trailingIcon,
contentDescription = null,
tint = AllInTheme.colors.allInPurple,
tint = AllInColorToken.allInPurple,
modifier = Modifier
.size(30.dp)
)
@ -105,7 +106,7 @@ fun AllInSelectionBox(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
borderColor = AllInColorToken.allInPurple.copy(alpha = .42f)
) {
Column(
Modifier.animateContentSize()
@ -125,7 +126,7 @@ fun AllInSelectionBox(
)
AnimatedVisibility(isOpen) {
Column {
HorizontalDivider(color = AllInTheme.themeColors.border)
HorizontalDivider(color = AllInTheme.colors.border)
elements.filter { it != selected }.forEach { element ->
AllInSelectionLine(
text = stringResource(id = element.textId),
@ -149,9 +150,9 @@ fun AllInSelectionBox(
private fun AllInSelectionBoxClosedPreview() {
AllInTheme {
val elements = listOf(
SelectionElement(R.string.yes_no, Icons.AutoMirrored.Default.HelpOutline),
SelectionElement(R.string.sport_match, Icons.Default.SportsFootball),
SelectionElement(R.string.custom_answers, Icons.Default.PinEnd)
SelectionElement(R.string.bet_type_binary, Icons.AutoMirrored.Default.HelpOutline),
SelectionElement(R.string.bet_type_match, Icons.Default.SportsFootball),
SelectionElement(R.string.bet_type_custom, Icons.Default.PinEnd)
)
AllInSelectionBox(
isOpen = false,
@ -188,7 +189,7 @@ private fun AllInSelectionLine(
Icon(
imageVector = trailingIcon,
contentDescription = null,
tint = AllInTheme.colors.allInPurple,
tint = AllInColorToken.allInPurple,
modifier = Modifier
.size(30.dp)
)
@ -211,7 +212,7 @@ fun AllInSelectionBox(
modifier = modifier.fillMaxWidth(),
radius = 10.dp,
borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f)
borderColor = AllInColorToken.allInPurple.copy(alpha = .42f)
) {
Column(Modifier.animateContentSize()) {
AllInSelectionLine(
@ -224,7 +225,7 @@ fun AllInSelectionBox(
)
AnimatedVisibility(isOpen) {
Column {
HorizontalDivider(color = AllInTheme.themeColors.border)
HorizontalDivider(color = AllInTheme.colors.border)
elements.filter { it != selected }.forEach { element ->
AllInSelectionLine(
element = element,
@ -247,9 +248,9 @@ fun AllInSelectionBox(
private fun AllInSelectionBoxOpenPreview() {
AllInTheme {
val elements = listOf(
SelectionElement(R.string.yes_no, Icons.AutoMirrored.Default.HelpOutline),
SelectionElement(R.string.sport_match, Icons.Default.SportsFootball),
SelectionElement(R.string.custom_answers, Icons.Default.Edit)
SelectionElement(R.string.bet_type_binary, Icons.AutoMirrored.Default.HelpOutline),
SelectionElement(R.string.bet_type_match, Icons.Default.SportsFootball),
SelectionElement(R.string.bet_type_custom, Icons.Default.Edit)
)
AllInSelectionBox(
isOpen = true,

@ -1,19 +1,26 @@
package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.input.*
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
@ -24,20 +31,25 @@ 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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
import java.util.Locale
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun AllInTextField(
value: String,
modifier: Modifier = Modifier,
textStyle: TextStyle = AllInTheme.typography.p1,
placeholder: String? = null,
value: String,
maxChar: Int? = null,
enabled: Boolean = true,
leadingIcon: Painter? = null,
leadingIconColor: Color? = null,
leadingContent: @Composable (() -> Unit)? = null,
trailingIcon: Painter? = null,
trailingContent: @Composable() (() -> Unit)? = null,
trailingIconColor: Color? = null,
trailingContent: @Composable (() -> Unit)? = null,
placeholderFontSize: TextUnit = 18.sp,
multiLine: Boolean = false,
errorText: String? = null,
@ -45,10 +57,12 @@ fun AllInTextField(
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
borderColor: Color = AllInTheme.themeColors.onBackground2,
containerColor: Color = AllInTheme.themeColors.background,
textColor: Color = AllInTheme.themeColors.onMainSurface,
placeholderColor: Color = AllInTheme.themeColors.onBackground2,
borderColor: Color = AllInTheme.colors.onBackground2,
disabledBorderColor: Color = AllInTheme.colors.disabledBorder,
containerColor: Color = AllInTheme.colors.background,
disabledContainerColor: Color = AllInTheme.colors.disabled,
textColor: Color = AllInTheme.colors.onMainSurface,
placeholderColor: Color = AllInTheme.colors.onBackground2,
onValueChange: (String) -> Unit,
) {
OutlinedTextField(
@ -82,11 +96,20 @@ fun AllInTextField(
Icon(
painter = it,
contentDescription = null,
tint = AllInTheme.colors.allInLightGrey300
tint = trailingIconColor ?: AllInColorToken.allInLightGrey300
)
}
},
leadingIcon = leadingContent ?: leadingIcon?.let {
@Composable {
Icon(
painter = it,
contentDescription = null,
tint = leadingIconColor ?: AllInColorToken.allInLightGrey300
)
}
},
textStyle = AllInTheme.typography.p1,
textStyle = textStyle,
enabled = enabled,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = imeAction),
keyboardActions = keyboardActions,
@ -95,17 +118,18 @@ fun AllInTextField(
cursorColor = textColor,
focusedBorderColor = borderColor,
unfocusedBorderColor = borderColor,
disabledBorderColor = disabledBorderColor,
focusedTextColor = textColor,
unfocusedTextColor = textColor,
errorTextColor = textColor,
focusedContainerColor = containerColor,
unfocusedContainerColor = containerColor,
errorContainerColor = containerColor,
disabledContainerColor = disabledContainerColor,
)
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AllInPasswordField(
placeholder: String,
@ -130,7 +154,7 @@ fun AllInPasswordField(
Icon(
imageVector = if (hidden) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = null,
tint = AllInTheme.colors.allInLightGrey300
tint = AllInColorToken.allInLightGrey300
)
}
},
@ -145,8 +169,17 @@ fun AllInPasswordField(
@Composable
fun AllInFloatTextfield(
modifier: Modifier = Modifier,
value: Float?,
modifier: Modifier = Modifier,
textStyle: TextStyle = AllInTheme.typography.p1,
placeholder: String? = null,
trailingIcon: Painter? = null,
trailingIconColor: Color? = null,
borderColor: Color = AllInTheme.colors.onBackground2,
containerColor: Color = AllInTheme.colors.background,
textColor: Color = AllInTheme.colors.onMainSurface,
placeholderColor: Color = AllInTheme.colors.onBackground2,
maxChar: Int? = 5,
setValue: (Float?) -> Unit
) {
val configuration = LocalConfiguration.current
@ -158,8 +191,16 @@ fun AllInFloatTextfield(
AllInTextField(
value = stringValue,
modifier = modifier,
maxChar = 5,
keyboardType = KeyboardType.Number
placeholder = placeholder,
maxChar = maxChar,
textStyle = textStyle,
trailingIcon = trailingIcon,
trailingIconColor = trailingIconColor,
keyboardType = KeyboardType.Number,
borderColor = borderColor,
containerColor = containerColor,
textColor = textColor,
placeholderColor = placeholderColor
) {
it.verifyIsFloat(locale)?.let {
stringValue = it
@ -176,11 +217,17 @@ fun AllInFloatTextfield(
@Composable
fun AllInIntTextField(
value: Int?,
modifier: Modifier = Modifier,
textStyle: TextStyle = AllInTheme.typography.p1,
placeholder: String? = null,
maxChar: Int? = 3,
value: Int?,
trailingIcon: Painter? = null,
trailingIconColor: Color? = null,
maxChar: Int? = 3,
borderColor: Color = AllInTheme.colors.onBackground2,
containerColor: Color = AllInTheme.colors.background,
textColor: Color = AllInTheme.colors.onMainSurface,
placeholderColor: Color = AllInTheme.colors.onBackground2,
setValue: (Int?) -> Unit
) {
AllInTextField(
@ -189,7 +236,13 @@ fun AllInIntTextField(
modifier = modifier,
maxChar = maxChar,
trailingIcon = trailingIcon,
keyboardType = KeyboardType.NumberPassword
trailingIconColor = trailingIconColor,
textStyle = textStyle,
keyboardType = KeyboardType.NumberPassword,
borderColor = borderColor,
containerColor = containerColor,
textColor = textColor,
placeholderColor = placeholderColor
) {
if (it.isEmpty()) setValue(null)
else if (it.isDigitsOnly()) {
@ -201,7 +254,6 @@ fun AllInIntTextField(
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
private fun AllInTextFieldPlaceholderPreview() {
@ -214,8 +266,19 @@ private fun AllInTextFieldPlaceholderPreview() {
}
}
@Preview
@Composable
private fun AllInTextFieldDisabledPreview() {
AllInTheme {
AllInTextField(
placeholder = "Email",
value = "",
onValueChange = { },
enabled = false
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@ -229,8 +292,8 @@ private fun AllInTextFieldValuePreview() {
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AllInTextFieldErrorPreview() {
AllInTheme {
@ -243,8 +306,8 @@ private fun AllInTextFieldErrorPreview() {
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AllInTextFieldPasswordPreview() {
AllInTheme {
@ -258,6 +321,7 @@ private fun AllInTextFieldPasswordPreview() {
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AllInTextFieldMultilinePreview() {
AllInTheme {

@ -17,10 +17,12 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@ -56,7 +58,10 @@ fun AllInTextIcon(
text = text,
color = color,
style = brush?.let { textStyle.copy(brush = it) } ?: textStyle,
fontSize = size.sp
fontSize = size.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false)
)
Icon(
painter = icon,
@ -77,7 +82,7 @@ private fun AllInTextIconPreview() {
AllInTextIcon(
text = "value",
icon = rememberVectorPainter(image = Icons.Default.Fireplace),
color = AllInTheme.colors.allInBlue
color = AllInColorToken.allInBlue
)
}
}
@ -89,8 +94,8 @@ private fun AllInTextIconReversePreview() {
AllInTextIcon(
text = "value",
icon = AllInTheme.icons.allCoins(),
color = AllInTheme.colors.allInBlue,
brush = AllInTheme.colors.allInMainGradient,
color = AllInColorToken.allInBlue,
brush = AllInColorToken.allInMainGradient,
position = IconPosition.LEADING
)
}

@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import java.util.*
@ -34,9 +35,9 @@ fun AllInTimePicker(
}
) {
Text(
text = stringResource(id = R.string.Validate),
text = stringResource(id = R.string.generic_validate),
style = AllInTheme.typography.h1.copy(
brush = AllInTheme.colors.allInMainGradient
brush = AllInColorToken.allInMainGradient
)
)
}
@ -44,8 +45,8 @@ fun AllInTimePicker(
dismissButton = {
TextButton(onClick = onDismiss) {
Text(
text = stringResource(id = R.string.Cancel),
color = AllInTheme.themeColors.onBackground2,
text = stringResource(id = R.string.generic_cancel),
color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.sm1
)
}
@ -54,16 +55,16 @@ fun AllInTimePicker(
TimePicker(
state = timePickerState,
colors = TimePickerDefaults.colors(
selectorColor = AllInTheme.colors.allInPurple,
containerColor = AllInTheme.themeColors.background,
clockDialColor = AllInTheme.themeColors.background2,
clockDialUnselectedContentColor = AllInTheme.themeColors.onMainSurface,
clockDialSelectedContentColor = AllInTheme.themeColors.background2,
selectorColor = AllInColorToken.allInPurple,
containerColor = AllInTheme.colors.background,
clockDialColor = AllInTheme.colors.background2,
clockDialUnselectedContentColor = AllInTheme.colors.onMainSurface,
clockDialSelectedContentColor = AllInTheme.colors.background2,
)
)
},
containerColor = AllInTheme.themeColors.mainSurface
containerColor = AllInTheme.colors.mainSurface
)
}

@ -66,14 +66,14 @@ fun AllInTitleInfo(
style = AllInTheme.typography.h2,
fontWeight = FontWeight.Bold,
fontSize = 15.sp,
color = AllInTheme.themeColors.onMainSurface
color = AllInTheme.colors.onMainSurface
)
Spacer(modifier = Modifier.width(5.dp))
AllInTooltip(text = tooltipText, state = tooltipState) {
Icon(
imageVector = icon,
contentDescription = null,
tint = AllInTheme.themeColors.onMainSurface,
tint = AllInTheme.colors.onMainSurface,
modifier = Modifier
.size(15.dp)
.alpha(.8f)

@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@ -30,7 +31,7 @@ fun AllInTooltip(
AllInPlainTooltip {
Text(
text = text,
color = AllInTheme.colors.allInLightGrey200,
color = AllInColorToken.allInLightGrey200,
style = AllInTheme.typography.p1,
fontSize = 12.sp
)
@ -60,9 +61,9 @@ private val AllInPlainTooltipContentPadding =
@Composable
fun AllInPlainTooltip(
modifier: Modifier = Modifier,
containerColor: Color = AllInTheme.colors.allInDark,
containerColor: Color = AllInColorToken.allInDark,
borderWidth: Dp = 1.dp,
borderColor: Color = AllInTheme.colors.allInDarkGrey100,
borderColor: Color = AllInColorToken.allInDarkGrey100,
shape: Shape = AbsoluteSmoothCornerShape(10.dp, 100),
content: @Composable () -> Unit,
) {
@ -120,7 +121,7 @@ private fun AllInTooltipPreview() {
AllInPlainTooltip(content = {
Text(
text = "Généralement une question qui sera répondu par les utilisateurs.",
color = AllInTheme.colors.allInLightGrey200,
color = AllInColorToken.allInLightGrey200,
style = AllInTheme.typography.p1,
fontSize = 10.sp
)

@ -22,10 +22,10 @@ fun HighlightedText(
text: String,
query: String,
highlightStyle: SpanStyle,
modifier: Modifier = Modifier,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
textAlign: TextAlign? = null,
style: TextStyle = LocalTextStyle.current

@ -1,61 +1,87 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import coil.compose.AsyncImage
import coil.request.ImageRequest
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun ProfilePicture(
fallback: String,
image: String?,
modifier: Modifier = Modifier,
image: Painter? = null,
borderWidth: Dp? = null,
size: Dp = 80.dp,
size: Dp = 80.dp
) {
val shape = RoundedCornerShape(100)
var hasImageloaded by remember { mutableStateOf(false) }
Card(
modifier = modifier.size(size),
shape = shape,
colors = CardDefaults.cardColors(containerColor = AllInTheme.colors.allInDarkGrey100),
border = borderWidth?.let { BorderStroke(it, AllInTheme.colors.allInDarkGrey50) }
colors = CardDefaults.cardColors(containerColor = AllInColorToken.allInDarkGrey100),
border = borderWidth?.let { BorderStroke(it, AllInColorToken.allInDarkGrey50) }
) {
Box(Modifier.fillMaxSize()) {
image?.let {
Image(
painter = it,
contentDescription = null,
AsyncImage(
modifier = Modifier
.align(Alignment.Center)
.fillMaxSize()
.clip(shape)
.width(300.dp)
.height(174.dp)
.clip(shape),
model = ImageRequest.Builder(LocalContext.current)
.data(image)
.crossfade(true)
.build(),
contentDescription = null,
onSuccess = { hasImageloaded = true },
contentScale = ContentScale.Crop
)
if (!hasImageloaded) {
Text(
text = fallback,
style = AllInTheme.typography.p2,
textAlign = TextAlign.Center,
fontSize = with(LocalDensity.current) { (size / 2).toSp() },
color = AllInColorToken.white,
modifier = Modifier.align(Alignment.Center)
)
}
} ?: run {
Icon(
imageVector = Icons.Default.PhotoCamera,
tint = AllInTheme.colors.white,
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.fillMaxSize(0.5f)
.clip(shape)
Text(
text = fallback,
style = AllInTheme.typography.p2,
textAlign = TextAlign.Center,
fontSize = with(LocalDensity.current) { (size / 2).toSp() },
color = AllInColorToken.white,
modifier = Modifier.align(Alignment.Center)
)
}
}
@ -67,16 +93,6 @@ fun ProfilePicture(
@Composable
private fun ProfilePictureDefaultPreview() {
AllInTheme {
ProfilePicture()
}
}
@Preview
@Composable
private fun ProfilePicturePreview() {
AllInTheme {
ProfilePicture(
image = painterResource(id = R.drawable.money_with_wings)
)
ProfilePicture(image = null, fallback = "LS")
}
}

@ -11,6 +11,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -19,7 +20,7 @@ fun RainbowButton(
text: String,
onClick: () -> Unit,
enabled: Boolean = true,
rippleColor: Color = AllInTheme.colors.allInBlue,
rippleColor: Color = AllInColorToken.allInBlue,
) {
AllInRipple(rippleColor) {
AllInCard(
@ -32,11 +33,11 @@ fun RainbowButton(
with(AllInTheme.typography.h2) {
if (enabled) {
copy(
brush = AllInTheme.colors.allInTextGradient
brush = AllInColorToken.allInTextGradient
)
} else {
copy(
color = AllInTheme.themeColors.disabledBorder
color = AllInTheme.colors.disabledBorder
)
}
}

@ -4,78 +4,81 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
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.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun StatBar(
percentage: Float,
modifier: Modifier = Modifier,
leadingBrush: Brush = AllInColorToken.allInBar1stGradient,
trailingBrush: Brush = AllInColorToken.allInBar2ndGradient,
icon: (@Composable () -> Unit)? = null,
) {
val radius100percent = if (percentage == 1f) 50 else 0
val radius0percent = if (percentage == 0f) 50 else 0
Box {
Box(modifier = modifier) {
Row(
Modifier.align(Alignment.Center)
modifier = Modifier.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.height(20.dp)
.fillMaxWidth(percentage)
.padding(end = if (percentage == 1f) 25.dp else 0.dp)
.clip(
AbsoluteRoundedCornerShape(
topLeftPercent = 50,
bottomLeftPercent = 50,
topRightPercent = radius100percent,
bottomRightPercent = radius100percent
topRightPercent = 0,
bottomRightPercent = 0
)
)
.background(AllInTheme.colors.allInBar1stGradient)
.background(leadingBrush)
)
if (percentage != 0f && percentage != 1f) {
Spacer(modifier = Modifier.width(15.dp))
}
Box(
modifier = Modifier
.height(20.dp)
.fillMaxWidth()
.padding(start = if (percentage == 0f) 25.dp else 15.dp)
.clip(
AbsoluteRoundedCornerShape(
topLeftPercent = radius0percent,
bottomLeftPercent = radius0percent,
topLeftPercent = 0,
bottomLeftPercent = 0,
topRightPercent = 50,
bottomRightPercent = 50
)
)
.background(AllInTheme.colors.allInBar2ndGradient)
.background(trailingBrush)
)
}
PercentagePositionnedElement(percentage = percentage) {
when (percentage) {
icon?.invoke() ?: when (percentage) {
0f -> Icon(
painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allInBarPink,
tint = AllInColorToken.allInPink,
contentDescription = null,
modifier = Modifier.size(32.dp)
)
1f -> Icon(
painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allInBarPurple,
tint = AllInColorToken.allInPurple,
contentDescription = null,
modifier = Modifier.size(32.dp)
)

@ -0,0 +1,81 @@
package fr.iut.alldev.allin.ui.core.bet
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
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
@Composable
fun AllInEmptyView(
image: Painter,
text: String,
subtext: String?,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.alpha(.35f)
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
painter = image,
contentDescription = null,
modifier = Modifier
.fillMaxWidth(.5f)
.aspectRatio(1f)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = text,
textAlign = TextAlign.Center,
style = AllInTheme.typography.h2,
color = AllInTheme.colors.onMainSurface,
fontSize = 16.sp
)
subtext?.let {
Text(
text = subtext,
textAlign = TextAlign.Center,
style = AllInTheme.typography.p1,
color = AllInTheme.colors.onBackground2,
fontSize = 14.sp
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun AllInEmptyViewPreview() {
AllInTheme {
AllInEmptyView(
text = "C'est un peu vide par ici",
subtext = "Ajoutez des amis pour les afficher dans le classement, et voir qui est le meilleur !",
image = painterResource(id = R.drawable.eyes),
modifier = Modifier.fillMaxSize()
)
}
}

@ -16,6 +16,7 @@ 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.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.AllInCard
@Composable
@ -51,7 +52,48 @@ fun BetCard(
}
HorizontalDivider(
thickness = 1.dp,
color = AllInTheme.themeColors.border
color = AllInTheme.colors.border
)
content()
}
}
@Composable
fun BetCard(
modifier: Modifier = Modifier,
title: String,
creator: String,
category: String,
date: String,
time: String,
status: BetStatus,
onClick: () -> Unit,
content: @Composable () -> Unit
) {
AllInBouncyCard(
modifier = modifier.fillMaxWidth(),
radius = 16.dp,
onClick = onClick
) {
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.colors.border
)
content()
}

@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
@ -23,15 +24,15 @@ fun BetDateTimeChip(
Card(
modifier = modifier.wrapContentSize(),
shape = RoundedCornerShape(50),
border = BorderStroke(1.dp, AllInTheme.themeColors.border),
colors = CardDefaults.cardColors(containerColor = AllInTheme.themeColors.background)
border = BorderStroke(1.dp, AllInTheme.colors.border),
colors = CardDefaults.cardColors(containerColor = AllInTheme.colors.background)
) {
Text(
text = text,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
style = AllInTheme.typography.sm1,
textAlign = TextAlign.Center,
color = AllInTheme.colors.allInPurple
color = AllInColorToken.allInPurple
)
}
}

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

@ -1,37 +1,33 @@
package fr.iut.alldev.allin.ui.core.bet
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width
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.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ui.core.ProfilePicture
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable
fun BetProfilePictureRow(
pictures: List<Painter?>,
maxLength: Int = 5,
modifier: Modifier = Modifier
pictures: List<Pair<String, String?>>,
modifier: Modifier = Modifier,
maxLength: Int = 5
) {
val nRepeat = remember{
if (pictures.size > maxLength) maxLength else pictures.size
}
val nRepeat = remember{ pictures.size.coerceAtMost(maxLength) }
Box(
modifier.width((nRepeat*17).dp)
){
pictures.forEachIndexed { index, painter ->
Box(modifier.width((35 + (nRepeat - 1) * 17).dp)){
pictures.take(nRepeat).forEachIndexed { index, (username, image) ->
ProfilePicture(
image = painter,
fallback = username.asFallbackProfileUsername(),
image = image,
size = 35.dp,
modifier = Modifier
.align(Alignment.CenterEnd)
@ -46,24 +42,33 @@ fun BetProfilePictureRow(
@Composable
private fun BetProfilePictureRowPreview() {
AllInTheme {
BetProfilePictureRow(pictures = listOf(
painterResource(id = R.drawable.money_with_wings),
null,
painterResource(id = R.drawable.money_with_wings)
))
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
BetProfilePictureRow(
pictures = listOf("lucas" to null)
)
}
}
}
@Preview
@Composable
private fun BetProfilePictureRowMaxPreview() {
AllInTheme {
BetProfilePictureRow(pictures = listOf(
painterResource(id = R.drawable.money_with_wings),
null,
painterResource(id = R.drawable.money_with_wings),
null,
painterResource(id = R.drawable.money_with_wings),
null
))
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
BetProfilePictureRow(
pictures = listOf(
"lucas" to null,
"lucas" to null,
"lucas" to null,
"lucas" to null,
"lucas" to null
)
)
}
}
}

@ -31,28 +31,28 @@ fun BetTitleHeader(
Modifier.align(Alignment.End)
) {
HighlightedText(
text = stringResource(id = R.string.Proposed_by_x, creator),
text = stringResource(id = R.string.bet_Proposed_by_format, creator),
query = creator,
highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold,
color = AllInTheme.themeColors.onMainSurface
color = AllInTheme.colors.onMainSurface
),
fontSize = 12.sp,
style = AllInTheme.typography.p2,
color = AllInTheme.themeColors.onBackground2
color = AllInTheme.colors.onBackground2
)
}
Spacer(modifier = Modifier.height(11.dp))
Text(
text = category,
fontSize = 15.sp,
color = AllInTheme.themeColors.onBackground2,
color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.sm2
)
Text(
text = title,
fontSize = 20.sp,
color = AllInTheme.themeColors.onMainSurface,
color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.h1
)
}

@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
@ -16,9 +17,9 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissValue
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.material3.rememberSwipeToDismissState
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@ -30,6 +31,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@OptIn(ExperimentalMaterial3Api::class)
@ -40,9 +42,9 @@ fun AllInSnackbar(
SnackbarHost(
hostState = snackbarState
) { snackbarData ->
val dismissState = rememberSwipeToDismissState(
val dismissState = rememberSwipeToDismissBoxState(
confirmValueChange = { value ->
if (value != SwipeToDismissValue.Settled) {
if (value != SwipeToDismissBoxValue.Settled) {
snackbarState.currentSnackbarData?.dismiss()
true
} else {
@ -53,7 +55,9 @@ fun AllInSnackbar(
SwipeToDismissBox(
state = dismissState,
backgroundContent = {},
modifier = Modifier.padding(8.dp)
modifier = Modifier
.padding(vertical = 8.dp)
.navigationBarsPadding()
) {
val snackbarType = remember {
if (snackbarData.visuals is AllInSnackbarVisualsImpl) {
@ -65,7 +69,7 @@ fun AllInSnackbar(
AllInSnackbarContent(
backgroundColor = snackbarType.getBackgroundColor(),
contentColor = AllInTheme.colors.white,
contentColor = AllInColorToken.white,
text = snackbarData.visuals.message,
icon = snackbarType.getIcon(),
dismiss = { snackbarState.currentSnackbarData?.dismiss() }
@ -86,6 +90,7 @@ fun AllInSnackbarContent(
Surface(
shape = RoundedCornerShape(16.dp),
shadowElevation = 4.dp,
modifier = Modifier.padding(horizontal = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@ -138,7 +143,7 @@ private fun AllInSnackbarContentPreview(
AllInTheme {
AllInSnackbarContent(
backgroundColor = snackbarType.getBackgroundColor(),
contentColor = AllInTheme.colors.white,
contentColor = AllInColorToken.white,
text = "Lorem Ipsum",
icon = snackbarType.getIcon(),
dismiss = {}

@ -7,7 +7,7 @@ import androidx.compose.material.icons.filled.Info
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType.ERROR
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType.STANDARD
import fr.iut.alldev.allin.ui.core.snackbar.SnackbarType.SUCCESS
@ -21,9 +21,9 @@ enum class SnackbarType {
@Composable
fun SnackbarType.getBackgroundColor(): Color =
when (this) {
STANDARD -> AllInTheme.colors.allInDark
SUCCESS -> AllInTheme.colors.allInPurple
ERROR -> AllInTheme.colors.allInBetWaiting
STANDARD -> AllInColorToken.allInDark
SUCCESS -> AllInColorToken.allInPurple
ERROR -> AllInColorToken.allInBetWaiting
}
@Composable

@ -0,0 +1,76 @@
package fr.iut.alldev.allin.ui.core.topbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.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.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInTopBar(
onMenuClicked: () -> Unit,
coinAmount: Int,
) {
TopAppBar(
modifier = Modifier.background(AllInColorToken.allInMainGradient),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent
),
title = { },
navigationIcon = {
IconButton(
onClick = onMenuClicked,
modifier = Modifier
) {
Icon(
painterResource(id = R.drawable.allin_menu),
modifier = Modifier.size(30.dp),
contentDescription = null,
tint = Color.White
)
}
},
actions = {
Box(
modifier = Modifier.fillMaxWidth()
) {
Icon(
painter = AllInTheme.icons.logo(),
contentDescription = null,
tint = AllInColorToken.white,
modifier = Modifier
.size(40.dp)
.align(Alignment.Center)
)
AllInTopBarCoinCounter(
amount = coinAmount,
modifier = Modifier
.align(Alignment.CenterEnd)
.offset(x = 4.dp)
)
}
}
)
}
@Preview
@Composable
private fun AllInTopBarPreview() {
AllInTheme {
AllInTopBar(onMenuClicked = { }, coinAmount = 541)
}
}

@ -0,0 +1,110 @@
package fr.iut.alldev.allin.ui.core.topbar
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInTopBarCoinCounter(
amount: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = AllInColorToken.white,
textColor: Color = AllInColorToken.allInDark,
iconColor: Color = AllInColorToken.allInBlue,
) {
var oldAmount by remember { mutableIntStateOf(amount) }
LaunchedEffect(amount) {
oldAmount = amount
}
val countString = remember(amount) { amount.toString() }
val oldCountString = remember(oldAmount) { oldAmount.toString() }
Card(
modifier = modifier.wrapContentSize(),
shape = RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50)
) {
Row(
modifier = Modifier
.background(backgroundColor)
.padding(horizontal = 13.dp),
horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row {
for (i in countString.indices) {
val oldChar = oldCountString.getOrNull(i)
val newChar = countString[i]
val char = if (oldChar == newChar) {
oldCountString[i]
} else {
countString[i]
}
AnimatedContent(
targetState = char,
transitionSpec = {
val delayMillis = (countString.indices.count() - i) * 50
if (oldAmount <= amount) {
(slideInVertically(tween(delayMillis)) { it } togetherWith
slideOutVertically(tween(delayMillis)) { -it })
} else {
(slideInVertically(tween(delayMillis)) { -it } togetherWith
slideOutVertically(tween(delayMillis)) { it })
}
},
label = ""
) { char ->
Text(
text = char.toString(),
style = AllInTheme.typography.h1,
color = textColor,
fontSize = 20.sp,
softWrap = false,
modifier = Modifier.padding(vertical = 5.dp)
)
}
}
}
Icon(
painter = AllInTheme.icons.allCoins(),
tint = iconColor,
contentDescription = null,
modifier = Modifier.padding(vertical = 5.dp)
)
}
}
}
@Preview
@Composable
private fun AllInTopBarCoinCounterPreview() {
AllInTheme {
AllInTopBarCoinCounter(547)
}
}

@ -1,61 +0,0 @@
package fr.iut.alldev.allin.ui.core.topbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun AllInTopBarCoinCounter(
amount: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = AllInTheme.colors.white,
textColor: Color = AllInTheme.colors.allInDark,
iconColor: Color = AllInTheme.colors.allInBlue,
) {
Card(
modifier = modifier.wrapContentSize(),
shape = RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50)
) {
Row(
modifier = Modifier
.background(backgroundColor)
.padding(horizontal = 13.dp, vertical = 5.dp),
horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = amount.toString(),
color = textColor,
style = AllInTheme.typography.h1,
fontSize = 20.sp
)
Icon(
painter = AllInTheme.icons.allCoins(),
tint = iconColor,
contentDescription = null,
)
}
}
}
@Preview
@Composable
private fun AllInTopBarCoinCounterPreview() {
AllInTheme {
AllInTopBarCoinCounter(547)
}
}

@ -1,67 +0,0 @@
package fr.iut.alldev.allin.ui.core.topbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.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.theme.AllInTheme
@Composable
fun AllInTopBar(
onMenuClicked: () -> Unit,
coinAmount: Int,
) {
Box(
modifier = Modifier
.height(86.dp)
.fillMaxWidth()
.background(brush = AllInTheme.colors.allInMainGradient)
) {
IconButton(
onClick = onMenuClicked,
modifier = Modifier
.padding(start = 19.dp)
.align(Alignment.CenterStart)
) {
Icon(
painterResource(id = R.drawable.allin_menu),
modifier = Modifier.size(30.dp),
contentDescription = null,
tint = Color.White
)
}
Icon(
painter = painterResource(R.drawable.allin),
contentDescription = null,
tint = AllInTheme.colors.white,
modifier = Modifier
.size(40.dp)
.align(Alignment.Center)
)
AllInTopBarCoinCounter(
amount = coinAmount,
modifier = Modifier
.align(Alignment.CenterEnd)
)
}
}
@Preview
@Composable
private fun AllInTopBarPreview() {
AllInTheme {
AllInTopBar(onMenuClicked = { }, coinAmount = 541)
}
}

@ -0,0 +1,219 @@
package fr.iut.alldev.allin.ui.dailyReward
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.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
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.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
@Composable
fun DailyRewardScreen(
amount: Int,
onDismiss: () -> Unit
) {
var hasOpened by remember { mutableStateOf(false) }
DailyRewardScreenContent(
amount = amount,
hasOpened = hasOpened
) {
if (hasOpened) {
onDismiss()
} else {
hasOpened = true
}
}
}
@Composable
fun DailyRewardScreenContent(
amount: Int,
hasOpened: Boolean,
onClick: () -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val infiniteTransition = rememberInfiniteTransition(label = "")
val rotation by infiniteTransition.animateFloat(
initialValue = 3f,
targetValue = -3f,
animationSpec = infiniteRepeatable(
animation = tween(900),
repeatMode = RepeatMode.Reverse
), label = ""
)
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = .95f,
animationSpec = infiniteRepeatable(
animation = tween(900),
repeatMode = RepeatMode.Reverse
), label = ""
)
Column(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
0f to AllInColorToken.black.copy(alpha = .71f),
1f to AllInColorToken.black.copy(alpha = .97f)
)
)
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onClick
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.daily_reward_title),
style = AllInTheme.typography.h1,
fontSize = 20.sp,
color = AllInColorToken.white,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 48.dp)
)
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.padding(horizontal = 48.dp),
contentAlignment = Alignment.Center
) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
)
androidx.compose.animation.AnimatedVisibility(
visible = !hasOpened,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut(targetScale = 2f)
) {
Image(
painter = painterResource(id = R.drawable.daily_reward_1),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.rotate(rotation)
.scale(scale)
)
}
androidx.compose.animation.AnimatedVisibility(
visible = hasOpened,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut(targetScale = 2f)
) {
Box(
contentAlignment = Alignment.BottomCenter
) {
Image(
painter = painterResource(id = R.drawable.daily_reward_2),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.rotate(rotation)
.scale(scale)
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.rotate(-rotation)
.scale(scale)
) {
Text(
text = "+$amount",
style = AllInTheme.typography.h1,
fontSize = 70.sp,
color = AllInColorToken.white
)
Icon(
painter = AllInTheme.icons.allCoins(),
tint = AllInColorToken.white,
contentDescription = null,
modifier = Modifier.size(48.dp)
)
}
}
}
}
Text(
text = stringResource(id = R.string.daily_reward_subtitle),
style = AllInTheme.typography.l1,
color = AllInColorToken.white,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 48.dp)
)
}
}
@Preview(showBackground = true)
@Composable
private fun DailyRewardScreenPreview() {
AllInTheme {
DailyRewardScreenContent(
amount = 125,
hasOpened = false,
onClick = {}
)
}
}
@Preview(showBackground = true)
@Composable
private fun DailyRewardScreenStep2Preview() {
AllInTheme {
DailyRewardScreenContent(
amount = 125,
hasOpened = true,
onClick = {}
)
}
}

@ -0,0 +1,42 @@
package fr.iut.alldev.allin.ui.friends
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalFocusManager
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.data.model.FriendStatus
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent
@Composable
fun FriendsScreen(
viewModel: FriendsScreenViewModel = hiltViewModel()
) {
val search by viewModel.search.collectAsStateWithLifecycle()
val addTabState by viewModel.addTabState.collectAsStateWithLifecycle()
val requestsTabState by viewModel.requestsTabState.collectAsStateWithLifecycle()
val refreshing by viewModel.refreshing.collectAsStateWithLifecycle()
val focus = LocalFocusManager.current
FriendsScreenContent(
addTabState = addTabState,
search = search,
setSearch = { viewModel.setSearch(it) },
onToggleDeleteFriend = {
if (it.friendStatus == FriendStatus.NOT_FRIEND) {
viewModel.addFriend(it.username)
} else {
viewModel.removeFriend(it.username)
}
},
requestsTabState = requestsTabState,
acceptRequest = { viewModel.addFriend(it.username) },
declineRequest = { viewModel.removeFriend(it.username) },
refresh = {
focus.clearFocus()
viewModel.refreshAll()
},
isRefreshing = refreshing
)
}

@ -0,0 +1,189 @@
package fr.iut.alldev.allin.ui.friends
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.FriendStatus
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel
class FriendsScreenViewModel @Inject constructor(
private val friendRepository: FriendRepository,
private val keystoreManager: AllInKeystoreManager
) : ViewModel() {
private val _search by lazy { MutableStateFlow("") }
val search get() = _search.asStateFlow()
private val _addTabState by lazy { MutableStateFlow<AddTabState>(AddTabState.Loading) }
val addTabState get() = _addTabState.asStateFlow()
private val _requestsTabState by lazy { MutableStateFlow<RequestsTabState>(RequestsTabState.Loading) }
val requestsTabState get() = _requestsTabState.asStateFlow()
private val _refreshing by lazy { MutableStateFlow(false) }
val refreshing by lazy { _refreshing.asStateFlow() }
init {
viewModelScope.launch {
try {
_addTabState.emit(loadFriends())
_requestsTabState.emit(loadRequests())
} catch (e: Exception) {
Timber.e(e)
}
_search
.debounce(1.seconds)
.collect { itSearch ->
try {
_addTabState.emit(
if (itSearch.isNotBlank()) {
loadSearch(itSearch)
} else {
loadFriends()
}
)
} catch (e: Exception) {
Timber.e(e)
}
}
}
}
fun refreshAll() {
viewModelScope.launch {
try {
_refreshing.emit(true)
_addTabState.emit(
_search.value.let { itSearch ->
if (itSearch.isNotBlank()) {
loadSearch(itSearch)
} else {
loadFriends()
}
}
)
_requestsTabState.emit(loadRequests())
} catch (e: Exception) {
Timber.e(e)
}
_refreshing.emit(false)
}
}
private suspend fun loadFriends() =
AddTabState.Loaded(
users = friendRepository.getFriends(
token = keystoreManager.getTokenOrEmpty()
)
)
private suspend fun loadSearch(search: String) =
AddTabState.Loaded(
users = friendRepository.searchNew(
token = keystoreManager.getTokenOrEmpty(),
search = search
)
)
private suspend fun loadRequests() =
RequestsTabState.Loaded(
users = friendRepository.getFriendRequests(
token = keystoreManager.getTokenOrEmpty()
)
)
fun setSearch(search: String) {
viewModelScope.launch {
_search.emit(search)
}
}
fun addFriend(username: String) {
viewModelScope.launch {
try {
friendRepository.add(
token = keystoreManager.getTokenOrEmpty(),
username = username
)
changeFriendStatus(username, FriendStatus.REQUESTED)
removeRequest(username)
} catch (e: Exception) {
Timber.e(e)
}
}
}
fun removeFriend(username: String) {
viewModelScope.launch {
try {
friendRepository.remove(
token = keystoreManager.getTokenOrEmpty(),
username = username
)
changeFriendStatus(username, FriendStatus.NOT_FRIEND)
removeRequest(username)
} catch (e: Exception) {
Timber.e(e)
}
}
}
private suspend fun changeFriendStatus(username: String, newStatus: FriendStatus) {
(_addTabState.value as? AddTabState.Loaded)?.let { friends ->
val usrIdx = friends.users.indexOfFirst { it.username == username }
val newList = if (usrIdx == -1 && newStatus == FriendStatus.REQUESTED) {
(_requestsTabState.value as? RequestsTabState.Loaded)?.let { requests ->
requests.users.find { it.username == username }
}?.let {
friends.users + it.copy(friendStatus = FriendStatus.FRIEND)
} ?: friends.users
} else {
friends.users.toMutableList().apply {
replaceAll {
if (it.username == username) {
it.copy(friendStatus = newStatus)
} else it
}
}
}
_addTabState.emit(
friends.copy(
users = newList
)
)
}
}
private suspend fun removeRequest(username: String) {
(_requestsTabState.value as? RequestsTabState.Loaded)?.let { requests ->
requests.users.find { it.username == username }?.let {
_requestsTabState.emit(
requests.copy(users = requests.users - it)
)
}
}
}
sealed interface AddTabState {
data object Loading : AddTabState
data class Loaded(val users: List<User>) : AddTabState
}
sealed interface RequestsTabState {
data object Loading : RequestsTabState
data class Loaded(val users: List<User>) : RequestsTabState
}
}

@ -0,0 +1,166 @@
package fr.iut.alldev.allin.ui.friends.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
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.data.model.User
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.AllInSections
import fr.iut.alldev.allin.ui.core.SectionElement
import fr.iut.alldev.allin.ui.friends.FriendsScreenViewModel
import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenAddTab
import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenRequestsTab
import kotlinx.coroutines.launch
@Composable
fun FriendsScreenContent(
addTabState: FriendsScreenViewModel.AddTabState,
search: String,
isRefreshing: Boolean,
onToggleDeleteFriend: (User) -> Unit,
setSearch: (String) -> Unit,
requestsTabState: FriendsScreenViewModel.RequestsTabState,
acceptRequest: (User) -> Unit,
declineRequest: (User) -> Unit,
refresh: () -> Unit
) {
val focus = LocalFocusManager.current
val pullRefreshState = rememberPullRefreshState(isRefreshing, refresh)
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
val scope = rememberCoroutineScope()
Box(
modifier = Modifier
.pullRefresh(pullRefreshState)
.padding(top = with(LocalDensity.current) {
progressAnimation.toDp()
})
) {
Column(Modifier.fillMaxSize()) {
Text(
text = stringResource(id = R.string.friends_title),
style = AllInTheme.typography.h1,
color = AllInColorToken.allInGrey,
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 11.dp)
)
AllInSections(
modifier = Modifier.let {
if (isRefreshing) {
it
.alpha(.5f)
.pointerInput(Unit) {
scope.launch {
awaitPointerEventScope {
while (true) {
awaitPointerEvent(pass = PointerEventPass.Initial)
.changes
.forEach(PointerInputChange::consume)
}
}
}
}
} else it
},
onLoadSection = { focus.clearFocus() },
sections = listOf(
SectionElement(stringResource(id = R.string.friends_add_tab)) {
when (addTabState) {
is FriendsScreenViewModel.AddTabState.Loaded -> {
FriendsScreenAddTab(
friends = addTabState.users,
search = search,
setSearch = setSearch,
onToggleDeleteFriend = onToggleDeleteFriend
)
}
FriendsScreenViewModel.AddTabState.Loading -> {
AllInLoading(visible = true)
}
}
},
SectionElement(
stringResource(
id = R.string.friends_requests_tab,
(requestsTabState as? FriendsScreenViewModel.RequestsTabState.Loaded)
?.users?.size ?: 0
)
) {
when (requestsTabState) {
is FriendsScreenViewModel.RequestsTabState.Loaded -> {
FriendsScreenRequestsTab(
requests = requestsTabState.users,
acceptRequest = acceptRequest,
declineRequest = declineRequest
)
}
FriendsScreenViewModel.RequestsTabState.Loading -> {
AllInLoading(visible = true)
}
}
}
)
)
}
PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = isRefreshing,
state = pullRefreshState
)
}
}
@Preview
@Composable
private fun FriendsScreenContentPreview() {
AllInTheme {
FriendsScreenContent(
addTabState = FriendsScreenViewModel.AddTabState.Loaded(emptyList()),
search = "",
setSearch = {},
onToggleDeleteFriend = {},
requestsTabState = FriendsScreenViewModel.RequestsTabState.Loaded(emptyList()),
acceptRequest = {},
declineRequest = {},
refresh = {},
isRefreshing = false
)
}
}

@ -0,0 +1,122 @@
package fr.iut.alldev.allin.ui.friends.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
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.text.style.TextOverflow
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.data.model.FriendStatus
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInButton
import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable
fun FriendsScreenLine(
username: String,
image: String?,
status: FriendStatus,
toggleIsFriend: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
ProfilePicture(
image = image,
fallback = username.asFallbackProfileUsername(),
size = 50.dp
)
Text(
text = username,
color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.sm2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 15.sp,
modifier = Modifier.weight(1f)
)
AllInButton(
color = when (status) {
FriendStatus.FRIEND -> AllInTheme.colors.background
FriendStatus.NOT_FRIEND -> AllInColorToken.allInPurple
FriendStatus.REQUESTED -> AllInTheme.colors.border
},
text = when (status) {
FriendStatus.FRIEND -> {
stringResource(id = R.string.generic_delete)
}
FriendStatus.NOT_FRIEND -> {
stringResource(id = R.string.generic_add)
}
FriendStatus.REQUESTED -> {
stringResource(id = R.string.friends_request_sent)
}
},
textColor = when (status) {
FriendStatus.FRIEND -> AllInTheme.colors.onBackground
FriendStatus.NOT_FRIEND -> AllInColorToken.white
FriendStatus.REQUESTED -> AllInTheme.colors.onBackground2
},
isSmall = true,
textStyle = AllInTheme.typography.sm2,
onClick = toggleIsFriend,
modifier = Modifier.weight(.8f)
)
}
}
@Preview
@Composable
private fun FriendsScreenLinePreview() {
AllInTheme {
FriendsScreenLine(
image = null,
username = "Random",
status = FriendStatus.NOT_FRIEND,
toggleIsFriend = { }
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FriendsScreenLineRequestedPreview() {
AllInTheme {
FriendsScreenLine(
image = null,
username = "Random",
status = FriendStatus.REQUESTED,
toggleIsFriend = { }
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FriendsScreenLineIsFriendPreview() {
AllInTheme {
FriendsScreenLine(
image = null,
username = "Random",
status = FriendStatus.FRIEND,
toggleIsFriend = { }
)
}
}

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

Loading…
Cancel
Save