Compare commits

..

343 Commits

Author SHA1 Message Date
Jeremy DUCOURTHIAL 139cdc500a fix: remove and update toast test
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL ea68e0fff4 fix: error merge
1 year ago
Jeremy DUCOURTHIAL 50a336a941 Merge remote-tracking branch 'origin/androidCompose'
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 4b7346c358 fix : remove old android
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 494133a76c Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 0d189081ee test : fini !
1 year ago
Maxence GUITARD 58a7c00376 Merge branch 'androidCompose' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths into androidCompose
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD d4f0164b1f feat : design et responsive pagethemechoice
1 year ago
Maxence GUITARD 65f06cec80 feat : design et responsive pagethemechoice
1 year ago
Jeremy DUCOURTHIAL 2474988e64 feat: ajout QuizMultiViewModel
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 130b1bcf0b fix : add route to launch button
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 4776e5ae0d feat : option deletePlayer, Disconnected and ChangeInfosPlayer
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 1019af52d0 Merge branch 'androidCompose' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths into androidCompose
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 7cd25b50d7 feat : design responsive profilePage
1 year ago
Maxence GUITARD 52aa1acaa4 feat : design responsive profilePage
1 year ago
Jeremy DUCOURTHIAL 163363ba91 fix : commit fix
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 6451a27b3a Merge remote-tracking branch 'origin/androidCompose' into androidCompose
1 year ago
Jeremy DUCOURTHIAL f6f63d73ad feat : finition ServerDetailsViewModel + ajout CreateLobbyViewModel
1 year ago
Yvan CALATAYUD 69e477b3ca feat : Navigation createLobby to ServerDetail + launch
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD fa91413a98 feat : ServerDetail Navigation
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 820ec2b59b feat : design et responsive pagelobby et pagecreationlobby
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD c0ece5a6f1 feat : lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 6e6579677d Merge branch 'androidCompose' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths into androidCompose
1 year ago
Maxence GUITARD a14b817c5b feat : design pagelobby et debut nav
1 year ago
Maxence GUITARD 6ee098482e feat : design pagelobby et debut nav
1 year ago
Yvan CALATAYUD 59c657877d feat : debut Navigation ( home, createLobby, Connexion, Multiplayer)
continuous-integration/drone/push Build is passing Details
1 year ago
Damien NORTIER 281672739a ajout de la documentation concernant la partie API
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL a1665f119c feat : ajout MultiPageViewModel utilisable
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 5382e21c65 feat : hide system bar
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 71edb91bf3 feat : Multipage responsive
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 14951c5d01 feat : Mainpage et connexionpage responsive
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD ef784ff3d3 feat : Confirmdialog password lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL ab3563723e feat : question, bar et chrono ajusté + sycro multi
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 14d05c7dd1 Merge remote-tracking branch 'origin/androidCompose' into androidCompose
1 year ago
Maxence GUITARD 047cb8c34f Merge branch 'androidCompose' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths into androidCompose
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD d7a1267223 Merge branch 'androidCompose' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths into androidCompose
1 year ago
Yvan CALATAYUD 7416798f95 fix : reduction lags using coroutines
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD b3fa0d740d feat : button retour et createPlayer (register)
1 year ago
Jeremy DUCOURTHIAL e2b1ce560b feat : recuperation des question + enchainement sur la page
1 year ago
Yvan CALATAYUD 27f83b26d4 feat : reduction lags QuizMulti
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 9f40a5a5a2 fix : pb intent
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU b1e79e1689 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU e098f783a0 Tests
1 year ago
Yvan CALATAYUD 10a5fbfc23 fix : correction de divers problemes sur les changements de pages et la creation des lobbies
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD d687d2cf96 feat : quiz multi style + debut chrono/progressBar
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 3c7f0a02c4 feat : ajout de l'utilisation du bouton retour avec fonctionnalités + fix lag sur l'application
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD a628d77a11 feat : create lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 8403b54792 fix : import page pb
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL f5b3f59d04 Merge remote-tracking branch 'origin/androidCompose' into androidCompose
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 9924eebb8a feat : ajout page en compose ServerDetailsActivity
1 year ago
Yvan CALATAYUD fb17127303 style : CreateLobbyActivity
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD cf581393f9 feat : Multi + ConnexionPlayerActivity
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD d0ab279f50 fix
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 5353378b06 init in compose
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 8e11a953d4 feat : ajout recup des questions pour QuizMultiActivity
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 5db24cad2f Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 0d0ab241de feat: ajout lancement du jeu quand le maitre du jeu lance la partie
1 year ago
Maxence GUITARD 133d1c9118 feat : ajout du boutton retour dans la page multi
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD f416910ed8 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 4d589241f2 feat : fini activité en cours
1 year ago
Maxence GUITARD 22c6bb216e feat : fleche bouton retour, arrangement du dialog inscription
1 year ago
Jeremy DUCOURTHIAL affd7b1d87 feat: mise en place des sécurités pour que ServerDetailsActivity.kt ne casse plus lors d'un changement d'état
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 6a640e0df8 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 781d98f71c feat : Page Quiz multi + debut chrono et progressBar
1 year ago
Maxence GUITARD 325aeb49ae Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 1228a00395 fix : double code
1 year ago
Maxence GUITARD 5b2f5f0d67 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
1 year ago
Jeremy DUCOURTHIAL f08b3d1a23 feat: ajout suppresion dans la base lorsque le joueur quitte le lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 1c5d2276ef feat : style page Connexion et lien entre page
1 year ago
Jeremy DUCOURTHIAL 74752b4ae7 Merge remote-tracking branch 'origin/master'
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 0cecffcb41 fix : bug lors de la création d'un lobby
1 year ago
Yvan CALATAYUD 9f7ac3f7ce style : responsive main page
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 1471d8484c style : responsive
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 01667c31b0 feat : ajout connexion auto quand ajout d'un lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 70bd4fffd8 feat : ajout connexion d'un joueur + obligation d'être connecter pour jouer en multi
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD f0483bb479 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 94f7927f17 feat : page lobby
1 year ago
Jeremy DUCOURTHIAL 9ad151d7be feat : ajout création d'un lobby utilisable (API)
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 5975fe39de Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 2e921b3754 feat : page liste lobbies
1 year ago
Jeremy DUCOURTHIAL fefae1ebfb feat : ajout création d'un lobby en android
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD e5c118d88b Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD eb9074920d feat : add dossier font et réglages styles page lobbies
1 year ago
Jeremy DUCOURTHIAL afd2b52013 Merge remote-tracking branch 'origin/master'
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 86f38c0a63 feat : ajout de la liste des lobby utilisable
1 year ago
Maxence GUITARD 1b3a0c4263 feat : page Home android
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD baf9981ca3 feat : ajout images et page Home
1 year ago
Jeremy DUCOURTHIAL 51c57bdcea fix : réparation du merge
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 4978d87065 Merge remote-tracking branch 'origin/master'
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 4d8fa0e13e feat : ajout android 21 base projet
1 year ago
Yvan CALATAYUD 511b3f3839 Merge remote-tracking branch 'origin/master'
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 8e84ae99a1 init Android project
1 year ago
BelsethUwU ce6871a30f fix : localhost port Backoffice -> Main site
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 52df7402f5 feat : liaison php/Blazor
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 3cd53c8477 fix : caractère accentués name
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 0daa67fc3c feat : sécurité password bcrypt
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD b4edd88cb4 fix : crash si aucunes questions
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 1bcd519397 fix : hide password
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD d7fc3bd57e Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD d8c60b710f docs : maj readme
1 year ago
BelsethUwU 916a2c0b9c fix : codesmells
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 8ba38344c5 fix : Import question from CSV file
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 1674e13065 feat : securité password avec bcrypt
1 year ago
jeducourth 0445347a17 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
jeducourth c01615c9d1 fix : conversion to csv
1 year ago
Jade VAN BRABANDT 3cd0de72d0 merge
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT a473d77cd8 feat : PageTitle with trad
1 year ago
Yvan CALATAYUD e98baf9966 feat : modif composant
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD df6b0d6046 fix : index
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 1aa6120226 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 0501d628d9 style : pages edit
1 year ago
Maxence GUITARD 02ea72d69b feat : verif questions
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 2ff45ba88b Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD fa905f319d merge
1 year ago
Maxence GUITARD b1aa9bab2d feat : radio button edit question
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 6bbeed7cfa style : page accueil + pages ajouter
1 year ago
Maxence GUITARD 2476ba0a78 feat : radio button add question
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL c8abacb857 merge : fusion
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 6db5d06a7e fix : Clean des actions pour localStorage dans DataLocalService et IDataService
1 year ago
Maxence GUITARD 1d83fa7549 feat : verification taille password
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL bf218310f3 fix : harmonisation ApiById
1 year ago
Maxence GUITARD 0f7f8626cb feat : vérification add et update
1 year ago
Jeremy DUCOURTHIAL ca2f9f36a8 fix : pagination avec Api
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD b513054114 feat : pagination
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 50b16d2115 fix : fix namespace
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 215506a4b2 Doc : conception
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 6124e3c728 fix : langue + remove True/False EditQuestion
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD b43bc329e0
1 year ago
Yvan CALATAYUD 91eb502038 fix : add
1 year ago
Maxence GUITARD b9694454b1 feat : logs add et update question
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 57a4332ccd feat : Globalisation & Localisation + fix add
1 year ago
Maxence GUITARD d6dd6afe7d Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 168699d4d9 feat : update question
1 year ago
Yvan CALATAYUD 27a5fea5db feat : Component CardViewQuestion and BackButton
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT e9037399f4 CI : Trying to implement test to CI
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 8d1b434eea Feat : implementing test sln
continuous-integration/drone/push Build is failing Details
1 year ago
Maxence GUITARD 45fde3b28f fix : ajouter questions remarche
continuous-integration/drone/push Build is failing Details
1 year ago
Maxence GUITARD a602557620 fix : conflit et acces a ajouter
1 year ago
Maxence GUITARD 7a13284a0b fix : ajouter question
1 year ago
Jade VAN BRABANDT f3fc1794d1 CI : Trying to implement test
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 02393bfc18 CI : Trying to implement test
continuous-integration/drone/push Build is failing Details
1 year ago
Maxence GUITARD a73a649c9a fix : probleme ajouter question
1 year ago
Jade VAN BRABANDT 6a72dbe4e6 CI : Trying to implement test
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 70b66ca779 CI : Trying to implement test
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 6166a5efec CI : Trying to implement test
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 8030adee07 CI : Bug
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT fd7a1dcac7 CI : code smells
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 66fbaae2ac CI : fix few codesmells
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD d7e2040aa9 feat : ajouter question
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD ca51465a17 feat : ajouter question
1 year ago
Maxence GUITARD 30f2da7a7b feat : ajouter question
1 year ago
Jade VAN BRABANDT c9058ea9d1 CI : Security hotspots
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT a51901e225 CI : fix code smells
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 109913308c CI : fix build sonar
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 135f283c25 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is failing Details
1 year ago
Yvan CALATAYUD 41387fddd2 feat : Composant DisplayQuestions
1 year ago
Yvan CALATAYUD 756cd07091 fix : Q_id -> Id / Q_Content -> Content
1 year ago
Maxence GUITARD f846db17c1 feat : ajouter question
1 year ago
Maxence GUITARD 3178f6a9e7 feat : ajouter question
1 year ago
Jade VAN BRABANDT d86cdc773c fix : export
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 6cf514c039 fix : removing every mention of data folder about weather
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT b2e987b67b refactor : removing useless folder/file
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT f6243f4703 refactor : code cleanliness
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 90d72219ff refactor : code smells
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT d21cd47e8c refactor : code smells
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 13f68738c5 Fix : finishing the WIP
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 936bbf76c0 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD e2a4f487dd Fix : Finishing the WIP
1 year ago
Yvan CALATAYUD 116e46dd80 Mise à jour de '.gitignore'
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 54001d34db refactor : variable name
1 year ago
Jade VAN BRABANDT 768e6ca7e7 Merge WIP
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT c05cb8cdef WIP
1 year ago
Jade VAN BRABANDT de4f51edc8 refactor : proper naming & refactor of URL (in progress)
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT f78c6eb530 Refactor : more token hidding
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 7a15491bf6 fix: codesmell fix
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 230b90f088 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 1d4d5b6c8f Fix : Codesmells fight
1 year ago
Jade VAN BRABANDT ac218856cf CI : fix build
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT e1c4a6f830 refactor : Token ID hidded from public GIT
continuous-integration/drone/push Build is failing Details
1 year ago
BelsethUwU 5401c208af feat : removed feature of export and importing chapter from CSV cause it was placeholder
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 20226c4159 Fix : CSV export question
1 year ago
Jeremy DUCOURTHIAL 539adefee7 feat : Ajout token Get Post Delete
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL a1bf965d02 feat : securité token
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 29c90af909 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL f6f9eaeb16 fix : nom page Question
1 year ago
Yvan CALATAYUD ef3940f44e fix : delete
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 2e83563566 fix : delete
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD cf83d8d840 feat : delete
1 year ago
Jade VAN BRABANDT 9d8903da36 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 23da554720 Feat : Import/export Question (API may not be implemented now)
1 year ago
Maxence GUITARD 50af63099e fix : add et edit de toute les pages
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD e9fbf7c15a feat : page edit et add question
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT c0d9b0e21a Merge pull request 'Drone_Update_Testing' (#38) from Drone_Update_Testing into master
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT c0e6d5ee29 Merge branch 'master' into Drone_Update_Testing
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT b174843ce7 Update '.drone.yml'
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT b1ab49ca71 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 62c3b5f6a0 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT fa5ce40768 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT b6a2d3f392 Update 'README.md'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT d18b04a2ef Update 'README.md'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 238b4822f5 Update 'README.md'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 692fe86254 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 30ff34d460 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT c759e1284b Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 89c71cfddc Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 5d91e1f152 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT d322ef3ecb Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Yvan CALATAYUD e8d32a0178 Ouio
continuous-integration/drone/push Build is failing Details
1 year ago
Yvan CALATAYUD fc62a08554 Merge branch 'master' into Drone_Update_Testing
continuous-integration/drone/push Build is failing Details
1 year ago
Yvan CALATAYUD a3bf6a4151 refactor : script js
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT c4b330ca8b Fix : Testing Drone thing
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 1e2f85db64 Fix : Testing Drone thing
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 89581e1e3d Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 5bb932d77e Fix : Testing Drone thing
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT ad091a572b Fix : Testing Drone thing
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 10936519c8 Test : Testing stuff with drone
continuous-integration/drone/push Build is failing Details
1 year ago
Jade VAN BRABANDT 753adb220d Merge : J'vais explosée
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT a09656fc08 Merge : oui
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 22171e3789 Merge : still oui
1 year ago
Jade VAN BRABANDT 90327265b9 Merge : Oui
1 year ago
Maxence GUITARD 9c35bf64f9 fix : fix confirm conflict
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 6156f89dfc fix : confirm conflict
1 year ago
Jade VAN BRABANDT 03191d5cee feat : Players and a bit of fix everywhere
1 year ago
Maxence GUITARD 25bddc1bbf fix : conflit
1 year ago
Maxence GUITARD 06c15130a6 feat : edit admin
1 year ago
Yvan CALATAYUD a5596357ea fix : logs
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD b3b630afda feat : edit admin
1 year ago
Yvan CALATAYUD f7024dac0d feat : logs
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD f323a92675 feat : logs
1 year ago
Yvan CALATAYUD 54724d4fe3 feat : logs
1 year ago
Yvan CALATAYUD e160e2f069 feat : logs
1 year ago
Maxence GUITARD 0fbcee4ada Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 9e49536a88 feat : add admin
1 year ago
Jade VAN BRABANDT 992c403b21 feat : API issue fix & implementation of update to API
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 86cb4700d9 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 536735faea feat : add chapter
1 year ago
Maxence GUITARD a45bbfb2a3 fix : affichage chapter
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 6ac3ecabed fix : affichage questions
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD a1b99f9cb7 feat : page questions
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 987bc11c9b fix : Les merges autos qui fonctionne pas correctement stylé
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 50f577beec feat : merge
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT a0aff98cb8 Feat: merge
1 year ago
Jade VAN BRABANDT 9f69883362 Feat : Merge
1 year ago
Maxence GUITARD f54ea9ee80 feat : supprimer admins
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT e2fcefddea feat : import chapters from CSV
1 year ago
Maxence GUITARD 4ad1d0aaaa Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
1 year ago
Maxence GUITARD a37f2ab60e feat : fonction editer et boutons
1 year ago
Yvan CALATAYUD fee5b40666 fix : accolade en trop
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 032b27f5fa feat : supprimer
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 58c72601db feat : supprimer
1 year ago
Yvan CALATAYUD 4010e2dcb0 feat : supprimer
1 year ago
Maxence GUITARD 441987fe09 feat : fonction ajout admins
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 70deb12669 feat : supprimer
1 year ago
Maxence GUITARD f7189fc321 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
1 year ago
Maxence GUITARD e2052f136b feat : page addAdmin
1 year ago
Jade VAN BRABANDT 153f66ef34 Merge : Preparation for merge
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 0f6e18d1f3 Fix : UTF32 encoding
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 00cb290649 Feat : Finished CSV export, code might not be clean
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 77f5a566e9 Fix : c'mieux quand je réfléchis correctement
continuous-integration/drone/push Build is passing Details
1 year ago
Damien NORTIER 2a565522e2 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Damien NORTIER 3d8e93d2a7 adding administrator page to the menu
continuous-integration/drone/push Build is passing Details
1 year ago
Damien NORTIER 1d0ca6eb25 feat : administrator page
1 year ago
Damien NORTIER e7a27ad94b adding classes
1 year ago
Damien NORTIER 786ac57a28 adding fake-datas
1 year ago
Jade VAN BRABANDT c9dc0c30a7 Feat : Not finished / Export implementation
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD a88dfa6fc9 feat : AddChapter + EditChapter
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 528308d655 Feat : API chapter integration
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 85ea42f958 feat : Ajout fake data
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 1dc7650866 feat : Blazor projet
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 150cca45ec Feat : Page & Start of API integration
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 3a0127df48 feat : partie blazor
continuous-integration/drone/push Build is passing Details
1 year ago
Damien NORTIER ed8bc240a1 modification de certaines classes
continuous-integration/drone/push Build is passing Details
1 year ago
Damien NORTIER b8256db24d création des classes
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL f8daa2bbe8 Feat : ajout projet Blazor + gitignore
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 1d1a0797d8 Build : Blazor build
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 6c19ed2dca Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL e34e092ab2 docs : finalisation du README
1 year ago
Jade VAN BRABANDT f3b6adcc5c docs : delete image not relevant
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU e30257f9c5 docs : merging
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU f886c47158 docs : diagramme de classe
1 year ago
Jeremy DUCOURTHIAL 06c0a96ba8 doc : ajout png doc
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 96f0345622 docs : changement folder
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT d1fce1fea0 docs : diagramme de classe
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 2128d34b2a docs : diagramme de classe
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 1275659a5f docs : Wireframe
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD c2217039c1 fix : score if player not connected
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 5eb99e060f fix : nbDifficulty
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD fe2f7bf6de fix : deletePlayerByID
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT c6324b8357 feat : I don't evne know what I did but fiexd thing
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 75634bc656 fix : small change ton nb fail updating
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 7a346c57c8 feat : btnRetour dans lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 60ac160cca Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 58b8dfa1e1 style : divQuestions fond
1 year ago
Jeremy DUCOURTHIAL be704e9dbc Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 4d2e84ca57 feat : page lobby utilisable
1 year ago
BelsethUwU 9bd50abba1 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU acc0c2bd7a Feat:large adjustment'nd verification on adminside
1 year ago
Yvan CALATAYUD b078e62b3c Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 205f84db48 feat : incrementation nbFails et difficulty
1 year ago
Maxence GUITARD 2824a55de2 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 1d66919d67 feat : addScore + updateScore
1 year ago
Jade VAN BRABANDT 20e3e9c849 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 28590b1d67 feat : nom chapitre
1 year ago
Maxence GUITARD cc60aecd33 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 080e3202b9 temp : working
1 year ago
Jade VAN BRABANDT 65f57a841e Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT d31c2c90f3 feat : page user
1 year ago
Yvan CALATAYUD 333dceb0d3 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 0ac96eecd2 style : design ++
1 year ago
Jeremy DUCOURTHIAL 844f3c06a1 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL c6f091387f feat : base player
1 year ago
Jade VAN BRABANDT 022bc0d2b8 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT a3f5331abb feat : little verification
1 year ago
Maxence GUITARD 30e1e91471 fix : probleme page blanche
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 6ddf62bea5 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Maxence GUITARD 9b4e48298a feat : gatewayJouer
1 year ago
Yvan CALATAYUD b8e6d7e336 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Yvan CALATAYUD 0429563f75 feat : confettis + vue lobby + vue error
1 year ago
Jeremy DUCOURTHIAL 13f401352e feat : espace !!!
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL a8e12e1612 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 9fa238e093 feat : Connexion + add Player
1 year ago
Jade VAN BRABANDT 9ae18cf866 feat : NAMESPACE !!!! 🔥
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT a6cbdb0315 feat : error UwU 🔥
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL 5689be418e Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jeremy DUCOURTHIAL c116fb230f feat : random question + loginPlayer (début)
1 year ago
Yvan CALATAYUD c9880c95d9 feat: Changement font + debut Lobby
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT b95a1791f7 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 1a951d96ce feat : change in git ignore
1 year ago
Maxence GUITARD ffff595705 style : ajout des progressBar multi
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 96ab357679 feat : nbfails
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT d64ab6c94b fix : ups
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT bc67e2fe0d Merge pull request 'Long-fix Pull Request' (#35) from Long-fix into master
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 225cad5fe4 fix : Everything is beautifull :flower:
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT ff94851391 fix : je finis après
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU 688c4801af fix : partial fix of class not being used right
continuous-integration/drone/push Build is passing Details
1 year ago
BelsethUwU e390a93eb5 fix : end of last commit
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 08543cbea3 feat : oui je regardes après
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT eba391d3d8 feat : Score management 🔥
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 6ac8e515ac fix : git ignore + retire var_dump()
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT aecde64977 Merge branch 'master' of https://codefirst.iut.uca.fr/git/jade.van_brabandt/3.01-QCM_MuscuMaths
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT add9b34d05 feat : question hors Json
1 year ago
Jade VAN BRABANDT 204152e11c feat : update gitignore (Salut Bastien)
continuous-integration/drone/push Build is passing Details
1 year ago
Jade VAN BRABANDT 252e229234 Delete 'Website/usages/Config.php'
continuous-integration/drone/push Build is failing Details
1 year ago

@ -1,17 +1,61 @@
kind: pipeline
type: docker
name: default
trigger:
event:
- push
steps:
- name: sonar
image: sonarsource/sonar-scanner-cli:5
- name: build
image: mcr.microsoft.com/dotnet/sdk:7.0
commands:
- cd Blazor/
- dotnet restore Blazor.sln
- dotnet build Blazor.sln -c Release --no-restore
- name: tests
image: mcr.microsoft.com/dotnet/sdk:7.0
commands:
- cd Blazor/
- dotnet restore Blazor.sln
- dotnet test Blazor.sln --no-restore
depends_on: [build]
- name: code-analysis
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7
commands:
- sonar-scanner -Dsonar.projectKey=MuscuMaths -Dsonar.sources=. -Dsonar.login=$${PLUGIN_SONAR_TOKEN} -Dsonar.host.url=$${PLUGIN_SONAR_HOST} -Dsonar.php.coverage.reportPaths=coverage.xml
- cd Blazor/
- dotnet restore Blazor.sln
- dotnet sonarscanner begin /k:MuscuMaths /d:sonar.host.url=$${PLUGIN_SONAR_HOST} /d:sonar.login=$${PLUGIN_SONAR_TOKEN} /d:sonar.coverageReportPaths="coveragereport/SonarQube.xml"
- dotnet sonarscanner begin /k:MuscuMaths /d:sonar.host.url=$${PLUGIN_SONAR_HOST} /d:sonar.login=$${PLUGIN_SONAR_TOKEN}
- dotnet build Blazor.sln -c Release --no-restore
- dotnet test Blazor.sln --logger trx --no-restore /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura --collect "XPlat Code Coverage"
- reportgenerator -reports:"**/coverage.cobertura.xml" -reporttypes:SonarQube -targetdir:"coveragereport" -verbosity:Verbose
- dotnet publish Blazor.sln -c Release --no-restore -o $CI_PROJECT_DIR/build/release
- dotnet sonarscanner end /d:sonar.login=$${PLUGIN_SONAR_TOKEN}
secrets: [ PLUGIN_SONAR_TOKEN ]
settings:
sonar_host:
from_secret: sonar_host
# accessible en ligne de commande par $${PLUGIN_SONAR_HOST}
sonar_host: https://codefirst.iut.uca.fr/sonar/
# accessible en ligne de commande par $${PLUGIN_SONAR_TOKEN}
sonar_token:
from_secret: sonar_token
from_secret: PLUGIN_SONAR_TOKEN
depends_on: [tests]
- name: generate-and-deploy-docs
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7
failure: ignore
volumes:
- name: docs
path: /docs
commands:
- /entrypoint.sh
when:
branch:
- master
depends_on: [ build ]
volumes:
- name: docs
temp: {}

430
.gitignore vendored

@ -819,5 +819,431 @@ replay_pid*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# cache folders
/Website/usages/config.php
# config ignore to not stock password on the git (: neither the Token of the API
/Website/usages/Config_DB.php
/Blazor/Blazor/Pages/API.cs
### DotnetCore ###
# .NET Core build folders
bin/
obj/
# Common node modules locations
/node_modules
/wwwroot/node_modules
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
*.code-workspace
# Local History for Visual Studio Code
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/dotnetcore,visualstudio,visualstudiocode

15
Android/.gitignore vendored

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

@ -0,0 +1 @@
/build

@ -0,0 +1,80 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.mathseduc"
compileSdk = 34
defaultConfig {
applicationId = "com.example.mathseduc"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.0")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-graphics-android:1.6.3")
implementation("androidx.navigation:navigation-common-ktx:2.7.7")
implementation("androidx.navigation:navigation-compose:2.7.7")
implementation("androidx.compose.material:material-icons-core")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.navigation:navigation-runtime-ktx:2.7.7")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("org.mindrot:jbcrypt:0.4")
implementation("androidx.navigation:navigation-compose:2.7.7")
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

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

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MathsEduc"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MathsEduc">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity android:name=".MultiActivity" />
<activity android:name=".ConnexionPlayerActivity" />
<activity android:name=".CreateLobbyActivity" />
<activity android:name=".ServerDetailsActivity" />
<activity android:name=".QuizMultiActivity" />
</application>
</manifest>

@ -0,0 +1,57 @@
package com.example.mathseduc
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.mathseduc.ui.CreateLobbyPage
import com.example.mathseduc.ui.HomePage
import com.example.mathseduc.ui.MultiPage
import com.example.mathseduc.ui.QuizMultiScreen
import com.example.mathseduc.ui.ServerDetailPage
import com.example.mathseduc.ui.ProfilePlayerPage
import com.example.mathseduc.ui.ThemeChoicePage
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomePage(navController) }
composable("connexion") { ConnexionPlayerContent(navController) }
composable("multiplayer") { MultiPage(navController) }
composable("createLobby") { CreateLobbyPage(navController) }
composable("profilePlayer") { ProfilePlayerPage(navController) }
composable("themeChoice") { ThemeChoicePage(navController) }
//composable("serverDetails/{serverName}/{lobbyId}") { ServerDetailPage(navController) }
composable(
route = "serverDetails/{lobbyName}/{lobbyId}/{lobbyChapter}/{lobbyNbPlayers}/{lobbyDifficulty}",
arguments = listOf(
navArgument("lobbyName") { type = NavType.StringType },
navArgument("lobbyId") { type = NavType.IntType },
navArgument("lobbyChapter") { type = NavType.IntType },
navArgument("lobbyNbPlayers") { type = NavType.IntType },
navArgument("lobbyDifficulty") { type = NavType.IntType }
)
) { backStackEntry ->
val lobbyName = backStackEntry.arguments?.getString("lobbyName")
val lobbyId = backStackEntry.arguments?.getInt("lobbyId")
val lobbyChapter = backStackEntry.arguments?.getInt("lobbyChapter")
val lobbyNbPlayers = backStackEntry.arguments?.getInt("lobbyNbPlayers")
val lobbyDifficulty = backStackEntry.arguments?.getInt("lobbyDifficulty")
ServerDetailPage(navController, lobbyName,lobbyId, lobbyChapter, lobbyNbPlayers, lobbyDifficulty)
}
composable(
route = "quizMultiScreen/{lobbyId}",
arguments = listOf(
navArgument("lobbyId") { type = NavType.IntType }
)
) { backStackEntry ->
val lobbyId = backStackEntry.arguments?.getInt("lobbyId")
QuizMultiScreen(navController, lobbyId)
}
}
}

