cards #8

Merged
faraphel merged 12 commits from cards into main 2024-06-13 15:12:13 +02:00
20 changed files with 733 additions and 248 deletions
Showing only changes of commit e73477b626 - Show all commits

View file

@ -11,7 +11,7 @@ android {
defaultConfig {
applicationId = "com.faraphel.tasks_valider"
minSdk = 24
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
@ -52,8 +52,8 @@ android {
}
dependencies {
implementation("androidx.core:core-ktx:1.13.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.1")
implementation("androidx.activity:activity-compose:1.9.0")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")

View file

@ -5,7 +5,7 @@
<!-- SDK -->
<uses-sdk
android:minSdkVersion="24"
android:minSdkVersion="26"
tools:ignore="GradleOverrides" />
<!-- Permissions -->

View file

@ -5,6 +5,8 @@ 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
/**
@ -32,7 +34,7 @@ class TaskClient(
this.cookies.addAll(cookies)
}
}
)
).callTimeout(30.seconds)
.build()
// TODO(Faraphel): automatically convert content to the correct type ?

View file

@ -1,14 +1,68 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class ClassApi(dao: BaseTaskDao<ClassEntity>, session: SessionEntity) :
BaseTaskApi<ClassEntity>(
dao,
object: TypeToken<ClassEntity>() {},
session
)
class ClassApi(private val dao: BaseTaskDao<ClassEntity>, private val session: SessionEntity) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, ClassEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, ClassEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, ClassEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,14 +1,68 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class PersonApi(dao: BaseTaskDao<PersonEntity>, session: SessionEntity) :
BaseTaskApi<PersonEntity>(
dao,
object: TypeToken<PersonEntity>() {},
session
)
class PersonApi(private val dao: BaseTaskDao<PersonEntity>, private val session: SessionEntity) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, PersonEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, PersonEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, PersonEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,15 +1,72 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class RelationClassPersonApi(dao: BaseTaskDao<RelationClassPersonEntity>, session: SessionEntity) :
BaseTaskApi<RelationClassPersonEntity>(
dao,
object: TypeToken<RelationClassPersonEntity>() {},
session
)
class RelationClassPersonApi(
private val dao: BaseTaskDao<RelationClassPersonEntity>,
private val session: SessionEntity
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, RelationClassPersonEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, RelationClassPersonEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, RelationClassPersonEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,15 +1,72 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class RelationPersonSessionSubjectApi(dao: BaseTaskDao<RelationPersonSessionSubjectEntity>, session: SessionEntity) :
BaseTaskApi<RelationPersonSessionSubjectEntity>(
dao,
object: TypeToken<RelationPersonSessionSubjectEntity>() {},
session
)
class RelationPersonSessionSubjectApi(
private val dao: BaseTaskDao<RelationPersonSessionSubjectEntity>,
private val session: SessionEntity
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, RelationPersonSessionSubjectEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, RelationPersonSessionSubjectEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, RelationPersonSessionSubjectEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,13 +1,71 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
import com.faraphel.tasks_valider.database.dao.base.BaseTaskDao
import com.faraphel.tasks_valider.database.entities.SessionEntity
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class SessionApi(dao: BaseTaskDao<SessionEntity>, session: SessionEntity) :
BaseTaskApi<SessionEntity>(
dao,
object: TypeToken<SessionEntity>() {},
session
)
class SessionApi(
private val dao: BaseTaskDao<SessionEntity>,
private val session: SessionEntity
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, SessionEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, SessionEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, SessionEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,15 +1,72 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class SubjectApi(dao: BaseTaskDao<SubjectEntity>, session: SessionEntity) :
BaseTaskApi<SubjectEntity>(
dao,
object: TypeToken<SubjectEntity>() {},
session
)
class SubjectApi(
private val dao: BaseTaskDao<SubjectEntity>,
private val session: SessionEntity
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, SubjectEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, SubjectEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, SubjectEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,15 +1,72 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class TaskApi(dao: BaseTaskDao<TaskEntity>, session: SessionEntity) :
BaseTaskApi<TaskEntity>(
dao,
object: TypeToken<TaskEntity>() {},
session
)
class TaskApi(
private val dao: BaseTaskDao<TaskEntity>,
private val session: SessionEntity
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, TaskEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, TaskEntity::class.java)
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, TaskEntity::class.java)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,15 +1,74 @@
package com.faraphel.tasks_valider.database.api.entities
import com.faraphel.tasks_valider.database.api.entities.base.BaseTaskApi
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
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
import com.google.gson.reflect.TypeToken
import com.faraphel.tasks_valider.utils.getBody
import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD
class ValidationApi(dao: BaseTaskDao<ValidationEntity>, session: SessionEntity) :
BaseTaskApi<ValidationEntity>(
dao,
object: TypeToken<ValidationEntity>() {},
session
)
class ValidationApi(
private val dao: BaseTaskDao<ValidationEntity>,
private val session: SessionEntity
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, ValidationEntity::class.java)
// check if the object is in the object accessible from the session
val exists = this.dao.getAllBySession(session.id).contains(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAllBySession(session.id))
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, ValidationEntity::class.java)
// save the data into the database
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
// get the content of the request
val data = httpSession.getBody()
// parse the object
val obj = parser.fromJson(data, ValidationEntity::class.java)
// delete the object from the database
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

@ -1,80 +0,0 @@
package com.faraphel.tasks_valider.database.api.entities.base
import android.util.Log
import com.faraphel.tasks_valider.database.dao.base.BaseCronDao
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import fi.iki.elonen.NanoHTTPD
/**
* A base for the API to handle the database operations.
* This is preconfigured to handle JSON data.
* @param Entity the entity type to handle
*/
abstract class BaseJsonApi<Entity: BaseEntity>(
private val dao: BaseCronDao<Entity>,
private val entityTypeToken: TypeToken<Entity>,
) : BaseApi {
companion object {
private val parser = Gson() ///< The JSON parser
}
// Requests
override fun head(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val obj = parser.fromJson<Entity>(
httpSession.inputStream.bufferedReader().readText(),
this.entityTypeToken.type
)
val exists = this.dao.exists(obj)
return NanoHTTPD.newFixedLengthResponse(
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
if (exists) "Exists" else "Not found"
)
}
override fun get(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.OK,
"application/json",
parser.toJson(this.dao.getAll())
)
}
override fun post(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val data = httpSession.inputStream.bufferedReader().readText()
Log.i("post data", "data raw : $data")
val obj = parser.fromJson<Entity>(
data,
this.entityTypeToken.type
)
Log.i("post data", "data parsed : $obj")
val id = this.dao.insert(obj)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.CREATED,
"text/plain",
id.toString()
)
}
override fun delete(httpSession: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
val obj = parser.fromJson<Entity>(
httpSession.inputStream.bufferedReader().readText(),
this.entityTypeToken.type
)
val count = this.dao.delete(obj)
return NanoHTTPD.newFixedLengthResponse(
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
count.toString()
)
}
}

View file

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

View file

@ -1,6 +1,6 @@
package com.faraphel.tasks_valider.ui.screen.authentification
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
@ -8,6 +8,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
import com.faraphel.tasks_valider.database.entities.PersonEntity
@ -20,19 +24,37 @@ fun AuthentificationServerScreen(personEntity: MutableState<PersonEntity?>) {
val firstName = remember { mutableStateOf("") }
val lastName = remember { mutableStateOf("") }
Column {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// title
Text(
text = "Your Profile",
fontSize = 32.sp
)
// separator
Spacer(modifier = Modifier.height(24.dp))
// first name
TextField(
value = firstName.value,
placeholder = { Text("first name") },
onValueChange = { text -> firstName.value = text },
)
// last name
TextField(
value = lastName.value,
placeholder = { Text("last name") },
onValueChange = { text -> lastName.value = text },
)
// separator
Spacer(modifier = Modifier.height(24.dp))
// confirm button
Button(onClick = {
// create the person entity with the given information

View file

@ -3,10 +3,14 @@ package com.faraphel.tasks_valider.ui.screen.communication.internet
import android.app.Activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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
@ -29,7 +33,20 @@ fun CommunicationInternetSelectScreen(activity: Activity, database: TaskDatabase
@Composable
fun CommunicationInternetSelectContent(controller: NavController) {
Column {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// title
Text(
text = "Role",
fontSize = 32.sp
)
// separator
Spacer(modifier = Modifier.height(24.dp))
// client mode
Button(onClick = { controller.navigate("client") }) { Text("Client") }
// server mode

View file

@ -4,8 +4,7 @@ import android.app.Activity
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
@ -13,7 +12,14 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
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
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@ -93,43 +99,71 @@ fun CommunicationInternetServerContent(
Thread { refreshClasses(database, classes) }.start()
}
Column {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// title
Text(
text = "New Session",
fontSize = 32.sp
)
// separator
Spacer(modifier = Modifier.height(24.dp))
// classes
Button(onClick = { areClassesExpanded.value = !areClassesExpanded.value }) {
Row {
Text(text = "Class")
Row(verticalAlignment = Alignment.CenterVertically) {
// description
Text(text = "Class", fontSize = 12.sp)
// separator
Spacer(modifier = Modifier.width(width = 12.dp))
// selector
Button(onClick = { areClassesExpanded.value = !areClassesExpanded.value }) {
// display the selected class, if selected
if (selectedClass.value != null)
Text(text = selectedClass.value!!.name)
if (selectedClass.value != null) Text(text = selectedClass.value!!.name)
else Text(text = "<Not selected>")
}
}
DropdownMenu(
expanded = areClassesExpanded.value,
onDismissRequest = { areClassesExpanded.value = false }
) {
// TODO(Faraphel): student lists should be loaded from the database or a file
classes.value?.forEach { class_ ->
DropdownMenuItem(
text = { Text(class_.name) },
onClick = {
selectedClass.value = class_
areClassesExpanded.value = false
}
)
// class selector
DropdownMenu(
expanded = areClassesExpanded.value,
onDismissRequest = { areClassesExpanded.value = false }
) {
// TODO(Faraphel): student lists should be loaded from the database or a file
classes.value?.forEach { class_ ->
DropdownMenuItem(
text = { Text(class_.name) },
onClick = {
selectedClass.value = class_
areClassesExpanded.value = false
}
)
}
}
}
// server port
TextField(
value = serverPort.intValue.toString(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onValueChange = { text ->
val port = text.toInt()
if (port in RANGE_SERVER_PORT) {
serverPort.intValue = port
Row(verticalAlignment = Alignment.CenterVertically) {
// descriptor
Text(text = "Port")
// separator
Spacer(modifier = Modifier.width(width = 12.dp))
// input
TextField(
modifier = Modifier.width(80.dp),
value = serverPort.intValue.toString(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true,
onValueChange = { text ->
val port = text.toInt()
if (port in RANGE_SERVER_PORT) {
serverPort.intValue = port
}
}
}
)
)
}
// check if a class is selected
if (selectedClass.value != null)

View file

@ -4,12 +4,17 @@ import android.app.Activity
import android.os.Build
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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
@ -58,7 +63,20 @@ fun CommunicationModeSelectionScreen(activity: Activity, database: TaskDatabase)
fun CommunicationSelectContent(controller: NavController, activity: Activity) {
val isWifiP2pSupported = BwfManager.isSupported(activity)
Column {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// title
Text(
text = "Connection Type",
fontSize = 32.sp
)
// separator
Spacer(modifier = Modifier.height(24.dp))
// internet communication mode
Button(onClick = { controller.navigate("internet") }) {
Text("Internet")
@ -68,7 +86,7 @@ fun CommunicationSelectContent(controller: NavController, activity: Activity) {
Button(
colors = ButtonDefaults.buttonColors(
// if the WiFi-Direct is not supported, the button is grayed out
containerColor = if (isWifiP2pSupported) Color.Unspecified else Color.Gray
containerColor = if (isWifiP2pSupported) MaterialTheme.colorScheme.primary else Color.Gray
),
onClick = {
// if the WiFi-Direct is supported, navigate to the WiFi-Direct screen

View file

@ -4,13 +4,17 @@ import android.app.Activity
import android.os.Build
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.faraphel.tasks_valider.connectivity.task.TaskClient
import com.faraphel.tasks_valider.database.entities.PersonEntity
import com.google.gson.Gson
@ -44,10 +48,25 @@ fun TaskSessionScreen(
selectedStudent.value!!
)
Column {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// title
Text(
text = "Session",
fontSize = 32.sp
)
// separator
Spacer(modifier = Modifier.height(24.dp))
// if the groups have already been defined, display them
for (student in students.value!!) {
Button(onClick = { selectedStudent.value = student }) {
Button(
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp, horizontal = 16.dp),
onClick = { selectedStudent.value = student },
) {
Text(text = student.fullName())
}
}

View file

@ -5,8 +5,7 @@ import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Text
@ -14,6 +13,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.faraphel.tasks_valider.connectivity.task.TaskClient
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
import com.faraphel.tasks_valider.database.entities.*
@ -25,7 +29,6 @@ import java.time.Instant
* This screen represent a student
* @param student the student object
*/
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun TaskStudentScreen(
activity: Activity,
@ -47,8 +50,17 @@ fun TaskStudentScreen(
)
}.start()
Column {
Text(text = student.fullName())
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
// title
Text(text = "Student", fontSize = 32.sp)
// student name - subtitle
Text(text = student.fullName(), fontSize = 24.sp)
// separator
Spacer(modifier = Modifier.height(24.dp))
// if both the list of tasks and validations are loaded
if (!(tasks.value == null || validations.value == null)) {
@ -56,22 +68,31 @@ fun TaskStudentScreen(
// get the validation
val validation = validations.value!!.firstOrNull { validation -> validation.taskId == task.id }
Button(onClick = {}) {
Box(
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 64.dp),
) {
Row {
Column {
// task title
Text(task.title)
Text(text = task.title, fontWeight = FontWeight.Bold)
// task description
task.description?.let { description -> Text(description) }
// if the task have been validated, show the date
if (validation != null) Text(validation.date.toString())
}
// separator
Spacer(modifier = Modifier.fillMaxWidth())
// the validation state
Checkbox(
checked = validation != null,
enabled = user.role.permissions.contains(TaskPermission.WRITE),
onCheckedChange = { state ->
Thread {
// TODO(Faraphel): simplify or put the UI refresh in the update function ?
// send a notification to the server about the validation
updateValidation(
client,
state,
@ -82,6 +103,14 @@ fun TaskStudentScreen(
task.id,
)
)
// refresh the UI
refreshTasksValidations(
activity,
client,
student,
tasks,
validations
)
}.start()
}
)

View file

@ -0,0 +1,22 @@
package com.faraphel.tasks_valider.utils
import fi.iki.elonen.NanoHTTPD
import java.nio.charset.Charset
/**
* Return the body of a request as a string.
* :param charset: the encoding of the body
*/
fun NanoHTTPD.IHTTPSession.getBody(
charset: Charset = Charset.forName("UTF-8")
): String {
// get the length of the body
val length = this.headers["content-length"]!!.toInt()
// prepare a buffer for the body
val buffer = ByteArray(length)
// read the body into the buffer
this.inputStream.read(buffer, 0, length)
// convert that buffer into a string
return buffer.toString(charset)
}