├── .cirrus.yml ├── .cirrus └── populate_properties.sh ├── .codeclimate.yml ├── .github └── workflows │ ├── detekt.yml │ └── wiki.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── detekt.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mobile ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ch │ │ └── epfl │ │ └── sdp │ │ └── mobile │ │ └── test │ │ ├── Assertions.kt │ │ ├── Collections.kt │ │ ├── Coroutines.kt │ │ ├── application │ │ ├── AuthenticationFacadeTest.kt │ │ ├── ChessFacadeTest.kt │ │ ├── Dsl.kt │ │ ├── SocialFacadeTest.kt │ │ ├── SpeechFacadeTest.kt │ │ ├── StoreDocumentsTest.kt │ │ ├── TournamentFacadeTest.kt │ │ ├── chess │ │ │ ├── engine │ │ │ │ ├── ActionTest.kt │ │ │ │ ├── GameTest.kt │ │ │ │ ├── Games.kt │ │ │ │ ├── PlayDsl.kt │ │ │ │ ├── PositionTest.kt │ │ │ │ ├── implementation │ │ │ │ │ ├── MutableBoardPieceTest.kt │ │ │ │ │ ├── MutableBoardTest.kt │ │ │ │ │ └── PersistentBoardTest.kt │ │ │ │ ├── rules │ │ │ │ │ └── CheckmateTest.kt │ │ │ │ └── truth │ │ │ │ │ ├── PieceBoardSubject.kt │ │ │ │ │ └── PieceSubject.kt │ │ │ ├── notation │ │ │ │ ├── AlgebraicNotationTest.kt │ │ │ │ ├── FenNotationTest.kt │ │ │ │ ├── FlowsTest.kt │ │ │ │ └── UciNotationTest.kt │ │ │ └── parser │ │ │ │ └── ParserTest.kt │ │ └── tournaments │ │ │ └── TournamentTest.kt │ │ ├── infrastructure │ │ ├── assets │ │ │ └── fake │ │ │ │ ├── FakeAssetManager.kt │ │ │ │ └── FakeAssetManagerTest.kt │ │ ├── persistence │ │ │ ├── auth │ │ │ │ ├── Dsl.kt │ │ │ │ ├── DslTest.kt │ │ │ │ ├── SuspendingAuth.kt │ │ │ │ ├── fake │ │ │ │ │ ├── FakeAuth.kt │ │ │ │ │ ├── FakeAuthRecord.kt │ │ │ │ │ └── FakeUser.kt │ │ │ │ └── firestore │ │ │ │ │ └── FirebaseAuthTest.kt │ │ │ ├── datastore │ │ │ │ ├── Dsl.kt │ │ │ │ ├── DslTest.kt │ │ │ │ ├── androidx │ │ │ │ │ ├── AndroidXDataStoreFactoryTest.kt │ │ │ │ │ ├── AndroidXKeyFactoryTest.kt │ │ │ │ │ ├── AndroidXMutablePreferencesTest.kt │ │ │ │ │ ├── AndroidXPreferencesDataStoreTest.kt │ │ │ │ │ └── AndroidXPreferencesTest.kt │ │ │ │ └── fake │ │ │ │ │ ├── FakeDataStoreFactory.kt │ │ │ │ │ ├── FakeKey.kt │ │ │ │ │ ├── FakeKeyFactory.kt │ │ │ │ │ ├── FakeMutablePreferences.kt │ │ │ │ │ ├── FakePreferences.kt │ │ │ │ │ └── FakePreferencesDataStore.kt │ │ │ └── store │ │ │ │ ├── Dsl.kt │ │ │ │ ├── DslNestingTest.kt │ │ │ │ ├── DslOrderingTest.kt │ │ │ │ ├── DslTest.kt │ │ │ │ ├── RecordingDocumentEditScopeTest.kt │ │ │ │ ├── fake │ │ │ │ ├── FakeCollectionReference.kt │ │ │ │ ├── FakeDocumentDocumentIdTest.kt │ │ │ │ ├── FakeDocumentId.kt │ │ │ │ ├── FakeDocumentRecord.kt │ │ │ │ ├── FakeDocumentReference.kt │ │ │ │ ├── FakeDocumentSnapshot.kt │ │ │ │ ├── FakeFieldMutation.kt │ │ │ │ ├── FakeQuerySnapshot.kt │ │ │ │ ├── FakeStore.kt │ │ │ │ ├── FakeTransaction.kt │ │ │ │ ├── FieldValueTest.kt │ │ │ │ ├── query │ │ │ │ │ ├── AbstractFakeQuery.kt │ │ │ │ │ ├── Comparisons.kt │ │ │ │ │ ├── FakeQuery.kt │ │ │ │ │ ├── LimitFakeQueryDecorator.kt │ │ │ │ │ ├── OrderByQueryDecorator.kt │ │ │ │ │ └── WhereFakeQueryDecorator.kt │ │ │ │ └── serialization │ │ │ │ │ └── Records.kt │ │ │ │ └── firestore │ │ │ │ ├── FirestoreCollectionReferenceTest.kt │ │ │ │ ├── FirestoreDocumentReferenceTest.kt │ │ │ │ ├── FirestoreFieldValueTest.kt │ │ │ │ ├── FirestoreQueryTest.kt │ │ │ │ ├── FirestoreStoreTest.kt │ │ │ │ └── FirestoreTransactionTest.kt │ │ ├── sound │ │ │ ├── android │ │ │ │ └── AndroidSoundPlayerTest.kt │ │ │ └── fake │ │ │ │ └── FakeSoundPlayer.kt │ │ ├── speech │ │ │ ├── FailingSpeechRecognizerFactory.kt │ │ │ ├── IllegalSpeechRecognizerFactory.kt │ │ │ ├── LegalSpeechRecognizerFactory.kt │ │ │ ├── SuspendingSpeechRecognizerFactory.kt │ │ │ ├── UnknownCommandSpeechRecognizerFactory.kt │ │ │ └── android │ │ │ │ ├── AndroidSpeechRecognizerFactoryTest.kt │ │ │ │ ├── AndroidSpeechRecognizerTest.kt │ │ │ │ └── RecognitionListenerAdapterTest.kt │ │ ├── time │ │ │ └── fake │ │ │ │ └── FakeTimeProvider.kt │ │ └── tts │ │ │ └── android │ │ │ ├── AndroidTextToSpeechRecognizerTest.kt │ │ │ └── FakeTextToSpeechFactory.kt │ │ ├── state │ │ ├── AuthenticatedUserProfileScreenStateTest.kt │ │ ├── AuthenticationApiAuthenticationScreenStateTest.kt │ │ ├── ClassicChessBoardStateTest.kt │ │ ├── CompositionLocalsTest.kt │ │ ├── Dsl.kt │ │ ├── HomeActivityTest.kt │ │ ├── LocalizedStrings.kt │ │ ├── NavigationTest.kt │ │ ├── ProfileColorTest.kt │ │ ├── StatefulArScreenTest.kt │ │ ├── StatefulContestScreenTest.kt │ │ ├── StatefulCreateTournamentScreenTest.kt │ │ ├── StatefulEditLanguageDialogTest.kt │ │ ├── StatefulEditProfileImageDialogTest.kt │ │ ├── StatefulEditProfileNameDialogTest.kt │ │ ├── StatefulFollowingScreenTest.kt │ │ ├── StatefulGameScreenTest.kt │ │ ├── StatefulHomeTest.kt │ │ ├── StatefulPlayScreenTest.kt │ │ ├── StatefulPrepareGameScreenTest.kt │ │ ├── StatefulProfileScreenTest.kt │ │ ├── StatefulPuzzleGameScreenTest.kt │ │ ├── StatefulPuzzleSelectionScreenTest.kt │ │ ├── StatefulSettingsScreenTest.kt │ │ ├── Utils.kt │ │ ├── game │ │ │ └── MatchChessBoardStateTest.kt │ │ └── tournaments │ │ │ ├── StatefulFiltersDialogScreenTest.kt │ │ │ └── StatefulTournamentDetailsScreenTest.kt │ │ └── ui │ │ ├── AbstractRobot.kt │ │ ├── Layout.kt │ │ ├── authentication │ │ ├── AuthenticationRobot.kt │ │ ├── AuthenticationScreenTest.kt │ │ ├── LoadingButtonTest.kt │ │ └── PasswordTextFieldTest.kt │ │ ├── game │ │ ├── ChessBoardRobot+PlayDsl.kt │ │ ├── ChessBoardRobot.kt │ │ ├── ChessBoardTest.kt │ │ ├── ConfettiTest.kt │ │ ├── GameScreenRobot.kt │ │ ├── GameScreenTest.kt │ │ ├── PromoteDialogTest.kt │ │ └── ar │ │ │ └── ChessSceneTest.kt │ │ ├── home │ │ └── HomeSectionRobot.kt │ │ ├── i18n │ │ ├── FrenchTest.kt │ │ ├── GermanTest.kt │ │ └── SwissGermanTest.kt │ │ ├── play │ │ ├── ExpandableFloatingActionButtonTest.kt │ │ └── PlayScreenTest.kt │ │ ├── prepare_game │ │ ├── PrepareGameRobot.kt │ │ └── PrepareGameScreenTest.kt │ │ ├── profile │ │ ├── ProfileRobot.kt │ │ ├── UserScreenTest.kt │ │ └── VisitedProfileScreenTest.kt │ │ ├── screenshots │ │ ├── BitmapMatcher.kt │ │ ├── Golden.kt │ │ └── matchers │ │ │ ├── MSSIMMatcher.kt │ │ │ └── PixelPerfectMatcher.kt │ │ ├── setting │ │ ├── SettingScreenTest.kt │ │ └── SettingTabBarTest.kt │ │ ├── social │ │ ├── SearchFieldTest.kt │ │ ├── SocialCardTest.kt │ │ └── SocialScreenTest.kt │ │ └── tournaments │ │ ├── BadgeTest.kt │ │ ├── ContestScreenTest.kt │ │ ├── CreateDialogTest.kt │ │ ├── FiltersDialogTest.kt │ │ ├── NextStepBannerTest.kt │ │ ├── PoolCardTest.kt │ │ └── TournamentsDetailsTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── models │ │ │ ├── bishop.glb │ │ │ ├── blue.glb │ │ │ ├── board.glb │ │ │ ├── green.glb │ │ │ ├── king.glb │ │ │ ├── knight.glb │ │ │ ├── pawn.glb │ │ │ ├── queen.glb │ │ │ ├── red.glb │ │ │ ├── rook.glb │ │ │ └── white.glb │ │ └── puzzles │ │ │ └── puzzles.csv │ ├── ic_launcher-playstore.png │ ├── java │ │ └── ch │ │ │ └── epfl │ │ │ └── sdp │ │ │ └── mobile │ │ │ ├── application │ │ │ ├── Profile.kt │ │ │ ├── StoreDocuments.kt │ │ │ ├── authentication │ │ │ │ ├── AuthenticatedUser.kt │ │ │ │ ├── AuthenticationFacade.kt │ │ │ │ ├── AuthenticationResult.kt │ │ │ │ ├── AuthenticationUser.kt │ │ │ │ └── NotAuthenticatedUser.kt │ │ │ ├── chess │ │ │ │ ├── ChessFacade.kt │ │ │ │ ├── Match.kt │ │ │ │ ├── Puzzle.kt │ │ │ │ ├── engine │ │ │ │ │ ├── Action.kt │ │ │ │ │ ├── Board.kt │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Delta.kt │ │ │ │ │ ├── Game.kt │ │ │ │ │ ├── NextStep.kt │ │ │ │ │ ├── Piece.kt │ │ │ │ │ ├── PieceIdentifier.kt │ │ │ │ │ ├── Position.kt │ │ │ │ │ ├── Rank.kt │ │ │ │ │ ├── implementation │ │ │ │ │ │ ├── Actions.kt │ │ │ │ │ │ ├── Conversions.kt │ │ │ │ │ │ ├── MutableBoard.kt │ │ │ │ │ │ ├── MutableBoardActionScope.kt │ │ │ │ │ │ ├── MutableBoardAttacked.kt │ │ │ │ │ │ ├── MutableBoardGame.kt │ │ │ │ │ │ ├── MutableBoardPiece.kt │ │ │ │ │ │ ├── MutableBoardScope.kt │ │ │ │ │ │ └── PersistentBoard.kt │ │ │ │ │ ├── rules │ │ │ │ │ │ ├── AttackRules.kt │ │ │ │ │ │ ├── AttackTowardsRules.kt │ │ │ │ │ │ ├── BishopRules.kt │ │ │ │ │ │ ├── KingRules.kt │ │ │ │ │ │ ├── KnightRules.kt │ │ │ │ │ │ ├── PawnRules.kt │ │ │ │ │ │ ├── QueenRules.kt │ │ │ │ │ │ ├── RookRules.kt │ │ │ │ │ │ └── Rules.kt │ │ │ │ │ └── utils │ │ │ │ │ │ └── Packing.kt │ │ │ │ ├── notation │ │ │ │ │ ├── AlgebraicNotation.kt │ │ │ │ │ ├── AlgebraicNotationCombinators.kt │ │ │ │ │ ├── CommonNotationCombinators.kt │ │ │ │ │ ├── FenNotation.kt │ │ │ │ │ ├── FenNotationCombinators.kt │ │ │ │ │ ├── Flows.kt │ │ │ │ │ ├── UCINotation.kt │ │ │ │ │ └── UCINotationCombinators.kt │ │ │ │ ├── parser │ │ │ │ │ ├── Combinators.kt │ │ │ │ │ ├── Parser.kt │ │ │ │ │ └── StringCombinators.kt │ │ │ │ └── voice │ │ │ │ │ ├── VoiceInput.kt │ │ │ │ │ └── VoiceInputCombinator.kt │ │ │ ├── settings │ │ │ │ └── SettingsFacade.kt │ │ │ ├── social │ │ │ │ └── SocialFacade.kt │ │ │ ├── speech │ │ │ │ ├── ChessDictionary.kt │ │ │ │ ├── ChessSpeechEnglishDictionary.kt │ │ │ │ ├── SpeechFacade.kt │ │ │ │ └── SpeechFilterRules.kt │ │ │ └── tournaments │ │ │ │ ├── EliminationMatch.kt │ │ │ │ ├── Pool.kt │ │ │ │ ├── PoolDocumentPool.kt │ │ │ │ ├── PoolResults.kt │ │ │ │ ├── StoreDocumentTournament.kt │ │ │ │ ├── Tournament.kt │ │ │ │ ├── TournamentFacade.kt │ │ │ │ └── TournamentReference.kt │ │ │ ├── infrastructure │ │ │ ├── assets │ │ │ │ ├── AssetManager.kt │ │ │ │ └── android │ │ │ │ │ └── AndroidAssetManager.kt │ │ │ ├── persistence │ │ │ │ ├── auth │ │ │ │ │ ├── Auth.kt │ │ │ │ │ ├── User.kt │ │ │ │ │ └── firebase │ │ │ │ │ │ ├── FirebaseAuth.kt │ │ │ │ │ │ └── FirebaseUser.kt │ │ │ │ ├── datastore │ │ │ │ │ ├── DataStore.kt │ │ │ │ │ ├── DataStoreFactory.kt │ │ │ │ │ ├── Edit.kt │ │ │ │ │ ├── Key.kt │ │ │ │ │ ├── KeyFactory.kt │ │ │ │ │ ├── MutablePreferences.kt │ │ │ │ │ ├── Preferences.kt │ │ │ │ │ └── androidx │ │ │ │ │ │ ├── AndroidXDataStoreFactory.kt │ │ │ │ │ │ ├── AndroidXKey.kt │ │ │ │ │ │ ├── AndroidXKeyFactory.kt │ │ │ │ │ │ ├── AndroidXMutablePreferences.kt │ │ │ │ │ │ ├── AndroidXPreferences.kt │ │ │ │ │ │ └── AndroidXPreferencesDataStore.kt │ │ │ │ └── store │ │ │ │ │ ├── CollectionReference.kt │ │ │ │ │ ├── DocumentEditScope.kt │ │ │ │ │ ├── DocumentReference.kt │ │ │ │ │ ├── DocumentSnapshot.kt │ │ │ │ │ ├── FieldPath.kt │ │ │ │ │ ├── FieldValue.kt │ │ │ │ │ ├── Query.kt │ │ │ │ │ ├── QuerySnapshot.kt │ │ │ │ │ ├── RecordingDocumentEditScope.kt │ │ │ │ │ ├── Store.kt │ │ │ │ │ ├── Transaction.kt │ │ │ │ │ └── firestore │ │ │ │ │ ├── FirestoreCollectionReference.kt │ │ │ │ │ ├── FirestoreDocumentReference.kt │ │ │ │ │ ├── FirestoreDocumentSnapshot.kt │ │ │ │ │ ├── FirestoreFieldPath.kt │ │ │ │ │ ├── FirestoreFieldValue.kt │ │ │ │ │ ├── FirestoreQuery.kt │ │ │ │ │ ├── FirestoreQuerySnapshot.kt │ │ │ │ │ ├── FirestoreStore.kt │ │ │ │ │ └── FirestoreTransaction.kt │ │ │ ├── sound │ │ │ │ ├── SoundPlayer.kt │ │ │ │ └── android │ │ │ │ │ └── AndroidSoundPlayer.kt │ │ │ ├── speech │ │ │ │ ├── SpeechRecognizer.kt │ │ │ │ ├── SpeechRecognizerFactory.kt │ │ │ │ └── android │ │ │ │ │ ├── AndroidSpeechRecognizerFactory.kt │ │ │ │ │ └── RecognitionListenerAdapter.kt │ │ │ ├── time │ │ │ │ ├── TimeProvider.kt │ │ │ │ └── system │ │ │ │ │ └── SystemTimeProvider.kt │ │ │ └── tts │ │ │ │ ├── TextToSpeech.kt │ │ │ │ ├── TextToSpeechFactory.kt │ │ │ │ └── android │ │ │ │ └── AndroidTextToSpeechFactory.kt │ │ │ ├── state │ │ │ ├── CompositionLocals.kt │ │ │ ├── HomeActivity.kt │ │ │ ├── LocalizedStrings.kt │ │ │ ├── Navigation.kt │ │ │ ├── ProfileColor.kt │ │ │ ├── StatefulAuthenticationScreen.kt │ │ │ ├── StatefulContestsScreen.kt │ │ │ ├── StatefulCreateTournamentScreen.kt │ │ │ ├── StatefulEditLanguageDialog.kt │ │ │ ├── StatefulEditProfileImageDialog.kt │ │ │ ├── StatefulEditProfileNameDialog.kt │ │ │ ├── StatefulFollowingScreen.kt │ │ │ ├── StatefulGameScreen.kt │ │ │ ├── StatefulHome.kt │ │ │ ├── StatefulPlayScreen.kt │ │ │ ├── StatefulPrepareGameScreen.kt │ │ │ ├── StatefulProfileScreen.kt │ │ │ ├── StatefulPuzzleGameScreen.kt │ │ │ ├── StatefulPuzzleSelectionScreen.kt │ │ │ ├── StatefulSettingsScreen.kt │ │ │ ├── StatefulVisitedProfileScreen.kt │ │ │ ├── StatefullArScreen.kt │ │ │ ├── game │ │ │ │ ├── AbstractMovableChessBoardState.kt │ │ │ │ ├── ActualChessBoardState.kt │ │ │ │ ├── ActualGameScreenState.kt │ │ │ │ ├── ActualPuzzleGameScreenState.kt │ │ │ │ ├── core │ │ │ │ │ ├── GameDelegate.kt │ │ │ │ │ ├── MatchGameDelegate.kt │ │ │ │ │ ├── MutableGameDelegate.kt │ │ │ │ │ └── PuzzleGameDelegate.kt │ │ │ │ └── delegating │ │ │ │ │ ├── DelegatingChessBoardState.kt │ │ │ │ │ ├── DelegatingMovesInfoState.kt │ │ │ │ │ ├── DelegatingPlayersInfoState.kt │ │ │ │ │ ├── DelegatingPromotionMovableChessBoardState.kt │ │ │ │ │ ├── DelegatingPromotionState.kt │ │ │ │ │ ├── DelegatingPuzzleInfoState.kt │ │ │ │ │ ├── DelegatingPuzzlePromotionMovableChessBoardState.kt │ │ │ │ │ ├── DelegatingSpeechRecognizerState.kt │ │ │ │ │ └── DelegatingTextToSpeechState.kt │ │ │ └── tournaments │ │ │ │ ├── ActualTournamentDetailsState.kt │ │ │ │ ├── StatefulFiltersDialogScreen.kt │ │ │ │ ├── StatefulTournamentDetailsScreen.kt │ │ │ │ └── TournamentDetailsActions.kt │ │ │ └── ui │ │ │ ├── Colors.kt │ │ │ ├── DrawScope.kt │ │ │ ├── Graphics.kt │ │ │ ├── Models.kt │ │ │ ├── Motion.kt │ │ │ ├── PaddingValues.kt │ │ │ ├── Shapes.kt │ │ │ ├── Theme.kt │ │ │ ├── Typography.kt │ │ │ ├── authentication │ │ │ ├── AuthenticationScreen.kt │ │ │ ├── AuthenticationScreenState.kt │ │ │ ├── LoadingButton.kt │ │ │ └── PasswordTextField.kt │ │ │ ├── game │ │ │ ├── ChessBoardState.kt │ │ │ ├── Confetti.kt │ │ │ ├── GameScreen.kt │ │ │ ├── GameScreenState.kt │ │ │ ├── GameScreenTopBar.kt │ │ │ ├── MovableChessBoardState.kt │ │ │ ├── MovesInfoState.kt │ │ │ ├── PlayersInfoState.kt │ │ │ ├── PromoteDialog.kt │ │ │ ├── PromotionState.kt │ │ │ ├── SpeechRecognizerState.kt │ │ │ ├── TextToSpeechState.kt │ │ │ ├── ar │ │ │ │ ├── ArChessBoardScreen.kt │ │ │ │ └── ChessScene.kt │ │ │ └── classic │ │ │ │ ├── ChessBoardModifiers.kt │ │ │ │ └── ClassicChessBoard.kt │ │ │ ├── home │ │ │ └── HomeScaffold.kt │ │ │ ├── i18n │ │ │ ├── English.kt │ │ │ ├── French.kt │ │ │ ├── German.kt │ │ │ ├── Language.kt │ │ │ ├── LocalizedStrings.kt │ │ │ └── SwissGerman.kt │ │ │ ├── play │ │ │ ├── ExpandableFloatingActionButton.kt │ │ │ ├── PlayScreen.kt │ │ │ └── PlayScreenState.kt │ │ │ ├── prepare_game │ │ │ ├── ColorChoiceTabBar.kt │ │ │ ├── Dialog.kt │ │ │ ├── OpponentList.kt │ │ │ ├── PrepareGameDialog.kt │ │ │ ├── PrepareGameScreen.kt │ │ │ └── PrepareGameScreenState.kt │ │ │ ├── profile │ │ │ ├── Match.kt │ │ │ ├── ProfileScreen.kt │ │ │ ├── ProfileScreenState.kt │ │ │ ├── ProfileTabBar.kt │ │ │ ├── UserScreen.kt │ │ │ └── VisitedProfileScreenState.kt │ │ │ ├── puzzles │ │ │ ├── PuzzleGameScreen.kt │ │ │ ├── PuzzleGameScreenState.kt │ │ │ ├── PuzzleInfo.kt │ │ │ ├── PuzzleInfoState.kt │ │ │ ├── PuzzleSelectionScreen.kt │ │ │ └── PuzzleSelectionScreenState.kt │ │ │ ├── setting │ │ │ ├── EditLanguageDialog.kt │ │ │ ├── EditLanguageDialogState.kt │ │ │ ├── EditProfileImageDialog.kt │ │ │ ├── EditProfileImageDialogState.kt │ │ │ ├── EditProfileNameDialog.kt │ │ │ ├── EditProfileNameDialogState.kt │ │ │ ├── Emojis.kt │ │ │ ├── SettingScreen.kt │ │ │ └── SettingScreenState.kt │ │ │ ├── social │ │ │ ├── ChessMatch.kt │ │ │ ├── FollowButton.kt │ │ │ ├── Person.kt │ │ │ ├── PersonItem.kt │ │ │ ├── SearchField.kt │ │ │ ├── SocialScreen.kt │ │ │ ├── SocialScreenState.kt │ │ │ └── SwipeToUnfollow.kt │ │ │ └── tournaments │ │ │ ├── Badge.kt │ │ │ ├── Contest.kt │ │ │ ├── ContestInfo.kt │ │ │ ├── ContestScreen.kt │ │ │ ├── ContestScreenState.kt │ │ │ ├── CreateDialog.kt │ │ │ ├── DashedDivider.kt │ │ │ ├── FiltersDialog.kt │ │ │ ├── NextStepBanner.kt │ │ │ ├── PoolCard.kt │ │ │ ├── PoolTable.kt │ │ │ └── TournamentDetails.kt │ └── res │ │ ├── drawable │ │ ├── ic_chess_bishop_black.xml │ │ ├── ic_chess_bishop_white.xml │ │ ├── ic_chess_king_black.xml │ │ ├── ic_chess_king_white.xml │ │ ├── ic_chess_knight_black.xml │ │ ├── ic_chess_knight_white.xml │ │ ├── ic_chess_pawn_black.xml │ │ ├── ic_chess_pawn_white.xml │ │ ├── ic_chess_queen_black.xml │ │ ├── ic_chess_queen_white.xml │ │ ├── ic_chess_rook_black.xml │ │ ├── ic_chess_rook_white.xml │ │ ├── ic_edit.xml │ │ ├── ic_filter.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_local_play.xml │ │ ├── ic_logos_authentication.xml │ │ ├── ic_online_play.xml │ │ ├── ic_tab_icons_friends_filled.xml │ │ ├── ic_tab_icons_friends_hollow.xml │ │ ├── ic_tab_icons_play_filled.xml │ │ ├── ic_tab_icons_play_hollow.xml │ │ ├── ic_tab_icons_puzzles_filled.xml │ │ ├── ic_tab_icons_puzzles_hollow.xml │ │ ├── ic_tab_icons_settings_filled.xml │ │ ├── ic_tab_icons_settings_hollow.xml │ │ ├── ic_tab_icons_tournaments_filled.xml │ │ └── ic_tab_icons_tournaments_hollow.xml │ │ ├── font │ │ ├── ibm_plex_sans_bold.ttf │ │ ├── ibm_plex_sans_extralight.ttf │ │ ├── ibm_plex_sans_light.ttf │ │ ├── ibm_plex_sans_medium.ttf │ │ ├── ibm_plex_sans_regular.ttf │ │ ├── ibm_plex_sans_semibold.ttf │ │ ├── ibm_plex_sans_thin.ttf │ │ ├── noto_sans_bold.ttf │ │ └── noto_sans_regular.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ └── chess_piece_sound.mp3 │ │ ├── values-v23 │ │ └── themes.xml │ │ ├── values-v27 │ │ └── themes.xml │ │ └── values │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── ch │ └── epfl │ └── sdp │ └── mobile │ ├── Test.kt │ └── application │ └── chess │ ├── parser │ └── StringCombinatorsTest.kt │ └── voice │ └── VoiceInputCombinatorTest.kt ├── settings.gradle └── wiki ├── Home.md ├── Sprint-1.md ├── Sprint-2.md ├── Sprint-3.md ├── Sprint-4.md ├── Sprint-5-first_half.md ├── Sprint-5-second_half.md ├── Sprint-6.md ├── Sprint-7.md └── image ├── ExpectedGradle.png ├── GradleSettings.png ├── ktfmtCheck.png └── mood-board.jpg /.cirrus/populate_properties.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Takes $GITHUB_USERNAME and $GITHUB_TOKEN and writes them to the ~/.gradle/gradle.properties 4 | mkdir ~/.gradle 5 | echo "githubJacocoUsername=$GITHUB_USERNAME\ngithubJacocoPassword=$GITHUB_TOKEN" > ~/.gradle/gradle.properties 6 | 7 | # Takes $GOOGLE_SERVICES and writes it to ./mobile/google-services.json 8 | echo $GOOGLE_SERVICES > ./mobile/google-services.json 9 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_patterns: 2 | - "**/test/" 3 | - "**/androidTest/" 4 | - "**test" 5 | -------------------------------------------------------------------------------- /.github/workflows/detekt.yml: -------------------------------------------------------------------------------- 1 | name: Run detekt 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | detekt: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: "checkout" 10 | uses: actions/checkout@v2 11 | - name: "detekt" 12 | uses: natiginfo/action-detekt-all@1.20.0 13 | with: 14 | args: --config detekt.yml 15 | -------------------------------------------------------------------------------- /.github/workflows/wiki.yml: -------------------------------------------------------------------------------- 1 | name: Publish wiki 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | ref: ${{ github.head_ref }} 16 | token: ${{ secrets.TOKEN_GITHUB }} 17 | 18 | - name: Update Wiki 19 | run : | 20 | cd wiki/ 21 | git init . 22 | git config user.name "${{github.actor}}" 23 | git config user.email "${{github.actor}}@users.noreply.github.com" 24 | git add . 25 | git commit -m "Deployed wiki 🚀" 26 | git remote add origin https://${{secrets.TOKEN_GITHUB}}@github.com/${{github.repository}}.wiki.git 27 | git push -u origin master --force 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, The Pawnies authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application").version("7.1.1").apply(false) 3 | id("com.android.library").version("7.1.1").apply(false) 4 | id("com.ncorti.ktfmt.gradle").version("0.8.0").apply(false) 5 | id("org.jetbrains.kotlin.android").version("1.6.10").apply(false) 6 | id("com.google.gms.google-services").version("4.3.10").apply(false) 7 | } 8 | 9 | tasks.register("clean", Delete::class) { delete(rootProject.buildDir) } 10 | -------------------------------------------------------------------------------- /detekt.yml: -------------------------------------------------------------------------------- 1 | comments: 2 | active: true 3 | excludes: 4 | - "**/test**" 5 | - "**/androidTest/**" 6 | # Individual files. 7 | - "**/Colors.kt" 8 | - "**/LocalizedStrings.kt" 9 | EndOfSentenceFormat: 10 | active: true 11 | UndocumentedPublicClass: 12 | active: true 13 | UndocumentedPublicFunction: 14 | active: true 15 | UndocumentedPublicProperty: 16 | active: true 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | # USER CONFIGURATION 5 | # 6 | # These properties MUST be set in ~/.gradle/gradle.properties, and MUST NOT be committed into the 7 | # VCS. The username must be your GitHub username, and the token must be a personal access token with 8 | # permission to read packages. 9 | # 10 | # githubJacocoUsername=myusername 11 | # githubJacocoPassword=mytoken 12 | # 13 | # 14 | # 15 | # Android and AndroidX configuration. 16 | android.nonTransitiveRClass=true 17 | android.useAndroidX=true 18 | # Kotlin configuration. 19 | kotlin.code.style=official 20 | # Gradle configuration. 21 | org.gradle.daemon=true 22 | org.gradle.caching=true 23 | org.gradle.parallel=true 24 | org.gradle.configureondemand=true 25 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 22 17:52:47 CET 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /mobile/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/Assertions.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Assert.fail 5 | 6 | /** 7 | * Asserts that the given [block] throws an exception or an error of type [T]. 8 | * 9 | * @param T the expected type of the [Throwable]. 10 | * @param block the [block] under test. 11 | */ 12 | inline fun assertThrows(block: () -> Unit) { 13 | var error = false 14 | try { 15 | block() 16 | error = true 17 | } catch (throwable: Throwable) { 18 | assertThat(throwable).isInstanceOf(T::class.java) 19 | } 20 | if (error) { 21 | fail("Expected an exception of type ${T::class.qualifiedName} but got nothing.") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/Collections.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test 2 | 3 | import kotlinx.collections.immutable.PersistentMap 4 | 5 | /** 6 | * Gets a value if it exists, or puts the value generated by [ifAbsent] in the [PersistentMap] 7 | * before returning it if it's not present. 8 | * 9 | * @see [MutableMap.getOrPut] the mutable equivalent for this function. 10 | * 11 | * @param K the type of the keys in the map. 12 | * @param V the type of the values in the map. 13 | * @param key the key to search for. 14 | * @param ifAbsent the function which generates a value if it's missing. 15 | * 16 | * @return a [Pair] of the updated map and the value found. 17 | */ 18 | inline fun PersistentMap.getOrPut( 19 | key: K, 20 | ifAbsent: () -> V, 21 | ): Pair, V> { 22 | val existing = get(key) 23 | return if (existing == null) { 24 | val value = ifAbsent() 25 | put(key, value) to value 26 | } else { 27 | this to existing 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/Dsl.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application 2 | 3 | import ch.epfl.sdp.mobile.application.authentication.AuthenticatedUser 4 | import ch.epfl.sdp.mobile.application.authentication.AuthenticationFacade 5 | import kotlinx.coroutines.flow.filterIsInstance 6 | import kotlinx.coroutines.flow.first 7 | 8 | /** Awaits and returns the first [AuthenticatedUser] user from this [AuthenticationFacade]. */ 9 | suspend fun AuthenticationFacade.awaitAuthenticatedUser(): AuthenticatedUser = 10 | currentUser.filterIsInstance().first() 11 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/engine/ActionTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.engine 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Action 4 | import ch.epfl.sdp.mobile.application.chess.engine.Delta 5 | import ch.epfl.sdp.mobile.application.chess.engine.Position 6 | import com.google.common.truth.Truth.assertThat 7 | import org.junit.Test 8 | 9 | class ActionTest { 10 | 11 | @Test 12 | fun given_action_when_readsComponent1_then_readsPosition() { 13 | val position = Position(1, 2) 14 | val delta = Delta(3, 4) 15 | val action = Action.Move(position, delta) 16 | assertThat(action.component1()).isEqualTo(position) 17 | } 18 | 19 | @Test 20 | fun given_action_when_readsComponent2_then_readsDelta() { 21 | val position = Position(1, 2) 22 | val delta = Delta(3, 4) 23 | val action = Action.Move(position, delta) 24 | assertThat(action.component2()).isEqualTo(delta) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/engine/PositionTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.engine 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Delta 4 | import ch.epfl.sdp.mobile.application.chess.engine.Position 5 | import com.google.common.truth.Truth.assertThat 6 | import org.junit.Test 7 | 8 | class PositionTest { 9 | 10 | @Test 11 | fun allPositions_hasCorrectSize() { 12 | assertThat(Position.all().count()).isEqualTo(64) 13 | } 14 | 15 | @Test 16 | fun allPositions_areInBounds() { 17 | val allInBounds = Position.all().all(Position::inBounds) 18 | assertThat(allInBounds).isTrue() 19 | } 20 | 21 | @Test 22 | fun negativePosition_notInBounds() { 23 | assertThat(Position(-1, -1).inBounds).isFalse() 24 | assertThat(Position(0, -1).inBounds).isFalse() 25 | assertThat(Position(-1, 0).inBounds).isFalse() 26 | } 27 | 28 | @Test 29 | fun tooFarRightPosition_notInBounds() { 30 | assertThat(Position(8, 0).inBounds).isFalse() 31 | } 32 | 33 | @Test 34 | fun tooFarBottom_position_notInBounds() { 35 | assertThat(Position(0, 8).inBounds).isFalse() 36 | } 37 | 38 | @Test 39 | fun plusDeltaInBounds_returnsPosition() { 40 | val end = Position(0, 0) + Delta(1, 2) 41 | assertThat(end).isEqualTo(Position(1, 2)) 42 | } 43 | 44 | @Test 45 | fun plusDeltaOutOfBounds_notInBounds() { 46 | val end = Position(0, 0) + Delta(-1, -2) 47 | assertThat(end.inBounds).isFalse() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/engine/implementation/MutableBoardPieceTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.engine.implementation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Color 4 | import ch.epfl.sdp.mobile.application.chess.engine.PieceIdentifier 5 | import ch.epfl.sdp.mobile.application.chess.engine.Rank 6 | import ch.epfl.sdp.mobile.application.chess.engine.implementation.MutableBoardPiece 7 | import com.google.common.truth.Truth.assertThat 8 | import org.junit.Test 9 | 10 | class MutableBoardPieceTest { 11 | 12 | @Test 13 | fun given_colorAndRank_when_creatingThenReadingPiece_thenColorAndRankMatch() { 14 | for (color in Color.values()) { 15 | for (rank in Rank.values()) { 16 | val piece = MutableBoardPiece(PieceIdentifier(0), rank, color) 17 | assertThat(piece.color).isEqualTo(color) 18 | assertThat(piece.rank).isEqualTo(rank) 19 | } 20 | } 21 | } 22 | 23 | @Test 24 | fun given_nonePiece_when_readingRankAndColor_then_returnsNull() { 25 | val piece = MutableBoardPiece.None 26 | assertThat(piece.rank).isNull() 27 | assertThat(piece.color).isNull() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/engine/implementation/MutableBoardTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.engine.implementation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Color.White 4 | import ch.epfl.sdp.mobile.application.chess.engine.PieceIdentifier 5 | import ch.epfl.sdp.mobile.application.chess.engine.Position 6 | import ch.epfl.sdp.mobile.application.chess.engine.Rank.King 7 | import ch.epfl.sdp.mobile.application.chess.engine.implementation.MutableBoard 8 | import ch.epfl.sdp.mobile.application.chess.engine.implementation.MutableBoardPiece 9 | import com.google.common.truth.Truth.assertThat 10 | import org.junit.Test 11 | 12 | class MutableBoardTest { 13 | 14 | @Test 15 | fun given_boardWithOnePiece_when_countsPieces_then_returnsOne() { 16 | val board = 17 | MutableBoard().apply { 18 | set(Position(0, 0), MutableBoardPiece(PieceIdentifier(0), King, White)) 19 | } 20 | var count = 0 21 | board.forEachPiece { _, _ -> count++ } 22 | 23 | assertThat(count).isEqualTo(1) 24 | } 25 | 26 | @Test 27 | fun given_emptyBoard_when_callsContains_then_returnsFalse() { 28 | val board = MutableBoard() 29 | assertThat(board.contains(Position(0, 0))).isFalse() 30 | } 31 | 32 | @Test 33 | fun given_board_when_setsOutOfBounds_then_doesNothing() { 34 | val board = MutableBoard() 35 | val piece = MutableBoardPiece(PieceIdentifier(0), King, White) 36 | board[Position(-1, -1)] = piece 37 | Position.all().forEach { assertThat(board.contains(it)).isFalse() } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/engine/implementation/PersistentBoardTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.engine.implementation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.* 4 | import ch.epfl.sdp.mobile.application.chess.engine.implementation.buildBoard 5 | import ch.epfl.sdp.mobile.application.chess.engine.implementation.emptyBoard 6 | import com.google.common.truth.Truth.assertThat 7 | import org.junit.Test 8 | 9 | class PersistentBoardTest { 10 | 11 | private val pawn = Piece(Unit, Rank.Pawn, PieceIdentifier(0)) 12 | 13 | @Test 14 | fun emptyBoard_hasNoPiece() { 15 | val board = emptyBoard() 16 | for (i in 0 until Board.Size) { 17 | for (j in 0 until Board.Size) { 18 | assertThat(board[Position(i, j)]).isNull() 19 | } 20 | } 21 | } 22 | 23 | @Test 24 | fun buildBoard_addsPiece() { 25 | val position = Position(0, 0) 26 | val board = buildBoard> { set(position, pawn) } 27 | assertThat(board[position]).isEqualTo(pawn) 28 | } 29 | 30 | @Test 31 | fun buildBoard_outOfBounds_addsNoPiece() { 32 | val position = Position(-1, -1) 33 | val board = buildBoard> { set(position, pawn) } 34 | assertThat(board[position]).isNull() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/engine/rules/CheckmateTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.* 4 | import ch.epfl.sdp.mobile.application.chess.engine.Color.Black 5 | import ch.epfl.sdp.mobile.application.chess.engine.Color.White 6 | import ch.epfl.sdp.mobile.application.chess.engine.Rank.* 7 | import com.google.common.truth.Truth.assertThat 8 | import org.junit.Test 9 | 10 | class CheckmateTest { 11 | 12 | private val whiteKing = Piece(White, King, PieceIdentifier(0)) 13 | private val whiteRook1 = Piece(White, Rook, PieceIdentifier(1)) 14 | private val whiteRook2 = Piece(White, Rook, PieceIdentifier(2)) 15 | private val whitePawn = Piece(White, Pawn, PieceIdentifier(0)) 16 | private val blackKing = Piece(Black, King, PieceIdentifier(0)) 17 | 18 | @Test 19 | fun twoRooks_canPutAdversaryKing_inCheckmate() { 20 | val game = 21 | buildGame(Black) { 22 | set(Position(0, 0), whiteRook1) 23 | set(Position(0, 1), whiteRook2) 24 | set(Position(3, 0), blackKing) 25 | set(Position(7, 0), whiteKing) 26 | } 27 | assertThat(game.nextStep).isEqualTo(NextStep.Checkmate(winner = White)) 28 | } 29 | 30 | @Test 31 | fun pawnAndKing_canPutAdversaryKing_inStalemate() { 32 | val game = 33 | buildGame(Black) { 34 | set(Position(1, 0), blackKing) 35 | set(Position(1, 1), whitePawn) 36 | set(Position(1, 2), whiteKing) 37 | } 38 | assertThat(game.nextStep).isEqualTo(NextStep.Stalemate) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/application/chess/notation/AlgebraicNotationTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.application.chess.notation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Game 4 | import ch.epfl.sdp.mobile.application.chess.notation.AlgebraicNotation.parseGame 5 | import ch.epfl.sdp.mobile.application.chess.notation.AlgebraicNotation.toAlgebraicNotation 6 | import ch.epfl.sdp.mobile.test.application.chess.engine.Games.FoolsMate 7 | import ch.epfl.sdp.mobile.test.application.chess.engine.Games.Stalemate 8 | import ch.epfl.sdp.mobile.test.application.chess.engine.play 9 | import com.google.common.truth.Truth.assertThat 10 | import org.junit.Test 11 | 12 | class AlgebraicNotationTest { 13 | 14 | @Test 15 | fun given_foolsMate_when_encodingThenDecodingInAlgebraicNotation_then_hasGoodBoard() { 16 | val foolsMate = Game.create().play(FoolsMate) 17 | 18 | val foolsMateBoard = foolsMate.board 19 | val serializedDeserializedBoard = parseGame(foolsMate.toAlgebraicNotation()).board 20 | 21 | assertThat(serializedDeserializedBoard).isEqualTo(foolsMateBoard) 22 | } 23 | 24 | @Test 25 | fun given_staleMate_when_encodingThenDecodingInAlgebraicNotation_then_hasGoodBoard() { 26 | val staleMate = Game.create().play(Stalemate) 27 | 28 | val foolsMateBoard = staleMate.board 29 | val serializedDeserializedBoard = parseGame(staleMate.toAlgebraicNotation()).board 30 | 31 | assertThat(serializedDeserializedBoard).isEqualTo(foolsMateBoard) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/assets/fake/FakeAssetManagerTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.assets.fake 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | 6 | class FakeAssetManagerTest { 7 | 8 | @Test 9 | fun given_fakeAssetManagerWithString_when_readingReaderString_then_isCorrectString() { 10 | val assets = FakeAssetManager("Test string") 11 | 12 | val res = assets.readText("whatever") 13 | assertThat(res).isEqualTo("Test string") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/auth/Dsl.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.auth 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.auth.Auth 4 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.auth.fake.FakeAuth 5 | 6 | /** Creates an in-memory implementation of [Auth], which can't fail. */ 7 | fun emptyAuth(): Auth = FakeAuth() 8 | 9 | /** 10 | * Builds an [Auth] using the provided [AuthBuilder]. 11 | * 12 | * @param builder the builder for the auth. 13 | * @return the newly build fake auth. 14 | */ 15 | fun buildAuth( 16 | builder: AuthBuilder.() -> Unit, 17 | ): Auth = FakeAuth().apply(builder) 18 | 19 | /** 20 | * A scope which can be used to create some users and perform some actions on an 21 | * [ch.epfl.sdp.mobile.infrastructure.persistence.auth.Auth] instance. 22 | */ 23 | interface AuthBuilder { 24 | 25 | /** 26 | * Adds a new user with the given email and password. 27 | * 28 | * @param email the email of the added user. Must be unique for this auth. 29 | * @param password the password which will protect authentication with the account. 30 | */ 31 | fun user(email: String, password: String) 32 | 33 | /** 34 | * Adds a new user with the given email, password and unique identifier. 35 | * 36 | * @param email the email of the added user. Must be unique for this auth. 37 | * @param password the password which will protect authentication with the account. 38 | * @param uid the unique identifier for this user. 39 | */ 40 | fun user(email: String, password: String, uid: String) 41 | } 42 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/auth/SuspendingAuth.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.auth 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.auth.Auth 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.auth.User 5 | import ch.epfl.sdp.mobile.test.suspendForever 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.emptyFlow 8 | 9 | /** 10 | * A fake implementation of [Auth] which always suspends execution and never returns any values for 11 | * the different API calls. 12 | */ 13 | object SuspendingAuth : Auth { 14 | 15 | override val currentUser: Flow = emptyFlow() 16 | 17 | override suspend fun signInWithEmail( 18 | email: String, 19 | password: String, 20 | ): Auth.AuthenticationResult = suspendForever() 21 | 22 | override suspend fun signUpWithEmail( 23 | email: String, 24 | password: String, 25 | ): Auth.AuthenticationResult = suspendForever() 26 | 27 | override suspend fun signOut() = suspendForever() 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/auth/fake/FakeAuthRecord.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.auth.fake 2 | 3 | /** 4 | * A record of the currently connected user, as well as the users which can be used to authenticate. 5 | */ 6 | data class FakeAuthRecord( 7 | val currentUser: FakeUser?, 8 | val users: Set, 9 | ) 10 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/auth/fake/FakeUser.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.auth.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.auth.User 4 | 5 | /** 6 | * An implementation of [User] which is faked and has no behavior. 7 | * 8 | * @param uid the unique identifier for this [User]. 9 | * @param email the email address of this [User], if available. 10 | * @param password the password which this user authenticates with. 11 | */ 12 | data class FakeUser( 13 | override val uid: String, 14 | override val email: String?, 15 | val password: String, 16 | ) : User 17 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/Dsl.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.DataStoreFactory 4 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake.FakeDataStoreFactory 5 | 6 | /** Returns a [DataStoreFactory] with a fake implementation. */ 7 | fun emptyDataStoreFactory(): DataStoreFactory = FakeDataStoreFactory() 8 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/androidx/AndroidXDataStoreFactoryTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.androidx 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore as ActualDataStore 5 | import androidx.datastore.preferences.core.Preferences as ActualPreferences 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx.AndroidXDataStoreFactory 7 | import com.google.common.truth.Truth.assertThat 8 | import io.mockk.every 9 | import io.mockk.mockk 10 | import io.mockk.verify 11 | import kotlinx.coroutines.flow.emptyFlow 12 | import kotlinx.coroutines.flow.toList 13 | import kotlinx.coroutines.test.runTest 14 | import org.junit.Test 15 | 16 | class AndroidXDataStoreFactoryTest { 17 | 18 | @Test 19 | fun given_factory_when_createsDataStore_then_callsGoodMethods() = runTest { 20 | val context = mockk() 21 | val actualDatastore = mockk>() 22 | val factory = AndroidXDataStoreFactory(context) { _, _ -> actualDatastore } 23 | 24 | val (store, _) = factory.createPreferencesDataStore() 25 | 26 | every { actualDatastore.data } returns emptyFlow() 27 | 28 | assertThat(store.data.toList()).isEmpty() 29 | 30 | verify { actualDatastore.data } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/androidx/AndroidXKeyFactoryTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.androidx 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx.AndroidXKeyFactory 4 | import org.junit.Assert.fail 5 | import org.junit.Test 6 | 7 | class AndroidXKeyFactoryTest { 8 | 9 | @Test 10 | fun given_keysWithSameName_when_compared_then_areAllEqual() { 11 | val factory = AndroidXKeyFactory 12 | val keys = 13 | listOf( 14 | factory.int("hello"), 15 | factory.double("hello"), 16 | factory.string("hello"), 17 | factory.boolean("hello"), 18 | factory.float("hello"), 19 | factory.long("hello"), 20 | factory.stringSet("hello"), 21 | ) 22 | 23 | // Truth does not have assertions to check that all values are duplicates. 24 | for (k1 in keys) { 25 | for (k2 in keys) { 26 | if (k1 != k2) fail() 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/androidx/AndroidXMutablePreferencesTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.androidx 2 | 3 | import androidx.datastore.preferences.core.MutablePreferences as ActualMutablePreferences 4 | import androidx.datastore.preferences.core.Preferences.Key as ActualKey 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx.AndroidXKeyFactory 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx.AndroidXMutablePreferences 7 | import io.mockk.every 8 | import io.mockk.mockk 9 | import io.mockk.verify 10 | import org.junit.Test 11 | 12 | class AndroidXMutablePreferencesTest { 13 | 14 | @Test 15 | fun given_preferences_when_sets_then_callsGoodMethods() { 16 | val actualPreferences = mockk() 17 | val preferences = AndroidXMutablePreferences(actualPreferences) 18 | 19 | every { actualPreferences[any>()] = true } returns Unit 20 | 21 | preferences[AndroidXKeyFactory.boolean("hello")] = true 22 | 23 | verify { actualPreferences[any>()] = true } 24 | } 25 | 26 | @Test 27 | fun given_preferences_when_remove_then_callsGoodMethods() { 28 | val actualPreferences = mockk() 29 | val preferences = AndroidXMutablePreferences(actualPreferences) 30 | 31 | every { actualPreferences.remove(any>()) } returns true 32 | 33 | preferences.remove(AndroidXKeyFactory.boolean("hello")) 34 | 35 | verify { actualPreferences.remove(any>()) } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/fake/FakeDataStoreFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.DataStoreFactory 4 | 5 | /** 6 | * A fake implementation of [DataStoreFactory] which always returns the same underlying instance of 7 | * [FakePreferencesDataStore]. 8 | */ 9 | class FakeDataStoreFactory : DataStoreFactory { 10 | 11 | /** The underlying [FakePreferencesDataStore]. */ 12 | private val preferences = FakePreferencesDataStore() 13 | 14 | override fun createPreferencesDataStore() = preferences to FakeKeyFactory 15 | } 16 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/fake/FakeKey.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Key 4 | 5 | /** 6 | * An implementation of a [Key] which has a [name]. 7 | * 8 | * @param name the name of the key. 9 | */ 10 | data class FakeKey(val name: String) : Key 11 | 12 | /** 13 | * Returns the [String] from this [Key]. If the key is not compatible and was not created for a 14 | * [FakePreferencesDataStore], an exception will be thrown. 15 | * 16 | * @param T the type of the value associated with the key. 17 | * @receiver the [Key] from which a [String] key is extracted. 18 | * @return [String] the actual underlying key. 19 | * @throws IllegalArgumentException if the [Key] is not a [FakeKey]. 20 | */ 21 | internal fun Key.extractActualKey(): String = 22 | requireNotNull((this as? FakeKey)?.name) { 23 | "This Key<*> is incompatible with this DataStore." 24 | } 25 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/fake/FakeKeyFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.KeyFactory 4 | 5 | /** An implementation of [KeyFactory] which only creates some [FakeKey]. */ 6 | object FakeKeyFactory : KeyFactory { 7 | override fun int(name: String) = FakeKey(name) 8 | override fun double(name: String) = FakeKey(name) 9 | override fun string(name: String) = FakeKey(name) 10 | override fun boolean(name: String) = FakeKey(name) 11 | override fun float(name: String) = FakeKey(name) 12 | override fun long(name: String) = FakeKey(name) 13 | override fun stringSet(name: String) = FakeKey>(name) 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/fake/FakeMutablePreferences.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Key 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.MutablePreferences 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Preferences 6 | 7 | /** 8 | * A fake implementation of [MutablePreferences] which uses a [MutableMap] of values. 9 | * 10 | * @param map the underlying [MutableMap] of values. 11 | */ 12 | class FakeMutablePreferences( 13 | private val map: MutableMap, 14 | ) : MutablePreferences, Preferences by FakePreferences(map) { 15 | 16 | override fun set(key: Key, value: T) = map.set(key.extractActualKey(), value) 17 | 18 | override fun remove(key: Key) { 19 | map.remove(key.extractActualKey()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/fake/FakePreferences.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST") 2 | 3 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake 4 | 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Key 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.MutablePreferences 7 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Preferences 8 | 9 | /** 10 | * A fake implementation of [Preferences] which uses a [Map] of values. 11 | * 12 | * @param map the underlying [Map] of values. 13 | */ 14 | class FakePreferences(private val map: Map) : Preferences { 15 | 16 | override fun contains(key: Key) = map.contains(key.extractActualKey()) 17 | 18 | override fun get(key: Key): T? = map[key.extractActualKey()] as? T 19 | 20 | override fun toMutablePreferences(): MutablePreferences = 21 | FakeMutablePreferences(map.toMutableMap()) 22 | 23 | override fun toPreferences(): Preferences = FakePreferences(map.toMap()) 24 | } 25 | 26 | /** 27 | * Returns the [FakePreferences] from this [Preferences]. If the preferences are not compatible , an 28 | * exception will be thrown. 29 | * 30 | * @receiver the [Preferences] from which a [FakePreferences] is extracted. 31 | * @return [FakePreferences] the actual underlying preferences. 32 | * @throws IllegalArgumentException if the [Preferences] is not some [FakePreferences]. 33 | */ 34 | internal fun Preferences.extractActualPreferences(): FakePreferences = 35 | requireNotNull(this as? FakePreferences) { "This Key<*> is incompatible with this DataStore." } 36 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/datastore/fake/FakePreferencesDataStore.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.datastore.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.DataStore 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Preferences 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.sync.Mutex 8 | import kotlinx.coroutines.sync.withLock 9 | 10 | /** An implementation of [DataStore] which uses a [MutableStateFlow] to implement observability. */ 11 | class FakePreferencesDataStore : DataStore { 12 | 13 | /** The [MutableStateFlow] which backs these fake preferences. */ 14 | private val current = MutableStateFlow(FakePreferences(emptyMap())) 15 | 16 | /** The [Mutex] which ensures mutually exclusive access to the [current] preferences.l */ 17 | private val mutex = Mutex() 18 | 19 | override val data: Flow 20 | get() = current 21 | 22 | override suspend fun updateData( 23 | transform: suspend (Preferences) -> Preferences, 24 | ): Preferences = 25 | mutex.withLock { 26 | transform(current.value).extractActualPreferences().also { current.value = it } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/RecordingDocumentEditScopeTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.FieldPath 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.RecordingDocumentEditScope 5 | import com.google.common.truth.Truth.assertThat 6 | import org.junit.Test 7 | 8 | class RecordingDocumentEditScopeTest { 9 | 10 | @Test 11 | fun given_recordingScope_when_notNestedValues_then_returnsRightResults() { 12 | val scope = RecordingDocumentEditScope() 13 | scope["hello"] = "world" 14 | 15 | assertThat(scope.mutations).containsExactly(FieldPath("hello"), "world") 16 | } 17 | 18 | @Test 19 | fun given_recordingScope_when_nestedValues_then_returnsRightResults() { 20 | val scope = RecordingDocumentEditScope() 21 | scope["k1"] = mapOf("k2" to "v") 22 | val expected = mapOf(FieldPath(listOf("k1", "k2")) to "v") 23 | assertThat(scope.mutations).containsExactlyEntriesIn(expected) 24 | } 25 | 26 | @Test 27 | fun given_recordingScope_when_deeplyNestedValues_then_returnsRightResults() { 28 | val scope = RecordingDocumentEditScope() 29 | scope["k1"] = mapOf("k2" to mapOf("k3" to "v")) 30 | val expected = mapOf(FieldPath(listOf("k1", "k2", "k3")) to "v") 31 | assertThat(scope.mutations).containsExactlyEntriesIn(expected) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/FakeDocumentId.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake 2 | 3 | /** 4 | * A class representing the unique identifier of a [FakeDocumentRecord]. Within a collection, all 5 | * documents are guaranteed to have different ids. 6 | * 7 | * @param value the value of the unique identifier. 8 | */ 9 | data class FakeDocumentId(val value: String) { 10 | 11 | companion object { 12 | 13 | /** The [FakeDocumentId] given to the root of the store. */ 14 | val Root = FakeDocumentId("") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/FakeDocumentRecord.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentEditScope 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.FieldPath 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.RecordingDocumentEditScope 6 | 7 | /** 8 | * A [FakeDocumentRecord] which contains all the fields from a document. Document records do not 9 | * enforce a specific shape. 10 | * 11 | * @param fields the fields of the record. 12 | */ 13 | data class FakeDocumentRecord(val fields: Map) { 14 | 15 | /** A convenience construction which builds an empty [FakeDocumentRecord]. */ 16 | constructor() : this(emptyMap()) 17 | 18 | /** 19 | * Updates the [FakeDocumentRecord] using the given [scope], starting from the given value. This 20 | * returns a new immutable record. 21 | * 22 | * @param scope the [DocumentEditScope] lambda which should be applied. 23 | */ 24 | fun update(scope: DocumentEditScope.() -> Unit): FakeDocumentRecord { 25 | val recorded = RecordingDocumentEditScope().apply(scope) 26 | val mutations = recorded.mutations.map { (f, v) -> FakeFieldMutation.from(f, v) } 27 | val updated = mutations.fold(fields) { acc, m -> acc + (m.field to m.mutate(acc[m.field])) } 28 | return copy(fields = updated) 29 | } 30 | 31 | companion object 32 | } 33 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/FakeDocumentSnapshot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentSnapshot 4 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.serialization.toObject 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * A [DocumentSnapshot] which wraps a [FakeDocumentRecord]. 9 | * 10 | * @param id the document identifier. 11 | * @param record the (optional) [FakeDocumentRecord] which the snapshot wraps. 12 | */ 13 | data class FakeDocumentSnapshot( 14 | val id: FakeDocumentId, 15 | val record: FakeDocumentRecord?, 16 | ) : DocumentSnapshot { 17 | override fun toObject(valueClass: KClass): T? = record?.toObject(id, valueClass) 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/FakeQuerySnapshot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.QuerySnapshot 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * An implementation of [QuerySnapshot] that uses some [FakeDocumentSnapshot]. 8 | * 9 | * @param documents the [List] of underlying [FakeDocumentSnapshot]. 10 | */ 11 | data class FakeQuerySnapshot( 12 | val documents: List, 13 | ) : QuerySnapshot { 14 | 15 | override fun toObjects(valueClass: KClass): List { 16 | return documents.map { it.toObject(valueClass) } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/FakeStore.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.CollectionReference 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentReference 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Store 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Transaction 7 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.store.CollectionBuilder 8 | 9 | /** 10 | * An implementation of a [Store] which delegates its implementation to an inner fake document. This 11 | * document will not have any value, however, it can be used to build some nested collections. 12 | * 13 | * @param root the [FakeDocumentReference] which is the root of the hierarchy. 14 | */ 15 | class FakeStore 16 | private constructor( 17 | private val root: FakeDocumentReference, 18 | ) : Store, CollectionBuilder by root { 19 | 20 | /** A convenience constructor, which creates an empty [FakeStore]. */ 21 | constructor() : this(FakeDocumentReference(FakeDocumentId.Root)) 22 | 23 | override fun collection(path: String): CollectionReference = root.collection(path) 24 | 25 | override suspend fun transaction( 26 | block: Transaction.() -> R, 27 | ): R = block(FakeTransaction()) 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/FakeTransaction.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentEditScope 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentSnapshot 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Transaction 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * An implementation of [Transaction] that supports [FakeDocumentReference]. It does not offer true 10 | * transaction semantics, however these aren't really needed in the context of our unit tests and 11 | * would be out of scope for the [FakeStore]. 12 | */ 13 | class FakeTransaction : Transaction { 14 | 15 | override fun set( 16 | reference: FakeDocumentReference, 17 | scope: DocumentEditScope.() -> Unit, 18 | ) = reference.atomicSet(scope) 19 | 20 | override fun set( 21 | reference: FakeDocumentReference, 22 | value: T, 23 | valueClass: KClass, 24 | ) = reference.atomicSet(value, valueClass) 25 | 26 | override fun update( 27 | reference: FakeDocumentReference, 28 | scope: DocumentEditScope.() -> Unit, 29 | ) = reference.atomicUpdate(scope) 30 | 31 | override fun delete( 32 | reference: FakeDocumentReference, 33 | ) = reference.atomicDelete() 34 | 35 | override fun getSnapshot( 36 | reference: FakeDocumentReference, 37 | ): DocumentSnapshot = reference.atomicGet() 38 | } 39 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/query/AbstractFakeQuery.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.query 2 | 3 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.FakeQuerySnapshot 4 | import kotlinx.coroutines.flow.map 5 | 6 | /** 7 | * An abstract implementation of [FakeQuery] which delegates all the transformation behaviour to its 8 | * [FakeQuerySnapshot.transform] function, decorating an underlying [FakeQuery]. 9 | * 10 | * @param parent the underlying [FakeQuery]. 11 | */ 12 | abstract class AbstractFakeQuery(private val parent: FakeQuery) : FakeQuery { 13 | 14 | /** 15 | * Transforms the [FakeQuerySnapshot] using this query. 16 | * 17 | * @receiver the original [FakeQuerySnapshot]. 18 | * @return the transformed [FakeQuerySnapshot]. 19 | */ 20 | abstract fun FakeQuerySnapshot.transform(): FakeQuerySnapshot 21 | 22 | override fun asQuerySnapshotFlow() = parent.asQuerySnapshotFlow().map { it.transform() } 23 | 24 | override suspend fun getQuerySnapshot() = parent.getQuerySnapshot().transform() 25 | } 26 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/query/LimitFakeQueryDecorator.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.query 2 | 3 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.FakeQuerySnapshot 4 | 5 | /** 6 | * A decorator which limits the number of results which are to be provided in the resulting results. 7 | * 8 | * @param query the query to decorate. 9 | * @param count the number of items to limit the query to. 10 | */ 11 | class LimitFakeQueryDecorator( 12 | query: FakeQuery, 13 | private val count: Long, 14 | ) : AbstractFakeQuery(query) { 15 | 16 | override fun FakeQuerySnapshot.transform() = copy(documents = documents.take(count.toInt())) 17 | } 18 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/fake/query/OrderByQueryDecorator.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.query 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.FieldPath 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Query 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Query.Direction.Ascending 6 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.FakeDocumentSnapshot 7 | import ch.epfl.sdp.mobile.test.infrastructure.persistence.store.fake.FakeQuerySnapshot 8 | import kotlin.Comparator 9 | 10 | /** 11 | * A decorator which orders results according to a certain [FakeFieldComparator] on the resulting 12 | * document snapshots. 13 | * 14 | * @param query the query to decorate. 15 | * @param path the field to compare. 16 | * @param order the direction of the comparison. 17 | */ 18 | class OrderByQueryDecorator( 19 | query: FakeQuery, 20 | path: FieldPath, 21 | order: Query.Direction, 22 | ) : AbstractFakeQuery(query) { 23 | 24 | private val comparator = 25 | Comparator { a, b -> 26 | FakeFieldComparator.compare( 27 | a?.record?.fields?.get(path), 28 | b?.record?.fields?.get(path), 29 | ) 30 | } 31 | 32 | private val withOrder = if (order == Ascending) comparator else comparator.reversed() 33 | 34 | override fun FakeQuerySnapshot.transform() = copy(documents = documents.sortedWith(withOrder)) 35 | } 36 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/firestore/FirestoreCollectionReferenceTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore.FirestoreCollectionReference 4 | import com.google.firebase.firestore.CollectionReference 5 | import com.google.firebase.firestore.DocumentReference 6 | import io.mockk.every 7 | import io.mockk.mockk 8 | import io.mockk.verify 9 | import org.junit.Test 10 | 11 | class FirestoreCollectionReferenceTest { 12 | 13 | @Test 14 | fun document_callsDocument() { 15 | val collection = mockk() 16 | val document = mockk() 17 | val reference = FirestoreCollectionReference(collection) 18 | 19 | every { collection.document(any()) } returns document 20 | reference.document("path") 21 | 22 | verify { collection.document("path") } 23 | } 24 | 25 | @Test 26 | fun documentAutoId_callsDocument() { 27 | val collection = mockk() 28 | val document = mockk() 29 | val reference = FirestoreCollectionReference(collection) 30 | 31 | every { collection.document() } returns document 32 | reference.document() 33 | 34 | verify { collection.document() } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/persistence/store/firestore/FirestoreStoreTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore.FirestoreStore 4 | import com.google.firebase.firestore.CollectionReference 5 | import com.google.firebase.firestore.FirebaseFirestore 6 | import io.mockk.every 7 | import io.mockk.mockk 8 | import io.mockk.verify 9 | import org.junit.Test 10 | 11 | class FirestoreStoreTest { 12 | 13 | @Test 14 | fun collection_callsCollection() { 15 | val firestore = mockk() 16 | val collection = mockk() 17 | val store = FirestoreStore(firestore) 18 | 19 | every { firestore.collection(any()) } returns collection 20 | store.collection("path") 21 | 22 | verify { firestore.collection("path") } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/sound/android/AndroidSoundPlayerTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.sound.android 2 | 3 | import android.content.Context 4 | import android.media.MediaPlayer 5 | import ch.epfl.sdp.mobile.infrastructure.sound.android.AndroidSoundPlayer 6 | import io.mockk.every 7 | import io.mockk.mockk 8 | import io.mockk.verify 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Test 11 | 12 | class AndroidSoundPlayerTest { 13 | @Test 14 | fun given_androidSoundPlayer_when_playSound_then_mediaPlayerStartCalled() = runTest { 15 | val mediaPlayer = mockk() 16 | val context = mockk() 17 | val soundPlayer = AndroidSoundPlayer(context = context, mediaPlayer = mediaPlayer) 18 | every { mediaPlayer.start() } returns Unit 19 | soundPlayer.playChessSound() 20 | verify { mediaPlayer.start() } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/sound/fake/FakeSoundPlayer.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.sound.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.sound.SoundPlayer 4 | 5 | /** An object that allows mocking the [SoundPlayer]. */ 6 | object FakeSoundPlayer : SoundPlayer { 7 | override fun playChessSound() {} 8 | } 9 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/FailingSpeechRecognizerFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizer 4 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizerFactory 5 | 6 | /** An implementation of [SpeechRecognizerFactory] which always fails. */ 7 | object FailingSpeechRecognizerFactory : SpeechRecognizerFactory { 8 | override fun createSpeechRecognizer() = FailingSpeechRecognizer() 9 | } 10 | 11 | /** A [SpeechRecognizer] which will always fail to recognize the user input, and return an error. */ 12 | class FailingSpeechRecognizer : SpeechRecognizer { 13 | private var listener: SpeechRecognizer.Listener? = null 14 | override fun setListener(listener: SpeechRecognizer.Listener) { 15 | this.listener = listener 16 | } 17 | override fun startListening() { 18 | listener?.onError() 19 | } 20 | override fun stopListening() = Unit 21 | override fun destroy() = Unit 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/IllegalSpeechRecognizerFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizer 4 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizerFactory 5 | 6 | /** 7 | * An implementation of [SpeechRecognizerFactory] which result is recognized but lead to a illegal 8 | * move. 9 | */ 10 | object IllegalActionSpeechRecognizerFactory : SpeechRecognizerFactory { 11 | override fun createSpeechRecognizer() = IllegalActionSpeechRecognizer() 12 | } 13 | 14 | class IllegalActionSpeechRecognizer : SpeechRecognizer { 15 | 16 | companion object { 17 | 18 | /** The results which will always be returned on success. */ 19 | val Results = listOf("King a3 to b3", "World") 20 | } 21 | 22 | private var listener: SpeechRecognizer.Listener? = null 23 | override fun setListener(listener: SpeechRecognizer.Listener) { 24 | this.listener = listener 25 | } 26 | override fun startListening() { 27 | listener?.onResults(Results) 28 | } 29 | override fun stopListening() = Unit 30 | override fun destroy() = Unit 31 | } 32 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/LegalSpeechRecognizerFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizer 4 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizerFactory 5 | 6 | /** 7 | * An implementation of [SpeechRecognizerFactory] which result is recognized but and lead to a legal 8 | * move. 9 | */ 10 | object LegalActionSpeechRecognizerFactory : SpeechRecognizerFactory { 11 | override fun createSpeechRecognizer() = LegalActionSpeechRecognizer() 12 | } 13 | 14 | class LegalActionSpeechRecognizer : SpeechRecognizer { 15 | 16 | companion object { 17 | 18 | /** The results which will always be returned on success. */ 19 | val Results = listOf("Pawn e2 to e4", "World") 20 | } 21 | 22 | private var listener: SpeechRecognizer.Listener? = null 23 | override fun setListener(listener: SpeechRecognizer.Listener) { 24 | this.listener = listener 25 | } 26 | override fun startListening() { 27 | listener?.onResults(Results) 28 | } 29 | override fun stopListening() = Unit 30 | override fun destroy() = Unit 31 | } 32 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/SuspendingSpeechRecognizerFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizer 4 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizerFactory 5 | 6 | /** An implementation of [SpeechRecognizerFactory] which never issues results. */ 7 | object SuspendingSpeechRecognizerFactory : SpeechRecognizerFactory { 8 | override fun createSpeechRecognizer(): SpeechRecognizer = SuspendingSpeechRecognizer 9 | } 10 | 11 | /** A [SpeechRecognizer] which never returns. */ 12 | object SuspendingSpeechRecognizer : SpeechRecognizer { 13 | override fun setListener(listener: SpeechRecognizer.Listener) = Unit 14 | override fun startListening() = Unit // Never triggers a result. 15 | override fun stopListening() = Unit 16 | override fun destroy() = Unit 17 | } 18 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/UnknownCommandSpeechRecognizerFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizer 4 | import ch.epfl.sdp.mobile.infrastructure.speech.SpeechRecognizerFactory 5 | 6 | /** An implementation of [SpeechRecognizerFactory] which always succeeds. */ 7 | object UnknownCommandSpeechRecognizerFactory : SpeechRecognizerFactory { 8 | override fun createSpeechRecognizer() = SuccessfulSpeechRecognizer() 9 | } 10 | 11 | /** A [SpeechRecognizer] which always succeeds to recognize the user input, and return [Results]. */ 12 | class SuccessfulSpeechRecognizer : SpeechRecognizer { 13 | 14 | companion object { 15 | 16 | /** The results which will always be returned on success. */ 17 | val Results = listOf("Hello", "World") 18 | } 19 | 20 | private var listener: SpeechRecognizer.Listener? = null 21 | override fun setListener(listener: SpeechRecognizer.Listener) { 22 | this.listener = listener 23 | } 24 | override fun startListening() { 25 | listener?.onResults(Results) 26 | } 27 | override fun stopListening() = Unit 28 | override fun destroy() = Unit 29 | } 30 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/android/AndroidSpeechRecognizerFactoryTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech.android 2 | 3 | import android.content.Context 4 | import android.speech.SpeechRecognizer 5 | import ch.epfl.sdp.mobile.infrastructure.speech.android.AndroidSpeechRecognizerFactory 6 | import io.mockk.every 7 | import io.mockk.mockk 8 | import io.mockk.mockkStatic 9 | import io.mockk.verify 10 | import org.junit.Test 11 | 12 | class AndroidSpeechRecognizerFactoryTest { 13 | 14 | @Test 15 | fun given_factory_when_createSpeechRecognizer_then_createsFrameworkSpeechRecognizer() { 16 | val context = mockk() 17 | val factory = AndroidSpeechRecognizerFactory(context) 18 | val recognizer = mockk() 19 | mockkStatic(SpeechRecognizer::class) { 20 | every { SpeechRecognizer.createSpeechRecognizer(context) } returns recognizer 21 | 22 | factory.createSpeechRecognizer() 23 | 24 | verify { SpeechRecognizer.createSpeechRecognizer(context) } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/speech/android/RecognitionListenerAdapterTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.speech.android 2 | 3 | import android.os.Bundle 4 | import ch.epfl.sdp.mobile.infrastructure.speech.android.RecognitionListenerAdapter 5 | import com.google.common.truth.Truth.assertThat 6 | import org.junit.Test 7 | 8 | class RecognitionListenerAdapterTest { 9 | 10 | @Test 11 | fun given_emptyAdapter_when_callingAnyMethod_then_neverThrowsAndAlwaysReturnsUnit() { 12 | // This is slightly artificial, but we don't really expect anything else from the subject under 13 | // test and it's required for code coverage. 14 | val adapter = object : RecognitionListenerAdapter() {} 15 | 16 | assertThat(adapter.onReadyForSpeech(Bundle.EMPTY)).isEqualTo(Unit) 17 | assertThat(adapter.onBeginningOfSpeech()).isEqualTo(Unit) 18 | assertThat(adapter.onRmsChanged(0f)).isEqualTo(Unit) 19 | assertThat(adapter.onBufferReceived(byteArrayOf())).isEqualTo(Unit) 20 | assertThat(adapter.onEndOfSpeech()).isEqualTo(Unit) 21 | assertThat(adapter.onResults(Bundle.EMPTY)).isEqualTo(Unit) 22 | assertThat(adapter.onPartialResults(Bundle.EMPTY)).isEqualTo(Unit) 23 | assertThat(adapter.onEvent(0, Bundle.EMPTY)).isEqualTo(Unit) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/time/fake/FakeTimeProvider.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.time.fake 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.time.TimeProvider 4 | 5 | /** An object that allows changing the time. */ 6 | object FakeTimeProvider : TimeProvider { 7 | /** The current time that can be modified */ 8 | var currentTime: Long = 0L 9 | 10 | override fun now(): Long { 11 | return currentTime 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/infrastructure/tts/android/FakeTextToSpeechFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.infrastructure.tts.android 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.tts.TextToSpeech 4 | import ch.epfl.sdp.mobile.infrastructure.tts.TextToSpeechFactory 5 | 6 | /** Used fake factory of text to speech used in tests. */ 7 | object FakeTextToSpeechFactory : TextToSpeechFactory { 8 | override suspend fun create(): TextToSpeech { 9 | return FakeTextToSpeech() 10 | } 11 | } 12 | 13 | /** Fake implementation of the text to speech. */ 14 | class FakeTextToSpeech : TextToSpeech { 15 | override fun speak(text: String) {} 16 | override fun stop() {} 17 | override fun shutdown() {} 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/AuthenticatedUserProfileScreenStateTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.compose.ui.test.onNodeWithText 5 | import ch.epfl.sdp.mobile.state.StatefulSettingsScreen 6 | import kotlinx.coroutines.test.runTest 7 | import org.junit.Rule 8 | import org.junit.Test 9 | 10 | class AuthenticatedUserProfileScreenStateTest { 11 | 12 | @get:Rule val rule = createComposeRule() 13 | 14 | @Test 15 | fun correctBehaviour_takesTheUsernameCorrectly() = runTest { 16 | val env = 17 | rule.setContentWithAuthenticatedTestEnvironment { 18 | StatefulSettingsScreen(user, {}, {}, {}, {}, {}) 19 | } 20 | rule.onNodeWithText(env.user.name).assertExists() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/CompositionLocalsTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import ch.epfl.sdp.mobile.state.* 5 | import org.junit.Assert.assertThrows 6 | import org.junit.Rule 7 | import org.junit.Test 8 | 9 | class CompositionLocalsTest { 10 | 11 | @get:Rule val rule = createComposeRule() 12 | 13 | @Test 14 | fun missingAuthenticationApi_throwsException() { 15 | assertThrows(IllegalStateException::class.java) { 16 | rule.setContent { LocalAuthenticationFacade.current } 17 | } 18 | } 19 | 20 | @Test 21 | fun missingChessFacade_throwsException() { 22 | assertThrows(IllegalStateException::class.java) { rule.setContent { LocalChessFacade.current } } 23 | } 24 | 25 | @Test 26 | fun missingSocialFacade_throwsException() { 27 | assertThrows(IllegalStateException::class.java) { 28 | rule.setContent { LocalSocialFacade.current } 29 | } 30 | } 31 | 32 | @Test 33 | fun missingSpeechFacade_throwsException() { 34 | assertThrows(IllegalStateException::class.java) { 35 | rule.setContent { LocalSpeechFacade.current } 36 | } 37 | } 38 | 39 | @Test 40 | fun missingSettingsFacade_throwsException() { 41 | assertThrows(IllegalStateException::class.java) { 42 | rule.setContent { LocalSettingsFacade.current } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/HomeActivityTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.test.ext.junit.rules.ActivityScenarioRule 5 | import ch.epfl.sdp.mobile.state.HomeActivity 6 | import com.google.common.truth.Truth.assertThat 7 | import org.junit.Rule 8 | import org.junit.Test 9 | 10 | class HomeActivityTest { 11 | 12 | @get:Rule val rule = ActivityScenarioRule(HomeActivity::class.java) 13 | 14 | @Test 15 | fun activity_getsCreated() { 16 | rule.scenario.moveToState(Lifecycle.State.CREATED) 17 | assertThat(rule.scenario.state).isEqualTo(Lifecycle.State.CREATED) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/LocalizedStrings.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import androidx.compose.ui.test.junit4.ComposeContentTestRule 6 | import ch.epfl.sdp.mobile.state.LocalLocalizedStrings 7 | import ch.epfl.sdp.mobile.ui.i18n.English 8 | import ch.epfl.sdp.mobile.ui.i18n.LocalizedStrings 9 | 10 | /** 11 | * Sets the current composable as the content of your test screen, and applies the given localized 12 | * strings to the tested composition. 13 | * 14 | * This function should only be used once. 15 | * 16 | * @see ComposeContentTestRule.setContent 17 | * 18 | * @param strings the [LocalizedStrings] which should be applied. 19 | * @param content the body of the composable under test. 20 | * 21 | * @return the [LocalizedStrings] which were used when setting the content. 22 | */ 23 | fun ComposeContentTestRule.setContentWithLocalizedStrings( 24 | strings: LocalizedStrings = English, 25 | content: @Composable () -> Unit, 26 | ): LocalizedStrings { 27 | setContent { CompositionLocalProvider(LocalLocalizedStrings provides strings) { content() } } 28 | return strings 29 | } 30 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/ProfileColorTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import ch.epfl.sdp.mobile.application.Profile.Color as ProfileColor 5 | import ch.epfl.sdp.mobile.state.toColor 6 | import com.google.common.truth.Truth.assertThat 7 | import org.junit.Test 8 | 9 | class ProfileColorTest { 10 | 11 | @Test 12 | fun toColor_worksWithGoodColor() { 13 | assertThat(ProfileColor("#01234567").toColor()).isEqualTo(Color(0x01234567)) 14 | } 15 | 16 | @Test 17 | fun toColor_withMissingLeadingHashtag_usesDefault() { 18 | assertThat(ProfileColor("01234567").toColor()).isEqualTo(ProfileColor.Default.toColor()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/StatefulEditProfileNameDialogTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import androidx.compose.ui.test.* 4 | import androidx.compose.ui.test.junit4.createComposeRule 5 | import ch.epfl.sdp.mobile.state.Navigation 6 | import kotlinx.coroutines.test.runTest 7 | import org.junit.Rule 8 | import org.junit.Test 9 | 10 | class StatefulEditProfileNameDialogTest { 11 | 12 | @get:Rule val rule = createComposeRule() 13 | 14 | @Test 15 | fun given_userIsLoggedIn_when_editProfileName_then_nameShouldBeUpdated() = runTest { 16 | val (_, _, strings, user) = rule.setContentWithAuthenticatedTestEnvironment { Navigation() } 17 | 18 | rule.onNodeWithText(strings.sectionSettings).performClick() 19 | rule.onNodeWithContentDescription(strings.profileEditNameIcon).performClick() 20 | rule.onNode(hasText(user.name) and hasSetTextAction()).performTextReplacement("New name") 21 | rule.onNodeWithText(strings.settingEditSave).performClick() 22 | rule.onNodeWithText("New name").assertIsDisplayed() 23 | } 24 | 25 | @Test 26 | fun given_userIsLoggedIn_when_editProfileName_then_cancelWithoutSave() = runTest { 27 | val (_, _, strings, user) = rule.setContentWithAuthenticatedTestEnvironment { Navigation() } 28 | 29 | rule.onNodeWithText(strings.sectionSettings).performClick() 30 | rule.onNodeWithContentDescription(strings.profileEditNameIcon).performClick() 31 | rule.onNodeWithText(strings.settingEditCancel).performClick() 32 | rule.onNodeWithText(user.name).assertIsDisplayed() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/state/Utils.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.state 2 | 3 | import android.app.Activity 4 | import android.app.Instrumentation.ActivityResult 5 | import androidx.test.espresso.intent.Intents 6 | import androidx.test.espresso.intent.matcher.IntentMatchers 7 | 8 | /** 9 | * Executes the given [block] by returning an [ActivityResult] with the code 10 | * [Activity.RESULT_CANCELED] each time an intent is triggered. This simulates devices which do not 11 | * support AR. 12 | * 13 | * @param block the block of code to execute. 14 | */ 15 | inline fun withCanceledIntents(block: () -> Unit) { 16 | try { 17 | Intents.init() 18 | Intents.intending(IntentMatchers.anyIntent()) 19 | .respondWith(ActivityResult(Activity.RESULT_CANCELED, null)) 20 | block() 21 | } finally { 22 | Intents.release() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/Layout.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui 2 | 3 | import androidx.compose.ui.geometry.Offset 4 | import androidx.compose.ui.geometry.Rect 5 | import androidx.compose.ui.semantics.SemanticsNode 6 | import androidx.compose.ui.unit.DpOffset 7 | import androidx.compose.ui.unit.DpRect 8 | 9 | /** 10 | * Returns the [DpRect] for the bounds of this [SemanticsNode] in the layout root. 11 | * 12 | * @receiver the [SemanticsNode] whose bounds we are computing. 13 | * @return the [DpRect] for the semantics node bounds. 14 | * 15 | * @see [androidx.compose.ui.test.getBoundsInRoot] 16 | */ 17 | fun SemanticsNode.getBoundsInRoot(): DpRect = 18 | with(root!!.density) { 19 | boundsInRoot.let { DpRect(it.left.toDp(), it.top.toDp(), it.right.toDp(), it.bottom.toDp()) } 20 | } 21 | 22 | /** 23 | * Returns true iff the [DpOffset] is contained within this [DpRect] bounds. 24 | * 25 | * @receiver the bounds of the [DpRect]. 26 | * @param offset the offset that we're checking some bounds for. 27 | * @return true if the offset is contained. 28 | */ 29 | operator fun DpRect.contains(offset: DpOffset): Boolean { 30 | return offset.x in left..right && offset.y in top..bottom 31 | } 32 | 33 | /** 34 | * Returns true iff the [Offset] is contained within this [Rect] bounds. 35 | * 36 | * @receiver the bounds of the [Rect]. 37 | * @param offset the offset that we're checking some bounds for. 38 | * @return true if the offset is contained. 39 | */ 40 | operator fun Rect.contains(offset: Offset): Boolean { 41 | return offset.x in left..right && offset.y in top..bottom 42 | } 43 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/authentication/LoadingButtonTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.authentication 2 | 3 | import androidx.compose.ui.semantics.ProgressBarRangeInfo.Companion.Indeterminate 4 | import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo 5 | import androidx.compose.ui.test.SemanticsMatcher.Companion.expectValue 6 | import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined 7 | import androidx.compose.ui.test.junit4.createComposeRule 8 | import ch.epfl.sdp.mobile.ui.authentication.LoadingButton 9 | import org.junit.Rule 10 | import org.junit.Test 11 | 12 | class LoadingButtonTest { 13 | 14 | @get:Rule val rule = createComposeRule() 15 | 16 | @Test 17 | fun loadingButton_hasProgressSemantics() { 18 | rule.setContent { LoadingButton(loading = true, onClick = {}) {} } 19 | rule.onNode(expectValue(ProgressBarRangeInfo, Indeterminate)).assertExists() 20 | } 21 | 22 | @Test 23 | fun notLoadingButton_hasNoProgressSemantics() { 24 | rule.setContent { LoadingButton(loading = false, onClick = {}) {} } 25 | rule.onNode(keyIsDefined(ProgressBarRangeInfo)).assertDoesNotExist() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/game/GameScreenRobot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.game 2 | 3 | import androidx.compose.ui.test.assertIsDisplayed 4 | import androidx.compose.ui.test.junit4.ComposeTestRule 5 | import androidx.compose.ui.test.onNodeWithText 6 | import ch.epfl.sdp.mobile.test.ui.AbstractRobot 7 | import ch.epfl.sdp.mobile.ui.i18n.LocalizedStrings 8 | 9 | /** 10 | * A robot which performs actions on the game screen. 11 | * 12 | * @param rule the underlying [ComposeTestRule]. 13 | * @param strings the [LocalizedStrings] for the composition. 14 | */ 15 | class GameScreenRobot( 16 | rule: ComposeTestRule, 17 | strings: LocalizedStrings, 18 | ) : AbstractRobot(rule, strings) { 19 | 20 | /** Asserts that this robot is currently displayed. */ 21 | fun assertIsDisplayed() = chessboard { assertIsDisplayed() } 22 | 23 | /** Asserts that this robot is not currently displayed. */ 24 | fun assertIsNotDisplayed() = chessboard { assertIsNotDisplayed() } 25 | 26 | /** 27 | * Asserts that the player with the given name is playing. 28 | * 29 | * @param name the name of the player. 30 | */ 31 | fun assertHasPlayer(name: String) { 32 | onNodeWithText(name).assertIsDisplayed() 33 | } 34 | 35 | /** 36 | * Executes the given actions in the scope of the [ChessBoardRobot] for this screen. 37 | * 38 | * @param R the return type of the [block]. 39 | * @param block the code to execute. 40 | */ 41 | inline fun chessboard( 42 | block: ChessBoardRobot.() -> R, 43 | ): R = switchTo(::ChessBoardRobot).run(block) 44 | } 45 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/play/PlayScreenTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.play 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.compose.ui.test.onNodeWithText 5 | import ch.epfl.sdp.mobile.test.state.setContentWithLocalizedStrings 6 | import ch.epfl.sdp.mobile.ui.play.PlayScreen 7 | import ch.epfl.sdp.mobile.ui.play.PlayScreenState 8 | import ch.epfl.sdp.mobile.ui.social.ChessMatch 9 | import org.junit.Rule 10 | import org.junit.Test 11 | 12 | open class TestPlayScreenState( 13 | val onGameItemClickAction: (ChessMatch) -> Unit, 14 | override val matches: List 15 | ) : PlayScreenState { 16 | override fun onLocalGameClick() {} 17 | override fun onOnlineGameClick() {} 18 | override fun onMatchClick(match: ChessMatch) { 19 | onGameItemClickAction(match) 20 | } 21 | } 22 | 23 | object FakePlayScreenState : TestPlayScreenState({}, emptyList()) 24 | 25 | class PlayScreenTest { 26 | @get:Rule val rule = createComposeRule() 27 | 28 | @Test 29 | fun newGame_isDisplayed() { 30 | val strings = rule.setContentWithLocalizedStrings { PlayScreen(FakePlayScreenState) } 31 | rule.onNodeWithText(strings.newGame).assertExists() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/profile/UserScreenTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.profile 2 | 3 | import androidx.compose.foundation.lazy.rememberLazyListState 4 | import androidx.compose.material.Text 5 | import androidx.compose.ui.test.junit4.createComposeRule 6 | import androidx.compose.ui.test.onNodeWithText 7 | import ch.epfl.sdp.mobile.state.ChessMatchAdapter 8 | import ch.epfl.sdp.mobile.test.state.setContentWithLocalizedStrings 9 | import ch.epfl.sdp.mobile.ui.profile.UserScreen 10 | import ch.epfl.sdp.mobile.ui.profile.rememberProfileTabBarState 11 | import ch.epfl.sdp.mobile.ui.social.ChessMatch 12 | import ch.epfl.sdp.mobile.ui.social.Tie 13 | import org.junit.Rule 14 | import org.junit.Test 15 | 16 | class UserScreenTest { 17 | 18 | @get:Rule val rule = createComposeRule() 19 | 20 | @Test 21 | fun given_listOfMatches_when_loaded_then_display() { 22 | val strings = 23 | rule.setContentWithLocalizedStrings { 24 | UserScreen( 25 | header = { Text("Header") }, 26 | profileTabBar = { Text("ProfileTabBar") }, 27 | tabBarState = rememberProfileTabBarState(pastGamesCount = 0, puzzlesCount = 0), 28 | matches = listOf(ChessMatchAdapter("1", "adversary", Tie, 0)), 29 | onMatchClick = {}, 30 | puzzles = emptyList(), 31 | onPuzzleClick = {}, 32 | lazyColumnState = rememberLazyListState()) 33 | } 34 | 35 | rule.onNodeWithText("Header").assertExists() 36 | rule.onNodeWithText(strings.profileMatchTitle("adversary")).assertExists() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/setting/SettingTabBarTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.setting 2 | 3 | import androidx.compose.ui.test.assertIsSelected 4 | import androidx.compose.ui.test.junit4.createComposeRule 5 | import androidx.compose.ui.test.onNodeWithText 6 | import androidx.compose.ui.test.performClick 7 | import ch.epfl.sdp.mobile.test.state.setContentWithLocalizedStrings 8 | import ch.epfl.sdp.mobile.ui.profile.ProfileTabBar 9 | import ch.epfl.sdp.mobile.ui.profile.rememberProfileTabBarState 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class SettingTabBarTest { 14 | 15 | @get:Rule val rule = createComposeRule() 16 | 17 | @Test 18 | fun clickingPastGames_preservesSelection() { 19 | val strings = 20 | rule.setContentWithLocalizedStrings { 21 | val state = rememberProfileTabBarState(0, 0) 22 | ProfileTabBar(state) 23 | } 24 | 25 | rule.onNodeWithText(strings.profilePastGames).performClick() 26 | rule.onNodeWithText(strings.profilePastGames).assertIsSelected() 27 | } 28 | 29 | @Test 30 | fun clickingPuzzles_selectsPuzzles() { 31 | val strings = 32 | rule.setContentWithLocalizedStrings { 33 | val state = rememberProfileTabBarState(0, 0) 34 | ProfileTabBar(state) 35 | } 36 | 37 | rule.onNodeWithText(strings.profilePuzzle).performClick() 38 | rule.onNodeWithText(strings.profilePuzzle).assertIsSelected() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/social/SearchFieldTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.social 2 | 3 | import androidx.compose.runtime.mutableStateOf 4 | import androidx.compose.runtime.remember 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.platform.testTag 7 | import androidx.compose.ui.test.* 8 | import androidx.compose.ui.test.junit4.createComposeRule 9 | import ch.epfl.sdp.mobile.test.state.setContentWithLocalizedStrings 10 | import ch.epfl.sdp.mobile.ui.social.SearchField 11 | import org.junit.Rule 12 | import org.junit.Test 13 | 14 | class SearchFieldTest { 15 | 16 | @get:Rule val rule = createComposeRule() 17 | 18 | @Test 19 | fun typingText_displaysText() { 20 | rule.setContent { 21 | val (value, setValue) = remember { mutableStateOf("") } 22 | SearchField(value, setValue, Modifier.testTag("search")) 23 | } 24 | rule.onNodeWithTag("search").performTextInput("Hello world") 25 | 26 | rule.onNodeWithText("Hello world").assertExists() 27 | } 28 | 29 | @Test 30 | fun clickingClearIcon_clearsText() { 31 | val strings = 32 | rule.setContentWithLocalizedStrings { 33 | val (value, setValue) = remember { mutableStateOf("") } 34 | SearchField(value, setValue, Modifier.testTag("search")) 35 | } 36 | rule.onNodeWithTag("search").performTextInput("Hello world") 37 | rule.onNodeWithContentDescription(strings.socialSearchClearContentDescription).performClick() 38 | 39 | rule.onNodeWithText(strings.socialSearchBarPlaceHolder).assertExists() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/social/SocialCardTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.social 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.compose.ui.test.onNodeWithText 5 | import ch.epfl.sdp.mobile.application.Profile.Color 6 | import ch.epfl.sdp.mobile.state.toColor 7 | import ch.epfl.sdp.mobile.ui.social.Person 8 | import ch.epfl.sdp.mobile.ui.social.PersonItem 9 | import org.junit.Rule 10 | import org.junit.Test 11 | 12 | class SocialCardTest { 13 | @get:Rule val rule = createComposeRule() 14 | 15 | private class FakeFriendCard : Person { 16 | override val backgroundColor = Color.Default.toColor() 17 | override val name: String = "Toto" 18 | override val emoji: String = ":3" 19 | override val followed: Boolean = false 20 | } 21 | 22 | @Test 23 | fun card_displayCorrectName() { 24 | 25 | rule.setContent { PersonItem(person = FakeFriendCard(), onShowProfileClick = {}) } 26 | 27 | rule.onNodeWithText("Toto").assertExists() 28 | } 29 | 30 | @Test 31 | fun card_displayCorrectEmoji() { 32 | rule.setContent { PersonItem(person = FakeFriendCard(), onShowProfileClick = {}) } 33 | 34 | rule.onNodeWithText(":3").assertExists() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/ch/epfl/sdp/mobile/test/ui/tournaments/NextStepBannerTest.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.test.ui.tournaments 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.compose.ui.test.onNodeWithText 5 | import androidx.compose.ui.test.performClick 6 | import ch.epfl.sdp.mobile.ui.tournaments.GreenNextStepBanner 7 | import ch.epfl.sdp.mobile.ui.tournaments.OrangeNextStepBanner 8 | import com.google.common.truth.Truth.assertThat 9 | import kotlinx.coroutines.channels.Channel 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class NextStepBannerTest { 14 | 15 | @get:Rule val rule = createComposeRule() 16 | 17 | @Test 18 | fun given_greenBanner_when_clicking_then_callsCallback() { 19 | val channel = Channel(1) 20 | rule.setContent { 21 | GreenNextStepBanner( 22 | title = "hello", 23 | message = "there", 24 | onClick = { channel.trySend(Unit) }, 25 | ) 26 | } 27 | rule.onNodeWithText("hello").performClick() 28 | assertThat(channel.tryReceive().getOrNull()).isEqualTo(Unit) 29 | } 30 | 31 | @Test 32 | fun given_orangeBanner_when_clicking_then_callsCallback() { 33 | val channel = Channel(1) 34 | rule.setContent { 35 | OrangeNextStepBanner( 36 | title = "hello", 37 | message = "there", 38 | onClick = { channel.trySend(Unit) }, 39 | ) 40 | } 41 | rule.onNodeWithText("hello").performClick() 42 | assertThat(channel.tryReceive().getOrNull()).isEqualTo(Unit) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mobile/src/main/assets/models/bishop.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/bishop.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/blue.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/blue.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/board.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/board.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/green.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/green.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/king.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/king.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/knight.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/knight.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/pawn.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/pawn.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/queen.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/queen.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/red.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/red.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/rook.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/rook.glb -------------------------------------------------------------------------------- /mobile/src/main/assets/models/white.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/assets/models/white.glb -------------------------------------------------------------------------------- /mobile/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/authentication/AuthenticationResult.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.authentication 2 | 3 | /** An enumeration which represents the result of an authentication operation. */ 4 | enum class AuthenticationResult { 5 | 6 | /** Indicates that there was a success during authentication. */ 7 | Success, 8 | 9 | /** Indicates that there was a failure during authentication. */ 10 | Failure, 11 | 12 | /** Indicates that the password the user chose for their account is not strong enough. */ 13 | FailureBadPassword, 14 | 15 | /** Indicates that the email the user typed is malformed. */ 16 | FailureIncorrectEmailFormat, 17 | 18 | /** Indicates that an account already exists with the given email. */ 19 | FailureExistingAccount, 20 | 21 | /** Indicates that the given password associated to an email is incorrect. */ 22 | FailureIncorrectPassword, 23 | 24 | /** 25 | * Indicates that the user account corresponding to an email does not exist or has been disabled. 26 | */ 27 | FailureInvalidUser, 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/authentication/AuthenticationUser.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.authentication 2 | 3 | /** 4 | * An interface which represents the current authentication status of the user of the 5 | * [AuthenticationFacade]. 6 | */ 7 | sealed interface AuthenticationUser 8 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/authentication/NotAuthenticatedUser.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.authentication 2 | 3 | /** Indicates that no [AuthenticationUser] is currently authenticated. */ 4 | object NotAuthenticatedUser : AuthenticationUser 5 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/Match.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess 2 | 3 | import ch.epfl.sdp.mobile.application.Profile 4 | import ch.epfl.sdp.mobile.application.chess.engine.Game 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** Represents a [Game] between two online players. */ 8 | interface Match { 9 | 10 | /** The id of the [Match]. */ 11 | val id: String? 12 | 13 | /** The [Flow] of the current [Game] state. */ 14 | val game: Flow 15 | 16 | /** The [Flow] of the [Profile] of the white player. */ 17 | val white: Flow 18 | 19 | /** The [Flow] of the [Profile] of the black player. */ 20 | val black: Flow 21 | 22 | /** 23 | * Updates the [Match] with a new [Game]. 24 | * 25 | * @param game the new [Game] with which to update the [Match]. 26 | */ 27 | suspend fun update(game: Game) 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/Board.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.implementation.MutableBoard 4 | 5 | /** 6 | * A [Board] contains a bunch of [Piece] positioned in a certain way. 7 | * 8 | * @param Piece the type of the pieces which are present in a board. 9 | */ 10 | interface Board : Iterable> { 11 | 12 | /** 13 | * Returns the [Piece] at a certain [Position] in the board, or null if no [Piece] is localed 14 | * there. 15 | * 16 | * @param position the [Position] for which we're trying to find a [Piece]. 17 | * @return the [Piece] which was found. 18 | */ 19 | operator fun get(position: Position): Piece? 20 | 21 | companion object { 22 | 23 | /** The size of a [Board]. */ 24 | const val Size = MutableBoard.Size 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/Color.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine 2 | 3 | /** 4 | * The classical colors of a game of chess. 5 | * 6 | * @param opposite a function that returns the opposite color. 7 | */ 8 | enum class Color(private val opposite: () -> Color) { 9 | 10 | /** The black color. */ 11 | Black(opposite = { White }), 12 | 13 | /** The white color. Starts the game. */ 14 | White(opposite = { Black }); 15 | 16 | /** Returns the [Color] of the adversary. */ 17 | fun other(): Color = opposite() 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/NextStep.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine 2 | 3 | /** 4 | * An interface representing the next steps that could be performed on an existing [Game] instance. 5 | * Because [Game] is immutable, the only way to move forward in a chess game is to perform a 6 | * [NextStep] which actually updates the state of the [Game] and returns an updated [Game]. 7 | */ 8 | sealed interface NextStep { 9 | 10 | /** 11 | * A [NextStep] which indicates that the current player may not perform any moves without putting 12 | * themselves in a check position, but is not currently in check. 13 | */ 14 | object Stalemate : NextStep 15 | 16 | /** 17 | * A [NextStep] which indicates that the game is over. 18 | * 19 | * @param winner the [Color] of the player who won. 20 | */ 21 | data class Checkmate(val winner: Color) : NextStep 22 | 23 | /** 24 | * A [NextStep] which indicates that the [Game] expects the player with color [turn] to perform a 25 | * move. 26 | * 27 | * @param turn the [Color] of the player who should perform a move next. 28 | * @param inCheck returns true if the current player is in check. 29 | * @param move provides the next [Game] depending on which [Action] was selected to be played. 30 | */ 31 | data class MovePiece( 32 | val turn: Color, 33 | val inCheck: Boolean, 34 | val move: (Action) -> Game, 35 | ) : NextStep 36 | } 37 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/Piece.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine 2 | 3 | /** 4 | * A class representing a chess piece, with its color and rank. 5 | * 6 | * When checking for equality, pieces should first check for their [color] and [rank], and if they 7 | * are equal, the [id] should be compared as well. A [Board] may contain some pieces with the same 8 | * [id] but different colors and ranks, but each triplet ([Color], [Rank], [id]) will be unique. 9 | * 10 | * @param Color type of the color of a [Piece]. 11 | * @property color the [Color] of the player who owns the piece. 12 | * @property rank the rank indicating the abilities of the [Piece]. 13 | * @property id the unique identifier for this piece, used for disambiguation. 14 | */ 15 | data class Piece( 16 | val color: Color, 17 | val rank: Rank, 18 | val id: PieceIdentifier, 19 | ) 20 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/PieceIdentifier.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine 2 | 3 | /** 4 | * A way for pieces to be uniquely identified. This will let the rendering system smoothly animate 5 | * between board positions, since pieces with the same stable ids can have their positions 6 | * interpolated. 7 | * 8 | * @property value the backing [Int], in which a piece identifier is encoded. 9 | */ 10 | @JvmInline 11 | value class PieceIdentifier(val value: Int) : Comparable { 12 | 13 | override operator fun compareTo(other: PieceIdentifier): Int = value.compareTo(other.value) 14 | 15 | /** Returns an incremented [PieceIdentifier]. */ 16 | operator fun inc(): PieceIdentifier { 17 | return PieceIdentifier(value + 1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/Rank.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.rules.* 4 | import ch.epfl.sdp.mobile.application.chess.engine.rules.Rules 5 | 6 | /** An enumeration representing the abilities of each [Piece] in classic chess. */ 7 | enum class Rank(delegate: Rules) : Rules by delegate { 8 | King(KingRules), 9 | Queen(QueenRules), 10 | Rook(RookRules), 11 | Bishop(BishopRules), 12 | Knight(KnightRules), 13 | Pawn(PawnRules), 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/implementation/Conversions.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.implementation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.* 4 | 5 | /** 6 | * Transforms this [Board] to a [MutableBoard]. 7 | * 8 | * @receiver the [Board] with some [Piece] of [Color]. 9 | * @return the newly created [MutableBoard]. 10 | */ 11 | fun Board>.toMutableBoard(): MutableBoard { 12 | val board = MutableBoard() 13 | for ((pos, piece) in this) { 14 | board[pos] = 15 | MutableBoardPiece( 16 | id = piece.id, 17 | rank = piece.rank, 18 | color = piece.color, 19 | ) 20 | } 21 | return board 22 | } 23 | 24 | /** Transforms this [MutableBoard] to a [Board] with some [Piece] o [Color]. */ 25 | fun MutableBoard.toBoard(): Board> = buildBoard { 26 | forEachPiece { (x, y), piece -> piece.toPiece()?.let { set(Position(x, y), it) } } 27 | } 28 | 29 | /** Maps this [MutableBoardPiece] to the corresponding [Piece] of [Color]. */ 30 | fun MutableBoardPiece.toPiece(): Piece? { 31 | val color = color ?: return null 32 | val rank = rank ?: return null 33 | return Piece(color, rank, PieceIdentifier(id)) 34 | } 35 | 36 | /** Maps this [Piece] to the corresponding [MutableBoardPiece]. */ 37 | fun Piece?.toMutableBoardPiece(): MutableBoardPiece { 38 | this ?: return MutableBoardPiece.None 39 | return MutableBoardPiece(id, rank, color) 40 | } 41 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/implementation/MutableBoardAttacked.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.implementation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Color 4 | import ch.epfl.sdp.mobile.application.chess.engine.Position 5 | import ch.epfl.sdp.mobile.application.chess.engine.rules.AttackScope 6 | import ch.epfl.sdp.mobile.application.chess.engine.rules.Attacked 7 | 8 | /** 9 | * An implementation of [Attacked] which computes all the attacks performed by the player of color 10 | * [Color] on the given [MutableBoard]. 11 | * 12 | * @property board the [MutableBoard] for which the attacks are computed. 13 | * @param player the [Color] of the attacking player. 14 | */ 15 | class MutableBoardAttacked( 16 | private val board: MutableBoard, 17 | player: Color, 18 | ) : Attacked, AttackScope { 19 | 20 | /** An [Array] of [BooleanArray] with the cells and whether they're attacked or not. */ 21 | private val cells = Array(MutableBoard.Size) { BooleanArray(MutableBoard.Size) } 22 | 23 | init { 24 | board.forEachPiece { position, piece -> 25 | val rank = requireNotNull(piece.rank) 26 | val color = requireNotNull(piece.color) 27 | if (player == color) { 28 | with(rank) { attacks(player, position) } 29 | } 30 | } 31 | } 32 | 33 | override fun isAttacked(position: Position): Boolean = 34 | position.inBounds && cells[position.x][position.y] 35 | override fun get(position: Position) = board[position] 36 | override fun attack(position: Position) { 37 | if (position.inBounds) cells[position.x][position.y] = true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/rules/AttackRules.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Color 4 | import ch.epfl.sdp.mobile.application.chess.engine.Delta 5 | import ch.epfl.sdp.mobile.application.chess.engine.Position 6 | 7 | /** 8 | * An implementation of [Rules] which attacks and moves with a set of possible [Delta]. 9 | * 10 | * @property directions the [List] of possible directions. 11 | */ 12 | abstract class AttackRules(private val directions: List) : Rules { 13 | 14 | override fun AttackScope.attacks(color: Color, position: Position) { 15 | for (direction in directions) { 16 | val next = position + direction 17 | if (next.inBounds) { 18 | val existing = get(next) 19 | if (existing.isNone || existing.color != color) { 20 | attack(next) 21 | } 22 | } 23 | } 24 | } 25 | 26 | override fun ActionScope.actions(color: Color, position: Position) = likeAttacks(color, position) 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/rules/AttackTowardsRules.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Color 4 | import ch.epfl.sdp.mobile.application.chess.engine.Delta 5 | import ch.epfl.sdp.mobile.application.chess.engine.Position 6 | 7 | /** 8 | * An implementation of [Rules] which attacks and moves towards a [List] of possible directions. 9 | * 10 | * @property directions the [List] of possible directions. 11 | */ 12 | abstract class AttackTowardsRules(private val directions: List) : Rules { 13 | 14 | override fun AttackScope.attacks(color: Color, position: Position) { 15 | for (direction in directions) { 16 | attackTowards(direction, color, position) 17 | } 18 | } 19 | 20 | override fun ActionScope.actions(color: Color, position: Position) = likeAttacks(color, position) 21 | } 22 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/rules/BishopRules.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Delta 4 | 5 | /** A rank implementation for bishops. */ 6 | object BishopRules : AttackTowardsRules(Delta.Directions.Diagonals) 7 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/rules/KnightRules.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Delta.CardinalPoints.E 4 | import ch.epfl.sdp.mobile.application.chess.engine.Delta.CardinalPoints.N 5 | import ch.epfl.sdp.mobile.application.chess.engine.Delta.CardinalPoints.S 6 | import ch.epfl.sdp.mobile.application.chess.engine.Delta.CardinalPoints.W 7 | 8 | /** All the directions in which a knight is allowed to jump. */ 9 | private val KnightDirections = 10 | listOf( 11 | N + N + E, 12 | N + E + E, 13 | S + E + E, 14 | S + S + E, 15 | S + S + W, 16 | S + W + W, 17 | N + W + W, 18 | N + N + W, 19 | ) 20 | 21 | /** A rank implementation for knights. */ 22 | object KnightRules : AttackRules(KnightDirections) 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/rules/QueenRules.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Delta.Directions 4 | 5 | /** A rank implementation for queens. */ 6 | object QueenRules : AttackTowardsRules(Directions.Lines + Directions.Diagonals) 7 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/rules/RookRules.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.engine.rules 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Delta.Directions 4 | 5 | /** A rank implementation for rooks. */ 6 | object RookRules : AttackTowardsRules(Directions.Lines) 7 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/engine/utils/Packing.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("NOTHING_TO_INLINE") 2 | 3 | package ch.epfl.sdp.mobile.application.chess.engine.utils 4 | 5 | /** 6 | * Unpacks the 4 most significant bytes from [value] and transforms them into a [Short]. 7 | * @param value the original [Int] value. 8 | * @return the unpacked [Short]. 9 | */ 10 | inline fun unpackShort1(value: Int): Short = (value shr Short.SIZE_BITS).toShort() 11 | 12 | /** 13 | * Unpacks the 4 least significant bytes from [value] and transforms them into a [Short]. 14 | * @param value the original [Int] value. 15 | * @return the unpacked [Short]. 16 | */ 17 | inline fun unpackShort2(value: Int): Short = (value and 0xFFFF).toShort() 18 | 19 | /** 20 | * Packs two [Short] values in an [Int] to avoid boxing them in an object. 21 | * @param val1 the first [Short] to pack. Can be unpacked with [unpackShort1]. 22 | * @param val2 the second [Short] to pack. Can be unpacked with [unpackShort2]. 23 | * @return the packed [Int] with both values. 24 | */ 25 | inline fun packShorts(val1: Short, val2: Short): Int = 26 | (val1.toInt() shl Short.SIZE_BITS) or (val2.toInt() and 0xFFFF) 27 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/notation/Flows.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.notation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Game 4 | import ch.epfl.sdp.mobile.application.chess.notation.AlgebraicNotation.parseGame 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flow 7 | 8 | /** 9 | * Returns the longest common prefix length between two [List]. 10 | * 11 | * @param a the first [List] to inspect. 12 | * @param b the second [List] to inspect. 13 | */ 14 | private fun prefixLength(a: List<*>, b: List<*>): Int = 15 | a.asSequence().zip(b.asSequence()).takeWhile { (a, b) -> (a == b) }.count() 16 | 17 | /** 18 | * A specialized [Flow] operator which incrementally maps some lists of [String] moves to the 19 | * resulting chess [Game]. 20 | * 21 | * @receiver a [Flow] of the list of the moves. 22 | * @return a [Flow] of the latest [Game] state. 23 | */ 24 | fun Flow>.mapToGame(): Flow = flow { 25 | var game = Game.create() 26 | var moves = emptyList() 27 | collect { list -> 28 | val prefix = prefixLength(moves, list) 29 | game = 30 | if (prefix == moves.size) parseGame(list.drop(prefix), initial = game) // Incremental. 31 | else parseGame(list) 32 | moves = list 33 | emit(game) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/notation/UCINotation.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.notation 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Action 4 | import ch.epfl.sdp.mobile.application.chess.notation.UCINotationCombinators.actions 5 | 6 | /** An object which contains some utilities to transform UCI notation to a [List] of [Action]s. */ 7 | object UCINotation { 8 | 9 | /** 10 | * Parses a [List] of [Action] from the provided [String] text in UCI notation. 11 | * 12 | * @param text the [String] that should be parsed. 13 | * @return the [List] of [Action] that was found. 14 | */ 15 | fun parseActions(text: String): List? { 16 | return actions().parse(input = text).firstOrNull()?.output 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/parser/Parser.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.parser 2 | 3 | /** 4 | * An interface which acts as the basis of some parser combinators. A [Parser] takes an [Input], and 5 | * returns a sequence of [Result]. 6 | * 7 | * @param Input the type of the inputs parsed by this [Parser]. 8 | * @param Output the type of the outputs generated by this [Parser]. 9 | */ 10 | fun interface Parser { 11 | 12 | /** 13 | * An intermediate result from the [Parser]. 14 | * 15 | * @param Input the type of the inputs of the parser. 16 | * @param Output the type of the outputs of the parser. 17 | * @property remaining the [Input] that can still be parsed. 18 | * @property output the [Output] generated by the parser. 19 | */ 20 | data class Result( 21 | val remaining: Input, 22 | val output: Output, 23 | ) 24 | 25 | /** 26 | * Parses the [Input] and results all the possible [Result]. 27 | * 28 | * @param input the [Input] to the parsed. 29 | * @return the resulting [Sequence] of [Result]. 30 | */ 31 | fun parse(input: Input): Sequence> 32 | } 33 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/chess/voice/VoiceInput.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.chess.voice 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Action 4 | 5 | /** An object which contains some utilities to transform user's voice input to a [Action]. */ 6 | object VoiceInput { 7 | 8 | /** 9 | * Parses a [List] of possible matching voice input into a [Action]. 10 | * 11 | * @param input the [List] of candidate voice input. 12 | * @return Null if none of the input can be transformed into an [Action] return null, otherwise 13 | * the parsed [Action]. 14 | */ 15 | fun parseInput(input: List): Action? { 16 | // TODO : parse until action found 17 | val parsedResult = 18 | input.firstNotNullOfOrNull { s -> 19 | // NOTE (Chau) : I let this log here in purpose. This allow us to refine our custom 20 | // [ChesSpeechEnglishDictionary]. If your command is not recognized and you think that we 21 | // need to add this in our dictionary, you can report it here : 22 | // https://github.com/epfl-SDP/android/issues/308 23 | println("DEBUGGING : SPEECH PARSING $s") 24 | 25 | val filtered = VoiceInputCombinator.action().parse(s.lowercase()).firstOrNull()?.output 26 | 27 | filtered 28 | } 29 | 30 | return parsedResult 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/speech/ChessDictionary.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.speech 2 | 3 | /** 4 | * Interface representing the vocabulary of chess move commands chessPieces mapping of chess pieces 5 | * keywords to their homophones letters mapping of chess letter keywords to their homophones. 6 | */ 7 | interface ChessDictionary { 8 | 9 | /** A mapping of chess pieces keywords to their homophones. */ 10 | val chessPieces: Map> 11 | 12 | /** A mapping of chess number keywords to their homophones. */ 13 | val numbers: Map> 14 | 15 | /** A mapping of chess letter keywords to their homophones. */ 16 | val letters: Map> 17 | 18 | /** A mapping of action keywords to their homophones. */ 19 | val actions: Map> 20 | 21 | /** All possible combinations of chess board placement. */ 22 | val placements: Set 23 | } 24 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/application/tournaments/TournamentReference.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.application.tournaments 2 | 3 | /** 4 | * A class which represents a reference to a tournament. 5 | * 6 | * @property uid the unique identifier of the tournament. 7 | */ 8 | data class TournamentReference(val uid: String) 9 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/assets/AssetManager.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.assets 2 | 3 | /** Represents an Asset Manager, which allows to load assets in different manners. */ 4 | interface AssetManager { 5 | 6 | /** 7 | * Opens a certain asset file as a [String] of its content, if successful. 8 | * 9 | * @param path The path to the asset. 10 | * 11 | * @return the [String] of the content of the opened file, if successful. 12 | */ 13 | fun readText(path: String): String? 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/assets/android/AndroidAssetManager.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.assets.android 2 | 3 | import android.content.Context 4 | import ch.epfl.sdp.mobile.infrastructure.assets.AssetManager 5 | 6 | /** 7 | * Represents the Android Assets Manager, which allows to load assets in different manners. 8 | * 9 | * @property context The Android [Context] used to load assets. 10 | */ 11 | class AndroidAssetManager( 12 | private val context: Context, 13 | ) : AssetManager { 14 | override fun readText(path: String): String? { 15 | return runCatching { context.assets.open(path).reader().use { it.readText() } }.getOrNull() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/auth/User.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.auth 2 | 3 | /** 4 | * An interface representing a [User], which may be used to perform some basic authentication 5 | * queries. 6 | */ 7 | interface User { 8 | 9 | /** The unique identifier for this [User]. */ 10 | val uid: String 11 | 12 | /** The email address for this user, if the user performed authentication with their email. */ 13 | val email: String? 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/auth/firebase/FirebaseUser.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.auth.firebase 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.auth.User 4 | import com.google.firebase.auth.FirebaseUser as ActualFirebaseUser 5 | 6 | /** 7 | * An implementation of [User] which makes use of the [ActualFirebaseUser] API and adapts it. 8 | * 9 | * @property actual the [ActualFirebaseUser]. 10 | */ 11 | class FirebaseUser(private val actual: ActualFirebaseUser) : User { 12 | 13 | override val uid: String 14 | get() = actual.uid 15 | 16 | override val email: String? 17 | get() = actual.email 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/DataStore.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | /** 6 | * An interface representing a facility which may store some values of a type [T]. 7 | * 8 | * @param T the values which are stored. 9 | */ 10 | interface DataStore { 11 | 12 | /** Returns a [Flow] of the value in this [DataStore]. */ 13 | val data: Flow 14 | 15 | /** 16 | * Atomically updates the data from the [DataStore] in a serializable fashion. 17 | * 18 | * @param transform the function which updates the value atomically. 19 | * @return the updated value. 20 | */ 21 | suspend fun updateData(transform: suspend (T) -> T): T 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/DataStoreFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | /** An interface which may be used to create some [DataStore] implementations. */ 4 | interface DataStoreFactory { 5 | 6 | /** 7 | * Returns the default [DataStore] for storing some [Preferences], as well as the [KeyFactory] 8 | * associated with this [DataStore] instance. 9 | */ 10 | fun createPreferencesDataStore(): Pair, KeyFactory> 11 | } 12 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/Edit.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | /** 4 | * Edits the [DataStore] with [Preferences], and applies the changes in a serializable fashion. 5 | * 6 | * @receiver the [DataStore] on which the updates are performed. 7 | * @param transform the transformation to apply to the [MutablePreferences]. 8 | * @return the updated [Preferences], after the changes. 9 | */ 10 | suspend fun DataStore.edit( 11 | transform: suspend (MutablePreferences) -> Unit 12 | ): Preferences = updateData { it.toMutablePreferences().apply { transform(this) }.toPreferences() } 13 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/Key.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | /** 4 | * A [Key] represents the key of a value in the preferences. Keys should always be built using a 5 | * [KeyFactory]. 6 | * 7 | * @param T the type of the associated value. 8 | */ 9 | interface Key 10 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/KeyFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | /** An interface representing ways to build some [Key]s for a [DataStore]. */ 4 | interface KeyFactory { 5 | 6 | /** 7 | * Returns a [Key] associated with an [Int] value. 8 | * 9 | * @param name the name of the property. 10 | */ 11 | fun int(name: String): Key 12 | 13 | /** 14 | * Returns a [Key] associated with a [Double] value. 15 | * 16 | * @param name the name of the property. 17 | */ 18 | fun double(name: String): Key 19 | 20 | /** 21 | * Returns a [Key] associated with a [String] value. 22 | * 23 | * @param name the name of the property. 24 | */ 25 | fun string(name: String): Key 26 | 27 | /** 28 | * Returns a [Key] associated with a [Boolean] value. 29 | * 30 | * @param name the name of the property. 31 | */ 32 | fun boolean(name: String): Key 33 | 34 | /** 35 | * Returns a [Key] associated with a [Float] value. 36 | * 37 | * @param name the name of the property. 38 | */ 39 | fun float(name: String): Key 40 | 41 | /** 42 | * Returns a [Key] associated with a [Long] value. 43 | * 44 | * @param name the name of the property. 45 | */ 46 | fun long(name: String): Key 47 | 48 | /** 49 | * Returns a [Key] associated with a [Set] of [String] values. 50 | * 51 | * @param name the name of the property. 52 | */ 53 | fun stringSet(name: String): Key> 54 | } 55 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/MutablePreferences.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | /** An interface representing some write operations available on some [Preferences]. */ 4 | interface MutablePreferences : Preferences { 5 | 6 | /** 7 | * Sets the associated value of the given [Key]. 8 | * 9 | * @param T the type of the associated value of the [key]. 10 | * @param key the [Key] whose value is set. 11 | * @param value the value which is set. 12 | */ 13 | operator fun set(key: Key, value: T) 14 | 15 | /** 16 | * Removes the value associated with the given key. 17 | * 18 | * @param T the type of the associated value of the [key]. 19 | * @param key the [Key] whose value is removed. 20 | */ 21 | fun remove(key: Key) 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/Preferences.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore 2 | 3 | /** An interface representing some read-only key-value preferences. */ 4 | interface Preferences { 5 | 6 | /** 7 | * Returns true iff the [Preferences] contain the [key]. 8 | * 9 | * @param T the type of the associated value of the [key]. 10 | * @param key the [Key] whose presence is checked. 11 | * @return true iff the key is in the [Preferences]. 12 | */ 13 | operator fun contains(key: Key): Boolean 14 | 15 | /** 16 | * Returns the associated value of the given [Key]. 17 | * 18 | * @param T the type of the associated value of the [key]. 19 | * @param key the [Key] whose value is queried. 20 | * @return the associated value, or null if absent. 21 | */ 22 | operator fun get(key: Key): T? 23 | 24 | /** 25 | * Returns a copy of these [Preferences] which may be mutated. 26 | * 27 | * @see MutablePreferences 28 | */ 29 | fun toMutablePreferences(): MutablePreferences 30 | 31 | /** 32 | * Returns a copy of these [Preferences] which is immutable. 33 | * 34 | * @see Preferences 35 | */ 36 | fun toPreferences(): Preferences 37 | } 38 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/androidx/AndroidXKey.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx 2 | 3 | import androidx.datastore.preferences.core.Preferences.Key as ActualKey 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Key 5 | 6 | /** 7 | * An implementation of [Key] which uses an [ActualKey] under-the-hood. 8 | * 9 | * @param T the type of the value in the [Key]. 10 | * @property actual the actual backing key. 11 | */ 12 | data class AndroidXKey(val actual: ActualKey) : Key 13 | 14 | /** 15 | * Returns the [ActualKey] from this [Key]. If the key is not compatible and was not created for an 16 | * [AndroidXPreferencesDataStore], an exception will be thrown. 17 | * 18 | * @param T the type of the value associated with the key. 19 | * @receiver the [Key] from which an [ActualKey] is extracted. 20 | * @return [ActualKey] the actual underlying key. 21 | * @throws IllegalArgumentException if the [Key] is not an [AndroidXKey]. 22 | */ 23 | internal fun Key.extractActualKey(): ActualKey = 24 | requireNotNull((this as? AndroidXKey)?.actual) { 25 | "This Key<*> is incompatible with this DataStore." 26 | } 27 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/androidx/AndroidXKeyFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx 2 | 3 | import androidx.datastore.preferences.core.* 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.KeyFactory 5 | 6 | /** An object which implements [KeyFactory] and returns [AndroidXKey]. */ 7 | object AndroidXKeyFactory : KeyFactory { 8 | override fun int(name: String) = AndroidXKey(intPreferencesKey(name)) 9 | override fun double(name: String) = AndroidXKey(doublePreferencesKey(name)) 10 | override fun string(name: String) = AndroidXKey(stringPreferencesKey(name)) 11 | override fun boolean(name: String) = AndroidXKey(booleanPreferencesKey(name)) 12 | override fun float(name: String) = AndroidXKey(floatPreferencesKey(name)) 13 | override fun long(name: String) = AndroidXKey(longPreferencesKey(name)) 14 | override fun stringSet(name: String) = AndroidXKey(stringSetPreferencesKey(name)) 15 | } 16 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/androidx/AndroidXMutablePreferences.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx 2 | 3 | import androidx.datastore.preferences.core.MutablePreferences as ActualMutablePreferences 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Key 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.MutablePreferences 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Preferences 7 | 8 | /** 9 | * An implementation of [MutablePreferences] which uses some [ActualMutablePreferences]. 10 | * 11 | * @property actual the underlying [ActualMutablePreferences]. 12 | */ 13 | class AndroidXMutablePreferences( 14 | private val actual: ActualMutablePreferences, 15 | ) : MutablePreferences, Preferences by AndroidXPreferences(actual) { 16 | 17 | override fun set(key: Key, value: T) = actual.set(key.extractActualKey(), value) 18 | 19 | override fun remove(key: Key) { 20 | actual.remove(key.extractActualKey()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/datastore/androidx/AndroidXPreferencesDataStore.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.datastore.androidx 2 | 3 | import androidx.datastore.core.DataStore as ActualDataStore 4 | import androidx.datastore.preferences.core.Preferences as ActualPreferences 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.DataStore 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.datastore.Preferences 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.map 9 | 10 | /** 11 | * An implementation of a [DataStore] which uses an [ActualDataStore]. 12 | * 13 | * @property actual the underlying [ActualDataStore]. 14 | */ 15 | class AndroidXPreferencesDataStore( 16 | private val actual: ActualDataStore, 17 | ) : DataStore { 18 | 19 | override val data: Flow 20 | get() = actual.data.map { AndroidXPreferences(it) } 21 | 22 | override suspend fun updateData( 23 | transform: suspend (Preferences) -> Preferences, 24 | ): Preferences = 25 | AndroidXPreferences( 26 | actual.updateData { transform(AndroidXPreferences(it)).extractActualPreferences() }, 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/CollectionReference.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | /** 6 | * An interface representing a collection in the hierarchy. It may contain some documents, and can 7 | * be observed as a [Flow] of changing values. 8 | */ 9 | interface CollectionReference : Query { 10 | 11 | /** 12 | * Creates a document with a unique identifier. 13 | * 14 | * @return a [DocumentReference] to the document. 15 | */ 16 | fun document(): DocumentReference 17 | 18 | /** 19 | * Accesses a document. 20 | * 21 | * @param path the id of the document. 22 | * @return a [DocumentReference] to the document. 23 | */ 24 | fun document(path: String): DocumentReference 25 | } 26 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/DocumentSnapshot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * An interface representing a view of the results of fetching a [DocumentReference]. Snapshots are 7 | * an abstract type which can be converted to an object with the actual result. 8 | */ 9 | interface DocumentSnapshot { 10 | 11 | /** 12 | * Maps the [DocumentSnapshot] to an optional [T]. 13 | * 14 | * @param T the type of the object to create with the fetch result. 15 | * @param valueClass the [KClass] of [T]. 16 | * 17 | * @return a [T] with the result of the fetch. 18 | */ 19 | fun toObject(valueClass: KClass): T? 20 | } 21 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/FieldPath.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | /** 4 | * A [FieldPath] represents the path of a field within a document. Each field is uniquely identified 5 | * by a non-empty path. 6 | * 7 | * @property segments the segments of the path within this document. 8 | */ 9 | data class FieldPath(val segments: List) { 10 | 11 | /** 12 | * A convenience constructor which builds a [FieldPath] from a single [String] segment. 13 | * 14 | * @param segment the unique segment of this [FieldPath]. 15 | */ 16 | constructor(segment: String) : this(listOf(segment)) 17 | 18 | /** 19 | * Concatenates the given [segment] to the [FieldPath]. 20 | * 21 | * @return an updated [FieldPath] with the new segment. 22 | */ 23 | operator fun plus(segment: String): FieldPath = FieldPath(segments + segment) 24 | 25 | init { 26 | require(segments.isNotEmpty()) { "A FieldPath may not have 0 segments." } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/FieldValue.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | /** 4 | * An interface defining some sentinel values which may be used within [DocumentEditScope] to 5 | * perform some special operations on fields. 6 | */ 7 | sealed interface FieldValue { 8 | 9 | /** 10 | * A sentinel value which performs array union on a field. 11 | * 12 | * @property values the values which are added to the array. 13 | */ 14 | data class ArrayUnion(val values: List) : FieldValue 15 | 16 | /** 17 | * A sentinel value which performs array removal on a field. 18 | * 19 | * @property values the values which are removed from the array. 20 | */ 21 | data class ArrayRemove(val values: List) : FieldValue 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/QuerySnapshot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * An interface representing a view of the results of a [Query]. Snapshots are an abstract type 7 | * which can then be converted to objects with the actual results of the query. 8 | */ 9 | interface QuerySnapshot { 10 | 11 | /** 12 | * Maps the [QuerySnapshot] to a [List] of [T]. 13 | * 14 | * @param T the type of the objects to create with the query results. 15 | * @param valueClass the [KClass] of [T]. 16 | * 17 | * @return a [List] of [T], with the results of the query. 18 | */ 19 | fun toObjects(valueClass: KClass): List 20 | } 21 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/RecordingDocumentEditScope.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | /** 4 | * An implementation of [DocumentEditScope] which records the mutations that were performed on the 5 | * scope. Mutations which affect the same field will be replaced with their latest value. 6 | */ 7 | class RecordingDocumentEditScope : DocumentEditScope { 8 | 9 | /** The mutations which have been applied within the [DocumentEditScope]. */ 10 | val mutations: Map 11 | get() = recording 12 | 13 | /** The map which records the mutations. */ 14 | private val recording = mutableMapOf() 15 | 16 | override fun set(path: FieldPath, value: Any?) { 17 | // Flatten the nested Map. 18 | val items = mutableListOf(path to value) 19 | while (items.isNotEmpty()) { 20 | val (p, v) = items.removeLast() 21 | if (v is Map<*, *>) { 22 | for ((km, vm) in v) items.add(p + km.toString() to vm) 23 | } else recording[p] = v 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/Store.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store 2 | 3 | import kotlin.jvm.Throws 4 | 5 | /** 6 | * An interface representing a place where collections are stored. This is the top-level of the 7 | * database hierarchy. 8 | */ 9 | interface Store { 10 | 11 | /** Accesses a collection with the given [String] identifier. */ 12 | fun collection(path: String): CollectionReference 13 | 14 | /** 15 | * Runs the give [Transaction] on the [Store], executing all the operations atomically. A 16 | * [Transaction] must perform all its reads before performing all of its writes. 17 | * 18 | * This function may throw an [Exception] if a transaction can't be executed. 19 | * 20 | * @throws Exception if a transaction fails. 21 | * @param R the type of the return value. 22 | * @param block the body of the [Transaction] to be executed. 23 | * @return the return value of the transaction. 24 | */ 25 | @Throws(Exception::class) 26 | suspend fun transaction(block: Transaction.() -> R): R 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/firestore/FirestoreCollectionReference.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.CollectionReference 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentReference 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Query 6 | import com.google.firebase.firestore.CollectionReference as ActualCollectionReference 7 | 8 | /** 9 | * An implementation of [CollectionReference] which uses a Firestore collection reference 10 | * under-the-hood. 11 | * 12 | * @property reference the [ActualCollectionReference]. 13 | */ 14 | class FirestoreCollectionReference( 15 | private val reference: ActualCollectionReference, 16 | ) : CollectionReference, Query by FirestoreQuery(reference) { 17 | 18 | override fun document(): DocumentReference = FirestoreDocumentReference(reference.document()) 19 | 20 | override fun document(path: String): DocumentReference = 21 | FirestoreDocumentReference(reference.document(path)) 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/firestore/FirestoreDocumentSnapshot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentSnapshot 4 | import com.google.firebase.firestore.DocumentSnapshot as ActualDocumentSnapshot 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * An implementation of [DocumentSnapshot] which uses an [ActualDocumentSnapshot] under-the-hood. 9 | * 10 | * @property actual the actual [ActualDocumentSnapshot]. 11 | */ 12 | class FirestoreDocumentSnapshot( 13 | private val actual: ActualDocumentSnapshot, 14 | ) : DocumentSnapshot { 15 | 16 | override fun toObject(valueClass: KClass): T? = actual.toObject(valueClass.java) 17 | } 18 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/firestore/FirestoreFieldPath.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.FieldPath 4 | import com.google.firebase.firestore.FieldPath as ActualFieldPath 5 | import com.google.firebase.firestore.FieldPath.of 6 | 7 | /** 8 | * An object which contains some utility methods to manipulate [FieldPath] and [ActualFieldPath]. 9 | */ 10 | object FirestoreFieldPath { 11 | 12 | /** Transforms the given [FieldPath] to an [ActualFieldPath], using the individual segments. */ 13 | fun FieldPath.toFirestoreFieldPath(): ActualFieldPath = of(*segments.toTypedArray()) 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/firestore/FirestoreFieldValue.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.FieldValue 4 | import com.google.firebase.firestore.FieldValue as ActualFieldValue 5 | 6 | /** 7 | * An object which contains some utility methods to manipulate [FieldValue] and [ActualFieldValue]. 8 | */ 9 | object FirestoreFieldValue { 10 | 11 | /** 12 | * Maps an optional [Any] which may be a [FieldValue] to a native Firestore [ActualFieldValue] if 13 | * it corresponds. 14 | * 15 | * @receiver an optional [Any] for which we want to map field values. 16 | * @return the mapped [Any] if it was a [FieldValue]. 17 | */ 18 | fun Any?.mapFirestoreFieldValue(): Any? = 19 | when (this) { 20 | is FieldValue.ArrayUnion -> ActualFieldValue.arrayUnion(*values.toTypedArray()) 21 | is FieldValue.ArrayRemove -> ActualFieldValue.arrayRemove(*values.toTypedArray()) 22 | else -> this 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/firestore/FirestoreQuerySnapshot.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.QuerySnapshot 4 | import com.google.firebase.firestore.QuerySnapshot as ActualQuerySnapshot 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * An implementation of [QuerySnapshot] which uses an [ActualQuerySnapshot] under-the-hood. 9 | * 10 | * @property actual the actual [ActualQuerySnapshot]. 11 | */ 12 | class FirestoreQuerySnapshot( 13 | private val actual: ActualQuerySnapshot, 14 | ) : QuerySnapshot { 15 | 16 | override fun toObjects(valueClass: KClass): List = 17 | actual.toObjects(valueClass.java) 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/persistence/store/firestore/FirestoreStore.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.persistence.store.firestore 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.CollectionReference 4 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.DocumentReference 5 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Store 6 | import ch.epfl.sdp.mobile.infrastructure.persistence.store.Transaction 7 | import com.google.firebase.firestore.FirebaseFirestore 8 | import kotlinx.coroutines.tasks.await 9 | 10 | /** 11 | * An implementation of [Store] which uses Firestore under-the-hood. 12 | * 13 | * @property firestore the actual [FirebaseFirestore] instance. 14 | */ 15 | class FirestoreStore( 16 | private val firestore: FirebaseFirestore, 17 | ) : Store { 18 | 19 | override fun collection(path: String): CollectionReference = 20 | FirestoreCollectionReference(firestore.collection(path)) 21 | 22 | override suspend fun transaction( 23 | block: Transaction.() -> R, 24 | ): R = firestore.runTransaction { block(FirestoreTransaction(it)) }.await() 25 | } 26 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/sound/SoundPlayer.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.sound 2 | 3 | /** An interface providing the ability to play a sound. */ 4 | interface SoundPlayer { 5 | 6 | /** Plays the provided sound. */ 7 | fun playChessSound() 8 | } 9 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/sound/android/AndroidSoundPlayer.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.sound.android 2 | 3 | import android.content.Context 4 | import android.media.MediaPlayer 5 | import ch.epfl.sdp.mobile.R 6 | import ch.epfl.sdp.mobile.infrastructure.sound.SoundPlayer 7 | 8 | /** 9 | * A class providing the ability to play a chess sound. 10 | * 11 | * @param context the current [Context] of the application. 12 | * @param mediaPlayer the [MediaPlayer] used to play sounds. 13 | */ 14 | class AndroidSoundPlayer( 15 | context: Context, 16 | private val mediaPlayer: MediaPlayer = MediaPlayer.create(context, R.raw.chess_piece_sound) 17 | ) : SoundPlayer { 18 | 19 | override fun playChessSound() { 20 | mediaPlayer.start() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/speech/SpeechRecognizer.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.speech 2 | 3 | /** An interface providing access to the native [SpeechRecognizer] of the platform. */ 4 | interface SpeechRecognizer { 5 | 6 | /** A listener which will be called when some new speech recognition results are available. */ 7 | interface Listener { 8 | 9 | /** A callback method, called when there's an error during the recognition. */ 10 | fun onError() 11 | 12 | /** 13 | * A callback method, called with the list of results. 14 | * 15 | * @param results the [List] of speech recognition results, ordered by decreasing score. 16 | */ 17 | fun onResults(results: List) 18 | } 19 | 20 | /** 21 | * Sets the [Listener] for this [SpeechRecognizer]. 22 | * 23 | * @param listener the [Listener] which is set. 24 | */ 25 | fun setListener(listener: Listener) 26 | 27 | /** Starts listening with this [SpeechRecognizer]. */ 28 | fun startListening() 29 | 30 | /** Stops listening with this [SpeechRecognizer]. */ 31 | fun stopListening() 32 | 33 | /** Destroys the recognizer. */ 34 | fun destroy() 35 | } 36 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/speech/SpeechRecognizerFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.speech 2 | 3 | /** A factory which can create some [SpeechRecognizer] instances. */ 4 | interface SpeechRecognizerFactory { 5 | 6 | /** Returns a new [SpeechRecognizer], which may be used to perform some voice recognition. */ 7 | fun createSpeechRecognizer(): SpeechRecognizer 8 | } 9 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/speech/android/RecognitionListenerAdapter.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.speech.android 2 | 3 | import android.os.Bundle 4 | import android.speech.RecognitionListener 5 | 6 | /** 7 | * An abstract adapter around the Android [RecognitionListener] interface which provides some 8 | * default implementations for all the methods. 9 | */ 10 | abstract class RecognitionListenerAdapter : RecognitionListener { 11 | override fun onReadyForSpeech(params: Bundle?) = Unit 12 | override fun onBeginningOfSpeech() = Unit 13 | override fun onRmsChanged(rmsdB: Float) = Unit 14 | override fun onBufferReceived(buffer: ByteArray?) = Unit 15 | override fun onEndOfSpeech() = Unit 16 | override fun onError(error: Int) = Unit 17 | override fun onResults(results: Bundle?) = Unit 18 | override fun onPartialResults(partialResults: Bundle?) = Unit 19 | override fun onEvent(eventType: Int, params: Bundle?) = Unit 20 | } 21 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/time/TimeProvider.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.time 2 | 3 | /** Interface providing the current time. */ 4 | interface TimeProvider { 5 | 6 | /** 7 | * Function that returns the current time. 8 | * 9 | * @return the current time in [Long] milliseconds 10 | */ 11 | fun now(): Long 12 | } 13 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/time/system/SystemTimeProvider.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.time.system 2 | 3 | import ch.epfl.sdp.mobile.infrastructure.time.TimeProvider 4 | 5 | /** An object providing the actual current time according to System.currentTimeMillis(). */ 6 | object SystemTimeProvider : TimeProvider { 7 | override fun now(): Long { 8 | return System.currentTimeMillis() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/tts/TextToSpeech.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.tts 2 | 3 | /** Interface representing the Text To Speech of the Pawnies infrastructure. */ 4 | interface TextToSpeech { 5 | 6 | /** 7 | * Synthesizes the given text. 8 | * @param text [String] to synthesize. 9 | */ 10 | fun speak(text: String) 11 | 12 | /** Stops the synthesizer and all the queued requests for speech. */ 13 | fun stop() 14 | 15 | /** Shuts down the text to speech service. */ 16 | fun shutdown() 17 | } 18 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/infrastructure/tts/TextToSpeechFactory.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.infrastructure.tts 2 | 3 | /** Interface of the text to speech factory of the Pawnies infrastructure. */ 4 | interface TextToSpeechFactory { 5 | 6 | /** Creates an implementation of the Text to speech. */ 7 | suspend fun create(): TextToSpeech 8 | } 9 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/ProfileColor.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state 2 | 3 | import android.graphics.Color as NativeColor 4 | import androidx.compose.ui.graphics.Color 5 | import ch.epfl.sdp.mobile.application.Profile.Color as ProfileColor 6 | 7 | /** The [Regex] which can figure out if a string is a valid ARGB hex color. */ 8 | private val ARGBPattern = Regex("#[0-9a-fA-F]{8}") 9 | 10 | /** 11 | * Transforms this [Color] into a compose-friendly [Color]. 12 | * 13 | * @receiver the [Color] which is transformed. 14 | * @return the transformed [Color]. 15 | */ 16 | fun ProfileColor.toColor(): Color { 17 | val color = takeIf { ARGBPattern.matches(hex) } ?: ProfileColor.Default 18 | return Color(NativeColor.parseColor(color.hex)) 19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/StatefullArScreen.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state 2 | 3 | import androidx.compose.runtime.* 4 | import androidx.compose.ui.Modifier 5 | import ch.epfl.sdp.mobile.state.game.ActualChessBoardState 6 | import ch.epfl.sdp.mobile.ui.game.ar.ArChessBoardScreen 7 | 8 | /** 9 | * A composable that make [ArChessBoardScreen] stateful. 10 | * 11 | * @param id the identifier for the match. 12 | * @param modifier the [Modifier] for the composable. 13 | */ 14 | @Composable 15 | fun StatefulArScreen( 16 | id: String, 17 | modifier: Modifier = Modifier, 18 | ) { 19 | 20 | val chessFacade = LocalChessFacade.current 21 | val scope = rememberCoroutineScope() 22 | val match = remember(chessFacade, id) { chessFacade.match(id) } 23 | 24 | val state = remember(match, scope) { ActualChessBoardState(match, scope) } 25 | 26 | ArChessBoardScreen(state, modifier) 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/game/ActualChessBoardState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.game 2 | 3 | import ch.epfl.sdp.mobile.application.chess.Match 4 | import ch.epfl.sdp.mobile.state.game.core.GameDelegate 5 | import ch.epfl.sdp.mobile.state.game.core.MatchGameDelegate 6 | import ch.epfl.sdp.mobile.state.game.delegating.DelegatingChessBoardState 7 | import ch.epfl.sdp.mobile.state.game.delegating.DelegatingChessBoardState.* 8 | import ch.epfl.sdp.mobile.ui.game.ChessBoardState 9 | import kotlinx.coroutines.CoroutineScope 10 | 11 | /** 12 | * Builds an [ActualChessBoardState]. 13 | * 14 | * @param match the [Match] for which the state is built. 15 | * @param scope the [CoroutineScope] with which the match will be loaded. 16 | */ 17 | fun ActualChessBoardState( 18 | match: Match, 19 | scope: CoroutineScope, 20 | ): ActualChessBoardState { 21 | val delegate = MatchGameDelegate(match, scope) 22 | return ActualChessBoardState(delegate) 23 | } 24 | 25 | /** 26 | * An implementation of [ChessBoardState] which delegates its state to a [GameDelegate]. 27 | * 28 | * @param delegate the underlying [GameDelegate]. 29 | */ 30 | class ActualChessBoardState( 31 | delegate: GameDelegate, 32 | ) : ChessBoardState by DelegatingChessBoardState(delegate) 33 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/game/core/GameDelegate.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.game.core 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Game 4 | 5 | /** An interface which represents a delegate which has read access to an underlying [Game]. */ 6 | interface GameDelegate { 7 | 8 | /** The available [Game]. */ 9 | val game: Game 10 | } 11 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/game/core/MatchGameDelegate.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.game.core 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import ch.epfl.sdp.mobile.application.chess.Match 7 | import ch.epfl.sdp.mobile.application.chess.engine.Action 8 | import ch.epfl.sdp.mobile.application.chess.engine.Game 9 | import ch.epfl.sdp.mobile.application.chess.engine.NextStep 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.launch 12 | 13 | /** 14 | * An implementation of [MutableGameDelegate] which writes its updates to a [Match]. 15 | * 16 | * @property match the underlying [Match]. 17 | * @property scope the [CoroutineScope] used to read and write the match updates. 18 | */ 19 | class MatchGameDelegate( 20 | private val match: Match, 21 | private val scope: CoroutineScope, 22 | ) : MutableGameDelegate { 23 | 24 | /** The underlying snapshot-aware [Game]. */ 25 | private var backing by mutableStateOf(Game.create()) 26 | 27 | override var game: Game 28 | get() = backing 29 | set(value) { 30 | backing = value // Local updates 31 | scope.launch { match.update(value) } 32 | } 33 | 34 | init { 35 | scope.launch { match.game.collect { backing = it } } 36 | } 37 | 38 | override fun tryPerformAction(action: Action): Boolean { 39 | val step = game.nextStep as? NextStep.MovePiece ?: return false 40 | if (action !in game.actions(action.from)) return false 41 | game = step.move(action) 42 | return true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/game/core/MutableGameDelegate.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.game.core 2 | 3 | import ch.epfl.sdp.mobile.application.chess.engine.Action 4 | import ch.epfl.sdp.mobile.application.chess.engine.Game 5 | 6 | /** A refinement of [GameDelegate] which has write access to the underlying [Game]. */ 7 | interface MutableGameDelegate : GameDelegate { 8 | 9 | /** 10 | * Tries to perform the given [Action] of the [Game], updating it on success. 11 | * 12 | * @param action the [Action] which is attempted. 13 | * @return true if the [Action] was performed on the [Game]. 14 | */ 15 | fun tryPerformAction(action: Action): Boolean 16 | } 17 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/game/delegating/DelegatingMovesInfoState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.game.delegating 2 | 3 | import ch.epfl.sdp.mobile.application.chess.notation.AlgebraicNotation.toAlgebraicNotation 4 | import ch.epfl.sdp.mobile.state.game.core.GameDelegate 5 | import ch.epfl.sdp.mobile.ui.game.MovesInfoState 6 | import ch.epfl.sdp.mobile.ui.game.MovesInfoState.Move 7 | 8 | /** 9 | * An implementation of [MovesInfoState] which uses a [GameDelegate] to extract the moves 10 | * information. 11 | * 12 | * @param delegate the underlying [GameDelegate]. 13 | */ 14 | class DelegatingMovesInfoState(private val delegate: GameDelegate) : MovesInfoState { 15 | 16 | override val moves: List 17 | get() = delegate.game.toAlgebraicNotation().map(::Move) 18 | } 19 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/game/delegating/DelegatingPuzzleInfoState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.game.delegating 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import ch.epfl.sdp.mobile.application.chess.Puzzle 7 | import ch.epfl.sdp.mobile.state.toPuzzleInfoAdapter 8 | import ch.epfl.sdp.mobile.ui.puzzles.PuzzleInfoState 9 | 10 | /** 11 | * An implementation of [PuzzleInfoState] that displays a [Puzzle]'s state informations. 12 | * 13 | * @param puzzle the [Puzzle] in question 14 | */ 15 | class DelegatingPuzzleInfoState( 16 | puzzle: Puzzle, 17 | ) : PuzzleInfoState { 18 | override val puzzleInfo = puzzle.toPuzzleInfoAdapter() 19 | override var puzzleState by mutableStateOf(PuzzleInfoState.PuzzleState.Solving) 20 | override var currentMoveNumber = 1 21 | override var expectedMoves = puzzle.puzzleMoves.size 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/state/tournaments/TournamentDetailsActions.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.state.tournaments 2 | 3 | /** 4 | * A data class which represents some common actions that might be performed on the tournament 5 | * details screen. 6 | * 7 | * @property onBackClick a callback which is called when the user wants to go back. 8 | * @property onMatchClick a callback which is called when the user wants to show a match. 9 | */ 10 | data class TournamentDetailsActions( 11 | val onBackClick: () -> Unit, 12 | val onMatchClick: (String) -> Unit, 13 | ) 14 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/Colors.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui 2 | 3 | import androidx.compose.material.lightColors 4 | import androidx.compose.ui.graphics.Color 5 | import io.github.sceneview.utils.colorOf 6 | 7 | /** The palette of colors used in the Pawnies application. */ 8 | val PawniesLightColors = 9 | lightColors( 10 | primary = PawniesColors.Green800, 11 | onPrimary = PawniesColors.Green100, 12 | primaryVariant = PawniesColors.Green500, 13 | secondary = PawniesColors.Orange500, 14 | surface = PawniesColors.Beige050, 15 | onSurface = PawniesColors.Green800, 16 | background = PawniesColors.Beige050, 17 | onBackground = PawniesColors.Green800, 18 | ) 19 | 20 | object PawniesColors { 21 | val Beige050 = Color(0xFFFFFBE6) 22 | val Green100 = Color(0xFFB9E4C9) 23 | val Green200 = Color(0xFFA9DBBB) 24 | val Green500 = Color(0xFF379665) 25 | val Green800 = Color(0xFF356859) 26 | val Orange500 = Color(0xFFFD5523) 27 | val Orange250 = Color(0xFFFD906F) 28 | val Orange200 = Color(0xFFF8A68D) 29 | } 30 | 31 | object PawniesArColors { 32 | // ArModel Color 33 | val White = colorOf(1f, 0.99f, 0.94f) 34 | val Black = colorOf(53 / 255f, 56 / 255f, 57 / 255f) 35 | } 36 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/DrawScope.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui 2 | 3 | import androidx.compose.ui.geometry.Offset 4 | import androidx.compose.ui.graphics.Color 5 | import androidx.compose.ui.graphics.Shadow 6 | import androidx.compose.ui.graphics.drawscope.DrawScope 7 | import androidx.compose.ui.graphics.drawscope.withTransform 8 | import androidx.compose.ui.text.Paragraph 9 | import androidx.compose.ui.text.style.TextDecoration 10 | 11 | /** 12 | * Draws a paragraph on the current [DrawScope]. 13 | * 14 | * @param paragraph the [Paragraph] that will be drawn. 15 | * @param color the [Color] of the paragraph. 16 | * @param topLeft the top left corner at which the paragraph is drawn. 17 | * @param shadow the [Shadow] to apply to the paragraph. 18 | * @param textDecoration the [TextDecoration] to apply to the paragraph. 19 | */ 20 | fun DrawScope.drawParagraph( 21 | paragraph: Paragraph, 22 | color: Color = Color.Unspecified, 23 | topLeft: Offset = Offset.Zero, 24 | shadow: Shadow? = null, 25 | textDecoration: TextDecoration? = null, 26 | ) { 27 | withTransform(transformBlock = { translate(topLeft.x, topLeft.y) }) { 28 | paragraph.paint( 29 | canvas = drawContext.canvas, 30 | color = color, 31 | shadow = shadow, 32 | textDecoration = textDecoration, 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/Models.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui 2 | 3 | /** Object that contains all the 3D models' path. */ 4 | object ChessModels 5 | 6 | /** The path of the 3d model for a king. */ 7 | val ChessModels.King 8 | get() = "models/king.glb" 9 | 10 | /** The path of the 3d model for a bishop. */ 11 | val ChessModels.Bishop 12 | get() = "models/bishop.glb" 13 | 14 | /** The path of the 3d model for a pawn. */ 15 | val ChessModels.Pawn 16 | get() = "models/pawn.glb" 17 | 18 | /** The path of the 3d model for a knight. */ 19 | val ChessModels.Knight 20 | get() = "models/knight.glb" 21 | 22 | /** The path of the 3d model for a queen. */ 23 | val ChessModels.Queen 24 | get() = "models/queen.glb" 25 | 26 | /** The path of the 3d model for a rook. */ 27 | val ChessModels.Rook 28 | get() = "models/rook.glb" 29 | 30 | /** The path of the 3d model for a board. */ 31 | val ChessModels.Board 32 | get() = "models/board.glb" 33 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/PaddingValues.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.ui.unit.Dp 5 | import androidx.compose.ui.unit.LayoutDirection 6 | 7 | /** 8 | * Adds two [PaddingValues] together, considering the layout direction. 9 | * 10 | * @receiver some [PaddingValues] to be added. 11 | * @param other the second [PaddingValues] which are added. 12 | * @return the sum of the [PaddingValues]. 13 | */ 14 | operator fun PaddingValues.plus(other: PaddingValues): PaddingValues = 15 | object : PaddingValues { 16 | 17 | override fun calculateBottomPadding() = 18 | this@plus.calculateBottomPadding() + other.calculateBottomPadding() 19 | 20 | override fun calculateLeftPadding(layoutDirection: LayoutDirection) = 21 | this@plus.calculateLeftPadding(layoutDirection) + 22 | other.calculateLeftPadding(layoutDirection) 23 | 24 | override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = 25 | this@plus.calculateRightPadding(layoutDirection) + 26 | other.calculateRightPadding(layoutDirection) 27 | 28 | override fun calculateTopPadding(): Dp = 29 | this@plus.calculateTopPadding() + other.calculateTopPadding() 30 | } 31 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/Shapes.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | /** The [Shapes] that will be used in the Pawnies application. */ 8 | val PawniesShapes = 9 | Shapes( 10 | small = RoundedCornerShape(0.dp), 11 | medium = RoundedCornerShape(0.dp), 12 | large = RoundedCornerShape(0.dp), 13 | ) 14 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | 6 | /** 7 | * Provides the theming elements of the Pawnies design system, and applies them to the 8 | * [MaterialTheme] components used within [content]. 9 | * 10 | * @param content the body of the composable, in which the theme will be applied. 11 | */ 12 | @Composable 13 | fun PawniesTheme( 14 | content: @Composable () -> Unit, 15 | ) { 16 | MaterialTheme( 17 | colors = PawniesLightColors, 18 | typography = PawniesTypography, 19 | shapes = PawniesShapes, 20 | content = content, 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/authentication/AuthenticationScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.authentication 2 | 3 | import androidx.compose.runtime.Stable 4 | import ch.epfl.sdp.mobile.ui.authentication.AuthenticationScreenState.Mode 5 | 6 | /** 7 | * A state which indicates the contents of an [AuthenticationScreen] composable. It will keep track 8 | * of the values of the different text fields, as well as the [Mode] in which the authentication 9 | * screen currently is. 10 | */ 11 | @Stable 12 | interface AuthenticationScreenState { 13 | 14 | /** Indicates whether the user is currently trying to authenticate or to register. */ 15 | enum class Mode { 16 | 17 | /** The user is trying to sign in. */ 18 | LogIn, 19 | 20 | /** The user is trying to sign up. */ 21 | Register, 22 | } 23 | 24 | /** The current [Mode]. */ 25 | var mode: Mode 26 | 27 | /** True if the authentication button should display a loading indicator. */ 28 | val loading: Boolean 29 | 30 | /** The value of the text in the email field. */ 31 | var email: String 32 | 33 | /** The value of the text in the name field. */ 34 | var name: String 35 | 36 | /** The value of the text in the password field. */ 37 | var password: String 38 | 39 | /** The value to display in the error field. */ 40 | val error: String? 41 | 42 | /** A callback invoked when the user clicks on the authentication button. */ 43 | fun onAuthenticate() 44 | } 45 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/game/GameScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.game 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | /** 6 | * A state which indicates the content of a [GameScreen] composable. It will keep track of the 7 | * values of moves history. 8 | * 9 | * @param Piece the type of the pieces of the underlying [ChessBoardState]. 10 | */ 11 | @Stable 12 | interface GameScreenState : 13 | MovableChessBoardState, 14 | MovesInfoState, 15 | PlayersInfoState, 16 | SpeechRecognizerState, 17 | TextToSpeechState { 18 | 19 | /** A callback which will be invoked when the user clicks on the AR button. */ 20 | fun onArClick() 21 | 22 | /** A callback which will be invoked if the user wants to go back in the hierarchy. */ 23 | fun onBackClick() 24 | } 25 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/game/MovesInfoState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.game 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | /** An interface that represents current game moves in the user interface. */ 6 | @Stable 7 | interface MovesInfoState { 8 | 9 | /** 10 | * A class representing a [Move] that's been performed by one of the players. 11 | * 12 | * @param text the [String] representation of the [Move]. 13 | */ 14 | data class Move(val text: String) 15 | 16 | /** 17 | * A [List] of all the moves which have been performed by the user. Moves are ordered and should 18 | * be displayed as such. 19 | */ 20 | val moves: List 21 | } 22 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/game/PlayersInfoState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.game 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | /** 6 | * An interface that represents the user interface information about the white and black players of 7 | * a game. 8 | */ 9 | @Stable 10 | interface PlayersInfoState { 11 | 12 | /** An enumeration representing the different messages that may be displayed with a [Player]. */ 13 | enum class Message { 14 | /** The player has no message. */ 15 | None, 16 | /** It's this player's turn. */ 17 | YourTurn, 18 | /** The player is in check. */ 19 | InCheck, 20 | /** The player has lost by checkmate. */ 21 | Checkmate, 22 | /** There's a stalemate. */ 23 | Stalemate, 24 | } 25 | 26 | /** 27 | * A class representing a [Player] in the game. There are two players in a game, one for each 28 | * color. 29 | * 30 | * @param name the name of the player, or null if the name is still loading. 31 | * @param message the message next to the player. 32 | */ 33 | data class Player(val name: String?, val message: Message) 34 | 35 | /** The information about the white player. */ 36 | val white: Player 37 | 38 | /** The information about the black player. */ 39 | val black: Player 40 | } 41 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/game/PromotionState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.game 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | /** An interface representing the possible states of the promotion actions. */ 6 | @Stable 7 | interface PromotionState { 8 | 9 | /** 10 | * The non-empty list of [ChessBoardState.Rank] that should be displayed as available for 11 | * promotion. If no promotion should be performed, this will be empty. 12 | */ 13 | val choices: List 14 | 15 | /** The currently selected [ChessBoardState.Rank] in the promotion dialog. */ 16 | val selection: ChessBoardState.Rank? 17 | 18 | /** True iff the confirm action of promotion is enabled. */ 19 | val confirmEnabled: Boolean 20 | 21 | /** 22 | * A callback which should be called when the user presses a specific promotion rank. 23 | * 24 | * @param rank the rank that was pressed. 25 | */ 26 | fun onSelect(rank: ChessBoardState.Rank) 27 | 28 | /** 29 | * A callback which should be called when the user presses the confirm action during promotion. 30 | */ 31 | fun onConfirm() 32 | } 33 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/game/SpeechRecognizerState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.game 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | /** An interface which represents the state of the speech recognition user interface. */ 6 | @Stable 7 | interface SpeechRecognizerState { 8 | 9 | /** 10 | * Store the current error value, if there are not error involved the value is set to 11 | * [SpeechRecognizerError.None]. 12 | */ 13 | var currentError: SpeechRecognizerError 14 | 15 | /** Enum class that defined different error related to the speech recognizer. */ 16 | enum class SpeechRecognizerError { 17 | 18 | // FIXME General error, temporary 19 | InternalError, 20 | 21 | /** The asked command cannot be performed. */ 22 | IllegalAction, 23 | 24 | /** The command cannot be parsed. */ 25 | UnknownCommand, 26 | 27 | /** None error. */ 28 | None, 29 | } 30 | 31 | /** A [Boolean] which indicates if the device is currently listening to voice inputs. */ 32 | val listening: Boolean 33 | 34 | /** A callback which will be invoked when the user clicks on the listening button. */ 35 | fun onListenClick() 36 | } 37 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/game/TextToSpeechState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.game 2 | 3 | /** Interface of the text to speech UI. */ 4 | interface TextToSpeechState { 5 | 6 | /** Indicates if the text to speech is enabled. */ 7 | val textToSpeechEnabled: Boolean 8 | 9 | /** Callback for the text to speech toggle action. */ 10 | fun onTextToSpeechToggle() 11 | } 12 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/i18n/Language.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.i18n 2 | 3 | import androidx.compose.ui.Modifier 4 | import java.util.* 5 | 6 | /** Language choices for the application. */ 7 | enum class Language { 8 | English, 9 | German, 10 | French, 11 | SwissGerman 12 | } 13 | 14 | /** 15 | * Converts Language to a readable string. 16 | * 17 | * @return modifier the [Modifier] for this composable. 18 | */ 19 | fun Language.toReadableString(): String = Languages[this] ?: "English" 20 | 21 | /** 22 | * Converts an ISO String to a Language Enum. 23 | * @param value which will be transformed into Language. 24 | * 25 | * @return an [Language] instance. 26 | */ 27 | fun fromISOStringToLanguage(value: String?): Language = 28 | when (value) { 29 | "fr" -> Language.French 30 | "de" -> Language.German 31 | "de-ch" -> Language.SwissGerman 32 | else -> Language.English 33 | } 34 | 35 | /** 36 | * Converts an Enum to an ISO string. 37 | * @param value is [Language] which will be converted into a string. 38 | * 39 | * @return a [String] format of ISO. 40 | */ 41 | fun fromLanguageToISOString(value: Language): String = 42 | when (value) { 43 | Language.French -> "fr" 44 | Language.German -> "de" 45 | Language.SwissGerman -> "de-ch" 46 | else -> "en" 47 | } 48 | 49 | /** A map of Enum values and String representations. */ 50 | val Languages = 51 | mapOf( 52 | Pair(Language.English, "English"), 53 | Pair(Language.French, "Français"), 54 | Pair(Language.German, "Deutsch"), 55 | Pair(Language.SwissGerman, "Schwiizerdütsch"), 56 | ) 57 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/play/PlayScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.play 2 | 3 | import ch.epfl.sdp.mobile.ui.social.ChessMatch 4 | 5 | /** 6 | * Interface that represents state of the PlayScreen. 7 | * 8 | * @param M the type of the matches. 9 | */ 10 | interface PlayScreenState { 11 | 12 | /** Callable upon actioning button for local games. */ 13 | fun onLocalGameClick() 14 | 15 | /** Callable upon actioning button for online games. */ 16 | fun onOnlineGameClick() 17 | 18 | /** 19 | * Action to execute when clicked on match item in list. 20 | * 21 | * @param match the clicked match. 22 | */ 23 | fun onMatchClick(match: M) 24 | 25 | /** List of matches of current user. */ 26 | val matches: List 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/prepare_game/PrepareGameScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.prepare_game 2 | 3 | import ch.epfl.sdp.mobile.ui.social.Person 4 | 5 | /** State interface of the [PrepareGameScreen]. */ 6 | interface PrepareGameScreenState