@ -0,0 +1,266 @@
package com.example.mathseduc
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.example.mathseduc.controllers.ControllerPlayer
class ConnexionPlayerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
ConnexionPlayerContent(navController = navController)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConnexionPlayerContent(navController: NavController) {
var nickname by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var showDialog by rememberSaveable { mutableStateOf(false) }
val activity = LocalView.current.context as Activity
val context = LocalContext.current
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
WindowCompat.setDecorFitsSystemWindows(activity.window, false)
val windowInsetsController = remember {
WindowCompat.getInsetsController(activity.window, activity.window.decorView)
}
// Hide the status bar
windowInsetsController.hide(WindowInsetsCompat.Type.statusBars())
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {},
navigationIcon = {
IconButton(
onClick = { navController.navigate("home") },
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
modifier = Modifier.size(36.dp),
tint = Color.White
)
}
},
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp,0.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
modifier = Modifier.padding(0.dp, 2.dp, 0.dp)
.size(if (isPortrait) 120.dp else 90.dp)
)
Spacer(modifier = Modifier.height(if (isPortrait) 16.dp else 4.dp))
OutlinedTextField(
value = nickname,
onValueChange = { nickname = it },
label = { Text("Nickname") },
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 8.dp,3.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White,
disabledContainerColor = Color.White,
focusedBorderColor = Color.Blue,
),
shape = RoundedCornerShape(8.dp)
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Password
),
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(8.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White,
disabledContainerColor = Color.White,
focusedBorderColor = Color.Blue,
),
shape = RoundedCornerShape(8.dp)
)
Spacer(modifier = Modifier.height(if (isPortrait) 16.dp else 7.dp))
Button(
onClick = {
val isAuthenticated = ControllerPlayer.authenticateUser(nickname, password)
if (isAuthenticated != -1) {
MainActivity.idPlayerConnected = isAuthenticated
Toast.makeText(context, "Connexion réussie, bienvenue $nickname !", Toast.LENGTH_SHORT).show()
navController.navigate("home")
} else {
Toast.makeText(context, "Connexion échouée. Veuillez réessayer.", Toast.LENGTH_SHORT).show()
nickname = ""
password = ""
}
},
modifier = Modifier
.width(230.dp)
.height(48.dp)
) {
Text("Login")
}
Spacer(modifier = Modifier.height(if (isPortrait) 16.dp else 10.dp))
Button(
onClick = { showDialog = true },
modifier = Modifier
.width(230.dp)
.height(48.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF40E0D0))
) {
Text("Register")
}
if (showDialog) {
RegisterDialog(onDismiss = { showDialog = false })
}
}
}
@Composable
fun RegisterDialog(onDismiss: () -> Unit) {
val context = LocalContext.current
var nickname by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var nicknameError by remember { mutableStateOf(false) }
var passwordError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = { Text("Register") },
confirmButton = {
Button(
onClick = {
if (nickname.isNotBlank() && password.isNotBlank()) {
val playerId = ControllerPlayer.createPlayer(nickname, password)
onDismiss()
} else {
if (nickname.isBlank()) nicknameError = true
if (password.isBlank()) passwordError = true
}
}
) {
Text("Save")
}
},
dismissButton = {
Button(
onClick = {
onDismiss()
}
) {
Text("Dismiss")
}
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
OutlinedTextField(
value = nickname,
onValueChange = {
nickname = it
nicknameError = false
},
label = { Text("Nickname") },
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
isError = nicknameError
)
OutlinedTextField(
value = password,
onValueChange = {
password = it
passwordError = false
},
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Password
),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
isError = passwordError
)
if (nicknameError || passwordError) {
Text(
text = "Please fill in all fields",
color = Color.Red,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
)
}

@ -0,0 +1,19 @@
package com.example.mathseduc
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.compose.rememberNavController
import com.example.mathseduc.ui.CreateLobbyPage
class CreateLobbyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
CreateLobbyPage(navController = navController)
}
}
}

