Compare commits

..

No commits in common. 'master' and 'Drone_Update_Testing' have entirely different histories.

@ -25,13 +25,12 @@ steps:
- name: code-analysis
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7
commands:
- echo $$PLUGIN_SONAR_TOKEN
- echo $$PLUGIN_SONAR_HOST
- 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 ]

3
.gitignore vendored

@ -819,9 +819,8 @@ replay_pid*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# config ignore to not stock password on the git (: neither the Token of the API
# config ignore to not stock password on the git (:
/Website/usages/Config_DB.php
/Blazor/Blazor/Pages/API.cs
### DotnetCore ###
# .NET Core build folders

15
Android/.gitignore vendored

@ -1,15 +0,0 @@
*.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

@ -1 +0,0 @@
/build

@ -1,80 +0,0 @@
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")
}

@ -1,21 +0,0 @@
# 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

@ -1,24 +0,0 @@
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)
}
}

@ -1,35 +0,0 @@
<?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>

@ -1,57 +0,0 @@
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)
}
}
}

@ -1,266 +0,0 @@
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)
)
}
}
}
)
}

@ -1,19 +0,0 @@
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)
}
}
}

@ -1,28 +0,0 @@
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()
}

@ -1,18 +0,0 @@
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)
}
}
}

@ -1,36 +0,0 @@
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()
}
}

@ -1,108 +0,0 @@
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()
}
}

@ -1,72 +0,0 @@
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
}
}
}

@ -1,356 +0,0 @@
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
}
}
}

@ -1,187 +0,0 @@
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
}
}
}

@ -1,79 +0,0 @@
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
}
}
}
}

@ -1,100 +0,0 @@
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
}
}
}

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

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

@ -1,51 +0,0 @@
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)
}
}
}

@ -1,36 +0,0 @@
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)
}
}
}

@ -1,13 +0,0 @@
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>
)

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

@ -1,315 +0,0 @@
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)
}
}
}

@ -1,160 +0,0 @@
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
)
}
}
}
}

@ -1,344 +0,0 @@
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)
)
}
}
)
}

@ -1,320 +0,0 @@
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?")
}
)
}

@ -1,223 +0,0 @@
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)
)
}

@ -1,291 +0,0 @@
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)
}
}
}

@ -1,29 +0,0 @@
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)
}

@ -1,79 +0,0 @@
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
)
}

@ -1,34 +0,0 @@
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
)
*/
)

@ -1,214 +0,0 @@
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
)
}
}
}

@ -1,77 +0,0 @@
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 }
}
}

@ -1,29 +0,0 @@
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)
}
}

@ -1,93 +0,0 @@
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
}
}
}

@ -1,73 +0,0 @@
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)
}
}

@ -1,30 +0,0 @@
<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.

Before

Width:  |  Height:  |  Size: 22 KiB

@ -1,170 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 152 KiB

@ -1,6 +0,0 @@
<?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>

@ -1,6 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

@ -1,10 +0,0 @@
<?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>

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

@ -1,7 +0,0 @@
<?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>

@ -1,13 +0,0 @@
<?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>

@ -1,19 +0,0 @@
<?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>

@ -1,17 +0,0 @@
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)
}
}

@ -1,5 +0,0 @@
// 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
}

@ -1,23 +0,0 @@
# 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

@ -1,6 +0,0 @@
#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

@ -1,185 +0,0 @@
#!/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" "$@"

@ -1,89 +0,0 @@
@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

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

@ -1,31 +1,25 @@

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

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
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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {75BB0A32-C002-4B33-88B3-421A9369D9CB}
EndGlobalSection
EndGlobal

@ -5,7 +5,7 @@
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found </PageTitle>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>

@ -18,7 +18,6 @@
</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" />
@ -26,9 +25,7 @@
<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>

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

@ -1,23 +0,0 @@
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);
}
}
}

@ -1,29 +0,0 @@
<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>

@ -1,15 +0,0 @@
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; }
}
}

@ -1,31 +0,0 @@
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,13 @@
namespace Blazor.Data
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

@ -0,0 +1,20 @@
namespace Blazor.Data
{
public class WeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<WeatherForecast[]> GetForecastAsync(DateOnly startDate)
{
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray());
}
}
}