{ 7 | 8 | /** Color choices for a chess game. */ 9 | enum class ColorChoice { 10 | White, 11 | Black 12 | } 13 | 14 | /** The chosen color for the authenticated user. */ 15 | var colorChoice: ColorChoice 16 | 17 | /** The list of opponents to display in the [PrepareGameScreen]. */ 18 | val opponents: List

19 | 20 | /** A potentially selected opponent to display differently in the opponent list. */ 21 | val selectedOpponent: P? 22 | 23 | /** Whether or not the confirm button should be clickable. */ 24 | val playEnabled: Boolean 25 | 26 | /** 27 | * The action to take when clicking on an opponent in the opponent's list. 28 | * @param opponent The [Person] which was clicked. 29 | */ 30 | fun onOpponentClick(opponent: P) 31 | 32 | /** 33 | * A callback for the action to take when clicking on the "play" button in the dialog, when a 34 | * specific opponent is selected. 35 | */ 36 | fun onPlayClick() 37 | 38 | /** A callback for the action to take when clicking on the "cancel" button in the dialog. */ 39 | fun onCancelClick() 40 | } 41 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/profile/ProfileScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.profile 2 | 3 | import androidx.compose.runtime.Stable 4 | import ch.epfl.sdp.mobile.ui.puzzles.PuzzleInfo 5 | import ch.epfl.sdp.mobile.ui.social.ChessMatch 6 | import ch.epfl.sdp.mobile.ui.social.Person 7 | 8 | /** 9 | * The view-model of the profile of the screen. 10 | * 11 | * @param C the type of the [ChessMatch]. 12 | * @param P the type of the [PuzzleInfo]. 13 | */ 14 | @Stable 15 | interface ProfileScreenState : Person { 16 | 17 | /** Number of past games. */ 18 | val pastGamesCount: Int 19 | 20 | /** List of chess matches. */ 21 | val matches: List 22 | 23 | /** Number of solved puzzles. */ 24 | val solvedPuzzlesCount: Int 25 | 26 | /** List of solved puzzles. */ 27 | val puzzles: List

28 | 29 | /** 30 | * Callback function to open a match. 31 | * 32 | * @param match the [ChessMatch] to open. 33 | */ 34 | fun onMatchClick(match: C) 35 | 36 | /** 37 | * Callback function to open a puzzle. 38 | * 39 | * @param puzzle the [PuzzleInfo] to open. 40 | */ 41 | fun onPuzzleClick(puzzle: P) 42 | } 43 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/profile/VisitedProfileScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.profile 2 | 3 | import androidx.compose.runtime.Stable 4 | import ch.epfl.sdp.mobile.ui.puzzles.PuzzleInfo 5 | import ch.epfl.sdp.mobile.ui.social.ChessMatch 6 | 7 | /** 8 | * The view-model of a visited profile. 9 | * 10 | * @param C the type of the [ChessMatch]. 11 | * @param P the type of the [PuzzleInfo]. 12 | */ 13 | @Stable 14 | interface VisitedProfileScreenState : ProfileScreenState { 15 | 16 | /** If current user follows the profile. */ 17 | val follows: Boolean 18 | 19 | /** A callback invoked when the user follows or unfollows another user. */ 20 | fun onFollowClick() 21 | 22 | /** On challenge button clicked. */ 23 | fun onChallengeClick() 24 | 25 | /** Call back function to get back to previous screen. */ 26 | fun onBack() 27 | } 28 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/puzzles/PuzzleGameScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.puzzles 2 | 3 | import androidx.compose.runtime.Stable 4 | import ch.epfl.sdp.mobile.ui.game.* 5 | 6 | /** 7 | * A state which indicates the content of a [PuzzleGameScreen] composable. It will keep track of the 8 | * values of moves history. 9 | */ 10 | @Stable 11 | interface PuzzleGameScreenState : 12 | MovableChessBoardState, 13 | MovesInfoState, 14 | PuzzleInfoState, 15 | SpeechRecognizerState, 16 | TextToSpeechState { 17 | 18 | /** A callback which will be invoked if the user wants to go back in the hierarchy. */ 19 | fun onBackClick() 20 | } 21 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/puzzles/PuzzleInfo.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.puzzles 2 | 3 | import androidx.compose.runtime.Composable 4 | import ch.epfl.sdp.mobile.application.chess.Puzzle 5 | import ch.epfl.sdp.mobile.ui.game.ChessBoardState.Color 6 | 7 | /** Represents the basic info of a [Puzzle] to display it in a list. */ 8 | interface PuzzleInfo { 9 | 10 | /** The [Puzzle]'s uid. */ 11 | val uid: String 12 | 13 | /** The elo/rank (difficulty) of the puzzle. */ 14 | val elo: Int 15 | 16 | /** The [Color] of the player in the [Puzzle]. */ 17 | val playerColor: Color 18 | 19 | /** The icon [Composable] to display next to the [Puzzle] description. */ 20 | val icon: @Composable () -> Unit 21 | } 22 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/puzzles/PuzzleInfoState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.puzzles 2 | 3 | import androidx.compose.runtime.Stable 4 | import ch.epfl.sdp.mobile.application.chess.Puzzle 5 | 6 | /** An interface that represents the user interface information about the puzzle. */ 7 | @Stable 8 | interface PuzzleInfoState { 9 | 10 | /** The [Puzzle] that is loaded. */ 11 | val puzzleInfo: PuzzleInfo 12 | 13 | /** The state of the puzzle. */ 14 | val puzzleState: PuzzleState 15 | 16 | /** The current move number. Includes the bot's moves. */ 17 | val currentMoveNumber: Int // Always start with playing "computer" move 18 | 19 | /** The number of expected moves in the puzzle. Includes the bot's moves. */ 20 | val expectedMoves: Int 21 | 22 | /** Represents the three possible states for a puzzle. */ 23 | enum class PuzzleState { 24 | Solving, 25 | Failed, 26 | Solved, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/puzzles/PuzzleSelectionScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.puzzles 2 | 3 | /** 4 | * Interface that represents state of the [PuzzleSelectionScreen]. 5 | * 6 | * @param P The actual [PuzzleInfo] type. 7 | */ 8 | interface PuzzleSelectionScreenState

{ 9 | 10 | /** 11 | * Action to execute when clicking on puzzle item in list. 12 | * 13 | * @param puzzle The puzzle that is clicked. 14 | */ 15 | fun onPuzzleItemClick(puzzle: P) 16 | 17 | /** List of puzzles of current user. */ 18 | val puzzles: List

19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/setting/EditLanguageDialogState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.setting 2 | 3 | import ch.epfl.sdp.mobile.ui.i18n.Language 4 | 5 | /** State interface of the [EditLanguageDialog]. */ 6 | interface EditLanguageDialogState { 7 | 8 | /** Language which will be changed. */ 9 | var selectedLanguage: Language 10 | 11 | /** Action to execute when clicking on the Save button. */ 12 | fun onSaveClick() 13 | 14 | /** Action to execute when clicking on the Cancel button. */ 15 | fun onCancelClick() 16 | } 17 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/setting/EditProfileImageDialogState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.setting 2 | 3 | import ch.epfl.sdp.mobile.application.Profile.Color 4 | 5 | /** State interface of the [EditProfileImageDialog]. */ 6 | interface EditProfileImageDialogState { 7 | 8 | /** Background Color which will be changed. */ 9 | var backgroundColor: Color 10 | 11 | /** Emoji String which will be changed. */ 12 | var emoji: String 13 | 14 | /** Action to execute when clicking on the Save button. */ 15 | fun onSaveClick() 16 | 17 | /** Action to execute when clicking on the Cancel button. */ 18 | fun onCancelClick() 19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/setting/EditProfileNameDialogState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.setting 2 | 3 | /** State interface of the [EditProfileNameDialog]. */ 4 | interface EditProfileNameDialogState { 5 | 6 | /** UserName which will be changed. */ 7 | var userName: String 8 | 9 | /** Action to execute when clicking on the Save button. */ 10 | fun onSaveClick() 11 | 12 | /** Action to execute when clicking on the Cancel button. */ 13 | fun onCancelClick() 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/setting/Emojis.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.setting 2 | 3 | /** The available emojis to customize a profile. */ 4 | val Emojis = listOf("😎", "🐟", "👽", "🎧", "😂") 5 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/setting/SettingScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.setting 2 | 3 | import androidx.compose.runtime.Stable 4 | import ch.epfl.sdp.mobile.ui.profile.ProfileScreenState 5 | import ch.epfl.sdp.mobile.ui.puzzles.PuzzleInfo 6 | import ch.epfl.sdp.mobile.ui.social.ChessMatch 7 | 8 | /** 9 | * The view-model of the profile of the currently logged-in user. 10 | * 11 | * @param C the type of the [ChessMatch]. 12 | * @param P the type of the [PuzzleInfo]. 13 | */ 14 | @Stable 15 | interface SettingScreenState : ProfileScreenState { 16 | 17 | /** The email address of the logged-in user. */ 18 | val email: String 19 | 20 | /** On edit profile image button clicked. */ 21 | fun onEditProfileImageClick() 22 | 23 | /** On edit profile name button clicked. */ 24 | fun onEditProfileNameClick() 25 | 26 | /** On edit language button clicked. */ 27 | fun onEditLanguageClick() 28 | 29 | /** On logout button clicked. */ 30 | fun onLogout() 31 | } 32 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/social/ChessMatch.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.social 2 | 3 | /** Some information about a chess match. */ 4 | interface ChessMatch { 5 | 6 | /** The adversary in the match. */ 7 | val adversary: String 8 | 9 | /** The current match result. */ 10 | val matchResult: MatchResult 11 | 12 | /** The number of played moves. */ 13 | val numberOfMoves: Int 14 | } 15 | 16 | /** The possible match results. */ 17 | sealed interface MatchResult { 18 | 19 | /** An enumeration which represents the possible reasons for match results. */ 20 | enum class Reason { 21 | CHECKMATE, 22 | FORFEIT 23 | } 24 | } 25 | 26 | /** 27 | * A [MatchResult] which represents a win. 28 | * 29 | * @property reason the [MatchResult.Reason] for the win. 30 | */ 31 | data class Win(val reason: MatchResult.Reason) : MatchResult 32 | 33 | /** 34 | * A [MatchResult] which represents a loss. 35 | * 36 | * @property reason the [MatchResult.Reason] for the loss. 37 | */ 38 | data class Loss(val reason: MatchResult.Reason) : MatchResult 39 | 40 | /** A [MatchResult] which represents a tie. */ 41 | object Tie : MatchResult 42 | 43 | /** A [MatchResult] which represents that it's the current player's turn to play. */ 44 | object YourTurn : MatchResult 45 | 46 | /** A [MatchResult] which represents that it's the other player's turn to play. */ 47 | object OtherTurn : MatchResult 48 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/social/Person.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.social 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | /** An interface representing a list of people you're following. */ 6 | interface Person { 7 | 8 | /** The background color of the profile of this user. */ 9 | val backgroundColor: Color 10 | 11 | /** The name of the user. */ 12 | val name: String 13 | 14 | /** The emoji of the profile picture of the user. */ 15 | val emoji: String 16 | 17 | /** True iff the current user is following this [Person]. */ 18 | val followed: Boolean 19 | } 20 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/tournaments/ContestInfo.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.tournaments 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.runtime.Stable 5 | import kotlin.time.Duration 6 | 7 | /** An interface which represents the information we may have about a contest. */ 8 | @Stable 9 | interface ContestInfo { 10 | 11 | /** The user-readable name of the contest. */ 12 | val name: String 13 | 14 | /** The badge of the contest, if there's any to display. */ 15 | val badge: BadgeType? 16 | 17 | /** The status of the contest. */ 18 | val status: Status 19 | 20 | /** An enumeration which represents the status of a contest. */ 21 | @Immutable 22 | sealed interface Status { 23 | 24 | /** Indicates that the contest has been open [since] a certain duration. */ 25 | data class Ongoing(val since: Duration) : Status 26 | 27 | /** Indicates that the contest is closed (and therefore can't be joined anymore). */ 28 | object Done : Status 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mobile/src/main/java/ch/epfl/sdp/mobile/ui/tournaments/ContestScreenState.kt: -------------------------------------------------------------------------------- 1 | package ch.epfl.sdp.mobile.ui.tournaments 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | /** 6 | * The view-model of the list of tournaments screen. 7 | * 8 | * @param C the type of the [ContestInfo]. 9 | */ 10 | @Stable 11 | interface ContestScreenState { 12 | 13 | /** The list of completed and ongoing tournaments. */ 14 | val contests: List 15 | 16 | /** 17 | * Callback called when a Contest is clicked. 18 | * 19 | * @param contest the contest which was clicked. 20 | */ 21 | fun onContestClick(contest: C) 22 | 23 | /** Callback called when the "New Contest" button is clicked. */ 24 | fun onNewContestClick() 25 | 26 | /** Callback called when the filter action is clicked. */ 27 | fun onFilterClick() 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_bishop_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_bishop_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_king_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_king_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_knight_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_knight_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_pawn_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_pawn_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_queen_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_queen_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_rook_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_chess_rook_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_filter.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_local_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_online_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_friends_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_play_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_play_hollow.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_puzzles_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_puzzles_hollow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_settings_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_tournaments_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_tab_icons_tournaments_hollow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_bold.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_extralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_extralight.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_light.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_medium.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_regular.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_semibold.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/ibm_plex_sans_thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/ibm_plex_sans_thin.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/noto_sans_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/noto_sans_bold.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/font/noto_sans_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/font/noto_sans_regular.ttf -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /mobile/src/main/res/raw/chess_piece_sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epfl-SDP/android/71f6e2a5978087205b35f82e89ed4005902d697e/mobile/src/main/res/raw/chess_piece_sound.mp3 -------------------------------------------------------------------------------- /mobile/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 8 | 9 | 9 | 10 |