Implemented Connection with Wi-Fi Direct and IP #9

Merged
faraphel merged 7 commits from connection into main 2024-06-30 18:01:26 +02:00
25 changed files with 371 additions and 141 deletions
Showing only changes of commit 30c7fb1b2a - Show all commits

View file

@ -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)
}

View file

@ -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

View file

@ -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
)
}
}

View file

@ -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

View file

@ -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,
) )

View file

@ -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 (" +

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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() {

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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
}
}

View file

@ -0,0 +1,146 @@
package com.faraphel.tasks_valider.ui.screen.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
/**
* 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()
}
}
}
}
}

View file

@ -1,4 +1,4 @@
package com.faraphel.tasks_valider.ui.screen.authentification package com.faraphel.tasks_valider.ui.screen.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("") }

View file

@ -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")
}
}
}

View file

@ -1,9 +1,7 @@
package com.faraphel.tasks_valider.ui.screen.communication.internet package com.faraphel.tasks_valider.ui.screen.communication.internet
import android.app.Activity import android.app.Activity
import android.os.Build import androidx.compose.foundation.layout.*
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -13,21 +11,38 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.faraphel.tasks_valider.connectivity.task.TaskClient import com.faraphel.tasks_valider.connectivity.task.TaskClient
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
import com.faraphel.tasks_valider.ui.screen.authentication.AuthenticationClientScreen
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_ADDRESS 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.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
@RequiresApi(Build.VERSION_CODES.O)
@Composable @Composable
fun CommunicationInternetClientScreen(activity: Activity) { fun CommunicationInternetClientScreen(activity: Activity) {
val client = remember { mutableStateOf<TaskClient?>(null) } val client = remember { mutableStateOf<TaskClient?>(null) }
val session = remember { mutableStateOf<TaskSession?>(null) }
// TODO(Faraphel): fix and get a user // if the client is not connected, show the connection screen
// if (client.value == null) CommunicationInternetClientContent(client) if (client.value == null)
// else TaskSessionScreen(activity, client.value!!, user) return CommunicationInternetClientContent(client)
// if the user is not authenticated, show the authentication screen
if (session.value == null)
return AuthenticationClientScreen(activity, client.value!!, session)
// show the main screen
TaskSessionController(
activity,
client.value!!,
session.value!!.person,
)
} }
@ -36,18 +51,31 @@ fun CommunicationInternetClientContent(client: MutableState<TaskClient?>) {
val serverAddress = remember { mutableStateOf(DEFAULT_SERVER_ADDRESS) } val serverAddress = remember { mutableStateOf(DEFAULT_SERVER_ADDRESS) }
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
Column { 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 // server address
TextField( TextField(
value = serverAddress.value, value = serverAddress.value,
onValueChange = { text -> placeholder = { Text("server IP") },
serverAddress.value = text onValueChange = { text -> serverAddress.value = text }
}
) )
// server port // server port
TextField( TextField(
value = serverPort.intValue.toString(), value = serverPort.intValue.toString(),
placeholder = { Text("server port") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onValueChange = { text -> onValueChange = { text ->
val port = text.toInt() val port = text.toInt()
@ -57,6 +85,10 @@ fun CommunicationInternetClientContent(client: MutableState<TaskClient?>) {
} }
) )
// separator
Spacer(modifier = Modifier.height(24.dp))
// connect button
Button(onClick = { Button(onClick = {
// TODO(Faraphel): check if the server is reachable // TODO(Faraphel): check if the server is reachable
client.value = TaskClient(serverAddress.value, serverPort.intValue) client.value = TaskClient(serverAddress.value, serverPort.intValue)

View file

@ -27,7 +27,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.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 +37,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 +51,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") {

View file

@ -33,7 +33,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()

View file

@ -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,

View file

@ -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

View file

@ -125,7 +125,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 +136,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 +144,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 +156,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)
} }

View file

@ -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()