@ -3,10 +3,10 @@
<div class="simple-form">
<p>
@Localizer["Question"]
Are you sure you want to delete @chapter.Name ?
</p>
<button @onclick="ConfirmDelete" class="btn btn-primary">@Localizer["Delete"]</button>
<button @onclick="ConfirmDelete" class="btn btn-primary">Delete</button>
<button @onclick="Cancel" class="btn btn-secondary">@Localizer["Cancel"]</button>
<button @onclick="Cancel" class="btn btn-secondary">Cancel</button>
</div>

@ -3,24 +3,28 @@ 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; }
public BlazoredModalInstance ModalInstance { get; set; }
[Inject]
public IStringLocalizer<DeleteConfirmation> Localizer { get; set; }
[Inject]
public required IDataService DataService { get; set; }
public IDataService DataService { get; set; }
[Parameter]
public int Id { get; set; }
private Chapter chapter = new Chapter();
protected override async Task OnInitializedAsync()
{
// Get the chapter
chapter = await DataService.GetById(Id);
}
void ConfirmDelete()
{
ModalInstance.CloseAsync(ModalResult.Ok(true));

@ -1,8 +1,6 @@
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;
@ -10,37 +8,23 @@ 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());
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();
}
}
//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();
// }
//}
}

@ -1,20 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace Blazor.Models;
namespace Blazor.Models;
public class AnswerModel
{
public int Id { get; set; }
public int Id { get; private set; }
public string Content { get; set; }
public int IdQuestion { get; private 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)
public AnswerModel(int id, string content, int idQuestion)
{
Id = id;
Content = content;
IdQuestion = idQuestion;
}
public AnswerModel(){}
}

@ -1,43 +1,11 @@
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();
// }
//}
}

@ -1,19 +1,23 @@
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 int Id { get; private set; }
public string Content { get; set; }
[Required(ErrorMessage = "Chapter is required")]
public int IdChapter { get; set; }
public int IdAnswerGood { get; set; }
public int IdChapter { get; private set; }
public int? IdAnswerGood { get; set; }
public int Difficulty { get; set; }
public int NbFails { get; set; }
public int NbFails { get; private set; }
public QuestionModel(int id, string content, int idChapter, int difficulty, int nbFails, int? idAnswerGood = null)
{
Id = id;
Content = content;
IdChapter = idChapter;
Difficulty = difficulty;
NbFails = nbFails;
IdAnswerGood = idAnswerGood;
}
public void addFails(int nb) { NbFails += nb; }
public void removeFails(int nb) { NbFails -= nb; }

@ -1,7 +0,0 @@
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
}

@ -1,35 +1,23 @@
@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 />
<h3>AddAdministrator</h3>
<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>
<EditForm Model="@administratorModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="username">
Username:
<InputText id="username" @bind-Value="administratorModel.Username" />
</label>
<label for="hashedPassword">
Password:
<InputText id="hashedPassword" @bind-Value="administratorModel.HashedPassword" />
</label>
</p>
<button type="submit" class="btn btn-success mb-2">@Localizer["Submit"]</button>
</EditForm>
</div>
</div>
<button type="submit">Submit</button>
</EditForm>

@ -1,58 +1,56 @@
using Microsoft.AspNetCore.Components;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Forms;
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; }
private AdministratorModel administratorModel = new();
[Inject]
public required IDataService DataService { get; set; }
public IDataService DataService { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
public NavigationManager NavigationManager { get; set; }
[Inject]
public required ILogger<AddAdministrator> Logger { get; set; }
public ILogger<AddAdministrator> Logger { get; set; }
private async Task HandleValidSubmit()
private async void 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");
administratorModel.HashPassword(administratorModel.HashedPassword);
await DataService.Add(administratorModel);
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 = "https://trusting-panini.87-106-126-109.plesk.page/api/add/administrator";
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");
}
}
}

@ -1,12 +1,12 @@
@page "/administrators"
@using Blazorise.DataGrid
@using Blazor.ViewClasses
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<h3>@Localizer["Title"]</h3>
<h3>Administrators</h3>
<div>
<NavLink class="btn btn-primary" href="addAdministrator" Match="NavLinkMatch.All">
<i class="fa fa-plus"></i> @Localizer["Add"]
<i class="fa fa-plus"></i> Ajouter
</NavLink>
</div>
<DataGrid TItem="Administrator"
@ -17,11 +17,11 @@
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.Username)" Caption="Display 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>
<a href="editAdministrator/@(context.Id)" class="btn btn-primary"><i class="fa fa-edit"></i> Editer</a>
<button type="button" class="btn btn-primary" @onclick="() => OnDelete(context.Id)"><i class="fa fa-trash"></i> Supprimer</button>
</DisplayTemplate>
</DataGridColumn>
</DataGrid>

