cards #8

Merged
faraphel merged 12 commits from cards into main 2024-06-13 15:12:13 +02:00
35 changed files with 583 additions and 151 deletions
Showing only changes of commit f821406578 - Show all commits

View file

@ -76,4 +76,5 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
ksp("androidx.room:room-compiler:2.6.1")
implementation(kotlin("reflect"))
}

View file

@ -6,24 +6,42 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.room.Room
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.database.populateTaskDatabaseTest
import com.faraphel.tasks_valider.ui.screen.communication.CommunicationModeSelectionScreen
class MainActivity : ComponentActivity() {
private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper
companion object {
private lateinit var database: TaskDatabase ///< the database manager
}
private lateinit var database: TaskDatabase ///< the database manager
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Reset the database | TODO(Faraphel): only for testing purpose
this.deleteDatabase("local")
// Create the database
this.database = Room.databaseBuilder(
this,
TaskDatabase::class.java,
"local"
).build()
// Populate the database with test data
// TODO(Faraphel): remove test data
Thread {
populateTaskDatabaseTest(database)
}.let { thread ->
thread.start()
thread.join()
}
this.setContent {
CommunicationModeSelectionScreen(this)
CommunicationModeSelectionScreen(this, database)
}
}

View file

@ -3,6 +3,7 @@ package com.faraphel.tasks_valider.connectivity.task
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody

View file

@ -5,21 +5,25 @@ import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.database.api.TaskDatabaseApi
import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
import fi.iki.elonen.NanoHTTPD
/**
* A server to handle the task API to allow clients to interact with the database.
* @param port the port of the server
* @param database the database to interact with
* @param port the port of the server.
* @param database the database to interact with.
* @param session the current session.
* @param adminPersonEntity the person that represent the host of the session.
*/
class TaskServer(
private val port: Int,
private val database: TaskDatabase,
adminPersonEntity: PersonEntity,
private val session: SessionEntity,
private val adminPersonEntity: PersonEntity,
) : NanoHTTPD(port) {
private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager
private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database
private val databaseApi = TaskDatabaseApi(this.database, session) ///< the api of the database
private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager, this.database) ///< the api of the session manager
/**

View file

@ -22,6 +22,7 @@ import com.faraphel.tasks_valider.database.entities.*
ValidationEntity::class,
RelationClassPersonEntity::class,
RelationPersonSessionSubjectEntity::class,
],
version = 1
)
@ -37,4 +38,5 @@ abstract class TaskDatabase: RoomDatabase() {
abstract fun validationDao(): ValidationDao
abstract fun relationClassPersonDao(): RelationClassPersonDao
abstract fun relationPersonSessionSubjectDao(): RelationPersonSessionSubjectDao
}

View file

@ -8,16 +8,20 @@ import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
import com.faraphel.tasks_valider.database.entities.*
import fi.iki.elonen.NanoHTTPD
class TaskDatabaseApi(private val database: TaskDatabase) {
class TaskDatabaseApi(
private val database: TaskDatabase,
private val session: SessionEntity,
) {
private val api: Map<String, BaseApi> = mapOf(
ClassEntity.TABLE_NAME to ClassApi(this.database.classDao()),
PersonEntity.TABLE_NAME to PersonApi(this.database.personDao()),
SessionEntity.TABLE_NAME to SessionApi(this.database.sessionDao()),
SubjectEntity.TABLE_NAME to SubjectApi(this.database.subjectDao()),
TaskEntity.TABLE_NAME to TaskApi(this.database.taskDao()),
ValidationEntity.TABLE_NAME to ValidationApi(this.database.validationDao()),
ClassEntity.TABLE_NAME to ClassApi(this.database.classDao(), session),
PersonEntity.TABLE_NAME to PersonApi(this.database.personDao(), session),
SessionEntity.TABLE_NAME to SessionApi(this.database.sessionDao(), session),
SubjectEntity.TABLE_NAME to SubjectApi(this.database.subjectDao(), session),
TaskEntity.TABLE_NAME to TaskApi(this.database.taskDao(), session),
ValidationEntity.TABLE_NAME to ValidationApi(this.database.validationDao(), session),
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao()),
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao(), session),
RelationPersonSessionSubjectEntity.TABLE_NAME to RelationPersonSessionSubjectApi(this.database.relationPersonSessionSubjectDao(), session),
)
/**

View file

@ -1,7 +1,9 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.ClassEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
class ClassApi(dao: BaseDao<ClassEntity>) : BaseJsonApi<ClassEntity>(dao)
class ClassApi(dao: BaseTaskDao<ClassEntity>, session: SessionEntity) :
BaseTaskApi<ClassEntity>(dao, session)

View file

@ -1,7 +1,9 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
class PersonApi(dao: BaseDao<PersonEntity>) : BaseJsonApi<PersonEntity>(dao)
class PersonApi(dao: BaseTaskDao<PersonEntity>, session: SessionEntity) :
BaseTaskApi<PersonEntity>(dao, session)

View file

@ -1,7 +1,10 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
class RelationClassPersonApi(dao: BaseDao<RelationClassPersonEntity>) : BaseJsonApi<RelationClassPersonEntity>(dao)
class RelationClassPersonApi(dao: BaseTaskDao<RelationClassPersonEntity>, session: SessionEntity) :
BaseTaskApi<RelationClassPersonEntity>(dao, session)

View file

@ -0,0 +1,10 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
class RelationPersonSessionSubjectApi(dao: BaseTaskDao<RelationPersonSessionSubjectEntity>, session: SessionEntity) :
BaseTaskApi<RelationPersonSessionSubjectEntity>(dao, session)

View file

@ -1,7 +1,8 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
class SessionApi(dao: BaseDao<SessionEntity>) : BaseJsonApi<SessionEntity>(dao)
class SessionApi(dao: BaseTaskDao<SessionEntity>, session: SessionEntity) :
BaseTaskApi<SessionEntity>(dao, session)

View file

@ -1,7 +1,10 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.SubjectEntity
class SubjectApi(dao: BaseDao<SubjectEntity>) : BaseJsonApi<SubjectEntity>(dao)
class SubjectApi(dao: BaseTaskDao<SubjectEntity>, session: SessionEntity) :
BaseTaskApi<SubjectEntity>(dao, session)

View file

@ -1,7 +1,10 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.TaskEntity
class TaskApi(dao: BaseDao<TaskEntity>) : BaseJsonApi<TaskEntity>(dao)
class TaskApi(dao: BaseTaskDao<TaskEntity>, session: SessionEntity) :
BaseTaskApi<TaskEntity>(dao, session)

View file

@ -1,7 +1,10 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.ValidationEntity
class ValidationApi(dao: BaseDao<ValidationEntity>) : BaseJsonApi<ValidationEntity>(dao)
class ValidationApi(dao: BaseTaskDao<ValidationEntity>, session: SessionEntity) :
BaseTaskApi<ValidationEntity>(dao, session)

View file

@ -10,23 +10,23 @@ interface BaseApi {
* Handle the HEAD request
* This is used to check if a data exists in the database
*/
fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
/**
* Handle the GET request
* This is used to get data from the database
* This is used to get all the data of a table from the database
*/
fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
/**
* Handle the POST request
* This is used to insert data into the database
*/
fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
/**
* Handle the PUT request
* This is used to delete data from the database
*/
fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
}

View file

@ -1,6 +1,6 @@
package com.faraphel.tasks_valider.database.api.entities.base
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
@ -11,7 +11,7 @@ import fi.iki.elonen.NanoHTTPD
* This is preconfigured to handle JSON data.
* @param Entity the entity type to handle
*/
abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>) : BaseApi {
abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseCronDao<Entity>) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
@ -20,9 +20,9 @@ abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>)
// Requests
override fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val obj = parser.fromJson<Entity>(
session.inputStream.bufferedReader().readText(),
httpSession.inputStream.bufferedReader().readText(),
this.entityTypeToken.type
)
val exists = this.dao.exists(obj)
@ -34,7 +34,7 @@ abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>)
)
}
override fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
@ -42,9 +42,9 @@ abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>)
)
}
override fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val obj = parser.fromJson<Entity>(
session.inputStream.bufferedReader().readText(),
httpSession.inputStream.bufferedReader().readText(),
this.entityTypeToken.type
)
val id = this.dao.insert(obj)
@ -56,9 +56,9 @@ abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>)
)
}
override fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val obj = parser.fromJson<Entity>(
session.inputStream.bufferedReader().readText(),
httpSession.inputStream.bufferedReader().readText(),
this.entityTypeToken.type
)
val count = this.dao.delete(obj)