@ -0,0 +1,28 @@
package com.example.mathseduc
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.mathseduc.ui.HomePage
class MainActivity : ComponentActivity() {
companion object {
var idPlayerConnected: Int = -1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppNavigation()
}
}
}
@Preview(showBackground = true)
@Composable
fun MainActivityPreview() {
MainActivity()
}

@ -0,0 +1,18 @@
package com.example.mathseduc
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.navigation.compose.rememberNavController
import com.example.mathseduc.ui.MultiPage
class MultiActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
MultiPage(navController)
}
}
}

@ -0,0 +1,36 @@
package com.example.mathseduc
import android.os.Bundle
import android.os.CountDownTimer
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.example.mathseduc.ui.theme.MathsEducTheme
class QuizMultiActivity : ComponentActivity() {
companion object {
var countDownTimer: CountDownTimer? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MathsEducTheme {
/*
val lobbyId = intent.getIntExtra("lobbyId",-1)
val serverName = intent.getStringExtra("serverName")
QuizMultiScreen(lobbyId, serverName!!) //TODO sus
*/
}
}
}
override fun onDestroy() {
super.onDestroy()
// Stop the CountDownTimer to avoid memory leaks
countDownTimer?.cancel()
}
}

@ -0,0 +1,108 @@
package com.example.mathseduc
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerUtiliser
import com.example.mathseduc.models.Player
import com.example.mathseduc.ui.theme.Colors
import com.example.mathseduc.viewModel.ServerDetailsViewModel
import okhttp3.MultipartBody
class ServerDetailsActivity : ComponentActivity() {
private var playerList: List<Player> = emptyList()
private val handler = Handler(Looper.getMainLooper())
private val refreshInterval: Long = 2000
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
myBackPressed()
}
}
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
setContent {
val navController = rememberNavController()
//ServerDetailPage(navController = navController)
}
}
private fun myBackPressed() {
val lobbyId = intent.getIntExtra("lobbyId", -1)
ControllerUtiliser.DeleteUtiliserForLobby(MainActivity.idPlayerConnected, lobbyId)
if (ControllerLobby.playerCreatorIdPresentInLobby(MainActivity.idPlayerConnected, lobbyId)) {
val idNextPlayerCreator = ControllerUtiliser.getIdNextPlayerInLobby(lobbyId)
if (idNextPlayerCreator == -1) {
ControllerLobby.deleteLobby(lobbyId)
} else {
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilder.addFormDataPart("idplayercreator", idNextPlayerCreator.toString())
ControllerLobby.updateLobbyIdCreatorLobby(lobbyId, formDataBuilder)
}
}
finish()
}
}

@ -0,0 +1,72 @@
package com.example.mathseduc.controllers
import android.os.StrictMode
import com.example.mathseduc.models.Chapter
import com.example.mathseduc.models.Lobby
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
class ControllerChapter {
companion object {
fun getChapters(): ArrayList<Chapter>? {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/all/chapters/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO/1")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Chapter>>() {}.type
// Parse API response and return the list of chapters
return gson.fromJson(response.body!!.string(), typeTokenProduct)
}
return null
}
fun getChapterNameById(idchapter : Int?): String? {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/chapters/$idchapter/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Chapter>>() {}.type
// Parse API response and return the list of chapters
val chapter: ArrayList<Chapter> = gson.fromJson(response.body!!.string(), typeTokenProduct)
return chapter[0].name
}
return null
}
}
}

@ -0,0 +1,356 @@
package com.example.mathseduc.controllers
import android.os.StrictMode
import android.util.Log
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.models.Utiliser
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
class ControllerLobby {
companion object {
fun getLobbies(): ArrayList<Lobby>? {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/all/lobbies/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Lobby>>() {}.type
return gson.fromJson(response.body!!.string(), typeTokenProduct)
}
return null
}
fun getIdQuestionsLobby(idLobby: Int): ArrayList<String> {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/propose/$idLobby/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Map<String, String>>>() {}.type
val questionList: ArrayList<Map<String, String>> = gson.fromJson(response.body!!.string(), typeTokenProduct)
// Extracting question IDs
val idList = ArrayList<String>()
for (question in questionList) {
val id = question["idquestion"]
if (id != null) {
idList.add(id)
}
}
return idList
}
}
fun createLobby(lobbyData: MultipartBody.Builder): Int {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/add/lobbies/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.post(lobbyData.build())
.build()
// API Response
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
// Si la réponse est réussie, extraire l'ID du lobby du corps de la réponse JSON
val responseBody = response.body?.string()
val jsonObject = JSONObject(responseBody)
// Retourner l'ID du lobby
return jsonObject.optInt("lobby_id", -1)
}
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("CreateLobby", "Error creating lobby", e)
}
return -1
}
fun playerCreatorIdPresentInLobby(idPlayer: Int, lobbyId: Int?): Boolean {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/lobbies/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Lobby>>() {}.type
val lobby: ArrayList<Lobby> = gson.fromJson(response.body!!.string(), typeTokenProduct)
return lobby[0].idplayercreator == idPlayer
}
return false
}
fun deleteLobby(lobbyId: Int) {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access - Suppression du lobby
val deleteRequest = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/lobbies/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.delete()
.build()
// API Response
val deleteResponse: Response = client.newCall(deleteRequest).execute()
// Vérifier si la suppression a réussi
if (!deleteResponse.isSuccessful) {
Log.e("deleteLobby", "Error deleting lobby")
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("deleteLobby", "Error deleting lobby", e)
}
}
fun updateLobbyIdCreatorLobby(lobbyId: Int,lobbyData: MultipartBody.Builder) {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access - Mise à jour du lobby
val updateRequest = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/update/lobbies/idplayercreator/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
// Ajoutez d'autres paramètres ou données pour la mise à jour si nécessaire
.post(lobbyData.build())
.build()
// API Response
val updateResponse: Response = client.newCall(updateRequest).execute()
// Vérifier si la mise à jour a réussi
if (!updateResponse.isSuccessful) {
Log.e("updateLobby", "Error updating lobby")
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("updateLobby", "Error updating lobby", e)
}
}
fun getLobbyUtiliserPlayerTime(lobbyId: Int, playerId: Int): Int {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access - Mise à jour du lobby
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/lobbies/utiliser/playertime/$lobbyId/$playerId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Utiliser>>() {}.type
val utiliser: ArrayList<Utiliser> = gson.fromJson(response.body!!.string(), typeTokenProduct)
return utiliser[0].playertime
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("updateLobby", "Error updating lobby", e)
return -1
}
}
fun updateLobbyUtiliserPlayerTime(lobbyId: Int,playerId: Int,lobbyData: MultipartBody.Builder) {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access - Mise à jour du lobby
val updateRequest = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/update/lobbies/utiliser/playertime/$lobbyId/$playerId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO\n")
.post(lobbyData.build())
.build()
// API Response
val updateResponse: Response = client.newCall(updateRequest).execute()
// Vérifier si la mise à jour a réussi
if (!updateResponse.isSuccessful) {
Log.e("updateLobby", "Error updating lobby")
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("updateLobby", "Error updating lobby", e)
}
}
fun updateLobbyLauched(lobbyId: Int?,lobbyData: MultipartBody.Builder) {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access - Mise à jour du lobby
val updateRequest = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/update/lobbies/launched/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
// Ajoutez d'autres paramètres ou données pour la mise à jour si nécessaire
.post(lobbyData.build())
.build()
// API Response
val updateResponse: Response = client.newCall(updateRequest).execute()
// Vérifier si la mise à jour a réussi
if (!updateResponse.isSuccessful) {
Log.e("updateLobby", "Error updating lobby")
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("updateLobby", "Error updating lobby", e)
}
}
fun getNbPlayerInLobby(lobbyId: Int?): Int {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/utiliser/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Utiliser>>() {}.type
val utiliser: ArrayList<Utiliser> = gson.fromJson(response.body!!.string(), typeTokenProduct)
return utiliser.size
}
return -1
}
fun getPlayerInLobby(lobbyId: Int): List<Utiliser> {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/utiliser/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<List<Utiliser>>() {}.type
return gson.fromJson(response.body!!.string(), typeTokenProduct)
}
}
fun lobbyIsLaunched(lobbyId: Int?): Boolean {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/lobbies/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Lobby>>() {}.type
val lobby: ArrayList<Lobby> = gson.fromJson(response.body!!.string(), typeTokenProduct)
return lobby[0].launched == 1
}
return false
}
}
}

@ -0,0 +1,187 @@
package com.example.mathseduc.controllers
import android.os.StrictMode
import android.util.Log
import com.example.mathseduc.models.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.FormBody
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.mindrot.jbcrypt.BCrypt
import java.io.IOException
class ControllerPlayer {
companion object {
fun getPlayersIdFromLobbyId(lobbyId: String?): List<Int>? {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
val client = OkHttpClient()
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/utiliser/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val gson = Gson()
val typeTokenProduct = object : TypeToken<List<Utiliser>>() {}.type
val playerInfoList: List<Utiliser> = gson.fromJson(response.body!!.string(), typeTokenProduct)
// Extract player IDs from PlayerInfo list
val playerIds: List<Int> = playerInfoList.map { it.idplayer }
return playerIds
}
return null
}
fun getPlayerInfoById(playerId: String): Player? {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
val client = OkHttpClient()
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/players/$playerId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val gson = Gson()
val playerInfo: List<Player> = gson.fromJson(response.body!!.string(), object : TypeToken<List<Player>>() {}.type)
// Assuming the API returns a list even for a single player, we take the first item
return playerInfo.firstOrNull()
}
return null
}
fun createPlayer(nickname: String, password: String): Int {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
val client = OkHttpClient()
val hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt())
val requestBody = FormBody.Builder()
.add("nickname", nickname)
.add("password", hashedPassword)
.build()
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/add/player/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
val responseData = response.body?.string()
val gson = Gson()
val player: Player = gson.fromJson(responseData, Player::class.java)
return player.id
} else {
throw IOException("Failed to create player: ${response.code}")
}
}
}
fun deletePlayer(playerId: String) {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access - Suppression du player
val deleteRequest = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/delete/player/$playerId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.delete()
.build()
// API Response
val deleteResponse: Response = client.newCall(deleteRequest).execute()
// Vérifier si la suppression a réussi
if (!deleteResponse.isSuccessful) {
Log.e("deletePlayer", "Error deleting player")
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("deletePlayer", "Error deleting player", e)
}
}
fun updatePlayer(playerId: String,lobbyData: MultipartBody.Builder) {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
val client = OkHttpClient()
val updateRequest = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/update/player/${playerId}/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.post(lobbyData.build())
.build()
val updateResponse: Response = client.newCall(updateRequest).execute()
if (!updateResponse.isSuccessful) {
Log.e("updatePlayer", "Error updating player")
}
} catch (e: Exception) {
Log.e("updatePlayer", "Error updating lobby", e)
}
}
fun authenticateUser(nickname: String, password: String): Int {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/verif/player/$nickname/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
val responseData = response.body?.string()
// Use Gson to parse the JSON response
val gson = Gson()
val playerListType = object : TypeToken<List<Player>>() {}.type
val playerList: List<Player> = gson.fromJson(responseData, playerListType)
// Check if the list is not empty
if (playerList.isNotEmpty()) {
val storedPasswordHash = playerList[0].password
// Use BCrypt to check if the provided password matches the stored hash
if(BCrypt.checkpw(password, storedPasswordHash)){
return playerList[0].id
}
}
}
}
return -1
}
}
}

