diff --git a/README.md b/README.md index c122a9a..b13bbfd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ -# Study-M1-PDS +# Master 1 - Projet de Spécialité -(Université) - Projet De Spécialité +Ce projet consiste en une application Android permettant à des enseignants de créer des session de cours où un ensemble +d'élève est assigné à des tâches qui peuvent être validées de manière collaborative. + +Voici les fonctionnalités proposées : +- Connection entre plusieurs appareils en IP ou Wi-Fi Direct +- Création de session de cours contenant une classe et des enseignants +- Authentification à l'aide du QR code présent sur les cartes étudiantes +- Support de différents sujets composés de différentes questions pour chaqu'un des élèves +- Possibilité pour un enseignant de rejoindre la connection pour valider collaborativement les tâches des élèves +- Possibilité pour un étudiant de rejoindre la connection pour vérifier les tâches validées +- Exportation des données dans un fichier `JSON` +- Validation rapide permettant de valider la tâche suivante d'un élève à l'aide de son QR code + +# Build + +1. Cloner le projet à l'aide de la commande `git clone https://git.faraphel.fr/study-faraphel/M1-PDS` +2. Ouvrer le dans [Android Studio](https://developer.android.com/studio?hl=fr) ou [Intellij IDEA](https://www.jetbrains.com/idea/) +3. Sélectionner votre téléphone dans le [menu de configuration] (https://www.jetbrains.com/help/idea/run-debug-configuration.html) + (l'utilisation d'émulateur n'est pas préconisé pour la fonctionnalité Wi-Fi Direct) +4. Démarrer l'application avec le bouton `Run` diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 32f3c79..c459751 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,7 +48,8 @@ + android:theme="@style/Theme.Tasksvalider" + android:screenOrientation="portrait"> diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/BwdManager.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/BwdManager.kt index e1d1994..97924cf 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/BwdManager.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/BwdManager.kt @@ -1,6 +1,7 @@ package com.faraphel.tasks_valider.connectivity.bwd import android.Manifest +import android.annotation.SuppressLint import android.app.Activity import android.content.BroadcastReceiver import android.content.Context @@ -41,15 +42,16 @@ class BwdManager( * Create a new BwfManager from an activity. * @param activity The activity to create the manager from */ + @SuppressLint("UnspecifiedRegisterReceiverFlag") fun fromActivity(activity: Activity): BwdManager { // check if the system support WiFi-Direct - if (this.isSupported(activity)) { + if (!this.isSupported(activity)) { Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature") throw BwdNotSupportedException() } // TODO(Faraphel): more check on permissions - if ( + /* if ( activity.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || activity.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED ) { @@ -59,6 +61,7 @@ class BwdManager( PERMISSION_ACCESS_FINE_LOCATION ) } + */ // get the WiFi-Direct manager val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? @@ -66,9 +69,14 @@ class BwdManager( // get the WiFi-Direct channel val channel = manager.initialize(activity, activity.mainLooper, null) - return BwdManager(manager, channel) + + // create the manager + val bwdManager = BwdManager(manager, channel) - // NOTE(Faraphel): the broadcast receiver should be registered in the activity onResume + // make the manager receive the application intents + activity.registerReceiver(bwdManager, ALL_INTENT_FILTER) + + return bwdManager } } @@ -173,6 +181,7 @@ class BwdManager( val stateConnectionInfo = mutableStateOf(null) val statePeers = mutableStateOf(null) + override fun onReceive(context: Context?, intent: Intent?) { // ignore empty intent if (intent == null) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/README.md b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/README.md index 4e0acc5..b94ec29 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/README.md +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwd/README.md @@ -1,6 +1,6 @@ -# Better WiFi-Direct (BWD) +# Better Wi-Fi Direct (BWD) -This package contain code to improve the base WiFi-Direct implementation. +This package contain code to improve the base Wi-Fi Direct implementation. The base have some issue, like an abusive usage of listener, error code and events that make using it very impractical. diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt index e4b7bd5..7f48023 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt @@ -1,5 +1,6 @@ package com.faraphel.tasks_valider.connectivity.task +import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerClientApi import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient import com.faraphel.tasks_valider.database.api.client.entities.* @@ -17,13 +18,19 @@ class TaskClient( ) { private val httpClient = TaskEntityHttpClient(address, port, baseCookies) - val clientApi = ClassClientApi(httpClient) - val personApi = PersonClientApi(httpClient) - val sessionApi = SessionClientApi(httpClient) - val subjectApi = SubjectClientApi(httpClient) - val taskApi = TaskClientApi(httpClient) - val validationApi = ValidationClientApi(httpClient) + // all the entities API + class Entities(httpClient: TaskEntityHttpClient) { + val client = ClassClientApi(httpClient) + val person = PersonClientApi(httpClient) + val session = SessionClientApi(httpClient) + val subject = SubjectClientApi(httpClient) + val task = TaskClientApi(httpClient) + val validation = ValidationClientApi(httpClient) - val relationClassPersonApi = RelationClassPersonClientApi(httpClient) - val relationPersonSessionSubjectApi = RelationPersonSessionSubjectClientApi(httpClient) -} \ No newline at end of file + val relationClassPerson = RelationClassPersonClientApi(httpClient) + val relationPersonSessionSubject = RelationPersonSessionSubjectClientApi(httpClient) + } + val entities = Entities(httpClient) + + val session = TaskSessionManagerClientApi(httpClient) +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt index a4c1893..640aeff 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt @@ -1,6 +1,6 @@ package com.faraphel.tasks_valider.connectivity.task -import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi +import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerServerApi import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.server.DatabaseApi @@ -24,7 +24,7 @@ class TaskServer( ) : NanoHTTPD(port) { private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager private val databaseApi = DatabaseApi(this.database, session) ///< the api of the database - private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager, this.database) ///< the api of the session manager + private val sessionManagerApi = TaskSessionManagerServerApi(this.sessionManager, this.database) ///< the api of the session manager /** * Get the admin person entity diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerClientApi.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerClientApi.kt new file mode 100644 index 0000000..1267a22 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerClientApi.kt @@ -0,0 +1,58 @@ +package com.faraphel.tasks_valider.connectivity.task.api + +import com.faraphel.tasks_valider.connectivity.task.session.TaskSession +import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient +import com.faraphel.tasks_valider.database.entities.SessionEntity +import com.faraphel.tasks_valider.database.entities.error.HttpException +import com.faraphel.tasks_valider.utils.parser + + +/** + * Interface to communicate with the server session manager + */ +class TaskSessionManagerClientApi(private val client: TaskEntityHttpClient) { + /** + * Create a new session + * @param cardId the id of the user's card + * @param password the password of the user + */ + fun newFromCardId(cardId: String, password: String): TaskSession { + val response = this.client.post( + "sessions/self", + parser.toJson(mapOf( + "card_id" to cardId, + "password" to password + )) + ) + + // in case of error, notify it + if (!response.isSuccessful) + throw HttpException(response.code) + + val data = response.body.string() + + // parse the result + return parser.fromJson( + data, + TaskSession::class.java + ) + + } + + /** + * Get the current session + */ + fun getSelf(): TaskSession { + val response = this.client.get("sessions/self") + + // in case of error, notify it + if (!response.isSuccessful) + throw HttpException(response.code) + + // parse the result + return parser.fromJson( + response.body.string(), + TaskSession::class.java + ) + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerServerApi.kt similarity index 80% rename from app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerServerApi.kt index 6d6f055..7b57fdb 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerServerApi.kt @@ -4,6 +4,8 @@ import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission import com.faraphel.tasks_valider.connectivity.task.session.TaskSession import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.entities.PersonEntity +import com.faraphel.tasks_valider.utils.getBody import com.faraphel.tasks_valider.utils.parser import com.google.gson.reflect.TypeToken import fi.iki.elonen.NanoHTTPD @@ -12,7 +14,7 @@ import fi.iki.elonen.NanoHTTPD /** * the HTTP API for the session manager */ -class TaskSessionManagerApi( +class TaskSessionManagerServerApi( private val sessionManager: TaskSessionManager, private val database: TaskDatabase ) { @@ -72,25 +74,44 @@ class TaskSessionManagerApi( NanoHTTPD.Method.POST -> { // get the user identifiers val identifiers: Map = parser.fromJson( - httpSession.inputStream.bufferedReader().readText(), + httpSession.getBody(), object : TypeToken>() {}.type ) + val person: PersonEntity + // check for the id - if (!identifiers.contains("id")) + if (identifiers.contains("id")) { + // get the id of the user (if invalid, return an error) + val personId: Long = identifiers["id"]!!.toLongOrNull() + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Invalid id" + ) + + // check if the identifiers are correct + person = this.database.personDao().getById(personId) + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "No person with this id" + ) + } else if (identifiers.contains("card_id")) { + // check if the identifiers are correct + person = this.database.personDao().getByCardId(identifiers["card_id"]!!) + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "No person with this id" + ) + } else { return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "Missing id" - ) - - // get the id of the user (if invalid, return an error) - val personId: Long = identifiers["id"]!!.toLongOrNull() - ?: return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.BAD_REQUEST, - "text/plain", - "Invalid id" + "Missing id or card_id" ) + } // check for the password if (!identifiers.contains("password")) @@ -100,14 +121,6 @@ class TaskSessionManagerApi( "Missing password" ) - // check if the identifiers are correct - val person = this.database.personDao().getById(personId) - ?: return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.BAD_REQUEST, - "text/plain", - "No person with this id" - ) - // check if the password is correct if (!person.checkPassword(identifiers["password"]!!)) return NanoHTTPD.newFixedLengthResponse( @@ -119,11 +132,11 @@ class TaskSessionManagerApi( // create a new session for the userJHH val (sessionToken, session) = this.sessionManager.newSessionData(person) - // create the response + // create the response with the session data val response = NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.OK, - "text/plain", - "Session updated" + "application/json", + parser.toJson(session) ) // set the session token in the cookies diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt index 99998cb..e8650ed 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt @@ -1,6 +1,7 @@ package com.faraphel.tasks_valider.connectivity.task.session import com.faraphel.tasks_valider.database.entities.PersonEntity +import com.google.gson.annotations.Expose import kotlinx.serialization.Serializable @@ -9,5 +10,5 @@ import kotlinx.serialization.Serializable */ @Serializable data class TaskSession( - val person: PersonEntity, -) \ No newline at end of file + @Expose val person: PersonEntity, +) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt index 7b481e8..84cb348 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt @@ -18,6 +18,12 @@ interface PersonDao : BaseTaskDao { @Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id") fun getById(id: Long): PersonEntity? + /** + * Get the object from its card identifier + */ + @Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE card_id = :cardId") + fun getByCardId(cardId: String): PersonEntity? + @Query( "SELECT * FROM ${PersonEntity.TABLE_NAME} " + "WHERE id IN (" + diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt index 97eb47e..f2f84c6 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt @@ -4,12 +4,13 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose @Entity(tableName = ClassEntity.TABLE_NAME) data class ClassEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("name") val name: String, + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0, + @ColumnInfo("name") @Expose val name: String, ) : BaseEntity() { companion object { const val TABLE_NAME = "classes" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt index 0e62606..4fc8972 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt @@ -5,6 +5,7 @@ import androidx.room.Entity import androidx.room.PrimaryKey import com.faraphel.tasks_valider.connectivity.task.session.TaskRole import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose import kotlinx.serialization.Serializable import java.security.MessageDigest import java.util.* @@ -12,12 +13,13 @@ import java.util.* @Serializable @Entity(tableName = PersonEntity.TABLE_NAME) data class PersonEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("first_name") val firstName: String, - @ColumnInfo("last_name") val lastName: String, - @ColumnInfo("card_id") val cardId: String? = null, + + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0, + @ColumnInfo("first_name") @Expose val firstName: String, + @ColumnInfo("last_name") @Expose val lastName: String, + @ColumnInfo("card_id") @Expose val cardId: String? = null, @ColumnInfo("password_hash") val passwordHash: String? = null, - @ColumnInfo("role") val role: TaskRole = TaskRole.STUDENT, + @ColumnInfo("role") @Expose val role: TaskRole = TaskRole.STUDENT, ) : BaseEntity() { companion object { const val TABLE_NAME = "persons" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt index 06c024a..0a68512 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt @@ -4,6 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose @Entity( tableName = RelationClassPersonEntity.TABLE_NAME, @@ -27,8 +28,8 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity ] ) data class RelationClassPersonEntity ( - @ColumnInfo("student_id", index = true) val studentId: Long, - @ColumnInfo("class_id", index = true) val classId: Long, + @ColumnInfo("student_id", index = true) @Expose val studentId: Long, + @ColumnInfo("class_id", index = true) @Expose val classId: Long, ) : BaseEntity() { companion object { const val TABLE_NAME = "relation_class_person" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationPersonSessionSubjectEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationPersonSessionSubjectEntity.kt index d39a8e9..109b2be 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationPersonSessionSubjectEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationPersonSessionSubjectEntity.kt @@ -4,6 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose /** @@ -38,9 +39,9 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity ] ) data class RelationPersonSessionSubjectEntity ( - @ColumnInfo("student_id", index = true) val studentId: Long, - @ColumnInfo("session_id", index = true) val sessionId: Long, - @ColumnInfo("subject_id", index = true) val subjectId: Long, + @ColumnInfo("student_id", index = true) @Expose val studentId: Long, + @ColumnInfo("session_id", index = true) @Expose val sessionId: Long, + @ColumnInfo("subject_id", index = true) @Expose val subjectId: Long, ) : BaseEntity() { companion object { const val TABLE_NAME = "relation_person_session_subject" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt index 0f2edf4..41e9648 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt @@ -5,6 +5,7 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose import java.time.Instant @Entity( @@ -19,9 +20,9 @@ import java.time.Instant ] ) data class SessionEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("name") val name: String? = null, - @ColumnInfo("start") val start: Instant, + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0, + @ColumnInfo("name") @Expose val name: String? = null, + @ColumnInfo("start") @Expose val start: Instant, @ColumnInfo("class_id", index = true) val classId: Long? = null, ) : BaseEntity() { diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt index ee8aa83..d104fed 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt @@ -4,11 +4,12 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose @Entity(tableName = SubjectEntity.TABLE_NAME) data class SubjectEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("name") val name: String, + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0, + @ColumnInfo("name") @Expose val name: String, ) : BaseEntity() { companion object { const val TABLE_NAME = "subjects" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt index 255fce3..99bf154 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt @@ -5,6 +5,7 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose @Entity( tableName = TaskEntity.TABLE_NAME, @@ -18,12 +19,12 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity ] ) data class TaskEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("title") val title: String, - @ColumnInfo("description") val description: String? = null, - @ColumnInfo("order") val order: Int, ///< the order of the task in the list of the subject + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0, + @ColumnInfo("title") @Expose val title: String, + @ColumnInfo("description") @Expose val description: String? = null, + @ColumnInfo("order") @Expose val order: Int, ///< the order of the task in the list of the subject - @ColumnInfo("subject_id", index = true) val subjectId: Long, + @ColumnInfo("subject_id", index = true) @Expose val subjectId: Long, ) : BaseEntity() { companion object { const val TABLE_NAME = "tasks" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt index 7e489ca..0ad2f8b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt @@ -4,6 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import com.google.gson.annotations.Expose import java.time.Instant @Entity( @@ -35,11 +36,11 @@ import java.time.Instant ] ) data class ValidationEntity ( - @ColumnInfo("teacher_id", index = true) val teacherId: Long, - @ColumnInfo("student_id", index = true) val studentId: Long, - @ColumnInfo("task_id", index = true) val taskId: Long, + @ColumnInfo("teacher_id", index = true) @Expose val teacherId: Long, + @ColumnInfo("student_id", index = true) @Expose val studentId: Long, + @ColumnInfo("task_id", index = true) @Expose val taskId: Long, - @ColumnInfo("date") val date: Instant, + @ColumnInfo("date") @Expose val date: Instant, ) : BaseEntity() { companion object { const val TABLE_NAME = "validations" diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/error/HttpException.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/error/HttpException.kt index 0c28d91..dae2aa8 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/error/HttpException.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/error/HttpException.kt @@ -3,4 +3,8 @@ package com.faraphel.tasks_valider.database.entities.error class HttpException( private val code: Int, -) : Exception("Http Exception: $code") \ No newline at end of file +) : Exception("Http Exception: $code") { + fun getCode(): Int { + return code + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/test.kt b/app/src/main/java/com/faraphel/tasks_valider/database/test.kt index ff027fb..4e84074 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/test.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/test.kt @@ -74,46 +74,97 @@ fun populateTaskDatabaseTest(database: TaskDatabase) { taskA1Id, taskA2Id, taskA3Id, - taskB1Id, - taskB2Id, + taskA4Id, + taskA5Id, ) = database.taskDao().insert( + // Subject A TaskEntity( - title = "Commencer A", - description = "Description 1", + title = "Installation Debian", + description = "Installer la dernière version de Debian sur les Raspberry Pi.", order = 1, subjectId = subjectA.id, ), TaskEntity( - title = "Continuer A", - description = "Description 2", + title = "Connection de LEDs", + description = + "Utiliser les broches GPIO du Raspberry Pi pour brancher vos LEDs.\n" + + "N'oublier pas les résistances !", order = 2, - subjectId = subjectA.id + subjectId = subjectA.id, ), TaskEntity( - title = "Finir A", - description = "Description 3", + title = "IDLE Python", + description = "Installer l'éditeur basique Python avec 'apt install idle-python3.11'.", order = 3, - subjectId = subjectA.id + subjectId = subjectA.id, ), TaskEntity( - title = "Commencer B", - description = "Description 1", + title = "Clignotement de LEDs", + description = "Faite clignoter les différentes LEDs.", + order = 4, + subjectId = subjectA.id, + ), + TaskEntity( + title = "Feu Tricolore", + description = "Simuler un feu de circulation à l'aide de vos components.", + order = 5, + subjectId = subjectA.id, + ), + ) + + val ( + taskB1Id, + taskB2Id, + taskB3Id, + taskB4Id, + taskB5Id, + ) = database.taskDao().insert( + // Subject B + TaskEntity( + title = "Préparation Arduino", + description = "Préparer votre Arduino et assurer vous qu'il soit reconnu par votre ordinateur.", order = 1, subjectId = subjectB.id, ), TaskEntity( - title = "Finir B", - description = "Description 2", + title = "Connection de LEDs", + description = + "Utiliser les broches de votre Arduino pour brancher vos LEDs.\n" + + "N'oublier pas les résistances !", order = 2, subjectId = subjectB.id, - ) + ), + TaskEntity( + title = "IDE Arduino", + description = "Installer l'éditeur de code 'Arduino IDE'.", + order = 3, + subjectId = subjectB.id, + ), + TaskEntity( + title = "Clignotement de LEDs", + description = "Faite clignoter les différentes LEDs.", + order = 4, + subjectId = subjectB.id, + ), + TaskEntity( + title = "Feu Tricolore", + description = "Simuler un feu de circulation à l'aide de vos components.", + order = 5, + subjectId = subjectB.id, + ), ) val taskA1 = database.taskDao().getById(taskA1Id)!! val taskA2 = database.taskDao().getById(taskA2Id)!! val taskA3 = database.taskDao().getById(taskA3Id)!! + val taskA4 = database.taskDao().getById(taskA4Id)!! + val taskA5 = database.taskDao().getById(taskA5Id)!! + val taskB1 = database.taskDao().getById(taskB1Id)!! val taskB2 = database.taskDao().getById(taskB2Id)!! + val taskB3 = database.taskDao().getById(taskB3Id)!! + val taskB4 = database.taskDao().getById(taskB4Id)!! + val taskB5 = database.taskDao().getById(taskB5Id)!! } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt deleted file mode 100644 index 1c83eb0..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.authentification - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.text.input.PasswordVisualTransformation - - -/** - * Authentification screen where the client can give his information - */ -@Composable -fun AuthentificationClientScreen() { - val cardId = remember { mutableStateOf("") } - val password = remember { mutableStateOf("") } - - Column { - // card identifier - TextField( - value = cardId.value, - onValueChange = { text -> cardId.value = text }, - ) - - // password - TextField( - value = password.value, - visualTransformation = PasswordVisualTransformation(), - onValueChange = { text -> password.value = text }, - ) - - // button - Button(onClick = { /* do something */ }) { - Text("Submit") - } - } -} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/authentication/client.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/authentication/client.kt new file mode 100644 index 0000000..7c49317 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/authentication/client.kt @@ -0,0 +1,151 @@ +package com.faraphel.tasks_valider.ui.screen.communication.authentication + +import android.app.Activity +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.task.TaskClient +import com.faraphel.tasks_valider.connectivity.task.session.TaskSession +import com.faraphel.tasks_valider.database.entities.error.HttpException +import com.faraphel.tasks_valider.ui.screen.scan.qr.ScanBarcodeScreen +import com.journeyapps.barcodescanner.BarcodeResult +import okhttp3.HttpUrl.Companion.toHttpUrl +import java.net.ConnectException + + +/** + * Authentification screen where the client can give his information + */ +@Composable +fun AuthenticationClientScreen(activity: Activity, client: TaskClient, session: MutableState) { + val controller = rememberNavController() + val barcode = remember { mutableStateOf(null) } + + NavHost(navController = controller, startDestination = "main") { + composable("main") { + AuthenticationClientContent(activity, controller, client, session, barcode) + } + composable("scan") { + if (barcode.value == null) ScanBarcodeScreen(activity, barcode) + else controller.navigate("main") + } + } +} + + +@Composable +fun AuthenticationClientContent( + activity: Activity, + controller: NavController, + client: TaskClient, + session: MutableState, + barcode: MutableState, +) { + val cardId = remember { mutableStateOf("") } + val password = remember { mutableStateOf("") } + + // check if the barcode contain information about the card + + var defaultCardId = "" + if (barcode.value != null) { + val studentUrl = barcode.value!!.text.toHttpUrl() + cardId.value = studentUrl.pathSegments[0] + } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // title + Text( + text = "Authentication", + fontSize = 32.sp + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) + + // card identifier + Row { + // text + TextField( + value = cardId.value, + placeholder = { Text("Card Id") }, + onValueChange = { text -> cardId.value = text }, + ) + // button to scan the card + Button(onClick = { + barcode.value = null + controller.navigate("scan") + }) { + Text("Scan") + } + } + + // password + TextField( + value = password.value, + visualTransformation = PasswordVisualTransformation(), + placeholder = { Text("Password") }, + onValueChange = { text -> password.value = text }, + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) + + // submit button + + Button(onClick = { + Thread { authenticate(activity, client, cardId.value, password.value, session) }.start() + }) { + Text("Submit") + } + } +} + +fun authenticate( + activity: Activity, + client: TaskClient, + cardId: String, + password: String, + session: MutableState +) { + try { + // try to get a session from the identifiers + session.value = client.session.newFromCardId(cardId, password) + } catch (exception: HttpException) { + // in case of error, show a message to the user + when (exception.getCode()) { + 401 -> { + activity.runOnUiThread { + Toast.makeText(activity, "Invalid card id or password", Toast.LENGTH_SHORT).show() + } + } + else -> { + activity.runOnUiThread { + Toast.makeText(activity, "Unknown error: $exception", Toast.LENGTH_SHORT).show() + } + } + } + } catch (exception: ConnectException) { + activity.runOnUiThread { + Toast.makeText(activity, "Could not connect to the server: $exception", Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/authentication/server.kt similarity index 93% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/authentication/server.kt index f9d5084..de9482b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/authentication/server.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.screen.authentification +package com.faraphel.tasks_valider.ui.screen.communication.authentication import androidx.compose.foundation.layout.* import androidx.compose.material3.Button @@ -20,7 +20,7 @@ import com.faraphel.tasks_valider.database.entities.PersonEntity * Authentification screen where the host can give his information */ @Composable -fun AuthentificationServerScreen(personEntity: MutableState) { +fun AuthenticationServerScreen(personEntity: MutableState) { val firstName = remember { mutableStateOf("") } val lastName = remember { mutableStateOf("") } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/role/client.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/role/client.kt new file mode 100644 index 0000000..3ec7087 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/role/client.kt @@ -0,0 +1,109 @@ +package com.faraphel.tasks_valider.ui.screen.communication.connection.internet.role + +import android.app.Activity +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.task.TaskClient +import com.faraphel.tasks_valider.connectivity.task.session.TaskSession +import com.faraphel.tasks_valider.ui.screen.communication.authentication.AuthenticationClientScreen +import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_ADDRESS +import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT +import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT +import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController + + +@Composable +fun CommunicationInternetClientScreen(activity: Activity) { + val controller = rememberNavController() + + val client = remember { mutableStateOf(null) } + val session = remember { mutableStateOf(null) } + + NavHost(controller, startDestination = "communication") { + composable("communication") { + CommunicationInternetClientContent(client) + if (client.value != null) controller.navigate("authentication") + } + composable("authentication") { + AuthenticationClientScreen(activity, client.value!!, session) + if (session.value != null) controller.navigate("session") + } + composable("session") { + // show the main screen + TaskSessionController( + activity, + client.value!!, + session.value!!.person, + ) + } + } +} + + +@Composable +fun CommunicationInternetClientContent(client: MutableState) { + val serverAddress = remember { mutableStateOf(DEFAULT_SERVER_ADDRESS) } + val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // title + Text( + text = "Connection", + fontSize = 32.sp + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) + + // server address + TextField( + value = serverAddress.value, + placeholder = { Text("server IP") }, + onValueChange = { text -> serverAddress.value = text } + ) + + // server port + TextField( + value = serverPort.intValue.toString(), + placeholder = { Text("server port") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + onValueChange = { text -> + val port = text.toInt() + if (port in RANGE_SERVER_PORT) { + serverPort.intValue = port + } + } + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) + + // connect button + Button(onClick = { + // TODO(Faraphel): check if the server is reachable + client.value = TaskClient(serverAddress.value, serverPort.intValue) + }) { + Text("Connect") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/role/server.kt similarity index 92% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/role/server.kt index 0d29936..9fb8127 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/role/server.kt @@ -1,9 +1,7 @@ -package com.faraphel.tasks_valider.ui.screen.communication.internet +package com.faraphel.tasks_valider.ui.screen.communication.connection.internet.role import android.app.Activity -import android.os.Build import android.util.Log -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button @@ -27,7 +25,7 @@ import com.faraphel.tasks_valider.database.entities.ClassEntity import com.faraphel.tasks_valider.database.entities.PersonEntity import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.populateSubjectSessionPersonTest -import com.faraphel.tasks_valider.ui.screen.authentification.AuthentificationServerScreen +import com.faraphel.tasks_valider.ui.screen.communication.authentication.AuthenticationServerScreen import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController @@ -37,7 +35,6 @@ import java.time.Instant /** * Screen for the host to configure the server */ -@RequiresApi(Build.VERSION_CODES.O) @Composable fun CommunicationInternetServerScreen( activity: Activity, @@ -52,7 +49,7 @@ fun CommunicationInternetServerScreen( NavHost(navController = controller, startDestination = "authentication") { composable("authentication") { // if the admin person is not created, prompt the user for the admin information - if (adminPersonEntityRaw.value == null) AuthentificationServerScreen(adminPersonEntityRaw) + if (adminPersonEntityRaw.value == null) AuthenticationServerScreen(adminPersonEntityRaw) else controller.navigate("configuration") } composable("configuration") { @@ -76,7 +73,6 @@ fun CommunicationInternetServerScreen( } -@RequiresApi(Build.VERSION_CODES.O) @Composable fun CommunicationInternetServerContent( database: TaskDatabase, @@ -183,12 +179,7 @@ fun CommunicationInternetServerContent( val session = database.sessionDao().getById(sessionId)!! // TODO(Faraphel): remove, this is a test function - Thread { - populateSubjectSessionPersonTest(database, session) - }.let { thread -> - thread.start() - thread.join() - } + populateSubjectSessionPersonTest(database, session) // Create the server Log.i("room-server", "creating the server") diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/selection.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/selection.kt similarity index 84% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/selection.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/selection.kt index 1575f12..5d4a0eb 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/selection.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/internet/selection.kt @@ -1,8 +1,6 @@ -package com.faraphel.tasks_valider.ui.screen.communication.internet +package com.faraphel.tasks_valider.ui.screen.communication.connection.internet import android.app.Activity -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.Text @@ -16,9 +14,10 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.ui.screen.communication.connection.internet.role.CommunicationInternetClientScreen +import com.faraphel.tasks_valider.ui.screen.communication.connection.internet.role.CommunicationInternetServerScreen -@RequiresApi(Build.VERSION_CODES.O) @Composable fun CommunicationInternetSelectScreen(activity: Activity, database: TaskDatabase) { val controller = rememberNavController() diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/role/client.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/role/client.kt new file mode 100644 index 0000000..c9be389 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/role/client.kt @@ -0,0 +1,97 @@ +package com.faraphel.tasks_valider.ui.screen.communication.connection.wifiP2p.role + +import android.app.Activity +import android.net.wifi.p2p.WifiP2pConfig +import android.net.wifi.p2p.WifiP2pDevice +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.* +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.bwd.BwdManager +import com.faraphel.tasks_valider.connectivity.task.TaskClient +import com.faraphel.tasks_valider.connectivity.task.session.TaskSession +import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT +import com.faraphel.tasks_valider.ui.screen.communication.authentication.AuthenticationClientScreen +import com.faraphel.tasks_valider.ui.screen.communication.connection.internet.role.CommunicationInternetClientContent +import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController +import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget + + +@Composable +fun CommunicationWifiP2pClientScreen(activity: Activity, bwdManager: BwdManager) { + val controller = rememberNavController() + + val client = remember { mutableStateOf(null) } + val session = remember { mutableStateOf(null) } + + NavHost(controller, startDestination = "communication") { + composable("communication") { + CommunicationWifiP2pClientContent(activity, client, bwdManager) + if (client.value != null) controller.navigate("authentication") + } + composable("authentication") { + AuthenticationClientScreen(activity, client.value!!, session) + if (session.value != null) controller.navigate("session") + } + composable("session") { + // show the main screen + TaskSessionController( + activity, + client.value!!, + session.value!!.person, + ) + } + } +} + + +@Composable +fun CommunicationWifiP2pClientContent( + activity: Activity, + client: MutableState, + bwdManager: BwdManager, +) { + val selectedDevice = remember { mutableStateOf(null) } + + // if the device is not selected + if (selectedDevice.value == null) { + Column { + WifiP2pDeviceListWidget( + peers = bwdManager.statePeers.value, + filter = { device: WifiP2pDevice -> device.isGroupOwner }, + selectedDevice, + ) + } + + LaunchedEffect(true) { + // look for new peers + bwdManager.discoverPeers() + } + + return + } + + val config = WifiP2pConfig().apply { + deviceAddress = selectedDevice.value!!.deviceAddress + } + bwdManager.connect(config) { + bwdManager.requestConnectionInfo { connectionInfo -> + // if the group has not been formed correctly, the host did not approve the connection + if (!connectionInfo.groupFormed) { + selectedDevice.value = null + Toast.makeText(activity, "Require host approval.", Toast.LENGTH_LONG).show() + return@requestConnectionInfo + } + + // create a connection to the server + // TODO(Faraphel): check if the server is reachable + client.value = TaskClient( + connectionInfo.groupOwnerAddress.hostAddress!!, + DEFAULT_SERVER_PORT + ) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/role/server.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/role/server.kt new file mode 100644 index 0000000..8ba0cd6 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/role/server.kt @@ -0,0 +1,187 @@ +package com.faraphel.tasks_valider.ui.screen.communication.connection.wifiP2p.role + +import android.app.Activity +import android.util.Log +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.bwd.BwdManager +import com.faraphel.tasks_valider.connectivity.task.TaskClient +import com.faraphel.tasks_valider.connectivity.task.TaskServer +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.entities.ClassEntity +import com.faraphel.tasks_valider.database.entities.PersonEntity +import com.faraphel.tasks_valider.database.entities.SessionEntity +import com.faraphel.tasks_valider.database.populateSubjectSessionPersonTest +import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT +import com.faraphel.tasks_valider.ui.screen.communication.authentication.AuthenticationServerScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController +import java.time.Instant + + +@Composable +fun CommunicationWifiP2pServerScreen( + activity: Activity, + database: TaskDatabase, + bwdManager: BwdManager +) { + val controller = rememberNavController() + + val adminPersonEntityRaw = remember { mutableStateOf(null) } + val adminPersonEntity = remember { mutableStateOf(null) } + val client = remember { mutableStateOf(null) } + + NavHost(navController = controller, startDestination = "authentication") { + composable("authentication") { + // if the admin person is not created, prompt the user for the admin information + if (adminPersonEntityRaw.value == null) AuthenticationServerScreen(adminPersonEntityRaw) + else controller.navigate("configuration") + } + composable("configuration") { + if (client.value == null) + CommunicationWifiP2pServerContent( + database, + bwdManager, + adminPersonEntityRaw, + adminPersonEntity, + client, + ) + else controller.navigate("session") + } + composable("session") { + TaskSessionController( + activity, + client.value!!, + adminPersonEntity.value!! + ) + } + } + // TODO(Faraphel): fix and get a user + // if the server is not created, prompt the user for the server configuration + // if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client) + // else, go to the base tasks screen + // else TaskSessionScreen(activity, client.value!!) +} + + +@Composable +fun CommunicationWifiP2pServerContent( + database: TaskDatabase, + bwdManager: BwdManager, + adminPersonEntityRaw: MutableState, + adminPersonEntity: MutableState, + client: MutableState +) { + val classes = remember { mutableStateOf?>(null) } + val selectedClass = remember { mutableStateOf(null) } + val areClassesExpanded = remember { mutableStateOf(false) } + + LaunchedEffect(true) { + // refresh the list of classes + Thread { refreshClasses(database, classes) }.start() + } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // title + Text( + text = "New Session", + fontSize = 32.sp + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) + + // classes + Row(verticalAlignment = Alignment.CenterVertically) { + // description + Text(text = "Class", fontSize = 12.sp) + // separator + Spacer(modifier = Modifier.width(width = 12.dp)) + // selector + Button(onClick = { areClassesExpanded.value = !areClassesExpanded.value }) { + // display the selected class, if selected + if (selectedClass.value != null) Text(text = selectedClass.value!!.name) + else Text(text = "") + } + + // class selector + DropdownMenu( + expanded = areClassesExpanded.value, + onDismissRequest = { areClassesExpanded.value = false } + ) { + // TODO(Faraphel): student lists should be loaded from the database or a file + classes.value?.forEach { class_ -> + DropdownMenuItem( + text = { Text(class_.name) }, + onClick = { + selectedClass.value = class_ + areClassesExpanded.value = false + } + ) + } + } + } + + // check if a class is selected + if (selectedClass.value != null) + // button to create the server + Button(onClick = { + Thread { // a thread is used for networking + // Insert the admin in the database and get its id + val adminPersonEntityId = database.personDao().insert(adminPersonEntityRaw.value!!) + adminPersonEntity.value = database.personDao().getById(adminPersonEntityId)!! + + // Create a new session + // TODO(Faraphel): name + val sessionId = database.sessionDao().insert( + SessionEntity( + name="NOM", + start= Instant.now(), + classId=selectedClass.value!!.id, + ) + ) + val session = database.sessionDao().getById(sessionId)!! + + // TODO(Faraphel): remove, this is a test function + populateSubjectSessionPersonTest(database, session) + + // create a new Wi-Fi Direct group + bwdManager.recreateGroup { + // Create the server + Log.i("room-server", "creating the server") + val server = TaskServer( + DEFAULT_SERVER_PORT, + database, + session, + adminPersonEntity.value!!, + ) + server.start() + + // Get the client from the server + client.value = server.getAdminClient() + } + }.start() + }) { + Text("Create") + } + } +} + + +/** + * Refresh the list of classes + */ +fun refreshClasses(database: TaskDatabase, classes: MutableState?>) { + classes.value = database.classDao().getAll() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/selection.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/selection.kt new file mode 100644 index 0000000..928f240 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/connection/wifiP2p/selection.kt @@ -0,0 +1,62 @@ +package com.faraphel.tasks_valider.ui.screen.communication.connection.wifiP2p + +import android.annotation.SuppressLint +import android.app.Activity +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +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.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.bwd.BwdManager +import com.faraphel.tasks_valider.connectivity.bwd.BwdManager.Companion.ALL_INTENT_FILTER +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.ui.screen.communication.connection.wifiP2p.role.CommunicationWifiP2pClientScreen +import com.faraphel.tasks_valider.ui.screen.communication.connection.wifiP2p.role.CommunicationWifiP2pServerScreen + + +@Composable +fun CommunicationWifiP2pScreen(activity: Activity, database: TaskDatabase) { + val controller = rememberNavController() + + // create the Wi-Fi Direct manager + val bwdManager = remember { BwdManager.fromActivity(activity) } + + NavHost(navController = controller, startDestination = "mode") { + composable("mode") { CommunicationWifiP2pSelectContent(controller) } + composable("client") { CommunicationWifiP2pClientScreen(activity, bwdManager) } + composable("server") { CommunicationWifiP2pServerScreen(activity, database, bwdManager) } + } +} + + + +@Composable +fun CommunicationWifiP2pSelectContent(controller: NavController) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // title + Text( + text = "Role", + fontSize = 32.sp + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) + + // client mode + Button(onClick = { controller.navigate("client") }) { Text("Client") } + // server mode + Button(onClick = { controller.navigate("server") }) { Text("Server") } + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client.kt deleted file mode 100644 index 5a7927d..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.communication.internet - -import android.app.Activity -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.text.input.KeyboardType -import com.faraphel.tasks_valider.connectivity.task.TaskClient -import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_ADDRESS -import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT -import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT - - -@RequiresApi(Build.VERSION_CODES.O) -@Composable -fun CommunicationInternetClientScreen(activity: Activity) { - val client = remember { mutableStateOf(null) } - - // TODO(Faraphel): fix and get a user - // if (client.value == null) CommunicationInternetClientContent(client) - // else TaskSessionScreen(activity, client.value!!, user) -} - - -@Composable -fun CommunicationInternetClientContent(client: MutableState) { - val serverAddress = remember { mutableStateOf(DEFAULT_SERVER_ADDRESS) } - val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } - - Column { - // server address - TextField( - value = serverAddress.value, - onValueChange = { text -> - serverAddress.value = text - } - ) - - // server port - TextField( - value = serverPort.intValue.toString(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - onValueChange = { text -> - val port = text.toInt() - if (port in RANGE_SERVER_PORT) { - serverPort.intValue = port - } - } - ) - - Button(onClick = { - // TODO(Faraphel): check if the server is reachable - client.value = TaskClient(serverAddress.value, serverPort.intValue) - }) { - Text("Connect") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt index a04f0ae..1750464 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt @@ -1,9 +1,7 @@ package com.faraphel.tasks_valider.ui.screen.communication import android.app.Activity -import android.os.Build import android.widget.Toast -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -21,8 +19,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.faraphel.tasks_valider.connectivity.bwd.BwdManager import com.faraphel.tasks_valider.database.TaskDatabase -import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetSelectScreen -import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen +import com.faraphel.tasks_valider.ui.screen.communication.connection.internet.CommunicationInternetSelectScreen +import com.faraphel.tasks_valider.ui.screen.communication.connection.wifiP2p.CommunicationWifiP2pScreen /** @@ -33,7 +31,6 @@ import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationW * @param activity: The activity that hosts the communication screen. * @param database: the database. */ -@RequiresApi(Build.VERSION_CODES.O) @Composable fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase) { val controller = rememberNavController() @@ -49,8 +46,7 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase) CommunicationInternetSelectScreen(activity, database) } composable("wifi-p2p") { - val bwdManager = BwdManager.fromActivity(activity) - CommunicationWifiP2pScreen(activity, bwdManager) + CommunicationWifiP2pScreen(activity, database) } } } @@ -92,10 +88,10 @@ fun CommunicationSelectContent(controller: NavController, activity: Activity) { // if the WiFi-Direct is supported, navigate to the WiFi-Direct screen if (isWifiP2pSupported) controller.navigate("wifi-p2p") // if the WiFi-Direct is not supported, show a toast message - else Toast.makeText(activity, "WiFi-Direct is not supported on this device", Toast.LENGTH_SHORT).show() + else Toast.makeText(activity, "Wi-Fi Direct is not supported on this device", Toast.LENGTH_SHORT).show() } ) { - Text("WiFi-Direct") + Text("Wi-Fi Direct") } } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt deleted file mode 100644 index b2cc732..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client - -import android.app.Activity -import android.net.wifi.p2p.WifiP2pConfig -import android.net.wifi.p2p.WifiP2pDevice -import androidx.compose.foundation.layout.Column -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import com.faraphel.tasks_valider.connectivity.bwd.BwdManager -import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget - - -@Composable -fun CommunicationWifiP2pClientScreen(activity: Activity, bwdManager: BwdManager) { - val selectedDevice = remember { mutableStateOf(null) } - val isConnected = remember { mutableStateOf(false) } - - // if connected, show the task group screen - if (isConnected.value) { - // TaskGroupScreen(activity, null) - // TODO(Faraphel): finish the connection - return - } - - - // if the device is selected but not connected, try to connect - if (selectedDevice.value != null) { - // TODO(Faraphel): error handling - val config = WifiP2pConfig().apply { - deviceAddress = selectedDevice.value!!.deviceAddress - } - bwdManager.connect(config) { - isConnected.value = true - } - return - } - - // display the list of devices - CommunicationWifiP2pClientContent(bwdManager, selectedDevice) -} - - -@Composable -fun CommunicationWifiP2pClientContent( - bwdManager: BwdManager, - selectedDevice: MutableState -) { - Column { - WifiP2pDeviceListWidget( - peers = bwdManager.statePeers.value, - filter = { device: WifiP2pDevice -> device.isGroupOwner }, - selectedDevice, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt deleted file mode 100644 index c7d4b58..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p - -import android.app.Activity -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.navigation.NavController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.faraphel.tasks_valider.connectivity.bwd.BwdManager -import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client.CommunicationWifiP2pClientScreen -import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server.CommunicationWifiP2pServerScreen - - -@Composable -fun CommunicationWifiP2pScreen(activity: Activity, bwdManager: BwdManager) { - val controller = rememberNavController() - - NavHost(navController = controller, startDestination = "mode") { - composable("mode") { CommunicationWifiP2pSelectContent(controller) } - composable("client") { CommunicationWifiP2pClientScreen(activity, bwdManager) } - composable("server") { CommunicationWifiP2pServerScreen(activity, bwdManager) } - } -} - - -@Composable -fun CommunicationWifiP2pSelectContent(controller: NavController) { - Column { - // client mode - Button(onClick = { controller.navigate("client") }) { Text("Client") } - // server mode - Button(onClick = { controller.navigate("server") }) { Text("Server") } - } -} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt deleted file mode 100644 index b3eba6d..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server - -import android.app.Activity -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import com.faraphel.tasks_valider.connectivity.bwd.BwdManager -import com.faraphel.tasks_valider.connectivity.task.TaskClient - - -@Composable -fun CommunicationWifiP2pServerScreen(activity: Activity, bwdManager: BwdManager) { - val client = remember { mutableStateOf(null) } - - // TODO(Faraphel): fix and get a user - // if the server is not created, prompt the user for the server configuration - // if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client) - // else, go to the base tasks screen - // else TaskSessionScreen(activity, client.value!!) -} - - -@Composable -fun CommunicationWifiP2pServerContent( - activity: Activity, - bwdManager: BwdManager, - client: MutableState -) { - /* - val expandedStudentList = remember { mutableStateOf(false) } - val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } - - Column { - // student list - Button(onClick = { expandedStudentList.value = !expandedStudentList.value }) { - Text(text = "Select Students List") - } - DropdownMenu( - expanded = expandedStudentList.value, - onDismissRequest = { expandedStudentList.value = false } - ) { - DropdownMenuItem( - text = { Text("ISRI") }, - onClick = {} - ) - DropdownMenuItem( - text = { Text("MIAGE") }, - onClick = {} - ) - // TODO(Faraphel): student lists should be loaded from the database or a file - } - - // server port - TextField( - value = serverPort.intValue.toString(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - onValueChange = { text -> - val port = text.toInt() - if (port in RANGE_SERVER_PORT) { - serverPort.intValue = port - } - } - ) - - Button(onClick = { - // TODO(Faraphel): should be merged with the internet server - - // Reset the database | TODO(Faraphel): only for testing purpose - activity.deleteDatabase("local") - - // Create the database - val database = Room.databaseBuilder( - activity, - TaskDatabase::class.java, - "local" - ).build() - - // Create the admin - // TODO: the admin should be created from the card reader - val adminPersonEntity = PersonEntity( - "admin", - "admin", - "123456789", - "admin", - - ) - - // Insert the admin in the database - database.personDao().insert(adminPersonEntity) - - bwfManager.recreateGroup { - // Create the server - val server = TaskServer(serverPort.intValue, database, adminPersonEntity) - server.start() - - // Get the client from the server - client.value = server.getAdminClient() - } - }) { - Text("Create") - } - } - */ -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/quick_validation.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/quick_validation.kt index 98eff5e..9dcd493 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/quick_validation.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/quick_validation.kt @@ -66,7 +66,7 @@ fun quickValidation( } // requests all the persons - val allPersons = client.personApi.getAll() + val allPersons = client.entities.person.getAll() // get the person with the matching card val person = allPersons.firstOrNull { person -> person.cardId == cardId } @@ -79,19 +79,19 @@ fun quickValidation( } // requests all the relation persons - subjects - val allRelationsPersonSubject = client.relationPersonSessionSubjectApi.getAll() + val allRelationsPersonSubject = client.entities.relationPersonSessionSubject.getAll() // get the corresponding relation val relationPersonSubject = allRelationsPersonSubject.first { relation -> relation.studentId == person.id } // requests all the tasks - val allTasks = client.taskApi.getAll() + val allTasks = client.entities.task.getAll() // get the corresponding tasks val tasks = allTasks .filter { task -> task.subjectId == relationPersonSubject.subjectId } .sortedBy { task -> task.order } // requests all the validations - val allValidations = client.validationApi.getAll() + val allValidations = client.entities.validation.getAll() // get the corresponding relation val validations = allValidations.filter { validation -> validation.studentId == person.id } @@ -112,7 +112,7 @@ fun quickValidation( } // create a new validation on the server - client.validationApi.save( + client.entities.validation.save( ValidationEntity( teacherId=user.id, studentId=person.id, diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/session.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/session.kt index 4b2805c..2684530 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/session.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/session.kt @@ -1,13 +1,8 @@ package com.faraphel.tasks_valider.ui.screen.task import android.app.Activity -import android.app.Activity.RESULT_OK -import android.content.Intent -import android.os.Environment import android.util.Log import android.widget.Toast -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.Text @@ -125,7 +120,7 @@ fun refreshStudents( ) { try { // try to get all the persons in that session - students.value = client.personApi.getAll() + students.value = client.entities.person.getAll() } catch (exception: Exception) { // in case of error, show a message return activity.runOnUiThread { @@ -141,12 +136,12 @@ fun exportToFile( client: TaskClient, ) { // get all the values to export - val allPersons = client.personApi.getAll() + val allPersons = client.entities.person.getAll() val allStudents = allPersons.filter { student -> student.role == TaskRole.STUDENT } - val allRelationsStudentSessionSubject = client.relationPersonSessionSubjectApi.getAll() - val allSubjects = client.subjectApi.getAll() - val allTasks = client.taskApi.getAll() - val allValidations = client.validationApi.getAll() + val allRelationsStudentSessionSubject = client.entities.relationPersonSessionSubject.getAll() + val allSubjects = client.entities.subject.getAll() + val allTasks = client.entities.task.getAll() + val allValidations = client.entities.validation.getAll() // for each student diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/student.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/student.kt index 61af054..bc9c657 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/student.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/student.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.faraphel.tasks_valider.connectivity.task.TaskClient @@ -43,7 +44,7 @@ fun TaskStudentScreen( client, student, tasks, - validations + validations, ) }.start() @@ -105,7 +106,7 @@ fun TaskStudentScreen( client, student, tasks, - validations + validations, ) }.start() } @@ -125,7 +126,7 @@ fun refreshTasksValidations( validations: MutableState?>, ) { // try to obtain the list of subject - val allRelationsPersonSessionSubject = client.relationPersonSessionSubjectApi.getAll() + val allRelationsPersonSessionSubject = client.entities.relationPersonSessionSubject.getAll() // get the subject that the student is using val relationPersonSessionSubject = allRelationsPersonSessionSubject.firstOrNull { relation -> relation.studentId == student.id @@ -136,7 +137,7 @@ fun refreshTasksValidations( return activity.runOnUiThread { Toast.makeText(activity, "No subject assigned", Toast.LENGTH_LONG).show() } // try to obtain the list of tasks - val allTasks = client.taskApi.getAll() + val allTasks = client.entities.task.getAll() // get the tasks that are linked to this subject tasks.value = allTasks @@ -144,7 +145,7 @@ fun refreshTasksValidations( .sortedBy { task -> task.order } // try to obtain the list of validations - val allValidations = client.validationApi.getAll() + val allValidations = client.entities.validation.getAll() // filter only the interesting validations validations.value = allValidations.filter { validation -> validation.studentId == student.id && @@ -156,8 +157,8 @@ fun refreshTasksValidations( fun updateValidation(client: TaskClient, checked: Boolean, validation: ValidationEntity) { if (checked) // if the validation is not set, create it - client.validationApi.save(validation) + client.entities.validation.save(validation) else // if the validation is set, delete it - client.validationApi.delete(validation) + client.entities.validation.delete(validation) } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt index 9c09b82..9bf4826 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt @@ -2,10 +2,14 @@ package com.faraphel.tasks_valider.ui.widgets.connectivity import android.net.wifi.p2p.WifiP2pDevice import android.net.wifi.p2p.WifiP2pDeviceList -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp /** @@ -20,9 +24,24 @@ fun WifiP2pDeviceListWidget( filter: ((WifiP2pDevice) -> Boolean)? = null, deviceState: MutableState? = null, ) { - Text(text = "Devices (${peers?.deviceList?.size ?: 0})") + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // title + Text( + text = "Devices", + fontSize = 32.sp, + ) + // found device count + Text( + text = "(${peers?.deviceList?.size ?: 0})", + fontSize = 24.sp, + ) + + // separator + Spacer(modifier = Modifier.height(24.dp)) - Column { // if there are peers to display if (peers != null) { // for every device in the list diff --git a/app/src/main/java/com/faraphel/tasks_valider/utils/json.kt b/app/src/main/java/com/faraphel/tasks_valider/utils/json.kt index 5612d11..7c75a63 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/utils/json.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/utils/json.kt @@ -8,4 +8,5 @@ import java.time.Instant val parser: Gson = GsonBuilder() .registerTypeAdapter(Instant::class.java, InstantConverter()) + .excludeFieldsWithoutExposeAnnotation() .create() \ No newline at end of file