From 038781de5a95b9bda450390857640aa4ad1b58be Mon Sep 17 00:00:00 2001 From: "arthur.valin" Date: Wed, 18 Oct 2023 18:55:56 +0200 Subject: [PATCH] Correcting API errors, starting the displaying of bets and adding simple UI tests --- src/app/build.gradle | 12 ++- .../alldev/allin/ExampleInstrumentedTest.kt | 24 ------ .../java/fr/iut/alldev/allin/TestRunner.kt | 13 ++++ .../iut/alldev/allin/test/finders/Finders.kt | 7 ++ .../fr/iut/alldev/allin/test/mock/Bets.kt | 31 ++++++++ .../fr/iut/alldev/allin/vo/bet/BetVOTest.kt | 66 ++++++++++++++++ .../allin/vo/bet/visitor/BetVOTestVisitor.kt | 21 +++++ .../iut/alldev/allin/di/CurrentUserModule.kt | 1 - .../fr/iut/alldev/allin/ext/BetStatusExt.kt | 2 +- .../fr/iut/alldev/allin/ext/BetTypeExt.kt | 2 +- .../java/fr/iut/alldev/allin/ext/FieldExt.kt | 4 + .../java/fr/iut/alldev/allin/test/TestTags.kt | 6 ++ .../fr/iut/alldev/allin/ui/bet/BetScreen.kt | 22 +++++- .../allin/ui/betcreation/BetCreationScreen.kt | 2 +- .../tabs/BetCreationScreenAnswerTab.kt | 2 +- .../ui/betstatus/BetStatusBottomSheet.kt | 19 +++-- .../components/BetStatusBottomSheetBack.kt | 4 +- .../ui/betstatus/components/YesNoStarBar.kt | 71 +++++++++++++++++ .../BetStatusBottomSheetDisplayBetVisitor.kt | 33 ++++++++ .../alldev/allin/ui/core/AllInTextField.kt | 10 +-- .../ui/core/PercentagePositionnedElement.kt | 53 +++++++++++++ .../fr/iut/alldev/allin/ui/core/StatBar.kt | 76 +++++-------------- .../iut/alldev/allin/ui/login/LoginScreen.kt | 25 +++++- .../alldev/allin/ui/login/LoginViewModel.kt | 3 +- .../fr/iut/alldev/allin/ui/main/MainScreen.kt | 14 +++- .../iut/alldev/allin/ui/main/MainViewModel.kt | 4 +- .../iut/alldev/allin/ui/navigation/NavHost.kt | 5 +- .../allin/ui/register/RegisterScreen.kt | 40 +++++++++- .../allin/ui/register/RegisterViewModel.kt | 17 +++-- .../java/fr/iut/alldev/allin/vo/ViewObject.kt | 8 ++ .../java/fr/iut/alldev/allin/vo/bet/BetVO.kt | 28 +++++++ .../allin/vo/bet/factory/BetVOFactory.kt | 30 ++++++++ .../alldev/allin/vo/bet/visitor/BetVisitor.kt | 14 ++++ src/app/src/main/res/values-fr/strings.xml | 3 + src/app/src/main/res/values/strings.xml | 5 +- src/data/build.gradle | 4 +- .../allin/data/di/DebugNetworkModule.kt | 2 + .../fr/iut/alldev/allin/data/api/AllInApi.kt | 1 - .../data/api/interceptors/ErrorInterceptor.kt | 30 ++++++++ .../allin/data/api/model/ResponseUser.kt | 2 +- .../fr/iut/alldev/allin/data/di/ApiModule.kt | 1 - .../iut/alldev/allin/data/di/NetworkModule.kt | 8 +- .../fr/iut/alldev/allin/data/ext/NumberExt.kt | 3 + .../fr/iut/alldev/allin/data/model/bet/Bet.kt | 12 +++ .../allin/data/model/{ => bet}/BetStatus.kt | 2 +- .../allin/data/model/{ => bet}/BetType.kt | 2 +- .../alldev/allin/data/model/bet/MatchBet.kt | 21 +++++ .../alldev/allin/data/model/bet/YesNoBet.kt | 19 +++++ .../repository/impl/UserRepositoryImpl.kt | 4 +- .../ReleaseNetworkModule.kt | 1 + 50 files changed, 656 insertions(+), 133 deletions(-) delete mode 100644 src/app/src/androidTest/java/fr/iut/alldev/allin/ExampleInstrumentedTest.kt create mode 100644 src/app/src/androidTest/java/fr/iut/alldev/allin/TestRunner.kt create mode 100644 src/app/src/androidTest/java/fr/iut/alldev/allin/test/finders/Finders.kt create mode 100644 src/app/src/androidTest/java/fr/iut/alldev/allin/test/mock/Bets.kt create mode 100644 src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/BetVOTest.kt create mode 100644 src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/visitor/BetVOTestVisitor.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/test/TestTags.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/YesNoStarBar.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/ui/core/PercentagePositionnedElement.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/vo/ViewObject.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/vo/bet/BetVO.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/vo/bet/factory/BetVOFactory.kt create mode 100644 src/app/src/main/java/fr/iut/alldev/allin/vo/bet/visitor/BetVisitor.kt create mode 100644 src/data/src/main/java/fr/iut/alldev/allin/data/api/interceptors/ErrorInterceptor.kt create mode 100644 src/data/src/main/java/fr/iut/alldev/allin/data/ext/NumberExt.kt create mode 100644 src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt rename src/data/src/main/java/fr/iut/alldev/allin/data/model/{ => bet}/BetStatus.kt (60%) rename src/data/src/main/java/fr/iut/alldev/allin/data/model/{ => bet}/BetType.kt (57%) create mode 100644 src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt create mode 100644 src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt diff --git a/src/app/build.gradle b/src/app/build.gradle index 8694db6..7ab7ee3 100644 --- a/src/app/build.gradle +++ b/src/app/build.gradle @@ -27,7 +27,7 @@ android { versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "fr.iut.alldev.allin.TestRunner" vectorDrawables { useSupportLibrary true } @@ -76,13 +76,20 @@ dependencies { implementation 'androidx.compose.material3:material3:1.2.0-alpha08' implementation "androidx.compose.material:material:1.5.3" implementation "androidx.navigation:navigation-compose:2.7.3" + implementation project(path: ':data') - testImplementation 'junit:junit:4.13.2' + implementation 'androidx.compose.material:material-icons-core' 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.espresso:espresso-core:3.5.1' 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-test-manifest:$compose_version" implementation("androidx.core:core-splashscreen:1.0.1") @@ -90,6 +97,7 @@ dependencies { //Hilt implementation "com.google.dagger:hilt-android:$hilt_version" kapt "com.google.dagger:hilt-android-compiler:$hilt_version" + implementation "androidx.hilt:hilt-navigation-compose:1.0.0" implementation 'com.github.racra:smooth-corner-rect-android-compose:v1.0.0' diff --git a/src/app/src/androidTest/java/fr/iut/alldev/allin/ExampleInstrumentedTest.kt b/src/app/src/androidTest/java/fr/iut/alldev/allin/ExampleInstrumentedTest.kt deleted file mode 100644 index 1a58a92..0000000 --- a/src/app/src/androidTest/java/fr/iut/alldev/allin/ExampleInstrumentedTest.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/src/app/src/androidTest/java/fr/iut/alldev/allin/TestRunner.kt b/src/app/src/androidTest/java/fr/iut/alldev/allin/TestRunner.kt new file mode 100644 index 0000000..cb9935c --- /dev/null +++ b/src/app/src/androidTest/java/fr/iut/alldev/allin/TestRunner.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/app/src/androidTest/java/fr/iut/alldev/allin/test/finders/Finders.kt b/src/app/src/androidTest/java/fr/iut/alldev/allin/test/finders/Finders.kt new file mode 100644 index 0000000..982c4fe --- /dev/null +++ b/src/app/src/androidTest/java/fr/iut/alldev/allin/test/finders/Finders.kt @@ -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)) \ No newline at end of file diff --git a/src/app/src/androidTest/java/fr/iut/alldev/allin/test/mock/Bets.kt b/src/app/src/androidTest/java/fr/iut/alldev/allin/test/mock/Bets.kt new file mode 100644 index 0000000..20fa7c4 --- /dev/null +++ b/src/app/src/androidTest/java/fr/iut/alldev/allin/test/mock/Bets.kt @@ -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" + ), + ) + } +} \ No newline at end of file diff --git a/src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/BetVOTest.kt b/src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/BetVOTest.kt new file mode 100644 index 0000000..177735c --- /dev/null +++ b/src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/BetVOTest.kt @@ -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() + + @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() + } + +} \ No newline at end of file diff --git a/src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/visitor/BetVOTestVisitor.kt b/src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/visitor/BetVOTestVisitor.kt new file mode 100644 index 0000000..15cc459 --- /dev/null +++ b/src/app/src/androidTest/java/fr/iut/alldev/allin/vo/bet/visitor/BetVOTestVisitor.kt @@ -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)) + } +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/di/CurrentUserModule.kt b/src/app/src/main/java/fr/iut/alldev/allin/di/CurrentUserModule.kt index fbce1c3..2010974 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/di/CurrentUserModule.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/di/CurrentUserModule.kt @@ -14,7 +14,6 @@ annotation class AllInCurrentUser @Module @InstallIn(SingletonComponent::class) internal object CurrentUserModule { - @AllInCurrentUser @Provides fun provideUser( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ext/BetStatusExt.kt b/src/app/src/main/java/fr/iut/alldev/allin/ext/BetStatusExt.kt index e5f5140..97e2b0c 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ext/BetStatusExt.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ext/BetStatusExt.kt @@ -3,7 +3,7 @@ package fr.iut.alldev.allin.ext import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color 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 fun BetStatus.getTitle(): Int { diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ext/BetTypeExt.kt b/src/app/src/main/java/fr/iut/alldev/allin/ext/BetTypeExt.kt index 9618e26..9820ad0 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ext/BetTypeExt.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ext/BetTypeExt.kt @@ -6,7 +6,7 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.SportsSoccer import androidx.compose.ui.graphics.vector.ImageVector 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 { return when (this) { diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ext/FieldExt.kt b/src/app/src/main/java/fr/iut/alldev/allin/ext/FieldExt.kt index f18262f..d8a89d6 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ext/FieldExt.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ext/FieldExt.kt @@ -25,6 +25,10 @@ sealed class FieldErrorState( data class NoSpecialCharacter(val fieldName: String, val characters: String = ALLOWED_SYMBOLS) : FieldErrorState(R.string.FieldError_NoSpecialCharacter, arrayOf(fieldName, characters)) + data class AlreadyUsed(val value: String) : + FieldErrorState(R.string.FieldError_AlreadyUsed, arrayOf(value)) + + @Composable fun errorResource() = stringResourceOrNull(id = messageId, messageArgs) } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/test/TestTags.kt b/src/app/src/main/java/fr/iut/alldev/allin/test/TestTags.kt new file mode 100644 index 0000000..fe01326 --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/test/TestTags.kt @@ -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") +} diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt index 3a6eaf7..270fa65 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/bet/BetScreen.kt @@ -21,17 +21,31 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import fr.iut.alldev.allin.R +import fr.iut.alldev.allin.data.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.BetScreenPopularCard import fr.iut.alldev.allin.ui.core.AllInChip 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 fun BetScreen( viewModel: BetViewModel = hiltViewModel(), - showBetStatus: ()->Unit + selectBet: (Bet)->Unit ){ val horizontalPadding = 23.dp @@ -100,7 +114,7 @@ fun BetScreen( } } } - items(5){ + items(bets){ BetScreenCard( creator = "Lucas", category = "Études", @@ -108,7 +122,7 @@ fun BetScreen( date = "11 Sept.", time = "13:00", players = List(3){ null }, - onClickParticipate = showBetStatus, + onClickParticipate = { selectBet(it) }, modifier = Modifier.padding(horizontal = horizontalPadding) ) Spacer(modifier = Modifier.height(24.dp)) diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/BetCreationScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/BetCreationScreen.kt index 928d086..ae6194a 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/BetCreationScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/BetCreationScreen.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.platform.LocalFocusManager 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.BetType +import fr.iut.alldev.allin.data.model.bet.BetType import fr.iut.alldev.allin.ext.getIcon import fr.iut.alldev.allin.ext.getTitle import fr.iut.alldev.allin.ui.betcreation.tabs.BetCreationScreenAnswerTab diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/tabs/BetCreationScreenAnswerTab.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/tabs/BetCreationScreenAnswerTab.kt index 946397e..343c91b 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/tabs/BetCreationScreenAnswerTab.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betcreation/tabs/BetCreationScreenAnswerTab.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.Modifier 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.BetType +import fr.iut.alldev.allin.data.model.bet.BetType import fr.iut.alldev.allin.ext.getTitle import fr.iut.alldev.allin.ui.betcreation.components.BetCreationScreenBottomText import fr.iut.alldev.allin.ui.core.AllInSelectionBox diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/BetStatusBottomSheet.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/BetStatusBottomSheet.kt index 6e7c525..9bd62c7 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/BetStatusBottomSheet.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/BetStatusBottomSheet.kt @@ -3,15 +3,19 @@ package fr.iut.alldev.allin.ui.betstatus import androidx.compose.animation.* import androidx.compose.foundation.layout.* import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier 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.visitor.BetStatusBottomSheetDisplayBetVisitor import fr.iut.alldev.allin.ui.core.AllInBottomSheet +import fr.iut.alldev.allin.vo.bet.BetVO internal const val SHEET_HEIGHT = .85f +private val visitor = BetStatusBottomSheetDisplayBetVisitor() + @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -19,7 +23,7 @@ fun BetStatusBottomSheet( state: SheetState, sheetVisibility: Boolean, sheetBackVisibility: Boolean, - betStatus: BetStatus, + bet: BetVO?, onDismiss: ()->Unit ) { AnimatedVisibility( @@ -31,9 +35,11 @@ fun BetStatusBottomSheet( targetOffsetY = { it } ) ) { - BetStatusBottomSheetBack( - status = betStatus - ) + bet?.let { + BetStatusBottomSheetBack( + status = it.bet.betStatus + ) + } } AllInBottomSheet( @@ -46,6 +52,7 @@ fun BetStatusBottomSheet( Modifier .fillMaxHeight(SHEET_HEIGHT) ) { + bet?.accept(visitor) } } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/BetStatusBottomSheetBack.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/BetStatusBottomSheetBack.kt index 6282f07..2232dca 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/BetStatusBottomSheetBack.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/BetStatusBottomSheetBack.kt @@ -16,7 +16,7 @@ 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.model.BetStatus +import fr.iut.alldev.allin.data.model.bet.BetStatus import fr.iut.alldev.allin.ext.getColor import fr.iut.alldev.allin.ext.getTextColor import fr.iut.alldev.allin.ext.getTitle @@ -45,7 +45,7 @@ fun BetStatusBottomSheetBack( style = AllInTheme.typography.h2.copy( fontStyle = FontStyle.Italic ), - fontSize = 20.sp, + fontSize = 30.sp, modifier = Modifier.weight(1f) ) Icon( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/YesNoStarBar.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/YesNoStarBar.kt new file mode 100644 index 0000000..13ac6df --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/components/YesNoStarBar.kt @@ -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 { + override val values = sequenceOf(0f, .33f, .5f, .66f, 1f) +} + + +@Preview +@Composable +private fun YesNoStatBarPreview( + @PreviewParameter(YesNoStatBarPreviewProvider::class) percentage: Float +) { + AllInTheme { + YesNoStatBar(percentage) + } +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt new file mode 100644 index 0000000..cc5bfca --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/betstatus/visitor/BetStatusBottomSheetDisplayBetVisitor.kt @@ -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") + } +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextField.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextField.kt index 95f99b8..8a904c3 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextField.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/AllInTextField.kt @@ -15,10 +15,7 @@ import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.KeyboardType -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.input.* import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.TextUnit @@ -45,6 +42,7 @@ fun AllInTextField( bringIntoViewRequester: BringIntoViewRequester, visualTransformation: VisualTransformation = VisualTransformation.None, keyboardType: KeyboardType = KeyboardType.Text, + imeAction: ImeAction = ImeAction.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, borderColor: Color = AllInTheme.themeColors.on_background_2, containerColor: Color = AllInTheme.themeColors.background, @@ -114,7 +112,7 @@ fun AllInTextField( }, textStyle = AllInTheme.typography.r, enabled = enabled, - keyboardOptions = KeyboardOptions(keyboardType = keyboardType), + keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = imeAction), keyboardActions = keyboardActions, shape = AbsoluteSmoothCornerShape(10.dp, 100), colors = OutlinedTextFieldDefaults.colors( @@ -137,6 +135,7 @@ fun AllInPasswordField( placeholder: String, value: String, modifier: Modifier = Modifier, + imeAction: ImeAction = ImeAction.Default, keyboardType: KeyboardType = KeyboardType.Password, keyboardActions: KeyboardActions = KeyboardActions.Default, errorText: String? = null, @@ -151,6 +150,7 @@ fun AllInPasswordField( modifier = modifier, errorText = errorText, placeholder = placeholder, + imeAction = imeAction, keyboardActions = keyboardActions, visualTransformation = if (hidden) PasswordVisualTransformation() else VisualTransformation.None, value = value, diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/PercentagePositionnedElement.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/PercentagePositionnedElement.kt new file mode 100644 index 0000000..52e48a0 --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/PercentagePositionnedElement.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/StatBar.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/StatBar.kt index 33f32d5..83d4ccd 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/core/StatBar.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/core/StatBar.kt @@ -5,17 +5,13 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* 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.graphics.Color 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.ui.theme.AllInTheme @@ -25,9 +21,7 @@ fun StatBar( ) { val radius100percent = if(percentage==1f) 50 else 0 val radius0percent = if(percentage==0f) 50 else 0 - Box( - Modifier.padding(horizontal = 9.dp) - ){ + Box{ Row( Modifier.align(Alignment.Center) ){ @@ -45,18 +39,7 @@ fun StatBar( ) .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) { Spacer(modifier = Modifier.width(15.dp)) } @@ -75,44 +58,27 @@ fun StatBar( .background(AllInTheme.colors.allIn_Bar2ndGradient) ) } - Box( - Modifier - .fillMaxWidth() - .align(Alignment.Center) - ) { - when (percentage) { - 0f -> { - Icon( - painter = painterResource(id = R.drawable.fire_solid), - tint = AllInTheme.colors.allIn_BarPink, - contentDescription = null, - modifier = Modifier - .size(32.dp) - ) - } - 1f -> { - Icon( - painter = painterResource(id = R.drawable.fire_solid), - tint = AllInTheme.colors.allIn_BarPurple, - contentDescription = null, - modifier = Modifier - .align(Alignment.CenterEnd) - .size(32.dp) + PercentagePositionnedElement(percentage = percentage) { + when(percentage){ + 0f -> Icon( + painter = painterResource(id = R.drawable.fire_solid), + tint = AllInTheme.colors.allIn_BarPink, + contentDescription = null, + modifier = Modifier.size(32.dp) ) - } - else -> { - Row { - Spacer(modifier = Modifier.fillMaxWidth(percentage)) - Image( - painter = painterResource(id = R.drawable.bar_flame), - contentDescription = null, - modifier = Modifier - .size(32.dp) - .offset(x = (-9).dp) - ) - } - } + 1f -> Icon( + painter = painterResource(id = R.drawable.fire_solid), + tint = AllInTheme.colors.allIn_BarPurple, + contentDescription = null, + modifier = Modifier.size(32.dp) + ) + else -> Image( + painter = painterResource(id = R.drawable.bar_flame), + contentDescription = null, + modifier = Modifier.size(32.dp) + ) } + } } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginScreen.kt index 2e11705..4192a39 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text @@ -16,9 +17,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment 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.text.AnnotatedString 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.unit.dp import androidx.compose.ui.unit.sp @@ -34,6 +38,7 @@ fun LoginScreen( navigateToRegister: ()->Unit, loginViewModel: LoginViewModel = hiltViewModel() ) { + val focusManager = LocalFocusManager.current val bringIntoViewRequester = BringIntoViewRequester() val loading by remember{ loginViewModel.loading } @@ -42,6 +47,18 @@ fun LoginScreen( val (username, setUsername) = remember{ loginViewModel.username } val (password, setPassword) = remember{ loginViewModel.password } + val keyboardActions = remember { + KeyboardActions( + onDone = { + focusManager.clearFocus() + loginViewModel.onLogin(navigateToDashboard) + }, + onNext = { + focusManager.moveFocus(FocusDirection.Down) + } + ) + } + Box( Modifier .fillMaxSize() @@ -78,14 +95,18 @@ fun LoginScreen( placeholder = stringResource(id = R.string.username), value = username, onValueChange = setUsername, - bringIntoViewRequester = bringIntoViewRequester + bringIntoViewRequester = bringIntoViewRequester, + imeAction = ImeAction.Next, + keyboardActions = keyboardActions ) AllInPasswordField( modifier = Modifier.fillMaxWidth(), placeholder = stringResource(id = R.string.password), value = password, onValueChange = setPassword, - bringIntoViewRequester = bringIntoViewRequester + bringIntoViewRequester = bringIntoViewRequester, + imeAction = ImeAction.Done, + keyboardActions = keyboardActions ) } ClickableText( diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt index a92bba6..ae6264e 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/login/LoginViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException import fr.iut.alldev.allin.data.repository.UserRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -29,7 +30,7 @@ class LoginViewModel @Inject constructor( withContext(Dispatchers.IO) { try{ userRepository.login(username.value, password.value) - } catch (e: retrofit2.HttpException){ + } catch (e: AllInAPIException){ hasError.value = true } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt index 0611601..8b57b1f 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController 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.main.components.AllInScaffold 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.popUpTo import fr.iut.alldev.allin.ui.theme.AllInTheme +import fr.iut.alldev.allin.vo.bet.factory.toBetVO import kotlinx.coroutines.launch private val topLevelDestinations = listOf( @@ -59,6 +59,10 @@ fun MainScreen( mainViewModel.currentUser } + val (selectedBet, setSelectedBet) = remember{ + mainViewModel.selectedBet + } + val scope = rememberCoroutineScope() val (statusVisibility, sheetBackVisibility, setStatusVisibility) @@ -100,7 +104,10 @@ fun MainScreen( ) { AllInDrawerNavHost( navController = navController, - setStatusVisibility = setStatusVisibility + selectBet = { + setSelectedBet(it) + setStatusVisibility(true) + } ) } } @@ -113,9 +120,8 @@ fun MainScreen( onDismiss = { setStatusVisibility(false) }, - betStatus = BetStatus.IN_PROGRESS + bet = selectedBet?.toBetVO() ) - BackHandler( enabled = drawerState.isOpen ) { diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt index ceed9b9..a863bc9 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/main/MainViewModel.kt @@ -3,8 +3,8 @@ package fr.iut.alldev.allin.ui.main import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel 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.data.model.BetStatus import fr.iut.alldev.allin.data.model.User import javax.inject.Inject @@ -13,5 +13,5 @@ import javax.inject.Inject class MainViewModel @Inject constructor( @AllInCurrentUser val currentUser: User ) : ViewModel() { - val selectedBet = mutableStateOf(null) + val selectedBet = mutableStateOf(null) } \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt index 602f6b9..22b85e9 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/navigation/NavHost.kt @@ -11,6 +11,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable 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.betcreation.BetCreationScreen import fr.iut.alldev.allin.ui.login.LoginScreen @@ -80,7 +81,7 @@ fun AllInNavHost(modifier: Modifier = Modifier, internal fun AllInDrawerNavHost( modifier: Modifier = Modifier, navController: NavHostController, - setStatusVisibility: (Boolean) -> Unit, + selectBet: (Bet) -> Unit, startDestination: String = Routes.PUBLIC_BETS ) { NavHost( @@ -92,7 +93,7 @@ internal fun AllInDrawerNavHost( ) { composable(route = Routes.PUBLIC_BETS) { BetScreen( - showBetStatus = { setStatusVisibility(true) } + selectBet = selectBet ) } composable(route = Routes.BET_CREATION) { diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterScreen.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterScreen.kt index 536ca5d..a64efcb 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterScreen.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterScreen.kt @@ -5,12 +5,16 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material3.Text import androidx.compose.runtime.* 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.text.AnnotatedString 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.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -31,6 +35,9 @@ fun RegisterScreen( navigateToLogin: () -> Unit, registerViewModel: RegisterViewModel = hiltViewModel(), ) { + + val focusManager = LocalFocusManager.current + val loading by remember{ registerViewModel.loading } val usernameError by remember{ registerViewModel.usernameError } @@ -50,6 +57,23 @@ fun RegisterScreen( val emailFieldName = stringResource(id = R.string.email) 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( Modifier .fillMaxSize() @@ -100,7 +124,9 @@ fun RegisterScreen( onValueChange = setUsername, maxChar = 20, errorText = usernameError.errorResource(), - bringIntoViewRequester = bringIntoViewRequester + bringIntoViewRequester = bringIntoViewRequester, + imeAction = ImeAction.Next, + keyboardActions = keyboardActions ) AllInTextField( modifier = Modifier.fillMaxWidth(), @@ -109,7 +135,9 @@ fun RegisterScreen( onValueChange = setEmail, errorText = emailError.errorResource(), keyboardType = KeyboardType.Email, - bringIntoViewRequester = bringIntoViewRequester + bringIntoViewRequester = bringIntoViewRequester, + imeAction = ImeAction.Next, + keyboardActions = keyboardActions ) AllInPasswordField( modifier = Modifier.fillMaxWidth(), @@ -117,7 +145,9 @@ fun RegisterScreen( value = password, errorText = passwordError.errorResource(), onValueChange = setPassword, - bringIntoViewRequester = bringIntoViewRequester + bringIntoViewRequester = bringIntoViewRequester, + imeAction = ImeAction.Next, + keyboardActions = keyboardActions ) AllInPasswordField( modifier = Modifier.fillMaxWidth(), @@ -125,7 +155,9 @@ fun RegisterScreen( value = passwordValidation, errorText = passwordValidationError.errorResource(), onValueChange = setPasswordValidation, - bringIntoViewRequester = bringIntoViewRequester + bringIntoViewRequester = bringIntoViewRequester, + imeAction = ImeAction.Done, + keyboardActions = keyboardActions ) } } diff --git a/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt b/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt index 13c5061..d274c26 100644 --- a/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt +++ b/src/app/src/main/java/fr/iut/alldev/allin/ui/register/RegisterViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import fr.iut.alldev.allin.data.api.interceptors.AllInAPIException import fr.iut.alldev.allin.data.repository.UserRepository import fr.iut.alldev.allin.ext.ALLOWED_SYMBOLS import fr.iut.alldev.allin.ext.FieldErrorState @@ -89,11 +90,17 @@ class RegisterViewModel @Inject constructor( passwordFieldName.lowercase() ) if(!hasError.value) { - userRepository.register( - username.value, - email.value, - password.value - ) + try { + userRepository.register( + username.value, + email.value, + password.value + ) + }catch(e : AllInAPIException){ + usernameError.value = FieldErrorState.AlreadyUsed(username.value) + emailError.value = FieldErrorState.AlreadyUsed(email.value) + hasError.value = true + } } } if(!hasError.value){ diff --git a/src/app/src/main/java/fr/iut/alldev/allin/vo/ViewObject.kt b/src/app/src/main/java/fr/iut/alldev/allin/vo/ViewObject.kt new file mode 100644 index 0000000..dceeeb1 --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/vo/ViewObject.kt @@ -0,0 +1,8 @@ +package fr.iut.alldev.allin.vo + +import androidx.compose.runtime.Composable + +interface ViewObject{ + @Composable + fun accept(v: V) +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/BetVO.kt b/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/BetVO.kt new file mode 100644 index 0000000..5520a94 --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/BetVO.kt @@ -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(val bet: T) +: ViewObject { + @Composable + abstract override fun accept(v: DisplayBetVisitor) +} + +class YesNoBetVO(bet: YesNoBet) : BetVO(bet){ + @Composable + override fun accept(v: DisplayBetVisitor){ + v.visitYesNoBet(b = bet) + } +} + +class MatchBetVO(bet: MatchBet) : BetVO(bet){ + @Composable + override fun accept(v: DisplayBetVisitor){ + v.visitMatchBet(b = bet) + } +} \ No newline at end of file diff --git a/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/factory/BetVOFactory.kt b/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/factory/BetVOFactory.kt new file mode 100644 index 0000000..1b93d5d --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/factory/BetVOFactory.kt @@ -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 { + abstract fun create(bet: @UnsafeVariance T): BetVO<@UnsafeVariance T> +} + +class YesNoBetVOFactory : BetVOFactory() { + override fun create(bet: YesNoBet) = + YesNoBetVO(bet) +} + +class MatchBetVOFactory : BetVOFactory() { + override fun create(bet: MatchBet) = + MatchBetVO(bet) +} + +fun Bet.toBetVO() = + betTypeToVOMap[this.javaClass]?.create(this) diff --git a/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/visitor/BetVisitor.kt b/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/visitor/BetVisitor.kt new file mode 100644 index 0000000..06101ea --- /dev/null +++ b/src/app/src/main/java/fr/iut/alldev/allin/vo/bet/visitor/BetVisitor.kt @@ -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) +} \ No newline at end of file diff --git a/src/app/src/main/res/values-fr/strings.xml b/src/app/src/main/res/values-fr/strings.xml index 6731556..1772fd4 100644 --- a/src/app/src/main/res/values-fr/strings.xml +++ b/src/app/src/main/res/values-fr/strings.xml @@ -17,6 +17,9 @@ Le %s a un mauvais format : %s. Les champs ne sont pas identiques. Le %s doit contenir au moins un caractère spécial : %s. + %s est déjà utilisé. + Oui + Non Bets diff --git a/src/app/src/main/res/values/strings.xml b/src/app/src/main/res/values/strings.xml index 04dd4bf..75b5482 100644 --- a/src/app/src/main/res/values/strings.xml +++ b/src/app/src/main/res/values/strings.xml @@ -19,7 +19,10 @@ The %s has bad format : %s. The fields are not identical. The %s must contain at least one special character : %s. - + %s is already used. + Yes + No + Bets Best win diff --git a/src/data/build.gradle b/src/data/build.gradle index 853b789..2414151 100644 --- a/src/data/build.gradle +++ b/src/data/build.gradle @@ -55,6 +55,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.9.0' + + //Tests testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' @@ -66,7 +68,7 @@ dependencies { // Retrofit api "com.squareup.retrofit2:retrofit:2.9.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" implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" } \ No newline at end of file diff --git a/src/data/src/debug/java/fr/iut/alldev/allin/data/di/DebugNetworkModule.kt b/src/data/src/debug/java/fr/iut/alldev/allin/data/di/DebugNetworkModule.kt index 8b47e52..e95770f 100644 --- a/src/data/src/debug/java/fr/iut/alldev/allin/data/di/DebugNetworkModule.kt +++ b/src/data/src/debug/java/fr/iut/alldev/allin/data/di/DebugNetworkModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import fr.iut.alldev.allin.data.api.interceptors.ErrorInterceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import javax.inject.Singleton @@ -21,5 +22,6 @@ internal object DebugNetworkModule { level = HttpLoggingInterceptor.Level.BODY } ) + .addInterceptor(ErrorInterceptor()) .build() } \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt index e96d2d4..9dd4a6c 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/AllInApi.kt @@ -5,7 +5,6 @@ import fr.iut.alldev.allin.data.api.model.ResponseUser import retrofit2.http.Body import retrofit2.http.POST - interface AllInApi { @POST("users/login") suspend fun login( diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/interceptors/ErrorInterceptor.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/interceptors/ErrorInterceptor.kt new file mode 100644 index 0000000..16abb5a --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/interceptors/ErrorInterceptor.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt index 985535f..3cbcd6e 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/api/model/ResponseUser.kt @@ -23,6 +23,6 @@ data class ResponseUser( @Keep @Serializable data class CheckUser( - val username: String, + val login: String, val password: String, ) \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/di/ApiModule.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/di/ApiModule.kt index 9bc2238..3883391 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/di/ApiModule.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/di/ApiModule.kt @@ -13,7 +13,6 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) class ApiModule { - @Provides @Singleton fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi { diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/di/NetworkModule.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/di/NetworkModule.kt index 05ada4a..0c26aae 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/di/NetworkModule.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/di/NetworkModule.kt @@ -5,6 +5,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl @@ -32,10 +33,13 @@ internal object NetworkModule { @Provides fun provideUrl(): HttpUrl = "https://codefirst.iut.uca.fr/containers/AllDev-api/".toHttpUrl() + @OptIn(ExperimentalSerializationApi::class) fun createRetrofit(url: HttpUrl, okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder() .client(okHttpClient) - .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .addConverterFactory( + json.asConverterFactory("application/json".toMediaType()) + ) .baseUrl(url) .build() -} \ No newline at end of file +} diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/ext/NumberExt.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/ext/NumberExt.kt new file mode 100644 index 0000000..a3a597b --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/ext/NumberExt.kt @@ -0,0 +1,3 @@ +package fr.iut.alldev.allin.data.ext + +fun Float.toPercentageString(precision: Int = 0) = String.format("%.${precision}f", this*100) + "%" \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt new file mode 100644 index 0000000..5475c2f --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/Bet.kt @@ -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, +) \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/BetStatus.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetStatus.kt similarity index 60% rename from src/data/src/main/java/fr/iut/alldev/allin/data/model/BetStatus.kt rename to src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetStatus.kt index 9f6e04e..4c10012 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/BetStatus.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetStatus.kt @@ -1,4 +1,4 @@ -package fr.iut.alldev.allin.data.model +package fr.iut.alldev.allin.data.model.bet enum class BetStatus { FINISHED, diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/BetType.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetType.kt similarity index 57% rename from src/data/src/main/java/fr/iut/alldev/allin/data/model/BetType.kt rename to src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetType.kt index ac8e8a7..2fa2bc7 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/model/BetType.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/BetType.kt @@ -1,4 +1,4 @@ -package fr.iut.alldev.allin.data.model +package fr.iut.alldev.allin.data.model.bet enum class BetType { YES_NO, diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt new file mode 100644 index 0000000..98f08f0 --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/MatchBet.kt @@ -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 +) \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt new file mode 100644 index 0000000..5300eab --- /dev/null +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/model/bet/YesNoBet.kt @@ -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 +) \ No newline at end of file diff --git a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt index e772746..8ae17e6 100644 --- a/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt +++ b/src/data/src/main/java/fr/iut/alldev/allin/data/repository/impl/UserRepositoryImpl.kt @@ -13,12 +13,13 @@ class UserRepositoryImpl @Inject constructor( override suspend fun login(username: String, password: String) { currentUser = api.login( CheckUser( - username = username, + login = username, password = password ) ).toUser() } + override suspend fun register(username: String, email: String, password: String) { currentUser = api.register( ResponseUser( @@ -28,5 +29,6 @@ class UserRepositoryImpl @Inject constructor( nbCoins = 0 ) ).toUser() + } } \ No newline at end of file diff --git a/src/data/src/release/java/fr.iut.alldev.allin.data.di/ReleaseNetworkModule.kt b/src/data/src/release/java/fr.iut.alldev.allin.data.di/ReleaseNetworkModule.kt index 328e8a8..4bb6870 100644 --- a/src/data/src/release/java/fr.iut.alldev.allin.data.di/ReleaseNetworkModule.kt +++ b/src/data/src/release/java/fr.iut.alldev.allin.data.di/ReleaseNetworkModule.kt @@ -16,5 +16,6 @@ internal object ReleaseNetworkModule { @Singleton fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(ErrorInterceptor()) .build() } \ No newline at end of file