@ -0,0 +1,79 @@
package com.example.mathseduc.controllers
import Answer
import android.annotation.SuppressLint
import android.os.StrictMode
import com.example.mathseduc.models.Question
import com.google.gson.Gson
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
class ControllerQuestion {
companion object {
fun getQuestionsForLobby(questionIds: ArrayList<String>): List<Question>? {
val questions = ArrayList<Question>()
for (questionId in questionIds) {
val question = getQuestionById(questionId)
if (question != null) {
questions.add(question)
}
}
return if (questions.isNotEmpty()) questions else null
}
private fun getQuestionById(questionId: String): Question? {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/questions/$questionId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// Handle error, log, or throw an exception as needed
return null
}
// Gson deserialization
val gson = Gson()
val responseData = response.body?.string() ?: return null
val jsonArray = JSONArray(responseData)
val answers = mutableListOf<Answer>()
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val answer = Answer(
id = jsonObject.getInt("a_id"),
content = jsonObject.getString("a_content")
)
answers.add(answer)
}
val questions = mutableListOf<Question>()
val jsonObject = jsonArray.getJSONObject(0)
val question = Question(
id = jsonObject.getInt("q_id"),
content = jsonObject.getString("q_content"),
nbfails = jsonObject.getInt("nbfails"),
difficulty = jsonObject.getInt("difficulty"),
idchapter = jsonObject.getInt("idchapter"),
idanswergood = jsonObject.getInt("idanswergood"),
answers = answers
)
questions.add(question)
return if (questions.isNotEmpty()) questions[0] else null
}
}
}
}

@ -0,0 +1,100 @@
package com.example.mathseduc.controllers
import android.os.StrictMode
import android.util.Log
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.models.Utiliser
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
class ControllerUtiliser {
companion object {
fun createUtiliserByIdLobby(utiliserData: MultipartBody.Builder): Boolean {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/add/utiliser/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.post(utiliserData.build())
.build()
// API Response
client.newCall(request).execute().use { response ->
// Vérifier si la création du lobby a réussi
return response.isSuccessful
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("CreateLobby", "Error creating lobby", e)
return false
}
}
fun DeleteUtiliserForLobby(idPlayerConnected: Int, lobbyId: Int): Boolean {
try {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/delete/utiliser/$lobbyId/$idPlayerConnected/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.delete()
.build()
// API Response
val response: Response = client.newCall(request).execute()
// Vérifier si la suppression a réussi
return response.isSuccessful
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("DeleteUtiliser", "Error deleting utiliser", e)
return false
}
}
fun getIdNextPlayerInLobby(lobbyId: Int): Int {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
// Client HTTP API
val client = OkHttpClient()
// API Access
val request = Request.Builder()
.url("https://trusting-panini.87-106-126-109.plesk.page/api/utiliser/$lobbyId/qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO")
.build()
// API Response
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// Gson deserialization
val gson = Gson()
val typeTokenProduct = object : TypeToken<ArrayList<Utiliser>>() {}.type
val utiliser: ArrayList<Utiliser> = gson.fromJson(response.body!!.string(), typeTokenProduct)
if (utiliser.isNotEmpty()){
return utiliser[0].idplayer
} else {
return -1
}
}
return -1
}
}
}

@ -0,0 +1,4 @@
data class Answer(
val id: Int,
val content: String
)

@ -0,0 +1,6 @@
package com.example.mathseduc.models
data class Chapter(
val id: String,
val name: String,
)

@ -0,0 +1,51 @@
package com.example.mathseduc.models
import android.os.Parcel
import android.os.Parcelable
data class Lobby(
val id: Int,
val name: String,
val password: String,
val nbplayers: Int,
val idplayercreator: Int,
val idchapter: Int,
val difficulty: Int,
val launched: Int
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(name)
parcel.writeString(password)
parcel.writeInt(nbplayers)
parcel.writeInt(idplayercreator)
parcel.writeInt(idchapter)
parcel.writeInt(difficulty)
parcel.writeInt(launched)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Lobby> {
override fun createFromParcel(parcel: Parcel): Lobby {
return Lobby(parcel)
}
override fun newArray(size: Int): Array<Lobby?> {
return arrayOfNulls(size)
}
}
}

@ -0,0 +1,36 @@
package com.example.mathseduc.models
import android.os.Parcel
import android.os.Parcelable
data class Player(
val id: Int,
val nickname: String,
val password: String
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString() ?: "",
parcel.readString() ?: ""
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(nickname)
parcel.writeString(password)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Player> {
override fun createFromParcel(parcel: Parcel): Player {
return Player(parcel)
}
override fun newArray(size: Int): Array<Player?> {
return arrayOfNulls(size)
}
}
}

@ -0,0 +1,13 @@
package com.example.mathseduc.models
import Answer
data class Question(
val id: Int,
val content: String,
val nbfails: Int,
val difficulty: Int,
val idchapter: Int,
val idanswergood: Int,
val answers: List<Answer>
)

@ -0,0 +1,6 @@
package com.example.mathseduc.models
data class Utiliser (
val idlobby: Int,
val idplayer: Int,
val playertime: Int
)

@ -0,0 +1,315 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.content.res.Configuration
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.NavController
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerChapter
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerUtiliser
import com.example.mathseduc.ui.theme.Colors
import com.example.mathseduc.viewModel.CreateLobbyViewModel
import com.example.mathseduc.viewModel.ServerDetailsViewModel
import okhttp3.MultipartBody
@Composable
fun CreateLobbyPage(navController: NavController) {
val viewModelStoreOwner = LocalContext.current as ViewModelStoreOwner
val viewModel = ViewModelProvider(viewModelStoreOwner).get(CreateLobbyViewModel::class.java)
val difficultyOptions = listOf("Facile", "Moyen", "Difficile")
val lobbyName by viewModel.lobbyName.collectAsState("")
val password by viewModel.password.collectAsState("")
val nbPlayers by viewModel.nbPlayers.collectAsState("")
val difficulty by viewModel.difficulty.collectAsState("")
val chapter by viewModel.chapter.collectAsState("")
val expandedDifficulty by viewModel.expandedDifficulty.collectAsState(false)
val expandedChapter by viewModel.expandedChapter.collectAsState(false)
val size by viewModel.size.collectAsState(IntSize.Zero)
val context = LocalContext.current
val activity = LocalView.current.context as Activity
val keyboardController = LocalSoftwareKeyboardController.current
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
val windowInsetsController = remember {
WindowCompat.getInsetsController(activity.window, activity.window.decorView)
}
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
Column(
modifier = Modifier
.fillMaxSize()
.padding(if (isPortrait) 16.dp else 5.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Create Lobby",
fontSize = (if(isPortrait) 30.sp else 0.sp),
color = Color.White,
)
Spacer(modifier = Modifier.height(if(isPortrait)25.dp else 0.dp))
Column(
modifier = Modifier.background(Colors.White, shape = RoundedCornerShape(8.dp)),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = lobbyName,
onValueChange = { viewModel.updateLobbyName(it) },
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 4.dp),
shape = RoundedCornerShape(8.dp),
label = { Text("Lobby Name") }
)
TextField(
value = password,
onValueChange = { viewModel.updatePassword(it) },
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 4.dp),
shape = RoundedCornerShape(8.dp),
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Password)
)
TextField(
value = nbPlayers,
onValueChange = { viewModel.updateNbPlayers(it) },
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 4.dp),
label = { Text("Number of Players") },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
Box{
TextField(
value = difficulty,
onValueChange = { viewModel.updateDifficulty(it) },
readOnly = true,
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = Colors.Black,
disabledLabelColor = Colors.Black,
disabledTrailingIconColor = Colors.Black,
disabledIndicatorColor = Color.Black
),
modifier = Modifier
.clickable(onClick = {
viewModel.updateExpandedDifficulty(!expandedDifficulty)
})
.onGloballyPositioned {
viewModel.updateSize(it.size)
}
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 4.dp),
label = { Text("Difficulté") },
trailingIcon = {
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
Modifier.clickable {
viewModel.updateExpandedDifficulty(!expandedDifficulty)
}
)
}
)
DropdownMenu(
expanded = expandedDifficulty,
onDismissRequest = { viewModel.updateExpandedDifficulty(false) },
modifier = Modifier
.width(with(LocalDensity.current) { size.width.toDp() })
.padding(if (isPortrait) 8.dp else 4.dp)
) {
difficultyOptions.forEach { option ->
DropdownMenuItem(
text = { Text(option, color = Colors.Black) },
onClick = {
viewModel.updateDifficulty(option)
viewModel.updateExpandedDifficulty(false)
keyboardController?.hide()
}
)
}
}
}
Box{
val chapterOptions = ControllerChapter.getChapters()
TextField(
value = chapter,
onValueChange = { viewModel.updatechapter(it) },
readOnly = true,
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = Colors.Black,
disabledLabelColor = Colors.Black,
disabledTrailingIconColor = Colors.Black,
disabledIndicatorColor = Color.Black
),
modifier = Modifier
.clickable(onClick = {
viewModel.updateExpandedChapter(!expandedChapter)
})
.onGloballyPositioned {
viewModel.updateSize(it.size)
}
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 4.dp),
label = { Text("Chapitre") },
trailingIcon = {
Icon(
Icons.Default.ArrowDropDown,
contentDescription = null,
Modifier.clickable {
viewModel.updateExpandedChapter(!expandedChapter)
}
)
}
)
DropdownMenu(
expanded = expandedChapter,
onDismissRequest = { viewModel.updateExpandedChapter(false) },
modifier = Modifier
.width(with(LocalDensity.current) { size.width.toDp() })
.padding(if (isPortrait) 8.dp else 4.dp)
) {
chapterOptions?.forEach { option ->
DropdownMenuItem(
text = { Text(option.name, color = Colors.Black) },
onClick = {
viewModel.updatechapter( option.name )
viewModel.updateExpandedChapter(false)
keyboardController?.hide()
}
)
}
}
}
}
Button(
onClick = {
// Handle button click (Create Lobby)
val selectedChapter = ControllerChapter.getChapters()?.find { it.name == chapter }
val difficultyNum = difficultyOptions.indexOf(difficulty) + 1
if (lobbyName.isBlank() || nbPlayers.isBlank() || nbPlayers.toInt() <= 0 || difficulty.isBlank() || selectedChapter == null) {
// Show error message if any field is invalid
Toast.makeText(context, "Invalid input. Please check all fields.", Toast.LENGTH_SHORT).show()
} else {
try {
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
// Add fields to form data
formDataBuilder.addFormDataPart("name", lobbyName)
formDataBuilder.addFormDataPart("password", password)
formDataBuilder.addFormDataPart("nbplayers", nbPlayers)
formDataBuilder.addFormDataPart("idplayercreator", MainActivity.idPlayerConnected.toString())
formDataBuilder.addFormDataPart("idchapter", selectedChapter.id)
formDataBuilder.addFormDataPart("difficulty", difficultyNum.toString())
Log.e("formData : ",formDataBuilder.toString())
// Call createLobby function from ControllerLobby
val lobbyId = ControllerLobby.createLobby(formDataBuilder)
// Check if lobby creation is successful
if (lobbyId != -1) {
val formDataBuilderConnexion = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilderConnexion.addFormDataPart("idplayer", MainActivity.idPlayerConnected.toString())
formDataBuilderConnexion.addFormDataPart("idlobby", lobbyId.toString())
formDataBuilderConnexion.addFormDataPart("playertime", "0")
ControllerUtiliser.createUtiliserByIdLobby(formDataBuilderConnexion)
Toast.makeText(context, "Lobby created successfully!", Toast.LENGTH_SHORT).show()
navController.navigate("serverDetails/${lobbyName}/${lobbyId}/${selectedChapter.id.toInt()}/${nbPlayers}/${difficultyNum}")
} else {
Toast.makeText(context, "Failed to create lobby. Please try again.", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
// Log en cas d'erreur
Log.e("CreateLobby", "Error creating lobby", e)
Toast.makeText(context, "Failed to create lobby. Please try again.", Toast.LENGTH_SHORT).show()
}
}
},
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.padding(if (isPortrait) 8.dp else 4.dp)
) {
Text(text = "Create Lobby", color = Colors.White)
}
}
}

@ -0,0 +1,160 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.NavController
import com.example.mathseduc.ConnexionPlayerActivity
import com.example.mathseduc.MainActivity
import com.example.mathseduc.MultiActivity
import com.example.mathseduc.QuizMultiActivity
import com.example.mathseduc.R
import com.example.mathseduc.ui.theme.Colors
@Composable
fun HomePage(navController: NavController) {
val context = LocalContext.current
val modifier = Modifier
.fillMaxSize()
.padding(16.dp)
val view = LocalView.current
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
val windowInsetsController = remember {
WindowCompat.getInsetsController(window, window.decorView)
}
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = null,
modifier = Modifier
.size(160.dp, 130.dp)
.clip(RoundedCornerShape(8.dp))
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {navController.navigate("themeChoice")},
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Green),
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.height(48.dp)
) {
Text(
text = "Solo",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
if (MainActivity.idPlayerConnected != -1){
navController.navigate("multiplayer")
}
else {
Toast.makeText(context, "Vous n'êtes pas connecté", Toast.LENGTH_SHORT).show()
navController.navigate("connexion")
}
},
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Orange),
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.height(48.dp)
) {
Text(
text = "Multiplayer",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(16.dp))
if(MainActivity.idPlayerConnected != -1){
Button(
onClick = {
navController.navigate("profilePlayer")
},
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Grey),
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.height(48.dp)
.padding(horizontal = 16.dp)
) {
Text(
text = "Profile",
color = Color.White
)
}
}
else {
Button(
onClick = {
navController.navigate("connexion")
},
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Grey),
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.height(48.dp)
.padding(horizontal = 16.dp)
) {
Text(
text = "Connexion",
color = Color.White
)
}
}
}
}