@ -2,40 +2,39 @@
using Blazorise.DataGrid;
using Blazor.ViewClasses;
using Blazor.Modals;
using Blazor.Models;
using Blazored.LocalStorage;
using Blazored.Modal.Services;
using Blazor.Modals;
using Blazor.Services;
using Blazored.Modal;
using Microsoft.Extensions.Localization;
using Blazored.Modal;
using Blazor.Pages.Chapters;
namespace Blazor.Pages.Admins;
public partial class Administrators
{
public List<Administrator> administrators = new();
public List<Administrator> administrators;
private int totalItem;
[CascadingParameter]
public required IModalService Modal { get; set; }
[Inject]
public IStringLocalizer<Administrators> Localizer { get; set; }
public IModalService Modal { get; set; }
[Inject]
public required IDataService DataService { get; set; }
public IDataService DataService { get; set; }
[Inject]
public required IWebHostEnvironment WebHostEnvironment { get; set; }
public IWebHostEnvironment WebHostEnvironment { get; set; }
[Inject]
public required ILocalStorageService LocalStorage { get; set; }
public ILocalStorageService LocalStorage { get; set; }
[Inject]
public required HttpClient Http { get; set; }
public HttpClient Http { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; set; }
public NavigationManager NavigationManager { get; set; }
private async Task OnReadData(DataGridReadDataEventArgs<Administrator> e)
{
@ -43,48 +42,59 @@ public partial class Administrators
{
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);
// When you use a real API, we use this follow code
//var response = await Http.GetFromJsonAsync<ChaptersModel[]>( $"https://trusting-panini.87-106-126-109.plesk.page/api/administrators?page={e.Page}&pageSize={e.PageSize}" );
var response = Http.GetFromJsonAsync<Administrator[]>($"https://trusting-panini.87-106-126-109.plesk.page/api/administrators").Result;
if (!e.CancellationToken.IsCancellationRequested && response != null)
if (!e.CancellationToken.IsCancellationRequested)
{
administrators = new List<Administrator>(response); // an actual data for the current page
totalItem = response[0].total_administrators;
totalItem = administrators.Count;
var currentData = await LocalStorage.GetItemAsync<Administrator[]>("data");
if (currentData == null || currentData.Length != administrators.Count)
{
var originalData = Http.GetFromJsonAsync<Administrator[]>($"https://trusting-panini.87-106-126-109.plesk.page/api/administrators").Result;
await LocalStorage.SetItemAsync("data", originalData);
}
}
}
//protected override async Task OnAfterRenderAsync(bool firstRender)
//{
// // Do not treat this action if is not the first render
// if (!firstRender)
// {
// return;
// }
// var currentData = await LocalStorage.GetItemAsync<AdministratorModel[]>("data");
// // Check if data exist in the local storage
// if (currentData == null)
// {
// // this code add in the local storage the fake data (we load the data sync for initialize the data before load the OnReadData method)
// var originalData = Http.GetFromJsonAsync<AdministratorModel[]>($"https://trusting-panini.87-106-126-109.plesk.page/api/administrators").Result;
// await LocalStorage.SetItemAsync("data", originalData);
// }
//}
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;
var result = modal.Result;
if (result.Cancelled)
if (result.IsCanceled)
{
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();
}
}
await DataService.Delete(id);
// Reload the page
NavigationManager.NavigateTo("administrators", true);
}
}

@ -1,35 +1,21 @@
@page "/editAdministrator/{Id:int}"
@using Blazor.Components;
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<BackButton RedirectionPage="/administrators"></BackButton>
<h3>EditAdministrator</h3>
<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 />
<EditForm Model="@administratorModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="username">
Username:
<InputText id="username" @bind-Value="administratorModel.Username" />
</label>
<label for="hashedPassword">
Password:
<InputText id="hashedPassword" @bind-Value="administratorModel.HashedPassword" />
</label>
</p>
<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>
<button type="submit">Submit</button>
</EditForm>

