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