@ -0,0 +1,344 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerChapter
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerUtiliser
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.ui.theme.Colors
import com.example.mathseduc.viewModel.MultiPageViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MultipartBody
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MultiPage(navController: NavController) {
val context = LocalContext.current
val activity = LocalView.current.context as Activity
val viewModel = ViewModelProvider(navController.getViewModelStoreOwner(navController.graph.id)).get(MultiPageViewModel::class.java)
val lobbyList by viewModel.lobbyList.collectAsState(initial = emptyList())
var selectedItem : Lobby? = null
val scope = rememberCoroutineScope()
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
WindowCompat.setDecorFitsSystemWindows(activity.window, false)
val windowInsetsController = remember {
WindowCompat.getInsetsController(activity.window, activity.window.decorView)
}
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide the status bar and navigation bar
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
// Fonction pour actualiser la liste des lobbies
suspend fun refreshLobbyList() {
viewModel.updateLobbyList()
}
DisposableEffect(Unit) {
val refreshRunnable = {
scope.launch(Dispatchers.IO) {
try {
refreshLobbyList()
} catch (e: Exception) {
Log.e("MainActivity", "Error refreshing lobbies: ${e.message}")
}
}
}
refreshRunnable.invoke()
onDispose {
}
}
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {},
navigationIcon = {
IconButton(
onClick = { navController.navigate("home") },
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
modifier = Modifier.size(36.dp),
tint = Color.White
)
}
},
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, top = 62.dp, end = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Liste des lobbies",
color = Colors.White,
fontSize = 17.sp,
modifier = Modifier
.weight(if (isPortrait) 1f else 0.6f)
.padding(start = 25.dp)
)
Button(
onClick = {
navController.navigate("createLobby")
},
shape = RoundedCornerShape(15),
modifier = Modifier
.weight(if (isPortrait) 1f else 0.4f)
.padding(end = 20.dp),
colors = ButtonDefaults.buttonColors(Colors.Blue)
) {
Text(text = "Ajouter un lobby")
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.border(2.dp, Color.White, shape = RoundedCornerShape(3))
.weight(0.85f),
) {
items(lobbyList) { lobby ->
LobbyItem(lobby, selectedItem == lobby) {
selectedItem = it
if (viewModel.getNbPlayerInLobby(selectedItem!!) < selectedItem!!.nbplayers) {
// Créer un Utiliser si le lobby n'est pas plein
scope.launch {
createUtiliserForLobby(context, selectedItem!!, navController)
}
} else {
// Afficher un message Toast si le lobby est plein
Toast.makeText(
context,
"Oh nan, le serveur est déjà plein ! Réessayer plus tard.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
Button(
onClick = {
scope.launch {
refreshLobbyList()
}
},
shape = RoundedCornerShape(15),
modifier = Modifier
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(Colors.Blue)
) {
Text(text = "Actualiser")
}
}
}
private suspend fun createUtiliserForLobby(context: Context, lobby: Lobby, navController: NavController) {
withContext(Dispatchers.IO) {
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilder.addFormDataPart("idplayer", MainActivity.idPlayerConnected.toString())
formDataBuilder.addFormDataPart("idlobby", lobby.id.toString())
formDataBuilder.addFormDataPart("playertime", "0")
ControllerUtiliser.createUtiliserByIdLobby(formDataBuilder)
// Naviguer vers l'activité ServerDetails avec les détails du lobby
val mainScope = MainScope()
mainScope.launch {
navController.navigate("serverDetails/${lobby.name}/${lobby.id}/${lobby.idchapter}/${lobby.nbplayers}/${lobby.difficulty}")
}
}
}
@Composable
fun LobbyItem(lobby: Lobby, isSelected: Boolean, onItemClick: (Lobby) -> Unit) {
val context = LocalContext.current
var showDialog by rememberSaveable { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable {
if (lobby.password.isNotEmpty()) {
showDialog = true
} else {
onItemClick(lobby)
}
}
.clip(RoundedCornerShape(8.dp))
.background(if (isSelected) Colors.Orange else Colors.Grey)
.padding(16.dp)
) {
Text(
text = lobby.name,
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier.weight(1f)
)
Text(
text = "${ControllerLobby.getNbPlayerInLobby(lobby.id)}/${lobby.nbplayers}",
fontSize = 18.sp,
color = (if(ControllerLobby.getNbPlayerInLobby(lobby.id)==lobby.nbplayers) Color.Red else Color.White),
modifier = Modifier.weight(1f)
)
Text(
text = ControllerChapter.getChapterNameById(lobby.idchapter).toString(),
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier.weight(3f)
)
Text(
text = lobby.difficulty.toString(),
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier.weight(1f)
)
if (showDialog) {
ConfirmDialog(
onDismiss = { showDialog = false },
onConfirm = { password ->
println("Password input : $password")
if (password == lobby.password) {
showDialog = false
onItemClick(lobby)
} else {
Toast.makeText(context, "Wrong password!", Toast.LENGTH_SHORT).show()
}
}
)
}
}
}
@Composable
fun ConfirmDialog(onDismiss: () -> Unit, onConfirm: (String) -> Unit) {
val context = LocalContext.current
var password by remember { mutableStateOf("") }
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = { Text("Enter Password") },
confirmButton = {
Button(
onClick = {
onConfirm(password)
}
) {
Text("Confirm")
}
},
dismissButton = {
Button(
onClick = {
onDismiss()
}
) {
Text("Dismiss")
}
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Password
),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
)
}

@ -0,0 +1,320 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.NavController
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerPlayer
import com.example.mathseduc.ui.theme.Colors
import okhttp3.MultipartBody
import org.mindrot.jbcrypt.BCrypt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProfilePlayerPage(navController: NavController) {
val activity = LocalView.current.context as Activity
val player = ControllerPlayer.getPlayerInfoById(MainActivity.idPlayerConnected.toString())
var showDialog by rememberSaveable { mutableStateOf(false) }
var confirmDialog by rememberSaveable { mutableStateOf(false) }
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
WindowCompat.setDecorFitsSystemWindows(activity.window, false)
val windowInsetsController = remember {
WindowCompat.getInsetsController(activity.window, activity.window.decorView)
}
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide the status bar and navigation bar
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {},
navigationIcon = {
IconButton(
onClick = { navController.navigate("home") },
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
modifier = Modifier.size(36.dp),
tint = Color.White
)
}
},
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(14.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.padding(5.dp)
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.background(Color.White, RoundedCornerShape(16.dp))
.padding(vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Profile",
fontSize = 25.sp,
color = Color.Black,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(10.dp))
Divider(
color = Color.Gray,
thickness = 2.dp,
modifier = Modifier.padding(horizontal = 10.dp)
)
Spacer(modifier = Modifier.height(10.dp))
if (player != null) {
Text(
text = "Nickname : ${player.nickname}",
fontSize = 19.sp,
color = Colors.Black
)
}
Spacer(modifier = Modifier.height(20.dp))
Button(
onClick = { showDialog = true },
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Blue),
modifier = Modifier
.height(48.dp)
.padding(horizontal = 6.dp)
) {
Text(
text = "Change Information",
color = Color.White
)
}
Spacer(modifier = Modifier.height(10.dp))
Button(
onClick = { MainActivity.idPlayerConnected = -1
navController.navigate("home") },
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Blue),
modifier = Modifier
.height(48.dp)
.padding(horizontal = 6.dp)
) {
Text(
text = "disconnection",
color = Color.White
)
}
Spacer(modifier = Modifier.height(10.dp))
Button(
onClick = { confirmDialog = true },
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Red),
modifier = Modifier
.height(48.dp)
.padding(horizontal = 16.dp)
) {
Text(
text = "Delete Account",
color = Color.White
)
}
if (showDialog) {
if (player != null) {
ChangeDialog(player.id.toString(),player.nickname,onDismiss = { showDialog = false })
}
}
if (confirmDialog) {
if (player != null) {
DeleteConfirmDialog(onConfirmDelete = { ControllerPlayer.deletePlayer(player.id.toString())
MainActivity.idPlayerConnected = -1
navController.navigate("home")
confirmDialog = false},onDismiss = { confirmDialog = false })
}
}
}
}
}
@Composable
fun ChangeDialog(playerId: String, currentNickname: String, onDismiss: () -> Unit) {
var nickname by rememberSaveable { mutableStateOf(currentNickname) }
var password by rememberSaveable { mutableStateOf("") }
var nicknameError by remember { mutableStateOf(false) }
var passwordError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = { Text("Change Information") },
confirmButton = {
Button(
colors = ButtonDefaults.buttonColors(Colors.Cyan),
onClick = {
if (nickname.isNotBlank() && password.isNotBlank()) {
val hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt())
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilder.addFormDataPart("nickname", nickname)
formDataBuilder.addFormDataPart("password", hashedPassword)
ControllerPlayer.updatePlayer(playerId, formDataBuilder)
onDismiss()
} else {
if (nickname.isBlank()) nicknameError = true
if (password.isBlank()) passwordError = true
}
}
) {
Text("Save")
}
},
dismissButton = {
Button(
colors = ButtonDefaults.buttonColors(Colors.BlackGrey),
onClick = {
onDismiss()
}
) {
Text("Dismiss")
}
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
OutlinedTextField(
value = nickname,
onValueChange = {
nickname = it
nicknameError = false
},
label = { Text("Nickname") },
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
isError = nicknameError
)
OutlinedTextField(
value = password,
onValueChange = {
password = it
passwordError = false
},
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Password
),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
isError = passwordError
)
if (nicknameError || passwordError) {
Text(
text = "Please fill in all fields",
color = Color.Red,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
)
}
@Composable
fun DeleteConfirmDialog(onConfirmDelete: () -> Unit, onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Confirm deleting account") },
confirmButton = {
Button(
onClick = {
onConfirmDelete()
},
colors = ButtonDefaults.buttonColors(Colors.Red)
) {
Text("Delete",color = Color.White)
}
},
dismissButton = {
Button(
onClick = {
onDismiss()
},
colors = ButtonDefaults.buttonColors(Color.LightGray)
) {
Text("Cancel",color = Color.White)
}
},
text = {
Text("Are you sure you want to delete your account?")
}
)
}

