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

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

@ -7,6 +7,7 @@ plugins {
id("kotlin-android") id("kotlin-android")
id("kotlin-kapt") id("kotlin-kapt")
id("dagger.hilt.android.plugin") id("dagger.hilt.android.plugin")
id("com.starter.easylauncher")
} }
// Keystore // Keystore
@ -74,6 +75,33 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}" 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 { dependencies {
@ -100,6 +128,9 @@ dependencies {
// Squircle // Squircle
implementation(libs.smoothCornerRect) implementation(libs.smoothCornerRect)
// Coil
implementation(libs.coil.compose)
// Tests // Tests
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
androidTestImplementation(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 androidx.compose.ui.test.onNodeWithTag
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest 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.TestTags
import fr.iut.alldev.allin.test.mock.Bets import fr.iut.alldev.allin.test.mock.Bets
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer import fr.iut.alldev.allin.vo.bet.displayer.BetTestDisplayer
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -33,13 +34,22 @@ class BetVOTest {
} }
@Test @Test
fun testDisplayer_shouldDisplayYesNoBetUI(){ fun testDisplayer_shouldDisplayYesNoBetUI() {
//Given //Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme {
displayer.DisplayBet(Bets.bets[0]) displayer.DisplayBet(
Bets.bets[0],
currentUser
)
} }
} }
//Expect //Expect
@ -49,13 +59,22 @@ class BetVOTest {
} }
@Test @Test
fun testDisplayer_shouldDisplayMatchUI(){ fun testDisplayer_shouldDisplayMatchUI() {
//Given //Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme {
displayer.DisplayBet(Bets.bets[1]) displayer.DisplayBet(
Bets.bets[1],
currentUser
)
} }
} }
//Expect //Expect
@ -65,13 +84,22 @@ class BetVOTest {
} }
@Test @Test
fun testDisplayer_shouldDisplayCustomBetUI(){ fun testDisplayer_shouldDisplayCustomBetUI() {
//Given //Given
val currentUser = User(
id = "1",
username = "test",
email = "test@test.fr",
coins = 120
)
//When //When
composeTestRule.activity.setContent { composeTestRule.activity.setContent {
AllInTheme{ AllInTheme {
displayer.DisplayBet(Bets.bets[2]) displayer.DisplayBet(
Bets.bets[2],
currentUser
)
} }
} }
//Expect //Expect

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

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

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".AllInApplication" android:name=".AllInApplication"
@ -17,12 +17,24 @@
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:exported="true" android:exported="true"
android:windowSoftInputMode="adjustPan"
android:theme="@style/Theme.Allin"> android:theme="@style/Theme.Allin">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </manifest>

@ -5,11 +5,11 @@ import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber import timber.log.Timber
@HiltAndroidApp @HiltAndroidApp
class AllInApplication : Application(){ class AllInApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if(BuildConfig.DEBUG){ if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree()) 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 androidx.compose.ui.graphics.SolidColor
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInColorToken
@StringRes @StringRes
fun BetStatus.getTitleId(): Int { fun BetStatus.getTitleId(): Int {
@ -23,36 +23,36 @@ fun BetStatus.getTitleId(): Int {
@StringRes @StringRes
fun BetStatus.getDateStartLabelId(): Int { fun BetStatus.getDateStartLabelId(): Int {
return when (this) { return when (this) {
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.Started BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.bet_started
else -> R.string.Starting else -> R.string.bet_starting
} }
} }
@StringRes @StringRes
fun BetStatus.getDateEndLabelId(): Int { fun BetStatus.getDateEndLabelId(): Int {
return when (this) { return when (this) {
BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.Ended BetStatus.CLOSING, BetStatus.FINISHED, BetStatus.CANCELLED -> R.string.bet_ended
else -> R.string.Ends else -> R.string.bet_ends
} }
} }
@Composable @Composable
fun BetStatus.getColor(): Color { fun BetStatus.getColor(): Color {
return when (this) { return when (this) {
BetStatus.FINISHED -> AllInTheme.colors.allInBetFinish BetStatus.FINISHED -> AllInColorToken.allInBetFinish
BetStatus.IN_PROGRESS -> AllInTheme.colors.allInBetInProgress BetStatus.IN_PROGRESS -> AllInColorToken.allInBetInProgress
BetStatus.WAITING -> AllInTheme.colors.allInBetWaiting BetStatus.WAITING -> AllInColorToken.allInBetWaiting
else -> AllInTheme.colors.allInBetFinish // TODO else -> AllInColorToken.allInBetFinish // TODO
} }
} }
@Composable @Composable
fun BetStatus.getTextColor(): Color { fun BetStatus.getTextColor(): Color {
return when (this) { return when (this) {
BetStatus.FINISHED -> AllInTheme.colors.allInBetFinishText BetStatus.FINISHED -> AllInColorToken.allInBetFinishText
BetStatus.IN_PROGRESS -> AllInTheme.colors.allInBetInProgressText BetStatus.IN_PROGRESS -> AllInColorToken.allInBetInProgressText
BetStatus.WAITING -> AllInTheme.colors.allInBetWaitingText BetStatus.WAITING -> AllInColorToken.allInBetWaitingText
else -> AllInTheme.colors.allInBetFinishText // TODO else -> AllInColorToken.allInBetFinishText // TODO
} }
} }
@ -70,8 +70,8 @@ fun BetStatus.getBetHistoryPhrase(won: Boolean): Int {
fun BetStatus.getBetHistoryStatusColor(won: Boolean): Brush { fun BetStatus.getBetHistoryStatusColor(won: Boolean): Brush {
return when (this) { return when (this) {
BetStatus.FINISHED -> 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 @StringRes
fun BetType.getTitleId(): Int { fun BetType.getTitleId(): Int {
return when (this) { return when (this) {
BetType.BINARY -> R.string.yes_no BetType.BINARY -> R.string.bet_type_binary
BetType.MATCH -> R.string.sport_match BetType.MATCH -> R.string.bet_type_match
BetType.CUSTOM -> R.string.custom_answers BetType.CUSTOM -> R.string.bet_type_custom
} }
} }

@ -14,29 +14,31 @@ sealed class FieldErrorState(
) { ) {
data object NoError : 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) : 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) : 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) : 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) : 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) : 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) : 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 @Composable
fun errorResource() = stringResourceOrNull(id = messageId, *messageArgs) fun errorResource() = stringResourceOrNull(id = messageId, *messageArgs)

@ -1,17 +1,26 @@
package fr.iut.alldev.allin.ext package fr.iut.alldev.allin.ext
import android.graphics.BlurMaskFilter import android.graphics.BlurMaskFilter
import androidx.compose.foundation.ScrollState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset 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.Color
import androidx.compose.ui.graphics.LinearGradientShader import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb 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 androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlin.math.min
@Composable @Composable
fun Modifier.shadow( fun Modifier.shadow(
@ -31,7 +40,7 @@ fun Modifier.shadow(
BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL) BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL)
} }
frameworkPaint.color = color.toArgb() frameworkPaint.color = color.toArgb()
frameworkPaint.alpha = (255*alpha).toInt() frameworkPaint.alpha = (255 * alpha).toInt()
val leftPixel = offsetX.toPx() val leftPixel = offsetX.toPx()
val topPixel = offsetY.toPx() val topPixel = offsetY.toPx()
val rightPixel = size.width + topPixel val rightPixel = size.width + topPixel
@ -71,7 +80,7 @@ fun Modifier.shadow(
Offset(size.width, 0f), Offset(size.width, 0f),
colors colors
) )
frameworkPaint.alpha = (255*alpha).toInt() frameworkPaint.alpha = (255 * alpha).toInt()
val leftPixel = offsetX.toPx() val leftPixel = offsetX.toPx()
val topPixel = offsetY.toPx() val topPixel = offsetY.toPx()
val rightPixel = size.width + topPixel 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? { fun String.toFloatOrNull(locale: Locale): Float? {
val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale)) val format = DecimalFormat("0.##", DecimalFormatSymbols.getInstance(locale))
return format.parse(this)?.toFloat() 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.Immutable
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
@Immutable @Immutable
data class AllInColors( 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 mainSurface: Color,
val onMainSurface: Color, val onMainSurface: Color,
val background: Color, val background: Color,
@ -97,8 +20,8 @@ data class AllInThemeColors(
val disabledBorder: Color, val disabledBorder: Color,
) )
internal val LocalThemeColors = staticCompositionLocalOf { internal val LocalColors = staticCompositionLocalOf {
AllInThemeColors( AllInColors(
mainSurface = Color.Unspecified, mainSurface = Color.Unspecified,
onMainSurface = Color.Unspecified, onMainSurface = Color.Unspecified,
background = Color.Unspecified, background = Color.Unspecified,
@ -110,4 +33,76 @@ internal val LocalThemeColors = staticCompositionLocalOf {
disabled = Color.Unspecified, disabled = Color.Unspecified,
disabledBorder = 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 @Immutable
data class AllInIcons( data class AllInIcons(
val allCoins: @Composable () -> Painter, val allCoins: @Composable () -> Painter,
val logo: @Composable () -> Painter
) )
internal val LocalIcons = staticCompositionLocalOf { internal val LocalIcons = staticCompositionLocalOf {
AllInIcons( 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.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable 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.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@ -19,74 +17,6 @@ import fr.iut.alldev.allin.R
fun AllInTheme( fun AllInTheme(
content: @Composable () -> Unit, 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( val customTypography = AllInTypography(
h1 = TextStyle( h1 = TextStyle(
fontFamily = fontFamilyPlusJakartaSans, fontFamily = fontFamilyPlusJakartaSans,
@ -119,54 +49,49 @@ fun AllInTheme(
) )
val customTheme = if (isSystemInDarkTheme()) { val customTheme = if (isSystemInDarkTheme()) {
AllInThemeColors( AllInColors(
mainSurface = customColors.allInDarkGrey300, mainSurface = AllInColorToken.allInDarkGrey300,
onMainSurface = customColors.allInWhite, onMainSurface = AllInColorToken.allInWhite,
background = customColors.allInDarkGrey200, background = AllInColorToken.allInDarkGrey200,
onBackground = customColors.white, onBackground = AllInColorToken.white,
tint1 = customColors.white, tint1 = AllInColorToken.white,
background2 = customColors.allInDark, background2 = AllInColorToken.allInDark,
onBackground2 = customColors.allInLightGrey200, onBackground2 = AllInColorToken.allInLightGrey200,
border = customColors.allInDarkGrey100, border = AllInColorToken.allInDarkGrey100,
disabled = customColors.allInDarkGrey200, disabled = AllInColorToken.allInDarkGrey200,
disabledBorder = customColors.allInDarkGrey100 disabledBorder = AllInColorToken.allInDarkGrey100
) )
} else { } else {
AllInThemeColors( AllInColors(
mainSurface = customColors.allInWhite, mainSurface = AllInColorToken.allInWhite,
onMainSurface = customColors.allInDark, onMainSurface = AllInColorToken.allInDark,
background = customColors.white, background = AllInColorToken.white,
onBackground = customColors.allInDarkBlue, onBackground = AllInColorToken.allInDarkBlue,
tint1 = customColors.allInLoginPurple, tint1 = AllInColorToken.allInLoginPurple,
background2 = customColors.allInLightGrey50, background2 = AllInColorToken.allInLightGrey50,
onBackground2 = customColors.allInLightGrey300, onBackground2 = AllInColorToken.allInLightGrey300,
border = customColors.allInLightGrey100, border = AllInColorToken.allInLightGrey100,
disabled = customColors.allInLightGrey100, disabled = AllInColorToken.allInLightGrey100,
disabledBorder = customColors.allInLightGrey200 disabledBorder = AllInColorToken.allInLightGrey200
) )
} }
val customIcons = AllInIcons( val customIcons = AllInIcons(
allCoins = { painterResource(id = R.drawable.allcoin) } allCoins = { painterResource(id = R.drawable.allcoin) },
logo = { painterResource(id = R.drawable.allin) }
) )
CompositionLocalProvider( CompositionLocalProvider(
LocalColors provides customColors,
LocalIcons provides customIcons, LocalIcons provides customIcons,
LocalTypography provides customTypography, LocalTypography provides customTypography,
LocalThemeColors provides customTheme LocalColors provides customTheme
) { ) {
content() content()
} }
} }
object AllInTheme { object AllInTheme {
val colors: AllInColors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
val icons: AllInIcons val icons: AllInIcons
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
@ -177,10 +102,10 @@ object AllInTheme {
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalTypography.current get() = LocalTypography.current
val themeColors: AllInThemeColors val colors: AllInColors
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
get() = LocalThemeColors.current get() = LocalColors.current
} }

@ -1,42 +1,29 @@
package fr.iut.alldev.allin.ui package fr.iut.alldev.allin.ui
import android.app.Activity
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent 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.Color
import androidx.compose.ui.graphics.toArgb 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 dagger.hilt.android.AndroidEntryPoint
import fr.iut.alldev.allin.ui.navigation.AllInNavHost
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.navigation.AllInNavHost
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.light(
scrim = Color.Transparent.toArgb(),
darkScrim = Color.Transparent.toArgb()
)
)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
AllInTheme{ 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)
}
}
}
}
AllInNavHost() AllInNavHost()
} }
} }

@ -1,138 +1,37 @@
package fr.iut.alldev.allin.ui.bet 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.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue 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 androidx.hilt.navigation.compose.hiltViewModel
import fr.iut.alldev.allin.R import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.Bet
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.ui.bet.components.BetScreenLoadedContent
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
import fr.iut.alldev.allin.ui.core.AllInChip
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable @Composable
fun BetScreen( fun BetScreen(
viewModel: BetViewModel = hiltViewModel(), viewModel: BetViewModel = hiltViewModel(),
selectBet: (Bet, Boolean) -> Unit, selectBet: (Bet, Boolean) -> Unit,
) { ) {
val state by viewModel.state.collectAsStateWithLifecycle()
val bets by viewModel.bets.collectAsState() val filters by viewModel.filters.collectAsStateWithLifecycle()
val isRefreshing by viewModel.refreshing.collectAsStateWithLifecycle()
val horizontalPadding = 23.dp
when (val s = state) {
val refreshing by viewModel.isRefreshing.collectAsState() is BetViewModel.State.Loaded -> {
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() }) BetScreenLoadedContent(
popularBet = s.popularBet,
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "") filters = filters,
bets = s.bets,
LazyColumn( isRefreshing = isRefreshing,
Modifier selectBet = selectBet,
.pullRefresh(pullRefreshState) toggleFilter = { viewModel.toggleFilter(it) },
.padding(top = with(LocalDensity.current) { refreshData = { viewModel.refreshData() }
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)
) )
Spacer(modifier = Modifier.height(24.dp))
} }
}
}
val items = listOf( BetViewModel.State.Loading -> {
R.string.Public, AllInLoading(visible = true)
R.string.Invitation, }
R.string.Current, }
R.string.Finished }
)

@ -4,16 +4,17 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.model.bet.Bet import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetFilter
import fr.iut.alldev.allin.data.repository.BetRepository import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel @HiltViewModel
class BetViewModel @Inject constructor( class BetViewModel @Inject constructor(
@ -21,42 +22,70 @@ class BetViewModel @Inject constructor(
private val betRepository: BetRepository private val betRepository: BetRepository
) : ViewModel() { ) : ViewModel() {
private val _isRefreshing by lazy { MutableStateFlow(false) } private val _state: MutableStateFlow<State> by lazy { MutableStateFlow(State.Loading) }
val isRefreshing: StateFlow<Boolean> val state: StateFlow<State> by lazy { _state.asStateFlow() }
get() = _isRefreshing.asStateFlow()
private val _bets: MutableStateFlow<List<Bet>> by lazy { private val _filters: MutableStateFlow<List<BetFilter>> by lazy { MutableStateFlow(emptyList()) }
MutableStateFlow(emptyList()) val filters get() = _filters.asStateFlow()
}
val bets: StateFlow<List<Bet>> by lazy { private val _refreshing by lazy { MutableStateFlow(false) }
_bets.asStateFlow() val refreshing by lazy { _refreshing.asStateFlow() }
.filterNotNull()
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000L),
emptyList()
)
}
init { init {
viewModelScope.launch { viewModelScope.launch {
refreshData() refreshBets()
filters
.debounce(1.seconds)
.collect {
refreshBets()
}
} }
} }
private suspend fun refreshData() { fun toggleFilter(filter: BetFilter) {
runCatching { viewModelScope.launch {
_bets.emit(betRepository.getAllBets(keystoreManager.getTokenOrEmpty())) _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 { viewModelScope.launch {
_isRefreshing.emit(true) _refreshing.emit(true)
refreshData() refreshBets()
_isRefreshing.emit(false) _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
import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBouncyCard import fr.iut.alldev.allin.ui.core.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.RainbowButton import fr.iut.alldev.allin.ui.core.RainbowButton
@ -35,15 +35,18 @@ fun BetScreenCard(
title: String, title: String,
date: String, date: String,
time: String, time: String,
players: List<Painter?>, players: List<User>,
modifier: Modifier = Modifier, totalParticipants: Int,
onClickParticipate: () -> Unit, onClickParticipate: () -> Unit,
onClickCard: () -> Unit, onClickCard: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true
) { ) {
AllInBouncyCard( AllInBouncyCard(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
radius = 16.dp, radius = 16.dp,
onClick = onClickCard onClick = onClickCard,
enabled = enabled
) { ) {
Column( Column(
Modifier.padding(horizontal = 19.dp, vertical = 11.dp) Modifier.padding(horizontal = 19.dp, vertical = 11.dp)
@ -55,15 +58,15 @@ fun BetScreenCard(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(11.dp)) 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( HorizontalDivider(
thickness = 1.dp, thickness = 1.dp,
color = AllInTheme.themeColors.border color = AllInTheme.colors.border
) )
Column( Column(
Modifier Modifier
.background(AllInTheme.themeColors.background2) .background(AllInTheme.colors.background2)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -71,22 +74,25 @@ fun BetScreenCard(
.padding(7.dp), .padding(7.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BetProfilePictureRow(pictures = players) if (players.isNotEmpty()) {
Spacer(modifier = Modifier.width(12.dp)) BetProfilePictureRow(pictures = players.map { it.username to it.image })
Spacer(modifier = Modifier.width(12.dp))
}
Text( Text(
text = pluralStringResource( text = pluralStringResource(
id = R.plurals.n_players_waiting, id = R.plurals.bet_players_waiting_format,
players.size, totalParticipants,
players.size totalParticipants
), ),
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2 color = AllInTheme.colors.onBackground2
) )
} }
RainbowButton( RainbowButton(
modifier = Modifier.padding(6.dp), modifier = Modifier.padding(6.dp),
text = stringResource(id = R.string.Participate), text = stringResource(id = R.string.bet_participate),
onClick = onClickParticipate onClick = onClickParticipate,
enabled = enabled
) )
} }
} }
@ -103,9 +109,59 @@ private fun BetScreenCardPreview() {
title = "Emre va réussir son TP de CI/CD mercredi?", title = "Emre va réussir son TP de CI/CD mercredi?",
date = "12 Sept.", date = "12 Sept.",
time = "13:00", 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 = {}, onClickParticipate = {},
onClickCard = {} 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 package fr.iut.alldev.allin.ui.bet.components
import android.icu.text.CompactDecimalFormat
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.os.ConfigurationCompat
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ext.shadow 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.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 fr.iut.alldev.allin.ui.core.HighlightedText
import kotlin.math.ceil
@Composable @Composable
fun BetScreenPopularCard( fun BetScreenPopularCard(
nbPlayers: Int, nbPlayers: Int,
points: Float, points: Int,
pointUnit: String,
title: String, title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier, 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 modifier = modifier
.let { .let {
if (isSystemInDarkTheme()) { if (isSystemInDarkTheme()) {
it.shadow( it.shadow(
colors = listOf( colors = listOf(
AllInTheme.colors.allInPink, AllInColorToken.allInPink,
AllInTheme.colors.allInBlue AllInColorToken.allInBlue
), ),
blurRadius = 10.dp, blurRadius = 10.dp,
alpha = .5f, alpha = .5f,
@ -60,9 +71,11 @@ fun BetScreenPopularCard(
} }
} }
.fillMaxWidth(), .fillMaxWidth(),
backgroundColor = AllInTheme.colors.allInDark, backgroundColor = AllInColorToken.allInDark,
borderWidth = 2.dp, borderWidth = 2.dp,
borderBrush = AllInTheme.colors.allInMainGradient borderBrush = AllInColorToken.allInMainGradient,
onClick = onClick,
enabled = enabled
) { ) {
Column(modifier = Modifier.padding(13.dp)) { Column(modifier = Modifier.padding(13.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@ -70,19 +83,19 @@ fun BetScreenPopularCard(
painter = painterResource(id = R.drawable.allin_fire), painter = painterResource(id = R.drawable.allin_fire),
modifier = Modifier.size(15.dp), modifier = Modifier.size(15.dp),
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.allInPink tint = AllInColorToken.allInPink
) )
Spacer(modifier = Modifier.width(3.dp)) Spacer(modifier = Modifier.width(3.dp))
Text( Text(
text = stringResource(id = R.string.Popular), text = stringResource(id = R.string.bet_popular),
color = AllInTheme.colors.allInPink, color = AllInColorToken.allInPink,
fontSize = 17.sp, fontSize = 17.sp,
style = AllInTheme.typography.h2 style = AllInTheme.typography.h2
) )
} }
Text( Text(
text = title, text = title,
color = AllInTheme.colors.white, color = AllInColorToken.white,
fontSize = 20.sp, fontSize = 20.sp,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
modifier = Modifier.padding(vertical = 22.dp) modifier = Modifier.padding(vertical = 22.dp)
@ -90,42 +103,38 @@ fun BetScreenPopularCard(
Row(modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) { Row(modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) {
HighlightedText( HighlightedText(
text = pluralStringResource( text = pluralStringResource(
id = R.plurals.n_players, id = R.plurals.bet_players_format,
nbPlayers, nbPlayers,
nbPlayers nbPlayers
), ),
query = nbPlayers.toString(), query = nbPlayers.toString(),
highlightStyle = SpanStyle( highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AllInTheme.colors.allInPink color = AllInColorToken.allInPink
), ),
color = AllInTheme.colors.white, color = AllInColorToken.white,
style = AllInTheme.typography.p1, style = AllInTheme.typography.p1,
fontSize = 15.sp fontSize = 15.sp
) )
Text( Text(
text = " - ", text = " - ",
color = AllInTheme.colors.white, color = AllInColorToken.white,
style = AllInTheme.typography.p1, style = AllInTheme.typography.p1,
fontSize = 15.sp fontSize = 15.sp
) )
val pointsText = if (points % 1 == 0f) { val pointsText = numberFormat.format(points)
stringResource(id = R.string.int_and_unit, points.toInt(), pointUnit)
} else {
stringResource(id = R.string.float_and_unit, points, pointUnit)
}
HighlightedText( HighlightedText(
text = pluralStringResource( text = pluralStringResource(
id = R.plurals.n_points_at_stake, id = R.plurals.bet_points_at_stake_format,
if (pointUnit.isEmpty()) ceil(points).toInt() else 2, points,
pointsText pointsText
), ),
query = pointsText, query = pointsText,
highlightStyle = SpanStyle( highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AllInTheme.colors.allInPink color = AllInColorToken.allInPink
), ),
color = AllInTheme.colors.white, color = AllInColorToken.white,
style = AllInTheme.typography.p1, style = AllInTheme.typography.p1,
fontSize = 15.sp fontSize = 15.sp
) )
@ -140,9 +149,9 @@ private fun BetScreenPopularCardPreview() {
AllInTheme { AllInTheme {
BetScreenPopularCard( BetScreenPopularCard(
nbPlayers = 12, nbPlayers = 12,
points = 2.35f, points = 2350,
pointUnit = "k", title = "Emre va réussir son TP de CI/CD mercredi?",
title = "Emre va réussir son TP de CI/CD mercredi?" onClick = {}
) )
} }
} }
@ -153,9 +162,9 @@ private fun BetScreenPopularCardSingularPreview() {
AllInTheme { AllInTheme {
BetScreenPopularCard( BetScreenPopularCard(
nbPlayers = 1, nbPlayers = 1,
points = 1.0f, points = 150,
pointUnit = "", title = "Emre va réussir son TP de CI/CD mercredi?",
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.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -28,12 +32,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha 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.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime 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.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.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE 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.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.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail 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.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.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.ui.core.AllInButton import fr.iut.alldev.allin.ui.core.AllInButton
@ -89,13 +99,13 @@ fun BetConfirmationBottomSheetAnswer(
text: String, text: String,
odds: Float, odds: Float,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
color: Color = AllInTheme.colors.allInBlue, color: Color = AllInColorToken.allInBlue,
isSelected: Boolean, isSelected: Boolean,
locale: Locale, locale: Locale,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val backColor = if (isSelected) AllInTheme.colors.allInPurple else AllInTheme.colors.white val backColor = if (isSelected) AllInColorToken.allInPurple else AllInColorToken.white
val contentColor = if (isSelected) AllInTheme.colors.white else null val contentColor = if (isSelected) AllInColorToken.white else null
AllInCard( AllInCard(
backgroundColor = backColor, backgroundColor = backColor,
@ -111,22 +121,27 @@ fun BetConfirmationBottomSheetAnswer(
text = text.uppercase(), text = text.uppercase(),
color = contentColor ?: color, color = contentColor ?: color,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontSize = 40.sp, fontSize = 35.sp,
modifier = Modifier.align(Alignment.Center) textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.align(Alignment.Center)
.padding(horizontal = 64.dp)
) )
AllInCard( Box(
radius = 50.dp, modifier = Modifier
backgroundColor = contentColor ?: AllInTheme.colors.allInPurple, .align(Alignment.CenterEnd)
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( text = "x${odds.formatToSimple(locale)}",
text = "x${odds.formatToSimple(locale)}", color = backColor,
color = backColor, style = AllInTheme.typography.h2
style = AllInTheme.typography.h2 )
)
}
} }
} }
} }
@ -139,123 +154,49 @@ fun ConfirmationAnswers(
onClick: (String) -> Unit onClick: (String) -> Unit
) { ) {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val locale = val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() } val scrollState = rememberScrollState()
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)
)
}
}
}
is YesNoBet -> { val possibleAnswers = remember {
item { when (val bet = betDetail.bet) {
betDetail.getAnswerOfResponse(YES_VALUE)?.let { is CustomBet -> bet.possibleAnswers
val opacity by animateFloatAsState( is MatchBet -> listOf(bet.nameTeam1, bet.nameTeam2)
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f, is BinaryBet -> listOf(YES_VALUE, NO_VALUE)
label = "" }
) }
val scale by animateFloatAsState(
targetValue = if (selectedAnswer == null) 1f
else if (selectedAnswer != it.response) .95f else 1.05f,
label = ""
)
BetConfirmationBottomSheetAnswer( Column(
text = it.response, verticalArrangement = Arrangement.spacedBy(8.dp),
odds = it.odds, modifier = Modifier
locale = locale, .nonLinkedScroll()
onClick = { onClick(it.response) }, .verticalScroll(scrollState)
isSelected = selectedAnswer == it.response, .fadingEdges(scrollState)
modifier = Modifier ) {
.alpha(opacity) possibleAnswers.forEachIndexed { idx, it ->
.scale(scale) betDetail.getAnswerOfResponse(it)?.let {
) val opacity by animateFloatAsState(
} targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
} label = ""
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 = ""
)
BetConfirmationBottomSheetAnswer( BetConfirmationBottomSheetAnswer(
text = it.response, text = it.response,
color = AllInTheme.colors.allInBarPink, odds = it.odds,
odds = it.odds, locale = locale,
locale = locale, color = if (possibleAnswers.size == 2 && idx == 1) {
onClick = { onClick(it.response) }, AllInColorToken.allInBarPink
isSelected = selectedAnswer == it.response, } else {
modifier = Modifier AllInColorToken.allInBlue
.alpha(opacity) },
.scale(scale) 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) } var selectedAnswer by remember { mutableStateOf<String?>(null) }
AllInMarqueeBox(backgroundColor = AllInTheme.colors.allInDarkGrey300) { AllInMarqueeBox(backgroundColor = AllInColorToken.allInDarkGrey300) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(
WindowInsets.safeContent
.takeTopOnly()
.asPaddingValues()
)
.padding(16.dp) .padding(16.dp)
) { ) {
IconButton( IconButton(
@ -281,16 +227,16 @@ fun BetConfirmationBottomSheetContent(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
tint = AllInTheme.colors.white, tint = AllInColorToken.white,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
Icon( Icon(
painter = painterResource(R.drawable.allin), painter = AllInTheme.icons.logo(),
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.white, tint = AllInColorToken.white,
modifier = Modifier modifier = Modifier
.size(40.dp) .size(40.dp)
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
@ -312,22 +258,22 @@ fun BetConfirmationBottomSheetContent(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(AllInTheme.colors.allInMainGradient) .background(AllInColorToken.allInMainGradient)
.padding(16.dp), .padding(16.dp),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = stringResource(id = R.string.Finished), text = stringResource(id = R.string.bet_finished),
color = AllInTheme.colors.white, color = AllInColorToken.white,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontSize = 24.sp fontSize = 24.sp
) )
} }
} }
Text( Text(
text = "Ce bet est arrivé à la date de fin. Vous devez à présent distribuer les gains en validant le pari gagnant.", text = stringResource(id = R.string.bet_confirmation_text),
color = AllInTheme.colors.allInLightGrey200, color = AllInColorToken.allInLightGrey200,
style = AllInTheme.typography.p2, style = AllInTheme.typography.p2,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
@ -335,9 +281,9 @@ fun BetConfirmationBottomSheetContent(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = "Veuillez choisir la réponse finale :", text = stringResource(id = R.string.bet_confirmation_choose_response),
fontSize = 20.sp, fontSize = 18.sp,
color = AllInTheme.colors.white, color = AllInColorToken.white,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@ -348,12 +294,15 @@ fun BetConfirmationBottomSheetContent(
} }
if (selectedAnswer != null) { if (selectedAnswer != null) {
AllInButton( AllInButton(
color = AllInTheme.colors.allInPurple, color = AllInColorToken.allInPurple,
text = stringResource(id = R.string.Validate), text = stringResource(id = R.string.generic_validate),
textColor = AllInTheme.colors.white, textColor = AllInColorToken.white,
radius = 5.dp, radius = 5.dp,
onClick = { selectedAnswer?.let(onConfirm) }, onClick = { selectedAnswer?.let(onConfirm) },
modifier = Modifier.align(Alignment.BottomCenter) modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.safeContentPadding()
) )
} }
} }
@ -366,7 +315,7 @@ private fun BetConfirmationBottomSheetContentPreview() {
AllInTheme { AllInTheme {
BetConfirmationBottomSheetContent( BetConfirmationBottomSheetContent(
betDetail = BetDetail( betDetail = BetDetail(
bet = YesNoBet( bet = BinaryBet(
id = "1", id = "1",
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",
@ -375,6 +324,8 @@ private fun BetConfirmationBottomSheetContentPreview() {
isPublic = true, isPublic = true,
betStatus = BetStatus.FINISHED, betStatus = BetStatus.FINISHED,
creator = "creator", creator = "creator",
totalStakes = 0,
totalParticipants = 0
), ),
answers = listOf( answers = listOf(
BetAnswerDetail( BetAnswerDetail(
@ -389,11 +340,12 @@ private fun BetConfirmationBottomSheetContentPreview() {
totalStakes = 150, totalStakes = 150,
totalParticipants = 1, totalParticipants = 1,
highestStake = 150, highestStake = 150,
odds = 2.0f odds = 2.255f
) )
), ),
participations = emptyList(), participations = emptyList(),
userParticipation = null userParticipation = null,
wonParticipation = null
), ),
onConfirm = { } onConfirm = { }
) { ) {

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

@ -4,14 +4,19 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel 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.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.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.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.ext.FieldErrorState
import fr.iut.alldev.allin.keystore.AllInKeystoreManager import fr.iut.alldev.allin.keystore.AllInKeystoreManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -19,9 +24,10 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class BetCreationViewModel @Inject constructor( class BetCreationViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User,
private val betRepository: BetRepository, private val betRepository: BetRepository,
private val keystoreManager: AllInKeystoreManager private val keystoreManager: AllInKeystoreManager,
private val userRepository: UserRepository,
private val friendRepository: FriendRepository
) : ViewModel() { ) : ViewModel() {
private var hasError = mutableStateOf(false) private var hasError = mutableStateOf(false)
@ -29,19 +35,37 @@ class BetCreationViewModel @Inject constructor(
var phrase = mutableStateOf("") var phrase = mutableStateOf("")
val registerDate = mutableStateOf(ZonedDateTime.now()) val registerDate = mutableStateOf(ZonedDateTime.now())
val betDate = mutableStateOf(ZonedDateTime.now()) val betDate = mutableStateOf(ZonedDateTime.now())
var isPublic = mutableStateOf(true) var isPrivate = mutableStateOf(false)
var selectedBetType = mutableStateOf(BetType.BINARY) var selectedBetType = mutableStateOf<BetTypeState>(BetTypeState.Binary)
val themeError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError) val themeError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val phraseError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError) val phraseError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError) val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val betDateError = 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() { private fun initErrorField() {
themeError.value = FieldErrorState.NoError themeError.value = FieldErrorState.NoError
phraseError.value = FieldErrorState.NoError phraseError.value = FieldErrorState.NoError
registerDateError.value = FieldErrorState.NoError registerDateError.value = FieldErrorState.NoError
betDateError.value = FieldErrorState.NoError betDateError.value = FieldErrorState.NoError
typeError.value = FieldErrorState.NoError
hasError.value = false hasError.value = false
} }
@ -77,6 +101,19 @@ class BetCreationViewModel @Inject constructor(
) )
hasError.value = true 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) { if (!hasError.value) {
try { try {
val bet = BetFactory.createBet( userRepository.currentUserState.value?.let { _ ->
id = "",
betType = selectedBetType.value, val bet = RequestBet(
theme = theme.value, theme = theme.value,
phrase = phrase.value, type = selectedBetType.value.type,
endRegisterDate = registerDate.value, sentenceBet = phrase.value,
endBetDate = betDate.value, endRegistration = registerDate.value,
isPublic = isPublic.value, endBet = betDate.value,
nameTeam1 = "", isPrivate = isPrivate.value,
nameTeam2 = "", response = selectedBetType.value.getPossibleAnswers(),
possibleAnswers = listOf(), userInvited = selectedFriends.value.toList()
creator = currentUser.username )
)
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty()) betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess() onSuccess()
} catch (e: AllInAPIException) { } ?: onError()
} catch (e: Exception) {
Timber.e(e) Timber.e(e)
onError() onError()
} }
@ -121,4 +159,64 @@ class BetCreationViewModel @Inject constructor(
setLoading(false) 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.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview 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.theme.AllInTheme
@Composable @Composable
@ -17,7 +18,7 @@ fun BetCreationScreenBottomText(
) { ) {
Text( Text(
text = text, text = text,
color = AllInTheme.colors.allInPurple, color = AllInColorToken.allInPurple,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.h2, 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard import fr.iut.alldev.allin.ui.core.AllInCard
@ -24,7 +25,7 @@ fun BetCreationScreenDateTimeButton(
) { ) {
Text( Text(
text = text, text = text,
color = AllInTheme.colors.allInPurple, color = AllInColorToken.allInPurple,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
fontSize = 16.sp, fontSize = 16.sp,
modifier = Modifier.padding(horizontal = 23.dp, vertical = 9.dp) 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.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment 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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCoinCount import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInRadioButton import fr.iut.alldev.allin.ui.core.AllInRadioButton
@ -24,6 +25,7 @@ import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable @Composable
fun BetCreationScreenFriendLine( fun BetCreationScreenFriendLine(
username: String, username: String,
image: String?,
allCoinsAmount: Int, allCoinsAmount: Int,
isSelected: Boolean, isSelected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
@ -33,8 +35,8 @@ fun BetCreationScreenFriendLine(
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.background( .background(
if (isSelected) AllInTheme.colors.allInPurple.copy(alpha = .13f) if (isSelected) AllInColorToken.allInPurple.copy(alpha = .13f)
else AllInTheme.themeColors.background else AllInTheme.colors.background
) )
.padding(15.dp), .padding(15.dp),
horizontalArrangement = Arrangement.spacedBy(7.dp), horizontalArrangement = Arrangement.spacedBy(7.dp),
@ -44,19 +46,23 @@ fun BetCreationScreenFriendLine(
checked = isSelected, checked = isSelected,
modifier = Modifier.padding(end = 7.dp) modifier = Modifier.padding(end = 7.dp)
) )
ProfilePicture(modifier = Modifier.size(25.dp)) ProfilePicture(
image = image,
fallback = username.asFallbackProfileUsername(),
size = 25.dp
)
Text( Text(
text = username, text = username,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface, color = AllInTheme.colors.onMainSurface,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
AllInCoinCount( AllInCoinCount(
amount = allCoinsAmount, amount = allCoinsAmount,
color = AllInTheme.colors.allInPurple color = AllInColorToken.allInPurple
) )
} }
} }
@ -68,6 +74,7 @@ private fun BetCreationScreenFriendLinePreview() {
AllInTheme { AllInTheme {
BetCreationScreenFriendLine( BetCreationScreenFriendLine(
username = "David", username = "David",
image = null,
allCoinsAmount = 542, allCoinsAmount = 542,
isSelected = false isSelected = false
) { ) {
@ -83,6 +90,7 @@ private fun BetCreationScreenFriendLineSelectedPreview() {
AllInTheme { AllInTheme {
BetCreationScreenFriendLine( BetCreationScreenFriendLine(
username = "David", username = "David",
image = null,
allCoinsAmount = 542, allCoinsAmount = 542,
isSelected = true 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.Arrangement
import androidx.compose.foundation.layout.Column 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.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource 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.util.fastForEach
import fr.iut.alldev.allin.R 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.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.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.AllInSelectionBox
import fr.iut.alldev.allin.ui.core.SelectionElement import fr.iut.alldev.allin.ui.core.SelectionElement
private const val BET_MAX_ANSWERS = 4
@Composable @Composable
fun BetCreationScreenAnswerTab( fun BetCreationScreenAnswerTab(
typeError: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
selected: SelectionElement?, selected: SelectionElement?,
selectedBetType: BetType, selectedBetType: BetCreationViewModel.BetTypeState,
setSelected: (SelectionElement) -> Unit, setSelected: (SelectionElement) -> Unit,
elements: List<SelectionElement>, elements: List<SelectionElement>,
addAnswer: (String) -> Unit,
deleteAnswer: (String) -> Unit
) { ) {
var isOpen by remember { var isOpen by remember {
mutableStateOf(false) 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) { Column(
AllInSelectionBox( modifier = Modifier.padding(vertical = 20.dp),
isOpen = isOpen, verticalArrangement = Arrangement.spacedBy(17.dp)
setIsOpen = { isOpen = it }, ) {
selected = selected, when (selectedBetType) {
setSelected = setSelected, BetCreationViewModel.BetTypeState.Binary -> {
elements = elements 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))
Spacer(modifier = Modifier.height(26.dp)) }
when (selectedBetType) {
BetType.BINARY -> { is BetCreationViewModel.BetTypeState.Match -> {
Column( BetCreationScreenBottomText(text = stringResource(selectedBetType.type.getTitleId()))
modifier = Modifier.padding(vertical = 20.dp), }
verticalArrangement = Arrangement.spacedBy(17.dp)
) { is BetCreationViewModel.BetTypeState.Custom -> {
BetCreationScreenBottomText(text = stringResource(id = R.string.yes_no_bottom_text_1)) val (currentAnswer, setCurrentAnswer) = remember { mutableStateOf("") }
BetCreationScreenBottomText(text = stringResource(id = R.string.yes_no_bottom_text_2))
}
} 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 -> { FlowRow(
BetCreationScreenBottomText( horizontalArrangement = Arrangement.spacedBy(8.dp),
text = stringResource(selectedBetType.getTitleId()) 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 package fr.iut.alldev.allin.ui.betCreation.tabs
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.ext.formatToMediumDate import fr.iut.alldev.allin.data.ext.formatToMediumDate
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.QuestionTabDateTimeSection
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabPrivacySection import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabPrivacySection
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
import java.time.ZonedDateTime import java.time.ZonedDateTime
import fr.iut.alldev.allin.data.ext.formatToTime as formatToTime1
@Composable @Composable
fun BetCreationScreenQuestionTab( fun BetCreationScreenQuestionTab(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
nbFriends: Int, friends: List<User>,
betTheme: String, betTheme: String,
betThemeError: String?, betThemeError: String?,
setBetTheme: (String) -> Unit, setBetTheme: (String) -> Unit,
betPhrase: String, betPhrase: String,
betPhraseError: String?, betPhraseError: String?,
setBetPhrase: (String) -> Unit, setBetPhrase: (String) -> Unit,
isPublic: Boolean, isPrivate: Boolean,
setIsPublic: (Boolean) -> Unit, setIsPrivate: (Boolean) -> Unit,
registerDate: ZonedDateTime, registerDate: ZonedDateTime,
registerDateError: String?, registerDateError: String?,
betDate: ZonedDateTime, betDate: ZonedDateTime,
betDateError: String?, betDateError: String?,
selectedFriends: MutableList<Int>, selectedFriends: List<String>,
setRegisterDateDialog: (Boolean) -> Unit, setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean) -> Unit, setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean) -> Unit, setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean) -> Unit, setEndTimeDialog: (Boolean) -> Unit,
toggleFriend: (String) -> Unit,
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
Column(modifier) { LazyColumn(
QuestionTabThemePhraseSection( modifier = modifier,
betTheme = betTheme, contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
betThemeError = betThemeError, verticalArrangement = Arrangement.spacedBy(35.dp)
setBetTheme = setBetTheme, ) {
betPhrase = betPhrase, item {
betPhraseError = betPhraseError, QuestionTabThemePhraseSection(
setBetPhrase = setBetPhrase, betTheme = betTheme,
interactionSource = interactionSource betThemeError = betThemeError,
) setBetTheme = setBetTheme,
Spacer(modifier = Modifier.height(35.dp)) betPhrase = betPhrase,
QuestionTabDateTimeSection( betPhraseError = betPhraseError,
registerDate = registerDate.formatToMediumDate(), setBetPhrase = setBetPhrase,
registerTime = registerDate.formatToTime(), interactionSource = interactionSource
registerDateError = registerDateError, )
betDateError = betDateError, }
endDate = betDate.formatToMediumDate(), item {
endTime = betDate.formatToTime(), QuestionTabDateTimeSection(
setEndDateDialog = setEndDateDialog, registerDate = registerDate.formatToMediumDate(),
setRegisterDateDialog = setRegisterDateDialog, registerTime = registerDate.formatToTime1(),
setRegisterTimeDialog = setRegisterTimeDialog, registerDateError = registerDateError,
setEndTimeDialog = setEndTimeDialog, betDateError = betDateError,
interactionSource = interactionSource, endDate = betDate.formatToMediumDate(),
) endTime = betDate.formatToTime1(),
Spacer(modifier = Modifier.height(35.dp)) setEndDateDialog = setEndDateDialog,
QuestionTabPrivacySection( setRegisterDateDialog = setRegisterDateDialog,
isPublic = isPublic, setRegisterTimeDialog = setRegisterTimeDialog,
setIsPublic = setIsPublic, setEndTimeDialog = setEndTimeDialog,
nbFriends = nbFriends, interactionSource = interactionSource,
selectedFriends = selectedFriends, )
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 @Composable
internal fun QuestionTabDateTimeSection( internal fun QuestionTabDateTimeSection(
setRegisterDateDialog: (Boolean)->Unit, setRegisterDateDialog: (Boolean) -> Unit,
setEndDateDialog: (Boolean)->Unit, setEndDateDialog: (Boolean) -> Unit,
setRegisterTimeDialog: (Boolean)->Unit, setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean)->Unit, setEndTimeDialog: (Boolean) -> Unit,
registerDateError: String?, registerDateError: String?,
betDateError: String?, betDateError: String?,
registerDate: String, registerDate: String,
@ -30,10 +30,10 @@ internal fun QuestionTabDateTimeSection(
interactionSource: MutableInteractionSource interactionSource: MutableInteractionSource
) { ) {
AllInTitleInfo( AllInTitleInfo(
text = stringResource(id = R.string.End_registration_date), text = stringResource(id = R.string.bet_creation_end_registration_date),
icon = Icons.AutoMirrored.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp), 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 interactionSource = interactionSource
) )
BetCreationScreenDateTimeRow( BetCreationScreenDateTimeRow(
@ -42,15 +42,15 @@ internal fun QuestionTabDateTimeSection(
onClickDate = { setRegisterDateDialog(true) }, onClickDate = { setRegisterDateDialog(true) },
onClickTime = { setRegisterTimeDialog(true) }, onClickTime = { setRegisterTimeDialog(true) },
) )
registerDateError?.let{ registerDateError?.let {
AllInErrorLine(text = it) AllInErrorLine(text = it)
} }
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
AllInTitleInfo( AllInTitleInfo(
text = stringResource(id = R.string.End_bet_date), text = stringResource(id = R.string.bet_creation_end_bet_date),
icon = Icons.AutoMirrored.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp), 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 interactionSource = interactionSource
) )
BetCreationScreenDateTimeRow( BetCreationScreenDateTimeRow(
@ -59,7 +59,7 @@ internal fun QuestionTabDateTimeSection(
onClickDate = { setEndDateDialog(true) }, onClickDate = { setEndDateDialog(true) },
onClickTime = { setEndTimeDialog(true) }, onClickTime = { setEndTimeDialog(true) },
) )
betDateError?.let{ betDateError?.let {
AllInErrorLine(text = it) 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.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.filled.Lock 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.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.ext.nonLinkedScroll
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenBottomText import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenBottomText
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenFriendLine import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenFriendLine
import fr.iut.alldev.allin.ui.core.AllInIconChip import fr.iut.alldev.allin.ui.core.AllInIconChip
import fr.iut.alldev.allin.ui.core.AllInRetractableCard import fr.iut.alldev.allin.ui.core.AllInRetractableCard
import fr.iut.alldev.allin.ui.core.AllInTitleInfo import fr.iut.alldev.allin.ui.core.AllInTitleInfo
import fr.iut.alldev.allin.ui.core.bet.AllInEmptyView
@Composable @Composable
fun QuestionTabPrivacySection( fun QuestionTabPrivacySection(
isPublic: Boolean, isPrivate: Boolean,
setIsPublic: (Boolean) -> Unit, setIsPrivate: (Boolean) -> Unit,
nbFriends: Int, friends: List<User>,
selectedFriends: MutableList<Int>, selectedFriends: List<String>,
interactionSource: MutableInteractionSource, interactionSource: MutableInteractionSource,
toggleFriend: (String) -> Unit
) { ) {
AllInTitleInfo( AllInTitleInfo(
text = stringResource(id = R.string.Bet_privacy), text = stringResource(id = R.string.bet_creation_bet_privacy),
icon = Icons.AutoMirrored.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
modifier = Modifier.padding(start = 11.dp, bottom = 8.dp), 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 interactionSource = interactionSource
) )
Row( Row(
@ -48,83 +58,90 @@ fun QuestionTabPrivacySection(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
AllInIconChip( AllInIconChip(
text = stringResource(id = R.string.Public), text = stringResource(id = R.string.bet_public),
leadingIcon = Icons.Default.Public, leadingIcon = Icons.Default.Public,
onClick = { onClick = { setIsPrivate(false) },
setIsPublic(true) isSelected = !isPrivate
},
isSelected = isPublic
) )
AllInIconChip( AllInIconChip(
text = stringResource(id = R.string.Private), text = stringResource(id = R.string.bet_private),
leadingIcon = Icons.Default.Lock, leadingIcon = Icons.Default.Lock,
onClick = { onClick = { setIsPrivate(true) },
setIsPublic(false) isSelected = isPrivate
},
isSelected = !isPublic
) )
} }
Column( Column(
verticalArrangement = Arrangement.spacedBy(17.dp) verticalArrangement = Arrangement.spacedBy(17.dp)
) { ) {
var isOpen by remember { var isOpen by remember { mutableStateOf(false) }
mutableStateOf(false)
}
if (isPublic) { if (isPrivate) {
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 {
AllInRetractableCard( AllInRetractableCard(
text = stringResource( text = pluralStringResource(
id = R.string.n_friends_available, id = R.plurals.bet_creation_friends_available_format,
nbFriends, friends.size,
nbFriends friends.size
), ),
borderWidth = 1.dp, borderWidth = 1.dp,
boldText = nbFriends.toString(), boldText = friends.size.toString(),
isOpen = isOpen, isOpen = isOpen,
setIsOpen = { isOpen = it } setIsOpen = { isOpen = it }
) { ) {
LazyColumn( Box {
modifier = Modifier.height(165.dp) LazyColumn(
) { modifier = Modifier
items(nbFriends) { .heightIn(max = 440.dp)
var isSelected by remember { .nonLinkedScroll()
mutableStateOf(selectedFriends.contains(it)) ) {
} itemsIndexed(friends, key = { _, it -> it.id }) { idx, it ->
var isSelected by remember {
mutableStateOf(selectedFriends.contains(it.id))
}
if (it != 0) { if (idx != 0) {
HorizontalDivider(color = AllInTheme.themeColors.border) HorizontalDivider(color = AllInTheme.colors.border)
} }
BetCreationScreenFriendLine( BetCreationScreenFriendLine(
username = "Dave", username = it.username,
allCoinsAmount = 542, allCoinsAmount = it.coins,
isSelected = isSelected image = it.image,
) { isSelected = isSelected
if (isSelected) { ) {
selectedFriends.remove(it) toggleFriend(it.id)
} else { isSelected = !isSelected
selectedFriends.add(it)
} }
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( Column(
modifier = Modifier.padding(vertical = 20.dp), modifier = Modifier.padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(17.dp) verticalArrangement = Arrangement.spacedBy(17.dp)
) { ) {
BetCreationScreenBottomText(text = stringResource(id = R.string.private_bottom_text_1)) BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_public_bottom_text_1))
BetCreationScreenBottomText(text = stringResource(id = R.string.private_bottom_text_2)) BetCreationScreenBottomText(text = stringResource(id = R.string.bet_creation_public_bottom_text_2))
BetCreationScreenBottomText(text = stringResource(id = R.string.private_bottom_text_3))
} }
} }
} }

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

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

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

@ -1,20 +1,22 @@
package fr.iut.alldev.allin.ui.betHistory package fr.iut.alldev.allin.ui.betHistory
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime 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 import fr.iut.alldev.allin.ui.betHistory.components.GenericHistory
@Composable @Composable
fun BetHistoryScreen( fun BetHistoryScreen(
selectBet: (Bet, Boolean) -> Unit,
viewModel: BetHistoryViewModel = hiltViewModel() viewModel: BetHistoryViewModel = hiltViewModel()
) { ) {
val bets by viewModel.bets.collectAsState() val bets by viewModel.bets.collectAsStateWithLifecycle()
GenericHistory( GenericHistory(
title = stringResource(id = R.string.bet_history_title), title = stringResource(id = R.string.bet_history_title),
bets = bets, bets = bets,
@ -25,6 +27,7 @@ fun BetHistoryScreen(
getEndBetTime = { it.bet.endBetDate.formatToTime() }, getEndBetTime = { it.bet.endBetDate.formatToTime() },
getStatus = { it.bet.betStatus }, getStatus = { it.bet.betStatus },
getNbCoins = { it.amount }, 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.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getBetHistoryPhrase import fr.iut.alldev.allin.ext.getBetHistoryPhrase
import fr.iut.alldev.allin.ext.getBetHistoryStatusColor 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider import fr.iut.alldev.allin.ui.preview.BetStatusPreviewProvider
@ -40,7 +41,7 @@ val betHistoryStatusInlineContent = mapOf(
) { ) {
Icon( Icon(
painter = AllInTheme.icons.allCoins(), painter = AllInTheme.icons.allCoins(),
tint = AllInTheme.colors.white, tint = AllInColorToken.white,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
@ -70,7 +71,7 @@ fun BetHistoryBetStatus(
appendInlineContent(betHistoryStatusModId, "[icon]") appendInlineContent(betHistoryStatusModId, "[icon]")
append(betHistoryPhrase.substringAfter("[icon]")) append(betHistoryPhrase.substringAfter("[icon]"))
}, },
color = AllInTheme.colors.white, color = AllInColorToken.white,
inlineContent = betHistoryStatusInlineContent, inlineContent = betHistoryStatusInlineContent,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontSize = 24.sp fontSize = 24.sp

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

@ -1,9 +1,10 @@
package fr.iut.alldev.allin.ui.betHistory.components package fr.iut.alldev.allin.ui.betHistory.components
import androidx.compose.foundation.layout.Arrangement 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.data.model.bet.BetStatus 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 import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
@ -27,17 +30,18 @@ fun <T> GenericHistory(
getStatus: (T) -> BetStatus, getStatus: (T) -> BetStatus,
getNbCoins: (T) -> Int, getNbCoins: (T) -> Int,
getWon: (T) -> Boolean, getWon: (T) -> Boolean,
onClick: (T) -> Unit,
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), 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), verticalArrangement = Arrangement.spacedBy(18.dp),
) { ) {
item { item {
Text( Text(
text = title, text = title,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
color = AllInTheme.colors.allInGrey, color = AllInColorToken.allInGrey,
fontSize = 24.sp, fontSize = 24.sp,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@ -53,7 +57,8 @@ fun <T> GenericHistory(
time = getEndBetTime(it), time = getEndBetTime(it),
status = getStatus(it), status = getStatus(it),
nbCoins = getNbCoins(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.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
@ -14,15 +15,14 @@ import androidx.compose.material3.SheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime 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.Bet
import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.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.theme.AllInTheme
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetBetCard import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetBetCard
import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCoinAmount import fr.iut.alldev.allin.ui.betResult.components.BetResultBottomSheetContentCoinAmount
@ -71,10 +71,11 @@ fun BetResultBottomSheetContent(
odds: Float, odds: Float,
onClose: () -> Unit onClose: () -> Unit
) { ) {
AllInMarqueeBox(backgroundBrush = AllInTheme.colors.allInMainGradientReverse) { AllInMarqueeBox(backgroundBrush = AllInColorToken.allInMainGradientReverse) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.safeContentPadding()
.padding(16.dp) .padding(16.dp)
) { ) {
IconButton( IconButton(
@ -85,16 +86,16 @@ fun BetResultBottomSheetContent(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
tint = AllInTheme.colors.white, tint = AllInColorToken.white,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
Icon( Icon(
painter = painterResource(R.drawable.allin), painter = AllInTheme.icons.logo(),
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.white, tint = AllInColorToken.white,
modifier = Modifier modifier = Modifier
.size(40.dp) .size(40.dp)
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
@ -132,7 +133,7 @@ private fun BetResultBottomSheetContentPreview() {
BetResultBottomSheetContent( BetResultBottomSheetContent(
username = "Pseudo", username = "Pseudo",
coinAmount = 3976, coinAmount = 3976,
bet = YesNoBet( bet = BinaryBet(
id = "1", id = "1",
theme = "Theme", theme = "Theme",
phrase = "Phrase", phrase = "Phrase",
@ -141,6 +142,8 @@ private fun BetResultBottomSheetContentPreview() {
isPublic = true, isPublic = true,
betStatus = BetStatus.IN_PROGRESS, betStatus = BetStatus.IN_PROGRESS,
creator = "creator", creator = "creator",
totalStakes = 0,
totalParticipants = 0
), ),
stake = 4175, stake = 4175,
winnings = 2600, winnings = 2600,

@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCard import fr.iut.alldev.allin.ui.core.AllInCard
import fr.iut.alldev.allin.ui.core.AllInCoinCount import fr.iut.alldev.allin.ui.core.AllInCoinCount
@ -30,7 +31,7 @@ fun BetResultBottomSheetBetCardStats(
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.background(AllInTheme.themeColors.background2) .background(AllInTheme.colors.background2)
.padding(horizontal = 19.dp, vertical = 11.dp), .padding(horizontal = 19.dp, vertical = 11.dp),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
@ -42,12 +43,12 @@ fun BetResultBottomSheetBetCardStats(
Text( Text(
text = stringResource(id = R.string.bet_result_stake), text = stringResource(id = R.string.bet_result_stake),
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onMainSurface color = AllInTheme.colors.onMainSurface
) )
AllInCoinCount( AllInCoinCount(
amount = stake, amount = stake,
color = AllInTheme.colors.allInPurple, color = AllInColorToken.allInPurple,
textStyle = AllInTheme.typography.sm1, textStyle = AllInTheme.typography.sm1,
position = IconPosition.TRAILING position = IconPosition.TRAILING
) )
@ -60,12 +61,12 @@ fun BetResultBottomSheetBetCardStats(
Text( Text(
text = stringResource(id = R.string.bet_result_winnings), text = stringResource(id = R.string.bet_result_winnings),
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
color = AllInTheme.colors.allInPurple color = AllInColorToken.allInPurple
) )
AllInCoinCount( AllInCoinCount(
amount = winnings, amount = winnings,
textStyle = AllInTheme.typography.sm1, textStyle = AllInTheme.typography.sm1,
brush = AllInTheme.colors.allInMainGradient, brush = AllInColorToken.allInMainGradient,
position = IconPosition.TRAILING position = IconPosition.TRAILING
) )
} }
@ -78,17 +79,17 @@ fun BetResultBottomSheetBetCardStats(
Text( Text(
text = stringResource(id = R.string.bet_result_odds), text = stringResource(id = R.string.bet_result_odds),
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
color = AllInTheme.themeColors.onBackground2 color = AllInTheme.colors.onBackground2
) )
AllInCard( AllInCard(
radius = 8.dp, radius = 8.dp,
backgroundBrush = AllInTheme.colors.allInMainGradient backgroundBrush = AllInColorToken.allInMainGradient
) { ) {
Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) { Box(Modifier.padding(vertical = 4.dp, horizontal = 8.dp)) {
Text( Text(
text = "$odds", text = "$odds",
style = AllInTheme.typography.sm1, 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R 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.theme.AllInTheme
@Composable @Composable
@ -54,18 +55,18 @@ fun BetResultBottomSheetContentCongratulations(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text( Text(
text= stringResource(id = R.string.bet_result_congratulations), text = stringResource(id = R.string.bet_result_congratulations),
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontSize = 25.sp, fontSize = 25.sp,
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
color = AllInTheme.colors.white color = AllInColorToken.white
) )
Text( Text(
text = "${username.uppercase()} !", text = "${username.uppercase()} !",
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontSize = 30.sp, fontSize = 30.sp,
fontStyle = FontStyle.Italic, 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInCoinCount import fr.iut.alldev.allin.ui.core.AllInCoinCount
import fr.iut.alldev.allin.ui.core.IconPosition import fr.iut.alldev.allin.ui.core.IconPosition
@ -33,7 +34,7 @@ fun BetResultBottomSheetContentCoinAmount(
Text( Text(
text = stringResource(id = R.string.bet_result_you_win), text = stringResource(id = R.string.bet_result_you_win),
style = AllInTheme.typography.sm2, style = AllInTheme.typography.sm2,
color = AllInTheme.colors.white, color = AllInColorToken.white,
fontSize = 20.sp fontSize = 20.sp
) )
@ -42,7 +43,7 @@ fun BetResultBottomSheetContentCoinAmount(
.border( .border(
width = 2.dp, width = 2.dp,
shape = RoundedCornerShape(100.dp), shape = RoundedCornerShape(100.dp),
color = AllInTheme.colors.white color = AllInColorToken.white
) )
.padding(vertical = 22.dp, horizontal = 33.dp) .padding(vertical = 22.dp, horizontal = 33.dp)
@ -51,7 +52,7 @@ fun BetResultBottomSheetContentCoinAmount(
amount = amount, amount = amount,
textStyle = AllInTheme.typography.h1, textStyle = AllInTheme.typography.h1,
position = IconPosition.TRAILING, position = IconPosition.TRAILING,
color = AllInTheme.colors.white, color = AllInColorToken.white,
size = 60, size = 60,
modifier = Modifier modifier = Modifier
.align(Alignment.Center) .align(Alignment.Center)
@ -61,7 +62,7 @@ fun BetResultBottomSheetContentCoinAmount(
amount = amount, amount = amount,
textStyle = AllInTheme.typography.h1, textStyle = AllInTheme.typography.h1,
position = IconPosition.TRAILING, position = IconPosition.TRAILING,
color = AllInTheme.colors.white.copy(alpha = .32f), color = AllInColorToken.white.copy(alpha = .32f),
size = 60, size = 60,
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )

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

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

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

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

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

@ -5,11 +5,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontStyle 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.data.ext.toPercentageString 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.PercentagePositionnedElement import fr.iut.alldev.allin.ui.core.PercentagePositionnedElement
import fr.iut.alldev.allin.ui.core.StatBar import fr.iut.alldev.allin.ui.core.StatBar
@ -25,7 +27,7 @@ fun BinaryStatBar(
Row { Row {
Text( Text(
text = response1, text = response1,
color = AllInTheme.colors.allInBlue, color = AllInColorToken.allInBlue,
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
fontSize = 30.sp, fontSize = 30.sp,
@ -36,7 +38,9 @@ fun BinaryStatBar(
style = AllInTheme.typography.h1, style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
fontSize = 30.sp, fontSize = 30.sp,
color = AllInTheme.colors.allInBarPink color = AllInColorToken.allInBarPink,
textAlign = TextAlign.End,
modifier = Modifier.weight(1f)
) )
} }
StatBar(percentage = response1Percentage) StatBar(percentage = response1Percentage)
@ -46,22 +50,22 @@ fun BinaryStatBar(
Text( Text(
text = response1Percentage.toPercentageString(), text = response1Percentage.toPercentageString(),
style = AllInTheme.typography.sm1, 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) override val values = sequenceOf(0f, .33f, .5f, .66f, 1f)
} }
@Preview @Preview
@Composable @Composable
private fun YesNoStatBarPreview( private fun BinaryStatBarPreview(
@PreviewParameter(YesNoStatBarPreviewProvider::class) percentage: Float, @PreviewParameter(BinaryStatBarPreviewProvider::class) percentage: Float,
) { ) {
AllInTheme { AllInTheme {
BinaryStatBar( 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.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EmojiEvents import androidx.compose.material.icons.filled.EmojiEvents
import androidx.compose.material.icons.filled.People import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.filled.WorkspacePremium import androidx.compose.material.icons.filled.WorkspacePremium
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource 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.R
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
import fr.iut.alldev.allin.data.ext.formatToTime 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.BetStatus
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.NO_VALUE 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.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.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.formatToSimple
import fr.iut.alldev.allin.ext.getDateEndLabelId import fr.iut.alldev.allin.ext.getDateEndLabelId
import fr.iut.alldev.allin.ext.getDateStartLabelId 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.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner 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.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.AllInCoinCount
import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer import fr.iut.alldev.allin.ui.core.AllInDetailsDrawer
import fr.iut.alldev.allin.ui.core.ProfilePicture import fr.iut.alldev.allin.ui.core.ProfilePicture
@ -65,27 +75,17 @@ import fr.iut.alldev.allin.vo.bet.BetDisplayer
import java.util.Locale import java.util.Locale
class BetStatusBottomSheetBetDisplayer( class BetStatusBottomSheetBetDisplayer(
val openParticipateSheet: () -> Unit val openParticipateSheet: () -> Unit,
val getImageUrl: (id: String) -> String
) : BetDisplayer { ) : BetDisplayer {
val paddingValues = mutableStateOf(PaddingValues())
@Composable @Composable
private fun DisplayBinaryBet( private fun DisplayBetDail(
betDetail: BetDetail, betDetail: BetDetail,
response1: String, currentUser: User,
response2: String, winnerColor: @Composable () -> Color,
response1Display: String = response1, statBar: LazyListScope.() -> Unit
response2Display: String = response2
) { ) {
val safeBottomPadding = paddingValues.value.calculateBottomPadding() Box(Modifier) {
val configuration = LocalConfiguration.current
val locale =
remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val response1Answer = remember { betDetail.getAnswerOfResponse(response1) }
val response2Answer = remember { betDetail.getAnswerOfResponse(response2) }
Box(Modifier.padding(bottom = safeBottomPadding)) {
Column { Column {
Column(Modifier.padding(horizontal = 20.dp)) { Column(Modifier.padding(horizontal = 20.dp)) {
BetTitleHeader( BetTitleHeader(
@ -115,98 +115,88 @@ class BetStatusBottomSheetBetDisplayer(
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
} }
if (betDetail.bet.betStatus == BetStatus.FINISHED) { if (betDetail.bet.betStatus == BetStatus.FINISHED) {
BetStatusWinner( betDetail.wonParticipation?.let { wonParticipation ->
answer = response1Display, BetStatusWinner(
color = AllInTheme.colors.allInBlue, answer = wonParticipation.response,
coinAmount = 442, color = winnerColor(),
username = "Imri", coinAmount = wonParticipation.stake,
multiplier = 1.2f username = wonParticipation.username,
) multiplier = betDetail.getAnswerOfResponse(wonParticipation.response)
?.odds ?: .5f
)
}
} else { } else {
HorizontalDivider(color = AllInTheme.themeColors.border) HorizontalDivider(color = AllInTheme.colors.border)
} }
Column( LazyColumn(
Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.background(AllInTheme.themeColors.background2) .background(AllInTheme.colors.background2)
.padding(horizontal = 20.dp) .nonLinkedScroll(),
contentPadding = bottomSheetNavigationBarsInsets().asPaddingValues(top = 20.dp, start = 20.dp, end = 20.dp)
) { ) {
Spacer(modifier = Modifier.height(20.dp))
BinaryStatBar( statBar(this)
response1Percentage = remember {
val total = (response1Answer?.totalParticipants if (betDetail.participations.isNotEmpty() || betDetail.userParticipation != null) {
?: 0) + (response2Answer?.totalParticipants ?: 0) item {
if (total == 0) .5f else (response1Answer?.totalParticipants Text(
?: 0) / total.toFloat() text = stringResource(id = R.string.bet_status_participants_list),
}, fontSize = 20.sp,
response1 = response1Display, color = AllInTheme.colors.onMainSurface,
response2 = response2Display style = AllInTheme.typography.h1,
) modifier = Modifier.padding(vertical = 36.dp)
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"}"
)
} }
Text( betDetail.userParticipation?.let {
text = stringResource(id = R.string.bet_status_participants_list), item {
fontSize = 20.sp, BetStatusParticipant(
color = AllInTheme.themeColors.onMainSurface, username = it.username,
style = AllInTheme.typography.h1, allCoinsAmount = it.stake,
modifier = Modifier.padding(vertical = 36.dp) image = getImageUrl(it.userId)
) )
LazyColumn( HorizontalDivider(
verticalArrangement = Arrangement.spacedBy(8.dp), color = AllInTheme.colors.border,
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(vertical = 8.dp, horizontal = 25.dp)
contentPadding = PaddingValues(horizontal = 13.dp, vertical = 8.dp), )
modifier = Modifier.fillMaxHeight()
) {
betDetail.userParticipation?.let {
item {
BetStatusParticipant(
username = it.username,
allCoinsAmount = it.stake
)
HorizontalDivider(
color = AllInTheme.themeColors.border,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 25.dp)
)
}
} }
items(betDetail.participations) { }
if (it.username != betDetail.userParticipation?.username) { items(betDetail.participations) {
BetStatusParticipant( if (it.username != betDetail.userParticipation?.username) {
username = it.username, BetStatusParticipant(
allCoinsAmount = it.stake 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( RainbowButton(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.padding(horizontal = 7.dp), .background(
text = stringResource(id = R.string.Participate), 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, enabled = betDetail.bet.betStatus == BetStatus.IN_PROGRESS,
onClick = openParticipateSheet 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 @Composable
override fun DisplayYesNoBet(betDetail: BetDetail) { override fun DisplayBinaryBet(betDetail: BetDetail, currentUser: User) {
DisplayBinaryBet( DisplayBetDail(
betDetail = betDetail, betDetail = betDetail,
response1 = YES_VALUE, currentUser = currentUser,
response2 = NO_VALUE, winnerColor = {
response1Display = stringResource(id = R.string.Yes).uppercase(), if (betDetail.wonParticipation?.response == YES_VALUE) AllInColorToken.allInBlue
response2Display = stringResource(id = R.string.No).uppercase() 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 @Composable
override fun DisplayMatchBet(betDetail: BetDetail) { override fun DisplayMatchBet(betDetail: BetDetail, currentUser: User) {
val bet = remember { betDetail.bet as MatchBet } val matchBet = remember { betDetail.bet as MatchBet }
DisplayBinaryBet(
DisplayBetDail(
betDetail = betDetail, betDetail = betDetail,
response1 = bet.nameTeam1, currentUser = currentUser,
response2 = bet.nameTeam2 winnerColor = {
) if (betDetail.wonParticipation?.response == matchBet.nameTeam1) AllInColorToken.allInBlue
else AllInColorToken.allInPink
}
) {
displayBinaryStatBar(
betDetail = betDetail,
response1 = matchBet.nameTeam1,
response2 = matchBet.nameTeam2
)
}
} }
@Composable @Composable
override fun DisplayCustomBet(betDetail: BetDetail) { override fun DisplayCustomBet(betDetail: BetDetail, currentUser: User) {
Text("This is a CUSTOM BET") 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 @Composable
fun BetStatusParticipant( fun BetStatusParticipant(
username: String, username: String,
image: String?,
allCoinsAmount: Int allCoinsAmount: Int
) { ) {
Row( Row(
@ -252,19 +385,23 @@ fun BetStatusParticipant(
horizontalArrangement = Arrangement.spacedBy(7.dp), horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
ProfilePicture(modifier = Modifier.size(25.dp)) ProfilePicture(
image = image,
fallback = username.asFallbackProfileUsername(),
size = 25.dp
)
Text( Text(
text = username, text = username,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
color = AllInTheme.themeColors.onMainSurface, color = AllInTheme.colors.onMainSurface,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
AllInCoinCount( AllInCoinCount(
amount = allCoinsAmount, amount = allCoinsAmount,
color = AllInTheme.colors.allInPurple color = AllInColorToken.allInPurple
) )
} }
} }
@ -276,8 +413,20 @@ private fun BetStatusBottomSheetPreview(
@PreviewParameter(BetDetailPreviewProvider::class) bet: BetDetail @PreviewParameter(BetDetailPreviewProvider::class) bet: BetDetail
) { ) {
AllInTheme { AllInTheme {
BetStatusBottomSheetBetDisplayer { BetStatusBottomSheetBetDisplayer(
openParticipateSheet = {},
}.DisplayBet(bet) 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.tooling.preview.Preview
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R 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.theme.AllInTheme
@Composable @Composable
@ -16,7 +17,7 @@ fun AllInAlertDialog(
enabled: Boolean = true, enabled: Boolean = true,
title: String, title: String,
text: String, text: String,
confirmText: String = stringResource(id = R.string.Ok), confirmText: String = stringResource(id = R.string.generic_ok),
onDismiss: () -> Unit, onDismiss: () -> Unit,
onConfirm: () -> Unit = onDismiss, onConfirm: () -> Unit = onDismiss,
) { ) {
@ -43,16 +44,16 @@ fun AllInAlertDialog(
text = confirmText, text = confirmText,
fontSize = 15.sp, fontSize = 15.sp,
style = AllInTheme.typography.h1.copy( style = AllInTheme.typography.h1.copy(
brush = AllInTheme.colors.allInMainGradient brush = AllInColorToken.allInMainGradient
) )
) )
} }
}, },
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
containerColor = AllInTheme.themeColors.mainSurface, containerColor = AllInTheme.colors.mainSurface,
titleContentColor = AllInTheme.themeColors.onMainSurface, titleContentColor = AllInTheme.colors.onMainSurface,
textContentColor = AllInTheme.themeColors.onBackground2, textContentColor = AllInTheme.colors.onBackground2,
) )
} }
} }

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

@ -1,44 +1,57 @@
package fr.iut.alldev.allin.ui.core 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.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@Composable @Composable
fun AllInButton( fun AllInButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
color: Color, color: Color,
text: String, text: String,
textColor: Color, textColor: Color,
radius: Dp = 15.dp,
onClick: () -> Unit, 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( Button(
onClick = onClick, shape = shape,
colors = ButtonDefaults.buttonColors(
containerColor = color,
disabledContainerColor = AllInTheme.colors.disabled
),
modifier = modifier, modifier = modifier,
radius = radius, enabled = enabled,
backgroundColor = color, onClick = onClick,
enabled = enabled contentPadding = if (isSmall) {
PaddingValues(horizontal = 12.dp)
} else ButtonDefaults.ContentPadding
) { ) {
Text( Text(
text = text, text = text,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.h2, style = textStyle,
color = if(enabled) textColor else AllInTheme.themeColors.disabledBorder, color = if (enabled) textColor else AllInTheme.colors.disabledBorder,
fontSize = 20.sp, fontSize = if (isSmall) 12.sp else 20.sp,
modifier = Modifier modifier = Modifier.padding(vertical = if (isSmall) 0.dp else 8.dp)
.padding(vertical = 15.dp)
.fillMaxWidth(),
) )
} }
} }
@ -48,12 +61,11 @@ fun AllInButton(
private fun AllInButtonPreview() { private fun AllInButtonPreview() {
AllInTheme { AllInTheme {
AllInButton( AllInButton(
color = AllInTheme.colors.allInLoginPurple, color = AllInColorToken.allInLoginPurple,
text = "Connexion", text = "Connexion",
textColor = Color.White textColor = Color.White,
) { onClick = { }
)
}
} }
} }
@ -62,12 +74,40 @@ private fun AllInButtonPreview() {
private fun AllInButtonDisabledPreview() { private fun AllInButtonDisabledPreview() {
AllInTheme { AllInTheme {
AllInButton( AllInButton(
color = AllInTheme.colors.allInLoginPurple, color = AllInColorToken.allInLoginPurple,
text = "Connexion", text = "Connexion",
textColor = Color.White, 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.foundation.interaction.collectIsPressedAsState
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier 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.draw.scale
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color 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 androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AllInCard( fun AllInCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
radius: Dp = 15.dp, radius: Dp = 15.dp,
shape: Shape? = null,
enabled: Boolean = true, enabled: Boolean = true,
backgroundColor: Color = AllInTheme.themeColors.background, backgroundColor: Color = AllInTheme.colors.background,
disabledBackgroundColor: Color = AllInTheme.themeColors.disabled, disabledBackgroundColor: Color = AllInTheme.colors.disabled,
backgroundBrush: Brush? = null, backgroundBrush: Brush? = null,
borderWidth: Dp? = null, borderWidth: Dp? = null,
borderColor: Color = AllInTheme.themeColors.border, borderColor: Color = AllInTheme.colors.border,
disabledBorderColor: Color = AllInTheme.themeColors.disabledBorder, disabledBorderColor: Color = AllInTheme.colors.disabledBorder,
borderBrush: Brush? = null, borderBrush: Brush? = null,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
val cardShape = AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100) val cardShape = shape ?: AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100)
val cardModifier = modifier val cardModifier = modifier
.run { .run {
backgroundBrush?.let { backgroundBrush?.let {
@ -89,15 +92,16 @@ fun AllInBouncyCard(
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
radius: Dp = 15.dp, radius: Dp = 15.dp,
enabled: Boolean = true, enabled: Boolean = true,
backgroundColor: Color = AllInTheme.themeColors.background, backgroundColor: Color = AllInTheme.colors.background,
disabledBackgroundColor: Color = AllInTheme.themeColors.disabled, disabledBackgroundColor: Color = AllInTheme.colors.disabled,
backgroundBrush: Brush? = null, backgroundBrush: Brush? = null,
borderWidth: Dp? = null, borderWidth: Dp? = null,
borderColor: Color = AllInTheme.themeColors.border, borderColor: Color = AllInTheme.colors.border,
disabledBorderColor: Color = AllInTheme.themeColors.disabledBorder, disabledBorderColor: Color = AllInTheme.colors.disabledBorder,
borderBrush: Brush? = null, borderBrush: Brush? = null,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
val haptic = LocalHapticFeedback.current
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState() val isPressed by interactionSource.collectIsPressedAsState()
@ -106,15 +110,24 @@ fun AllInBouncyCard(
animationSpec = spring( animationSpec = spring(
Spring.DampingRatioHighBouncy, Spring.DampingRatioHighBouncy,
Spring.StiffnessMediumLow Spring.StiffnessMediumLow
) ), label = ""
) )
LaunchedEffect(key1 = scale < .97f) {
if (scale < .97f) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
AllInCard( AllInCard(
modifier = modifier modifier = modifier
.combinedClickable( .let {
interactionSource = interactionSource, if (enabled) it.combinedClickable(
indication = null, interactionSource = interactionSource,
onClick = { onClick?.let { it() } } indication = null,
) onClick = { onClick?.let { it() } }
) else it
}
.scale(scale), .scale(scale),
onClick = null, onClick = null,
radius = radius, radius = radius,

@ -2,8 +2,11 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box
import androidx.compose.material3.* 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.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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 fr.iut.alldev.allin.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AllInChip( fun AllInChip(
text: String, text: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isSelected: Boolean = false, isSelected: Boolean = false,
enabled: Boolean = true,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
radius: Dp = 50.dp, radius: Dp = 50.dp,
selectedColor: Color = AllInTheme.colors.allInPurple, selectedColor: Color = AllInColorToken.allInPurple,
unselectedColor: Color = AllInTheme.themeColors.background, unselectedColor: Color = AllInTheme.colors.background,
) { ) {
Card( Card(
modifier = modifier, modifier = modifier,
shape = AbsoluteSmoothCornerShape(radius, 100), shape = AbsoluteSmoothCornerShape(radius, 100),
onClick = onClick, 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( 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 = text, text = text,
modifier = Modifier modifier = Modifier
@ -45,7 +51,7 @@ fun AllInChip(
.alpha(if (isSelected) 0f else 1f), .alpha(if (isSelected) 0f else 1f),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.p1, style = AllInTheme.typography.p1,
color = AllInTheme.themeColors.onBackground2 color = AllInTheme.colors.onBackground2
) )
if (isSelected) { if (isSelected) {
Text( Text(
@ -53,8 +59,7 @@ fun AllInChip(
modifier = modifier.align(Alignment.Center), modifier = modifier.align(Alignment.Center),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.h1, 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.graphics.Color
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
@ -14,7 +15,7 @@ fun AllInCoinCount(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
amount: Int, amount: Int,
color: Color? = null, color: Color? = null,
brush: Brush?= null, brush: Brush? = null,
size: Int = 15, size: Int = 15,
textStyle: TextStyle = AllInTheme.typography.h1, textStyle: TextStyle = AllInTheme.typography.h1,
position: IconPosition = IconPosition.TRAILING position: IconPosition = IconPosition.TRAILING
@ -36,6 +37,6 @@ fun AllInCoinCount(
@Composable @Composable
private fun AllInCoinCountPreview() { private fun AllInCoinCountPreview() {
AllInTheme { 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.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import fr.iut.alldev.allin.R 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.theme.AllInTheme
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.* import java.util.*
@ -39,9 +40,9 @@ fun AllInDatePicker(
} }
) { ) {
Text( Text(
text = stringResource(id = R.string.Validate), text = stringResource(id = R.string.generic_validate),
style = AllInTheme.typography.h1.copy( style = AllInTheme.typography.h1.copy(
brush = AllInTheme.colors.allInMainGradient brush = AllInColorToken.allInMainGradient
) )
) )
} }
@ -49,37 +50,37 @@ fun AllInDatePicker(
dismissButton = { dismissButton = {
TextButton(onClick = onDismiss) { TextButton(onClick = onDismiss) {
Text( Text(
text = stringResource(id = R.string.Cancel), text = stringResource(id = R.string.generic_cancel),
color = AllInTheme.themeColors.onBackground2, color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.sm1 style = AllInTheme.typography.sm1
) )
} }
}, },
colors = DatePickerDefaults.colors( colors = DatePickerDefaults.colors(
containerColor = AllInTheme.themeColors.mainSurface containerColor = AllInTheme.colors.mainSurface
) )
) { ) {
DatePicker( DatePicker(
state = datePickerState, state = datePickerState,
colors = DatePickerDefaults.colors( colors = DatePickerDefaults.colors(
todayDateBorderColor = AllInTheme.colors.allInBlue, todayDateBorderColor = AllInColorToken.allInBlue,
selectedDayContainerColor = AllInTheme.colors.allInBlue, selectedDayContainerColor = AllInColorToken.allInBlue,
todayContentColor = AllInTheme.colors.allInBlue, todayContentColor = AllInColorToken.allInBlue,
dayContentColor = AllInTheme.colors.allInBlue, dayContentColor = AllInColorToken.allInBlue,
dividerColor = AllInTheme.themeColors.border, dividerColor = AllInTheme.colors.border,
containerColor = AllInTheme.themeColors.background, containerColor = AllInTheme.colors.background,
titleContentColor = AllInTheme.themeColors.onMainSurface, titleContentColor = AllInTheme.colors.onMainSurface,
headlineContentColor = AllInTheme.themeColors.onMainSurface, headlineContentColor = AllInTheme.colors.onMainSurface,
subheadContentColor = AllInTheme.themeColors.onBackground2, subheadContentColor = AllInTheme.colors.onBackground2,
dateTextFieldColors = OutlinedTextFieldDefaults.colors( dateTextFieldColors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = AllInTheme.themeColors.mainSurface, focusedContainerColor = AllInTheme.colors.mainSurface,
unfocusedContainerColor = AllInTheme.themeColors.mainSurface, unfocusedContainerColor = AllInTheme.colors.mainSurface,
focusedBorderColor = AllInTheme.colors.allInPurple, focusedBorderColor = AllInColorToken.allInPurple,
unfocusedBorderColor = AllInTheme.themeColors.border, unfocusedBorderColor = AllInTheme.colors.border,
focusedTextColor = AllInTheme.themeColors.onMainSurface, focusedTextColor = AllInTheme.colors.onMainSurface,
unfocusedTextColor = AllInTheme.themeColors.onMainSurface, unfocusedTextColor = AllInTheme.colors.onMainSurface,
focusedLabelColor = AllInTheme.colors.allInPurple, focusedLabelColor = AllInColorToken.allInPurple,
unfocusedLabelColor = AllInTheme.themeColors.onMainSurface, unfocusedLabelColor = AllInTheme.colors.onMainSurface,
) )
) )
) )

@ -32,8 +32,8 @@ import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
fun AllInDetailsDrawer( fun AllInDetailsDrawer(
text: String = stringResource(id = R.string.Details), text: String = stringResource(id = R.string.bet_status_details_drawer),
textColor: Color = AllInTheme.themeColors.onBackground2, textColor: Color = AllInTheme.colors.onBackground2,
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
) { ) {
val interactionSource = remember { MutableInteractionSource() } 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.ext.shadow 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.theme.AllInTheme
@Composable @Composable
@ -23,8 +24,8 @@ fun AllInGradientButton(
modifier = modifier modifier = modifier
.shadow( .shadow(
colors = listOf( colors = listOf(
AllInTheme.colors.allInPink, AllInColorToken.allInPink,
AllInTheme.colors.allInBlue AllInColorToken.allInBlue
), ),
blurRadius = 20.dp, blurRadius = 20.dp,
alpha = .5f, alpha = .5f,
@ -32,13 +33,13 @@ fun AllInGradientButton(
) )
.fillMaxWidth(), .fillMaxWidth(),
radius = 10.dp, radius = 10.dp,
backgroundBrush = AllInTheme.colors.allInMainGradient backgroundBrush = AllInColorToken.allInMainGradient
) { ) {
Text( Text(
text = text, text = text,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
color = AllInTheme.colors.white, color = AllInColorToken.white,
fontSize = 20.sp, fontSize = 20.sp,
modifier = Modifier modifier = Modifier
.padding(vertical = 15.dp) .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.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.theme.AllInTheme
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AllInIconChip( fun AllInIconChip(
text: String, text: String,
@ -29,16 +29,16 @@ fun AllInIconChip(
isSelected: Boolean = false, isSelected: Boolean = false,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
radius: Dp = 15.dp, radius: Dp = 15.dp,
selectedColor: Color = AllInTheme.colors.allInPurple, selectedColor: Color = AllInColorToken.allInPurple,
unselectedColor: Color = AllInTheme.themeColors.background, unselectedColor: Color = AllInTheme.colors.background,
leadingIcon: ImageVector, leadingIcon: ImageVector,
) { ) {
val contentColor = if (isSelected) AllInTheme.colors.white else selectedColor val contentColor = if (isSelected) AllInColorToken.white else selectedColor
Card( Card(
modifier = modifier, modifier = modifier,
shape = AbsoluteSmoothCornerShape(radius, 100), shape = AbsoluteSmoothCornerShape(radius, 100),
onClick = onClick, 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( colors = CardDefaults.cardColors(
containerColor = if (isSelected) selectedColor else unselectedColor 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.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import fr.iut.alldev.allin.theme.AllInColorToken
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.abs import kotlin.math.abs
@ -50,7 +47,7 @@ import kotlin.math.max
fun AllInLoading( fun AllInLoading(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
visible: Boolean, visible: Boolean,
brush: Brush = AllInTheme.colors.allInMainGradient, brush: Brush = AllInColorToken.allInMainGradient,
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
AnimatedVisibility( AnimatedVisibility(
@ -58,34 +55,23 @@ fun AllInLoading(
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut() exit = fadeOut()
) { ) {
Dialog( Box(
onDismissRequest = {}, modifier = modifier
properties = DialogProperties( .fillMaxSize()
dismissOnBackPress = false, .clickable(
dismissOnClickOutside = false, interactionSource = interactionSource,
decorFitsSystemWindows = false, indication = null,
usePlatformDefaultWidth = false onClick = {}
)
) {
(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
) )
} .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 @Composable
fun AllInCircularProgressIndicator( fun AllInCircularProgressIndicator(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
brush: Brush = AllInTheme.colors.allInMainGradient, brush: Brush = AllInColorToken.allInMainGradient,
strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth, strokeWidth: Dp = ProgressIndicatorDefaults.CircularStrokeWidth,
strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap, strokeCap: StrokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
) { ) {
@ -111,7 +97,7 @@ fun AllInCircularProgressIndicator(
durationMillis = RotationDuration * RotationsPerCycle, durationMillis = RotationDuration * RotationsPerCycle,
easing = LinearEasing easing = LinearEasing
) )
) ), label = ""
) )
val baseRotation = transition.animateFloat( val baseRotation = transition.animateFloat(
0f, 0f,

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

@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@Composable @Composable
@ -16,9 +17,9 @@ fun AllInRadioButton(
) { ) {
AllInCard( AllInCard(
radius = 100.dp, radius = 100.dp,
borderColor = AllInTheme.colors.allInMint, borderColor = AllInColorToken.allInMint,
borderWidth = if (!checked) 1.dp else null, 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) modifier = modifier.size(12.dp)
) {} ) {}
} }
@ -35,7 +36,7 @@ private fun AllInRadioButtonNotCheckedPreview() {
@Composable @Composable
private fun AllInRadioButtonNotCheckedFilledPreview() { private fun AllInRadioButtonNotCheckedFilledPreview() {
AllInTheme { 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.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.theme.AllInTheme
@Composable @Composable
@ -39,7 +40,7 @@ fun AllInRetractableCard(
AllInCard( AllInCard(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
borderWidth = borderWidth, borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(.5f) borderColor = AllInColorToken.allInPurple.copy(.5f)
) { ) {
Column( Column(
Modifier.animateContentSize() Modifier.animateContentSize()
@ -60,10 +61,10 @@ fun AllInRetractableCard(
query = boldText, query = boldText,
highlightStyle = SpanStyle( highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AllInTheme.themeColors.onMainSurface, color = AllInTheme.colors.onMainSurface,
fontStyle = AllInTheme.typography.h2.fontStyle fontStyle = AllInTheme.typography.h2.fontStyle
), ),
color = AllInTheme.themeColors.onBackground2, color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.p1, style = AllInTheme.typography.p1,
fontSize = 16.sp, fontSize = 16.sp,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
@ -73,13 +74,13 @@ fun AllInRetractableCard(
if (isOpen) ExpandLess else ExpandMore if (isOpen) ExpandLess else ExpandMore
}, },
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.allInPurple, tint = AllInColorToken.allInPurple,
modifier = Modifier.size(30.dp) modifier = Modifier.size(30.dp)
) )
} }
AnimatedVisibility(isOpen) { AnimatedVisibility(isOpen) {
Column { Column {
HorizontalDivider(color = AllInTheme.themeColors.border) HorizontalDivider(color = AllInTheme.colors.border)
content() content()
} }
} }

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

@ -2,16 +2,24 @@ package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.* import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* 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.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Text 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -20,27 +28,47 @@ import kotlinx.coroutines.launch
class SectionElement( class SectionElement(
val text: String, val text: String,
val content: @Composable ()->Unit val content: @Composable () -> Unit
) )
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun AllInSections( fun AllInSections(
sections: List<SectionElement>, sections: List<SectionElement>,
interSectionsPadding: Dp = 56.dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onLoadSection: ()->Unit = {} interSectionsPadding: Dp = 56.dp,
onLoadSection: () -> Unit = { }
) { ) {
val pagerState = rememberPagerState(pageCount = { val pagerState = rememberPagerState(pageCount = { sections.size })
sections.size
})
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
Column(
modifier = modifier, Box(modifier = modifier) {
horizontalAlignment = Alignment.CenterHorizontally HorizontalPager(state = pagerState) { page ->
){ LaunchedEffect(key1 = page) { onLoadSection() }
LazyRow( Box(
horizontalArrangement = Arrangement.spacedBy(interSectionsPadding) 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 -> itemsIndexed(sections) { index, section ->
AllInSectionButton( 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 androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
@ -66,13 +67,13 @@ private fun AllInSelectionLine(
Icon( Icon(
imageVector = it, imageVector = it,
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.allInPurple, tint = AllInColorToken.allInPurple,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
) )
} }
Text( Text(
text = text, text = text,
color = AllInTheme.colors.allInPurple, color = AllInColorToken.allInPurple,
style = AllInTheme.typography.h2, style = AllInTheme.typography.h2,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
@ -81,7 +82,7 @@ private fun AllInSelectionLine(
Icon( Icon(
imageVector = trailingIcon, imageVector = trailingIcon,
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.allInPurple, tint = AllInColorToken.allInPurple,
modifier = Modifier modifier = Modifier
.size(30.dp) .size(30.dp)
) )
@ -105,7 +106,7 @@ fun AllInSelectionBox(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
radius = 10.dp, radius = 10.dp,
borderWidth = borderWidth, borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f) borderColor = AllInColorToken.allInPurple.copy(alpha = .42f)
) { ) {
Column( Column(
Modifier.animateContentSize() Modifier.animateContentSize()
@ -125,7 +126,7 @@ fun AllInSelectionBox(
) )
AnimatedVisibility(isOpen) { AnimatedVisibility(isOpen) {
Column { Column {
HorizontalDivider(color = AllInTheme.themeColors.border) HorizontalDivider(color = AllInTheme.colors.border)
elements.filter { it != selected }.forEach { element -> elements.filter { it != selected }.forEach { element ->
AllInSelectionLine( AllInSelectionLine(
text = stringResource(id = element.textId), text = stringResource(id = element.textId),
@ -149,9 +150,9 @@ fun AllInSelectionBox(
private fun AllInSelectionBoxClosedPreview() { private fun AllInSelectionBoxClosedPreview() {
AllInTheme { AllInTheme {
val elements = listOf( val elements = listOf(
SelectionElement(R.string.yes_no, Icons.AutoMirrored.Default.HelpOutline), SelectionElement(R.string.bet_type_binary, Icons.AutoMirrored.Default.HelpOutline),
SelectionElement(R.string.sport_match, Icons.Default.SportsFootball), SelectionElement(R.string.bet_type_match, Icons.Default.SportsFootball),
SelectionElement(R.string.custom_answers, Icons.Default.PinEnd) SelectionElement(R.string.bet_type_custom, Icons.Default.PinEnd)
) )
AllInSelectionBox( AllInSelectionBox(
isOpen = false, isOpen = false,
@ -188,7 +189,7 @@ private fun AllInSelectionLine(
Icon( Icon(
imageVector = trailingIcon, imageVector = trailingIcon,
contentDescription = null, contentDescription = null,
tint = AllInTheme.colors.allInPurple, tint = AllInColorToken.allInPurple,
modifier = Modifier modifier = Modifier
.size(30.dp) .size(30.dp)
) )
@ -211,7 +212,7 @@ fun AllInSelectionBox(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
radius = 10.dp, radius = 10.dp,
borderWidth = borderWidth, borderWidth = borderWidth,
borderColor = AllInTheme.colors.allInPurple.copy(alpha = .42f) borderColor = AllInColorToken.allInPurple.copy(alpha = .42f)
) { ) {
Column(Modifier.animateContentSize()) { Column(Modifier.animateContentSize()) {
AllInSelectionLine( AllInSelectionLine(
@ -224,7 +225,7 @@ fun AllInSelectionBox(
) )
AnimatedVisibility(isOpen) { AnimatedVisibility(isOpen) {
Column { Column {
HorizontalDivider(color = AllInTheme.themeColors.border) HorizontalDivider(color = AllInTheme.colors.border)
elements.filter { it != selected }.forEach { element -> elements.filter { it != selected }.forEach { element ->
AllInSelectionLine( AllInSelectionLine(
element = element, element = element,
@ -247,9 +248,9 @@ fun AllInSelectionBox(
private fun AllInSelectionBoxOpenPreview() { private fun AllInSelectionBoxOpenPreview() {
AllInTheme { AllInTheme {
val elements = listOf( val elements = listOf(
SelectionElement(R.string.yes_no, Icons.AutoMirrored.Default.HelpOutline), SelectionElement(R.string.bet_type_binary, Icons.AutoMirrored.Default.HelpOutline),
SelectionElement(R.string.sport_match, Icons.Default.SportsFootball), SelectionElement(R.string.bet_type_match, Icons.Default.SportsFootball),
SelectionElement(R.string.custom_answers, Icons.Default.Edit) SelectionElement(R.string.bet_type_custom, Icons.Default.Edit)
) )
AllInSelectionBox( AllInSelectionBox(
isOpen = true, isOpen = true,

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

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

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

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

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

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

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

@ -11,6 +11,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.theme.AllInTheme
@Composable @Composable
@ -19,7 +20,7 @@ fun RainbowButton(
text: String, text: String,
onClick: () -> Unit, onClick: () -> Unit,
enabled: Boolean = true, enabled: Boolean = true,
rippleColor: Color = AllInTheme.colors.allInBlue, rippleColor: Color = AllInColorToken.allInBlue,
) { ) {
AllInRipple(rippleColor) { AllInRipple(rippleColor) {
AllInCard( AllInCard(
@ -32,11 +33,11 @@ fun RainbowButton(
with(AllInTheme.typography.h2) { with(AllInTheme.typography.h2) {
if (enabled) { if (enabled) {
copy( copy(
brush = AllInTheme.colors.allInTextGradient brush = AllInColorToken.allInTextGradient
) )
} else { } else {
copy( 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.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R 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.theme.AllInTheme
@Composable @Composable
fun StatBar( fun StatBar(
percentage: Float, 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 Box(modifier = modifier) {
val radius0percent = if (percentage == 0f) 50 else 0
Box {
Row( Row(
Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.height(20.dp) .height(20.dp)
.fillMaxWidth(percentage) .fillMaxWidth(percentage)
.padding(end = if (percentage == 1f) 25.dp else 0.dp)
.clip( .clip(
AbsoluteRoundedCornerShape( AbsoluteRoundedCornerShape(
topLeftPercent = 50, topLeftPercent = 50,
bottomLeftPercent = 50, bottomLeftPercent = 50,
topRightPercent = radius100percent, topRightPercent = 0,
bottomRightPercent = radius100percent bottomRightPercent = 0
) )
) )
.background(AllInTheme.colors.allInBar1stGradient) .background(leadingBrush)
) )
if (percentage != 0f && percentage != 1f) {
Spacer(modifier = Modifier.width(15.dp))
}
Box( Box(
modifier = Modifier modifier = Modifier
.height(20.dp) .height(20.dp)
.fillMaxWidth() .fillMaxWidth()
.padding(start = if (percentage == 0f) 25.dp else 15.dp)
.clip( .clip(
AbsoluteRoundedCornerShape( AbsoluteRoundedCornerShape(
topLeftPercent = radius0percent, topLeftPercent = 0,
bottomLeftPercent = radius0percent, bottomLeftPercent = 0,
topRightPercent = 50, topRightPercent = 50,
bottomRightPercent = 50 bottomRightPercent = 50
) )
) )
.background(AllInTheme.colors.allInBar2ndGradient) .background(trailingBrush)
) )
} }
PercentagePositionnedElement(percentage = percentage) { PercentagePositionnedElement(percentage = percentage) {
when (percentage) { icon?.invoke() ?: when (percentage) {
0f -> Icon( 0f -> Icon(
painter = painterResource(id = R.drawable.fire_solid), painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allInBarPink, tint = AllInColorToken.allInPink,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) )
1f -> Icon( 1f -> Icon(
painter = painterResource(id = R.drawable.fire_solid), painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allInBarPurple, tint = AllInColorToken.allInPurple,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp) 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.data.model.bet.BetStatus
import fr.iut.alldev.allin.ext.getDateStartLabelId import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBouncyCard
import fr.iut.alldev.allin.ui.core.AllInCard import fr.iut.alldev.allin.ui.core.AllInCard
@Composable @Composable
@ -51,7 +52,48 @@ fun BetCard(
} }
HorizontalDivider( HorizontalDivider(
thickness = 1.dp, 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() content()
} }

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

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

@ -1,37 +1,33 @@
package fr.iut.alldev.allin.ui.core.bet package fr.iut.alldev.allin.ui.core.bet
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.ext.asFallbackProfileUsername
import fr.iut.alldev.allin.ui.core.ProfilePicture
import fr.iut.alldev.allin.theme.AllInTheme import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.ProfilePicture
@Composable @Composable
fun BetProfilePictureRow( fun BetProfilePictureRow(
pictures: List<Painter?>, pictures: List<Pair<String, String?>>,
maxLength: Int = 5, modifier: Modifier = Modifier,
modifier: Modifier = Modifier maxLength: Int = 5
) { ) {
val nRepeat = remember{ val nRepeat = remember{ pictures.size.coerceAtMost(maxLength) }
if (pictures.size > maxLength) maxLength else pictures.size
}
Box( Box(modifier.width((35 + (nRepeat - 1) * 17).dp)){
modifier.width((nRepeat*17).dp) pictures.take(nRepeat).forEachIndexed { index, (username, image) ->
){
pictures.forEachIndexed { index, painter ->
ProfilePicture( ProfilePicture(
image = painter, fallback = username.asFallbackProfileUsername(),
image = image,
size = 35.dp, size = 35.dp,
modifier = Modifier modifier = Modifier
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
@ -46,24 +42,33 @@ fun BetProfilePictureRow(
@Composable @Composable
private fun BetProfilePictureRowPreview() { private fun BetProfilePictureRowPreview() {
AllInTheme { AllInTheme {
BetProfilePictureRow(pictures = listOf( Box(
painterResource(id = R.drawable.money_with_wings), modifier = Modifier.fillMaxWidth(),
null, contentAlignment = Alignment.Center
painterResource(id = R.drawable.money_with_wings) ) {
)) BetProfilePictureRow(
pictures = listOf("lucas" to null)
)
}
} }
} }
@Preview @Preview
@Composable @Composable
private fun BetProfilePictureRowMaxPreview() { private fun BetProfilePictureRowMaxPreview() {
AllInTheme { AllInTheme {
BetProfilePictureRow(pictures = listOf( Box(
painterResource(id = R.drawable.money_with_wings), modifier = Modifier.fillMaxWidth(),
null, contentAlignment = Alignment.Center
painterResource(id = R.drawable.money_with_wings), ) {
null, BetProfilePictureRow(
painterResource(id = R.drawable.money_with_wings), pictures = listOf(
null "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) Modifier.align(Alignment.End)
) { ) {
HighlightedText( HighlightedText(
text = stringResource(id = R.string.Proposed_by_x, creator), text = stringResource(id = R.string.bet_Proposed_by_format, creator),
query = creator, query = creator,
highlightStyle = SpanStyle( highlightStyle = SpanStyle(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AllInTheme.themeColors.onMainSurface color = AllInTheme.colors.onMainSurface
), ),
fontSize = 12.sp, fontSize = 12.sp,
style = AllInTheme.typography.p2, style = AllInTheme.typography.p2,
color = AllInTheme.themeColors.onBackground2 color = AllInTheme.colors.onBackground2
) )
} }
Spacer(modifier = Modifier.height(11.dp)) Spacer(modifier = Modifier.height(11.dp))
Text( Text(
text = category, text = category,
fontSize = 15.sp, fontSize = 15.sp,
color = AllInTheme.themeColors.onBackground2, color = AllInTheme.colors.onBackground2,
style = AllInTheme.typography.sm2 style = AllInTheme.typography.sm2
) )
Text( Text(
text = title, text = title,
fontSize = 20.sp, fontSize = 20.sp,
color = AllInTheme.themeColors.onMainSurface, color = AllInTheme.colors.onMainSurface,
style = AllInTheme.typography.h1 style = AllInTheme.typography.h1
) )
} }

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

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