Merge pull request 'Implemented Connection with Wi-Fi Direct and IP' (#9) from connection into main
Reviewed-on: #9
This commit is contained in:
commit
033ad040b5
39 changed files with 927 additions and 447 deletions
23
README.md
23
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`
|
||||||
|
|
|
@ -48,7 +48,8 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Tasksvalider">
|
android:theme="@style/Theme.Tasksvalider"
|
||||||
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.bwd
|
package com.faraphel.tasks_valider.connectivity.bwd
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -41,15 +42,16 @@ class BwdManager(
|
||||||
* Create a new BwfManager from an activity.
|
* Create a new BwfManager from an activity.
|
||||||
* @param activity The activity to create the manager from
|
* @param activity The activity to create the manager from
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
fun fromActivity(activity: Activity): BwdManager {
|
fun fromActivity(activity: Activity): BwdManager {
|
||||||
// check if the system support WiFi-Direct
|
// 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")
|
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
|
||||||
throw BwdNotSupportedException()
|
throw BwdNotSupportedException()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Faraphel): more check on permissions
|
// TODO(Faraphel): more check on permissions
|
||||||
if (
|
/* if (
|
||||||
activity.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
|
activity.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
|
||||||
activity.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED
|
activity.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
|
@ -59,6 +61,7 @@ class BwdManager(
|
||||||
PERMISSION_ACCESS_FINE_LOCATION
|
PERMISSION_ACCESS_FINE_LOCATION
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// get the WiFi-Direct manager
|
// get the WiFi-Direct manager
|
||||||
val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
|
val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
|
||||||
|
@ -66,9 +69,14 @@ class BwdManager(
|
||||||
|
|
||||||
// get the WiFi-Direct channel
|
// get the WiFi-Direct channel
|
||||||
val channel = manager.initialize(activity, activity.mainLooper, null)
|
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<WifiP2pInfo?>(null)
|
val stateConnectionInfo = mutableStateOf<WifiP2pInfo?>(null)
|
||||||
val statePeers = mutableStateOf<WifiP2pDeviceList?>(null)
|
val statePeers = mutableStateOf<WifiP2pDeviceList?>(null)
|
||||||
|
|
||||||
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
// ignore empty intent
|
// ignore empty intent
|
||||||
if (intent == null)
|
if (intent == null)
|
||||||
|
|
|
@ -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
|
The base have some issue, like an abusive usage of listener, error code and events that make using it
|
||||||
very impractical.
|
very impractical.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.task
|
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.TaskEntityHttpClient
|
||||||
import com.faraphel.tasks_valider.database.api.client.entities.*
|
import com.faraphel.tasks_valider.database.api.client.entities.*
|
||||||
|
|
||||||
|
@ -17,13 +18,19 @@ class TaskClient(
|
||||||
) {
|
) {
|
||||||
private val httpClient = TaskEntityHttpClient(address, port, baseCookies)
|
private val httpClient = TaskEntityHttpClient(address, port, baseCookies)
|
||||||
|
|
||||||
val clientApi = ClassClientApi(httpClient)
|
// all the entities API
|
||||||
val personApi = PersonClientApi(httpClient)
|
class Entities(httpClient: TaskEntityHttpClient) {
|
||||||
val sessionApi = SessionClientApi(httpClient)
|
val client = ClassClientApi(httpClient)
|
||||||
val subjectApi = SubjectClientApi(httpClient)
|
val person = PersonClientApi(httpClient)
|
||||||
val taskApi = TaskClientApi(httpClient)
|
val session = SessionClientApi(httpClient)
|
||||||
val validationApi = ValidationClientApi(httpClient)
|
val subject = SubjectClientApi(httpClient)
|
||||||
|
val task = TaskClientApi(httpClient)
|
||||||
|
val validation = ValidationClientApi(httpClient)
|
||||||
|
|
||||||
val relationClassPersonApi = RelationClassPersonClientApi(httpClient)
|
val relationClassPerson = RelationClassPersonClientApi(httpClient)
|
||||||
val relationPersonSessionSubjectApi = RelationPersonSessionSubjectClientApi(httpClient)
|
val relationPersonSessionSubject = RelationPersonSessionSubjectClientApi(httpClient)
|
||||||
}
|
}
|
||||||
|
val entities = Entities(httpClient)
|
||||||
|
|
||||||
|
val session = TaskSessionManagerClientApi(httpClient)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.task
|
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.connectivity.task.session.TaskSessionManager
|
||||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||||
import com.faraphel.tasks_valider.database.api.server.DatabaseApi
|
import com.faraphel.tasks_valider.database.api.server.DatabaseApi
|
||||||
|
@ -24,7 +24,7 @@ class TaskServer(
|
||||||
) : NanoHTTPD(port) {
|
) : NanoHTTPD(port) {
|
||||||
private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager
|
private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager
|
||||||
private val databaseApi = DatabaseApi(this.database, session) ///< the api of the database
|
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
|
* Get the admin person entity
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.TaskSession
|
||||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
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.faraphel.tasks_valider.utils.parser
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import fi.iki.elonen.NanoHTTPD
|
import fi.iki.elonen.NanoHTTPD
|
||||||
|
@ -12,7 +14,7 @@ import fi.iki.elonen.NanoHTTPD
|
||||||
/**
|
/**
|
||||||
* the HTTP API for the session manager
|
* the HTTP API for the session manager
|
||||||
*/
|
*/
|
||||||
class TaskSessionManagerApi(
|
class TaskSessionManagerServerApi(
|
||||||
private val sessionManager: TaskSessionManager,
|
private val sessionManager: TaskSessionManager,
|
||||||
private val database: TaskDatabase
|
private val database: TaskDatabase
|
||||||
) {
|
) {
|
||||||
|
@ -72,25 +74,44 @@ class TaskSessionManagerApi(
|
||||||
NanoHTTPD.Method.POST -> {
|
NanoHTTPD.Method.POST -> {
|
||||||
// get the user identifiers
|
// get the user identifiers
|
||||||
val identifiers: Map<String, String> = parser.fromJson(
|
val identifiers: Map<String, String> = parser.fromJson(
|
||||||
httpSession.inputStream.bufferedReader().readText(),
|
httpSession.getBody(),
|
||||||
object : TypeToken<Map<String, String>>() {}.type
|
object : TypeToken<Map<String, String>>() {}.type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val person: PersonEntity
|
||||||
|
|
||||||
// check for the id
|
// 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(
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"Missing id"
|
"Missing id or card_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 for the password
|
// check for the password
|
||||||
if (!identifiers.contains("password"))
|
if (!identifiers.contains("password"))
|
||||||
|
@ -100,14 +121,6 @@ class TaskSessionManagerApi(
|
||||||
"Missing password"
|
"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
|
// check if the password is correct
|
||||||
if (!person.checkPassword(identifiers["password"]!!))
|
if (!person.checkPassword(identifiers["password"]!!))
|
||||||
return NanoHTTPD.newFixedLengthResponse(
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
|
@ -119,11 +132,11 @@ class TaskSessionManagerApi(
|
||||||
// create a new session for the userJHH
|
// create a new session for the userJHH
|
||||||
val (sessionToken, session) = this.sessionManager.newSessionData(person)
|
val (sessionToken, session) = this.sessionManager.newSessionData(person)
|
||||||
|
|
||||||
// create the response
|
// create the response with the session data
|
||||||
val response = NanoHTTPD.newFixedLengthResponse(
|
val response = NanoHTTPD.newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.OK,
|
NanoHTTPD.Response.Status.OK,
|
||||||
"text/plain",
|
"application/json",
|
||||||
"Session updated"
|
parser.toJson(session)
|
||||||
)
|
)
|
||||||
|
|
||||||
// set the session token in the cookies
|
// set the session token in the cookies
|
|
@ -1,6 +1,7 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.task.session
|
package com.faraphel.tasks_valider.connectivity.task.session
|
||||||
|
|
||||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,5 +10,5 @@ import kotlinx.serialization.Serializable
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TaskSession(
|
data class TaskSession(
|
||||||
val person: PersonEntity,
|
@Expose val person: PersonEntity,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,6 +18,12 @@ interface PersonDao : BaseTaskDao<PersonEntity> {
|
||||||
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id")
|
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id")
|
||||||
fun getById(id: Long): PersonEntity?
|
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(
|
@Query(
|
||||||
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
|
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
|
||||||
"WHERE id IN (" +
|
"WHERE id IN (" +
|
||||||
|
|
|
@ -4,12 +4,13 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
|
||||||
|
|
||||||
@Entity(tableName = ClassEntity.TABLE_NAME)
|
@Entity(tableName = ClassEntity.TABLE_NAME)
|
||||||
data class ClassEntity (
|
data class ClassEntity (
|
||||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0,
|
||||||
@ColumnInfo("name") val name: String,
|
@ColumnInfo("name") @Expose val name: String,
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "classes"
|
const val TABLE_NAME = "classes"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -12,12 +13,13 @@ import java.util.*
|
||||||
@Serializable
|
@Serializable
|
||||||
@Entity(tableName = PersonEntity.TABLE_NAME)
|
@Entity(tableName = PersonEntity.TABLE_NAME)
|
||||||
data class PersonEntity (
|
data class PersonEntity (
|
||||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
|
||||||
@ColumnInfo("first_name") val firstName: String,
|
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0,
|
||||||
@ColumnInfo("last_name") val lastName: String,
|
@ColumnInfo("first_name") @Expose val firstName: String,
|
||||||
@ColumnInfo("card_id") val cardId: String? = null,
|
@ColumnInfo("last_name") @Expose val lastName: String,
|
||||||
|
@ColumnInfo("card_id") @Expose val cardId: String? = null,
|
||||||
@ColumnInfo("password_hash") val passwordHash: 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() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "persons"
|
const val TABLE_NAME = "persons"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = RelationClassPersonEntity.TABLE_NAME,
|
tableName = RelationClassPersonEntity.TABLE_NAME,
|
||||||
|
@ -27,8 +28,8 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class RelationClassPersonEntity (
|
data class RelationClassPersonEntity (
|
||||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
@ColumnInfo("student_id", index = true) @Expose val studentId: Long,
|
||||||
@ColumnInfo("class_id", index = true) val classId: Long,
|
@ColumnInfo("class_id", index = true) @Expose val classId: Long,
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "relation_class_person"
|
const val TABLE_NAME = "relation_class_person"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
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 (
|
data class RelationPersonSessionSubjectEntity (
|
||||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
@ColumnInfo("student_id", index = true) @Expose val studentId: Long,
|
||||||
@ColumnInfo("session_id", index = true) val sessionId: Long,
|
@ColumnInfo("session_id", index = true) @Expose val sessionId: Long,
|
||||||
@ColumnInfo("subject_id", index = true) val subjectId: Long,
|
@ColumnInfo("subject_id", index = true) @Expose val subjectId: Long,
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "relation_person_session_subject"
|
const val TABLE_NAME = "relation_person_session_subject"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
@ -19,9 +20,9 @@ import java.time.Instant
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class SessionEntity (
|
data class SessionEntity (
|
||||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0,
|
||||||
@ColumnInfo("name") val name: String? = null,
|
@ColumnInfo("name") @Expose val name: String? = null,
|
||||||
@ColumnInfo("start") val start: Instant,
|
@ColumnInfo("start") @Expose val start: Instant,
|
||||||
|
|
||||||
@ColumnInfo("class_id", index = true) val classId: Long? = null,
|
@ColumnInfo("class_id", index = true) val classId: Long? = null,
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
|
|
|
@ -4,11 +4,12 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
|
||||||
@Entity(tableName = SubjectEntity.TABLE_NAME)
|
@Entity(tableName = SubjectEntity.TABLE_NAME)
|
||||||
data class SubjectEntity (
|
data class SubjectEntity (
|
||||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0,
|
||||||
@ColumnInfo("name") val name: String,
|
@ColumnInfo("name") @Expose val name: String,
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "subjects"
|
const val TABLE_NAME = "subjects"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = TaskEntity.TABLE_NAME,
|
tableName = TaskEntity.TABLE_NAME,
|
||||||
|
@ -18,12 +19,12 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class TaskEntity (
|
data class TaskEntity (
|
||||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) @Expose val id: Long = 0,
|
||||||
@ColumnInfo("title") val title: String,
|
@ColumnInfo("title") @Expose val title: String,
|
||||||
@ColumnInfo("description") val description: String? = null,
|
@ColumnInfo("description") @Expose val description: String? = null,
|
||||||
@ColumnInfo("order") val order: Int, ///< the order of the task in the list of the subject
|
@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() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "tasks"
|
const val TABLE_NAME = "tasks"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
@ -35,11 +36,11 @@ import java.time.Instant
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class ValidationEntity (
|
data class ValidationEntity (
|
||||||
@ColumnInfo("teacher_id", index = true) val teacherId: Long,
|
@ColumnInfo("teacher_id", index = true) @Expose val teacherId: Long,
|
||||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
@ColumnInfo("student_id", index = true) @Expose val studentId: Long,
|
||||||
@ColumnInfo("task_id", index = true) val taskId: Long,
|
@ColumnInfo("task_id", index = true) @Expose val taskId: Long,
|
||||||
|
|
||||||
@ColumnInfo("date") val date: Instant,
|
@ColumnInfo("date") @Expose val date: Instant,
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TABLE_NAME = "validations"
|
const val TABLE_NAME = "validations"
|
||||||
|
|
|
@ -3,4 +3,8 @@ package com.faraphel.tasks_valider.database.entities.error
|
||||||
|
|
||||||
class HttpException(
|
class HttpException(
|
||||||
private val code: Int,
|
private val code: Int,
|
||||||
) : Exception("Http Exception: $code")
|
) : Exception("Http Exception: $code") {
|
||||||
|
fun getCode(): Int {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -74,46 +74,97 @@ fun populateTaskDatabaseTest(database: TaskDatabase) {
|
||||||
taskA1Id,
|
taskA1Id,
|
||||||
taskA2Id,
|
taskA2Id,
|
||||||
taskA3Id,
|
taskA3Id,
|
||||||
taskB1Id,
|
taskA4Id,
|
||||||
taskB2Id,
|
taskA5Id,
|
||||||
) = database.taskDao().insert(
|
) = database.taskDao().insert(
|
||||||
|
// Subject A
|
||||||
TaskEntity(
|
TaskEntity(
|
||||||
title = "Commencer A",
|
title = "Installation Debian",
|
||||||
description = "Description 1",
|
description = "Installer la dernière version de Debian sur les Raspberry Pi.",
|
||||||
order = 1,
|
order = 1,
|
||||||
subjectId = subjectA.id,
|
subjectId = subjectA.id,
|
||||||
),
|
),
|
||||||
TaskEntity(
|
TaskEntity(
|
||||||
title = "Continuer A",
|
title = "Connection de LEDs",
|
||||||
description = "Description 2",
|
description =
|
||||||
|
"Utiliser les broches GPIO du Raspberry Pi pour brancher vos LEDs.\n" +
|
||||||
|
"N'oublier pas les résistances !",
|
||||||
order = 2,
|
order = 2,
|
||||||
subjectId = subjectA.id
|
subjectId = subjectA.id,
|
||||||
),
|
),
|
||||||
TaskEntity(
|
TaskEntity(
|
||||||
title = "Finir A",
|
title = "IDLE Python",
|
||||||
description = "Description 3",
|
description = "Installer l'éditeur basique Python avec 'apt install idle-python3.11'.",
|
||||||
order = 3,
|
order = 3,
|
||||||
subjectId = subjectA.id
|
subjectId = subjectA.id,
|
||||||
),
|
),
|
||||||
TaskEntity(
|
TaskEntity(
|
||||||
title = "Commencer B",
|
title = "Clignotement de LEDs",
|
||||||
description = "Description 1",
|
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,
|
order = 1,
|
||||||
subjectId = subjectB.id,
|
subjectId = subjectB.id,
|
||||||
),
|
),
|
||||||
TaskEntity(
|
TaskEntity(
|
||||||
title = "Finir B",
|
title = "Connection de LEDs",
|
||||||
description = "Description 2",
|
description =
|
||||||
|
"Utiliser les broches de votre Arduino pour brancher vos LEDs.\n" +
|
||||||
|
"N'oublier pas les résistances !",
|
||||||
order = 2,
|
order = 2,
|
||||||
subjectId = subjectB.id,
|
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 taskA1 = database.taskDao().getById(taskA1Id)!!
|
||||||
val taskA2 = database.taskDao().getById(taskA2Id)!!
|
val taskA2 = database.taskDao().getById(taskA2Id)!!
|
||||||
val taskA3 = database.taskDao().getById(taskA3Id)!!
|
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 taskB1 = database.taskDao().getById(taskB1Id)!!
|
||||||
val taskB2 = database.taskDao().getById(taskB2Id)!!
|
val taskB2 = database.taskDao().getById(taskB2Id)!!
|
||||||
|
val taskB3 = database.taskDao().getById(taskB3Id)!!
|
||||||
|
val taskB4 = database.taskDao().getById(taskB4Id)!!
|
||||||
|
val taskB5 = database.taskDao().getById(taskB5Id)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<TaskSession?>) {
|
||||||
|
val controller = rememberNavController()
|
||||||
|
val barcode = remember { mutableStateOf<BarcodeResult?>(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<TaskSession?>,
|
||||||
|
barcode: MutableState<BarcodeResult?>,
|
||||||
|
) {
|
||||||
|
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<TaskSession?>
|
||||||
|
) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.foundation.layout.*
|
||||||
import androidx.compose.material3.Button
|
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
|
* Authentification screen where the host can give his information
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthentificationServerScreen(personEntity: MutableState<PersonEntity?>) {
|
fun AuthenticationServerScreen(personEntity: MutableState<PersonEntity?>) {
|
||||||
val firstName = remember { mutableStateOf("") }
|
val firstName = remember { mutableStateOf("") }
|
||||||
val lastName = remember { mutableStateOf("") }
|
val lastName = remember { mutableStateOf("") }
|
||||||
|
|
|
@ -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<TaskClient?>(null) }
|
||||||
|
val session = remember { mutableStateOf<TaskSession?>(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<TaskClient?>) {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.app.Activity
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Button
|
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.PersonEntity
|
||||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||||
import com.faraphel.tasks_valider.database.populateSubjectSessionPersonTest
|
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.DEFAULT_SERVER_PORT
|
||||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController
|
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
|
* Screen for the host to configure the server
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommunicationInternetServerScreen(
|
fun CommunicationInternetServerScreen(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
|
@ -52,7 +49,7 @@ fun CommunicationInternetServerScreen(
|
||||||
NavHost(navController = controller, startDestination = "authentication") {
|
NavHost(navController = controller, startDestination = "authentication") {
|
||||||
composable("authentication") {
|
composable("authentication") {
|
||||||
// if the admin person is not created, prompt the user for the admin information
|
// 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")
|
else controller.navigate("configuration")
|
||||||
}
|
}
|
||||||
composable("configuration") {
|
composable("configuration") {
|
||||||
|
@ -76,7 +73,6 @@ fun CommunicationInternetServerScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommunicationInternetServerContent(
|
fun CommunicationInternetServerContent(
|
||||||
database: TaskDatabase,
|
database: TaskDatabase,
|
||||||
|
@ -183,12 +179,7 @@ fun CommunicationInternetServerContent(
|
||||||
val session = database.sessionDao().getById(sessionId)!!
|
val session = database.sessionDao().getById(sessionId)!!
|
||||||
|
|
||||||
// TODO(Faraphel): remove, this is a test function
|
// TODO(Faraphel): remove, this is a test function
|
||||||
Thread {
|
populateSubjectSessionPersonTest(database, session)
|
||||||
populateSubjectSessionPersonTest(database, session)
|
|
||||||
}.let { thread ->
|
|
||||||
thread.start()
|
|
||||||
thread.join()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the server
|
// Create the server
|
||||||
Log.i("room-server", "creating the server")
|
Log.i("room-server", "creating the server")
|
|
@ -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.app.Activity
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -16,9 +14,10 @@ import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
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
|
@Composable
|
||||||
fun CommunicationInternetSelectScreen(activity: Activity, database: TaskDatabase) {
|
fun CommunicationInternetSelectScreen(activity: Activity, database: TaskDatabase) {
|
||||||
val controller = rememberNavController()
|
val controller = rememberNavController()
|
|
@ -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<TaskClient?>(null) }
|
||||||
|
val session = remember { mutableStateOf<TaskSession?>(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<TaskClient?>,
|
||||||
|
bwdManager: BwdManager,
|
||||||
|
) {
|
||||||
|
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<PersonEntity?>(null) }
|
||||||
|
val adminPersonEntity = remember { mutableStateOf<PersonEntity?>(null) }
|
||||||
|
val client = remember { mutableStateOf<TaskClient?>(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<PersonEntity?>,
|
||||||
|
adminPersonEntity: MutableState<PersonEntity?>,
|
||||||
|
client: MutableState<TaskClient?>
|
||||||
|
) {
|
||||||
|
val classes = remember { mutableStateOf<List<ClassEntity>?>(null) }
|
||||||
|
val selectedClass = remember { mutableStateOf<ClassEntity?>(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 = "<Not selected>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<List<ClassEntity>?>) {
|
||||||
|
classes.value = database.classDao().getAll()
|
||||||
|
}
|
|
@ -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") }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TaskClient?>(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<TaskClient?>) {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
package com.faraphel.tasks_valider.ui.screen.communication
|
package com.faraphel.tasks_valider.ui.screen.communication
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
@ -21,8 +19,8 @@ import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
|
||||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
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.connection.internet.CommunicationInternetSelectScreen
|
||||||
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen
|
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 activity: The activity that hosts the communication screen.
|
||||||
* @param database: the database.
|
* @param database: the database.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase) {
|
fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase) {
|
||||||
val controller = rememberNavController()
|
val controller = rememberNavController()
|
||||||
|
@ -49,8 +46,7 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase)
|
||||||
CommunicationInternetSelectScreen(activity, database)
|
CommunicationInternetSelectScreen(activity, database)
|
||||||
}
|
}
|
||||||
composable("wifi-p2p") {
|
composable("wifi-p2p") {
|
||||||
val bwdManager = BwdManager.fromActivity(activity)
|
CommunicationWifiP2pScreen(activity, database)
|
||||||
CommunicationWifiP2pScreen(activity, bwdManager)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,10 +88,10 @@ fun CommunicationSelectContent(controller: NavController, activity: Activity) {
|
||||||
// if the WiFi-Direct is supported, navigate to the WiFi-Direct screen
|
// if the WiFi-Direct is supported, navigate to the WiFi-Direct screen
|
||||||
if (isWifiP2pSupported) controller.navigate("wifi-p2p")
|
if (isWifiP2pSupported) controller.navigate("wifi-p2p")
|
||||||
// if the WiFi-Direct is not supported, show a toast message
|
// 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<WifiP2pDevice?>(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<WifiP2pDevice?>
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
WifiP2pDeviceListWidget(
|
|
||||||
peers = bwdManager.statePeers.value,
|
|
||||||
filter = { device: WifiP2pDevice -> device.isGroupOwner },
|
|
||||||
selectedDevice,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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") }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<TaskClient?>(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<TaskClient?>
|
|
||||||
) {
|
|
||||||
/*
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
|
@ -66,7 +66,7 @@ fun quickValidation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// requests all the persons
|
// requests all the persons
|
||||||
val allPersons = client.personApi.getAll()
|
val allPersons = client.entities.person.getAll()
|
||||||
// get the person with the matching card
|
// get the person with the matching card
|
||||||
val person = allPersons.firstOrNull { person -> person.cardId == cardId }
|
val person = allPersons.firstOrNull { person -> person.cardId == cardId }
|
||||||
|
|
||||||
|
@ -79,19 +79,19 @@ fun quickValidation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// requests all the relation persons - subjects
|
// requests all the relation persons - subjects
|
||||||
val allRelationsPersonSubject = client.relationPersonSessionSubjectApi.getAll()
|
val allRelationsPersonSubject = client.entities.relationPersonSessionSubject.getAll()
|
||||||
// get the corresponding relation
|
// get the corresponding relation
|
||||||
val relationPersonSubject = allRelationsPersonSubject.first { relation -> relation.studentId == person.id }
|
val relationPersonSubject = allRelationsPersonSubject.first { relation -> relation.studentId == person.id }
|
||||||
|
|
||||||
// requests all the tasks
|
// requests all the tasks
|
||||||
val allTasks = client.taskApi.getAll()
|
val allTasks = client.entities.task.getAll()
|
||||||
// get the corresponding tasks
|
// get the corresponding tasks
|
||||||
val tasks = allTasks
|
val tasks = allTasks
|
||||||
.filter { task -> task.subjectId == relationPersonSubject.subjectId }
|
.filter { task -> task.subjectId == relationPersonSubject.subjectId }
|
||||||
.sortedBy { task -> task.order }
|
.sortedBy { task -> task.order }
|
||||||
|
|
||||||
// requests all the validations
|
// requests all the validations
|
||||||
val allValidations = client.validationApi.getAll()
|
val allValidations = client.entities.validation.getAll()
|
||||||
// get the corresponding relation
|
// get the corresponding relation
|
||||||
val validations = allValidations.filter { validation -> validation.studentId == person.id }
|
val validations = allValidations.filter { validation -> validation.studentId == person.id }
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ fun quickValidation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new validation on the server
|
// create a new validation on the server
|
||||||
client.validationApi.save(
|
client.entities.validation.save(
|
||||||
ValidationEntity(
|
ValidationEntity(
|
||||||
teacherId=user.id,
|
teacherId=user.id,
|
||||||
studentId=person.id,
|
studentId=person.id,
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
package com.faraphel.tasks_valider.ui.screen.task
|
package com.faraphel.tasks_valider.ui.screen.task
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Activity.RESULT_OK
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Environment
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -125,7 +120,7 @@ fun refreshStudents(
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// try to get all the persons in that session
|
// try to get all the persons in that session
|
||||||
students.value = client.personApi.getAll()
|
students.value = client.entities.person.getAll()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
// in case of error, show a message
|
// in case of error, show a message
|
||||||
return activity.runOnUiThread {
|
return activity.runOnUiThread {
|
||||||
|
@ -141,12 +136,12 @@ fun exportToFile(
|
||||||
client: TaskClient,
|
client: TaskClient,
|
||||||
) {
|
) {
|
||||||
// get all the values to export
|
// 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 allStudents = allPersons.filter { student -> student.role == TaskRole.STUDENT }
|
||||||
val allRelationsStudentSessionSubject = client.relationPersonSessionSubjectApi.getAll()
|
val allRelationsStudentSessionSubject = client.entities.relationPersonSessionSubject.getAll()
|
||||||
val allSubjects = client.subjectApi.getAll()
|
val allSubjects = client.entities.subject.getAll()
|
||||||
val allTasks = client.taskApi.getAll()
|
val allTasks = client.entities.task.getAll()
|
||||||
val allValidations = client.validationApi.getAll()
|
val allValidations = client.entities.validation.getAll()
|
||||||
|
|
||||||
// for each student
|
// for each student
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||||
|
@ -43,7 +44,7 @@ fun TaskStudentScreen(
|
||||||
client,
|
client,
|
||||||
student,
|
student,
|
||||||
tasks,
|
tasks,
|
||||||
validations
|
validations,
|
||||||
)
|
)
|
||||||
}.start()
|
}.start()
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ fun TaskStudentScreen(
|
||||||
client,
|
client,
|
||||||
student,
|
student,
|
||||||
tasks,
|
tasks,
|
||||||
validations
|
validations,
|
||||||
)
|
)
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
@ -125,7 +126,7 @@ fun refreshTasksValidations(
|
||||||
validations: MutableState<List<ValidationEntity>?>,
|
validations: MutableState<List<ValidationEntity>?>,
|
||||||
) {
|
) {
|
||||||
// try to obtain the list of subject
|
// 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
|
// get the subject that the student is using
|
||||||
val relationPersonSessionSubject = allRelationsPersonSessionSubject.firstOrNull { relation ->
|
val relationPersonSessionSubject = allRelationsPersonSessionSubject.firstOrNull { relation ->
|
||||||
relation.studentId == student.id
|
relation.studentId == student.id
|
||||||
|
@ -136,7 +137,7 @@ fun refreshTasksValidations(
|
||||||
return activity.runOnUiThread { Toast.makeText(activity, "No subject assigned", Toast.LENGTH_LONG).show() }
|
return activity.runOnUiThread { Toast.makeText(activity, "No subject assigned", Toast.LENGTH_LONG).show() }
|
||||||
|
|
||||||
// try to obtain the list of tasks
|
// 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
|
// get the tasks that are linked to this subject
|
||||||
tasks.value = allTasks
|
tasks.value = allTasks
|
||||||
|
@ -144,7 +145,7 @@ fun refreshTasksValidations(
|
||||||
.sortedBy { task -> task.order }
|
.sortedBy { task -> task.order }
|
||||||
|
|
||||||
// try to obtain the list of validations
|
// try to obtain the list of validations
|
||||||
val allValidations = client.validationApi.getAll()
|
val allValidations = client.entities.validation.getAll()
|
||||||
// filter only the interesting validations
|
// filter only the interesting validations
|
||||||
validations.value = allValidations.filter { validation ->
|
validations.value = allValidations.filter { validation ->
|
||||||
validation.studentId == student.id &&
|
validation.studentId == student.id &&
|
||||||
|
@ -156,8 +157,8 @@ fun refreshTasksValidations(
|
||||||
fun updateValidation(client: TaskClient, checked: Boolean, validation: ValidationEntity) {
|
fun updateValidation(client: TaskClient, checked: Boolean, validation: ValidationEntity) {
|
||||||
if (checked)
|
if (checked)
|
||||||
// if the validation is not set, create it
|
// if the validation is not set, create it
|
||||||
client.validationApi.save(validation)
|
client.entities.validation.save(validation)
|
||||||
else
|
else
|
||||||
// if the validation is set, delete it
|
// if the validation is set, delete it
|
||||||
client.validationApi.delete(validation)
|
client.entities.validation.delete(validation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,14 @@ package com.faraphel.tasks_valider.ui.widgets.connectivity
|
||||||
|
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
import android.net.wifi.p2p.WifiP2pDeviceList
|
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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
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,
|
filter: ((WifiP2pDevice) -> Boolean)? = null,
|
||||||
deviceState: MutableState<WifiP2pDevice?>? = null,
|
deviceState: MutableState<WifiP2pDevice?>? = 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 there are peers to display
|
||||||
if (peers != null) {
|
if (peers != null) {
|
||||||
// for every device in the list
|
// for every device in the list
|
||||||
|
|
|
@ -8,4 +8,5 @@ import java.time.Instant
|
||||||
|
|
||||||
val parser: Gson = GsonBuilder()
|
val parser: Gson = GsonBuilder()
|
||||||
.registerTypeAdapter(Instant::class.java, InstantConverter())
|
.registerTypeAdapter(Instant::class.java, InstantConverter())
|
||||||
|
.excludeFieldsWithoutExposeAnnotation()
|
||||||
.create()
|
.create()
|
Loading…
Reference in a new issue