@ -0,0 +1,223 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerQuestion
import com.example.mathseduc.ui.theme.Colors
import com.example.mathseduc.viewModel.QuizMultiViewModel
import kotlinx.coroutines.channels.ticker
import okhttp3.MultipartBody
@Composable
fun QuizMultiScreen(navController: NavController, lobbyId: Int?) {
val context = LocalContext.current
val activity = LocalView.current.context as Activity
val viewModel = ViewModelProvider(navController.getViewModelStoreOwner(navController.graph.id)).get(QuizMultiViewModel::class.java)
val chronoValue by viewModel.chronoValue.collectAsState(0.0f)
val listQuestion by viewModel.listQuestion.collectAsState(ControllerQuestion.getQuestionsForLobby(ControllerLobby.getIdQuestionsLobby(lobbyId!!)))
val listPlayer by viewModel.listPlayer.collectAsState(ControllerLobby.getPlayerInLobby(lobbyId!!))
val currentQuestionIndex by viewModel.currentQuestionIndex.collectAsState(0)
val progressBarValues by viewModel.progressBarValues.collectAsState(List(listPlayer.size) { 0.0f })
val progressBarTotalValues by viewModel.progressBarTotalValues.collectAsState(List(listPlayer.size) { 0.0f })
val quizFinished by viewModel.quizFinished.collectAsState(false)
val windowInsetsController by viewModel.windowInsetsController.collectAsState(WindowCompat.getInsetsController(activity.window, activity.window.decorView))
windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController?.hide(WindowInsetsCompat.Type.systemBars())
viewModel.updateListQuestion(lobbyId)
viewModel.updateListPlayer(lobbyId)
viewModel.initializeProgressBar()
viewModel.initializeProgressBarTotal()
LaunchedEffect(Unit) {
val timer = ticker(delayMillis = 1000) // Update every 1000 milliseconds
for (tick in timer) {
if (quizFinished) break
for ((index, player) in listPlayer.withIndex()) {
viewModel.updateProgressBarValues(index)
}
viewModel.updateChronoValue(1f)
if (chronoValue >= 30f) {
viewModel.updateCurrentQuestionIndex()
viewModel.resetChronoValue()
}
}
}
LaunchedEffect(Unit) {
val timer = ticker(delayMillis = 3000)
for (tick in timer) {
if (quizFinished) break
var valueBD = ControllerLobby.getPlayerInLobby(lobbyId!!)
for ((index, player) in listPlayer.withIndex()) {
viewModel.updateProgressBarTotalValues(index,valueBD)
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
for ((index, player) in listPlayer.withIndex()) {
CustomProgressBar(progressBarTotalValues.toMutableList()[index])
}
// Chrono ProgressBar
ChronoProgressBar(chronoValue)
if (currentQuestionIndex < listQuestion!!.size) {
val currentQuestion = listQuestion!![currentQuestionIndex]
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Row(
modifier = Modifier
.weight(2f)
.padding(vertical = 20.dp)
) {
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.weight(5f)
.background(color = Colors.Grey, shape = RoundedCornerShape(8.dp))
) {
Text(
text = currentQuestion.content,
modifier = Modifier.padding(16.dp),
color = Colors.White,
fontSize = 20.sp,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.weight(1f))
}
Column(modifier = Modifier.weight(1f)) {
currentQuestion.answers.forEachIndexed { index, answer ->
val buttonBackgroundColor = when (index) {
0 -> Colors.Red
1 -> Colors.Blue
2 -> Colors.Green
else -> Colors.Cyan
}
Button(
onClick = {
if (answer.id == currentQuestion.idanswergood) {
Toast.makeText(context, "Réussi !!", Toast.LENGTH_SHORT).show()
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
val playertime = ControllerLobby.getLobbyUtiliserPlayerTime(lobbyId!!,MainActivity.idPlayerConnected) + 10
formDataBuilder.addFormDataPart("playertime", playertime.toString())
ControllerLobby.updateLobbyUtiliserPlayerTime(lobbyId,MainActivity.idPlayerConnected,formDataBuilder)
} else {
Toast.makeText(context, "Raté !!", Toast.LENGTH_SHORT).show()
}
viewModel.resetChronoValue()
viewModel.updateCurrentQuestionIndex()
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(buttonBackgroundColor)
) {
Text(text = answer.content)
}
}
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(vertical = 10.dp)
) {
Button(
onClick = {
viewModel.updateCurrentQuestionIndex()
},
modifier = Modifier
.weight(2f),
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Grey),
) {
Text(text = "Passer")
}
}
} else {
Toast.makeText(context, "Fini !!", Toast.LENGTH_SHORT).show()
navController.navigate("home")
viewModel.updateQuizFinished(true)
}
}
}
@Composable
fun CustomProgressBar(progressBarValue: Float) {
LinearProgressIndicator(
progress = progressBarValue / 100f,
modifier = Modifier.fillMaxWidth()
)
}
@Composable
fun ChronoProgressBar(chronoValue: Float) {
CircularProgressIndicator(
progress = chronoValue / 30f,
modifier = Modifier.padding(horizontal = 10.dp)
)
}

@ -0,0 +1,291 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.content.res.Configuration
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.NavController
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerUtiliser
import com.example.mathseduc.ui.theme.Colors
import com.example.mathseduc.viewModel.ServerDetailsViewModel
import okhttp3.MultipartBody
@Composable
fun LeaveLobbyDialog(namelobby: String,lobbyId : Int?, onConfirmLeave: () -> Unit, onCancelLeave: () -> Unit) {
val context = LocalContext.current
AlertDialog(
onDismissRequest = onCancelLeave,
title = { Text("Confirm leaving lobby") },
confirmButton = {
Button(
onClick = {
if (lobbyId != null) {
myBackPressed(lobbyId)
}
onConfirmLeave()
}
) {
Text("Leave")
}
},
dismissButton = {
Button(
onClick = {
onCancelLeave()
}
) {
Text("Cancel")
}
},
text = {
Text("Are you sure you want to leave the lobby $namelobby?")
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ServerDetailPage(navController: NavController, serverName: String?, lobbyId: Int?, chapterId: Int?, nbPlayers: Int?, lobbyDifficulty: Int?) {
val context = LocalContext.current
val viewModelStoreOwner = LocalContext.current as ViewModelStoreOwner
val viewModel = ViewModelProvider(viewModelStoreOwner).get(ServerDetailsViewModel::class.java)
val playerList by viewModel.playerList.collectAsState(initial = emptyList())
val playerListInfos by viewModel.playerListInfos.collectAsState(initial = emptyList())
val isCreator by viewModel.isCreator.collectAsState(false)
val refreshState by viewModel.refreshState.collectAsState(true)
val showDialog by viewModel.showDialog.collectAsState(false)
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
val activity = LocalView.current.context as Activity
val windowInsetsController = remember {
WindowCompat.getInsetsController(activity.window, activity.window.decorView)
}
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
DisposableEffect(refreshState) {
val handler = Handler(Looper.getMainLooper())
val refreshRunnable = object : Runnable {
override fun run() {
viewModel.updatePlayerList(lobbyId)
viewModel.updatePlayerListInfos()
viewModel.updateIsCreator(lobbyId)
if(viewModel.Launchedlobby(lobbyId)){
/*
val intent = Intent(context, QuizMultiActivity::class.java)
intent.putExtra("serverName", serverName)
intent.putExtra("lobbyId", lobbyId)
intent.putExtra("chapterId", chapterId)
intent.putExtra("nbPlayers", nbPlayers)
intent.putExtra("lobbyDifficulty", lobbyDifficulty)
context.startActivity(intent)
*/
navController.navigate("quizMultiScreen/${lobbyId}")
viewModel.updateRefresh(false)
}
if (refreshState){
handler.postDelayed(this,3000)
Log.e("MainActivity", "Refresh ServerDetails")
}
}
}
handler.post(refreshRunnable)
onDispose {
viewModel.updateRefresh(false)
handler.removeCallbacks(refreshRunnable)
}
}
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {},
navigationIcon = {
IconButton(
onClick = { viewModel.updateShowDialog(true) },
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
modifier = Modifier.size(36.dp),
tint = Color.White
)
}
},
)
val modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp, top = 8.dp)
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = serverName!!,
color = Color.White,
fontSize = 30.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(if(isPortrait)25.dp else 10.dp))
Text(
text = "Lobby Settings",
fontSize = 23.sp,
color = Color.White,
fontWeight = FontWeight.Bold
)
Text(
text = "Chapter : ${viewModel.getChapterNameById(chapterId).toString()}",
fontSize = 19.sp,
color = Color.White
)
Row {
Text(
text = "Players : ${playerListInfos.size}/${nbPlayers}",
fontSize = 19.sp,
color = (if(viewModel.getNbPlayerInLobby(lobbyId)==nbPlayers) Color.Red else Color.White)
)
Spacer(modifier = Modifier.width(40.dp))
Text(
text = "Difficulty : $lobbyDifficulty",
fontSize = 19.sp,
color = Colors.White,
)
}
Spacer(modifier = Modifier.height(if(isPortrait)30.dp else 10.dp))
Text(
text = "Player Name",
fontSize = 19.sp,
modifier = Modifier.background(Color.Black),
color = Colors.White,
)
Divider(
color = Color.Gray,
thickness = 2.dp,
modifier = Modifier.fillMaxWidth()
)
LazyColumn(
modifier = Modifier
.fillMaxSize()
.weight(0.75f)
.padding(top = 1.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
items(playerListInfos) { player ->
Text(
text = player.nickname,
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier
.weight(1f)
.padding(top = if (isPortrait) 5.dp else 1.dp)
)
}
}
if (isCreator) {
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilder.addFormDataPart("launched", "1")
Button(
onClick = {
viewModel.updateLobbyLauched(lobbyId,formDataBuilder)
},
shape = RoundedCornerShape(15),
enabled = isCreator,
colors = ButtonDefaults.buttonColors(Colors.Green),
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
) {
Text(
text = "LAUNCH",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
}
if (showDialog) {
LeaveLobbyDialog(serverName,lobbyId, onConfirmLeave = {navController.navigate("multiplayer")},onCancelLeave = { viewModel.updateShowDialog(false) })
}
}
}
private fun myBackPressed(lobbyId: Int) {
ControllerUtiliser.DeleteUtiliserForLobby(MainActivity.idPlayerConnected, lobbyId)
if (ControllerLobby.playerCreatorIdPresentInLobby(MainActivity.idPlayerConnected, lobbyId)) {
val idNextPlayerCreator = ControllerUtiliser.getIdNextPlayerInLobby(lobbyId)
if (idNextPlayerCreator == -1) {
ControllerLobby.deleteLobby(lobbyId)
} else {
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilder.addFormDataPart("idplayercreator", idNextPlayerCreator.toString())
ControllerLobby.updateLobbyIdCreatorLobby(lobbyId, formDataBuilder)
}
}
}

@ -0,0 +1,29 @@
package com.example.mathseduc.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
object Colors {
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val Teal700 = Color(0xFF018786)
val Black = Color(0xFF000000)
val White = Color(0xFFFFFFFF)
val Green = Color(0xFF008000)
val Orange = Color(0xFFFFA500)
val Grey = Color(0xFF8C92AC)
val Blue = Color(0xFF0D6EFD)
val Red = Color(0xFFFF0000)
val BlackGrey = Color(0xFF5C636A)
val Cyan = Color(0xFF00FFFF)
val Turquoise = Color(0xFF40E0D0)
}

@ -0,0 +1,79 @@
package com.example.mathseduc.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.core.view.WindowCompat
import com.example.mathseduc.R
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun MathsEducTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

@ -0,0 +1,34 @@
package com.example.mathseduc.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

@ -0,0 +1,214 @@
package com.example.mathseduc.ui
import android.app.Activity
import android.content.res.Configuration
import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.NavController
import com.example.mathseduc.controllers.ControllerChapter
import com.example.mathseduc.ui.theme.Colors
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ThemeChoicePage(navController: NavController){
var selectedDifficulty by rememberSaveable { mutableStateOf("") }
var selectedChapter by rememberSaveable { mutableStateOf("") }
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
val activity = LocalView.current.context as Activity
val chapterOptions = ControllerChapter.getChapters()
val windowInsetsController = remember {
WindowCompat.getInsetsController(activity.window, activity.window.decorView)
}
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {},
navigationIcon = {
IconButton(
onClick = { navController.navigate("home") },
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
modifier = Modifier.size(36.dp),
tint = Color.White
)
}
},
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(14.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Difficulty :",
fontSize = 25.sp,
color = Color.White,
)
Spacer(modifier = Modifier.height(15.dp))
Row {
Button(
onClick = { selectedDifficulty = "1"},
shape = RoundedCornerShape(20),
colors = ButtonDefaults.buttonColors(if (selectedDifficulty == "1") Colors.Green else Color.Transparent),
modifier = Modifier
.height(60.dp)
.border(
width = 1.dp,
color = Colors.Green,
shape = RoundedCornerShape(20)
)
) {
Text(
text = "Easy",
color = Color.White,
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.width(if(isPortrait) 15.dp else 20.dp))
Button(
onClick = { selectedDifficulty = "2"},
shape = RoundedCornerShape(20),
colors = ButtonDefaults.buttonColors(if (selectedDifficulty == "2") Colors.Orange else Color.Transparent),
modifier = Modifier
.height(60.dp)
.border(
width = 1.dp,
color = Colors.Orange,
shape = RoundedCornerShape(20)
)
) {
Text(
text = "Medium",
color = Color.White,
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.width(if(isPortrait) 15.dp else 20.dp))
Button(
onClick = { selectedDifficulty = "3"},
shape = RoundedCornerShape(20),
colors = ButtonDefaults.buttonColors(if (selectedDifficulty == "3") Colors.Red else Color.Transparent),
modifier = Modifier
.height(60.dp)
.border(
width = 1.dp,
color = Colors.Red,
shape = RoundedCornerShape(20)
)
) {
Text(
text = "Hard",
color = Color.White,
fontSize = 20.sp
)
}
}
Spacer(modifier = Modifier.height(if(isPortrait) 25.dp else 30.dp))
Text(
text = "Chapter :",
fontSize = 25.sp,
color = Color.White,
)
Row(
horizontalArrangement = Arrangement.spacedBy(15.dp),
modifier = Modifier
.padding(vertical = 8.dp)
.horizontalScroll(rememberScrollState())
) {
if (chapterOptions != null) {
chapterOptions.forEach { chapter ->
Button(
onClick = { selectedChapter = chapter.name },
shape = RoundedCornerShape(10),
modifier = Modifier
.padding(vertical = 5.dp)
.border(
width = 1.dp,
color = Color.Gray,
shape = RoundedCornerShape(10)
),
colors = ButtonDefaults.buttonColors(if (selectedChapter == chapter.name) Color.Gray else Color.Transparent)
) {
Text(text = chapter.name, modifier = Modifier.padding(horizontal = 1.dp, vertical = 8.dp))
}
}
}
}
Spacer(modifier = Modifier.height(if(isPortrait) 15.dp else 20.dp))
Button(
onClick = {},
shape = RoundedCornerShape(15),
colors = ButtonDefaults.buttonColors(Colors.Green),
modifier = Modifier
.fillMaxWidth(if (isPortrait) 1f else 0.5f)
.height(48.dp)
) {
Text(
text = "Start Game",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
}
}

@ -0,0 +1,77 @@
package com.example.mathseduc.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.IntSize
import androidx.lifecycle.ViewModel
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerChapter
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerPlayer
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.models.Player
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import okhttp3.MultipartBody
class CreateLobbyViewModel : ViewModel() {
private var lobbyNameState = MutableStateFlow("")
val lobbyName : StateFlow<String> = lobbyNameState
private var passwordState = MutableStateFlow("")
val password: StateFlow<String> = passwordState
private var nbPlayersState = MutableStateFlow("")
val nbPlayers: StateFlow<String> = nbPlayersState
private var difficultyState = MutableStateFlow("")
val difficulty: StateFlow<String> = difficultyState
private var chapterState = MutableStateFlow("")
val chapter : StateFlow<String> = chapterState
private var expandedDifficultyState = MutableStateFlow(false)
val expandedDifficulty: StateFlow<Boolean> = expandedDifficultyState
private var expandedChapterState = MutableStateFlow(false)
val expandedChapter: StateFlow<Boolean> = expandedChapterState
private var sizeState = MutableStateFlow(IntSize.Zero)
val size : StateFlow<IntSize> = sizeState
fun updateLobbyName(lobbyName : String) {
this.lobbyNameState.update { lobbyName }
}
fun updatePassword(password : String) {
this.passwordState.update { password }
}
fun updateNbPlayers(nbPlayers : String) {
this.nbPlayersState.update { nbPlayers }
}
fun updateDifficulty(difficulty : String) {
this.difficultyState.update { difficulty }
}
fun updatechapter(chapter : String) {
this.chapterState.update { chapter }
}
fun updateExpandedChapter(expandedChapter : Boolean) {
this.expandedChapterState.update { expandedChapter }
}
fun updateExpandedDifficulty(expandedDifficulty : Boolean) {
this.expandedDifficultyState.update { expandedDifficulty }
}
fun updateSize(size : IntSize) {
this.sizeState.update { size }
}
}

@ -0,0 +1,29 @@
package com.example.mathseduc.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerPlayer
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.models.Player
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
class MultiPageViewModel : ViewModel() {
private var lobbyListState = MutableStateFlow<List<Lobby>>(emptyList())
val lobbyList : StateFlow<List<Lobby>> = lobbyListState
fun updateLobbyList() {
this.lobbyListState.update {
ControllerLobby.getLobbies() ?: emptyList()
}
}
fun getNbPlayerInLobby(selectedItem : Lobby) : Int {
return ControllerLobby.getNbPlayerInLobby(selectedItem!!.id)
}
}

@ -0,0 +1,93 @@
package com.example.mathseduc.viewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModel
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerQuestion
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.models.Player
import com.example.mathseduc.models.Question
import com.example.mathseduc.models.Utiliser
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.count
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.withIndex
import kotlin.coroutines.CoroutineContext
class QuizMultiViewModel : ViewModel() {
private var chronoValueState = MutableStateFlow(0.0f)
val chronoValue : StateFlow<Float> = chronoValueState
private var listQuestionState = MutableStateFlow<List<Question>?>(emptyList())
val listQuestion : StateFlow<List<Question>?> = listQuestionState
private var listPlayerState = MutableStateFlow<List<Utiliser>>(emptyList())
val listPlayer : StateFlow<List<Utiliser>> = listPlayerState
private var currentQuestionIndexState = MutableStateFlow(0)
val currentQuestionIndex : StateFlow<Int> = currentQuestionIndexState
private var progressBarValuesState = MutableStateFlow<List<Float>>(emptyList())
val progressBarValues : StateFlow<List<Float>> = progressBarValuesState
private var progressBarTotalValuesState = MutableStateFlow<List<Float>>(emptyList())
val progressBarTotalValues : StateFlow<List<Float>> = progressBarTotalValuesState
private var quizFinishedState = MutableStateFlow(false)
val quizFinished : StateFlow<Boolean> = quizFinishedState
private var windowInsetsControllerState = MutableStateFlow(null)
val windowInsetsController : StateFlow<WindowInsetsControllerCompat?> = windowInsetsControllerState
fun updateListQuestion(lobbyId : Int?){
this.listQuestionState.update{ ControllerQuestion.getQuestionsForLobby(ControllerLobby.getIdQuestionsLobby(lobbyId!!)) }
}
fun updateListPlayer(lobbyId : Int?){
this.listPlayerState.update{ ControllerLobby.getPlayerInLobby(lobbyId!!) }
}
fun initializeProgressBar(){
this.progressBarValuesState.update{ List(this.listPlayer.value.size) { 0.0f } }
}
fun initializeProgressBarTotal(){
this.progressBarTotalValuesState.update{ List(this.listPlayer.value.size) { 0.0f } }
}
fun updateChronoValue(time : Float){
this.chronoValueState.update { this.chronoValue.value + time }
}
fun resetChronoValue(){
this.chronoValueState.update { 0.0f }
}
fun updateCurrentQuestionIndex(){
this.currentQuestionIndexState.update { this.currentQuestionIndex.value + 1 }
}
fun updateQuizFinished(bool : Boolean){
this.quizFinishedState.update { bool }
}
fun updateProgressBarValues(index : Int){
this.progressBarValuesState.update {
val newList = it.toMutableList()
newList[index] += 1f
newList
}
}
fun updateProgressBarTotalValues(index : Int,valueBD : List<Utiliser>){
this.progressBarTotalValuesState.update {
val newList = it.toMutableList()
newList[index] += this.progressBarValues.value[index] + valueBD[index].playertime.toFloat()
newList
}
}
}

@ -0,0 +1,73 @@
package com.example.mathseduc.viewModel
import androidx.lifecycle.ViewModel
import com.example.mathseduc.MainActivity
import com.example.mathseduc.controllers.ControllerChapter
import com.example.mathseduc.controllers.ControllerLobby
import com.example.mathseduc.controllers.ControllerPlayer
import com.example.mathseduc.models.Lobby
import com.example.mathseduc.models.Player
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import okhttp3.MultipartBody
class ServerDetailsViewModel : ViewModel() {
private var playerListState = MutableStateFlow<List<Int>>(emptyList())
val playerList : StateFlow<List<Int>> = playerListState
private var playerListInfosState = MutableStateFlow<List<Player>>(emptyList())
val playerListInfos: StateFlow<List<Player>> = playerListInfosState
private var isCreatorState = MutableStateFlow(false)
val isCreator: StateFlow<Boolean> = isCreatorState
private var showDialogState = MutableStateFlow(false)
val showDialog: StateFlow<Boolean> = showDialogState
private var refreshStateState = MutableStateFlow(true)
val refreshState : StateFlow<Boolean> = refreshStateState
fun updatePlayerList(lobbyId : Int?) {
this.playerListState.update {
ControllerPlayer.getPlayersIdFromLobbyId(lobbyId.toString()) ?: emptyList()
}
}
fun updatePlayerListInfos() {
this.playerListInfosState.update {
playerList.value.mapNotNull { playerId -> ControllerPlayer.getPlayerInfoById(playerId.toString()) }
}
}
fun updateIsCreator(lobbyId : Int?) {
this.isCreatorState.update {
ControllerLobby.playerCreatorIdPresentInLobby(MainActivity.idPlayerConnected, lobbyId)
}
}
fun updateRefresh(bool : Boolean) {
this.refreshStateState.update { bool }
}
fun updateShowDialog(bool : Boolean) {
this.showDialogState.update { bool }
}
fun Launchedlobby(lobbyId : Int?): Boolean{
return ControllerLobby.lobbyIsLaunched(lobbyId)
}
fun getNbPlayerInLobby(lobbyId : Int?) : Int{
return ControllerLobby.getNbPlayerInLobby(lobbyId)
}
fun getChapterNameById(chapterId: Int?): String?{
return ControllerChapter.getChapterNameById(chapterId)
}
fun updateLobbyLauched(lobbyId : Int?,formDataBuilder: MultipartBody.Builder){
ControllerLobby.updateLobbyLauched(lobbyId,formDataBuilder)
}
}

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

@ -0,0 +1,3 @@
<resources>
<string name="app_name">MathsEduc</string>
</resources>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MathsEduc" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:windowBackground">@drawable/background</item>
</style>
</resources>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

@ -0,0 +1,17 @@
package com.example.mathseduc
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
}

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

@ -0,0 +1,6 @@
#Wed Mar 06 13:56:05 CET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
Android/gradlew vendored

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1,18 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MathsEduc"
include(":app")

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor", "Blazor\Blazor.csproj", "{F9B19564-ED8F-49F7-97D7-2132F92DE3C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{FC6D090C-6922-4F72-8DD2-AE8FEDD773A5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F9B19564-ED8F-49F7-97D7-2132F92DE3C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9B19564-ED8F-49F7-97D7-2132F92DE3C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9B19564-ED8F-49F7-97D7-2132F92DE3C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9B19564-ED8F-49F7-97D7-2132F92DE3C2}.Release|Any CPU.Build.0 = Release|Any CPU
{FC6D090C-6922-4F72-8DD2-AE8FEDD773A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC6D090C-6922-4F72-8DD2-AE8FEDD773A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC6D090C-6922-4F72-8DD2-AE8FEDD773A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC6D090C-6922-4F72-8DD2-AE8FEDD773A5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {75BB0A32-C002-4B33-88B3-421A9369D9CB}
EndGlobalSection
EndGlobal

@ -0,0 +1,14 @@
<CascadingBlazoredModal>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found </PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingBlazoredModal>

@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Pages\Player\**" />
<Content Remove="Pages\Player\**" />
<EmbeddedResource Remove="Pages\Player\**" />
<None Remove="Pages\Player\**" />
</ItemGroup>
<ItemGroup>
<Content Remove="Pages\_Layout.cshtml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Blazored.LocalStorage" Version="4.4.0" />
<PackageReference Include="Blazored.Modal" Version="7.1.0" />
<PackageReference Include="Blazorise.Bootstrap" Version="1.4.0" />
<PackageReference Include="Blazorise.DataGrid" Version="1.4.0" />
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="1.4.0" />
<PackageReference Include="ChoETL.JSON.NETStandard" Version="1.2.1.64" />
<PackageReference Include="ChoETL.NETStandard" Version="1.2.1.64" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="8.0.0" />
<PackageReference Include="SuperConvert" Version="1.0.4.9" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Pages\_Layout.cshtml" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Pages\_Layout.cshtml" />
</ItemGroup>
<ItemGroup>
<None Include="Pages\_Layout.cshtml" />
</ItemGroup>
</Project>

@ -0,0 +1 @@
<button type="button" class="btn btn-primary mb-2" @onclick="Back">@Localizer["Back"]</button>

@ -0,0 +1,23 @@
using Blazor.Pages.Questions;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace Blazor.Components
{
public partial class BackButton
{
[Parameter]
public string RedirectionPage { get; set; }
[Inject]
public IStringLocalizer<BackButton> Localizer { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
private void Back()
{
NavigationManager.NavigateTo(RedirectionPage, true);
}
}
}

@ -0,0 +1,29 @@
<BackButton RedirectionPage="/questions"></BackButton>
<div class="text-center pb-2">
<h3>Question n°@Question.Id</h3>
</div>
<div class="text-center pb-5 mt-3">
<h5>@Question.Content</h5>
</div>
<div class="container-fluid text-center justify-content-center row card-body">
@foreach (var answer in Answers)
{
<div class="col-3 text-center">
@if (answer.Id == Question.IdAnswerGood)
{
<p class="text-success"><strong>@answer.Content</strong></p>
}
else
{
<p class="text-danger">@answer.Content</p>
}
</div>
}
</div>
<div class="card-footer text-center text-muted">
Chapitre : @Question.ChapterName
</div>

@ -0,0 +1,15 @@
using Blazor.Pages;
using Blazor.ViewClasses;
using Microsoft.AspNetCore.Components;
namespace Blazor.Components
{
public partial class CardViewQuestion
{
[Parameter]
public Question Question { get; set; }
[Parameter]
public List<Answer> Answers { get; set; }
}
}

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
namespace Blazor.Controllers;
/// <summary>
/// The culture controller.
/// </summary>
[Route("[controller]/[action]")]
public class CultureController : Controller
{
/// <summary>
/// Sets the culture.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="redirectUri">The redirect URI.</param>
/// <returns>
/// The action result.
/// </returns>
public IActionResult SetCulture(string culture, string redirectUri)
{
if (culture != null)
{
// Define a cookie with the selected culture
this.HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return this.LocalRedirect(redirectUri);
}
}

@ -0,0 +1,12 @@
@page "/DeleteConfirmation"
<div class="simple-form">
<p>
@Localizer["Question"]
</p>
<button @onclick="ConfirmDelete" class="btn btn-primary">@Localizer["Delete"]</button>
<button @onclick="Cancel" class="btn btn-secondary">@Localizer["Cancel"]</button>
</div>

@ -0,0 +1,34 @@
using Blazor.Services;
using Blazored.Modal.Services;
using Blazored.Modal;
using Microsoft.AspNetCore.Components;
using Blazor.ViewClasses;
using Microsoft.Extensions.Localization;
namespace Blazor.Modals
{
public partial class DeleteConfirmation
{
[CascadingParameter]
public required BlazoredModalInstance ModalInstance { get; set; }
[Inject]
public IStringLocalizer<DeleteConfirmation> Localizer { get; set; }
[Inject]
public required IDataService DataService { get; set; }
[Parameter]
public int Id { get; set; }
void ConfirmDelete()
{
ModalInstance.CloseAsync(ModalResult.Ok(true));
}
void Cancel()
{
ModalInstance.CancelAsync();
}
}
}

@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using Microsoft.AspNetCore.Identity;
using BCrypt.Net;
using System.Security.Cryptography;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Blazor.Models;
public class AdministratorModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Username is required")]
[RegularExpression(@"^[\p{L}0-9]+$", ErrorMessage = "La chaîne doit être composée uniquement de lettres, des chiffres et peut inclure des caractères accentués, sans espaces.")]
[StringLength(20, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 3)]
public string Username { get; set; }
[Required(ErrorMessage = "Password is required")]
[RegularExpression(@"^[^\s]+$", ErrorMessage = "Le champ ne doit pas contenir d'espaces.")]
[StringLength(255, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]
public string HashedPassword { get; set; }
public void HashPassword(string password)
{
this.HashedPassword = BCrypt.Net.BCrypt.HashPassword(password, BCrypt.Net.BCrypt.GenerateSalt());
}
//public void HashPassword(string password)
//{
// using (MD5 md5 = MD5.Create())
// {
// byte[] inputBytes = Encoding.UTF8.GetBytes(password);
// byte[] hashBytes = md5.ComputeHash(inputBytes);
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < hashBytes.Length; i++)
// {
// sb.Append(hashBytes[i].ToString("x2"));
// }
// HashedPassword = sb.ToString();
// }
//}
}

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace Blazor.Models;
public class AnswerModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Content answer is required")]
[StringLength(40, ErrorMessage = "Content answer is too long.")]
public string? Content { get; set; }
public int? IdQuestion { get; set; }
public AnswerModel(int id)
{
Id = id;
}
public AnswerModel(){}
}

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Blazor.Models
{
public class ChapterModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Name is required")]
[StringLength(50, ErrorMessage = "Name is too long.")]
public string Name { get; set; }
}
}

@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
namespace Blazor.Models;
public class PlayerModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Nickname is required")]
[RegularExpression(@"^[\p{L}0-9]+$", ErrorMessage = "La chaîne doit être composée uniquement de lettres, des chiffres et peut inclure des caractères accentués, sans espaces.")]
[StringLength(20, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 3)]
public string Nickname { get; set; }
[Required(ErrorMessage = "Password is required")]
[RegularExpression(@"^[^\s]+$", ErrorMessage = "Le champ ne doit pas contenir d'espaces.")]
[StringLength(255, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]
public string HashedPassword { get; set; }
public void HashPassword(string password)
{
this.HashedPassword = BCrypt.Net.BCrypt.HashPassword(password, BCrypt.Net.BCrypt.GenerateSalt());
}
//public void HashPassword(string password)
//{
// using (MD5 md5 = MD5.Create())
// {
// byte[] inputBytes = Encoding.UTF8.GetBytes(password);
// byte[] hashBytes = md5.ComputeHash(inputBytes);
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < hashBytes.Length; i++)
// {
// sb.Append(hashBytes[i].ToString("x2"));
// }
// HashedPassword = sb.ToString();
// }
//}
}

@ -0,0 +1,20 @@
namespace Blazor.Models;
using System.ComponentModel.DataAnnotations;
public class QuestionModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Content question is required")]
[StringLength(255, ErrorMessage = "Content question is too long.")]
public string Content { get; set; }
[Required(ErrorMessage = "Chapter is required")]
public int IdChapter { get; set; }
public int IdAnswerGood { get; set; }
public int Difficulty { get; set; }
public int NbFails { get; set; }
public void addFails(int nb) { NbFails += nb; }
public void removeFails(int nb) { NbFails -= nb; }
}

@ -0,0 +1,7 @@
namespace Blazor.Pages;
public static class API
{
public static string TOKEN = "qUOGkWdoPCgbmuqxIC8xiaX0rV1Pw1LoPafkaoHOgszEyD9P2vcOu493xCDZpAqO"; //ASK IF YOU WANT THOSE ACCESS
public static string API_URL = "https://trusting-panini.87-106-126-109.plesk.page/api/"; //ASK IF YOU WANT THOSE ACCESS
}

@ -0,0 +1,35 @@
@page "/addAdministrator"
@using Blazor.Models
@using Blazor.Components
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<BackButton RedirectionPage="/administrators"></BackButton>
<div class="container text-center">
<div class="border border-dark p-4 d-inline-block">
<h3>@Localizer["Title"]</h3>
<EditForm Model="@administratorModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row mb-3">
<div class="col-5 text-end">
<label for="username" class="col-form-label">@Localizer["Username"] :</label>
</div>
<div class="col-7">
<InputText id="username" @bind-Value="administratorModel.Username" class="form-control" />
</div>
</div>
<div class="row mb-3">
<div class="col-5 text-end">
<label for="hashedPassword" class="col-form-label">@Localizer["Password"] :</label>
</div>
<div class="col-7">
<InputText id="hashedPassword" @bind-Value="administratorModel.HashedPassword" class="form-control" type="password"/>
</div>
</div>
<button type="submit" class="btn btn-success mb-2">@Localizer["Submit"]</button>
</EditForm>
</div>
</div>

@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Components;
using Blazor.Models;
using Blazor.Services;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Admins
{
public partial class AddAdministrator
{
private AdministratorModel administratorModel = new();
[Inject]
public IStringLocalizer<AddAdministrator> Localizer { get; set; }
[Inject]
public required IDataService DataService { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
[Inject]
public required ILogger<AddAdministrator> Logger { get; set; }
private async Task HandleValidSubmit()
{
if (administratorModel != null)
{
administratorModel.HashPassword(administratorModel.HashedPassword);
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("username", administratorModel.Username));
formData.Add(new KeyValuePair<string, string>("password", administratorModel.HashedPassword));
var formContent = new FormUrlEncodedContent(formData);
string apiUri = API.API_URL + "add/administrator/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.PostAsync(apiUri, formContent);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
Logger.LogInformation("Admin '{administratorsModelName}' added", administratorModel.Username);
NavigationManager.NavigateTo("administrators");
}
}
}
}

@ -0,0 +1,28 @@
@page "/administrators"
@using Blazorise.DataGrid
@using Blazor.ViewClasses
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<h3>@Localizer["Title"]</h3>
<div>
<NavLink class="btn btn-primary" href="addAdministrator" Match="NavLinkMatch.All">
<i class="fa fa-plus"></i> @Localizer["Add"]
</NavLink>
</div>
<DataGrid TItem="Administrator"
Data="@administrators"
ReadData="@OnReadData"
TotalItems="@totalItem"
PageSize="10"
ShowPager
Responsive>
<DataGridColumn TItem="Administrator" Field="@nameof(Administrator.Id)" Caption="#" />
<DataGridColumn TItem="Administrator" Field="@nameof(Administrator.Username)" Caption="@Localizer["Username"]" />
<DataGridColumn TItem="Administrator" Field="@nameof(Administrator.Id)" Caption="Action">
<DisplayTemplate>
<a href="editAdministrator/@(context.Id)" class="btn btn-primary"><i class="fa fa-edit"></i> @Localizer["Edit"]</a>
<button type="button" class="btn btn-danger" @onclick="() => OnDelete(context.Id)"><i class="fa fa-trash"></i> @Localizer["Delete"]</button>
</DisplayTemplate>
</DataGridColumn>
</DataGrid>

@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Components;
using Blazorise.DataGrid;
using Blazor.ViewClasses;
using Blazor.Modals;
using Blazored.LocalStorage;
using Blazored.Modal.Services;
using Blazor.Services;
using Blazored.Modal;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Admins;
public partial class Administrators
{
public List<Administrator> administrators = new();
private int totalItem;
[CascadingParameter]
public required IModalService Modal { get; set; }
[Inject]
public IStringLocalizer<Administrators> Localizer { get; set; }
[Inject]
public required IDataService DataService { get; set; }
[Inject]
public required IWebHostEnvironment WebHostEnvironment { get; set; }
[Inject]
public required ILocalStorageService LocalStorage { get; set; }
[Inject]
public required HttpClient Http { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
private async Task OnReadData(DataGridReadDataEventArgs<Administrator> e)
{
if (e.CancellationToken.IsCancellationRequested)
{
return;
}
// Calculez l'index de départ pour la pagination
int page = ((e.Page - 1) * e.PageSize) / 10 + 1;
// Envoyez une requête à l'API pour récupérer les questions de la page actuelle
var response = await Http.GetFromJsonAsync<Administrator[]>(API.API_URL + "all/administrators/" + API.TOKEN + "/" + page);
if (!e.CancellationToken.IsCancellationRequested && response != null)
{
administrators = new List<Administrator>(response); // an actual data for the current page
totalItem = response[0].total_administrators;
}
}
private async void OnDelete(int id)
{
var parameters = new ModalParameters();
parameters.Add(nameof(Administrator.Id), id);
var modal = Modal.Show<DeleteConfirmation>("Delete Confirmation", parameters);
var result = await modal.Result;
if (result.Cancelled)
{
return;
}
string apiUri = API.API_URL +"delete/administrator/" + id + "/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.DeleteAsync(apiUri);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
NavigationManager.NavigateTo("administrators", true);
}
}

@ -0,0 +1,35 @@
@page "/editAdministrator/{Id:int}"
@using Blazor.Components;
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<BackButton RedirectionPage="/administrators"></BackButton>
<div class="container text-center">
<div class="border border-dark p-4 d-inline-block">
<h3>@Localizer["Title"]</h3>
<EditForm Model="@administratorModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row mb-3">
<div class="col-5 text-end">
<label for="username" class="col-form-label">@Localizer["Username"] :</label>
</div>
<div class="col-7">
<InputText id="username" @bind-Value="administratorModel.Username" class="form-control"/>
</div>
</div>
<div class="row mb-3">
<div class="col-5 text-end">
<label for="hashedPassword" class="col-form-label">@Localizer["HashedPassword"] :</label>
</div>
<div class="col-7">
<InputText id="hashedPassword" @bind-Value="administratorModel.HashedPassword" class="form-control" type="password"/>
</div>
</div>
<button type="submit" class="btn btn-success mb-2">@Localizer["Submit"]</button>
</EditForm>
</div>
</div>

@ -0,0 +1,80 @@
using Blazor.Models;
using Blazor.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Admins
{
public partial class EditAdministrator
{
[Parameter]
public int Id { get; set; }
private AdministratorModel? administratorModel;
[Inject]
public IStringLocalizer<EditAdministrator> Localizer { get; set; }
[Inject]
public required IDataService DataService { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
[Inject]
public required IWebHostEnvironment WebHostEnvironment { get; set; }
[Inject]
public required ILogger<EditAdministrator> Logger { get; set; }
private string OldAdminName { get; set; } = "";
protected override async Task OnInitializedAsync()
{
var administrator = await DataService.GetAdminById(Id);
OldAdminName = administrator.Username;
administratorModel = new AdministratorModel
{
Id = administrator.Id,
Username = administrator.Username,
HashedPassword = administrator.HashedPassword
};
}
private async Task HandleValidSubmit()
{
if (administratorModel != null)
{
administratorModel.HashPassword(administratorModel.HashedPassword);
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("username", administratorModel.Username));
formData.Add(new KeyValuePair<string, string>("password", administratorModel.HashedPassword));
var formContent = new FormUrlEncodedContent(formData);
string apiUri = API.API_URL + "update/administrator/" + administratorModel.Id + "/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.PostAsync(apiUri, formContent);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
Logger.LogInformation("Admin '{OldAdminModelName}' edited in '{NewAdminModelName}'", OldAdminName, administratorModel.Username);
NavigationManager.NavigateTo("administrators");
}
}
}
}

@ -0,0 +1,29 @@
@page "/addChapter"
@using Blazor.Models
@using Blazor.Components
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<BackButton RedirectionPage="/chapters"></BackButton>
<div class="container text-center">
<div class="border border-dark p-4 d-inline-block">
<h3>@Localizer["Title"]</h3>
<EditForm Model="@chapterModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row mb-3">
<div class="col-4 text-end">
<label for="name" class="col-form-label">@Localizer["Name"] :</label>
</div>
<div class="col-8">
<InputText id="name" @bind-Value="chapterModel.Name" class="form-control" />
</div>
</div>
<button type="submit" class="btn btn-success mb-2">@Localizer["Submit"]</button>
</EditForm>
</div>
</div>

@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Components;
using Blazor.Models;
using Blazor.Services;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Chapters;
public partial class AddChapter
{
private ChapterModel chapterModel = new();
[Inject]
public IStringLocalizer<AddChapter> Localizer { get; set; }
[Inject]
public required IDataService DataService { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
[Inject]
public required ILogger<AddChapter> Logger { get; set; }
private async Task HandleValidSubmit()
{
if (chapterModel != null)
{
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("name", chapterModel.Name));
var formContent = new FormUrlEncodedContent(formData);
string apiUri = API.API_URL + "add/chapter/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.PostAsync(apiUri, formContent);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
Logger.LogInformation("Chapter '{chapterModelName}' added", chapterModel.Name);
NavigationManager.NavigateTo("chapters");
}
}
}

@ -0,0 +1,28 @@
@page "/chapters"
@using Blazor.ViewClasses;
@using Blazorise.DataGrid
@using Blazored.Modal;
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<h3>@Localizer["Title"]</h3>
<div>
<NavLink class="btn btn-primary" href="addChapter" Match="NavLinkMatch.All">
<i class="fa fa-plus"></i> @Localizer["Add"]
</NavLink>
</div>
<DataGrid TItem="Chapter"
Data="@chapters"
ReadData="@OnReadData"
TotalItems="@totalChapter"
PageSize="10"
ShowPager
Responsive>
<DataGridColumn TItem="Chapter" Field="@nameof(Chapter.Id)" Caption="#" />
<DataGridColumn TItem="Chapter" Field="@nameof(Chapter.Name)" Caption="@Localizer["Name"]" />
<DataGridColumn TItem="Chapter" Field="@nameof(Chapter.Id)" Caption="Action">
<DisplayTemplate>
<a href="editChapter/@(context.Id)" class="btn btn-primary"><i class="fa fa-edit"></i> @Localizer["Edit"]</a>
<button type="button" class="btn btn-danger" @onclick="() => OnDelete(context.Id)"><i class="fa fa-trash"></i> @Localizer["Delete"]</button>
</DisplayTemplate>
</DataGridColumn>
</DataGrid>

@ -0,0 +1,92 @@
using Blazored.LocalStorage;
using Blazor.Services;
using Blazored.Modal.Services;
using Blazor.ViewClasses;
using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components;
using Blazorise.DataGrid;
using Blazor.Modals;
using Blazored.Modal;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Chapters;
public partial class Chapters
{
public List<Chapter> chapters;
private int totalChapter;
[Inject]
public IStringLocalizer<Chapters> Localizer { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
[CascadingParameter]
public IModalService Modal { get; set; }
[Inject]
public IDataService DataService { get; set; }
public IWebHostEnvironment WebHostEnvironment { get; set; }
[Inject]
public HttpClient Http { get; set; }
[Inject]
public ILocalStorageService LocalStorage { get; set; }
[Inject]
public IJSRuntime IJSRuntime { get; set; }
private async void OnDelete(int id)
{
var parameters = new ModalParameters();
parameters.Add(nameof(Chapter.Id), id);
var modal = Modal.Show<DeleteConfirmation>("Delete Confirmation", parameters);
var result = await modal.Result;
if (result.Cancelled)
{
return;
}
string apiUri = API.API_URL+"delete/chapter/" + id + "/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.DeleteAsync(apiUri);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
NavigationManager.NavigateTo("chapters", true);
}
private async Task OnReadData(DataGridReadDataEventArgs<Chapter> e)
{
if (e.CancellationToken.IsCancellationRequested)
{
return;
}
// Calculez l'index de départ pour la pagination
int page = ((e.Page - 1) * e.PageSize) / 10 + 1;
// Envoyez une requête à l'API pour récupérer les questions de la page actuelle
var response = await Http.GetFromJsonAsync<Chapter[]>(API.API_URL + "all/chapters/" + API.TOKEN + "/" + page);
if (!e.CancellationToken.IsCancellationRequested)
{
chapters = new List<Chapter>(response); // an actual data for the current page
totalChapter = response[0].total_chapters;
}
}
}

@ -0,0 +1,29 @@
@page "/editChapter/{Id:int}"
@using Blazor.Components;
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<BackButton RedirectionPage="/chapters"></BackButton>
<div class="container text-center">
<div class="border border-dark p-4 d-inline-block">
<h3>@Localizer["Title"]</h3>
<EditForm Model="@chapterModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row mb-3">
<div class="col-4 text-end">
<label for="name" class="col-form-label">@Localizer["Name"] : </label>
</div>
<div class="col-8">
<InputText id="name" @bind-Value="chapterModel.Name" class="form-control" />
</div>
</div>
<button type="submit" class="btn btn-success mb-2">@Localizer["Submit"]</button>
</EditForm>
</div>
</div>

@ -0,0 +1,73 @@
using Blazor.Models;
using Blazor.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Chapters;
public partial class EditChapter
{
[Parameter]
public int Id { get; set; }
private ChapterModel? chapterModel;
[Inject]
public IStringLocalizer<EditChapter> Localizer { get; set; }
[Inject]
public IDataService DataService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
[Inject]
public IWebHostEnvironment WebHostEnvironment { get; set; }
[Inject]
public ILogger<EditChapter> Logger { get; set; }
private string OldChapterName { get; set; }
protected override async Task OnInitializedAsync()
{
var chapter = await DataService.GetById(Id);
OldChapterName = chapter.Name;
chapterModel = new ChapterModel
{
Id = chapter.Id,
Name = chapter.Name
};
}
private async Task HandleValidSubmit()
{
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("name", chapterModel.Name));
var formContent = new FormUrlEncodedContent(formData);
string apiUri = API.API_URL+"update/chapter/" + chapterModel.Id + "/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.PostAsync(apiUri, formContent);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
Logger.LogInformation("Chapter '{OldChapterModelName}' edited in '{NewChapterModelName}'", OldChapterName, chapterModel.Name);
NavigationManager.NavigateTo("chapters");
}
}

@ -0,0 +1,42 @@
@page
@model Blazor.Pages.ErrorModel
<!DOCTYPE html>
<html lang="en">
<PageTitle>Error</PageTitle>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>

@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace Blazor.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

@ -0,0 +1,13 @@
@page "/"
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<div class="container text-center mb-5">
<img src="/images/mathseduc.png" alt="Logo MathsEduc" height="150" />
</div>
<h1 class="text-center">@Localizer["Title"]</h1>
<p class="text-center">@Localizer["Text"]</p>
<button type="button" class="btn btn-primary mb-2" @onclick="CloseApplication">Fermer l'application</button>

@ -0,0 +1,24 @@
using Blazor.Pages.Questions;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages
{
public partial class Index
{
[Inject]
public IStringLocalizer<Index> Localizer { get; set; }
[Inject]
public IHostApplicationLifetime AppLifetime { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
private void CloseApplication()
{
NavigationManager.NavigateTo("http://localhost:8888", forceLoad: true);
AppLifetime.StopApplication();
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save