Correcting API errors, starting the displaying of bets and adding simple UI tests
continuous-integration/drone/push Build is passing Details

pull/3/head
Arthur VALIN 1 year ago
parent 6f559862e4
commit 038781de5a

@ -27,7 +27,7 @@ android {
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "fr.iut.alldev.allin.TestRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary true useSupportLibrary true
} }
@ -76,13 +76,20 @@ dependencies {
implementation 'androidx.compose.material3:material3:1.2.0-alpha08' implementation 'androidx.compose.material3:material3:1.2.0-alpha08'
implementation "androidx.compose.material:material:1.5.3" implementation "androidx.compose.material:material:1.5.3"
implementation "androidx.navigation:navigation-compose:2.7.3" implementation "androidx.navigation:navigation-compose:2.7.3"
implementation project(path: ':data') implementation project(path: ':data')
testImplementation 'junit:junit:4.13.2'
implementation 'androidx.compose.material:material-icons-core' implementation 'androidx.compose.material:material-icons-core'
implementation 'androidx.compose.material:material-icons-extended' implementation 'androidx.compose.material:material-icons-extended'
//Tests
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.core:core-splashscreen:1.0.1")
@ -90,6 +97,7 @@ dependencies {
//Hilt //Hilt
implementation "com.google.dagger:hilt-android:$hilt_version" implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version" kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0" implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
implementation 'com.github.racra:smooth-corner-rect-android-compose:v1.0.0' implementation 'com.github.racra:smooth-corner-rect-android-compose:v1.0.0'

@ -1,24 +0,0 @@
package fr.iut.alldev.allin
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("fr.iut.alldev.allin", appContext.packageName)
}
}

@ -0,0 +1,13 @@
package fr.iut.alldev.allin
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication
class TestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}

@ -0,0 +1,7 @@
package fr.iut.alldev.allin.test.finders
import androidx.compose.ui.test.*
fun SemanticsNodeInteraction.onChildWith(matcher: SemanticsMatcher) = onChildren().filterToOne(matcher)
fun SemanticsNodeInteraction.onChildWithTag(testTag: String) = onChildWith(hasTestTag(testTag))

@ -0,0 +1,31 @@
package fr.iut.alldev.allin.test.mock
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import java.time.ZonedDateTime
object Bets {
val bets by lazy{
listOf(
YesNoBet(
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.IN_PROGRESS
),
MatchBet(
theme = "Theme",
phrase = "Phrase",
endRegisterDate = ZonedDateTime.now(),
endBetDate = ZonedDateTime.now(),
isPublic = true,
betStatus = BetStatus.IN_PROGRESS,
nameTeam1 = "Team_1",
nameTeam2 = "Team_2"
),
)
}
}

@ -0,0 +1,66 @@
package fr.iut.alldev.allin.vo.bet
import androidx.activity.compose.setContent
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import fr.iut.alldev.allin.test.TestTags
import fr.iut.alldev.allin.test.mock.Bets
import fr.iut.alldev.allin.ui.MainActivity
import fr.iut.alldev.allin.ui.theme.AllInTheme
import fr.iut.alldev.allin.vo.bet.factory.toBetVO
import fr.iut.alldev.allin.vo.bet.visitor.BetTestVisitor
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@HiltAndroidTest
class BetVOTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Before
fun init() {
hiltRule.inject()
}
companion object {
val visitor = BetTestVisitor()
}
@Test
fun testVisitor_shouldDisplayYesNoBetUI(){
//Given
//When
composeTestRule.activity.setContent {
AllInTheme{
Bets.bets[0].toBetVO()?.accept(v = visitor)
}
}
//Expect
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertExists()
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertDoesNotExist()
}
@Test
fun testVisitor_shouldDisplayMatchUI(){
//Given
//When
composeTestRule.activity.setContent {
AllInTheme{
Bets.bets[1].toBetVO()?.accept(v = visitor)
}
}
//Expect
composeTestRule.onNodeWithTag(TestTags.MATCH_BET.tag).assertExists()
composeTestRule.onNodeWithTag(TestTags.YES_NO_BET.tag).assertDoesNotExist()
}
}

@ -0,0 +1,21 @@
package fr.iut.alldev.allin.vo.bet.visitor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.test.TestTags
class BetTestVisitor : DisplayBetVisitor {
@Composable
override fun visitYesNoBet(b: YesNoBet) {
Text("This is a YesNo Bet", Modifier.testTag(TestTags.YES_NO_BET.tag))
}
@Composable
override fun visitMatchBet(b: MatchBet) {
Text("This is a Match Bet", Modifier.testTag(TestTags.MATCH_BET.tag))
}
}

