Custom bet creation
continuous-integration/drone/push Build is passing Details

pull/5/head
avalin 8 months ago
parent 38a42582f3
commit 0005994ea7

@ -37,6 +37,8 @@ sealed class FieldErrorState(
data class DateOrder(val fieldName1: String, val fieldName2: String) :
FieldErrorState(R.string.field_error_date_order, fieldName1, fieldName2)
data object NoResponse :
FieldErrorState(R.string.field_error_no_response)
@Composable
fun errorResource() = stringResourceOrNull(id = messageId, *messageArgs)

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

@ -47,6 +47,13 @@ fun WindowInsets.takeBottomOnly(): WindowInsets {
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

@ -7,15 +7,17 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
@ -51,7 +53,12 @@ import fr.iut.alldev.allin.data.model.bet.NO_VALUE
import fr.iut.alldev.allin.data.model.bet.YES_VALUE
import fr.iut.alldev.allin.data.model.bet.vo.BetAnswerDetail
import fr.iut.alldev.allin.data.model.bet.vo.BetDetail
import fr.iut.alldev.allin.ext.asPaddingValues
import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsInsets
import fr.iut.alldev.allin.ext.fadingEdges
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.ext.nonLinkedScroll
import fr.iut.alldev.allin.ext.takeTopOnly
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.core.AllInBottomSheet
@ -148,6 +155,7 @@ fun ConfirmationAnswers(
) {
val configuration = LocalConfiguration.current
val locale = remember { ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() }
val scrollState = rememberScrollState()
val possibleAnswers = remember {
when (val bet = betDetail.bet) {
@ -157,10 +165,14 @@ fun ConfirmationAnswers(
}
}
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.nonLinkedScroll()
.verticalScroll(scrollState)
.fadingEdges(scrollState)
) {
itemsIndexed(possibleAnswers) { idx, it ->
possibleAnswers.forEachIndexed { idx, it ->
betDetail.getAnswerOfResponse(it)?.let {
val opacity by animateFloatAsState(
targetValue = if (selectedAnswer != null && selectedAnswer != it.response) .5f else 1f,
@ -182,6 +194,9 @@ fun ConfirmationAnswers(
)
}
}
Spacer(
modifier = Modifier.padding(bottomSheetNavigationBarsInsets().asPaddingValues(bottom = 56.dp))
)
}
}
@ -197,7 +212,11 @@ fun BetConfirmationBottomSheetContent(
Box(
modifier = Modifier
.fillMaxSize()
.safeContentPadding()
.padding(
WindowInsets.safeContent
.takeTopOnly()
.asPaddingValues()
)
.padding(16.dp)
) {
IconButton(
@ -283,6 +302,7 @@ fun BetConfirmationBottomSheetContent(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.safeContentPadding()
)
}
}

@ -13,6 +13,7 @@ import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.ext.getIcon
import fr.iut.alldev.allin.ext.getTitleId
import fr.iut.alldev.allin.ui.betCreation.BetCreationViewModel.BetTypeState.Companion.typeState
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenContent
import fr.iut.alldev.allin.ui.core.AllInAlertDialog
import fr.iut.alldev.allin.ui.core.AllInDatePicker
@ -42,6 +43,7 @@ fun BetCreationScreen(
val phraseError by remember { viewModel.phraseError }
val registerDateError by remember { viewModel.registerDateError }
val betDateError by remember { viewModel.betDateError }
val typeError by remember { viewModel.typeError }
val friends by viewModel.friends.collectAsStateWithLifecycle()
@ -63,7 +65,7 @@ fun BetCreationScreen(
}
LaunchedEffect(key1 = selectedBetTypeElement) {
selectedBetType = betTypes[selectionElements.indexOf(selectedBetTypeElement)]
selectedBetType = betTypes[selectionElements.indexOf(selectedBetTypeElement)].typeState()
}
val (showRegisterDatePicker, setRegisterDatePicker) = remember { mutableStateOf(false) }
@ -85,6 +87,7 @@ fun BetCreationScreen(
registerDateError = registerDateError.errorResource(),
betDate = betDate,
betDateError = betDateError.errorResource(),
typeError = typeError.errorResource(),
friends = friends,
selectedFriends = selectedFriends,
setRegisterDateDialog = setRegisterDatePicker,
@ -95,6 +98,8 @@ fun BetCreationScreen(
selectedBetType = selectedBetType,
setSelectedBetTypeElement = { selectedBetTypeElement = it },
selectionBetType = selectionElements,
addAnswer = { viewModel.addAnswer(it) },
deleteAnswer = { viewModel.deleteAnswer(it) },
onCreateBet = {
viewModel.createBet(
themeFieldName = themeFieldName,

@ -6,8 +6,11 @@ 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.model.bet.BetFactory
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.data.model.bet.BinaryBet
import fr.iut.alldev.allin.data.model.bet.CustomBet
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.repository.BetRepository
import fr.iut.alldev.allin.data.repository.FriendRepository
import fr.iut.alldev.allin.data.repository.UserRepository
@ -34,12 +37,13 @@ class BetCreationViewModel @Inject constructor(
val registerDate = mutableStateOf(ZonedDateTime.now())
val betDate = mutableStateOf(ZonedDateTime.now())
var isPublic = mutableStateOf(true)
var selectedBetType = mutableStateOf(BetType.BINARY)
var selectedBetType = mutableStateOf<BetTypeState>(BetTypeState.Binary)
val themeError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val phraseError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val registerDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val betDateError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
val typeError = mutableStateOf<FieldErrorState>(FieldErrorState.NoError)
private val _friends by lazy { MutableStateFlow<List<User>>(emptyList()) }
val friends get() = _friends.asStateFlow()
@ -59,6 +63,7 @@ class BetCreationViewModel @Inject constructor(
phraseError.value = FieldErrorState.NoError
registerDateError.value = FieldErrorState.NoError
betDateError.value = FieldErrorState.NoError
typeError.value = FieldErrorState.NoError
hasError.value = false
}
@ -94,6 +99,19 @@ class BetCreationViewModel @Inject constructor(
)
hasError.value = true
}
when (val state = selectedBetType.value) {
BetTypeState.Binary -> Unit
is BetTypeState.Custom -> if (state.possibleAnswers.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
}
}
}
@ -116,19 +134,58 @@ class BetCreationViewModel @Inject constructor(
if (!hasError.value) {
try {
userRepository.currentUserState.value?.let { currentUser ->
val bet = BetFactory.createBet(
id = "",
betType = selectedBetType.value,
theme = theme.value,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
nameTeam1 = "",
nameTeam2 = "",
possibleAnswers = listOf(),
creator = currentUser.username
)
val bet =
when (val type = selectedBetType.value) {
BetTypeState.Binary -> {
BinaryBet(
id = "",
theme = theme.value,
creator = currentUser.username,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
betStatus = BetStatus.WAITING,
totalStakes = 0,
totalParticipants = 0
)
}
is BetTypeState.Match -> {
MatchBet(
id = "",
theme = theme.value,
creator = currentUser.username,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
betStatus = BetStatus.WAITING,
totalStakes = 0,
totalParticipants = 0,
nameTeam1 = type.team1,
nameTeam2 = type.team2
)
}
is BetTypeState.Custom -> {
CustomBet(
id = "",
theme = theme.value,
creator = currentUser.username,
phrase = phrase.value,
endRegisterDate = registerDate.value,
endBetDate = betDate.value,
isPublic = isPublic.value,
betStatus = BetStatus.WAITING,
totalStakes = 0,
totalParticipants = 0,
possibleAnswers = type.possibleAnswers
)
}
}
betRepository.createBet(bet, keystoreManager.getTokenOrEmpty())
onSuccess()
} ?: onError()
@ -140,4 +197,40 @@ class BetCreationViewModel @Inject constructor(
setLoading(false)
}
}
fun addAnswer(value: String) {
viewModelScope.launch {
selectedBetType.value.let {
if (it is BetTypeState.Custom) {
selectedBetType.value = BetTypeState.Custom(possibleAnswers = it.possibleAnswers + value)
}
}
}
}
fun deleteAnswer(value: String) {
viewModelScope.launch {
selectedBetType.value.let {
if (it is BetTypeState.Custom) {
selectedBetType.value = BetTypeState.Custom(possibleAnswers = it.possibleAnswers - value)
}
}
}
}
sealed class BetTypeState(val type: BetType) {
data object Binary : BetTypeState(type = BetType.BINARY)
data class Match(val team1: String, val team2: String) : BetTypeState(type = BetType.MATCH)
data class Custom(val possibleAnswers: List<String>) : BetTypeState(type = BetType.CUSTOM)
companion object {
fun BetType.typeState() =
when (this) {
BetType.BINARY -> Binary
BetType.MATCH -> Match(team1 = "", team2 = "")
BetType.CUSTOM -> Custom(emptyList())
}
}
}
}

@ -18,8 +18,8 @@ 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.data.model.bet.BetType
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
@ -49,9 +49,12 @@ fun BetCreationScreenContent(
setRegisterTimeDialog: (Boolean) -> Unit,
setEndTimeDialog: (Boolean) -> Unit,
selectedBetTypeElement: SelectionElement?,
selectedBetType: BetType,
selectedBetType: BetCreationViewModel.BetTypeState,
typeError: String?,
setSelectedBetTypeElement: (SelectionElement) -> Unit,
selectionBetType: List<SelectionElement>,
addAnswer: (String) -> Unit,
deleteAnswer: (String) -> Unit,
onCreateBet: () -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
@ -90,7 +93,10 @@ fun BetCreationScreenContent(
selectedBetType = selectedBetType,
selected = selectedBetTypeElement,
setSelected = setSelectedBetTypeElement,
elements = selectionBetType
elements = selectionBetType,
addAnswer = addAnswer,
deleteAnswer = deleteAnswer,
typeError = typeError
)
}
)
@ -128,6 +134,7 @@ private fun BetCreationScreenContentPreview() {
betThemeError = null,
setBetTheme = { },
betPhrase = "Bryon",
typeError = null,
betPhraseError = null,
setBetPhrase = { },
isPublic = false,
@ -142,10 +149,12 @@ private fun BetCreationScreenContentPreview() {
setRegisterTimeDialog = { },
setEndTimeDialog = { },
selectedBetTypeElement = null,
selectedBetType = BetType.BINARY,
selectedBetType = BetCreationViewModel.BetTypeState.Binary,
setSelectedBetTypeElement = { },
selectionBetType = listOf(),
onCreateBet = { }
onCreateBet = { },
addAnswer = { },
deleteAnswer = { }
)
}
}

@ -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 = { }
)
}
}

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

@ -22,16 +22,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
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.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.User
import fr.iut.alldev.allin.ext.nonLinkedScroll
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenBottomText
import fr.iut.alldev.allin.ui.betCreation.components.BetCreationScreenFriendLine
@ -108,13 +105,7 @@ fun QuestionTabPrivacySection(
LazyColumn(
modifier = Modifier
.heightIn(max = 440.dp)
.nestedScroll(object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
) = available.copy(x = 0f)
})
.nonLinkedScroll()
) {
itemsIndexed(friends, key = { _, it -> it.id }) { idx, it ->
var isSelected by remember {

@ -27,13 +27,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
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.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@ -60,6 +56,7 @@ import fr.iut.alldev.allin.ext.bottomSheetNavigationBarsInsets
import fr.iut.alldev.allin.ext.formatToSimple
import fr.iut.alldev.allin.ext.getDateEndLabelId
import fr.iut.alldev.allin.ext.getDateStartLabelId
import fr.iut.alldev.allin.ext.nonLinkedScroll
import fr.iut.alldev.allin.theme.AllInColorToken
import fr.iut.alldev.allin.theme.AllInTheme
import fr.iut.alldev.allin.ui.betStatus.components.BetStatusWinner
@ -134,13 +131,7 @@ class BetStatusBottomSheetBetDisplayer(
modifier = Modifier
.fillMaxHeight()
.background(AllInTheme.colors.background2)
.nestedScroll(object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
) = available.copy(x = 0f)
}),
.nonLinkedScroll(),
contentPadding = bottomSheetNavigationBarsInsets().asPaddingValues(top = 20.dp, start = 20.dp, end = 20.dp)
) {

@ -8,6 +8,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -29,9 +30,10 @@ fun AllInButton(
enabled: Boolean = true,
isSmall: Boolean = false,
radius: Dp = 10.dp,
shape: Shape = AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100)
) {
Button(
shape = AbsoluteSmoothCornerShape(radius, smoothnessAsPercent = 100),
shape = shape,
colors = ButtonDefaults.buttonColors(
containerColor = color,
disabledContainerColor = AllInTheme.colors.disabled

@ -1,7 +1,6 @@
package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
@ -59,7 +58,9 @@ fun AllInTextField(
imeAction: ImeAction = ImeAction.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
borderColor: Color = AllInTheme.colors.onBackground2,
disabledBorderColor: Color = AllInTheme.colors.disabledBorder,
containerColor: Color = AllInTheme.colors.background,
disabledContainerColor: Color = AllInTheme.colors.disabled,
textColor: Color = AllInTheme.colors.onMainSurface,
placeholderColor: Color = AllInTheme.colors.onBackground2,
onValueChange: (String) -> Unit,
@ -117,12 +118,14 @@ fun AllInTextField(
cursorColor = textColor,
focusedBorderColor = borderColor,
unfocusedBorderColor = borderColor,
disabledBorderColor = disabledBorderColor,
focusedTextColor = textColor,
unfocusedTextColor = textColor,
errorTextColor = textColor,
focusedContainerColor = containerColor,
unfocusedContainerColor = containerColor,
errorContainerColor = containerColor,
disabledContainerColor = disabledContainerColor,
)
)
}
@ -251,7 +254,6 @@ fun AllInIntTextField(
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
private fun AllInTextFieldPlaceholderPreview() {
@ -264,6 +266,18 @@ private fun AllInTextFieldPlaceholderPreview() {
}
}
@Preview
@Composable
private fun AllInTextFieldDisabledPreview() {
AllInTheme {
AllInTextField(
placeholder = "Email",
value = "",
onValueChange = { },
enabled = false
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)

@ -34,6 +34,7 @@
<string name="field_error_already_used">%s est déjà utilisé.</string>
<string name="field_error_past_date">La %s doit être dans le futur.</string>
<string name="field_error_date_order">La %s doit venir après %s.</string>
<string name="field_error_no_response">Le bet doit au moins avoir 2 réponses.</string>
<!--Drawer-->
<string name="drawer_bets">Bets</string>
@ -99,8 +100,11 @@
<string name="bet_creation_private_bottom_text_3">Vous pourrez inviter des amis à tout moment pendant la période dinscription.</string>
<string name="bet_creation_yes_no_bottom_text_1">Les utilisateurs devront répondre au pari avec OUI ou NON.</string>
<string name="bet_creation_yes_no_bottom_text_2">Aucune autre réponse ne sera acceptée.</string>
<string name="bet_creation_custom_bottom_text_1">Vous allez renseiner les différentes réponses disponibles dans ce pari.</string>
<string name="bet_creation_custom_bottom_text_2">Faites attention à être clair et éviter toutes incertitudes.</string>
<string name="bet_creation_error">Erreur lors de la création du bet, veuillez rééssayer.</string>
<string name="bet_creation_success_message">Bet créé !</string>
<string name="bet_creation_max_answers">encore %d max.</string>
<!--Bet Page-->
<string name="bet_popular">Populaire</string>

@ -36,6 +36,7 @@
<string name="field_error_already_used">%s is already used.</string>
<string name="field_error_past_date">The %s should be in the future.</string>
<string name="field_error_date_order">The %s should come after the %s.</string>
<string name="field_error_no_response">The bet should at least have 2 answers.</string>
<!--Drawer-->
<string name="drawer_bets">Bets</string>
@ -100,9 +101,12 @@
<string name="bet_creation_private_bottom_text_2">Only your friends will be able to join the bet.</string>
<string name="bet_creation_private_bottom_text_3">You can invite friends at any moment during the registration period.</string>
<string name="bet_creation_yes_no_bottom_text_1">The participants will have to respond with either YES or NO.</string>
<string name="bet_creation_custom_bottom_text_1">You will fill in all the possible answers for this bet.</string>
<string name="bet_creation_custom_bottom_text_2">Make sure to be clear and to avoir any uncertainties.</string>
<string name="bet_creation_yes_no_bottom_text_2">No other answer will be accepted.</string>
<string name="bet_creation_error">Error while creating the bet. Please try again.</string>
<string name="bet_creation_success_message">Bet created !</string>
<string name="bet_creation_max_answers">still %d max.</string>
<!--Bet Page-->
<string name="bet_popular">Popular</string>

Loading…
Cancel
Save