diff --git a/.gitignore b/.gitignore
index aa724b7..faf530b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,7 @@
*.iml
.gradle
/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
+/.idea/
.DS_Store
/build
/captures
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 01f6c76..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-tasks-valider
\ No newline at end of file
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
deleted file mode 100644
index 6bbe2ae..0000000
--- a/.idea/appInsightsSettings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index 0c0c338..0000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 0897082..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 44ca2d9..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index fdf8d99..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
deleted file mode 100644
index f8051a6..0000000
--- a/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 8978d23..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 998a95c..2568df0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,7 +1,8 @@
plugins {
id("com.android.application")
- id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
+ kotlin("android")
+ kotlin("plugin.serialization")
}
android {
@@ -41,7 +42,7 @@ android {
compose = true
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.5.1"
+ kotlinCompilerExtensionVersion = "1.5.13"
}
packaging {
resources {
@@ -51,15 +52,20 @@ android {
}
dependencies {
- implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.core:core-ktx:1.13.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
- implementation("androidx.activity:activity-compose:1.8.2")
+ implementation("androidx.activity:activity-compose:1.9.0")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.room:room-ktx:2.6.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
+ implementation("androidx.navigation:navigation-compose:2.7.7")
+ implementation("org.nanohttpd:nanohttpd:2.3.1")
+ implementation("com.google.code.gson:gson:2.10.1")
+ implementation("com.squareup.okhttp3:okhttp-android:5.0.0-alpha.14")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e31d521..051a946 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- WidgetTaskStudent(database, taskGroup)
- }
- }
+ CommunicationScreen(this)
}
+ }
- */
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ override fun onResume() {
+ super.onResume()
+ // enable the WiFi-Direct events
+ this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER)
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ // disable the WiFi-Direct events
+ this.unregisterReceiver(this.bwfManager)
}
}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/BwfManager.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/BwfManager.kt
new file mode 100644
index 0000000..31f6681
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/BwfManager.kt
@@ -0,0 +1,192 @@
+package com.faraphel.tasks_valider.connectivity.bwf
+
+import android.Manifest
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.wifi.p2p.*
+import android.util.Log
+import androidx.compose.runtime.mutableStateOf
+import com.faraphel.tasks_valider.connectivity.bwf.error.*
+
+
+/**
+ * A helper to wrap the WiFi-Direct manager, the channel and the events.
+ *
+ * This avoids certain annoying features such as always specifying the channel as the first argument or
+ * handling all the events with the base event system.
+ *
+ * @param manager The WiFi-Direct manager
+ * @param channel The WiFi-Direct channel
+ */
+class BwfManager(
+ private var manager: WifiP2pManager,
+ private var channel: WifiP2pManager.Channel,
+) : BroadcastReceiver() {
+ companion object {
+ var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location
+ var ALL_INTENT_FILTER = IntentFilter().apply {
+ addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
+ addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
+ }
+
+ fun isSupported(context: Context): Boolean {
+ return context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)
+ }
+
+ /**
+ * Create a new BwfManager from an activity.
+ * @param activity The activity to create the manager from
+ */
+ fun fromActivity(activity: Activity): BwfManager {
+ // check if the system support WiFi-Direct
+ if (this.isSupported(activity)) {
+ Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
+ throw BwfNotSupportedException()
+ }
+
+ // TODO(Faraphel): more check on permissions
+ if (
+ activity.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
+ activity.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED
+ ) {
+ // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check
+ activity.requestPermissions(
+ arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
+ PERMISSION_ACCESS_FINE_LOCATION
+ )
+ }
+
+ // get the WiFi-Direct manager
+ val manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
+ ?: throw BwfPermissionException()
+
+ // get the WiFi-Direct channel
+ val channel = manager.initialize(activity, activity.mainLooper, null)
+ return BwfManager(manager, channel)
+
+ // NOTE(Faraphel): the broadcast receiver should be registered in the activity onResume
+ }
+ }
+
+ // Wrappers
+
+ /**
+ * Connect to another device, allowing for a communication using Sockets
+ * @see WifiP2pManager.connect
+ * @throws SecurityException if the permission has not been given
+ */
+ @Throws(SecurityException::class)
+ fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) =
+ this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener {
+ override fun onSuccess() { callback() }
+ override fun onFailure(reason: Int) = throw BwfConnectException(reason)
+ })
+
+ /**
+ * Request the list of peers after a discovery.
+ * @see WifiP2pManager.requestPeers
+ * @throws SecurityException if the permission has not been given
+ */
+ @Throws(SecurityException::class)
+ fun requestPeers(callback: (WifiP2pDeviceList) -> Unit = {}) =
+ this.manager.requestPeers(this.channel, callback)
+
+ /**
+ * Start discovering peers.
+ * Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered.
+ * @see WifiP2pManager.discoverPeers
+ * @throws SecurityException if the permission has not been given
+ */
+ @Throws(SecurityException::class)
+ fun discoverPeers(callback: () -> Unit = {}) =
+ this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener {
+ override fun onSuccess() { callback() }
+ override fun onFailure(reason: Int) = throw BwfDiscoverException(reason)
+ })
+
+ /**
+ * Obtain information about a connection with another device.
+ * @see WifiP2pManager.requestConnectionInfo
+ */
+ fun requestConnectionInfo(callback: (WifiP2pInfo) -> Unit = {}) =
+ this.manager.requestConnectionInfo(this.channel, callback)
+
+ /**
+ * Obtain information about the current group.
+ * @see WifiP2pManager.requestGroupInfo
+ * @throws SecurityException if the permission has not been given
+ */
+ @Throws(SecurityException::class)
+ fun requestGroupInfo(callback: (WifiP2pGroup?) -> Unit = {}) =
+ this.manager.requestGroupInfo(this.channel, callback)
+
+ /**
+ * Create a new WiFi-Direct group.
+ * @see WifiP2pManager.createGroup
+ * @throws SecurityException if the permission has not been given
+ */
+ @Throws(SecurityException::class)
+ fun createGroup(callback: () -> Unit = {}) =
+ this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener {
+ override fun onSuccess() { callback() }
+ override fun onFailure(reason: Int) = throw BwfCreateGroupException(reason)
+ })
+
+ /**
+ * Disconnect from the current WiFi-Direct group.
+ * @see WifiP2pManager.removeGroup
+ */
+ fun removeGroup(callback: () -> Unit = {}) =
+ this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener {
+ override fun onSuccess() { callback() }
+ override fun onFailure(reason: Int) = throw BwfRemoveGroupException(reason)
+ })
+
+ /**
+ * Create a new WiFi-Direct group. If already connected to a group, quit it first.
+ *
+ * Note: most of the failure on removal are caused by not having a group already created, which is checked.
+ *
+ * @param callback: the createGroup listener
+ *
+ * @see WifiP2pManager.createGroup
+ * @see WifiP2pManager.removeGroup
+ */
+ fun recreateGroup(callback: () -> Unit = {}) {
+ // get the current group information
+ this.requestGroupInfo { group ->
+ // if a group exist, quit it
+ if (group != null)
+ this.removeGroup { this@BwfManager.createGroup(callback) }
+ else
+ // create the group
+ this.createGroup(callback)
+ }
+ }
+
+ // Events
+
+ val stateConnectionInfo = mutableStateOf(null)
+ val statePeers = mutableStateOf(null)
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ // ignore empty intent
+ if (intent == null)
+ return
+
+ // update the action corresponding state
+ when (intent.action) {
+ WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> this.requestConnectionInfo {
+ connectionInfo -> stateConnectionInfo.value = connectionInfo
+ }
+ WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> this.requestPeers {
+ peers -> statePeers.value = peers
+ }
+ }
+ // TODO(Faraphel): implement event dispatcher
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/README.md b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/README.md
new file mode 100644
index 0000000..4e0acc5
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/README.md
@@ -0,0 +1,11 @@
+# Better WiFi-Direct (BWD)
+
+This package contain code to improve the base WiFi-Direct implementation.
+
+The base have some issue, like an abusive usage of listener, error code and events that make using it
+very impractical.
+
+This improved version will instead focus on asynchronous function and exception, allowing for a
+cleaner linear code instead.
+
+(Author: https://git.faraphel.fr/Faraphel)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfConnectException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfConnectException.kt
new file mode 100644
index 0000000..a581c09
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfConnectException.kt
@@ -0,0 +1,5 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfConnectException(
+ reason: Int
+) : BwfException("Cannot connect to the peer. Reason: $reason")
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfCreateGroupException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfCreateGroupException.kt
new file mode 100644
index 0000000..0ce1169
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfCreateGroupException.kt
@@ -0,0 +1,5 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfCreateGroupException (
+ reason: Int
+) : BwfException("Could not create the group : $reason")
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfDiscoverException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfDiscoverException.kt
new file mode 100644
index 0000000..a4e818a
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfDiscoverException.kt
@@ -0,0 +1,5 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfDiscoverException(
+ reason: Int
+) : BwfException("Could not discover peers : $reason")
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfException.kt
new file mode 100644
index 0000000..7fd8810
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfException.kt
@@ -0,0 +1,9 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+
+/**
+ * Base Exception for everything concerning the WifiP2pHelper class
+ */
+open class BwfException(
+ override val message: String?
+) : Exception(message)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfInvalidActionException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfInvalidActionException.kt
new file mode 100644
index 0000000..0b690a3
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfInvalidActionException.kt
@@ -0,0 +1,5 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfInvalidActionException(
+ action: String
+) : BwfException("This WiFi-Direct action is not supported : $action")
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfNotSupportedException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfNotSupportedException.kt
new file mode 100644
index 0000000..3585496
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfNotSupportedException.kt
@@ -0,0 +1,4 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfNotSupportedException :
+ BwfException("WiFi-Direct is not supported on this device.")
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfPermissionException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfPermissionException.kt
new file mode 100644
index 0000000..81c85ee
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfPermissionException.kt
@@ -0,0 +1,4 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfPermissionException :
+ BwfException("WiFi-Direct requires permissions to work properly. Please grant the permissions.")
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfRemoveGroupException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfRemoveGroupException.kt
new file mode 100644
index 0000000..92d0789
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfRemoveGroupException.kt
@@ -0,0 +1,5 @@
+package com.faraphel.tasks_valider.connectivity.bwf.error
+
+class BwfRemoveGroupException (
+ reason: Int
+) : BwfException("Could not remove the group : $reason")
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt
new file mode 100644
index 0000000..b1917e1
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt
@@ -0,0 +1,100 @@
+package com.faraphel.tasks_valider.connectivity.task
+
+import okhttp3.HttpUrl
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.RequestBody.Companion.toRequestBody
+
+
+/**
+ * A 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 TaskClient(
+ private val address: String,
+ private val port: Int,
+ private val baseCookies: List = listOf()
+) {
+ private val baseUrl = "http://$address:$port"
+ private val client = OkHttpClient().newBuilder().cookieJar(
+ // TODO(Faraphel): should be moved into another object
+ object : okhttp3.CookieJar {
+ private val cookies = baseCookies.toMutableList() ///< list of cookies
+
+ override fun loadForRequest(url: HttpUrl): List {
+ return this.cookies
+ }
+ override fun saveFromResponse(url: HttpUrl, cookies: List) {
+ this.cookies.addAll(cookies)
+ }
+ }
+ ).build()
+
+ // 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()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt
new file mode 100644
index 0000000..ba2151a
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt
@@ -0,0 +1,94 @@
+package com.faraphel.tasks_valider.connectivity.task
+
+import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi
+import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
+import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
+import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
+import com.faraphel.tasks_valider.database.TaskDatabase
+import com.faraphel.tasks_valider.database.api.TaskDatabaseApi
+import fi.iki.elonen.NanoHTTPD
+
+
+/**
+ * A server to handle the task API to allow clients to interact with the database.
+ * @param port the port of the server
+ * @param database the database to interact with
+ */
+class TaskServer(
+ private val port: Int,
+ private val database: TaskDatabase
+) : NanoHTTPD(port) {
+ companion object {
+ private val TASK_SESSION_ADMIN = TaskSession( ///< the admin default session
+ role = TaskRole.ADMIN
+ )
+ }
+
+ private val sessionManager = TaskSessionManager() ///< the session manager
+ private val adminSessionId = this.sessionManager.newSessionData(TASK_SESSION_ADMIN) ///< default admin session id
+
+ private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager) ///< the api of the session manager
+ private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database
+
+ /**
+ * Return a new client that can be used by the admin
+ */
+ fun getClientAdmin(): TaskClient {
+ // create the session cookie for the admin
+ val cookieSession = okhttp3.Cookie.Builder()
+ .domain("localhost")
+ .name("sessionId")
+ .value(adminSessionId)
+ .build()
+ // create a new client
+ return TaskClient(
+ "localhost",
+ this.port,
+ listOf(cookieSession)
+ )
+ }
+
+ /**
+ * Handle an API request
+ * @param httpSession the http session
+ */
+ override fun serve(httpSession: IHTTPSession): Response {
+ // get the session data of the client
+ val taskSession = this.sessionManager.getOrCreateSessionData(httpSession)
+
+ // parse the url
+ val uri: String = httpSession.uri.trim('/')
+ val path = uri.split("/").toMutableList()
+
+ // get the type of the request from the uri
+ val requestType = path.removeFirstOrNull()
+ ?: return newFixedLengthResponse(
+ NanoHTTPD.Response.Status.BAD_REQUEST,
+ "text/plain",
+ "Missing request type"
+ )
+
+ // get the response from the correct part of the application
+ val response = when (requestType) {
+ // session requests
+ "sessions" -> this.sessionManagerApi.handleRequest(taskSession, httpSession, path)
+ // entities requests
+ "entities" -> return this.databaseApi.handleRequest(taskSession, httpSession, path)
+ // invalid requests
+ else ->
+ newFixedLengthResponse(
+ NanoHTTPD.Response.Status.BAD_REQUEST,
+ "text/plain",
+ "Unknown request type"
+ )
+ }
+
+ // wrap additional information in the response
+ return this.sessionManager.responseSetSessionData(response, httpSession.cookies)
+ }
+
+ /**
+ * Start the server with the default configuration
+ */
+ override fun start() = super.start(SOCKET_READ_TIMEOUT, false)
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt
new file mode 100644
index 0000000..305e3f3
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt
@@ -0,0 +1,139 @@
+package com.faraphel.tasks_valider.connectivity.task.api
+
+import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
+import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
+import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
+import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import fi.iki.elonen.NanoHTTPD
+
+
+/**
+ * the HTTP API for the session manager
+ */
+class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) {
+ private val jsonParser = Gson() ///< the json parser
+
+ /**
+ * Handle a HTTP Api request
+ * @param taskSession the data of the client session
+ * @param httpSession the data of the http session
+ * @param path the path of the request
+ */
+ fun handleRequest(
+ taskSession: TaskSession,
+ httpSession: NanoHTTPD.IHTTPSession,
+ path: MutableList,
+ ): NanoHTTPD.Response {
+ // get the target session id
+ val targetSessionId = path.removeFirstOrNull()
+
+ return if (targetSessionId == null) {
+ // no specific session targeted
+ this.handleRequestGeneric(taskSession, httpSession)
+ } else {
+ // a specific session is targeted
+ this.handleRequestSpecific(taskSession, httpSession, targetSessionId)
+ }
+ }
+
+ /**
+ * Handle a request with no specific session targeted
+ */
+ private fun handleRequestGeneric(
+ taskSession: TaskSession,
+ httpSession: NanoHTTPD.IHTTPSession,
+ ): NanoHTTPD.Response {
+ when (httpSession.method) {
+ // get all the session data
+ NanoHTTPD.Method.GET -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.READ))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "Forbidden"
+ )
+
+ // return the session data
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.OK,
+ "application/json",
+ jsonParser.toJson(taskSession)
+ )
+ }
+ // other action are limited
+ else -> {
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
+ "text/plain",
+ "Unknown method"
+ )
+ }
+ }
+ }
+
+ private fun handleRequestSpecific(
+ taskSession: TaskSession,
+ httpSession: NanoHTTPD.IHTTPSession,
+ targetSessionId: String,
+ ): NanoHTTPD.Response {
+ when (httpSession.method) {
+ // change a specific client session data
+ NanoHTTPD.Method.POST -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "You are not allowed to update a session"
+ )
+
+ // parse the content of the request
+ val targetSession = jsonParser.fromJson(
+ httpSession.inputStream.bufferedReader().readText(),
+ TaskSession::class.java
+ )
+
+ // update the session
+ this.sessionManager.setSessionData(targetSessionId, targetSession)
+
+ // success message
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.OK,
+ "text/plain",
+ "Session updated"
+ )
+ }
+ // delete the session
+ NanoHTTPD.Method.DELETE -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "You are not allowed to delete a session"
+ )
+
+ // delete the target session
+ this.sessionManager.deleteSessionData(targetSessionId)
+
+ // success message
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.OK,
+ "text/plain",
+ "Session deleted"
+ )
+ }
+ // ignore other methods
+ else -> {
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
+ "text/plain",
+ "Invalid method"
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt
new file mode 100644
index 0000000..74d1809
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.connectivity.task.session
+
+enum class TaskPermission {
+ READ,
+ WRITE,
+ ADMIN
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt
new file mode 100644
index 0000000..cb519be
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt
@@ -0,0 +1,31 @@
+package com.faraphel.tasks_valider.connectivity.task.session
+
+
+/**
+ * A role system that can be used for in the task system
+ */
+enum class TaskRole(val value: String) {
+ NONE("none") {
+ override var permissions: List = listOf()
+ },
+ STUDENT("student") {
+ override var permissions = listOf(
+ TaskPermission.READ
+ )
+ },
+ TEACHER("teacher") {
+ override var permissions: List = listOf(
+ TaskPermission.READ,
+ TaskPermission.WRITE
+ )
+ },
+ ADMIN("admin") {
+ override var permissions: List = listOf(
+ TaskPermission.READ,
+ TaskPermission.WRITE,
+ TaskPermission.ADMIN
+ )
+ };
+
+ abstract var permissions: List
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt
new file mode 100644
index 0000000..84c4c37
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt
@@ -0,0 +1,13 @@
+package com.faraphel.tasks_valider.connectivity.task.session
+
+import kotlinx.serialization.Serializable
+
+
+/**
+ * store the data of a session in the task system
+ * @param role the role accorded to the session
+ */
+@Serializable
+data class TaskSession(
+ var role: TaskRole = TaskRole.STUDENT
+)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt
new file mode 100644
index 0000000..e0552c6
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt
@@ -0,0 +1,100 @@
+package com.faraphel.tasks_valider.connectivity.task.session
+
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import fi.iki.elonen.NanoHTTPD
+import java.util.*
+
+
+/**
+ * The manager for the session system
+ */
+class TaskSessionManager {
+ private val sessions = mutableMapOf() ///< sessions specific data
+
+ /**
+ * Create a new session
+ * @param session the data for the session (optional)
+ * @param sessionId the session id to use (optional)
+ * @return a new session identifier
+ */
+ fun newSessionData(
+ session: TaskSession = TaskSession(),
+ sessionId: String = UUID.randomUUID().toString()
+ ): String {
+ this.sessions[sessionId] = session
+ return sessionId
+ }
+
+ /**
+ * Get data from a http session
+ * @param httpSession the HTTP session
+ * @return the session data
+ */
+ fun getSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession? {
+ val sessionId = httpSession.cookies.read("sessionId") ?: return null
+ val sessionData = this.getSessionData(sessionId)
+ return sessionData
+ }
+
+ /**
+ * Get data from a session identifier
+ * @param sessionId the identifier of the session
+ * @return the session data
+ */
+ fun getSessionData(sessionId: String): TaskSession? {
+ return this.sessions[sessionId]
+ }
+
+ /**
+ * Set the data of a session
+ * @param sessionId the identifier of the session
+ * @param session the session data
+ */
+ fun setSessionData(sessionId: String, session: TaskSession) {
+ this.sessions[sessionId] = session
+ }
+
+ /**
+ * Delete a session
+ * @param sessionId the identifier of the session
+ */
+ fun deleteSessionData(sessionId: String): TaskSession? {
+ return this.sessions.remove(sessionId)
+ }
+
+ /**
+ * Get data from a http session. If it does not exist, create it.
+ * @param httpSession the HTTP session
+ */
+ fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession {
+ // try to get the session directly
+ var session = this.getSessionData(httpSession)
+
+ // if the session does not exist, create it
+ if (session == null) {
+ val sessionId = this.newSessionData()
+ session = this.getSessionData(sessionId)!!
+ }
+
+ // return the session
+ return session
+ }
+
+ /**
+ * Insert new cookies for a session in a response
+ * @param response the response to inject cookies into
+ * @param cookies the cookie handler
+ */
+ fun responseSetSessionData(
+ response: NanoHTTPD.Response,
+ cookies: NanoHTTPD.CookieHandler
+ ): NanoHTTPD.Response {
+ // update the cookie of the user
+ cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData()))
+ // load them in the response
+ cookies.unloadQueue(response)
+
+ return response
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/Database.kt b/app/src/main/java/com/faraphel/tasks_valider/database/Database.kt
deleted file mode 100644
index 17222f1..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/Database.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.faraphel.tasks_valider.database
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.room.Database
-import androidx.room.DatabaseConfiguration
-import androidx.room.RoomDatabase
-import androidx.room.TypeConverters
-import com.faraphel.tasks_valider.database.converters.InstantConverter
-import com.faraphel.tasks_valider.database.dao.GroupDao
-import com.faraphel.tasks_valider.database.dao.GroupStudentDao
-import com.faraphel.tasks_valider.database.dao.StudentDao
-import com.faraphel.tasks_valider.database.dao.TaskDao
-import com.faraphel.tasks_valider.database.dao.TaskGroupDao
-import com.faraphel.tasks_valider.database.dao.TeacherDao
-import com.faraphel.tasks_valider.database.entities.Group
-import com.faraphel.tasks_valider.database.entities.GroupStudent
-import com.faraphel.tasks_valider.database.entities.Student
-import com.faraphel.tasks_valider.database.entities.Task
-import com.faraphel.tasks_valider.database.entities.TaskGroup
-import com.faraphel.tasks_valider.database.entities.Teacher
-import java.time.Instant
-
-
-@Database(
- entities = [
- Group::class,
- Student::class,
- Teacher::class,
- Task::class,
-
- GroupStudent::class,
- TaskGroup::class,
- ],
- version = 1
-)
-@TypeConverters(
- InstantConverter::class
-)
-abstract class Database : RoomDatabase() {
- // entities
- abstract fun groupDao(): GroupDao
- abstract fun studentDao(): StudentDao
- abstract fun teacherDao(): TeacherDao
- abstract fun taskDao(): TaskDao
-
- // relations
- abstract fun groupStudentDao(): GroupStudentDao
- abstract fun taskGroupDao(): TaskGroupDao
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt
new file mode 100644
index 0000000..f2ff03e
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt
@@ -0,0 +1,40 @@
+package com.faraphel.tasks_valider.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import com.faraphel.tasks_valider.database.converters.InstantConverter
+import com.faraphel.tasks_valider.database.dao.*
+import com.faraphel.tasks_valider.database.entities.*
+
+
+/**
+ * The database for the tasks' application.
+ * Contains the entities and the relations between them.
+ */
+@Database(
+ entities = [
+ ClassEntity::class,
+ PersonEntity::class,
+ SessionEntity::class,
+ SubjectEntity::class,
+ TaskEntity::class,
+ ValidationEntity::class,
+
+ RelationClassPersonEntity::class,
+ ],
+ version = 1
+)
+@TypeConverters(
+ InstantConverter::class
+)
+abstract class TaskDatabase: RoomDatabase() {
+ abstract fun classDao(): ClassDao
+ abstract fun personDao(): PersonDao
+ abstract fun sessionDao(): SessionDao
+ abstract fun subjectDao(): SubjectDao
+ abstract fun taskDao(): TaskDao
+ abstract fun validationDao(): ValidationDao
+
+ abstract fun relationClassPersonDao(): RelationClassPersonDao
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt
new file mode 100644
index 0000000..2303a34
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt
@@ -0,0 +1,110 @@
+package com.faraphel.tasks_valider.database.api
+
+import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
+import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
+import com.faraphel.tasks_valider.database.TaskDatabase
+import com.faraphel.tasks_valider.database.api.entities.*
+import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
+import com.faraphel.tasks_valider.database.entities.*
+import fi.iki.elonen.NanoHTTPD
+
+class TaskDatabaseApi(private val database: TaskDatabase) {
+ private val api: Map = mapOf(
+ ClassEntity.TABLE_NAME to ClassApi(this.database.classDao()),
+ PersonEntity.TABLE_NAME to PersonApi(this.database.personDao()),
+ SessionEntity.TABLE_NAME to SessionApi(this.database.sessionDao()),
+ SubjectEntity.TABLE_NAME to SubjectApi(this.database.subjectDao()),
+ TaskEntity.TABLE_NAME to TaskApi(this.database.taskDao()),
+ ValidationEntity.TABLE_NAME to ValidationApi(this.database.validationDao()),
+
+ RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao()),
+ )
+
+ /**
+ * handle an API request
+ * @param taskSession the current user session
+ * @param httpSession the http session
+ * @param path the path of the request
+ */
+ fun handleRequest(
+ taskSession: TaskSession,
+ httpSession: NanoHTTPD.IHTTPSession,
+ path: MutableList
+ ): NanoHTTPD.Response {
+ // get the entity name
+ val entityName = path.removeFirstOrNull()
+ ?: return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.BAD_REQUEST,
+ "text/plain",
+ "Missing entity name"
+ )
+
+ // get the correspond Api object for this entity
+ val entityApi = this.api[entityName]
+ ?: return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.NOT_FOUND,
+ "text/plain",
+ "Unknown entity name"
+ )
+
+ // dispatch the request to the correct entity API
+ when (httpSession.method) {
+ // check if the data is in the database
+ // TODO(Faraphel): should only be allowed to read data concerning the current class session
+ NanoHTTPD.Method.HEAD -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.READ))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "Forbidden"
+ )
+
+ return entityApi.head(httpSession)
+ }
+ // get the data from the database
+ NanoHTTPD.Method.GET -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.READ))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "Forbidden"
+ )
+
+ return entityApi.get(httpSession)
+ }
+ // insert the data into the database
+ NanoHTTPD.Method.POST -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.WRITE))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "Forbidden"
+ )
+
+ return entityApi.post(httpSession)
+ }
+ // delete the data from the database
+ NanoHTTPD.Method.DELETE -> {
+ // check the permission of the session
+ if (taskSession.role.permissions.contains(TaskPermission.WRITE))
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.FORBIDDEN,
+ "text/plain",
+ "Forbidden"
+ )
+
+ return entityApi.delete(httpSession)
+ }
+ // other methods are not allowed
+ else ->
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
+ "text/plain",
+ "Method not allowed"
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ClassApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ClassApi.kt
new file mode 100644
index 0000000..a012aa2
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ClassApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.ClassEntity
+
+class ClassApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/PersonApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/PersonApi.kt
new file mode 100644
index 0000000..d9edf18
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/PersonApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.PersonEntity
+
+class PersonApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/RelationClassPersonApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/RelationClassPersonApi.kt
new file mode 100644
index 0000000..3f437b5
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/RelationClassPersonApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
+
+class RelationClassPersonApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SessionApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SessionApi.kt
new file mode 100644
index 0000000..2eb7dc2
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SessionApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.SessionEntity
+
+class SessionApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SubjectApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SubjectApi.kt
new file mode 100644
index 0000000..caca63a
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SubjectApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.SubjectEntity
+
+class SubjectApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskApi.kt
new file mode 100644
index 0000000..c331a98
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.TaskEntity
+
+class TaskApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ValidationApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ValidationApi.kt
new file mode 100644
index 0000000..60e3010
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ValidationApi.kt
@@ -0,0 +1,7 @@
+package com.faraphel.tasks_valider.database.api.entities
+
+import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.ValidationEntity
+
+class ValidationApi(dao: BaseDao) : BaseJsonApi(dao)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseApi.kt
new file mode 100644
index 0000000..d40665e
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseApi.kt
@@ -0,0 +1,32 @@
+package com.faraphel.tasks_valider.database.api.entities.base
+
+import fi.iki.elonen.NanoHTTPD
+
+/**
+ * A base for the API to handle the database operations with an HTTP server.
+ */
+interface BaseApi {
+ /**
+ * Handle the HEAD request
+ * This is used to check if a data exists in the database
+ */
+ fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
+
+ /**
+ * Handle the GET request
+ * This is used to get data from the database
+ */
+ fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
+
+ /**
+ * Handle the POST request
+ * This is used to insert data into the database
+ */
+ fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
+
+ /**
+ * Handle the PUT request
+ * This is used to delete data from the database
+ */
+ fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseJsonApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseJsonApi.kt
new file mode 100644
index 0000000..4668feb
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseJsonApi.kt
@@ -0,0 +1,72 @@
+package com.faraphel.tasks_valider.database.api.entities.base
+
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+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(private val dao: BaseDao) : BaseApi {
+ companion object {
+ private val parser = Gson() ///< The JSON parser
+ }
+
+ private val entityTypeToken: TypeToken = object: TypeToken() {} ///< the type of the managed entity
+
+ // Requests
+
+ override fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
+ val obj = parser.fromJson(
+ session.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(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.OK,
+ "application/json",
+ parser.toJson(this.dao.getAll())
+ )
+ }
+
+ override fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
+ val obj = parser.fromJson(
+ session.inputStream.bufferedReader().readText(),
+ this.entityTypeToken.type
+ )
+ val id = this.dao.insert(obj)
+
+ return NanoHTTPD.newFixedLengthResponse(
+ NanoHTTPD.Response.Status.CREATED,
+ "text/plain",
+ id.toString()
+ )
+ }
+
+ override fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
+ val obj = parser.fromJson(
+ session.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()
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt
new file mode 100644
index 0000000..baef405
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt
@@ -0,0 +1,40 @@
+package com.faraphel.tasks_valider.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.ClassEntity
+import com.faraphel.tasks_valider.database.entities.PersonEntity
+import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
+import com.faraphel.tasks_valider.database.entities.SessionEntity
+
+@Dao
+interface ClassDao : BaseDao {
+ @Query("SELECT * FROM ${ClassEntity.TABLE_NAME}")
+ override fun getAll(): List
+
+ /**
+ * Get the object from its identifier
+ */
+ @Query("SELECT * FROM ${ClassEntity.TABLE_NAME} WHERE id = :id")
+ fun getById(id: Long): ClassEntity
+
+ /**
+ * Get all the sessions this class attended
+ * @param id the id of the class
+ */
+ @Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE class_id = :id")
+ fun getSessions(id: Long): List
+
+ /**
+ * Get all the students in a class
+ * @param id the id of the class
+ */
+ @Query(
+ "SELECT * FROM ${PersonEntity.TABLE_NAME} " +
+ "JOIN ${RelationClassPersonEntity.TABLE_NAME} " +
+ "ON ${PersonEntity.TABLE_NAME}.id = ${RelationClassPersonEntity.TABLE_NAME}.student_id " +
+ "WHERE ${RelationClassPersonEntity.TABLE_NAME}.class_id = :id"
+ )
+ fun getStudents(id: Long): List
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupDao.kt
deleted file mode 100644
index 33511fd..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupDao.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.faraphel.tasks_valider.database.dao
-
-import androidx.room.Dao
-import androidx.room.Query
-import androidx.room.RewriteQueriesToDropUnusedColumns
-import com.faraphel.tasks_valider.database.dao.base.BaseDao
-import com.faraphel.tasks_valider.database.entities.Group
-import com.faraphel.tasks_valider.database.entities.Student
-
-
-@Dao
-interface GroupDao : BaseDao {
- @Query("SELECT * FROM `groups`")
- override fun getAll(): List
-
- @Query("SELECT * FROM `groups` WHERE id = :id")
- fun getById(id: Long): Group
-
- /**
- Allow to get all groups with a specific student
- */
- @Query(
- "SELECT * FROM `groups` " +
- "JOIN `group_student` ON `groups`.id = `group_student`.student_id " +
- "WHERE `group_student`.student_id = :studentId"
- )
- @RewriteQueriesToDropUnusedColumns
- fun filterByStudentId(studentId: Long): List
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupStudentDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupStudentDao.kt
deleted file mode 100644
index 2bd355e..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupStudentDao.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.faraphel.tasks_valider.database.dao
-
-import androidx.room.Dao
-import androidx.room.Query
-import androidx.room.RewriteQueriesToDropUnusedColumns
-import com.faraphel.tasks_valider.database.dao.base.BaseDao
-import com.faraphel.tasks_valider.database.entities.Group
-import com.faraphel.tasks_valider.database.entities.GroupStudent
-import com.faraphel.tasks_valider.database.entities.Student
-
-
-@Dao
-interface GroupStudentDao : BaseDao {
- @Query("SELECT * FROM `group_student`")
- override fun getAll(): List
-
- @Query("SELECT * FROM `group_student` WHERE group_id = :groupId AND student_id = :studentId")
- fun getById(groupId: Long, studentId: Long): GroupStudent
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt
new file mode 100644
index 0000000..601a2f6
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt
@@ -0,0 +1,44 @@
+package com.faraphel.tasks_valider.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.*
+
+@Dao
+interface PersonDao : BaseDao {
+ @Query("SELECT * FROM ${PersonEntity.TABLE_NAME}")
+ override fun getAll(): List
+
+ /**
+ * Get the object from its identifier
+ */
+ @Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id")
+ fun getById(id: Long): PersonEntity
+
+ /**
+ * Allow to get all the classes the person is attending as a student
+ */
+ @Query(
+ "SELECT * FROM ${ClassEntity.TABLE_NAME} " +
+ "JOIN ${RelationClassPersonEntity.TABLE_NAME} " +
+ "ON ${ClassEntity.TABLE_NAME}.id = ${RelationClassPersonEntity.TABLE_NAME}.student_id " +
+ "WHERE ${RelationClassPersonEntity.TABLE_NAME}.student_id = :id"
+ )
+ fun getClasses(id: Long): List
+
+
+ /**
+ * Get all the tasks this user approved as a teacher
+ * @param id the id of the person
+ */
+ @Query("SELECT * FROM ${ValidationEntity.TABLE_NAME} WHERE teacher_id = :id")
+ fun getTasksApproved(id: Long): List
+
+ /**
+ * Get all the tasks this user validated as a student
+ * @param id the id of the person
+ */
+ @Query("SELECT * FROM ${ValidationEntity.TABLE_NAME} WHERE student_id = :id")
+ fun getTasksValidated(id: Long): List
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt
new file mode 100644
index 0000000..b0296df
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt
@@ -0,0 +1,23 @@
+package com.faraphel.tasks_valider.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
+
+@Dao
+interface RelationClassPersonDao : BaseDao {
+ @Query("SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME}")
+ override fun getAll(): List
+
+ /**
+ * Get the object from its identifiers
+ */
+ @Query(
+ "SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME} " +
+ "WHERE " +
+ "class_id = :classId AND " +
+ "student_id = :studentId"
+ )
+ fun getById(classId: Long, studentId: Long): RelationClassPersonEntity
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt
new file mode 100644
index 0000000..8054915
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt
@@ -0,0 +1,18 @@
+package com.faraphel.tasks_valider.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.SessionEntity
+
+@Dao
+interface SessionDao : BaseDao {
+ @Query("SELECT * FROM ${SessionEntity.TABLE_NAME}")
+ override fun getAll(): List
+
+ /**
+ * Get the object from its identifier
+ */
+ @Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE id = :id")
+ fun getById(id: Long): SessionEntity
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/StudentDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/StudentDao.kt
deleted file mode 100644
index e4b8dfb..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/StudentDao.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.faraphel.tasks_valider.database.dao
-
-import androidx.lifecycle.LiveData
-import androidx.room.Dao
-import androidx.room.Query
-import androidx.room.RewriteQueriesToDropUnusedColumns
-import com.faraphel.tasks_valider.database.dao.base.BaseDao
-import com.faraphel.tasks_valider.database.entities.Group
-import com.faraphel.tasks_valider.database.entities.Person
-import com.faraphel.tasks_valider.database.entities.Student
-
-
-@Dao
-interface StudentDao : BaseDao {
- @Query("SELECT * FROM `students`")
- override fun getAll(): List
-
- @Query("SELECT * FROM `students` WHERE id = :id")
- fun getById(id: Long): Student
-
-
- /**
- Allow to get all the students in a group
- */
- @Query(
- "SELECT * FROM `students` " +
- "JOIN `group_student` ON `students`.id = `group_student`.student_id " +
- "WHERE `group_student`.group_id = :groupId"
- )
- @RewriteQueriesToDropUnusedColumns
- fun filterByGroupId(groupId: Long): List
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt
new file mode 100644
index 0000000..8dc676a
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt
@@ -0,0 +1,27 @@
+package com.faraphel.tasks_valider.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.SessionEntity
+import com.faraphel.tasks_valider.database.entities.SubjectEntity
+import com.faraphel.tasks_valider.database.entities.TaskEntity
+
+@Dao
+interface SubjectDao : BaseDao {
+ @Query("SELECT * FROM ${SubjectEntity.TABLE_NAME}")
+ override fun getAll(): List
+
+ /**
+ * Get the object from its identifier
+ */
+ @Query("SELECT * FROM ${SubjectEntity.TABLE_NAME} WHERE id = :id")
+ fun getById(id: Long): SubjectEntity
+
+ /**
+ * Get all the tasks available in a subject
+ * @param id the id of the subject
+ */
+ @Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE subject_id = :id")
+ fun getSessions(id: Long): List
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt
index 1e2cb8c..0053cd0 100644
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt
@@ -2,28 +2,25 @@ package com.faraphel.tasks_valider.database.dao
import androidx.room.Dao
import androidx.room.Query
-import androidx.room.RewriteQueriesToDropUnusedColumns
import com.faraphel.tasks_valider.database.dao.base.BaseDao
-import com.faraphel.tasks_valider.database.entities.Group
-import com.faraphel.tasks_valider.database.entities.Task
-
+import com.faraphel.tasks_valider.database.entities.TaskEntity
+import com.faraphel.tasks_valider.database.entities.ValidationEntity
@Dao
-interface TaskDao : BaseDao {
- @Query("SELECT * FROM `tasks`")
- override fun getAll(): List
-
- @Query("SELECT * FROM `tasks` WHERE id = :id")
- fun getById(id: Long): Task
+interface TaskDao : BaseDao {
+ @Query("SELECT * FROM ${TaskEntity.TABLE_NAME}")
+ override fun getAll(): List
/**
- Get all the tasks for a specific group
+ * Get the object from its identifier
*/
- @Query(
- "SELECT * FROM `tasks` " +
- "JOIN `task_group` ON `tasks`.id = `task_group`.task_id " +
- "WHERE `task_group`.group_id = :groupId"
- )
- @RewriteQueriesToDropUnusedColumns
- fun filterByGroupId(groupId: Long): List
+ @Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE id = :id")
+ fun getById(id: Long): TaskEntity
+
+ /**
+ * Get all the validations have been approved for this tasks
+ * @param id the id of the task
+ */
+ @Query("SELECT * FROM ${ValidationEntity.TABLE_NAME} WHERE task_id = :id")
+ fun getTasksValidated(id: Long): List
}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskGroupDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskGroupDao.kt
deleted file mode 100644
index 0caa673..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskGroupDao.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.faraphel.tasks_valider.database.dao
-
-import androidx.room.Dao
-import androidx.room.Query
-import com.faraphel.tasks_valider.database.dao.base.BaseDao
-import com.faraphel.tasks_valider.database.entities.TaskGroup
-
-
-@Dao
-interface TaskGroupDao : BaseDao {
- @Query("SELECT * FROM `task_group`")
- override fun getAll(): List
-
- @Query("SELECT * FROM `task_group` WHERE task_id = :taskId AND group_id = :groupId")
- fun getById(taskId: Long, groupId: Long): TaskGroup
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TeacherDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/TeacherDao.kt
deleted file mode 100644
index f489725..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TeacherDao.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.faraphel.tasks_valider.database.dao
-
-import androidx.room.Dao
-import androidx.room.Query
-import com.faraphel.tasks_valider.database.dao.base.BaseDao
-import com.faraphel.tasks_valider.database.entities.Person
-import com.faraphel.tasks_valider.database.entities.Student
-import com.faraphel.tasks_valider.database.entities.Teacher
-
-
-@Dao
-interface TeacherDao : BaseDao {
- @Query("SELECT * FROM `teachers`")
- override fun getAll(): List
-
- @Query("SELECT * FROM `teachers` WHERE id = :id")
- fun getById(id: Long): Teacher
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt
new file mode 100644
index 0000000..544da65
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt
@@ -0,0 +1,24 @@
+package com.faraphel.tasks_valider.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import com.faraphel.tasks_valider.database.dao.base.BaseDao
+import com.faraphel.tasks_valider.database.entities.ValidationEntity
+
+@Dao
+interface ValidationDao : BaseDao {
+ @Query("SELECT * FROM ${ValidationEntity.TABLE_NAME}")
+ override fun getAll(): List
+
+ /**
+ * Get the object from its identifiers
+ */
+ @Query(
+ "SELECT * FROM ${ValidationEntity.TABLE_NAME} " +
+ "WHERE " +
+ "teacher_id = :teacherId and " +
+ "student_id = :studentId and " +
+ "task_id = :taskId"
+ )
+ fun getById(teacherId: Long, studentId: Long, taskId: Long): ValidationEntity
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/base/BaseDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/base/BaseDao.kt
index c9c4fe2..b06e62f 100644
--- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/base/BaseDao.kt
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/base/BaseDao.kt
@@ -2,18 +2,40 @@ package com.faraphel.tasks_valider.database.dao.base
import androidx.room.Delete
import androidx.room.Insert
+import androidx.room.OnConflictStrategy
import androidx.room.Update
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+
+/**
+ * A base DAO to handle the database operations.
+ * @param Entity the entity to handle
+ */
interface BaseDao {
- @Insert
+ /**
+ * Check if the entities exists in the database.
+ */
+ fun exists(vararg entities: Entity): Boolean {
+ return this.getAll().containsAll(entities.toList())
+ }
+
+ /**
+ * Insert the entities into the database.
+ */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg entities: Entity): List
- @Update
- fun update(vararg entities: Entity): Int
-
+ /**
+ * Delete the entities from the database.
+ */
@Delete
fun delete(vararg entities: Entity): Int
+ /**
+ * Get all the entities from the database.
+ * TODO(Faraphel): support filters ?
+ */
fun getAll(): List
}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt
new file mode 100644
index 0000000..97eb47e
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt
@@ -0,0 +1,17 @@
+package com.faraphel.tasks_valider.database.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
+
+
+@Entity(tableName = ClassEntity.TABLE_NAME)
+data class ClassEntity (
+ @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ @ColumnInfo("name") val name: String,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "classes"
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/Group.kt
deleted file mode 100644
index d0f3a09..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Group.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.faraphel.tasks_valider.database.entities
-
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-
-
-@Entity(tableName = "groups")
-data class Group (
- @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
- @ColumnInfo("name") val name: String? = null,
-)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Person.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/Person.kt
deleted file mode 100644
index 347b05d..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Person.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.faraphel.tasks_valider.database.entities
-
-import java.util.Locale
-
-
-open class Person (
- open val id: Long = 0,
- open val firstName: String,
- open val lastName: String,
-) {
- /**
- Get the full name of the person
- */
- val fullName: String
- get() {
- return "${firstName.capitalize(Locale.ROOT)} ${lastName.uppercase()}"
- }
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt
new file mode 100644
index 0000000..2ee3506
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt
@@ -0,0 +1,19 @@
+package com.faraphel.tasks_valider.database.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
+import java.util.*
+
+@Entity(tableName = PersonEntity.TABLE_NAME)
+data class PersonEntity (
+ @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ @ColumnInfo("first_name") val firstName: String,
+ @ColumnInfo("last_name") val lastName: String,
+ @ColumnInfo("card_id") val cardId: UUID,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "persons"
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupStudent.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt
similarity index 51%
rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupStudent.kt
rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt
index 2e830e3..06c024a 100644
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupStudent.kt
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt
@@ -3,31 +3,34 @@ package com.faraphel.tasks_valider.database.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
-import com.faraphel.tasks_valider.database.entities.Group
-import com.faraphel.tasks_valider.database.entities.Student
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
@Entity(
- tableName = "group_student",
+ tableName = RelationClassPersonEntity.TABLE_NAME,
primaryKeys = [
- "group_id",
- "student_id"
+ "student_id",
+ "class_id",
],
foreignKeys = [
ForeignKey(
- entity = Group::class,
- parentColumns = ["id"],
- childColumns = ["group_id"],
- onDelete = ForeignKey.CASCADE
- ),
- ForeignKey(
- entity = Student::class,
+ entity = PersonEntity::class,
parentColumns = ["id"],
childColumns = ["student_id"],
onDelete = ForeignKey.CASCADE
- )
+ ),
+ ForeignKey(
+ entity = ClassEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["class_id"],
+ onDelete = ForeignKey.CASCADE
+ ),
]
)
-data class GroupStudent(
- @ColumnInfo("group_id", index = true) val groupId: Long,
+data class RelationClassPersonEntity (
@ColumnInfo("student_id", index = true) val studentId: Long,
-)
+ @ColumnInfo("class_id", index = true) val classId: Long,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "relation_class_person"
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt
new file mode 100644
index 0000000..0f2edf4
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt
@@ -0,0 +1,31 @@
+package com.faraphel.tasks_valider.database.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.PrimaryKey
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
+import java.time.Instant
+
+@Entity(
+ tableName = SessionEntity.TABLE_NAME,
+ foreignKeys = [
+ ForeignKey(
+ entity = ClassEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["class_id"],
+ onDelete = ForeignKey.CASCADE
+ ),
+ ]
+)
+data class SessionEntity (
+ @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ @ColumnInfo("name") val name: String? = null,
+ @ColumnInfo("start") val start: Instant,
+
+ @ColumnInfo("class_id", index = true) val classId: Long? = null,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "sessions"
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Student.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/Student.kt
deleted file mode 100644
index 4cff4c6..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Student.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.faraphel.tasks_valider.database.entities
-
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-
-@Entity(tableName = "students")
-class Student(
- @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0,
- @ColumnInfo("first_name") override val firstName: String,
- @ColumnInfo("last_name") override val lastName: String
-) : Person(id, firstName, lastName)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt
new file mode 100644
index 0000000..ee8aa83
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt
@@ -0,0 +1,16 @@
+package com.faraphel.tasks_valider.database.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
+
+@Entity(tableName = SubjectEntity.TABLE_NAME)
+data class SubjectEntity (
+ @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ @ColumnInfo("name") val name: String,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "subjects"
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Task.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/Task.kt
deleted file mode 100644
index 15e1361..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Task.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.faraphel.tasks_valider.database.entities
-
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-
-@Entity(tableName = "tasks")
-data class Task (
- @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
- @ColumnInfo("title") val title: String,
- @ColumnInfo("description") val description: String,
-)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt
new file mode 100644
index 0000000..4548c36
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt
@@ -0,0 +1,30 @@
+package com.faraphel.tasks_valider.database.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.PrimaryKey
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
+
+@Entity(
+ tableName = TaskEntity.TABLE_NAME,
+ foreignKeys = [
+ ForeignKey(
+ entity = SubjectEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["subject_id"],
+ onDelete = ForeignKey.CASCADE
+ ),
+ ]
+)
+data class TaskEntity (
+ @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ @ColumnInfo("title") val title: String,
+ @ColumnInfo("description") val description: String? = null,
+
+ @ColumnInfo("subject_id", index = true) val subjectId: Long,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "tasks"
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroup.kt
deleted file mode 100644
index 07e9c57..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroup.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.faraphel.tasks_valider.database.entities
-
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.ForeignKey
-import java.time.Instant
-
-
-@Entity(
- tableName = "task_group",
- primaryKeys = [
- "task_id",
- "group_id"
- ],
- foreignKeys = [
- ForeignKey(
- entity = Group::class,
- parentColumns = ["id"],
- childColumns = ["group_id"],
- onDelete = ForeignKey.CASCADE
- ),
- ForeignKey(
- entity = Task::class,
- parentColumns = ["id"],
- childColumns = ["task_id"],
- onDelete = ForeignKey.CASCADE
- ),
- ForeignKey(
- entity = Teacher::class,
- parentColumns = ["id"],
- childColumns = ["approval_teacher_id"],
- onDelete = ForeignKey.CASCADE
- ),
- ]
-)
-data class TaskGroup (
- @ColumnInfo("task_id") val taskId: Long,
- @ColumnInfo("group_id") val groupId: Long,
- @ColumnInfo("approval_status") var approvalStatus: Boolean = false,
- @ColumnInfo("approval_teacher_id") val approvalTeacherId: Long? = null,
- @ColumnInfo("approval_time") val approvalTime: Instant? = null
-)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Teacher.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/Teacher.kt
deleted file mode 100644
index b499973..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Teacher.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.faraphel.tasks_valider.database.entities
-
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
-
-@Entity(tableName = "teachers")
-class Teacher(
- @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0,
- @ColumnInfo("first_name") override val firstName: String,
- @ColumnInfo("last_name") override val lastName: String
-) : Person(id, firstName, lastName)
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt
new file mode 100644
index 0000000..467f5df
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt
@@ -0,0 +1,48 @@
+package com.faraphel.tasks_valider.database.entities
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import com.faraphel.tasks_valider.database.entities.base.BaseEntity
+import java.time.Instant
+
+@Entity(
+ tableName = ValidationEntity.TABLE_NAME,
+ primaryKeys = [
+ "teacher_id",
+ "student_id",
+ "task_id",
+ ],
+ foreignKeys = [
+ ForeignKey(
+ entity = PersonEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["teacher_id"],
+ onDelete = ForeignKey.CASCADE
+ ),
+ ForeignKey(
+ entity = PersonEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["student_id"],
+ onDelete = ForeignKey.CASCADE
+ ),
+ ForeignKey(
+ entity = TaskEntity::class,
+ parentColumns = ["id"],
+ childColumns = ["task_id"],
+ onDelete = ForeignKey.CASCADE
+ ),
+ ]
+)
+data class ValidationEntity (
+ @ColumnInfo("date") val date: Instant,
+
+ @ColumnInfo("teacher_id", index = true) val teacherId: Long,
+ @ColumnInfo("student_id", index = true) val studentId: Long,
+ @ColumnInfo("task_id", index = true) val taskId: Long,
+) : BaseEntity() {
+ companion object {
+ const val TABLE_NAME = "validations"
+ }
+}
+
diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/base/BaseEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/base/BaseEntity.kt
new file mode 100644
index 0000000..44654b2
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/base/BaseEntity.kt
@@ -0,0 +1,3 @@
+package com.faraphel.tasks_valider.database.entities.base
+
+open class BaseEntity
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client/screen.kt
new file mode 100644
index 0000000..2cc75b7
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client/screen.kt
@@ -0,0 +1,64 @@
+package com.faraphel.tasks_valider.ui.screen.communication.internet.client
+
+import android.app.Activity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.input.KeyboardType
+import com.faraphel.tasks_valider.connectivity.task.TaskClient
+import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_ADDRESS
+import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
+import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
+import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
+
+
+@Composable
+fun CommunicationInternetClientScreen(activity: Activity) {
+ val client = remember { mutableStateOf(null) }
+
+ if (client.value == null) CommunicationInternetClientContent(client)
+ else TaskSessionScreen(activity, client.value!!)
+}
+
+
+@Composable
+fun CommunicationInternetClientContent(client: MutableState) {
+ val serverAddress = remember { mutableStateOf(DEFAULT_SERVER_ADDRESS) }
+ val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
+
+ Column {
+ // server address
+ TextField(
+ value = serverAddress.value,
+ onValueChange = { text ->
+ serverAddress.value = text
+ }
+ )
+
+ // 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
+ }
+ }
+ )
+
+ Button(onClick = {
+ // TODO(Faraphel): check if the server is reachable
+ client.value = TaskClient(serverAddress.value, serverPort.intValue)
+ }) {
+ Text("Connect")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt
new file mode 100644
index 0000000..335c2f7
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt
@@ -0,0 +1,36 @@
+package com.faraphel.tasks_valider.ui.screen.communication.internet
+
+import android.app.Activity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.faraphel.tasks_valider.ui.screen.communication.internet.client.CommunicationInternetClientScreen
+import com.faraphel.tasks_valider.ui.screen.communication.internet.server.CommunicationInternetServerScreen
+
+
+@Composable
+fun CommunicationInternetScreen(activity: Activity) {
+ val controller = rememberNavController()
+
+ NavHost(navController = controller, startDestination = "mode") {
+ composable("mode") { CommunicationInternetSelectContent(controller) }
+ composable("client") { CommunicationInternetClientScreen(activity) }
+ composable("server") { CommunicationInternetServerScreen(activity) }
+ }
+}
+
+
+@Composable
+fun CommunicationInternetSelectContent(controller: NavController) {
+ Column {
+ // client mode
+ Button(onClick = { controller.navigate("client") }) { Text("Client") }
+ // server mode
+ Button(onClick = { controller.navigate("server") }) { Text("Server") }
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt
new file mode 100644
index 0000000..cdf8f13
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt
@@ -0,0 +1,99 @@
+package com.faraphel.tasks_valider.ui.screen.communication.internet.server
+
+import android.app.Activity
+import android.util.Log
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.room.Room
+import com.faraphel.tasks_valider.connectivity.task.TaskClient
+import com.faraphel.tasks_valider.connectivity.task.TaskServer
+import com.faraphel.tasks_valider.database.TaskDatabase
+import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
+import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
+import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
+
+
+@Composable
+fun CommunicationInternetServerScreen(activity: Activity) {
+ val client = remember { mutableStateOf(null) }
+
+ // if the server is not created, prompt the user for the server configuration
+ if (client.value == null) CommunicationInternetServerContent(activity, client)
+ // else, go to the base tasks screen
+ else TaskSessionScreen(activity, client.value!!)
+}
+
+
+@Composable
+fun CommunicationInternetServerContent(activity: Activity, client: MutableState) {
+ val expandedStudentList = remember { mutableStateOf(false) }
+ val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
+
+ Column {
+ // student list
+ Button(onClick = { expandedStudentList.value = !expandedStudentList.value }) {
+ Text(text = "Select Students List")
+ }
+ DropdownMenu(
+ expanded = expandedStudentList.value,
+ onDismissRequest = { expandedStudentList.value = false }
+ ) {
+ DropdownMenuItem(
+ text = { Text("ISRI") },
+ onClick = {}
+ )
+ DropdownMenuItem(
+ text = { Text("MIAGE") },
+ onClick = {}
+ )
+ // TODO(Faraphel): student lists should be loaded from the database or a file
+ }
+
+ // 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
+ }
+ }
+ )
+
+ Button(onClick = {
+ // Reset the database | TODO(Faraphel): only for testing purpose
+ activity.deleteDatabase("local")
+
+ // Create the database
+ val database = Room.databaseBuilder(
+ activity,
+ TaskDatabase::class.java,
+ "local"
+ ).build()
+
+ // Create the server
+ Log.i("room-server", "creating the server")
+ Thread { // a thread is used for networking
+ val server = TaskServer(serverPort.intValue, database)
+ server.start()
+
+ // Get the client from the server
+ client.value = server.getClientAdmin()
+ }.start()
+ }) {
+ Text("Create")
+ }
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt
new file mode 100644
index 0000000..bb260ce
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt
@@ -0,0 +1,77 @@
+package com.faraphel.tasks_valider.ui.screen.communication
+
+import android.app.Activity
+import android.widget.Toast
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.navigation.NavController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
+import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetScreen
+import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen
+
+
+/**
+ * CommunicationController is the main controller for the communication screen.
+ * It is responsible for handling the navigation between the different communication methods.
+ * It is also responsible for initializing the communication methods.
+ * @param activity: The activity that hosts the communication screen.
+ */
+@Composable
+fun CommunicationScreen(activity: Activity) {
+ val controller = rememberNavController()
+
+ NavHost(
+ navController = controller,
+ startDestination = "select"
+ ) {
+ composable("select") {
+ CommunicationSelectContent(controller, activity)
+ }
+ composable("internet") {
+ CommunicationInternetScreen(activity)
+ }
+ composable("wifi-p2p") {
+ val bwfManager = BwfManager.fromActivity(activity)
+ CommunicationWifiP2pScreen(activity, bwfManager)
+ }
+ }
+}
+
+
+/**
+ * Communication screen that allows the user to choose the communication mode
+ */
+@Composable
+fun CommunicationSelectContent(controller: NavController, activity: Activity) {
+ val isWifiP2pSupported = BwfManager.isSupported(activity)
+
+ Column {
+ // internet communication mode
+ Button(onClick = { controller.navigate("internet") }) {
+ Text("Internet")
+ }
+
+ // wifi-direct communication mode
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ // if the WiFi-Direct is not supported, the button is grayed out
+ containerColor = if (isWifiP2pSupported) Color.Unspecified else Color.Gray
+ ),
+ onClick = {
+ // if the WiFi-Direct is supported, navigate to the WiFi-Direct screen
+ if (isWifiP2pSupported) controller.navigate("wifi-p2p")
+ // if the WiFi-Direct is not supported, show a toast message
+ else Toast.makeText(activity, "WiFi-Direct is not supported on this device", Toast.LENGTH_SHORT).show()
+ }
+ ) {
+ Text("WiFi-Direct")
+ }
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/settings.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/settings.kt
new file mode 100644
index 0000000..e9362dc
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/settings.kt
@@ -0,0 +1,6 @@
+package com.faraphel.tasks_valider.ui.screen.communication
+
+
+const val DEFAULT_SERVER_ADDRESS: String = "127.0.0.1"
+const val DEFAULT_SERVER_PORT: Int = 9876
+val RANGE_SERVER_PORT: IntRange = 1024..65535
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt
new file mode 100644
index 0000000..3ba8cdd
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt
@@ -0,0 +1,57 @@
+package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client
+
+import android.app.Activity
+import android.net.wifi.p2p.WifiP2pConfig
+import android.net.wifi.p2p.WifiP2pDevice
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
+import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
+
+
+@Composable
+fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager) {
+ val selectedDevice = remember { mutableStateOf(null) }
+ val isConnected = remember { mutableStateOf(false) }
+
+ // if connected, show the task group screen
+ if (isConnected.value) {
+ // TaskGroupScreen(activity, null)
+ // TODO(Faraphel): finish the connection
+ return
+ }
+
+
+ // if the device is selected but not connected, try to connect
+ if (selectedDevice.value != null) {
+ // TODO(Faraphel): error handling
+ val config = WifiP2pConfig().apply {
+ deviceAddress = selectedDevice.value!!.deviceAddress
+ }
+ bwfManager.connect(config) {
+ isConnected.value = true
+ }
+ return
+ }
+
+ // display the list of devices
+ CommunicationWifiP2pClientContent(bwfManager, selectedDevice)
+}
+
+
+@Composable
+fun CommunicationWifiP2pClientContent(
+ bwfManager: BwfManager,
+ selectedDevice: MutableState
+) {
+ Column {
+ WifiP2pDeviceListWidget(
+ peers = bwfManager.statePeers.value,
+ filter = { device: WifiP2pDevice -> device.isGroupOwner },
+ selectedDevice,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt
new file mode 100644
index 0000000..bd21fef
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt
@@ -0,0 +1,37 @@
+package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p
+
+import android.app.Activity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
+import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client.CommunicationWifiP2pClientScreen
+import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server.CommunicationWifiP2pServerScreen
+
+
+@Composable
+fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) {
+ val controller = rememberNavController()
+
+ NavHost(navController = controller, startDestination = "mode") {
+ composable("mode") { CommunicationWifiP2pSelectContent(controller) }
+ composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) }
+ composable("server") { CommunicationWifiP2pServerScreen(activity, bwfManager) }
+ }
+}
+
+
+@Composable
+fun CommunicationWifiP2pSelectContent(controller: NavController) {
+ Column {
+ // client mode
+ Button(onClick = { controller.navigate("client") }) { Text("Client") }
+ // server mode
+ Button(onClick = { controller.navigate("server") }) { Text("Server") }
+ }
+}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt
new file mode 100644
index 0000000..f608bc0
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt
@@ -0,0 +1,106 @@
+package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server
+
+import android.app.Activity
+import android.util.Log
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.room.Room
+import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
+import com.faraphel.tasks_valider.connectivity.task.TaskClient
+import com.faraphel.tasks_valider.connectivity.task.TaskServer
+import com.faraphel.tasks_valider.database.TaskDatabase
+import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
+import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
+import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
+
+
+@Composable
+fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) {
+ val client = remember { mutableStateOf(null) }
+
+ // if the server is not created, prompt the user for the server configuration
+ if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client)
+ // else, go to the base tasks screen
+ else TaskSessionScreen(activity, client.value!!)
+}
+
+
+@Composable
+fun CommunicationWifiP2pServerContent(
+ activity: Activity,
+ bwfManager: BwfManager,
+ client: MutableState
+) {
+ val expandedStudentList = remember { mutableStateOf(false) }
+ val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) }
+
+ Column {
+ // student list
+ Button(onClick = { expandedStudentList.value = !expandedStudentList.value }) {
+ Text(text = "Select Students List")
+ }
+ DropdownMenu(
+ expanded = expandedStudentList.value,
+ onDismissRequest = { expandedStudentList.value = false }
+ ) {
+ DropdownMenuItem(
+ text = { Text("ISRI") },
+ onClick = {}
+ )
+ DropdownMenuItem(
+ text = { Text("MIAGE") },
+ onClick = {}
+ )
+ // TODO(Faraphel): student lists should be loaded from the database or a file
+ }
+
+ // 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
+ }
+ }
+ )
+
+ Button(onClick = {
+ // TODO(Faraphel): should be merged with the internet server
+
+ // Reset the database | TODO(Faraphel): only for testing purpose
+ activity.deleteDatabase("local")
+
+ // Create the database
+ val database = Room.databaseBuilder(
+ activity,
+ TaskDatabase::class.java,
+ "local"
+ ).build()
+
+ bwfManager.recreateGroup {
+ // Create the server
+ Log.i("room-server", "creating the server")
+ val server = TaskServer(serverPort.intValue, database)
+ server.start()
+
+ // Get the client from the server
+ client.value = server.getClientAdmin()
+ }
+ }) {
+ Text("Create")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/screen.kt
new file mode 100644
index 0000000..b14c204
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/screen.kt
@@ -0,0 +1,55 @@
+package com.faraphel.tasks_valider.ui.screen.task
+
+import android.app.Activity
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import com.faraphel.tasks_valider.connectivity.task.TaskClient
+
+
+/**
+ * This screen represent a session
+ * @param activity the android activity
+ * @param client an HTTP client that can communicate with the server
+ */
+@Composable
+fun TaskSessionScreen(activity: Activity, client: TaskClient) {
+ Text("WIP : Session Screen")
+
+ /*
+ val students = remember { mutableStateOf?>(null) }
+
+ // title
+ Text(text = "Task Group")
+
+ // if the groups are not yet defined, refresh the list
+ if (groups.value == null) {
+ Thread { refreshGroups(activity, client, groups) }.start()
+ return
+ }
+
+ // if the groups have already been defined, display them
+ for (group in groups.value!!) {
+ Text(text = group.toString())
+ }
+ */
+}
+
+
+/*
+fun refreshGroups(activity: Activity, client: TaskClient, groups: MutableState?>) {
+ // try to obtain the list of groups
+ val response = client.get("entities/group")
+
+ // in case of error, notify it
+ if (!response.isSuccessful) {
+ Toast.makeText(activity, response.message, Toast.LENGTH_LONG).show()
+ return
+ }
+
+ // parse the list of groups
+ groups.value = jsonParser.fromJson(
+ response.body.toString(),
+ object : TypeToken>(){}
+ )
+}
+*/
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Group.kt
deleted file mode 100644
index 42c2c51..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Group.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.faraphel.tasks_valider.ui.widgets
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import com.faraphel.tasks_valider.database.entities.Group
-
-@Composable
-fun WidgetGroup(group: Group) {
- // TODO
- Column {
- Text(text = group.name!!)
-
- // group.tasks.forEach { task ->
- // WidgetTask(task)
- // }
- }
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Task.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Task.kt
deleted file mode 100644
index eddc7fd..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Task.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.faraphel.tasks_valider.ui.widgets
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import com.faraphel.tasks_valider.database.entities.Task
-
-@Composable
-fun WidgetTask(task: Task) {
- // task information
- Column {
- Text(text = task.title)
- Text(text = task.description)
- }
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt
deleted file mode 100644
index 0055489..0000000
--- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.faraphel.tasks_valider.ui.widgets
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import com.faraphel.tasks_valider.database.Database
-import com.faraphel.tasks_valider.database.entities.TaskGroup
-
-@Composable
-fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) {
- val teacherDao = database.teacherDao()
-
- Column {
- // row for this task
- Row {
- // task information
- // TODO: WidgetTask(task = taskStudent.task)
-
- // align the other columns to the right
- Spacer(modifier = Modifier.weight(1f))
-
- // task status
- Checkbox(
- checked = taskStudent.approvalStatus,
- onCheckedChange = { status -> taskStudent.approvalStatus = status }
- )
- }
-
- // if the task has been approved
- if (taskStudent.approvalStatus) {
- Row (
- modifier = Modifier.fillMaxSize(),
- horizontalArrangement = Arrangement.Center
- ) {
- // teacher who approved the task
- Text(text = teacherDao.getById(taskStudent.approvalTeacherId!!).fullName)
-
- // align the other columns to the right
- Spacer(modifier = Modifier.width(16.dp))
-
- // date of approval
- Text(text = taskStudent.approvalTime.toString())
- }
- }
- }
-}
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt
new file mode 100644
index 0000000..9c09b82
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt
@@ -0,0 +1,39 @@
+package com.faraphel.tasks_valider.ui.widgets.connectivity
+
+import android.net.wifi.p2p.WifiP2pDevice
+import android.net.wifi.p2p.WifiP2pDeviceList
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+
+
+/**
+ * Represent a list of WiFi-Direct devices.
+ * @param peers the list of peers to represent
+ * @param filter a filter for the peers
+ * @param deviceState a state containing the selected device
+ */
+@Composable
+fun WifiP2pDeviceListWidget(
+ peers: WifiP2pDeviceList?,
+ filter: ((WifiP2pDevice) -> Boolean)? = null,
+ deviceState: MutableState? = null,
+) {
+ Text(text = "Devices (${peers?.deviceList?.size ?: 0})")
+
+ Column {
+ // if there are peers to display
+ if (peers != null) {
+ // for every device in the list
+ for (device in peers.deviceList) {
+ // if the filter (if set) does not apply to this device, ignore it
+ if (filter != null && !filter(device))
+ continue
+
+ // create a new object for the device
+ WifiP2pDeviceWidget(device, deviceState)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceWidget.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceWidget.kt
new file mode 100644
index 0000000..71e9b70
--- /dev/null
+++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceWidget.kt
@@ -0,0 +1,44 @@
+package com.faraphel.tasks_valider.ui.widgets.connectivity
+
+import android.net.wifi.p2p.WifiP2pDevice
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.ui.graphics.Color
+
+
+/**
+ * A widget that represent a WiFi-Direct device.
+ * @param device the device that should be represented
+ * @param deviceState a state that will be updated to the device when it is selected
+ */
+@Composable
+fun WifiP2pDeviceWidget(device: WifiP2pDevice, deviceState: MutableState? = null) {
+ Button(onClick = { if (deviceState != null) deviceState.value = device }) {
+ Column {
+ Row {
+ Text(text = "Name: ")
+ Text(text = device.deviceName)
+ }
+ Row {
+ Text(text = "Is Owner: ")
+ Text(text = device.isGroupOwner.toString())
+ }
+ Row {
+ Text(text = "Address: ", color = Color.LightGray)
+ Text(text = device.deviceAddress, color = Color.LightGray)
+ }
+ Row {
+ Text(text = "Primary Type: ", color = Color.Green)
+ Text(text = device.primaryDeviceType, color = Color.Green)
+ }
+ Row {
+ Text(text = "Status: ", color = Color.Yellow)
+ Text(text = device.status.toString(), color = Color.Yellow)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 5cdb21d..b87154e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.2.2" apply false
- id("org.jetbrains.kotlin.android") version "1.9.0" apply false
id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false
-}
\ No newline at end of file
+ kotlin("android") version "1.9.23" apply false
+ kotlin("plugin.serialization") version "1.9.23" apply false
+}
diff --git a/gradlew b/gradlew
old mode 100755
new mode 100644
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f..107acd3 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,89 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega