implemented a better API for the client and the quick validation system

This commit is contained in:
Faraphel 2024-06-11 17:08:45 +02:00
parent 48a3932f64
commit e12339abb5
58 changed files with 675 additions and 323 deletions

View file

@ -7,14 +7,14 @@ 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.connectivity.bwd.BwdManager
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
private var bwdManager: BwdManager? = null ///< the WiFi-Direct helper
private lateinit var database: TaskDatabase ///< the database manager
@RequiresApi(Build.VERSION_CODES.O)
@ -50,13 +50,13 @@ class MainActivity : ComponentActivity() {
super.onResume()
// enable the WiFi-Direct events
this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER)
this.registerReceiver(this.bwdManager, BwdManager.ALL_INTENT_FILTER)
}
override fun onPause() {
super.onPause()
// disable the WiFi-Direct events
this.unregisterReceiver(this.bwfManager)
this.unregisterReceiver(this.bwdManager)
}
}

View file

@ -1,4 +1,4 @@
package com.faraphel.tasks_valider.connectivity.bwf
package com.faraphel.tasks_valider.connectivity.bwd
import android.Manifest
import android.app.Activity
@ -10,7 +10,7 @@ import android.content.pm.PackageManager
import android.net.wifi.p2p.*
import android.util.Log
import androidx.compose.runtime.mutableStateOf
import com.faraphel.tasks_valider.connectivity.bwf.error.*
import com.faraphel.tasks_valider.connectivity.bwd.error.*
/**
@ -22,7 +22,7 @@ import com.faraphel.tasks_valider.connectivity.bwf.error.*
* @param manager The WiFi-Direct manager
* @param channel The WiFi-Direct channel
*/
class BwfManager(
class BwdManager(
private var manager: WifiP2pManager,
private var channel: WifiP2pManager.Channel,
) : BroadcastReceiver() {
@ -41,11 +41,11 @@ class BwfManager(
* Create a new BwfManager from an activity.
* @param activity The activity to create the manager from
*/
fun fromActivity(activity: Activity): BwfManager {
fun fromActivity(activity: Activity): BwdManager {
// check if the system support WiFi-Direct
if (this.isSupported(activity)) {
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
throw BwfNotSupportedException()
throw BwdNotSupportedException()
}
// TODO(Faraphel): more check on permissions
@ -62,11 +62,11 @@ class BwfManager(
// get the WiFi-Direct manager
val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
?: throw BwfPermissionException()
?: throw BwdPermissionException()
// get the WiFi-Direct channel
val channel = manager.initialize(activity, activity.mainLooper, null)
return BwfManager(manager, channel)
return BwdManager(manager, channel)
// NOTE(Faraphel): the broadcast receiver should be registered in the activity onResume
}
@ -83,7 +83,7 @@ class BwfManager(
fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) =
this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfConnectException(reason)
override fun onFailure(reason: Int) = throw BwdConnectException(reason)
})
/**
@ -105,7 +105,7 @@ class BwfManager(
fun discoverPeers(callback: () -> Unit = {}) =
this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfDiscoverException(reason)
override fun onFailure(reason: Int) = throw BwdDiscoverException(reason)
})
/**
@ -133,7 +133,7 @@ class BwfManager(
fun createGroup(callback: () -> Unit = {}) =
this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfCreateGroupException(reason)
override fun onFailure(reason: Int) = throw BwdCreateGroupException(reason)
})
/**
@ -143,7 +143,7 @@ class BwfManager(
fun removeGroup(callback: () -> Unit = {}) =
this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfRemoveGroupException(reason)
override fun onFailure(reason: Int) = throw BwdRemoveGroupException(reason)
})
/**
@ -161,7 +161,7 @@ class BwfManager(
this.requestGroupInfo { group ->
// if a group exist, quit it
if (group != null)
this.removeGroup { this@BwfManager.createGroup(callback) }
this.removeGroup { this@BwdManager.createGroup(callback) }
else
// create the group
this.createGroup(callback)

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdConnectException(
reason: Int
) : BwdException("Cannot connect to the peer. Reason: $reason")

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdCreateGroupException (
reason: Int
) : BwdException("Could not create the group : $reason")

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdDiscoverException(
reason: Int
) : BwdException("Could not discover peers : $reason")

View file

@ -1,9 +1,9 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
package com.faraphel.tasks_valider.connectivity.bwd.error
/**
* Base Exception for everything concerning the WifiP2pHelper class
*/
open class BwfException(
open class BwdException(
override val message: String?
) : Exception(message)

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdInvalidActionException(
action: String
) : BwdException("This WiFi-Direct action is not supported : $action")

View file

@ -0,0 +1,4 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdNotSupportedException :
BwdException("WiFi-Direct is not supported on this device.")

View file

@ -0,0 +1,4 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdPermissionException :
BwdException("WiFi-Direct requires permissions to work properly. Please grant the permissions.")

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwd.error
class BwdRemoveGroupException (
reason: Int
) : BwdException("Could not remove the group : $reason")

View file

@ -1,5 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfConnectException(
reason: Int
) : BwfException("Cannot connect to the peer. Reason: $reason")

View file

@ -1,5 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfCreateGroupException (
reason: Int
) : BwfException("Could not create the group : $reason")

View file

@ -1,5 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfDiscoverException(
reason: Int
) : BwfException("Could not discover peers : $reason")

View file

@ -1,5 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfInvalidActionException(
action: String
) : BwfException("This WiFi-Direct action is not supported : $action")

View file

@ -1,4 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfNotSupportedException :
BwfException("WiFi-Direct is not supported on this device.")

View file

@ -1,4 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfPermissionException :
BwfException("WiFi-Direct requires permissions to work properly. Please grant the permissions.")

View file

@ -1,5 +0,0 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfRemoveGroupException (
reason: Int
) : BwfException("Could not remove the group : $reason")

View file

@ -1,16 +1,11 @@
package com.faraphel.tasks_valider.connectivity.task
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.*
/**
* A client to handle the room connection.
* A client to handle the room connection and access the API
* @param address the address of the server
* @param port the port of the server
* @param baseCookies list of cookies to use (optional)
@ -20,86 +15,15 @@ class TaskClient(
private val port: Int,
private val baseCookies: List<okhttp3.Cookie> = listOf()
) {
private val baseUrl = "http://$address:$port"
private val client = OkHttpClient().newBuilder()
.cookieJar(
// TODO(Faraphel): should be moved into another object
object : okhttp3.CookieJar {
private val cookies = baseCookies.toMutableList() ///< list of cookies
private val httpClient = TaskEntityHttpClient(address, port, baseCookies)
override fun loadForRequest(url: HttpUrl): List<okhttp3.Cookie> {
return this.cookies
}
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) {
this.cookies.addAll(cookies)
}
}
).callTimeout(30.seconds)
.build()
val clientApi = ClassClientApi(httpClient)
val personApi = PersonClientApi(httpClient)
val sessionApi = SessionClientApi(httpClient)
val subjectApi = SubjectClientApi(httpClient)
val taskApi = TaskClientApi(httpClient)
val validationApi = ValidationClientApi(httpClient)
// TODO(Faraphel): automatically convert content to the correct type ?
/**
* Return a basic request to the server
* @param endpoint the endpoint of the server
*/
private fun baseRequestBuilder(endpoint: String): okhttp3.Request.Builder =
okhttp3.Request.Builder().url("$baseUrl/$endpoint")
/**
* Run a HEAD request
* @param endpoint the endpoint of the server
*/
fun head(endpoint: String): okhttp3.Request =
this.baseRequestBuilder(endpoint).head().build()
/**
* Run a GET request
* @param endpoint the endpoint of the server
*/
fun get(endpoint: String): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.get()
.build()
).execute()
/**
* Run a POST request
* @param endpoint the endpoint of the server
* @param content the content of the request
* @param type the type of the content
*/
fun post(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.post(content.toRequestBody(type.toMediaType()))
.build()
).execute()
/**
* Run a PATCH request
* @param endpoint the endpoint of the server
* @param content the content of the request
* @param type the type of the content
*/
fun patch(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.patch(content.toRequestBody(type.toMediaType()))
.build()
).execute()
/**
* Run a DELETE request
* @param endpoint the endpoint of the server
* @param content the content of the request
* @param type the type of the content
*/
fun delete(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.delete(content.toRequestBody(type.toMediaType()))
.build()
).execute()
val relationClassPersonApi = RelationClassPersonClientApi(httpClient)
val relationPersonSessionSubjectApi = RelationPersonSessionSubjectClientApi(httpClient)
}

View file

@ -3,7 +3,7 @@ package com.faraphel.tasks_valider.connectivity.task
import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi
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.api.server.DatabaseApi
import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity
import fi.iki.elonen.NanoHTTPD
@ -23,7 +23,7 @@ class TaskServer(
private val adminPersonEntity: PersonEntity,
) : NanoHTTPD(port) {
private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager
private val databaseApi = TaskDatabaseApi(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
/**

View file

@ -3,7 +3,7 @@ package com.faraphel.tasks_valider.database
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.faraphel.tasks_valider.database.converters.InstantConverter
import com.faraphel.tasks_valider.utils.converters.InstantConverter
import com.faraphel.tasks_valider.database.dao.*
import com.faraphel.tasks_valider.database.entities.*

View file

@ -0,0 +1,98 @@
package com.faraphel.tasks_valider.database.api.client
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import kotlin.time.Duration.Companion.seconds
/**
* An HTTP client to handle the room connection.
* @param address the address of the server
* @param port the port of the server
* @param baseCookies list of cookies to use (optional)
*/
class TaskEntityHttpClient(
private val address: String,
private val port: Int,
private val baseCookies: List<okhttp3.Cookie> = listOf()
) {
// the base url for the server
private val baseUrl = "http://$address:$port"
// the HTTP client
private val client = OkHttpClient().newBuilder()
.cookieJar(
object : okhttp3.CookieJar {
private val cookies = baseCookies.toMutableList() ///< list of cookies
override fun loadForRequest(url: HttpUrl) = this.cookies
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) { this.cookies.addAll(cookies) }
}
)
.callTimeout(30.seconds)
.build()
/**
* Return a basic request to the server
* @param endpoint the endpoint of the server
*/
private fun baseRequestBuilder(endpoint: String): okhttp3.Request.Builder =
okhttp3.Request.Builder().url("$baseUrl/$endpoint")
/**
* Run a HEAD request
* @param endpoint the endpoint of the server
*/
fun head(endpoint: String): okhttp3.Request =
this.baseRequestBuilder(endpoint).head().build()
/**
* Run a GET request
* @param endpoint the endpoint of the server
*/
fun get(endpoint: String): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.get()
.build()
).execute()
/**
* Run a POST request
* @param endpoint the endpoint of the server
* @param content the content of the request
* @param type the type of the content
*/
fun post(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.post(content.toRequestBody(type.toMediaType()))
.build()
).execute()
/**
* Run a PATCH request
* @param endpoint the endpoint of the server
* @param content the content of the request
* @param type the type of the content
*/
fun patch(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.patch(content.toRequestBody(type.toMediaType()))
.build()
).execute()
/**
* Run a DELETE request
* @param endpoint the endpoint of the server
* @param content the content of the request
* @param type the type of the content
*/
fun delete(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
this.client.newCall(
this.baseRequestBuilder(endpoint)
.delete(content.toRequestBody(type.toMediaType()))
.build()
).execute()
}

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.ClassEntity
class ClassClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<ClassEntity>(
client,
ClassEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.PersonEntity
class PersonClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<PersonEntity>(
client,
PersonEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
class RelationClassPersonClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<RelationClassPersonEntity>(
client,
RelationClassPersonEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
class RelationPersonSessionSubjectClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<RelationPersonSessionSubjectEntity>(
client,
RelationPersonSessionSubjectEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.SessionEntity
class SessionClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<SessionEntity>(
client,
SessionEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.SubjectEntity
class SubjectClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<SubjectEntity>(
client,
SubjectEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.TaskEntity
class TaskClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<TaskEntity>(
client,
TaskEntity::class
)

View file

@ -0,0 +1,13 @@
package com.faraphel.tasks_valider.database.api.client.entities
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.api.client.entities.base.BaseClientApi
import com.faraphel.tasks_valider.database.entities.ValidationEntity
class ValidationClientApi(
client: TaskEntityHttpClient,
) : BaseClientApi<ValidationEntity>(
client,
ValidationEntity::class
)

View file

@ -0,0 +1,102 @@
package com.faraphel.tasks_valider.database.api.client.entities.base;
import android.util.Log
import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
import com.faraphel.tasks_valider.database.entities.error.HttpException
import com.faraphel.tasks_valider.utils.parser
import com.google.gson.reflect.TypeToken
import kotlin.reflect.KClass
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
abstract class BaseClientApi<Entity: BaseEntity>(
private val client: TaskEntityHttpClient,
private val entityType: KClass<Entity>,
) {
/**
* return the API endpoint for this entity
* @return the API endpoint for this entity
*/
private fun getEndpoint(): String {
// get the property for the name of the table
val propertyTableName = entityType.companionObject!!.declaredMemberProperties.first { member ->
member.name == "TABLE_NAME"
}
// get the table name by calling the getter of the property
val tableName = propertyTableName.getter.call(entityType.companionObjectInstance)
// return the endpoint
return "entities/${tableName}"
}
/**
* return all the entities for that table
* @return all the entities for that table
* @throws java.io.IOException reading error while parsing request
* @throws HttpException error of the request
*/
fun getAll(): List<Entity> {
// try to obtain the list of validations
Log.i("base-api", this.getEndpoint())
val response = client.get(this.getEndpoint())
// in case of error, notify it
if (!response.isSuccessful)
throw HttpException(response.code)
val data = response.body.string()
Log.i("base-api", data)
// parse the list of validations
return parser.fromJson(
data,
TypeToken.getParameterized(ArrayList::class.java, entityType.java).type
)
}
/**
* create a new entity in the table
* @return the id of the object in the database
* @throws java.io.IOException reading error while parsing request
* @throws HttpException error of the request
*/
fun save(entity: Entity): Long {
// try to send the serialized entity as json
val response = client.post(
this.getEndpoint(),
parser.toJson(entity),
"application/json; charset=utf-8"
)
// in case of error, notify it
if (!response.isSuccessful)
throw HttpException(response.code)
// return the id of the object
return response.body.string().toLong()
}
/**
* delete an entity in the table
* @return the number of object deleted in the database
* @throws java.io.IOException reading error while parsing request
* @throws HttpException error of the request
*/
fun delete(entity: Entity): Long {
// try to delete the object
val response = client.delete(
this.getEndpoint(),
parser.toJson(entity),
"application/json; charset=utf-8"
)
// in case of error, notify it
if (!response.isSuccessful)
throw HttpException(response.code)
// return the id of the object
return response.body.string().toLong()
}
}

View file

@ -1,27 +1,28 @@
package com.faraphel.tasks_valider.database.api
package com.faraphel.tasks_valider.database.api.server
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.database.api.entities.*
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseDatabaseApi
import com.faraphel.tasks_valider.database.api.server.entities.*
import com.faraphel.tasks_valider.database.entities.*
import fi.iki.elonen.NanoHTTPD
class TaskDatabaseApi(
class DatabaseApi(
private val database: TaskDatabase,
private val session: SessionEntity,
) {
private val api: Map<String, BaseApi> = mapOf(
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),
private val api: Map<String, BaseDatabaseApi> = mapOf(
ClassEntity.TABLE_NAME to ClassDatabaseApi(this.database.classDao(), session),
PersonEntity.TABLE_NAME to PersonDatabaseApi(this.database.personDao(), session),
SessionEntity.TABLE_NAME to SessionDatabaseApi(this.database.sessionDao(), session),
SubjectEntity.TABLE_NAME to SubjectDatabaseApi(this.database.subjectDao(), session),
TaskEntity.TABLE_NAME to TaskDatabaseApi(database.taskDao(), session),
ValidationEntity.TABLE_NAME to ValidationDatabaseApi(this.database.validationDao(), session),
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao(), session),
RelationPersonSessionSubjectEntity.TABLE_NAME to RelationPersonSessionSubjectApi(this.database.relationPersonSessionSubjectDao(), session),
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonDatabaseApi(this.database.relationClassPersonDao(), session),
RelationPersonSessionSubjectEntity.TABLE_NAME to RelationPersonSessionSubjectDatabaseApi(this.database.relationPersonSessionSubjectDao(), session),
)
/**

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class ClassDatabaseApi(
dao: BaseTaskDao<ClassEntity>,
session: SessionEntity
) : BaseTaskApi<ClassEntity>(
) : BaseTaskDatabaseApi<ClassEntity>(
dao,
session,
ClassEntity::class.java

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class PersonDatabaseApi(
dao: BaseTaskDao<PersonEntity>,
session: SessionEntity
) : BaseTaskApi<PersonEntity>(
) : BaseTaskDatabaseApi<PersonEntity>(
dao,
session,
PersonEntity::class.java

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class RelationClassPersonDatabaseApi(
dao: BaseTaskDao<RelationClassPersonEntity>,
session: SessionEntity
) : BaseTaskApi<RelationClassPersonEntity>(
) : BaseTaskDatabaseApi<RelationClassPersonEntity>(
dao,
session,
RelationClassPersonEntity::class.java

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class RelationPersonSessionSubjectDatabaseApi(
dao: BaseTaskDao<RelationPersonSessionSubjectEntity>,
session: SessionEntity
) : BaseTaskApi<RelationPersonSessionSubjectEntity>(
) : BaseTaskDatabaseApi<RelationPersonSessionSubjectEntity>(
dao,
session,
RelationPersonSessionSubjectEntity::class.java

View file

@ -1,14 +1,14 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
class SessionApi(
class SessionDatabaseApi(
dao: BaseTaskDao<SessionEntity>,
session: SessionEntity
) : BaseTaskApi<SessionEntity>(
) : BaseTaskDatabaseApi<SessionEntity>(
dao,
session,
SessionEntity::class.java

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class SubjectDatabaseApi(
dao: BaseTaskDao<SubjectEntity>,
session: SessionEntity
) : BaseTaskApi<SubjectEntity>(
) : BaseTaskDatabaseApi<SubjectEntity>(
dao,
session,
SubjectEntity::class.java

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class TaskDatabaseApi(
dao: BaseTaskDao<TaskEntity>,
session: SessionEntity
) : BaseTaskApi<TaskEntity>(
) : BaseTaskDatabaseApi<TaskEntity>(
dao,
session,
TaskEntity::class.java

View file

@ -1,15 +1,15 @@
package com.faraphel.tasks_valider.database.api.entities
package com.faraphel.tasks_valider.database.api.server.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.server.entities.base.BaseTaskDatabaseApi
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(
class ValidationDatabaseApi(
dao: BaseTaskDao<ValidationEntity>,
session: SessionEntity
) : BaseTaskApi<ValidationEntity>(
) : BaseTaskDatabaseApi<ValidationEntity>(
dao,
session,
ValidationEntity::class.java

View file

@ -1,11 +1,11 @@
package com.faraphel.tasks_valider.database.api.entities.base
package com.faraphel.tasks_valider.database.api.server.entities.base
import fi.iki.elonen.NanoHTTPD
/**
* A base for the API to handle the database operations with an HTTP server.
*/
interface BaseApi {
interface BaseDatabaseApi {
/**
* Handle the HEAD request
* This is used to check if a data exists in the database

View file

@ -1,4 +1,4 @@
package com.faraphel.tasks_valider.database.api.entities.base
package com.faraphel.tasks_valider.database.api.server.entities.base
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
@ -7,14 +7,11 @@ import com.faraphel.tasks_valider.utils.parser
import fi.iki.elonen.NanoHTTPD
abstract class BaseTaskApi<Entity> (
abstract class BaseTaskDatabaseApi<Entity> (
private val dao: BaseTaskDao<Entity>,
private val session: SessionEntity,
private val entityType: Class<Entity>,
) : BaseApi {
private fun parseJson(data: String): Entity =
parser.fromJson(data, entityType)
) : BaseDatabaseApi {
/**
* Handle an HTTP HEAD request.
* Indicate if an object exist in the database.
@ -25,7 +22,7 @@ abstract class BaseTaskApi<Entity> (
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = this.parseJson(data)
val obj = parser.fromJson(data, entityType)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)

View file

@ -35,13 +35,28 @@ import java.time.Instant
]
)
data class ValidationEntity (
@ColumnInfo("date") val date: Instant,
@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("date") val date: Instant,
) : BaseEntity() {
companion object {
const val TABLE_NAME = "validations"
}
/**
* Construct a new ValidationEntity. Automatically set the date to today.
*/
constructor(
teacherId: Long,
studentId: Long,
taskId: Long
):
this(
teacherId = teacherId,
studentId = studentId,
taskId = taskId,
date = Instant.now()
)
}

View file

@ -1,3 +1,7 @@
package com.faraphel.tasks_valider.database.entities.base
open class BaseEntity
open class BaseEntity {
companion object {
const val TABLE_NAME = "<Undefined>"
}
}

View file

@ -0,0 +1,6 @@
package com.faraphel.tasks_valider.database.entities.error
class HttpException(
private val code: Int,
) : Exception("Http Exception: $code")

View file

@ -24,7 +24,7 @@ fun populateTaskDatabaseTest(database: TaskDatabase) {
) = database.personDao().insert(
PersonEntity(
"Billy", "Bob",
null,
"0A1A7553-9DE5-103C-B23C-630998207116",
"1234",
TaskRole.STUDENT
),

View file

@ -18,7 +18,6 @@ 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
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
@RequiresApi(Build.VERSION_CODES.O)

View file

@ -14,10 +14,7 @@ import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.compose.NavHost
@ -33,8 +30,7 @@ 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 kotlinx.coroutines.flow.MutableStateFlow
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionController
import java.time.Instant
@ -70,7 +66,7 @@ fun CommunicationInternetServerScreen(
else controller.navigate("session")
}
composable("session") {
TaskSessionScreen(
TaskSessionController(
activity,
client.value!!,
adminPersonEntity.value!!

View file

@ -19,7 +19,7 @@ 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.bwf.BwfManager
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
@ -49,8 +49,8 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase)
CommunicationInternetSelectScreen(activity, database)
}
composable("wifi-p2p") {
val bwfManager = BwfManager.fromActivity(activity)
CommunicationWifiP2pScreen(activity, bwfManager)
val bwdManager = BwdManager.fromActivity(activity)
CommunicationWifiP2pScreen(activity, bwdManager)
}
}
}
@ -61,7 +61,7 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase)
*/
@Composable
fun CommunicationSelectContent(controller: NavController, activity: Activity) {
val isWifiP2pSupported = BwfManager.isSupported(activity)
val isWifiP2pSupported = BwdManager.isSupported(activity)
Column(
modifier = Modifier.fillMaxSize(),

View file

@ -8,12 +8,12 @@ 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.bwf.BwfManager
import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
@Composable
fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager) {
fun CommunicationWifiP2pClientScreen(activity: Activity, bwdManager: BwdManager) {
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
val isConnected = remember { mutableStateOf(false) }
@ -31,25 +31,25 @@ fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager)
val config = WifiP2pConfig().apply {
deviceAddress = selectedDevice.value!!.deviceAddress
}
bwfManager.connect(config) {
bwdManager.connect(config) {
isConnected.value = true
}
return
}
// display the list of devices
CommunicationWifiP2pClientContent(bwfManager, selectedDevice)
CommunicationWifiP2pClientContent(bwdManager, selectedDevice)
}
@Composable
fun CommunicationWifiP2pClientContent(
bwfManager: BwfManager,
bwdManager: BwdManager,
selectedDevice: MutableState<WifiP2pDevice?>
) {
Column {
WifiP2pDeviceListWidget(
peers = bwfManager.statePeers.value,
peers = bwdManager.statePeers.value,
filter = { device: WifiP2pDevice -> device.isGroupOwner },
selectedDevice,
)

View file

@ -9,19 +9,19 @@ 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.bwf.BwfManager
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, bwfManager: BwfManager) {
fun CommunicationWifiP2pScreen(activity: Activity, bwdManager: BwdManager) {
val controller = rememberNavController()
NavHost(navController = controller, startDestination = "mode") {
composable("mode") { CommunicationWifiP2pSelectContent(controller) }
composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) }
composable("server") { CommunicationWifiP2pServerScreen(activity, bwfManager) }
composable("client") { CommunicationWifiP2pClientScreen(activity, bwdManager) }
composable("server") { CommunicationWifiP2pServerScreen(activity, bwdManager) }
}
}

View file

@ -1,33 +1,16 @@
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server
import android.app.Activity
import android.util.Log
import androidx.compose.foundation.layout.Column
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.ui.text.input.KeyboardType
import androidx.room.Room
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
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.PersonEntity
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
@Composable
fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) {
fun CommunicationWifiP2pServerScreen(activity: Activity, bwdManager: BwdManager) {
val client = remember { mutableStateOf<TaskClient?>(null) }
// TODO(Faraphel): fix and get a user
@ -41,7 +24,7 @@ fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager)
@Composable
fun CommunicationWifiP2pServerContent(
activity: Activity,
bwfManager: BwfManager,
bwdManager: BwdManager,
client: MutableState<TaskClient?>
) {
/*

View file

@ -1,6 +1,8 @@
package com.faraphel.tasks_valider.ui.screen.scan.qr
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
@ -17,6 +19,11 @@ import com.journeyapps.barcodescanner.DefaultDecoderFactory
@Composable
fun ScanBarcodeScreen(activity: Activity, barcode: MutableState<BarcodeResult?>) {
Box(modifier = Modifier.fillMaxSize()) {
// check and prompt for the camera permission
if (activity.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
// FIXME(Faraphel): seem to crash the application
activity.requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
// AndroidView is used because "DecoratedBarcodeView" only support the legacy view system
AndroidView(factory = {
DecoratedBarcodeView(activity).apply {

View file

@ -0,0 +1,128 @@
package com.faraphel.tasks_valider.ui.screen.task
import android.app.Activity
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.navigation.NavController
import com.faraphel.tasks_valider.connectivity.task.TaskClient
import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.faraphel.tasks_valider.database.entities.ValidationEntity
import com.faraphel.tasks_valider.ui.screen.scan.qr.ScanBarcodeScreen
import com.journeyapps.barcodescanner.BarcodeResult
import okhttp3.HttpUrl.Companion.toHttpUrl
@Composable
fun QuickValidationScreen(
controller: NavController,
activity: Activity,
client: TaskClient,
user: PersonEntity,
) {
val barcode = remember { mutableStateOf<BarcodeResult?>(null) }
// prompt for the qr code if not found
if (barcode.value == null)
return ScanBarcodeScreen(activity, barcode)
// show the content of the qr code
val studentUrl = barcode.value!!.text.toHttpUrl()
val cardId = studentUrl.pathSegments[0]
// when the barcode changed
LaunchedEffect(cardId) {
Thread {
quickValidation(
controller,
activity,
client,
user,
cardId,
)
}.start()
}
}
/**
* Validate the latest task of a user from its student card
*/
fun quickValidation(
controller: NavController,
activity: Activity,
client: TaskClient,
user: PersonEntity,
cardId: String,
) {
// action when an error occurred or everything worked
fun finish() {
activity.runOnUiThread {
// go back to the main screen
controller.navigateUp()
}
}
// requests all the persons
val allPersons = client.personApi.getAll()
// get the person with the matching card
val person = allPersons.firstOrNull { person -> person.cardId == cardId }
if (person == null) {
// tell to the user that this card is linked to nobody
activity.runOnUiThread {
Toast.makeText(activity, "No person found for that card.", Toast.LENGTH_LONG).show()
}
return finish()
}
// requests all the relation persons - subjects
val allRelationsPersonSubject = client.relationPersonSessionSubjectApi.getAll()
// get the corresponding relation
val relationPersonSubject = allRelationsPersonSubject.first { relation -> relation.studentId == person.id }
// requests all the tasks
val allTasks = client.taskApi.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()
// get the corresponding relation
val validations = allValidations.filter { validation -> validation.studentId == person.id }
// get the first task without any validation
val task = tasks.firstOrNull { task ->
// check in all the validations if the task is found
val validation = validations.firstOrNull { validation -> validation.taskId == task.id }
// keep the task if it has no validation
validation == null
}
if (task == null) {
// tell to the user the action cannot be done
activity.runOnUiThread {
Toast.makeText(activity, "There are no tasks left.", Toast.LENGTH_LONG).show()
}
return finish()
}
// create a new validation on the server
client.validationApi.save(
ValidationEntity(
teacherId=user.id,
studentId=person.id,
taskId=task.id,
)
)
// confirm to the user the action was successful
activity.runOnUiThread {
Toast.makeText(activity, "Validated \"${task.title}\".", Toast.LENGTH_LONG).show()
}
return finish()
}

View file

@ -1,24 +1,44 @@
package com.faraphel.tasks_valider.ui.screen.task
import android.app.Activity
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.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.database.entities.PersonEntity
import com.faraphel.tasks_valider.utils.parser
import com.google.gson.reflect.TypeToken
@Composable
fun TaskSessionController(
activity: Activity,
client: TaskClient,
user: PersonEntity,
) {
val controller = rememberNavController()
NavHost(
navController = controller,
startDestination = "main"
) {
composable("main") {
TaskSessionScreen(controller, activity, client, user)
}
composable("quick_validation") {
QuickValidationScreen(controller, activity, client, user)
}
}
}
/**
@ -28,6 +48,7 @@ import com.google.gson.reflect.TypeToken
*/
@Composable
fun TaskSessionScreen(
controller: NavController,
activity: Activity,
client: TaskClient,
user: PersonEntity,
@ -37,7 +58,9 @@ fun TaskSessionScreen(
// if the groups are not yet defined, refresh the list
if (students.value == null)
return Thread { refreshStudents(activity, client, students) }.start()
return LaunchedEffect(true) {
Thread { refreshStudents(activity, client, students) }.start()
}
if (selectedStudent.value != null)
return TaskStudentScreen(
@ -69,6 +92,17 @@ fun TaskSessionScreen(
Text(text = student.fullName())
}
}
// separator
Spacer(modifier = Modifier.weight(1f))
// buttons
Row {
// quick validation
Button(onClick = { controller.navigate("quick_validation") }) {
Text("Quick Validation")
}
}
}
}
@ -79,16 +113,14 @@ fun refreshStudents(
client: TaskClient,
students: MutableState<List<PersonEntity>?>
) {
// try to obtain the list of groups
val response = client.get("entities/" + PersonEntity.TABLE_NAME)
// in case of error, notify it
if (!response.isSuccessful)
return activity.runOnUiThread { Toast.makeText(activity, response.message, Toast.LENGTH_LONG).show() }
// parse the list of groups
students.value = parser.fromJson(
response.body.string(),
object : TypeToken<List<PersonEntity>>(){}.type
)
try {
// try to get all the persons in that session
students.value = client.personApi.getAll()
} catch (exception: Exception) {
// in case of error, show a message
return activity.runOnUiThread {
Log.e("students", "$exception")
Toast.makeText(activity, "Could not retrieve students.\n\n$exception", Toast.LENGTH_LONG).show()
}
}
}

View file

@ -20,7 +20,6 @@ import com.faraphel.tasks_valider.database.entities.*
import com.faraphel.tasks_valider.utils.dateTimeFormatter
import com.faraphel.tasks_valider.utils.parser
import com.google.gson.reflect.TypeToken
import java.time.Instant
/**
@ -95,7 +94,6 @@ fun TaskStudentScreen(
client,
state,
validation ?: ValidationEntity(
date=Instant.now(),
teacherId=user.id,
studentId=student.id,
taskId=task.id,
@ -127,58 +125,26 @@ fun refreshTasksValidations(
validations: MutableState<List<ValidationEntity>?>,
) {
// 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 = parser.fromJson<List<RelationPersonSessionSubjectEntity>>(
responseSubjects.body.string(),
object : TypeToken<List<RelationPersonSessionSubjectEntity>>(){}.type
)
val allRelationsPersonSessionSubject = client.relationPersonSessionSubjectApi.getAll()
// get the subject that the student is using
val relationPersonSessionSubjects = allPersonSessionSubjects.firstOrNull { relation -> relation.studentId == student.id }
val relationPersonSessionSubject = allRelationsPersonSessionSubject.firstOrNull { relation ->
relation.studentId == student.id
}
if (relationPersonSessionSubjects == null)
if (relationPersonSessionSubject == 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 = parser.fromJson<List<TaskEntity>>(
responseTasks.body.string(),
object : TypeToken<List<TaskEntity>>(){}.type
)
val allTasks = client.taskApi.getAll()
// get the tasks that are linked to this subject
tasks.value = allTasks.filter { task ->
task.subjectId == relationPersonSessionSubjects.subjectId
}.sortedBy { task ->
task.order
}
tasks.value = allTasks
.filter { task -> task.subjectId == relationPersonSessionSubject.subjectId }
.sortedBy { task -> task.order }
// try to obtain the list of validations
val responseValidations = client.get("entities/" + ValidationEntity.TABLE_NAME)
// in case of error, notify it
if (!responseValidations.isSuccessful)
return activity.runOnUiThread { Toast.makeText(activity, responseTasks.message, Toast.LENGTH_LONG).show() }
// parse the list of validations
val allValidations = parser.fromJson<List<ValidationEntity>>(
responseValidations.body.string(),
object : TypeToken<List<ValidationEntity>>(){}.type
)
val allValidations = client.validationApi.getAll()
// filter only the interesting validations
validations.value = allValidations.filter { validation ->
validation.studentId == student.id &&
@ -188,20 +154,10 @@ fun refreshTasksValidations(
fun updateValidation(client: TaskClient, checked: Boolean, validation: ValidationEntity) {
if (checked) {
if (checked)
// if the validation is not set, create it
client.post(
"entities/" + ValidationEntity.TABLE_NAME,
parser.toJson(validation),
"application/json"
)
}
else {
client.validationApi.save(validation)
else
// if the validation is set, delete it
client.delete(
"entities/" + ValidationEntity.TABLE_NAME,
parser.toJson(validation),
"application/json"
)
}
}
client.validationApi.delete(validation)
}

View file

@ -1,4 +1,4 @@
package com.faraphel.tasks_valider.database.converters
package com.faraphel.tasks_valider.utils.converters
import androidx.room.TypeConverter
import com.google.gson.*

View file

@ -1,6 +1,6 @@
package com.faraphel.tasks_valider.utils
import com.faraphel.tasks_valider.database.converters.InstantConverter
import com.faraphel.tasks_valider.utils.converters.InstantConverter
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.time.Instant