Merge remote-tracking branch 'origin/androidCompose'
continuous-integration/drone/push Build is passing Details

master
Jeremy DUCOURTHIAL 1 year ago
commit 50a336a941

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,345 @@
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 {
Toast.makeText(context, "Au REVOIR", Toast.LENGTH_SHORT).show()
}
}
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
),
title = {},
navigationIcon = {
IconButton(
onClick = { navController.navigate("home") },
modifier = Modifier.size(60.dp)
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
modifier = Modifier.size(36.dp),
tint = Color.White
)
}
},
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, top = 62.dp, end = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Liste des lobbies",
color = Colors.White,
fontSize = 17.sp,
modifier = Modifier
.weight(if (isPortrait) 1f else 0.6f)
.padding(start = 25.dp)
)
Button(
onClick = {
navController.navigate("createLobby")
},
shape = RoundedCornerShape(15),
modifier = Modifier
.weight(if (isPortrait) 1f else 0.4f)
.padding(end = 20.dp),
colors = ButtonDefaults.buttonColors(Colors.Blue)
) {
Text(text = "Ajouter un lobby")
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.border(2.dp, Color.White, shape = RoundedCornerShape(3))
.weight(0.85f),
) {
items(lobbyList) { lobby ->
LobbyItem(lobby, selectedItem == lobby) {
selectedItem = it
if (viewModel.getNbPlayerInLobby(selectedItem!!) < selectedItem!!.nbplayers) {
// Créer un Utiliser si le lobby n'est pas plein
scope.launch {
createUtiliserForLobby(context, selectedItem!!, navController)
}
} else {
// Afficher un message Toast si le lobby est plein
Toast.makeText(
context,
"Oh nan, le serveur est déjà plein ! Réessayer plus tard.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
Button(
onClick = {
scope.launch {
refreshLobbyList()
}
},
shape = RoundedCornerShape(15),
modifier = Modifier
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(Colors.Blue)
) {
Text(text = "Actualiser")
}
}
}
private suspend fun createUtiliserForLobby(context: Context, lobby: Lobby, navController: NavController) {
withContext(Dispatchers.IO) {
val formDataBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
formDataBuilder.addFormDataPart("idplayer", MainActivity.idPlayerConnected.toString())
formDataBuilder.addFormDataPart("idlobby", lobby.id.toString())
formDataBuilder.addFormDataPart("playertime", "0")
ControllerUtiliser.createUtiliserByIdLobby(formDataBuilder)
// Naviguer vers l'activité ServerDetails avec les détails du lobby
val mainScope = MainScope()
mainScope.launch {
navController.navigate("serverDetails/${lobby.name}/${lobby.id}/${lobby.idchapter}/${lobby.nbplayers}/${lobby.difficulty}")
}
}
}
@Composable
fun LobbyItem(lobby: Lobby, isSelected: Boolean, onItemClick: (Lobby) -> Unit) {
val context = LocalContext.current
var showDialog by rememberSaveable { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable {
if (lobby.password.isNotEmpty()) {
showDialog = true
} else {
onItemClick(lobby)
}
}
.clip(RoundedCornerShape(8.dp))
.background(if (isSelected) Colors.Orange else Colors.Grey)
.padding(16.dp)
) {
Text(
text = lobby.name,
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier.weight(1f)
)
Text(
text = "${ControllerLobby.getNbPlayerInLobby(lobby.id)}/${lobby.nbplayers}",
fontSize = 18.sp,
color = (if(ControllerLobby.getNbPlayerInLobby(lobby.id)==lobby.nbplayers) Color.Red else Color.White),
modifier = Modifier.weight(1f)
)
Text(
text = ControllerChapter.getChapterNameById(lobby.idchapter).toString(),
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier.weight(3f)
)
Text(
text = lobby.difficulty.toString(),
fontSize = 18.sp,
color = Colors.White,
modifier = Modifier.weight(1f)
)
if (showDialog) {
ConfirmDialog(
onDismiss = { showDialog = false },
onConfirm = { password ->
println("Password input : $password")
if (password == lobby.password) {
showDialog = false
onItemClick(lobby)
} else {
Toast.makeText(context, "Wrong password!", Toast.LENGTH_SHORT).show()
}
}
)
}
}
}
@Composable
fun ConfirmDialog(onDismiss: () -> Unit, onConfirm: (String) -> Unit) {
val context = LocalContext.current
var password by remember { mutableStateOf("") }
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = { Text("Enter Password") },
confirmButton = {
Button(
onClick = {
onConfirm(password)
}
) {
Text("Confirm")
}
},
dismissButton = {
Button(
onClick = {
onDismiss()
}
) {
Text("Dismiss")
}
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Password
),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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