View file

@ -0,0 +1,49 @@
package com.faraphel.tasks_valider.database.api.entities.base
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import fi.iki.elonen.NanoHTTPD
/**
* A base for the API to handle the database operations.
* This is preconfigured to handle data for Task objects.
* @param Entity the entity type to handle
*/
abstract class BaseTaskApi<Entity: BaseEntity>(
private val dao: BaseTaskDao<Entity>,
private val session: SessionEntity,
) : BaseJsonApi<Entity>(dao) {
companion object {
private val parser = Gson() ///< The JSON parser
}
private val entityTypeToken: TypeToken<Entity> = object: TypeToken<Entity>() {} ///< the type of the managed entity
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val obj = parser.fromJson<Entity>(
httpSession.inputStream.bufferedReader().readText(),
this.entityTypeToken.type
)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
}

View file

@ -2,17 +2,27 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.ClassEntity
import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
@Dao
interface ClassDao : BaseDao<ClassEntity> {
interface ClassDao : BaseTaskDao<ClassEntity> {
@Query("SELECT * FROM ${ClassEntity.TABLE_NAME}")
override fun getAll(): List<ClassEntity>
@Query(
"SELECT * FROM ${ClassEntity.TABLE_NAME} " +
"JOIN ${SessionEntity.TABLE_NAME} " +
"ON ${SessionEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
"WHERE ${SessionEntity.TABLE_NAME}.id = :sessionId"
)
override fun getAllBySession(sessionId: Long): List<ClassEntity>
/**
* Get the object from its identifier
*/
@ -31,7 +41,7 @@ interface ClassDao : BaseDao<ClassEntity> {
* @param id the id of the class
*/
@Query(
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
"SELECT ${PersonEntity.TABLE_NAME}.* FROM ${PersonEntity.TABLE_NAME} " +
"JOIN ${RelationClassPersonEntity.TABLE_NAME} " +
"ON ${PersonEntity.TABLE_NAME}.id = ${RelationClassPersonEntity.TABLE_NAME}.student_id " +
"WHERE ${RelationClassPersonEntity.TABLE_NAME}.class_id = :id"

View file

@ -2,11 +2,13 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.*
@Dao
interface PersonDao : BaseDao<PersonEntity> {
interface PersonDao : BaseTaskDao<PersonEntity> {
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME}")
override fun getAll(): List<PersonEntity>
@ -16,6 +18,19 @@ interface PersonDao : BaseDao<PersonEntity> {
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id")
fun getById(id: Long): PersonEntity?
@Query(
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
"WHERE id IN (" +
"SELECT student_id FROM ${RelationClassPersonEntity.TABLE_NAME} " +
"JOIN ${ClassEntity.TABLE_NAME} " +
"ON ${RelationClassPersonEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
"JOIN ${SessionEntity.TABLE_NAME} " +
"ON ${SessionEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
"WHERE ${SessionEntity.TABLE_NAME}.id = :sessionId" +
")"
)
override fun getAllBySession(sessionId: Long): List<PersonEntity>
/**
* Allow to get all the classes the person is attending as a student
*/

View file

@ -2,22 +2,35 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.ClassEntity
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
@Dao
interface RelationClassPersonDao : BaseDao<RelationClassPersonEntity> {
interface RelationClassPersonDao : BaseTaskDao<RelationClassPersonEntity> {
@Query("SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME}")
override fun getAll(): List<RelationClassPersonEntity>
@Query(
"SELECT ${RelationClassPersonEntity.TABLE_NAME}.* FROM ${RelationClassPersonEntity.TABLE_NAME} " +
"JOIN ${ClassEntity.TABLE_NAME} " +
"ON ${RelationClassPersonEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
"JOIN ${SessionEntity.TABLE_NAME} " +
"ON ${SessionEntity.TABLE_NAME}.class_id = ${ClassEntity.TABLE_NAME}.id " +
"WHERE ${SessionEntity.TABLE_NAME}.id = :sessionId"
)
override fun getAllBySession(sessionId: Long): List<RelationClassPersonEntity>
/**
* Get the object from its identifiers
*/
@Query(
"SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME} " +
"WHERE " +
"class_id = :classId AND " +
"student_id = :studentId"
"WHERE class_id = :classId " +
"AND student_id = :studentId"
)
fun getById(classId: Long, studentId: Long): RelationClassPersonEntity?
}

View file

@ -0,0 +1,32 @@
package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
@Dao
interface RelationPersonSessionSubjectDao : BaseTaskDao<RelationPersonSessionSubjectEntity> {
@Query("SELECT * FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME}")
override fun getAll(): List<RelationPersonSessionSubjectEntity>
@Query(
"SELECT * FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
"WHERE session_id = :sessionId"
)
override fun getAllBySession(sessionId: Long): List<RelationPersonSessionSubjectEntity>
/**
* Get the object from its identifiers
*/
@Query(
"SELECT * FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
"WHERE student_id = :studentId " +
"AND session_id = :sessionId " +
"AND subject_id = :subjectId"
)
fun getById(studentId: Long, sessionId: Long, subjectId: Long): RelationPersonSessionSubjectEntity?
}

View file

@ -2,14 +2,20 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.ClassEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
@Dao
interface SessionDao : BaseDao<SessionEntity> {
interface SessionDao : BaseTaskDao<SessionEntity> {
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME}")
override fun getAll(): List<SessionEntity>
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE id = :sessionId")
override fun getAllBySession(sessionId: Long): List<SessionEntity>
/**
* Get the object from its identifier
*/

View file

@ -2,15 +2,28 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.ClassEntity
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
import com.faraphel.tasks_valider.database.entities.SubjectEntity
import com.faraphel.tasks_valider.database.entities.TaskEntity
@Dao
interface SubjectDao : BaseDao<SubjectEntity> {
interface SubjectDao : BaseTaskDao<SubjectEntity> {
@Query("SELECT * FROM ${SubjectEntity.TABLE_NAME}")
override fun getAll(): List<SubjectEntity>
@Query(
"SELECT * FROM ${SubjectEntity.TABLE_NAME} " +
"WHERE id IN (" +
"SELECT subject_id FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
"WHERE session_id = :sessionId" +
")"
)
override fun getAllBySession(sessionId: Long): List<SubjectEntity>
/**
* Get the object from its identifier
*/

View file

@ -2,15 +2,27 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
import com.faraphel.tasks_valider.database.entities.TaskEntity
import com.faraphel.tasks_valider.database.entities.ValidationEntity
@Dao
interface TaskDao : BaseDao<TaskEntity> {
interface TaskDao : BaseTaskDao<TaskEntity> {
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME}")
override fun getAll(): List<TaskEntity>
@Query(
"SELECT * FROM ${TaskEntity.TABLE_NAME} " +
"WHERE subject_id IN (" +
"SELECT subject_id FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
"WHERE session_id = :sessionId" +
")"
)
override fun getAllBySession(sessionId: Long): List<TaskEntity>
/**
* Get the object from its identifier
*/

View file

@ -2,14 +2,29 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
import com.faraphel.tasks_valider.database.dao.base.BaseDao
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.dao.base.BaseSessionDao
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
import com.faraphel.tasks_valider.database.entities.TaskEntity
import com.faraphel.tasks_valider.database.entities.ValidationEntity
@Dao
interface ValidationDao : BaseDao<ValidationEntity> {
interface ValidationDao : BaseTaskDao<ValidationEntity> {
@Query("SELECT * FROM ${ValidationEntity.TABLE_NAME}")
override fun getAll(): List<ValidationEntity>
@Query(
"SELECT * FROM ${ValidationEntity.TABLE_NAME} " +
"JOIN ${TaskEntity.TABLE_NAME} " +
"ON ${ValidationEntity.TABLE_NAME}.task_id = ${TaskEntity.TABLE_NAME}.id " +
"WHERE ${TaskEntity.TABLE_NAME}.subject_id IN (" +
"SELECT subject_id FROM ${RelationPersonSessionSubjectEntity.TABLE_NAME} " +
"WHERE session_id = :sessionId" +
")"
)
override fun getAllBySession(sessionId: Long): List<ValidationEntity>
/**
* Get the object from its identifiers
*/

View file

@ -3,17 +3,13 @@ package com.faraphel.tasks_valider.database.dao.base
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Update
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
/**
* A base DAO to handle the database operations.
* A base DAO to handle the database operations (CRON).
* @param Entity the entity to handle
*/
interface BaseDao<Entity> {
interface BaseCronDao<Entity> {
/**
* Check if the entities exists in the database.
*/

View file

@ -0,0 +1,11 @@
package com.faraphel.tasks_valider.database.dao.base
/**
* The base for a DAO that is inside a session scope.
*/
interface BaseSessionDao<Entity> {
/**
* Return all the objects concerned by a session.
*/
fun getAllBySession(sessionId: Long): List<Entity>
}

View file

@ -0,0 +1,6 @@
package com.faraphel.tasks_valider.database.dao.base
/**
* The base for a DAO for the task project.
*/
interface BaseTaskDao<Entity>: BaseCronDao<Entity>, BaseSessionDao<Entity>

View file

@ -0,0 +1,48 @@
package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
/**
* Represent the relation that associate a subject to a person for a specific session.
*/
@Entity(
tableName = RelationPersonSessionSubjectEntity.TABLE_NAME,
primaryKeys = [
"student_id",
"session_id",
"subject_id"
],
foreignKeys = [
ForeignKey(
entity = PersonEntity::class,
parentColumns = ["id"],
childColumns = ["student_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = SessionEntity::class,
parentColumns = ["id"],
childColumns = ["session_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = SubjectEntity::class,
parentColumns = ["id"],
childColumns = ["subject_id"],
onDelete = ForeignKey.CASCADE
),
]
)
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,
) : BaseEntity() {
companion object {
const val TABLE_NAME = "relation_person_session_subject"
}
}

View file

@ -114,4 +114,20 @@ fun populateTaskDatabaseTest(database: TaskDatabase) {
val taskA3 = database.taskDao().getById(taskA3Id)!!
val taskB1 = database.taskDao().getById(taskB1Id)!!
val taskB2 = database.taskDao().getById(taskB2Id)!!
}
fun populateSubjectSessionPersonTest(database: TaskDatabase, session: SessionEntity) {
// get the list of available subjects
val subjects = database.subjectDao().getAll()
// give a random subject to everyone in the session
database.personDao().getAllBySession(session.id).forEach { person ->
// get a random subject
val subject = subjects.random()
// insert a new subject for a person for a specific session
database.relationPersonSessionSubjectDao().insert(
RelationPersonSessionSubjectEntity(person.id, session.id, subject.id)
)
}
}

View file

@ -1,6 +1,8 @@
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.material3.Button
import androidx.compose.material3.Text
@ -9,16 +11,18 @@ import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.faraphel.tasks_valider.database.TaskDatabase
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun CommunicationInternetSelectScreen(activity: Activity) {
fun CommunicationInternetSelectScreen(activity: Activity, database: TaskDatabase) {
val controller = rememberNavController()
NavHost(navController = controller, startDestination = "mode") {
composable("mode") { CommunicationInternetSelectContent(controller) }
composable("client") { CommunicationInternetClientScreen(activity) }
composable("server") { CommunicationInternetServerScreen(activity) }
composable("server") { CommunicationInternetServerScreen(activity, database) }
}
}

View file

@ -1,88 +1,114 @@
package com.faraphel.tasks_valider.ui.screen.communication.internet
import android.app.Activity
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
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.runtime.*
import androidx.compose.ui.text.input.KeyboardType
import androidx.room.Room
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.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.populateTaskDatabaseTest
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.DEFAULT_SERVER_PORT
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
import java.time.Instant
/**
* Screen for the host to configure the server
*/
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun CommunicationInternetServerScreen(activity: Activity) {
fun CommunicationInternetServerScreen(
activity: Activity,
database: TaskDatabase,
) {
val controller = rememberNavController()
val adminPersonEntity = remember { mutableStateOf<PersonEntity?>(null) }
// if the admin person is not created, prompt the user for the admin information
if (adminPersonEntity.value == null) {
return AuthentificationServerScreen(adminPersonEntity)
}
val client = remember { mutableStateOf<TaskClient?>(null) }
// if the client to the server is not created, prompt the user for the server configuration
if (client.value == null) {
return CommunicationInternetServerContent(
activity,
adminPersonEntity.value!!,
client
)
NavHost(navController = controller, startDestination = "authentication") {
composable("authentication") {
// if the admin person is not created, prompt the user for the admin information
if (adminPersonEntity.value == null) AuthentificationServerScreen(adminPersonEntity)
else controller.navigate("configuration")
}
composable("configuration") {
if (client.value == null)
CommunicationInternetServerContent(
database,
adminPersonEntity.value!!,
client
)
else controller.navigate("session")
}
composable("session") {
TaskSessionScreen(activity, client.value!!)
}
}
// otherwise, go to the base tasks screen
return TaskSessionScreen(activity, client.value!!)
}
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun CommunicationInternetServerContent(
activity: Activity,
database: TaskDatabase,
adminPersonEntity: PersonEntity,
client: MutableState<TaskClient?>
) {
val expandedStudentList = remember { mutableStateOf(false) }
val classes = remember { mutableStateOf<List<ClassEntity>?>(null) }
val selectedClass = remember { mutableStateOf<ClassEntity?>(null) }
val areClassesExpanded = remember { mutableStateOf(false) }
val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
LaunchedEffect(true) {
// refresh the list of classes
Thread { refreshClasses(database, classes) }.start()
}
Column {
// student list
Button(onClick = { expandedStudentList.value = !expandedStudentList.value }) {
Text(text = "Select Students List")
// classes
Button(onClick = { areClassesExpanded.value = !areClassesExpanded.value }) {
Row {
Text(text = "Class")
// display the selected class, if selected
if (selectedClass.value != null)
Text(text = selectedClass.value!!.name)
}
}
DropdownMenu(
expanded = expandedStudentList.value,
onDismissRequest = { expandedStudentList.value = false }
expanded = areClassesExpanded.value,
onDismissRequest = { areClassesExpanded.value = false }
) {
// TODO(Faraphel): student lists should be loaded from the database or a file
DropdownMenuItem(
text = { Text("ISRI") },
onClick = {}
)
DropdownMenuItem(
text = { Text("MIAGE") },
onClick = {}
)
classes.value?.forEach { class_ ->
DropdownMenuItem(
text = { Text(class_.name) },
onClick = {
selectedClass.value = class_
areClassesExpanded.value = false
}
)
}
}
// server port
@ -97,35 +123,57 @@ fun CommunicationInternetServerContent(
}
)
Button(onClick = {
Thread { // a thread is used for networking
// Reset the database | TODO(Faraphel): only for testing purpose
activity.deleteDatabase("local")
// check if a class is selected
if (selectedClass.value != null)
// button to create the server
Button(onClick = {
Thread { // a thread is used for networking
// Create the database
val database = Room.databaseBuilder(
activity,
TaskDatabase::class.java,
"local"
).build()
// Insert the admin in the database
database.personDao().insert(adminPersonEntity)
// Populate the database with test data
// TODO(Faraphel): remove test data
populateTaskDatabaseTest(database)
// 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)!!
// Insert the admin in the database
database.personDao().insert(adminPersonEntity)
// TODO(Faraphel): remove, this is a test function
Thread {
populateSubjectSessionPersonTest(database, session)
}.let { thread ->
thread.start()
thread.join()
}
// Create the server
Log.i("room-server", "creating the server")
val server = TaskServer(serverPort.intValue, database, adminPersonEntity)
server.start()
// Create the server
Log.i("room-server", "creating the server")
val server = TaskServer(
serverPort.intValue,
database,
session,
adminPersonEntity,
)
server.start()
// Get the client from the server
client.value = server.getAdminClient()
}.start()
}) {
Text("Create")
}
// 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()
}

View file

@ -1,7 +1,9 @@
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.Column
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@ -13,6 +15,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
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
@ -21,10 +24,13 @@ import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationW
* CommunicationController is the main controller for the communication screen.
* It is responsible for handling the navigation between the different communication methods.
* It is also responsible for initializing the communication methods.
*
* @param activity: The activity that hosts the communication screen.
* @param database: the database.
*/
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun CommunicationModeSelectionScreen(activity: Activity) {
fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase) {
val controller = rememberNavController()
NavHost(
@ -35,7 +41,7 @@ fun CommunicationModeSelectionScreen(activity: Activity) {
CommunicationSelectContent(controller, activity)
}
composable("internet") {
CommunicationInternetSelectScreen(activity)
CommunicationInternetSelectScreen(activity, database)
}
composable("wifi-p2p") {
val bwfManager = BwfManager.fromActivity(activity)

View file

@ -23,19 +23,19 @@ import com.google.gson.reflect.TypeToken
@Composable
fun TaskSessionScreen(activity: Activity, client: TaskClient) {
val students = remember { mutableStateOf<List<PersonEntity>?>(null) }
val selected_student = remember { mutableStateOf<PersonEntity?>(null) }
val selectedStudent = remember { mutableStateOf<PersonEntity?>(null) }
// if the groups are not yet defined, refresh the list
if (students.value == null)
return Thread { refreshStudents(activity, client, students) }.start()
if (selected_student.value != null)
return TaskStudentScreen(selected_student.value!!)
if (selectedStudent.value != null)
return TaskStudentScreen(activity, client, selectedStudent.value!!)
Column {
// if the groups have already been defined, display them
for (student in students.value!!) {
Button(onClick = { selected_student.value = student }) {
Button(onClick = { selectedStudent.value = student }) {
Text(text = student.fullName())
}
}

View file

@ -1,14 +1,89 @@
package com.faraphel.tasks_valider.ui.screen.task
import android.app.Activity
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.faraphel.tasks_valider.database.entities.PersonEntity
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.faraphel.tasks_valider.connectivity.task.TaskClient
import com.faraphel.tasks_valider.database.entities.*
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
/**
* This screen represent a student
* @param student the student object
*/
@Composable
fun TaskStudentScreen(student: PersonEntity) {
Text(text = student.fullName())
fun TaskStudentScreen(activity: Activity, client: TaskClient, student: PersonEntity) {
val tasks = remember { mutableStateOf<List<TaskEntity>?>(null) }
if (tasks.value == null)
Thread { refreshTasks(activity, client, student, tasks) }.start()
Column {
Text(text = student.fullName())
tasks.value?.forEach { task ->
Button(onClick = {}) {
Column {
Text(task.title)
task.description?.let { description -> Text(description) }
}
}
}
}
}
fun refreshTasks(
activity: Activity,
client: TaskClient,
student: PersonEntity,
tasks: MutableState<List<TaskEntity>?>
) {
val jsonParser = Gson()
// try to obtain the list of subject
val responseSubjects = client.get("entities/" + RelationPersonSessionSubjectEntity.TABLE_NAME)
// in case of error, notify it
if (!responseSubjects.isSuccessful)
return activity.runOnUiThread { Toast.makeText(activity, responseSubjects.message, Toast.LENGTH_LONG).show() }
// parse the list of subjects
val allPersonSessionSubjects = jsonParser.fromJson<List<RelationPersonSessionSubjectEntity>>(
responseSubjects.body.string(),
object : TypeToken<List<RelationPersonSessionSubjectEntity>>(){}.type
)
// get the subject that the student is using
val relationPersonSessionSubjects = allPersonSessionSubjects.firstOrNull { relation -> relation.studentId == student.id }
if (relationPersonSessionSubjects == null)
// TODO(Faraphel): should be able to assign a subject ?
return activity.runOnUiThread { Toast.makeText(activity, "No subject assigned", Toast.LENGTH_LONG).show() }
// try to obtain the list of tasks
val responseTasks = client.get("entities/" + TaskEntity.TABLE_NAME)
// in case of error, notify it
if (!responseTasks.isSuccessful)
return activity.runOnUiThread { Toast.makeText(activity, responseTasks.message, Toast.LENGTH_LONG).show() }
// parse the list of subjects
val allTasks = jsonParser.fromJson<List<TaskEntity>>(
responseTasks.body.string(),
object : TypeToken<List<TaskEntity>>(){}.type
)
// get the tasks that are linked to this subject
tasks.value = allTasks.filter { task ->
task.subjectId == relationPersonSessionSubjects.subjectId
}.sortedBy { task ->
task.order
}
}