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.activity.compose.setContent
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.room.Room 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.TaskDatabase
import com.faraphel.tasks_valider.database.populateTaskDatabaseTest import com.faraphel.tasks_valider.database.populateTaskDatabaseTest
import com.faraphel.tasks_valider.ui.screen.communication.CommunicationModeSelectionScreen import com.faraphel.tasks_valider.ui.screen.communication.CommunicationModeSelectionScreen
class MainActivity : ComponentActivity() { 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 private lateinit var database: TaskDatabase ///< the database manager
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -50,13 +50,13 @@ class MainActivity : ComponentActivity() {
super.onResume() super.onResume()
// enable the WiFi-Direct events // enable the WiFi-Direct events
this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER) this.registerReceiver(this.bwdManager, BwdManager.ALL_INTENT_FILTER)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
// disable the WiFi-Direct events // 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.Manifest
import android.app.Activity import android.app.Activity
@ -10,7 +10,7 @@ import android.content.pm.PackageManager
import android.net.wifi.p2p.* import android.net.wifi.p2p.*
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateOf 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 manager The WiFi-Direct manager
* @param channel The WiFi-Direct channel * @param channel The WiFi-Direct channel
*/ */
class BwfManager( class BwdManager(
private var manager: WifiP2pManager, private var manager: WifiP2pManager,
private var channel: WifiP2pManager.Channel, private var channel: WifiP2pManager.Channel,
) : BroadcastReceiver() { ) : BroadcastReceiver() {
@ -41,11 +41,11 @@ class BwfManager(
* Create a new BwfManager from an activity. * Create a new BwfManager from an activity.
* @param activity The activity to create the manager from * @param activity The activity to create the manager from
*/ */
fun fromActivity(activity: Activity): BwfManager { fun fromActivity(activity: Activity): BwdManager {
// check if the system support WiFi-Direct // check if the system support WiFi-Direct
if (this.isSupported(activity)) { if (this.isSupported(activity)) {
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature") Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
throw BwfNotSupportedException() throw BwdNotSupportedException()
} }
// TODO(Faraphel): more check on permissions // TODO(Faraphel): more check on permissions
@ -62,11 +62,11 @@ class BwfManager(
// get the WiFi-Direct manager // get the WiFi-Direct manager
val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
?: throw BwfPermissionException() ?: throw BwdPermissionException()
// get the WiFi-Direct channel // get the WiFi-Direct channel
val channel = manager.initialize(activity, activity.mainLooper, null) val channel = manager.initialize(activity, activity.mainLooper, null)
return BwfManager(manager, channel) return BwdManager(manager, channel)
// NOTE(Faraphel): the broadcast receiver should be registered in the activity onResume // NOTE(Faraphel): the broadcast receiver should be registered in the activity onResume
} }
@ -83,7 +83,7 @@ class BwfManager(
fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) = fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) =
this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener { this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() } 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 = {}) = fun discoverPeers(callback: () -> Unit = {}) =
this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener { this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() } 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 = {}) = fun createGroup(callback: () -> Unit = {}) =
this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener { this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() } 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 = {}) = fun removeGroup(callback: () -> Unit = {}) =
this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener { this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() } 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 -> this.requestGroupInfo { group ->
// if a group exist, quit it // if a group exist, quit it
if (group != null) if (group != null)
this.removeGroup { this@BwfManager.createGroup(callback) } this.removeGroup { this@BwdManager.createGroup(callback) }
else else
// create the group // create the group
this.createGroup(callback) 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 * Base Exception for everything concerning the WifiP2pHelper class
*/ */
open class BwfException( open class BwdException(
override val message: String? override val message: String?
) : Exception(message) ) : 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 package com.faraphel.tasks_valider.connectivity.task
import okhttp3.HttpUrl import com.faraphel.tasks_valider.database.api.client.TaskEntityHttpClient
import okhttp3.MediaType.Companion.toMediaType import com.faraphel.tasks_valider.database.api.client.entities.*
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
/** /**
* 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 address the address of the server
* @param port the port of the server * @param port the port of the server
* @param baseCookies list of cookies to use (optional) * @param baseCookies list of cookies to use (optional)
@ -20,86 +15,15 @@ class TaskClient(
private val port: Int, private val port: Int,
private val baseCookies: List<okhttp3.Cookie> = listOf() private val baseCookies: List<okhttp3.Cookie> = listOf()
) { ) {
private val baseUrl = "http://$address:$port" private val httpClient = TaskEntityHttpClient(address, port, baseCookies)
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
override fun loadForRequest(url: HttpUrl): List<okhttp3.Cookie> { val clientApi = ClassClientApi(httpClient)
return this.cookies val personApi = PersonClientApi(httpClient)
} val sessionApi = SessionClientApi(httpClient)
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) { val subjectApi = SubjectClientApi(httpClient)
this.cookies.addAll(cookies) val taskApi = TaskClientApi(httpClient)
} val validationApi = ValidationClientApi(httpClient)
}
).callTimeout(30.seconds) val relationClassPersonApi = RelationClassPersonClientApi(httpClient)
.build() val relationPersonSessionSubjectApi = RelationPersonSessionSubjectClientApi(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()
} }

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.api.TaskSessionManagerApi
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.database.api.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.PersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
import fi.iki.elonen.NanoHTTPD import fi.iki.elonen.NanoHTTPD
@ -23,7 +23,7 @@ class TaskServer(
private val adminPersonEntity: PersonEntity, private val adminPersonEntity: PersonEntity,
) : NanoHTTPD(port) { ) : NanoHTTPD(port) {
private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager 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 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.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters 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.dao.*
import com.faraphel.tasks_valider.database.entities.* 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.TaskPermission
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.database.api.entities.* import com.faraphel.tasks_valider.database.api.server.entities.base.BaseDatabaseApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi import com.faraphel.tasks_valider.database.api.server.entities.*
import com.faraphel.tasks_valider.database.entities.* import com.faraphel.tasks_valider.database.entities.*
import fi.iki.elonen.NanoHTTPD import fi.iki.elonen.NanoHTTPD
class TaskDatabaseApi(
class DatabaseApi(
private val database: TaskDatabase, private val database: TaskDatabase,
private val session: SessionEntity, private val session: SessionEntity,
) { ) {
private val api: Map<String, BaseApi> = mapOf( private val api: Map<String, BaseDatabaseApi> = mapOf(
ClassEntity.TABLE_NAME to ClassApi(this.database.classDao(), session), ClassEntity.TABLE_NAME to ClassDatabaseApi(this.database.classDao(), session),
PersonEntity.TABLE_NAME to PersonApi(this.database.personDao(), session), PersonEntity.TABLE_NAME to PersonDatabaseApi(this.database.personDao(), session),
SessionEntity.TABLE_NAME to SessionApi(this.database.sessionDao(), session), SessionEntity.TABLE_NAME to SessionDatabaseApi(this.database.sessionDao(), session),
SubjectEntity.TABLE_NAME to SubjectApi(this.database.subjectDao(), session), SubjectEntity.TABLE_NAME to SubjectDatabaseApi(this.database.subjectDao(), session),
TaskEntity.TABLE_NAME to TaskApi(this.database.taskDao(), session), TaskEntity.TABLE_NAME to TaskDatabaseApi(database.taskDao(), session),
ValidationEntity.TABLE_NAME to ValidationApi(this.database.validationDao(), session), ValidationEntity.TABLE_NAME to ValidationDatabaseApi(this.database.validationDao(), session),
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao(), session), RelationClassPersonEntity.TABLE_NAME to RelationClassPersonDatabaseApi(this.database.relationClassPersonDao(), session),
RelationPersonSessionSubjectEntity.TABLE_NAME to RelationPersonSessionSubjectApi(this.database.relationPersonSessionSubjectDao(), 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.ClassEntity import com.faraphel.tasks_valider.database.entities.ClassEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
class ClassApi( class ClassDatabaseApi(
dao: BaseTaskDao<ClassEntity>, dao: BaseTaskDao<ClassEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<ClassEntity>( ) : BaseTaskDatabaseApi<ClassEntity>(
dao, dao,
session, session,
ClassEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.PersonEntity import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
class PersonApi( class PersonDatabaseApi(
dao: BaseTaskDao<PersonEntity>, dao: BaseTaskDao<PersonEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<PersonEntity>( ) : BaseTaskDatabaseApi<PersonEntity>(
dao, dao,
session, session,
PersonEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
class RelationClassPersonApi( class RelationClassPersonDatabaseApi(
dao: BaseTaskDao<RelationClassPersonEntity>, dao: BaseTaskDao<RelationClassPersonEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<RelationClassPersonEntity>( ) : BaseTaskDatabaseApi<RelationClassPersonEntity>(
dao, dao,
session, session,
RelationClassPersonEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity import com.faraphel.tasks_valider.database.entities.RelationPersonSessionSubjectEntity
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
class RelationPersonSessionSubjectApi( class RelationPersonSessionSubjectDatabaseApi(
dao: BaseTaskDao<RelationPersonSessionSubjectEntity>, dao: BaseTaskDao<RelationPersonSessionSubjectEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<RelationPersonSessionSubjectEntity>( ) : BaseTaskDatabaseApi<RelationPersonSessionSubjectEntity>(
dao, dao,
session, session,
RelationPersonSessionSubjectEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
class SessionApi( class SessionDatabaseApi(
dao: BaseTaskDao<SessionEntity>, dao: BaseTaskDao<SessionEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<SessionEntity>( ) : BaseTaskDatabaseApi<SessionEntity>(
dao, dao,
session, session,
SessionEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.SubjectEntity import com.faraphel.tasks_valider.database.entities.SubjectEntity
class SubjectApi( class SubjectDatabaseApi(
dao: BaseTaskDao<SubjectEntity>, dao: BaseTaskDao<SubjectEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<SubjectEntity>( ) : BaseTaskDatabaseApi<SubjectEntity>(
dao, dao,
session, session,
SubjectEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.TaskEntity import com.faraphel.tasks_valider.database.entities.TaskEntity
class TaskApi( class TaskDatabaseApi(
dao: BaseTaskDao<TaskEntity>, dao: BaseTaskDao<TaskEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<TaskEntity>( ) : BaseTaskDatabaseApi<TaskEntity>(
dao, dao,
session, session,
TaskEntity::class.java 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.faraphel.tasks_valider.database.entities.ValidationEntity import com.faraphel.tasks_valider.database.entities.ValidationEntity
class ValidationApi( class ValidationDatabaseApi(
dao: BaseTaskDao<ValidationEntity>, dao: BaseTaskDao<ValidationEntity>,
session: SessionEntity session: SessionEntity
) : BaseTaskApi<ValidationEntity>( ) : BaseTaskDatabaseApi<ValidationEntity>(
dao, dao,
session, session,
ValidationEntity::class.java 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 import fi.iki.elonen.NanoHTTPD
/** /**
* A base for the API to handle the database operations with an HTTP server. * A base for the API to handle the database operations with an HTTP server.
*/ */
interface BaseApi { interface BaseDatabaseApi {
/** /**
* Handle the HEAD request * Handle the HEAD request
* This is used to check if a data exists in the database * 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.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity import com.faraphel.tasks_valider.database.entities.SessionEntity
@ -7,14 +7,11 @@ import com.faraphel.tasks_valider.utils.parser
import fi.iki.elonen.NanoHTTPD import fi.iki.elonen.NanoHTTPD
abstract class BaseTaskApi<Entity> ( abstract class BaseTaskDatabaseApi<Entity> (
private val dao: BaseTaskDao<Entity>, private val dao: BaseTaskDao<Entity>,
private val session: SessionEntity, private val session: SessionEntity,
private val entityType: Class<Entity>, private val entityType: Class<Entity>,
) : BaseApi { ) : BaseDatabaseApi {
private fun parseJson(data: String): Entity =
parser.fromJson(data, entityType)
/** /**
* Handle an HTTP HEAD request. * Handle an HTTP HEAD request.
* Indicate if an object exist in the database. * Indicate if an object exist in the database.
@ -25,7 +22,7 @@ abstract class BaseTaskApi<Entity> (
// get the content of the request // get the content of the request
val data = httpSession.getBody() val data = httpSession.getBody()
// parse the object // 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 // check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj) val exists = this.dao.getAllBySession(session.id).contains(obj)

View file

@ -35,13 +35,28 @@ import java.time.Instant
] ]
) )
data class ValidationEntity ( data class ValidationEntity (
@ColumnInfo("date") val date: Instant,
@ColumnInfo("teacher_id", index = true) val teacherId: Long, @ColumnInfo("teacher_id", index = true) val teacherId: Long,
@ColumnInfo("student_id", index = true) val studentId: Long, @ColumnInfo("student_id", index = true) val studentId: Long,
@ColumnInfo("task_id", index = true) val taskId: Long, @ColumnInfo("task_id", index = true) val taskId: Long,
@ColumnInfo("date") val date: Instant,
) : BaseEntity() { ) : BaseEntity() {
companion object { companion object {
const val TABLE_NAME = "validations" 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 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( ) = database.personDao().insert(
PersonEntity( PersonEntity(
"Billy", "Bob", "Billy", "Bob",
null, "0A1A7553-9DE5-103C-B23C-630998207116",
"1234", "1234",
TaskRole.STUDENT 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_ADDRESS
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)

View file

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

View file

@ -19,7 +19,7 @@ import androidx.navigation.NavController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager import com.faraphel.tasks_valider.connectivity.bwd.BwdManager
import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetSelectScreen import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetSelectScreen
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen
@ -49,8 +49,8 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase)
CommunicationInternetSelectScreen(activity, database) CommunicationInternetSelectScreen(activity, database)
} }
composable("wifi-p2p") { composable("wifi-p2p") {
val bwfManager = BwfManager.fromActivity(activity) val bwdManager = BwdManager.fromActivity(activity)
CommunicationWifiP2pScreen(activity, bwfManager) CommunicationWifiP2pScreen(activity, bwdManager)
} }
} }
} }
@ -61,7 +61,7 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase)
*/ */
@Composable @Composable
fun CommunicationSelectContent(controller: NavController, activity: Activity) { fun CommunicationSelectContent(controller: NavController, activity: Activity) {
val isWifiP2pSupported = BwfManager.isSupported(activity) val isWifiP2pSupported = BwdManager.isSupported(activity)
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),

View file

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

View file

@ -9,19 +9,19 @@ import androidx.navigation.NavController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.faraphel.tasks_valider.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.client.CommunicationWifiP2pClientScreen
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server.CommunicationWifiP2pServerScreen import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server.CommunicationWifiP2pServerScreen
@Composable @Composable
fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) { fun CommunicationWifiP2pScreen(activity: Activity, bwdManager: BwdManager) {
val controller = rememberNavController() val controller = rememberNavController()
NavHost(navController = controller, startDestination = "mode") { NavHost(navController = controller, startDestination = "mode") {
composable("mode") { CommunicationWifiP2pSelectContent(controller) } composable("mode") { CommunicationWifiP2pSelectContent(controller) }
composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) } composable("client") { CommunicationWifiP2pClientScreen(activity, bwdManager) }
composable("server") { CommunicationWifiP2pServerScreen(activity, bwfManager) } composable("server") { CommunicationWifiP2pServerScreen(activity, bwdManager) }
} }
} }

View file

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

View file

@ -1,6 +1,8 @@
package com.faraphel.tasks_valider.ui.screen.scan.qr package com.faraphel.tasks_valider.ui.screen.scan.qr
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.pm.PackageManager
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -17,6 +19,11 @@ import com.journeyapps.barcodescanner.DefaultDecoderFactory
@Composable @Composable
fun ScanBarcodeScreen(activity: Activity, barcode: MutableState<BarcodeResult?>) { fun ScanBarcodeScreen(activity: Activity, barcode: MutableState<BarcodeResult?>) {
Box(modifier = Modifier.fillMaxSize()) { 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 is used because "DecoratedBarcodeView" only support the legacy view system
AndroidView(factory = { AndroidView(factory = {
DecoratedBarcodeView(activity).apply { 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 package com.faraphel.tasks_valider.ui.screen.task
import android.app.Activity import android.app.Activity
import android.os.Build import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.faraphel.tasks_valider.connectivity.task.TaskClient import com.faraphel.tasks_valider.connectivity.task.TaskClient
import com.faraphel.tasks_valider.database.entities.PersonEntity 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 @Composable
fun TaskSessionScreen( fun TaskSessionScreen(
controller: NavController,
activity: Activity, activity: Activity,
client: TaskClient, client: TaskClient,
user: PersonEntity, user: PersonEntity,
@ -37,7 +58,9 @@ fun TaskSessionScreen(
// if the groups are not yet defined, refresh the list // if the groups are not yet defined, refresh the list
if (students.value == null) if (students.value == null)
return Thread { refreshStudents(activity, client, students) }.start() return LaunchedEffect(true) {
Thread { refreshStudents(activity, client, students) }.start()
}
if (selectedStudent.value != null) if (selectedStudent.value != null)
return TaskStudentScreen( return TaskStudentScreen(
@ -69,6 +92,17 @@ fun TaskSessionScreen(
Text(text = student.fullName()) 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, client: TaskClient,
students: MutableState<List<PersonEntity>?> students: MutableState<List<PersonEntity>?>
) { ) {
// try to obtain the list of groups try {
val response = client.get("entities/" + PersonEntity.TABLE_NAME) // try to get all the persons in that session
students.value = client.personApi.getAll()
// in case of error, notify it } catch (exception: Exception) {
if (!response.isSuccessful) // in case of error, show a message
return activity.runOnUiThread { Toast.makeText(activity, response.message, Toast.LENGTH_LONG).show() } return activity.runOnUiThread {
Log.e("students", "$exception")
// parse the list of groups Toast.makeText(activity, "Could not retrieve students.\n\n$exception", Toast.LENGTH_LONG).show()
students.value = parser.fromJson( }
response.body.string(), }
object : TypeToken<List<PersonEntity>>(){}.type
)
} }

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

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 androidx.room.TypeConverter
import com.google.gson.* import com.google.gson.*

View file

@ -1,6 +1,6 @@
package com.faraphel.tasks_valider.utils 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.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import java.time.Instant import java.time.Instant