@ -1,80 +1,77 @@
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");
}
}
}
}
using Blazor.Models;
using Blazor.Pages.Chapters;
using Blazor.Services;
using Microsoft.AspNetCore.Components;
namespace Blazor.Pages.Admins
{
public partial class EditAdministrator
{
[Parameter]
public int Id { get; set; }
private AdministratorModel administratorModel = new();
[Inject]
public IDataService DataService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
[Inject]
public IWebHostEnvironment WebHostEnvironment { get; set; }
[Inject]
public ILogger<EditAdministrator> Logger { get; set; }
private string OldAdminName { get; set; }
protected override async Task OnInitializedAsync()
{
var administrator = await DataService.GetAdminById(Id);
OldAdminName = administrator.Username;
// Set the model with the admin
administratorModel = new AdministratorModel
{
Id = administrator.Id,
Username = administrator.Username,
HashedPassword = administrator.HashedPassword
};
}
private async void HandleValidSubmit()
{
administratorModel.HashPassword(administratorModel.HashedPassword);
await DataService.Update(Id, administratorModel);
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 = "https://trusting-panini.87-106-126-109.plesk.page/api/update/administrator/" + administratorModel.Id;
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");
}
}
}

@ -1,29 +1,20 @@
@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>
<h3>Add Chapter</h3>
<EditForm Model="@chapterModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<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>
<p>
<label for="name">
Name:
<InputText id="name" @bind-Value="chapterModel.Name" />
</label>
</p>
<button type="submit" class="btn btn-success mb-2">@Localizer["Submit"]</button>
</EditForm>
</div>
</div>
<button type="submit">Submit</button>
</EditForm>

@ -1,58 +1,54 @@
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");
}
}
}
using Microsoft.AspNetCore.Components;
using Blazor.Models;
using Blazor.Services;
namespace Blazor.Pages.Chapters;
public partial class AddChapter
{
private ChapterModel chapterModel = new();
[Inject]
public IDataService DataService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
[Inject]
public ILogger<AddChapter> Logger { get; set; }
private async void HandleValidSubmit()
{
await DataService.Add(chapterModel);
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("name", chapterModel.Name));
var formContent = new FormUrlEncodedContent(formData);
string apiUri = "https://trusting-panini.87-106-126-109.plesk.page/api/add/chapter";
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");
}
}

@ -2,12 +2,16 @@
@using Blazor.ViewClasses;
@using Blazorise.DataGrid
@using Blazored.Modal;
<PageTitle>@Localizer["PageTitle"]</PageTitle>
<h3>@Localizer["Title"]</h3>
<h3>Chapters</h3>
<div>
<NavLink class="btn btn-primary" href="addChapter" Match="NavLinkMatch.All">
<i class="fa fa-plus"></i> @Localizer["Add"]
<i class="fa fa-plus"></i> Ajouter
</NavLink>
<NavLink class="btn btn-primary" @onclick="Export">
<i class="fa fa-plus"></i> Exporter
</NavLink>
<InputFile OnChange="@SingleUpload"/>
</div>
<DataGrid TItem="Chapter"
@ -18,11 +22,11 @@
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.Name)" Caption="Display 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>
<a href="editChapter/@(context.Id)" class="btn btn-primary"><i class="fa fa-edit"></i> Editer</a>
<button type="button" class="btn btn-primary" @onclick="() => OnDelete(context.Id)"><i class="fa fa-trash"></i> Supprimer</button>
</DisplayTemplate>
</DataGridColumn>
</DataGrid>