@ -14,7 +14,6 @@ annotation class AllInCurrentUser
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal object CurrentUserModule { internal object CurrentUserModule {
@AllInCurrentUser @AllInCurrentUser
@Provides @Provides
fun provideUser( fun provideUser(

@ -3,7 +3,7 @@ package fr.iut.alldev.allin.ext
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.BetStatus import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.ui.theme.AllInTheme import fr.iut.alldev.allin.ui.theme.AllInTheme
fun BetStatus.getTitle(): Int { fun BetStatus.getTitle(): Int {

@ -6,7 +6,7 @@ import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.SportsSoccer import androidx.compose.material.icons.filled.SportsSoccer
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.BetType import fr.iut.alldev.allin.data.model.bet.BetType
fun BetType.getTitle(): Int { fun BetType.getTitle(): Int {
return when (this) { return when (this) {

@ -25,6 +25,10 @@ sealed class FieldErrorState(
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, arrayOf(fieldName, characters)) FieldErrorState(R.string.FieldError_NoSpecialCharacter, arrayOf(fieldName, characters))
data class AlreadyUsed(val value: String) :
FieldErrorState(R.string.FieldError_AlreadyUsed, arrayOf(value))
@Composable @Composable
fun errorResource() = stringResourceOrNull(id = messageId, messageArgs) fun errorResource() = stringResourceOrNull(id = messageId, messageArgs)
} }

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

@ -21,17 +21,31 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp 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 fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.data.model.bet.BetStatus
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard import fr.iut.alldev.allin.ui.bet.components.BetScreenCard
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
import fr.iut.alldev.allin.ui.core.AllInChip import fr.iut.alldev.allin.ui.core.AllInChip
import fr.iut.alldev.allin.ui.theme.AllInTheme import fr.iut.alldev.allin.ui.theme.AllInTheme
import java.time.ZonedDateTime
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class private val bets = listOf(
YesNoBet(
"Études",
"Emre va t'il finir son TP de MAUI?",
ZonedDateTime.now(),
ZonedDateTime.now(),
true,
BetStatus.IN_PROGRESS
),
) )
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable @Composable
fun BetScreen( fun BetScreen(
viewModel: BetViewModel = hiltViewModel(), viewModel: BetViewModel = hiltViewModel(),
showBetStatus: ()->Unit selectBet: (Bet)->Unit
){ ){
val horizontalPadding = 23.dp val horizontalPadding = 23.dp
@ -100,7 +114,7 @@ fun BetScreen(
} }
} }
} }
items(5){ items(bets){
BetScreenCard( BetScreenCard(
creator = "Lucas", creator = "Lucas",
category = "Études", category = "Études",
@ -108,7 +122,7 @@ fun BetScreen(
date = "11 Sept.", date = "11 Sept.",
time = "13:00", time = "13:00",
players = List(3){ null }, players = List(3){ null },
onClickParticipate = showBetStatus, onClickParticipate = { selectBet(it) },
modifier = Modifier.padding(horizontal = horizontalPadding) modifier = Modifier.padding(horizontal = horizontalPadding)
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))

@ -13,7 +13,7 @@ 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.compose.ui.unit.dp
import fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.data.model.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.getTitle import fr.iut.alldev.allin.ext.getTitle
import fr.iut.alldev.allin.ui.betcreation.tabs.BetCreationScreenAnswerTab import fr.iut.alldev.allin.ui.betcreation.tabs.BetCreationScreenAnswerTab

@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier
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.BetType import fr.iut.alldev.allin.data.model.bet.BetType
import fr.iut.alldev.allin.ext.getTitle import fr.iut.alldev.allin.ext.getTitle
import fr.iut.alldev.allin.ui.betcreation.components.BetCreationScreenBottomText import fr.iut.alldev.allin.ui.betcreation.components.BetCreationScreenBottomText
import fr.iut.alldev.allin.ui.core.AllInSelectionBox import fr.iut.alldev.allin.ui.core.AllInSelectionBox

@ -3,15 +3,19 @@ package fr.iut.alldev.allin.ui.betstatus
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import fr.iut.alldev.allin.data.model.BetStatus import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.ui.betstatus.components.BetStatusBottomSheetBack import fr.iut.alldev.allin.ui.betstatus.components.BetStatusBottomSheetBack
import fr.iut.alldev.allin.ui.betstatus.visitor.BetStatusBottomSheetDisplayBetVisitor
import fr.iut.alldev.allin.ui.core.AllInBottomSheet import fr.iut.alldev.allin.ui.core.AllInBottomSheet
import fr.iut.alldev.allin.vo.bet.BetVO
internal const val SHEET_HEIGHT = .85f internal const val SHEET_HEIGHT = .85f
private val visitor = BetStatusBottomSheetDisplayBetVisitor()
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -19,7 +23,7 @@ fun BetStatusBottomSheet(
state: SheetState, state: SheetState,
sheetVisibility: Boolean, sheetVisibility: Boolean,
sheetBackVisibility: Boolean, sheetBackVisibility: Boolean,
betStatus: BetStatus, bet: BetVO<Bet>?,
onDismiss: ()->Unit onDismiss: ()->Unit
) { ) {
AnimatedVisibility( AnimatedVisibility(
@ -31,10 +35,12 @@ fun BetStatusBottomSheet(
targetOffsetY = { it } targetOffsetY = { it }
) )
) { ) {
bet?.let {
BetStatusBottomSheetBack( BetStatusBottomSheetBack(
status = betStatus status = it.bet.betStatus
) )
} }
}
AllInBottomSheet( AllInBottomSheet(
sheetVisibility = sheetVisibility, sheetVisibility = sheetVisibility,
@ -46,6 +52,7 @@ fun BetStatusBottomSheet(
Modifier Modifier
.fillMaxHeight(SHEET_HEIGHT) .fillMaxHeight(SHEET_HEIGHT)
) { ) {
bet?.accept(visitor)
} }
} }
} }

@ -16,7 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
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.data.model.BetStatus 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.getTitle import fr.iut.alldev.allin.ext.getTitle
@ -45,7 +45,7 @@ fun BetStatusBottomSheetBack(
style = AllInTheme.typography.h2.copy( style = AllInTheme.typography.h2.copy(
fontStyle = FontStyle.Italic fontStyle = FontStyle.Italic
), ),
fontSize = 20.sp, fontSize = 30.sp,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Icon( Icon(

@ -0,0 +1,71 @@
package fr.iut.alldev.allin.ui.betstatus.components
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.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.ui.core.PercentagePositionnedElement
import fr.iut.alldev.allin.ui.core.StatBar
import fr.iut.alldev.allin.ui.theme.AllInTheme
@Composable
fun YesNoStatBar(
yesPercentage: Float
) {
Column(
Modifier.padding(horizontal = 9.dp)
){
Row{
Text(
text = stringResource(id = R.string.Yes).uppercase(),
color = AllInTheme.colors.allIn_Blue,
style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic,
fontSize = 30.sp,
modifier = Modifier.weight(1f)
)
Text(
text = stringResource(id = R.string.No).uppercase(),
style = AllInTheme.typography.h1,
fontStyle = FontStyle.Italic,
fontSize = 30.sp,
color = AllInTheme.colors.allIn_BarPink
)
}
StatBar(percentage = yesPercentage)
PercentagePositionnedElement(
percentage = yesPercentage
){
Text(
text = yesPercentage.toPercentageString(),
style = AllInTheme.typography.h3,
color = AllInTheme.colors.allIn_BarPurple
)
}
}
}
private class YesNoStatBarPreviewProvider: PreviewParameterProvider<Float> {
override val values = sequenceOf(0f, .33f, .5f, .66f, 1f)
}
@Preview
@Composable
private fun YesNoStatBarPreview(
@PreviewParameter(YesNoStatBarPreviewProvider::class) percentage: Float
) {
AllInTheme {
YesNoStatBar(percentage)
}
}

@ -0,0 +1,33 @@
package fr.iut.alldev.allin.ui.betstatus.visitor
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import fr.iut.alldev.allin.data.model.bet.MatchBet
import fr.iut.alldev.allin.data.model.bet.YesNoBet
import fr.iut.alldev.allin.ui.core.StatBar
import fr.iut.alldev.allin.ui.core.bet.BetTitleHeader
import fr.iut.alldev.allin.vo.bet.visitor.DisplayBetVisitor
class BetStatusBottomSheetDisplayBetVisitor : DisplayBetVisitor {
@Composable
override fun visitYesNoBet(b: YesNoBet) {
Column {
BetTitleHeader(
title = b.phrase,
category = b.theme,
creator = "Lucas" /*TODO : Creator*/,
modifier = Modifier.padding(horizontal = 20.dp)
)
StatBar(percentage = .86f)
}
}
@Composable
override fun visitMatchBet(b: MatchBet) {
Text("This is a MATCH BET")
}
}

@ -15,10 +15,7 @@ import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.*
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
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
@ -45,6 +42,7 @@ fun AllInTextField(
bringIntoViewRequester: BringIntoViewRequester, bringIntoViewRequester: BringIntoViewRequester,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardType: KeyboardType = KeyboardType.Text, keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default,
borderColor: Color = AllInTheme.themeColors.on_background_2, borderColor: Color = AllInTheme.themeColors.on_background_2,
containerColor: Color = AllInTheme.themeColors.background, containerColor: Color = AllInTheme.themeColors.background,
@ -114,7 +112,7 @@ fun AllInTextField(
}, },
textStyle = AllInTheme.typography.r, textStyle = AllInTheme.typography.r,
enabled = enabled, enabled = enabled,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType), keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = imeAction),
keyboardActions = keyboardActions, keyboardActions = keyboardActions,
shape = AbsoluteSmoothCornerShape(10.dp, 100), shape = AbsoluteSmoothCornerShape(10.dp, 100),
colors = OutlinedTextFieldDefaults.colors( colors = OutlinedTextFieldDefaults.colors(
@ -137,6 +135,7 @@ fun AllInPasswordField(
placeholder: String, placeholder: String,
value: String, value: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imeAction: ImeAction = ImeAction.Default,
keyboardType: KeyboardType = KeyboardType.Password, keyboardType: KeyboardType = KeyboardType.Password,
keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default,
errorText: String? = null, errorText: String? = null,
@ -151,6 +150,7 @@ fun AllInPasswordField(
modifier = modifier, modifier = modifier,
errorText = errorText, errorText = errorText,
placeholder = placeholder, placeholder = placeholder,
imeAction = imeAction,
keyboardActions = keyboardActions, keyboardActions = keyboardActions,
visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None, visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None,
value = value, value = value,

@ -0,0 +1,53 @@
package fr.iut.alldev.allin.ui.core
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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.ui.theme.AllInTheme
@Composable
fun PercentagePositionnedElement(
percentage: Float,
offset: Dp = (-9).dp,
content: @Composable ()->Unit
) {
Box(
Modifier.fillMaxWidth()
) {
when (percentage) {
0f -> {
content()
}
1f -> {
Box(
Modifier.align(Alignment.CenterEnd)
){
content()
}
}
else -> {
Row {
Spacer(modifier = Modifier.fillMaxWidth(percentage))
Box(Modifier.offset(x = offset)) {
content()
}
}
}
}
}
}
@Preview
@Composable
private fun PercentagePositionnedElementPreview() {
AllInTheme {
PercentagePositionnedElement(percentage = .4f) {
Text("MyElement")
}
}
}

@ -5,17 +5,13 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
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.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
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
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 fr.iut.alldev.allin.R import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ui.theme.AllInTheme import fr.iut.alldev.allin.ui.theme.AllInTheme
@ -25,9 +21,7 @@ fun StatBar(
) { ) {
val radius100percent = if(percentage==1f) 50 else 0 val radius100percent = if(percentage==1f) 50 else 0
val radius0percent = if(percentage==0f) 50 else 0 val radius0percent = if(percentage==0f) 50 else 0
Box( Box{
Modifier.padding(horizontal = 9.dp)
){
Row( Row(
Modifier.align(Alignment.Center) Modifier.align(Alignment.Center)
){ ){
@ -45,18 +39,7 @@ fun StatBar(
) )
.background(AllInTheme.colors.allIn_Bar1stGradient) .background(AllInTheme.colors.allIn_Bar1stGradient)
){
Text(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 15.dp),
text = "OUI",
style = AllInTheme.typography.h2,
textAlign = TextAlign.Center,
fontSize = 25.sp,
color = Color.White.copy(alpha = 0.3f),
) )
}
if(percentage!=0f && percentage!=1f) { if(percentage!=0f && percentage!=1f) {
Spacer(modifier = Modifier.width(15.dp)) Spacer(modifier = Modifier.width(15.dp))
} }
@ -75,44 +58,27 @@ fun StatBar(
.background(AllInTheme.colors.allIn_Bar2ndGradient) .background(AllInTheme.colors.allIn_Bar2ndGradient)
) )
} }
Box( PercentagePositionnedElement(percentage = percentage) {
Modifier when(percentage){
.fillMaxWidth() 0f -> Icon(
.align(Alignment.Center)
) {
when (percentage) {
0f -> {
Icon(
painter = painterResource(id = R.drawable.fire_solid), painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allIn_BarPink, tint = AllInTheme.colors.allIn_BarPink,
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier.size(32.dp)
.size(32.dp)
) )
} 1f -> Icon(
1f -> {
Icon(
painter = painterResource(id = R.drawable.fire_solid), painter = painterResource(id = R.drawable.fire_solid),
tint = AllInTheme.colors.allIn_BarPurple, tint = AllInTheme.colors.allIn_BarPurple,
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier.size(32.dp)
.align(Alignment.CenterEnd)
.size(32.dp)
) )
} else -> Image(
else -> {
Row {
Spacer(modifier = Modifier.fillMaxWidth(percentage))
Image(
painter = painterResource(id = R.drawable.bar_flame), painter = painterResource(id = R.drawable.bar_flame),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier.size(32.dp)
.size(32.dp)
.offset(x = (-9).dp)
) )
} }
}
}
} }
} }
} }

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -16,9 +17,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign 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
@ -34,6 +38,7 @@ fun LoginScreen(
navigateToRegister: ()->Unit, navigateToRegister: ()->Unit,
loginViewModel: LoginViewModel = hiltViewModel() loginViewModel: LoginViewModel = hiltViewModel()
) { ) {
val focusManager = LocalFocusManager.current
val bringIntoViewRequester = BringIntoViewRequester() val bringIntoViewRequester = BringIntoViewRequester()
val loading by remember{ loginViewModel.loading } val loading by remember{ loginViewModel.loading }
@ -42,6 +47,18 @@ fun LoginScreen(
val (username, setUsername) = remember{ loginViewModel.username } val (username, setUsername) = remember{ loginViewModel.username }
val (password, setPassword) = remember{ loginViewModel.password } val (password, setPassword) = remember{ loginViewModel.password }
val keyboardActions = remember {
KeyboardActions(
onDone = {
focusManager.clearFocus()
loginViewModel.onLogin(navigateToDashboard)
},
onNext = {
focusManager.moveFocus(FocusDirection.Down)
}
)
}
Box( Box(
Modifier Modifier
.fillMaxSize() .fillMaxSize()
@ -78,14 +95,18 @@ fun LoginScreen(
placeholder = stringResource(id = R.string.username), placeholder = stringResource(id = R.string.username),
value = username, value = username,
onValueChange = setUsername, onValueChange = setUsername,
bringIntoViewRequester = bringIntoViewRequester bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
) )
AllInPasswordField( AllInPasswordField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.password), placeholder = stringResource(id = R.string.password),
value = password, value = password,
onValueChange = setPassword, onValueChange = setPassword,
bringIntoViewRequester = bringIntoViewRequester bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Done,
keyboardActions = keyboardActions
) )
} }
ClickableText( ClickableText(

@ -4,6 +4,7 @@ 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.repository.UserRepository import fr.iut.alldev.allin.data.repository.UserRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -29,7 +30,7 @@ class LoginViewModel @Inject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try{ try{
userRepository.login(username.value, password.value) userRepository.login(username.value, password.value)
} catch (e: retrofit2.HttpException){ } catch (e: AllInAPIException){
hasError.value = true hasError.value = true
} }
} }

@ -10,7 +10,6 @@ import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import fr.iut.alldev.allin.data.model.BetStatus
import fr.iut.alldev.allin.ui.betstatus.BetStatusBottomSheet import fr.iut.alldev.allin.ui.betstatus.BetStatusBottomSheet
import fr.iut.alldev.allin.ui.main.components.AllInScaffold import fr.iut.alldev.allin.ui.main.components.AllInScaffold
import fr.iut.alldev.allin.ui.navigation.AllInDrawerNavHost import fr.iut.alldev.allin.ui.navigation.AllInDrawerNavHost
@ -19,6 +18,7 @@ import fr.iut.alldev.allin.ui.navigation.TopLevelDestination
import fr.iut.alldev.allin.ui.navigation.drawer.AllInDrawer import fr.iut.alldev.allin.ui.navigation.drawer.AllInDrawer
import fr.iut.alldev.allin.ui.navigation.popUpTo import fr.iut.alldev.allin.ui.navigation.popUpTo
import fr.iut.alldev.allin.ui.theme.AllInTheme import fr.iut.alldev.allin.ui.theme.AllInTheme
import fr.iut.alldev.allin.vo.bet.factory.toBetVO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
private val topLevelDestinations = listOf( private val topLevelDestinations = listOf(
@ -59,6 +59,10 @@ fun MainScreen(
mainViewModel.currentUser mainViewModel.currentUser
} }
val (selectedBet, setSelectedBet) = remember{
mainViewModel.selectedBet
}
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val (statusVisibility, sheetBackVisibility, setStatusVisibility) val (statusVisibility, sheetBackVisibility, setStatusVisibility)
@ -100,7 +104,10 @@ fun MainScreen(
) { ) {
AllInDrawerNavHost( AllInDrawerNavHost(
navController = navController, navController = navController,
setStatusVisibility = setStatusVisibility selectBet = {
setSelectedBet(it)
setStatusVisibility(true)
}
) )
} }
} }
@ -113,9 +120,8 @@ fun MainScreen(
onDismiss = { onDismiss = {
setStatusVisibility(false) setStatusVisibility(false)
}, },
betStatus = BetStatus.IN_PROGRESS bet = selectedBet?.toBetVO()
) )
BackHandler( BackHandler(
enabled = drawerState.isOpen enabled = drawerState.isOpen
) { ) {

@ -3,8 +3,8 @@ package fr.iut.alldev.allin.ui.main
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
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.di.AllInCurrentUser import fr.iut.alldev.allin.di.AllInCurrentUser
import fr.iut.alldev.allin.data.model.BetStatus
import fr.iut.alldev.allin.data.model.User import fr.iut.alldev.allin.data.model.User
import javax.inject.Inject import javax.inject.Inject
@ -13,5 +13,5 @@ import javax.inject.Inject
class MainViewModel @Inject constructor( class MainViewModel @Inject constructor(
@AllInCurrentUser val currentUser: User @AllInCurrentUser val currentUser: User
) : ViewModel() { ) : ViewModel() {
val selectedBet = mutableStateOf<BetStatus?>(null) val selectedBet = mutableStateOf<Bet?>(null)
} }

@ -11,6 +11,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import fr.iut.alldev.allin.data.model.bet.Bet
import fr.iut.alldev.allin.ui.bet.BetScreen import fr.iut.alldev.allin.ui.bet.BetScreen
import fr.iut.alldev.allin.ui.betcreation.BetCreationScreen import fr.iut.alldev.allin.ui.betcreation.BetCreationScreen
import fr.iut.alldev.allin.ui.login.LoginScreen import fr.iut.alldev.allin.ui.login.LoginScreen
@ -80,7 +81,7 @@ fun AllInNavHost(modifier: Modifier = Modifier,
internal fun AllInDrawerNavHost( internal fun AllInDrawerNavHost(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
navController: NavHostController, navController: NavHostController,
setStatusVisibility: (Boolean) -> Unit, selectBet: (Bet) -> Unit,
startDestination: String = Routes.PUBLIC_BETS startDestination: String = Routes.PUBLIC_BETS
) { ) {
NavHost( NavHost(
@ -92,7 +93,7 @@ internal fun AllInDrawerNavHost(
) { ) {
composable(route = Routes.PUBLIC_BETS) { composable(route = Routes.PUBLIC_BETS) {
BetScreen( BetScreen(
showBetStatus = { setStatusVisibility(true) } selectBet = selectBet
) )
} }
composable(route = Routes.BET_CREATION) { composable(route = Routes.BET_CREATION) {

@ -5,12 +5,16 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
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.text.style.TextOverflow
@ -31,6 +35,9 @@ fun RegisterScreen(
navigateToLogin: () -> Unit, navigateToLogin: () -> Unit,
registerViewModel: RegisterViewModel = hiltViewModel(), registerViewModel: RegisterViewModel = hiltViewModel(),
) { ) {
val focusManager = LocalFocusManager.current
val loading by remember{ registerViewModel.loading } val loading by remember{ registerViewModel.loading }
val usernameError by remember{ registerViewModel.usernameError } val usernameError by remember{ registerViewModel.usernameError }
@ -50,6 +57,23 @@ fun RegisterScreen(
val emailFieldName = stringResource(id = R.string.email) val emailFieldName = stringResource(id = R.string.email)
val passwordFieldName = stringResource(id = R.string.password) val passwordFieldName = stringResource(id = R.string.password)
val keyboardActions = remember {
KeyboardActions(
onDone = {
focusManager.clearFocus()
registerViewModel.onRegister(
usernameFieldName,
emailFieldName,
passwordFieldName,
navigateToDashboard
)
},
onNext = {
focusManager.moveFocus(FocusDirection.Down)
}
)
}
Column( Column(
Modifier Modifier
.fillMaxSize() .fillMaxSize()
@ -100,7 +124,9 @@ fun RegisterScreen(
onValueChange = setUsername, onValueChange = setUsername,
maxChar = 20, maxChar = 20,
errorText = usernameError.errorResource(), errorText = usernameError.errorResource(),
bringIntoViewRequester = bringIntoViewRequester bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
) )
AllInTextField( AllInTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -109,7 +135,9 @@ fun RegisterScreen(
onValueChange = setEmail, onValueChange = setEmail,
errorText = emailError.errorResource(), errorText = emailError.errorResource(),
keyboardType = KeyboardType.Email, keyboardType = KeyboardType.Email,
bringIntoViewRequester = bringIntoViewRequester bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
) )
AllInPasswordField( AllInPasswordField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -117,7 +145,9 @@ fun RegisterScreen(
value = password, value = password,
errorText = passwordError.errorResource(), errorText = passwordError.errorResource(),
onValueChange = setPassword, onValueChange = setPassword,
bringIntoViewRequester = bringIntoViewRequester bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Next,
keyboardActions = keyboardActions
) )
AllInPasswordField( AllInPasswordField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -125,7 +155,9 @@ fun RegisterScreen(
value = passwordValidation, value = passwordValidation,
errorText = passwordValidationError.errorResource(), errorText = passwordValidationError.errorResource(),
onValueChange = setPasswordValidation, onValueChange = setPasswordValidation,
bringIntoViewRequester = bringIntoViewRequester bringIntoViewRequester = bringIntoViewRequester,
imeAction = ImeAction.Done,
keyboardActions = keyboardActions
) )
} }
} }

@ -4,6 +4,7 @@ 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.repository.UserRepository import fr.iut.alldev.allin.data.repository.UserRepository
import fr.iut.alldev.allin.ext.ALLOWED_SYMBOLS import fr.iut.alldev.allin.ext.ALLOWED_SYMBOLS
import fr.iut.alldev.allin.ext.FieldErrorState import fr.iut.alldev.allin.ext.FieldErrorState
@ -89,11 +90,17 @@ class RegisterViewModel @Inject constructor(
passwordFieldName.lowercase() passwordFieldName.lowercase()
) )
if(!hasError.value) { if(!hasError.value) {
try {
userRepository.register( userRepository.register(
username.value, username.value,
email.value, email.value,
password.value password.value
) )
}catch(e : AllInAPIException){
usernameError.value = FieldErrorState.AlreadyUsed(username.value)
emailError.value = FieldErrorState.AlreadyUsed(email.value)
hasError.value = true
}
} }
} }
if(!hasError.value){ if(!hasError.value){

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

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

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

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

@ -17,6 +17,9 @@
<string name="FieldError_BadFormat">Le %s a un mauvais format : %s.</string> <string name="FieldError_BadFormat">Le %s a un mauvais format : %s.</string>
<string name="FieldError_NotIdentical">Les champs ne sont pas identiques.</string> <string name="FieldError_NotIdentical">Les champs ne sont pas identiques.</string>
<string name="FieldError_NoSpecialCharacter">Le %s doit contenir au moins un caractère spécial : %s.</string> <string name="FieldError_NoSpecialCharacter">Le %s doit contenir au moins un caractère spécial : %s.</string>
<string name="FieldError_AlreadyUsed">%s est déjà utilisé.</string>
<string name="Yes">Oui</string>
<string name="No">Non</string>
<!--Drawer--> <!--Drawer-->
<string name="bets">Bets</string> <string name="bets">Bets</string>

@ -19,6 +19,9 @@
<string name="FieldError_BadFormat">The %s has bad format : %s.</string> <string name="FieldError_BadFormat">The %s has bad format : %s.</string>
<string name="FieldError_NotIdentical">The fields are not identical.</string> <string name="FieldError_NotIdentical">The fields are not identical.</string>
<string name="FieldError_NoSpecialCharacter">The %s must contain at least one special character : %s.</string> <string name="FieldError_NoSpecialCharacter">The %s must contain at least one special character : %s.</string>
<string name="FieldError_AlreadyUsed">%s is already used.</string>
<string name="Yes">Yes</string>
<string name="No">No</string>
<!--Drawer--> <!--Drawer-->
<string name="bets">Bets</string> <string name="bets">Bets</string>

@ -55,6 +55,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.9.0'
//Tests
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@ -66,7 +68,7 @@ dependencies {
// Retrofit // Retrofit
api "com.squareup.retrofit2:retrofit:2.9.0" api "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:okhttp:4.11.0" implementation "com.squareup.okhttp3:okhttp:4.11.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" debugImplementation "com.squareup.okhttp3:logging-interceptor:4.11.0"
api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
} }

@ -4,6 +4,7 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.api.interceptors.ErrorInterceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton import javax.inject.Singleton
@ -21,5 +22,6 @@ internal object DebugNetworkModule {
level = HttpLoggingInterceptor.Level.BODY level = HttpLoggingInterceptor.Level.BODY
} }
) )
.addInterceptor(ErrorInterceptor())
.build() .build()
} }

@ -5,7 +5,6 @@ import fr.iut.alldev.allin.data.api.model.ResponseUser
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.POST import retrofit2.http.POST
interface AllInApi { interface AllInApi {
@POST("users/login") @POST("users/login")
suspend fun login( suspend fun login(

@ -0,0 +1,30 @@
package fr.iut.alldev.allin.data.api.interceptors
import okhttp3.Interceptor
import okhttp3.Response
import okio.IOException
open class AllInAPIException(message: String) : IOException(message)
class AllInNotFoundException(message: String) : AllInAPIException(message)
class AllInUnauthorizedException(message: String) : AllInAPIException(message)
class AllInUnsuccessfulException(message: String) : AllInAPIException(message)
class ErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if(!response.isSuccessful){
when (response.code) {
404 -> throw AllInNotFoundException(response.message)
401 -> throw AllInUnauthorizedException(response.message)
else -> throw AllInUnsuccessfulException(response.message)
}
}
if (response.body?.contentType()?.subtype != "json") {
throw AllInAPIException(response.message)
}
return response
}
}

@ -23,6 +23,6 @@ data class ResponseUser(
@Keep @Keep
@Serializable @Serializable
data class CheckUser( data class CheckUser(
val username: String, val login: String,
val password: String, val password: String,
) )

@ -13,7 +13,6 @@ import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
class ApiModule { class ApiModule {
@Provides @Provides
@Singleton @Singleton
fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi { fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi {

@ -5,6 +5,7 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
@ -32,10 +33,13 @@ internal object NetworkModule {
@Provides @Provides
fun provideUrl(): HttpUrl = "https://codefirst.iut.uca.fr/containers/AllDev-api/".toHttpUrl() fun provideUrl(): HttpUrl = "https://codefirst.iut.uca.fr/containers/AllDev-api/".toHttpUrl()
@OptIn(ExperimentalSerializationApi::class)
fun createRetrofit(url: HttpUrl, okHttpClient: OkHttpClient): Retrofit = fun createRetrofit(url: HttpUrl, okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder() Retrofit.Builder()
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .addConverterFactory(
json.asConverterFactory("application/json".toMediaType())
)
.baseUrl(url) .baseUrl(url)
.build() .build()
} }

@ -0,0 +1,3 @@
package fr.iut.alldev.allin.data.ext
fun Float.toPercentageString(precision: Int = 0) = String.format("%.${precision}f", this*100) + "%"

@ -0,0 +1,12 @@
package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime
abstract class Bet(
val theme: String,
val phrase: String,
val endRegisterDate: ZonedDateTime,
val endBetDate: ZonedDateTime,
val isPublic: Boolean,
val betStatus: BetStatus,
)

@ -1,4 +1,4 @@
package fr.iut.alldev.allin.data.model package fr.iut.alldev.allin.data.model.bet
enum class BetStatus { enum class BetStatus {
FINISHED, FINISHED,

@ -1,4 +1,4 @@
package fr.iut.alldev.allin.data.model package fr.iut.alldev.allin.data.model.bet
enum class BetType { enum class BetType {
YES_NO, YES_NO,

@ -0,0 +1,21 @@
package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime
class MatchBet(
theme: String,
phrase: String,
endRegisterDate: ZonedDateTime,
endBetDate: ZonedDateTime,
isPublic: Boolean,
betStatus: BetStatus,
val nameTeam1: String,
val nameTeam2: String
) : Bet(
theme,
phrase,
endRegisterDate,
endBetDate,
isPublic,
betStatus
)

@ -0,0 +1,19 @@
package fr.iut.alldev.allin.data.model.bet
import java.time.ZonedDateTime
class YesNoBet(
theme: String,
phrase: String,
endRegisterDate: ZonedDateTime,
endBetDate: ZonedDateTime,
isPublic: Boolean,
betStatus: BetStatus
) : Bet(
theme,
phrase,
endRegisterDate,
endBetDate,
isPublic,
betStatus
)

@ -13,12 +13,13 @@ class UserRepositoryImpl @Inject constructor(
override suspend fun login(username: String, password: String) { override suspend fun login(username: String, password: String) {
currentUser = api.login( currentUser = api.login(
CheckUser( CheckUser(
username = username, login = username,
password = password password = password
) )
).toUser() ).toUser()
} }
override suspend fun register(username: String, email: String, password: String) { override suspend fun register(username: String, email: String, password: String) {
currentUser = api.register( currentUser = api.register(
ResponseUser( ResponseUser(
@ -28,5 +29,6 @@ class UserRepositoryImpl @Inject constructor(
nbCoins = 0 nbCoins = 0
) )
).toUser() ).toUser()
} }
} }

@ -16,5 +16,6 @@ internal object ReleaseNetworkModule {
@Singleton @Singleton
fun provideOkHttpClient(): OkHttpClient = fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder() OkHttpClient.Builder()
.addInterceptor(ErrorInterceptor())
.build() .build()
} }
Loading…
Cancel
Save