@ -2,23 +2,22 @@
using Blazor.Services;
using Blazored.Modal.Services;
using Blazor.ViewClasses;
using System.Text;
using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components;
using Blazorise.DataGrid;
using ChoETL;
using Microsoft.AspNetCore.Components.Forms;
using Blazor.Modals;
using Blazored.Modal;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Chapters;
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; }
@ -45,31 +44,28 @@ public partial class Chapters
parameters.Add(nameof(Chapter.Id), id);
var modal = Modal.Show<DeleteConfirmation>("Delete Confirmation", parameters);
var result = await modal.Result;
var result = modal.Result;
if (result.Cancelled)
if (result.IsCanceled)
{
return;
}
string apiUri = API.API_URL+"delete/chapter/" + id + "/" + API.TOKEN;
using (var httpClient = new HttpClient())
{
var response = await httpClient.DeleteAsync(apiUri);
await DataService.Delete(id);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
}
else
{
var errorResponse = await response.Content.ReadAsStringAsync();
}
}
// Reload the page
NavigationManager.NavigateTo("chapters", true);
}
//protected override async Task OnAfterRenderAsync(bool firstRender)
//{
// if (!firstRender)
// {
// return;
// }
//}
private async Task OnReadData(DataGridReadDataEventArgs<Chapter> e)
{
if (e.CancellationToken.IsCancellationRequested)
@ -77,16 +73,99 @@ public partial class Chapters
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);
var response = Http.GetFromJsonAsync<Chapter[]>($"https://trusting-panini.87-106-126-109.plesk.page/api/chapters").Result;
if (!e.CancellationToken.IsCancellationRequested)
{
chapters = new List<Chapter>(response); // an actual data for the current page
totalChapter = response[0].total_chapters;
totalChapter = chapters.Count;
var currentData = await LocalStorage.GetItemAsync<Chapter[]>("data");
if (currentData == null || currentData.Length != chapters.Count)
{
var originalData = Http.GetFromJsonAsync<Chapter[]>($"https://trusting-panini.87-106-126-109.plesk.page/api/chapters").Result;
await LocalStorage.SetItemAsync("data", originalData);
}
}
}
private async void Export()
{
StringBuilder sb = new StringBuilder();
HttpResponseMessage response = await Http.GetAsync("https://trusting-panini.87-106-126-109.plesk.page/api/chapters");
var json = await response.Content.ReadAsStringAsync();
using (var jsonFile = ChoJSONReader.LoadText(json))
{
using (var csvFile = new ChoCSVWriter(sb).WithFirstLineHeader())
{
csvFile.Write(jsonFile);
}
}
var sentFile = new MemoryStream(Encoding.UTF32.GetBytes(sb.ToString()));
using (var streamRef = new DotNetStreamReference(stream: sentFile))
{
await IJSRuntime.InvokeVoidAsync("downloadFileFromStream", "data.csv", streamRef);
}
}
private async Task SingleUpload(InputFileChangeEventArgs e)
{
using (MemoryStream ms = new MemoryStream())
{
await e.File.OpenReadStream().CopyToAsync(ms);
var bytes = ms.ToArray();
string s = Encoding.UTF8.GetString(bytes);
char[] invalidChars = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '\r', '\n', ',', ' ' };
List<string> filteredStrings = new List<string>();
StringBuilder filteredString = new StringBuilder();
foreach (var c in s)
{
if (!invalidChars.Contains(c))
{
filteredString.Append(c);
}
else
{
if (filteredString.Length > 0)
{
filteredStrings.Add(filteredString.ToString());
filteredString.Clear();
}
}
}
if (filteredString.Length > 0)
{
filteredStrings.Add(filteredString.ToString());
}
foreach (var filteredStr in filteredStrings)
{
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("name", filteredStr));
var formContent = new FormUrlEncodedContent(formData);
string apiUri = "https://trusting-panini.87-106-126-109.plesk.page/api/add/chapter";
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();
}
}
}
}
}
}

@ -1,29 +1,18 @@
@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>
<h3>Edit Chapter</h3>
<EditForm Model="@chapterModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label for="name">
Name:
<InputText id="name" @bind-Value="chapterModel.Name" />
</label>
</p>
<button type="submit">Submit</button>
</EditForm>

@ -1,7 +1,6 @@
using Blazor.Models;
using Blazor.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
namespace Blazor.Pages.Chapters;
@ -10,10 +9,7 @@ public partial class EditChapter
[Parameter]
public int Id { get; set; }
private ChapterModel? chapterModel;
[Inject]
public IStringLocalizer<EditChapter> Localizer { get; set; }
private ChapterModel chapterModel = new();
[Inject]
public IDataService DataService { get; set; }
@ -34,6 +30,7 @@ public partial class EditChapter
var chapter = await DataService.GetById(Id);
OldChapterName = chapter.Name;
// Set the model with the chapter
chapterModel = new ChapterModel
{
Id = chapter.Id,
@ -41,8 +38,9 @@ public partial class EditChapter
};
}
private async Task HandleValidSubmit()
private async void HandleValidSubmit()
{
await DataService.Update(Id, chapterModel);
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("name", chapterModel.Name));
@ -50,7 +48,7 @@ public partial class EditChapter
var formContent = new FormUrlEncodedContent(formData);
string apiUri = API.API_URL+"update/chapter/" + chapterModel.Id + "/" + API.TOKEN;
string apiUri = "https://trusting-panini.87-106-126-109.plesk.page/api/update/chapter/"+chapterModel.Id;
using (var httpClient = new HttpClient())
{

@ -3,7 +3,7 @@
<!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" />

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

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

Loading…
Cancel
Save