From 17e99248e154520d9832fe281ac452fe7d9b35a4 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 22 Mar 2024 22:06:50 +0100 Subject: [PATCH 01/24] [WIP] implementing WiFi direct --- .idea/deploymentTargetDropDown.xml | 35 +++- app/src/main/AndroidManifest.xml | 27 +++ .../faraphel/tasks_valider/MainActivity.kt | 154 ++++++++++-------- 3 files changed, 145 insertions(+), 71 deletions(-) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 0c0c338..fda3e39 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -3,7 +3,40 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e31d521..6780e64 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,32 @@ + + + + + + + + + + + + + + + + + + + - WidgetTaskStudent(database, taskGroup) + // Indicates a change in the Wi-Fi Direct status. + intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) + intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) + intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) + + this.registerReceiver(MyBroadcastReceiver(this), intentFilter) + */ + + // get the WiFi direct manager + val manager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? + if (manager == null) { + Log.e("wifi-p2p", "cannot get the manager") + return + } + + val channel = manager.initialize(this, this.mainLooper, null) + + // get the list of peers in the group + manager.requestPeers(channel) { devices -> + devices.deviceList.forEach { device -> + Log.d("wifi-p2p", "peer found : [${device.deviceAddress}] ${device.deviceName}") + } + } + + manager.requestGroupInfo(channel) { group -> + val config = WifiP2pConfig().apply { + this.deviceAddress = group.owner.deviceAddress + } + + if (group.isGroupOwner) { + // the device is the host + Log.d("wifi-p2p", "I am the owner ! (${group.owner})") + + Thread { + while (true) { + // declare the server + val serverSocket = ServerSocket() + serverSocket.bind(InetSocketAddress(8888)) + + // accept a connection + val clientSocket = serverSocket.accept() + val data = clientSocket.getInputStream().read() + + // print the data + Log.i("wifi-p2p", "Data received (${clientSocket.inetAddress}) : $data") + + // close the connection + clientSocket.close() + } + }.start() + + } else { + // the device is a simple peer + Log.d("wifi-p2p", "I am a peer !") + + // connect to the host and send a packet + Thread { + manager.connect(channel, config, object : ActionListener { + override fun onSuccess() { + val hostAddress = InetAddress.getByName(config.deviceAddress) + val port = 8888 + + Log.d("wifi-p2p", "Sending packet to host") + val socket = Socket() + socket.connect(InetSocketAddress(hostAddress, port)) + socket.outputStream.write("test".toByteArray()) + } + + override fun onFailure(reason: Int) { + Log.e("wifi-p2p", "Error when connecting to host : $reason") + } + }) } } } - */ - } + } From 4ede83c55c30fab3b74e6ef3b4fbf5e63bc049c2 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Wed, 27 Mar 2024 22:28:05 +0100 Subject: [PATCH 02/24] [WIP] tried to implement a base for WiFi-Direct --- .idea/deploymentTargetDropDown.xml | 17 +++-------------- .../com/faraphel/tasks_valider/MainActivity.kt | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index fda3e39..d68bcc3 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -4,25 +4,14 @@ - - - - - - - - - - - - + - + @@ -31,7 +20,7 @@ - + diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index c30db2f..fe158c8 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -28,6 +28,7 @@ class MainActivity : ComponentActivity() { // ----- CONNECTION + /* /* val intentFilter = IntentFilter() @@ -56,6 +57,18 @@ class MainActivity : ComponentActivity() { } } + manager.createGroup(channel, object : ActionListener { + override fun onSuccess() { + Log.d("wifi-p2p", "Group created") + } + + override fun onFailure(reason: Int) { + Log.e("wifi-p2p", "Error when creating group : $reason") + } + }) + + return + manager.requestGroupInfo(channel) { group -> val config = WifiP2pConfig().apply { this.deviceAddress = group.owner.deviceAddress @@ -107,7 +120,6 @@ class MainActivity : ComponentActivity() { } } } - + */ } - } From 08956df9c899ad1ba01f0d03c40d147d479a1e74 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Mon, 1 Apr 2024 11:22:48 +0200 Subject: [PATCH 03/24] [WIP] continued to implement a base for WiFi-Direct --- .../faraphel/tasks_valider/MainActivity.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index fe158c8..d31399b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -28,7 +28,6 @@ class MainActivity : ComponentActivity() { // ----- CONNECTION - /* /* val intentFilter = IntentFilter() @@ -41,6 +40,7 @@ class MainActivity : ComponentActivity() { this.registerReceiver(MyBroadcastReceiver(this), intentFilter) */ + /* // get the WiFi direct manager val manager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? if (manager == null) { @@ -120,6 +120,36 @@ class MainActivity : ComponentActivity() { } } } - */ + */ + + Log.d("socket", "Test") + + Thread { + try { + Log.d("socket", "Starting server") + + ServerSocket(26203).use { serverSocket -> + while (true) { + val clientSocket = serverSocket.accept() + val data = clientSocket.getInputStream().read() + + Log.i("socket", "Data received (${clientSocket.inetAddress}) : $data") + + clientSocket.close() + } + } + } catch (e: Exception) { + Log.d("socket", "server already started. Becoming client") + + while (true) { + Socket("localhost", 26203).use { socket -> + socket.outputStream.write("test".toByteArray()) + + Log.i("socket", "Data sent") + } + } + } + }.start() + } } From 449bb6f5d3b50542512bda6887459b1e61b48691 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sun, 14 Apr 2024 17:17:56 +0200 Subject: [PATCH 04/24] [WIP] continued to implement a base for WiFi-Direct --- .../faraphel/tasks_valider/MainActivity.kt | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index d31399b..4131ee2 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -4,6 +4,8 @@ import android.content.Context import android.net.wifi.p2p.WifiP2pConfig import android.net.wifi.p2p.WifiP2pManager import android.net.wifi.p2p.WifiP2pManager.ActionListener +import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION +import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED import android.os.Build import android.os.Bundle import android.util.Log @@ -40,35 +42,47 @@ class MainActivity : ComponentActivity() { this.registerReceiver(MyBroadcastReceiver(this), intentFilter) */ - /* + // get the WiFi direct manager val manager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? if (manager == null) { Log.e("wifi-p2p", "cannot get the manager") return } + Log.d("wifi-p2p", "manager: $manager") val channel = manager.initialize(this, this.mainLooper, null) + Log.d("wifi-p2p", "channel: $channel") + /* // get the list of peers in the group - manager.requestPeers(channel) { devices -> - devices.deviceList.forEach { device -> - Log.d("wifi-p2p", "peer found : [${device.deviceAddress}] ${device.deviceName}") - } - } - - manager.createGroup(channel, object : ActionListener { + manager.discoverPeers(channel, object : ActionListener { override fun onSuccess() { - Log.d("wifi-p2p", "Group created") + Log.d("wifi-p2p", "Peers discovery started") } override fun onFailure(reason: Int) { - Log.e("wifi-p2p", "Error when creating group : $reason") + Log.e("wifi-p2p", "Error when starting peers discovery : $reason") + // NOTE: might fail if the "Location" permission is not granted } }) + */ - return + /* + manager.requestPeers(channel) { devices -> + // NOTE: might be empty if the "Location" permission is not granted + Log.d("wifi-p2p", "Peers found : ${devices.deviceList.size}") + devices.deviceList.forEach { device -> + Log.d( + "wifi-p2p", + "peer found : [${device.deviceAddress}] ${device.deviceName}" + ) + } + } + */ + + /* manager.requestGroupInfo(channel) { group -> val config = WifiP2pConfig().apply { this.deviceAddress = group.owner.deviceAddress @@ -122,8 +136,11 @@ class MainActivity : ComponentActivity() { } */ + /* Log.d("socket", "Test") + */ + /* Thread { try { Log.d("socket", "Starting server") @@ -150,6 +167,7 @@ class MainActivity : ComponentActivity() { } } }.start() + */ } } From 10e01c3a9b521cd194bf4f6100587c93cd77d931 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sat, 27 Apr 2024 22:15:11 +0200 Subject: [PATCH 05/24] [WIP] added very basic p2p test (seeing peers and group information) --- .../faraphel/tasks_valider/MainActivity.kt | 146 +++++------------- 1 file changed, 38 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 4131ee2..20253aa 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -1,8 +1,7 @@ package com.faraphel.tasks_valider import android.content.Context -import android.net.wifi.p2p.WifiP2pConfig -import android.net.wifi.p2p.WifiP2pManager +import android.net.wifi.p2p.* import android.net.wifi.p2p.WifiP2pManager.ActionListener import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED @@ -19,9 +18,11 @@ import java.net.Socket class MainActivity : ComponentActivity() { + private var p2pManager: WifiP2pManager? = null + private var p2pChannel: WifiP2pManager.Channel? = null companion object { - lateinit var database: Database + private lateinit var database: Database } @RequiresApi(Build.VERSION_CODES.O) @@ -42,19 +43,34 @@ class MainActivity : ComponentActivity() { this.registerReceiver(MyBroadcastReceiver(this), intentFilter) */ - - // get the WiFi direct manager - val manager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? - if (manager == null) { - Log.e("wifi-p2p", "cannot get the manager") + // get the WiFi-Direct manager + this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? + if (this.p2pManager == null) { + Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") return } - Log.d("wifi-p2p", "manager: $manager") - val channel = manager.initialize(this, this.mainLooper, null) - Log.d("wifi-p2p", "channel: $channel") + // get the Wifi-Direct channel + this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) + + // test the current channel + this.callWithP2pPeers { devices -> + // DEBUG + Log.d("wifi-p2p", "Peers found : ${devices.size}") + devices.forEach { device -> + Log.d( + "wifi-p2p", + "peer found : [${device.deviceAddress}] ${device.deviceName}" + ) + } + } + + this.callWithP2pGroup { group -> + Log.d("wifi-p2p", "Is group owner : ${group.isGroupOwner}") + } /* + // NOTE(Faraphel): it seem that this feature is deprecated and should be done manually. // get the list of peers in the group manager.discoverPeers(channel, object : ActionListener { override fun onSuccess() { @@ -67,107 +83,21 @@ class MainActivity : ComponentActivity() { } }) */ + } - /* - manager.requestPeers(channel) { devices -> - // NOTE: might be empty if the "Location" permission is not granted + private fun callWithP2pPeers(callback: (Collection) -> Unit) { + this.p2pManager?.requestPeers(this.p2pChannel) { devices -> + // NOTE(Faraphel): might be empty if the "Location" permission is not granted + if (devices.deviceList.isEmpty()) + Log.w("wifi-p2p", "No WiFi-Direct devices found. Have you enabled, granted required permissions and created the group ?") - Log.d("wifi-p2p", "Peers found : ${devices.deviceList.size}") - devices.deviceList.forEach { device -> - Log.d( - "wifi-p2p", - "peer found : [${device.deviceAddress}] ${device.deviceName}" - ) - } + callback(devices.deviceList) } - */ + } - /* - manager.requestGroupInfo(channel) { group -> - val config = WifiP2pConfig().apply { - this.deviceAddress = group.owner.deviceAddress - } - - if (group.isGroupOwner) { - // the device is the host - Log.d("wifi-p2p", "I am the owner ! (${group.owner})") - - Thread { - while (true) { - // declare the server - val serverSocket = ServerSocket() - serverSocket.bind(InetSocketAddress(8888)) - - // accept a connection - val clientSocket = serverSocket.accept() - val data = clientSocket.getInputStream().read() - - // print the data - Log.i("wifi-p2p", "Data received (${clientSocket.inetAddress}) : $data") - - // close the connection - clientSocket.close() - } - }.start() - - } else { - // the device is a simple peer - Log.d("wifi-p2p", "I am a peer !") - - // connect to the host and send a packet - Thread { - manager.connect(channel, config, object : ActionListener { - override fun onSuccess() { - val hostAddress = InetAddress.getByName(config.deviceAddress) - val port = 8888 - - Log.d("wifi-p2p", "Sending packet to host") - val socket = Socket() - socket.connect(InetSocketAddress(hostAddress, port)) - socket.outputStream.write("test".toByteArray()) - } - - override fun onFailure(reason: Int) { - Log.e("wifi-p2p", "Error when connecting to host : $reason") - } - }) - } - } + private fun callWithP2pGroup(callback: (WifiP2pGroup) -> Unit) { + this.p2pManager?.requestGroupInfo(this.p2pChannel) { group -> + callback(group) } - */ - - /* - Log.d("socket", "Test") - */ - - /* - Thread { - try { - Log.d("socket", "Starting server") - - ServerSocket(26203).use { serverSocket -> - while (true) { - val clientSocket = serverSocket.accept() - val data = clientSocket.getInputStream().read() - - Log.i("socket", "Data received (${clientSocket.inetAddress}) : $data") - - clientSocket.close() - } - } - } catch (e: Exception) { - Log.d("socket", "server already started. Becoming client") - - while (true) { - Socket("localhost", 26203).use { socket -> - socket.outputStream.write("test".toByteArray()) - - Log.i("socket", "Data sent") - } - } - } - }.start() - */ - } } From adab37fd4941cf3534856b51dfe055d1f4706744 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sun, 28 Apr 2024 22:48:02 +0200 Subject: [PATCH 06/24] added very basic p2p test (small communication between two devices, a bit unstable) --- .../faraphel/tasks_valider/MainActivity.kt | 151 +++++++++++------- .../tasks_valider/connectivity/DemoClient.kt | 49 ++++++ .../tasks_valider/connectivity/DemoServer.kt | 50 ++++++ .../wifi_p2p/WifiP2pBroadcastReceiver.kt | 39 +++++ 4 files changed, 234 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 20253aa..ebe3a57 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -1,28 +1,38 @@ package com.faraphel.tasks_valider import android.content.Context +import android.content.pm.PackageManager +import android.Manifest +import android.annotation.SuppressLint +import android.content.BroadcastReceiver import android.net.wifi.p2p.* -import android.net.wifi.p2p.WifiP2pManager.ActionListener -import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -import android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED +import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener +import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.annotation.RequiresApi +import androidx.compose.material3.Button +import com.faraphel.tasks_valider.connectivity.DemoClient +import com.faraphel.tasks_valider.connectivity.DemoServer +import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pBroadcastReceiver import com.faraphel.tasks_valider.database.Database -import java.net.InetAddress import java.net.InetSocketAddress import java.net.ServerSocket import java.net.Socket class MainActivity : ComponentActivity() { - private var p2pManager: WifiP2pManager? = null - private var p2pChannel: WifiP2pManager.Channel? = null + private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location (required for WiFi-Direct) + + private var p2pManager: WifiP2pManager? = null ///< the wifi direct manager + private var p2pChannel: WifiP2pManager.Channel? = null ///< the wifi direct channel + private var p2pReceiver: BroadcastReceiver? = null ///< the wifi direct event receiver handler companion object { - private lateinit var database: Database + private lateinit var database: Database ///< the database manager } @RequiresApi(Build.VERSION_CODES.O) @@ -31,17 +41,16 @@ class MainActivity : ComponentActivity() { // ----- CONNECTION - /* - val intentFilter = IntentFilter() + // TOOD: more check on permissions + if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) { + Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature") + return + } - // Indicates a change in the Wi-Fi Direct status. - intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) - intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) - intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) - intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) - - this.registerReceiver(MyBroadcastReceiver(this), intentFilter) - */ + if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check + this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ACCESS_FINE_LOCATION) + } // get the WiFi-Direct manager this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? @@ -52,52 +61,84 @@ class MainActivity : ComponentActivity() { // get the Wifi-Direct channel this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) - - // test the current channel - this.callWithP2pPeers { devices -> - // DEBUG - Log.d("wifi-p2p", "Peers found : ${devices.size}") - devices.forEach { device -> - Log.d( - "wifi-p2p", - "peer found : [${device.deviceAddress}] ${device.deviceName}" - ) - } + if (this.p2pManager == null) { + Log.e("wifi-p2p", "cannot access the WiFi-Direct channel") + return } - this.callWithP2pGroup { group -> - Log.d("wifi-p2p", "Is group owner : ${group.isGroupOwner}") - } + // create a handler for the WiFi-Direct events + this.p2pReceiver = WifiP2pBroadcastReceiver(this.p2pManager!!, this.p2pChannel!!) - /* - // NOTE(Faraphel): it seem that this feature is deprecated and should be done manually. - // get the list of peers in the group - manager.discoverPeers(channel, object : ActionListener { - override fun onSuccess() { - Log.d("wifi-p2p", "Peers discovery started") - } - - override fun onFailure(reason: Int) { - Log.e("wifi-p2p", "Error when starting peers discovery : $reason") - // NOTE: might fail if the "Location" permission is not granted - } - }) - */ + // start a demo connexion + this.demoP2pConnexion() } - private fun callWithP2pPeers(callback: (Collection) -> Unit) { - this.p2pManager?.requestPeers(this.p2pChannel) { devices -> - // NOTE(Faraphel): might be empty if the "Location" permission is not granted - if (devices.deviceList.isEmpty()) - Log.w("wifi-p2p", "No WiFi-Direct devices found. Have you enabled, granted required permissions and created the group ?") + private fun demoP2pConnexion() { + // Get the connection information (for the host IP) + this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection -> - callback(devices.deviceList) + Log.v("wifi-p2p", "connection information: $connection") + + // Get the group information (for the owner) + this.p2pManager!!.requestGroupInfo(this.p2pChannel) { group -> + + Log.v("wifi-p2p", "group information: $group") + + if (group.isGroupOwner) { + Log.d("wifi-p2p", "mode: server") + DemoServer(9876).start() + } else { + Log.d("wifi-p2p", "mode: client") + + Thread { // NOTE(Faraphel): this thread is started to avoid blocking the main loop + + while (true) { + + // TODO(Faraphel): the thread is handled outside the connect block to prevent connection before the server started. + // This should be handled in the future by selecting the server before starting the client. + p2pManager!!.connect( + this.p2pChannel, + WifiP2pConfig().apply { deviceAddress = group.owner.deviceAddress }, + object : WifiP2pManager.ActionListener { + override fun onSuccess() { + Log.d("wifi-p2p", "peer connection successful !") + Thread { + try { + DemoClient(connection.groupOwnerAddress, 9876).once() + } catch(exception: Exception) { + Log.e("wifi-p2p", "could not connect to the server", exception) + } + }.start() // NOTE(Faraphel): this thread is started to avoid networking in main thread + } + + override fun onFailure(reason: Int) { + Log.e("wifi-p2p", "could not establish a peer connection. Reason: $reason") + } + } + ) + + Thread.sleep(1000) + } + + }.start() + } + } } } - private fun callWithP2pGroup(callback: (WifiP2pGroup) -> Unit) { - this.p2pManager?.requestGroupInfo(this.p2pChannel) { group -> - callback(group) - } + @RequiresApi(Build.VERSION_CODES.O) + @SuppressLint("UnspecifiedRegisterReceiverFlag") + override fun onResume() { + super.onResume() + + // enable the WiFi-Direct events + this.registerReceiver(this.p2pReceiver, WifiP2pBroadcastReceiver.INTENT_FILTER) + } + + override fun onPause() { + super.onPause() + + // disable the WiFi-Direct events + this.unregisterReceiver(this.p2pReceiver) } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt new file mode 100644 index 0000000..c33ebe0 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt @@ -0,0 +1,49 @@ +package com.faraphel.tasks_valider.connectivity + +import android.util.Log +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket + + +class DemoClient(hostname: InetAddress, port: Int) { + companion object { + var SOCKET_TIMEOUT = 120 * 1000 ///< TEST(Faraphel): long timeout for testing + } + + private var server = Socket() + private var address = InetSocketAddress(hostname, port) + + init { + server.soTimeout = SOCKET_TIMEOUT + } + + /** + * Try to contact the server once + */ + fun once() { + Log.d("demo-client", "connecting to the server...") + this.server.connect(this.address, SOCKET_TIMEOUT) + Log.d("demo-client", "connected !") + + Log.d("demo-client", "sending message...") + server.getOutputStream().write(42) // send a byte + Log.d("demo-client", "message sent !") + } + + /** + * Try to contact the server indefinitely + */ + fun start() { + Thread { + while (true) { + try { + this.once() + } catch (exception: Exception) { + Log.w("demo-client", "an error occured: $exception") + } + Thread.sleep(1000) + } + }.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt new file mode 100644 index 0000000..5f0aa31 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt @@ -0,0 +1,50 @@ +package com.faraphel.tasks_valider.connectivity + +import android.util.Log +import java.net.ServerSocket + + +class DemoServer(port: Int) { + + companion object { + var SOCKET_TIMEOUT = 120 * 1000 ///< TEST(Faraphel): long timeout for testing + } + + private var server = ServerSocket(port) + + init { + server.soTimeout = SOCKET_TIMEOUT + } + + /** + * Accept and treat a client once + */ + private fun once() { + // wait for a client connexion + Log.d("demo-server", "waiting for a client...") + val client = server.accept() + Log.d("demo-server", "client connected !") + + // once connected, wait and read a byte from the client. + val value = client.getInputStream().read() + Log.d("demo-server", "client sent data : $value") + + // TODO(Faraphel): an abstraction layer should be added in the future to receive type of packets and read the data + } + + /** + * Accept and threat clients indefinitely + */ + fun start() { + Thread { + while (true) { + try { + this.once() + } catch (exception: Exception) { + Log.w("demo-server", "an error occured: $exception") + } + Thread.sleep(1000) + } + }.start() + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt new file mode 100644 index 0000000..18d1906 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt @@ -0,0 +1,39 @@ +package com.faraphel.tasks_valider.connectivity.wifi_p2p + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.wifi.p2p.WifiP2pManager +import android.util.Log + +class WifiP2pBroadcastReceiver( + private val manager: WifiP2pManager, + private val channel: WifiP2pManager.Channel, +) : BroadcastReceiver() { + companion object { + val INTENT_FILTER = IntentFilter().apply { + addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) + } + } + + override fun onReceive(context: Context?, intent: Intent) { + when (intent.action) { + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { + Log.i("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}") + } + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { + Log.i("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}") + } + WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { + Log.i("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}") + } + WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { + Log.i("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}") + } + } + } +} \ No newline at end of file From 23e39c026a4cd803419f1aed16b3035db14dd3cf Mon Sep 17 00:00:00 2001 From: Faraphel Date: Mon, 29 Apr 2024 22:41:54 +0200 Subject: [PATCH 07/24] added a helper to improve the WiFi-Direct handling and prototyped the connection screen --- .../faraphel/tasks_valider/MainActivity.kt | 110 ++++-------- .../wifi_p2p/WifiP2pBroadcastReceiver.kt | 39 ----- .../connectivity/wifi_p2p/WifiP2pHelper.kt | 165 ++++++++++++++++++ .../wifi_p2p/WifiP2pHelperListenerInfo.kt | 15 ++ .../wifi_p2p/error/WifiP2pHelperException.kt | 9 + .../WifiP2pHelperInvalidActionException.kt | 5 + .../tasks_valider/ui/widgets/TaskGroup.kt | 1 + .../ui/widgets/WifiP2pWidgetDevice.kt | 26 +++ 8 files changed, 252 insertions(+), 118 deletions(-) delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index ebe3a57..3ecae51 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -4,32 +4,21 @@ import android.content.Context import android.content.pm.PackageManager import android.Manifest import android.annotation.SuppressLint -import android.content.BroadcastReceiver import android.net.wifi.p2p.* -import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener -import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.RequiresApi -import androidx.compose.material3.Button -import com.faraphel.tasks_valider.connectivity.DemoClient -import com.faraphel.tasks_valider.connectivity.DemoServer -import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pBroadcastReceiver +import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.database.Database -import java.net.InetSocketAddress -import java.net.ServerSocket -import java.net.Socket +import com.faraphel.tasks_valider.ui.widgets.WifiP2pWidgetDevice class MainActivity : ComponentActivity() { - private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location (required for WiFi-Direct) - - private var p2pManager: WifiP2pManager? = null ///< the wifi direct manager - private var p2pChannel: WifiP2pManager.Channel? = null ///< the wifi direct channel - private var p2pReceiver: BroadcastReceiver? = null ///< the wifi direct event receiver handler + private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fi + private var p2pHelper: WifiP2pHelper? = null ///< the Wi-Fi Direct helper companion object { private lateinit var database: Database ///< the database manager @@ -53,77 +42,40 @@ class MainActivity : ComponentActivity() { } // get the WiFi-Direct manager - this.p2pManager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? - if (this.p2pManager == null) { + val p2pManager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? + if (p2pManager == null) { Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") return } // get the Wifi-Direct channel - this.p2pChannel = this.p2pManager!!.initialize(this, this.mainLooper, null) - if (this.p2pManager == null) { - Log.e("wifi-p2p", "cannot access the WiFi-Direct channel") - return - } + val p2pChannel: WifiP2pManager.Channel = p2pManager.initialize(this, this.mainLooper, null) - // create a handler for the WiFi-Direct events - this.p2pReceiver = WifiP2pBroadcastReceiver(this.p2pManager!!, this.p2pChannel!!) + // create a helper for handling the WiFi-Direct + this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel) - // start a demo connexion - this.demoP2pConnexion() - } + // try to display the different devices when available. + this.p2pHelper!!.registerListener( + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, + { + peers: WifiP2pDeviceList? -> - private fun demoP2pConnexion() { - // Get the connection information (for the host IP) - this.p2pManager!!.requestConnectionInfo(this.p2pChannel) { connection -> - - Log.v("wifi-p2p", "connection information: $connection") - - // Get the group information (for the owner) - this.p2pManager!!.requestGroupInfo(this.p2pChannel) { group -> - - Log.v("wifi-p2p", "group information: $group") - - if (group.isGroupOwner) { - Log.d("wifi-p2p", "mode: server") - DemoServer(9876).start() - } else { - Log.d("wifi-p2p", "mode: client") - - Thread { // NOTE(Faraphel): this thread is started to avoid blocking the main loop - - while (true) { - - // TODO(Faraphel): the thread is handled outside the connect block to prevent connection before the server started. - // This should be handled in the future by selecting the server before starting the client. - p2pManager!!.connect( - this.p2pChannel, - WifiP2pConfig().apply { deviceAddress = group.owner.deviceAddress }, - object : WifiP2pManager.ActionListener { - override fun onSuccess() { - Log.d("wifi-p2p", "peer connection successful !") - Thread { - try { - DemoClient(connection.groupOwnerAddress, 9876).once() - } catch(exception: Exception) { - Log.e("wifi-p2p", "could not connect to the server", exception) - } - }.start() // NOTE(Faraphel): this thread is started to avoid networking in main thread - } - - override fun onFailure(reason: Int) { - Log.e("wifi-p2p", "could not establish a peer connection. Reason: $reason") - } - } - ) - - Thread.sleep(1000) - } - - }.start() + Log.d("wifi-p2p", "peers: $peers") + if (peers != null) this.setContent { + for (device in peers.deviceList) { + Log.d("wifi-p2p", "device: ${device.deviceAddress}") + WifiP2pWidgetDevice(device) + } } - } - } + }, + once = true + ) + + // discovers the peers to trigger the peer changed event + this.p2pHelper!!.discoverPeers(object : WifiP2pManager.ActionListener { + override fun onSuccess() { Log.i("wifi-p2p", "discovering successful !") } + override fun onFailure(reason: Int) { Log.e("wifi-p2p", "discovering failed. Reason: $reason") } + }) } @RequiresApi(Build.VERSION_CODES.O) @@ -132,13 +84,13 @@ class MainActivity : ComponentActivity() { super.onResume() // enable the WiFi-Direct events - this.registerReceiver(this.p2pReceiver, WifiP2pBroadcastReceiver.INTENT_FILTER) + this.registerReceiver(this.p2pHelper, WifiP2pHelper.ALL_INTENT_FILTER) } override fun onPause() { super.onPause() // disable the WiFi-Direct events - this.unregisterReceiver(this.p2pReceiver) + this.unregisterReceiver(this.p2pHelper) } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt deleted file mode 100644 index 18d1906..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pBroadcastReceiver.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.wifi_p2p - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.net.wifi.p2p.WifiP2pManager -import android.util.Log - -class WifiP2pBroadcastReceiver( - private val manager: WifiP2pManager, - private val channel: WifiP2pManager.Channel, -) : BroadcastReceiver() { - companion object { - val INTENT_FILTER = IntentFilter().apply { - addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) - addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) - addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) - addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) - } - } - - override fun onReceive(context: Context?, intent: Intent) { - when (intent.action) { - WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { - Log.i("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}") - } - WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { - Log.i("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}") - } - WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { - Log.i("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}") - } - WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { - Log.i("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}") - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt new file mode 100644 index 0000000..381f3a7 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt @@ -0,0 +1,165 @@ +package com.faraphel.tasks_valider.connectivity.wifi_p2p + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.wifi.p2p.WifiP2pConfig +import android.net.wifi.p2p.WifiP2pDeviceList +import android.net.wifi.p2p.WifiP2pManager +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.faraphel.tasks_valider.connectivity.wifi_p2p.error.WifiP2pHelperInvalidActionException + + +/** + * 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 base manager + * @param channel: the WiFi-Direct channel + */ +class WifiP2pHelper( + private val manager: WifiP2pManager, + private val channel: WifiP2pManager.Channel, +) : BroadcastReceiver() { + companion object { + /** + * This IntentFilter can be used to subscribe the helper to every supported WiFi-Direct events. + */ + val ALL_INTENT_FILTER = IntentFilter().apply { + addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) + } + } + + // Events + + /** + * Contains the listeners to call when the peers are updated. + */ + private val listenersPeersChanged = mutableListOf Unit>>() + + /** + * Event receiver for the WiFi-Direct events. + * Should not be called manually. + * @param context: the WiFi-Direct context + * @param intent: the event information + */ + @RequiresApi(Build.VERSION_CODES.N) + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { + Log.v("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}") + } + + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { + Log.v("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}") + + this.requestPeers { peers -> + this@WifiP2pHelper.listenersPeersChanged.forEach { listenerInfo -> listenerInfo.callback(peers) } + this@WifiP2pHelper.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.once } + } + } + + WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { + Log.v("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}") + } + + WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { + Log.v("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}") + } + } + } + + /** + * Register a listener to a specific WiFi-Direct event. + * @param action: the name of the WiFi-Direct event + * @param callback: the callback function + * @param once: should the event be only called once ? + */ + fun registerListener(action: String, callback: CallbackType, once: Boolean = false) { + when (action) { + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { + @Suppress("UNCHECKED_CAST") + this.listenersPeersChanged.add(WifiP2pHelperListenerInfo( + callback as ((WifiP2pDeviceList?) -> Unit), + once + )) + } + else -> { + // raise an exception if the action is invalid + throw WifiP2pHelperInvalidActionException(action) + } + } + } + + @RequiresApi(Build.VERSION_CODES.N) + fun unregisterListener(action: String, callback: CallbackType) { + when (action) { + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { + this.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.callback == callback } + } + else -> { + // raise an exception if the action is invalid + throw WifiP2pHelperInvalidActionException(action) + } + } + } + + // Wrappers + + /** + * Wrapper around WifiP2pManager::connect + * + * Connect to another device, allowing for a communication using Sockets + */ + fun connect(deviceAddress: String, listener: WifiP2pManager.ActionListener? = null) { + val config = WifiP2pConfig().apply { + this.deviceAddress = deviceAddress + } + this.manager.connect(this.channel, config, listener) + } + + /** + * Wrapper around WifiP2pManager::requestPeers + * + * Request the list of peers after a discovery. + */ + fun requestPeers(listener: WifiP2pManager.PeerListListener? = null) { + this.manager.requestPeers(this.channel, listener) + } + + /** + * Wrapper around WifiP2pManager::discoverPeers + * + * Start discovering peers. + * Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered. + */ + fun discoverPeers(listener: WifiP2pManager.ActionListener? = null) { + this.manager.discoverPeers(this.channel, listener) + } + + /** + * Wrapper around WifiP2pManager::requestConnectionInfo + * + * Obtain information about a connection with another device. + */ + fun requestConnectionInfo(listener: WifiP2pManager.ConnectionInfoListener? = null) { + this.manager.requestConnectionInfo(this.channel, listener) + } + + /** + * Wrapper around WifiP2pManager::requestGroupInfo + * + * Obtain information about the current group. + */ + fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) { + this.manager.requestGroupInfo(this.channel, listener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt new file mode 100644 index 0000000..3c68903 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt @@ -0,0 +1,15 @@ +package com.faraphel.tasks_valider.connectivity.wifi_p2p + +import java.util.concurrent.Callable + + +/** + * This class contain the information required to store a listener in the WifiP2pHelper class to correctly handle events. + * @param CallbackType: the type of the callback function + * @param callback: the function to call when the event is triggered + * @param once: should the listener be called only once ? + */ +class WifiP2pHelperListenerInfo( + val callback: CallbackType, + val once: Boolean, +) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt new file mode 100644 index 0000000..892e2b2 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt @@ -0,0 +1,9 @@ +package com.faraphel.tasks_valider.connectivity.wifi_p2p.error + + +/** + * Base Exception for everything concerning the WifiP2pHelper class + */ +open class WifiP2pHelperException( + override val message: String? +) : Exception(message) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt new file mode 100644 index 0000000..26d147f --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt @@ -0,0 +1,5 @@ +package com.faraphel.tasks_valider.connectivity.wifi_p2p.error + +class WifiP2pHelperInvalidActionException( + action: String +) : WifiP2pHelperException("This WiFi-Direct action is not supported : $action") 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 index 0055489..eb3dd80 100644 --- 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 @@ -14,6 +14,7 @@ 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() diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt new file mode 100644 index 0000000..dfd009a --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt @@ -0,0 +1,26 @@ +package com.faraphel.tasks_valider.ui.widgets + +import android.net.wifi.p2p.WifiP2pDevice +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + + +@Composable +fun WifiP2pWidgetDevice(device: WifiP2pDevice) { + Column { + Row { + Text(text = "Name: ") + Text(text = device.deviceName) + } + Row { + Text(text = "Address: ") + Text(text = device.deviceAddress) + } + Row { + Text(text = "Is Owner: ") + Text(text = device.isGroupOwner.toString()) + } + } +} \ No newline at end of file From 7d0f3e88e0cefff9bf8c3b73056b0d21f2426056 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Tue, 30 Apr 2024 16:58:03 +0200 Subject: [PATCH 08/24] very basic screen for the wifi-p2p --- .../faraphel/tasks_valider/MainActivity.kt | 28 +++--------- .../ui/screen/RoomClientScreen.kt | 31 +++++++++++++ .../tasks_valider/ui/screen/RoomScreen.kt | 44 +++++++++++++++++++ .../connectivity/WifiP2pDeviceListWidget.kt | 23 ++++++++++ .../WifiP2pDeviceWidget.kt} | 4 +- 5 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{WifiP2pWidgetDevice.kt => connectivity/WifiP2pDeviceWidget.kt} (84%) diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 3ecae51..9a773a0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -13,7 +13,7 @@ import androidx.activity.compose.setContent import androidx.annotation.RequiresApi import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.database.Database -import com.faraphel.tasks_valider.ui.widgets.WifiP2pWidgetDevice +import com.faraphel.tasks_valider.ui.screen.RoomScreen class MainActivity : ComponentActivity() { @@ -54,28 +54,10 @@ class MainActivity : ComponentActivity() { // create a helper for handling the WiFi-Direct this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel) - // try to display the different devices when available. - this.p2pHelper!!.registerListener( - WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, - { - peers: WifiP2pDeviceList? -> - - Log.d("wifi-p2p", "peers: $peers") - if (peers != null) this.setContent { - for (device in peers.deviceList) { - Log.d("wifi-p2p", "device: ${device.deviceAddress}") - WifiP2pWidgetDevice(device) - } - } - }, - once = true - ) - - // discovers the peers to trigger the peer changed event - this.p2pHelper!!.discoverPeers(object : WifiP2pManager.ActionListener { - override fun onSuccess() { Log.i("wifi-p2p", "discovering successful !") } - override fun onFailure(reason: Int) { Log.e("wifi-p2p", "discovering failed. Reason: $reason") } - }) + // open the room selection screen + this.setContent { + RoomScreen(this.p2pHelper!!) + } } @RequiresApi(Build.VERSION_CODES.O) diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt new file mode 100644 index 0000000..f0ee7dd --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt @@ -0,0 +1,31 @@ +package com.faraphel.tasks_valider.ui.screen + +import android.net.wifi.p2p.WifiP2pDeviceList +import android.net.wifi.p2p.WifiP2pManager +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper +import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget + + +@Composable +fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) { + val peers = remember { mutableStateOf(null) } + + Column { + Text(text = "Find a Server") + WifiP2pDeviceListWidget(peers.value) + } + + // TODO(Faraphel): might be doubtful. Test with more phones, should it only run once ? refresh button ? + wifiP2pHelper.registerListener( + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, + { new_peers: WifiP2pDeviceList? -> peers.value = new_peers }, + once = true + ) + wifiP2pHelper.discoverPeers() +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt new file mode 100644 index 0000000..cef5f5e --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt @@ -0,0 +1,44 @@ +package com.faraphel.tasks_valider.ui.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper + + +enum class RoomScreenType { + UNDEFINED, + HOST, + CLIENT +} + + +/** + * This screen will allow the user to choose which room is he connecting to, or let him create one. + */ +@Composable +fun RoomScreen(wifiP2pHelper: WifiP2pHelper) { + val roomMode = remember { mutableStateOf(RoomScreenType.UNDEFINED) } + + when (roomMode.value) { + RoomScreenType.UNDEFINED -> Column { + Button(onClick = { roomMode.value = RoomScreenType.CLIENT }) { + Text(text = "Join a room") + } + Button(onClick = { roomMode.value = RoomScreenType.HOST }) { + Text(text = "Create a room") + } + } + RoomScreenType.CLIENT -> { + // Client mode + RoomClientScreen(wifiP2pHelper) + } + RoomScreenType.HOST -> { + // Host mode + TODO("Faraphel: host mode not implemented") + } + } +} 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..f6e19f1 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceListWidget.kt @@ -0,0 +1,23 @@ +package com.faraphel.tasks_valider.ui.widgets.connectivity + +import android.net.wifi.p2p.WifiP2pDeviceList +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + + +/** + * Represent a list of WiFi-Direct devices. + */ +@Composable +fun WifiP2pDeviceListWidget(peers: WifiP2pDeviceList?) { + Text(text = "Devices (${peers?.deviceList?.size ?: 0})") + + Column { + if (peers != null) { + for (device in peers.deviceList) { + WifiP2pDeviceWidget(device) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceWidget.kt similarity index 84% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceWidget.kt index dfd009a..b0363e0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/WifiP2pWidgetDevice.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/connectivity/WifiP2pDeviceWidget.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets +package com.faraphel.tasks_valider.ui.widgets.connectivity import android.net.wifi.p2p.WifiP2pDevice import androidx.compose.foundation.layout.Column @@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable @Composable -fun WifiP2pWidgetDevice(device: WifiP2pDevice) { +fun WifiP2pDeviceWidget(device: WifiP2pDevice) { Column { Row { Text(text = "Name: ") From 7ed38b2624a1ad73147f4bf29635af7b1353bc41 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Tue, 30 Apr 2024 20:21:25 +0200 Subject: [PATCH 09/24] very basic screen for the room creation --- .../faraphel/tasks_valider/MainActivity.kt | 2 +- .../connectivity/wifi_p2p/WifiP2pHelper.kt | 56 +++++++++++++++-- .../ui/screen/{ => room}/RoomClientScreen.kt | 6 +- .../ui/screen/room/RoomHostScreen.kt | 61 +++++++++++++++++++ .../ui/screen/{ => room}/RoomScreen.kt | 29 +++++---- .../ui/screen/tasks/TaskGroupScreen.kt | 13 ++++ 6 files changed, 147 insertions(+), 20 deletions(-) rename app/src/main/java/com/faraphel/tasks_valider/ui/screen/{ => room}/RoomClientScreen.kt (90%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt rename app/src/main/java/com/faraphel/tasks_valider/ui/screen/{ => room}/RoomScreen.kt (58%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 9a773a0..3689afc 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -13,7 +13,7 @@ import androidx.activity.compose.setContent import androidx.annotation.RequiresApi import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.database.Database -import com.faraphel.tasks_valider.ui.screen.RoomScreen +import com.faraphel.tasks_valider.ui.screen.room.RoomScreen class MainActivity : ComponentActivity() { diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt index 381f3a7..6d2b309 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt @@ -119,10 +119,7 @@ class WifiP2pHelper( * * Connect to another device, allowing for a communication using Sockets */ - fun connect(deviceAddress: String, listener: WifiP2pManager.ActionListener? = null) { - val config = WifiP2pConfig().apply { - this.deviceAddress = deviceAddress - } + fun connect(config: WifiP2pConfig, listener: WifiP2pManager.ActionListener? = null) { this.manager.connect(this.channel, config, listener) } @@ -162,4 +159,55 @@ class WifiP2pHelper( fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) { this.manager.requestGroupInfo(this.channel, listener) } + + /** + * Wrapper around WifiP2pManager::createGroup + * + * Create a new WiFi-Direct group. + */ + fun createGroup(listener: WifiP2pManager.ActionListener? = null) { + this.manager.createGroup(this.channel, listener) + } + + /** + * Wrapper around WifiP2pManager::removeGroup + * + * Disconnect from the current WiFi-Direct group. + */ + fun removeGroup(listener: WifiP2pManager.ActionListener? = null) { + this.manager.removeGroup(this.channel, listener) + } + + /** + * Create a new WiFi-Direct group. + * If already connected to a group, quit it first. + * + * @param listener: the createGroup listener + * @param onFailure: error handler for the removeGroup event + */ + fun recreateGroup( + listener: WifiP2pManager.ActionListener? = null, + onFailure: ((Int) -> Unit)? = null + ) { + // get the current group information + this.requestGroupInfo { group -> + // if a group exist, quit it + if (group != null) + this.removeGroup(object : WifiP2pManager.ActionListener { + override fun onSuccess() { + // once left, create the group + this@WifiP2pHelper.createGroup(listener) + } + + override fun onFailure(reason: Int) { + // call the handler + onFailure?.invoke(reason) + } + + }) + else + // create the group + this.createGroup(listener) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt similarity index 90% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt index f0ee7dd..6bc878c 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomClientScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt @@ -1,17 +1,19 @@ -package com.faraphel.tasks_valider.ui.screen +package com.faraphel.tasks_valider.ui.screen.room import android.net.wifi.p2p.WifiP2pDeviceList import android.net.wifi.p2p.WifiP2pManager import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget +/** + * This screen allow the user to join a room + */ @Composable fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) { val peers = remember { mutableStateOf(null) } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt new file mode 100644 index 0000000..eb2dcf0 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt @@ -0,0 +1,61 @@ +package com.faraphel.tasks_valider.ui.screen.room + +import android.net.wifi.p2p.WifiP2pManager +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper +import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen + + +/** + * This screen allow the user to create a room + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) { + val expanded = remember { mutableStateOf(false) } + val isCreated = remember { mutableStateOf(false) } + + // if the group have been created, display the group screen + if (isCreated.value) { + TaskGroupScreen() + return + } + + Column { + ExposedDropdownMenuBox( + expanded = expanded.value, + onExpandedChange = { value -> expanded.value = !value } + ) { + DropdownMenuItem( + text = { Text("ISRI") }, + onClick = {} + ) + DropdownMenuItem( + text = { Text("MIAGE") }, + onClick = {} + ) + } + + Button(onClick = { + // create a new WiFi-Direct group + wifiP2pHelper.recreateGroup(object : WifiP2pManager.ActionListener { + override fun onSuccess() { + Log.i("room", "group created !") + // mark the group as created + isCreated.value = true + } + override fun onFailure(reason: Int) { + Log.e("room", "error while creating group. Reason: $reason") + } + }) + }) { + Text("Create") + } + } + +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt similarity index 58% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt index cef5f5e..3e7b38a 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/RoomScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.screen +package com.faraphel.tasks_valider.ui.screen.room import androidx.compose.foundation.layout.Column import androidx.compose.material3.Button @@ -10,7 +10,6 @@ import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper enum class RoomScreenType { - UNDEFINED, HOST, CLIENT } @@ -21,24 +20,28 @@ enum class RoomScreenType { */ @Composable fun RoomScreen(wifiP2pHelper: WifiP2pHelper) { - val roomMode = remember { mutableStateOf(RoomScreenType.UNDEFINED) } + val roomMode = remember { mutableStateOf(null) } when (roomMode.value) { - RoomScreenType.UNDEFINED -> Column { - Button(onClick = { roomMode.value = RoomScreenType.CLIENT }) { - Text(text = "Join a room") - } - Button(onClick = { roomMode.value = RoomScreenType.HOST }) { - Text(text = "Create a room") - } - } RoomScreenType.CLIENT -> { // Client mode RoomClientScreen(wifiP2pHelper) } RoomScreenType.HOST -> { // Host mode - TODO("Faraphel: host mode not implemented") + RoomHostScreen(wifiP2pHelper) + } + + null -> { + // let the user decide which room mode he which to use + Column { + Button(onClick = { roomMode.value = RoomScreenType.CLIENT }) { + Text(text = "Join a room") + } + Button(onClick = { roomMode.value = RoomScreenType.HOST }) { + Text(text = "Create a room") + } + } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt new file mode 100644 index 0000000..60c4fca --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt @@ -0,0 +1,13 @@ +package com.faraphel.tasks_valider.ui.screen.tasks + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + + +/** + * This screen let the user decide which student team he wants to interact with + */ +@Composable +fun TaskGroupScreen() { + Text(text = "Task Group") +} From 653d10260fdc71caeddb7c9c87e6349ff62eb075 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Tue, 30 Apr 2024 20:28:25 +0200 Subject: [PATCH 10/24] improved comments in the WiFi-Direct Helper --- .../connectivity/wifi_p2p/WifiP2pHelper.kt | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt index 6d2b309..574a030 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt @@ -115,79 +115,78 @@ class WifiP2pHelper( // Wrappers /** - * Wrapper around WifiP2pManager::connect - * * Connect to another device, allowing for a communication using Sockets + * @see WifiP2pManager.connect */ fun connect(config: WifiP2pConfig, listener: WifiP2pManager.ActionListener? = null) { this.manager.connect(this.channel, config, listener) } /** - * Wrapper around WifiP2pManager::requestPeers - * * Request the list of peers after a discovery. + * @see WifiP2pManager.requestPeers */ fun requestPeers(listener: WifiP2pManager.PeerListListener? = null) { this.manager.requestPeers(this.channel, listener) } /** - * Wrapper around WifiP2pManager::discoverPeers - * * Start discovering peers. * Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered. + * @see WifiP2pManager.discoverPeers */ fun discoverPeers(listener: WifiP2pManager.ActionListener? = null) { this.manager.discoverPeers(this.channel, listener) } /** - * Wrapper around WifiP2pManager::requestConnectionInfo - * * Obtain information about a connection with another device. + * @see WifiP2pManager.requestConnectionInfo */ fun requestConnectionInfo(listener: WifiP2pManager.ConnectionInfoListener? = null) { this.manager.requestConnectionInfo(this.channel, listener) } /** - * Wrapper around WifiP2pManager::requestGroupInfo - * * Obtain information about the current group. + * @see WifiP2pManager.requestGroupInfo */ fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) { this.manager.requestGroupInfo(this.channel, listener) } /** - * Wrapper around WifiP2pManager::createGroup - * * Create a new WiFi-Direct group. + * @see WifiP2pManager.createGroup */ fun createGroup(listener: WifiP2pManager.ActionListener? = null) { this.manager.createGroup(this.channel, listener) } /** - * Wrapper around WifiP2pManager::removeGroup - * * Disconnect from the current WiFi-Direct group. + * @see WifiP2pManager.removeGroup */ fun removeGroup(listener: WifiP2pManager.ActionListener? = null) { this.manager.removeGroup(this.channel, listener) } + // Shortcuts + /** - * Create a new WiFi-Direct group. - * If already connected to a group, quit it first. + * 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 listener: the createGroup listener - * @param onFailure: error handler for the removeGroup event + * @param onRemoveFailure: error handler for the removeGroup event + * + * @see WifiP2pManager.createGroup + * @see WifiP2pManager.removeGroup */ fun recreateGroup( listener: WifiP2pManager.ActionListener? = null, - onFailure: ((Int) -> Unit)? = null + onRemoveFailure: ((Int) -> Unit)? = null ) { // get the current group information this.requestGroupInfo { group -> @@ -201,7 +200,7 @@ class WifiP2pHelper( override fun onFailure(reason: Int) { // call the handler - onFailure?.invoke(reason) + onRemoveFailure?.invoke(reason) } }) From ddb63a47ed471b857446284c8e9990daef65fe5d Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 2 May 2024 12:46:50 +0200 Subject: [PATCH 11/24] [WIP] added a packet system with the network server and client --- .idea/deploymentTargetDropDown.xml | 16 +-- .idea/kotlinc.xml | 2 +- .idea/misc.xml | 6 +- .idea/uiDesigner.xml | 124 ++++++++++++++++++ app/build.gradle.kts | 10 +- .../faraphel/tasks_valider/MainActivity.kt | 6 + .../tasks_valider/connectivity/DemoClient.kt | 49 ------- .../tasks_valider/connectivity/DemoServer.kt | 50 ------- .../connectivity/packets/BasePacket.kt | 29 ++++ .../connectivity/packets/PacketPing.kt | 10 ++ .../connectivity/room/RoomClient.kt | 41 ++++++ .../connectivity/room/RoomServer.kt | 50 +++++++ .../ui/screen/room/RoomClientScreen.kt | 62 ++++++++- .../ui/screen/room/RoomHostScreen.kt | 57 ++++++-- .../ui/screen/tasks/TaskGroupScreen.kt | 1 + .../connectivity/WifiP2pDeviceListWidget.kt | 20 ++- .../connectivity/WifiP2pDeviceWidget.kt | 44 +++++-- .../ui/widgets/{ => tasks}/Group.kt | 2 +- .../ui/widgets/{ => tasks}/Task.kt | 2 +- .../ui/widgets/{ => tasks}/TaskGroup.kt | 2 +- build.gradle.kts | 5 +- 21 files changed, 438 insertions(+), 150 deletions(-) create mode 100644 .idea/uiDesigner.xml delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{ => tasks}/Group.kt (88%) rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{ => tasks}/Task.kt (86%) rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{ => tasks}/TaskGroup.kt (97%) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index d68bcc3..665b551 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -5,26 +5,26 @@ - + - + - - + + - + - - + + - + diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fdf8d99..fe63bb6 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 8978d23..f9d6e36 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,10 @@ + - + + + + diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 998a95c..2bfae1d 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,16 @@ 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") 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/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 3689afc..56cbc1c 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -11,6 +11,7 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.RequiresApi +import com.faraphel.tasks_valider.connectivity.packets.PacketPing import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.database.Database import com.faraphel.tasks_valider.ui.screen.room.RoomScreen @@ -75,4 +76,9 @@ class MainActivity : ComponentActivity() { // disable the WiFi-Direct events this.unregisterReceiver(this.p2pHelper) } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + // TODO(Faraphel): save the current state to reload it later + } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt deleted file mode 100644 index c33ebe0..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoClient.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.faraphel.tasks_valider.connectivity - -import android.util.Log -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.Socket - - -class DemoClient(hostname: InetAddress, port: Int) { - companion object { - var SOCKET_TIMEOUT = 120 * 1000 ///< TEST(Faraphel): long timeout for testing - } - - private var server = Socket() - private var address = InetSocketAddress(hostname, port) - - init { - server.soTimeout = SOCKET_TIMEOUT - } - - /** - * Try to contact the server once - */ - fun once() { - Log.d("demo-client", "connecting to the server...") - this.server.connect(this.address, SOCKET_TIMEOUT) - Log.d("demo-client", "connected !") - - Log.d("demo-client", "sending message...") - server.getOutputStream().write(42) // send a byte - Log.d("demo-client", "message sent !") - } - - /** - * Try to contact the server indefinitely - */ - fun start() { - Thread { - while (true) { - try { - this.once() - } catch (exception: Exception) { - Log.w("demo-client", "an error occured: $exception") - } - Thread.sleep(1000) - } - }.start() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt deleted file mode 100644 index 5f0aa31..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/DemoServer.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.faraphel.tasks_valider.connectivity - -import android.util.Log -import java.net.ServerSocket - - -class DemoServer(port: Int) { - - companion object { - var SOCKET_TIMEOUT = 120 * 1000 ///< TEST(Faraphel): long timeout for testing - } - - private var server = ServerSocket(port) - - init { - server.soTimeout = SOCKET_TIMEOUT - } - - /** - * Accept and treat a client once - */ - private fun once() { - // wait for a client connexion - Log.d("demo-server", "waiting for a client...") - val client = server.accept() - Log.d("demo-server", "client connected !") - - // once connected, wait and read a byte from the client. - val value = client.getInputStream().read() - Log.d("demo-server", "client sent data : $value") - - // TODO(Faraphel): an abstraction layer should be added in the future to receive type of packets and read the data - } - - /** - * Accept and threat clients indefinitely - */ - fun start() { - Thread { - while (true) { - try { - this.once() - } catch (exception: Exception) { - Log.w("demo-server", "an error occured: $exception") - } - Thread.sleep(1000) - } - }.start() - } -} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt new file mode 100644 index 0000000..6b5a297 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt @@ -0,0 +1,29 @@ +package com.faraphel.tasks_valider.connectivity.packets + +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + + +/** + * Base for a packet that can be encoded and decoded for a Socket + */ +@Serializable +sealed class BasePacket { + companion object { + /** + * Create a new instance from an array of bytes. + * @param data: data obtained from the toBytes function. + */ + inline fun fromBytes(data: ByteArray): Packet { + return Json.decodeFromString(data.toString()) + } + } + + /** + * Encode the content of the packet into an array of bytes + */ + fun toBytes(): ByteArray { + return Json.encodeToString(this).encodeToByteArray() + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt new file mode 100644 index 0000000..3d641b2 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.connectivity.packets + +import kotlinx.serialization.Serializable + + +/** + * This is a simple packet class to test a connection + */ +@Serializable +data object PacketPing : BasePacket() diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt new file mode 100644 index 0000000..3431bbe --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt @@ -0,0 +1,41 @@ +package com.faraphel.tasks_valider.connectivity.room + +import android.util.Log +import com.faraphel.tasks_valider.connectivity.packets.BasePacket +import com.faraphel.tasks_valider.connectivity.packets.PacketPing +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket + + +/** + * A client to handle the room connection. + * @param address the address of the server + * @param port the port of the server + */ +class RoomClient( + private val address: InetAddress, + private val port: Int +) { + private val server = Socket() + + fun start() { + Log.d("room-client", "connecting to the server...") + try { + server.connect(InetSocketAddress(address, port), 10_000) + } catch (exception: Exception) { + Log.e("room-client", "could not connect to the server", exception) + return + } + Log.d("room-client", "connection successful !") + + val serverIn = server.getInputStream() + val serverOut = server.getOutputStream() + + serverOut.write(PacketPing.toBytes()) + val data = serverIn.readBytes() + val packet = BasePacket.fromBytes(data) + + Log.d("room-client", packet.toString()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt new file mode 100644 index 0000000..4e51489 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt @@ -0,0 +1,50 @@ +package com.faraphel.tasks_valider.connectivity.room + +import android.util.Log +import com.faraphel.tasks_valider.connectivity.packets.PacketPing +import java.net.ServerSocket +import java.net.Socket + + +/** + * A server to handle the room connection. + * @param port the port of the server + * @param timeout the timeout for a client (in milliseconds) + */ +class RoomServer( + private val port: Int, + private val timeout: Int = 10_000 +) { + private var server = ServerSocket(port) + + init { + server.soTimeout = 0 // accepting clients take an infinite timeout + } + + /** + * Accept and treat a client + */ + private fun handleClient(client: Socket) { + // TODO(Faraphel): should every client be handled in a new small thread ? + // Create the thread here and handle it until the connection is broken + + val clientIn = client.getInputStream() + val clientOut = client.getOutputStream() + + Log.i("room-server", "data: ${PacketPing.toBytes().toList()}") + clientOut.write(PacketPing.toBytes()) + } + + /** + * Accept connections and treat them + */ + fun start() { + Thread { + while (true) { + val client = server.accept() + client.soTimeout = timeout // set the timeout for the communication + this.handleClient(client) + } + }.start() + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt index 6bc878c..681d4c8 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt @@ -1,13 +1,19 @@ package com.faraphel.tasks_valider.ui.screen.room +import android.net.wifi.p2p.WifiP2pConfig +import android.net.wifi.p2p.WifiP2pDevice import android.net.wifi.p2p.WifiP2pDeviceList +import android.net.wifi.p2p.WifiP2pInfo import android.net.wifi.p2p.WifiP2pManager +import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import com.faraphel.tasks_valider.connectivity.room.RoomClient import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper +import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget @@ -17,17 +23,67 @@ import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidge @Composable fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) { val peers = remember { mutableStateOf(null) } + val selectedDevice = remember { mutableStateOf(null) } + val connected = remember { mutableStateOf(false) } + + // if the device is connected to a host, display the main screen + if (connected.value) { + TaskGroupScreen() + } + + // if a device is selected, connect to it + if (selectedDevice.value != null) { + // configure the connection to point to the selected device + val config = WifiP2pConfig().apply { + deviceAddress = selectedDevice.value!!.deviceAddress + } + // try to connect + wifiP2pHelper.connect(config, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + Log.i("room", "Connection successful to the host !") + // request additional information about the connection to obtain the host IP + wifiP2pHelper.registerListener( + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION, + { connectionInfo: WifiP2pInfo -> + val client = RoomClient( + connectionInfo.groupOwnerAddress, + 9876 // TODO(Faraphel): port should be a settings + ) + Thread { + client.start() + }.start() + + connected.value = true + }, + once = true + ) + } + + override fun onFailure(reason: Int) { + // TODO(Faraphel): for most of theses messages, shouldn't a toast be used instead ? + Log.e("room", "Could not connect to this host. Reason: $reason") + } + }) + return + } + + // let the user pick which host he wishes to connect to Column { Text(text = "Find a Server") - WifiP2pDeviceListWidget(peers.value) + // display all the devices that are owner of their group + WifiP2pDeviceListWidget( + peers = peers.value, + filter = { device: WifiP2pDevice -> device.isGroupOwner }, + selectedDevice, + ) } // TODO(Faraphel): might be doubtful. Test with more phones, should it only run once ? refresh button ? + // update the list when a new device is detected wifiP2pHelper.registerListener( WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, - { new_peers: WifiP2pDeviceList? -> peers.value = new_peers }, - once = true + { newPeers: WifiP2pDeviceList? -> peers.value = newPeers }, ) wifiP2pHelper.discoverPeers() } \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt index eb2dcf0..467470b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt @@ -2,22 +2,29 @@ package com.faraphel.tasks_valider.ui.screen.room import android.net.wifi.p2p.WifiP2pManager import android.util.Log -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen +import java.net.ServerSocket + + +var DEFAULT_SERVER_PORT: Int = 9876 /** * This screen allow the user to create a room */ -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) { val expanded = remember { mutableStateOf(false) } + val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) } val isCreated = remember { mutableStateOf(false) } // if the group have been created, display the group screen @@ -26,26 +33,48 @@ fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) { return } + // ask the user settings about the room Column { - ExposedDropdownMenuBox( - expanded = expanded.value, - onExpandedChange = { value -> expanded.value = !value } - ) { - DropdownMenuItem( - text = { Text("ISRI") }, - onClick = {} - ) - DropdownMenuItem( - text = { Text("MIAGE") }, - onClick = {} - ) + Box(Modifier.fillMaxWidth()) { + // dropdown button + Button(onClick = { expanded.value = !expanded.value }) { + Text(text = "Select Students List") + } + // dropdown list + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } + ) { + DropdownMenuItem( + text = { Text("ISRI") }, + onClick = {} + ) + DropdownMenuItem( + text = { Text("MIAGE") }, + onClick = {} + ) + } } + // ask the user what will be the server port + TextField( + value = serverPort.value.toString(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + onValueChange = { value -> serverPort.value = value.toIntOrNull() ?: DEFAULT_SERVER_PORT } + ) + Button(onClick = { // create a new WiFi-Direct group wifiP2pHelper.recreateGroup(object : WifiP2pManager.ActionListener { override fun onSuccess() { Log.i("room", "group created !") + + val server = ServerSocket(serverPort.value) // TODO(Faraphel): should the port be a settings ? + Thread { + val client = server.accept() // TODO(Faraphel): should run in a thread + client.getInputStream() + } + // mark the group as created isCreated.value = true } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt index 60c4fca..f9b4e26 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt @@ -9,5 +9,6 @@ import androidx.compose.runtime.Composable */ @Composable fun TaskGroupScreen() { + // TODO(Faraphel): should handle connexion with the server Text(text = "Task Group") } 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 index f6e19f1..9c09b82 100644 --- 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 @@ -1,22 +1,38 @@ 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?) { +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) { - WifiP2pDeviceWidget(device) + // 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) } } } 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 index b0363e0..71e9b70 100644 --- 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 @@ -3,24 +3,42 @@ 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) { - Column { - Row { - Text(text = "Name: ") - Text(text = device.deviceName) - } - Row { - Text(text = "Address: ") - Text(text = device.deviceAddress) - } - Row { - Text(text = "Is Owner: ") - Text(text = device.isGroupOwner.toString()) +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/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Group.kt similarity index 88% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Group.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Group.kt index 42c2c51..79e5c85 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Group.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Group.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets +package com.faraphel.tasks_valider.ui.widgets.tasks import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text 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/tasks/Task.kt similarity index 86% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Task.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Task.kt index eddc7fd..67b182f 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/Task.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Task.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets +package com.faraphel.tasks_valider.ui.widgets.tasks import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text 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/tasks/TaskGroup.kt similarity index 97% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/TaskGroup.kt index eb3dd80..99b3fd0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/TaskGroup.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/TaskGroup.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets +package com.faraphel.tasks_valider.ui.widgets.tasks import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column 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 +} From 60d5be49f7146e13898b4b000b98033ae6eef395 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 3 May 2024 21:57:22 +0200 Subject: [PATCH 12/24] [WIP] use navcontroller --- .idea/deploymentTargetDropDown.xml | 21 +- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 2 +- .../faraphel/tasks_valider/MainActivity.kt | 55 +++-- .../connectivity/bwf/BwfManager.kt | 151 +++++++++++++ .../tasks_valider/connectivity/bwf/README.md | 11 + .../bwf/error/BwfConnectException.kt | 5 + .../bwf/error/BwfCreateGroupException.kt | 5 + .../bwf/error/BwfDiscoverException.kt | 5 + .../error/BwfException.kt} | 4 +- .../bwf/error/BwfInvalidActionException.kt | 5 + .../bwf/error/BwfNotSupportedException.kt | 4 + .../bwf/error/BwfRemoveGroupException.kt | 5 + .../connectivity/wifi_p2p/WifiP2pHelper.kt | 212 ------------------ .../wifi_p2p/WifiP2pHelperListenerInfo.kt | 15 -- .../WifiP2pHelperInvalidActionException.kt | 5 - .../ui/screen/room/RoomClientScreen.kt | 90 +++----- .../ui/screen/room/RoomHostScreen.kt | 32 ++- .../ui/screen/room/RoomScreen.kt | 39 ++-- 19 files changed, 294 insertions(+), 373 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/BwfManager.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/README.md create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfConnectException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfCreateGroupException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfDiscoverException.kt rename app/src/main/java/com/faraphel/tasks_valider/connectivity/{wifi_p2p/error/WifiP2pHelperException.kt => bwf/error/BwfException.kt} (57%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfInvalidActionException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfNotSupportedException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfRemoveGroupException.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 665b551..5cd0755 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -4,27 +4,18 @@ - - + - + - - + + - - - - - - - - - - + + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2bfae1d..e1f0009 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,6 +62,7 @@ dependencies { 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") 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 6780e64..4dad71a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 56cbc1c..b3ef37d 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -1,25 +1,24 @@ package com.faraphel.tasks_valider -import android.content.Context -import android.content.pm.PackageManager import android.Manifest import android.annotation.SuppressLint -import android.net.wifi.p2p.* +import android.content.Context +import android.content.pm.PackageManager +import android.net.wifi.p2p.WifiP2pManager import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.RequiresApi -import com.faraphel.tasks_valider.connectivity.packets.PacketPing -import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper +import com.faraphel.tasks_valider.connectivity.bwf.BwfManager +import com.faraphel.tasks_valider.connectivity.bwf.error.BwfNotSupportedException import com.faraphel.tasks_valider.database.Database import com.faraphel.tasks_valider.ui.screen.room.RoomScreen class MainActivity : ComponentActivity() { - private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fi - private var p2pHelper: WifiP2pHelper? = null ///< the Wi-Fi Direct helper + private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper companion object { private lateinit var database: Database ///< the database manager @@ -29,56 +28,56 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // ----- CONNECTION + // TODO(Faraphel): more check on permissions - // TOOD: more check on permissions + // check if the system support WiFi-Direct if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) { Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature") - return + throw BwfNotSupportedException() } - if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + if ( + this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || + this.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED + ) { // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check - this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ACCESS_FINE_LOCATION) + this.requestPermissions( + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + BwfManager.PERMISSION_ACCESS_FINE_LOCATION + ) } // get the WiFi-Direct manager - val p2pManager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? - if (p2pManager == null) { + val manager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? + if (manager == null) { Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") - return + throw BwfNotSupportedException() } - // get the Wifi-Direct channel - val p2pChannel: WifiP2pManager.Channel = p2pManager.initialize(this, this.mainLooper, null) + // create a channel for the manager + val channel = manager.initialize(this, this.mainLooper, null) - // create a helper for handling the WiFi-Direct - this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel) + // create a new manager for handling the WiFi-Direct + this.bwfManager = BwfManager(manager, channel) // open the room selection screen this.setContent { - RoomScreen(this.p2pHelper!!) + RoomScreen(this.bwfManager!!) } } - @RequiresApi(Build.VERSION_CODES.O) @SuppressLint("UnspecifiedRegisterReceiverFlag") override fun onResume() { super.onResume() // enable the WiFi-Direct events - this.registerReceiver(this.p2pHelper, WifiP2pHelper.ALL_INTENT_FILTER) + this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER) } override fun onPause() { super.onPause() // disable the WiFi-Direct events - this.unregisterReceiver(this.p2pHelper) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - // TODO(Faraphel): save the current state to reload it later + 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..19780b3 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/BwfManager.kt @@ -0,0 +1,151 @@ +package com.faraphel.tasks_valider.connectivity.bwf + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.wifi.p2p.* +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) + } + } + + // 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 listener: the createGroup listener + * @param onRemoveFailure: error handler for the removeGroup event + * + * @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/wifi_p2p/error/WifiP2pHelperException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfException.kt similarity index 57% rename from app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt rename to app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfException.kt index 892e2b2..7fd8810 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperException.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfException.kt @@ -1,9 +1,9 @@ -package com.faraphel.tasks_valider.connectivity.wifi_p2p.error +package com.faraphel.tasks_valider.connectivity.bwf.error /** * Base Exception for everything concerning the WifiP2pHelper class */ -open class WifiP2pHelperException( +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..9f11424 --- /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, or it is disabled.") \ No newline at end of file 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/wifi_p2p/WifiP2pHelper.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt deleted file mode 100644 index 574a030..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelper.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.wifi_p2p - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.net.wifi.p2p.WifiP2pConfig -import android.net.wifi.p2p.WifiP2pDeviceList -import android.net.wifi.p2p.WifiP2pManager -import android.os.Build -import android.util.Log -import androidx.annotation.RequiresApi -import com.faraphel.tasks_valider.connectivity.wifi_p2p.error.WifiP2pHelperInvalidActionException - - -/** - * 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 base manager - * @param channel: the WiFi-Direct channel - */ -class WifiP2pHelper( - private val manager: WifiP2pManager, - private val channel: WifiP2pManager.Channel, -) : BroadcastReceiver() { - companion object { - /** - * This IntentFilter can be used to subscribe the helper to every supported WiFi-Direct events. - */ - val ALL_INTENT_FILTER = IntentFilter().apply { - addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) - addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) - addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) - addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) - } - } - - // Events - - /** - * Contains the listeners to call when the peers are updated. - */ - private val listenersPeersChanged = mutableListOf Unit>>() - - /** - * Event receiver for the WiFi-Direct events. - * Should not be called manually. - * @param context: the WiFi-Direct context - * @param intent: the event information - */ - @RequiresApi(Build.VERSION_CODES.N) - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { - Log.v("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}") - } - - WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { - Log.v("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}") - - this.requestPeers { peers -> - this@WifiP2pHelper.listenersPeersChanged.forEach { listenerInfo -> listenerInfo.callback(peers) } - this@WifiP2pHelper.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.once } - } - } - - WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { - Log.v("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}") - } - - WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { - Log.v("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}") - } - } - } - - /** - * Register a listener to a specific WiFi-Direct event. - * @param action: the name of the WiFi-Direct event - * @param callback: the callback function - * @param once: should the event be only called once ? - */ - fun registerListener(action: String, callback: CallbackType, once: Boolean = false) { - when (action) { - WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { - @Suppress("UNCHECKED_CAST") - this.listenersPeersChanged.add(WifiP2pHelperListenerInfo( - callback as ((WifiP2pDeviceList?) -> Unit), - once - )) - } - else -> { - // raise an exception if the action is invalid - throw WifiP2pHelperInvalidActionException(action) - } - } - } - - @RequiresApi(Build.VERSION_CODES.N) - fun unregisterListener(action: String, callback: CallbackType) { - when (action) { - WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { - this.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.callback == callback } - } - else -> { - // raise an exception if the action is invalid - throw WifiP2pHelperInvalidActionException(action) - } - } - } - - // Wrappers - - /** - * Connect to another device, allowing for a communication using Sockets - * @see WifiP2pManager.connect - */ - fun connect(config: WifiP2pConfig, listener: WifiP2pManager.ActionListener? = null) { - this.manager.connect(this.channel, config, listener) - } - - /** - * Request the list of peers after a discovery. - * @see WifiP2pManager.requestPeers - */ - fun requestPeers(listener: WifiP2pManager.PeerListListener? = null) { - this.manager.requestPeers(this.channel, listener) - } - - /** - * Start discovering peers. - * Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered. - * @see WifiP2pManager.discoverPeers - */ - fun discoverPeers(listener: WifiP2pManager.ActionListener? = null) { - this.manager.discoverPeers(this.channel, listener) - } - - /** - * Obtain information about a connection with another device. - * @see WifiP2pManager.requestConnectionInfo - */ - fun requestConnectionInfo(listener: WifiP2pManager.ConnectionInfoListener? = null) { - this.manager.requestConnectionInfo(this.channel, listener) - } - - /** - * Obtain information about the current group. - * @see WifiP2pManager.requestGroupInfo - */ - fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) { - this.manager.requestGroupInfo(this.channel, listener) - } - - /** - * Create a new WiFi-Direct group. - * @see WifiP2pManager.createGroup - */ - fun createGroup(listener: WifiP2pManager.ActionListener? = null) { - this.manager.createGroup(this.channel, listener) - } - - /** - * Disconnect from the current WiFi-Direct group. - * @see WifiP2pManager.removeGroup - */ - fun removeGroup(listener: WifiP2pManager.ActionListener? = null) { - this.manager.removeGroup(this.channel, listener) - } - - // Shortcuts - - /** - * 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 listener: the createGroup listener - * @param onRemoveFailure: error handler for the removeGroup event - * - * @see WifiP2pManager.createGroup - * @see WifiP2pManager.removeGroup - */ - fun recreateGroup( - listener: WifiP2pManager.ActionListener? = null, - onRemoveFailure: ((Int) -> Unit)? = null - ) { - // get the current group information - this.requestGroupInfo { group -> - // if a group exist, quit it - if (group != null) - this.removeGroup(object : WifiP2pManager.ActionListener { - override fun onSuccess() { - // once left, create the group - this@WifiP2pHelper.createGroup(listener) - } - - override fun onFailure(reason: Int) { - // call the handler - onRemoveFailure?.invoke(reason) - } - - }) - else - // create the group - this.createGroup(listener) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt deleted file mode 100644 index 3c68903..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/WifiP2pHelperListenerInfo.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.wifi_p2p - -import java.util.concurrent.Callable - - -/** - * This class contain the information required to store a listener in the WifiP2pHelper class to correctly handle events. - * @param CallbackType: the type of the callback function - * @param callback: the function to call when the event is triggered - * @param once: should the listener be called only once ? - */ -class WifiP2pHelperListenerInfo( - val callback: CallbackType, - val once: Boolean, -) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt deleted file mode 100644 index 26d147f..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/wifi_p2p/error/WifiP2pHelperInvalidActionException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.wifi_p2p.error - -class WifiP2pHelperInvalidActionException( - action: String -) : WifiP2pHelperException("This WiFi-Direct action is not supported : $action") diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt index 681d4c8..44b1b0b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt @@ -3,32 +3,50 @@ package com.faraphel.tasks_valider.ui.screen.room import android.net.wifi.p2p.WifiP2pConfig import android.net.wifi.p2p.WifiP2pDevice import android.net.wifi.p2p.WifiP2pDeviceList -import android.net.wifi.p2p.WifiP2pInfo -import android.net.wifi.p2p.WifiP2pManager import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.bwf.BwfManager import com.faraphel.tasks_valider.connectivity.room.RoomClient -import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget +import kotlinx.coroutines.runBlocking /** * This screen allow the user to join a room */ @Composable -fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) { - val peers = remember { mutableStateOf(null) } +fun RoomClientScreen(bwfManager: BwfManager) { + val navController = rememberNavController() + val selectedDevice = remember { mutableStateOf(null) } val connected = remember { mutableStateOf(false) } - // if the device is connected to a host, display the main screen - if (connected.value) { - TaskGroupScreen() + NavHost(navController = navController, startDestination = "roomSearch") { + composable(route = "roomSearch") { + // let the user pick which host he wishes to connect to + Column { + Text(text = "Find a Server") + // display all the devices that are owner of their group + WifiP2pDeviceListWidget( + peers = bwfManager.statePeers.value, + filter = { device: WifiP2pDevice -> device.isGroupOwner }, + selectedDevice, + ) + } + + // update the list when a new device is detected + } + composable(route = "taskGroup") { + TaskGroupScreen() + } } // if a device is selected, connect to it @@ -37,53 +55,17 @@ fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) { val config = WifiP2pConfig().apply { deviceAddress = selectedDevice.value!!.deviceAddress } - // try to connect - wifiP2pHelper.connect(config, object : WifiP2pManager.ActionListener { - override fun onSuccess() { - Log.i("room", "Connection successful to the host !") - // request additional information about the connection to obtain the host IP - wifiP2pHelper.registerListener( - WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION, - { connectionInfo: WifiP2pInfo -> - val client = RoomClient( - connectionInfo.groupOwnerAddress, - 9876 // TODO(Faraphel): port should be a settings - ) - Thread { - client.start() - }.start() - connected.value = true - }, - once = true - ) + // try to connect to the host + bwfManager.connect(config) { + Log.i("room", "Connection successful to the host !") + + bwfManager.requestConnectionInfo { connectionInfo -> + val client = RoomClient(connectionInfo.groupOwnerAddress, 9876) // TODO(Faraphel): port should be a settings + Thread { client.start() }.start() + + connected.value = true } - - override fun onFailure(reason: Int) { - // TODO(Faraphel): for most of theses messages, shouldn't a toast be used instead ? - Log.e("room", "Could not connect to this host. Reason: $reason") - } - }) - return + } } - - // let the user pick which host he wishes to connect to - - Column { - Text(text = "Find a Server") - // display all the devices that are owner of their group - WifiP2pDeviceListWidget( - peers = peers.value, - filter = { device: WifiP2pDevice -> device.isGroupOwner }, - selectedDevice, - ) - } - - // TODO(Faraphel): might be doubtful. Test with more phones, should it only run once ? refresh button ? - // update the list when a new device is detected - wifiP2pHelper.registerListener( - WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, - { newPeers: WifiP2pDeviceList? -> peers.value = newPeers }, - ) - wifiP2pHelper.discoverPeers() } \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt index 467470b..bb9ba87 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt @@ -1,6 +1,5 @@ package com.faraphel.tasks_valider.ui.screen.room -import android.net.wifi.p2p.WifiP2pManager import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions @@ -10,8 +9,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType -import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper +import com.faraphel.tasks_valider.connectivity.bwf.BwfManager import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen +import kotlinx.coroutines.runBlocking import java.net.ServerSocket @@ -22,7 +22,7 @@ var DEFAULT_SERVER_PORT: Int = 9876 * This screen allow the user to create a room */ @Composable -fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) { +fun RoomHostScreen(bwfManager: BwfManager) { val expanded = remember { mutableStateOf(false) } val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) } val isCreated = remember { mutableStateOf(false) } @@ -65,23 +65,19 @@ fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) { Button(onClick = { // create a new WiFi-Direct group - wifiP2pHelper.recreateGroup(object : WifiP2pManager.ActionListener { - override fun onSuccess() { - Log.i("room", "group created !") + bwfManager.recreateGroup { + Log.i("room", "group created !") - val server = ServerSocket(serverPort.value) // TODO(Faraphel): should the port be a settings ? - Thread { - val client = server.accept() // TODO(Faraphel): should run in a thread - client.getInputStream() - } + // open a new server + val server = ServerSocket(serverPort.value) + Thread { + val client = server.accept() // TODO(Faraphel): should run in a thread + client.getInputStream() + } - // mark the group as created - isCreated.value = true - } - override fun onFailure(reason: Int) { - Log.e("room", "error while creating group. Reason: $reason") - } - }) + // mark the group as created + isCreated.value = true + } }) { Text("Create") } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt index 3e7b38a..efa95bd 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt @@ -6,42 +6,35 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper - - -enum class RoomScreenType { - HOST, - CLIENT -} +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.faraphel.tasks_valider.connectivity.bwf.BwfManager /** * This screen will allow the user to choose which room is he connecting to, or let him create one. */ @Composable -fun RoomScreen(wifiP2pHelper: WifiP2pHelper) { - val roomMode = remember { mutableStateOf(null) } +fun RoomScreen(bwfManager: BwfManager) { + val navController = rememberNavController() - when (roomMode.value) { - RoomScreenType.CLIENT -> { - // Client mode - RoomClientScreen(wifiP2pHelper) - } - RoomScreenType.HOST -> { - // Host mode - RoomHostScreen(wifiP2pHelper) - } - - null -> { - // let the user decide which room mode he which to use + NavHost(navController = navController, startDestination = "pickMode") { + composable(route = "pickMode") { Column { - Button(onClick = { roomMode.value = RoomScreenType.CLIENT }) { + Button(onClick = { navController.navigate("client") }) { Text(text = "Join a room") } - Button(onClick = { roomMode.value = RoomScreenType.HOST }) { + Button(onClick = { navController.navigate("host") }) { Text(text = "Create a room") } } } + composable(route = "client") { + RoomClientScreen(bwfManager) + } + composable(route = "host") { + RoomHostScreen(bwfManager) + } } } \ No newline at end of file From 2637f2fe8bd824c0d803ec869fe2aeae69956d1c Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sat, 4 May 2024 16:07:47 +0200 Subject: [PATCH 13/24] [WIP] added support for normal Internet connection additionally to WiFi-Direct and improved UI management --- .idea/deploymentTargetDropDown.xml | 24 +++++- .idea/misc.xml | 2 +- .../faraphel/tasks_valider/MainActivity.kt | 43 +--------- .../connectivity/bwf/BwfManager.kt | 45 +++++++++- .../bwf/error/BwfNotSupportedException.kt | 2 +- .../bwf/error/BwfPermissionException.kt | 4 + .../connectivity/room/RoomClient.kt | 6 +- .../connectivity/room/RoomServer.kt | 23 +++-- .../communication/internet/client/screen.kt | 64 ++++++++++++++ .../screen/communication/internet/screen.kt | 45 ++++++++++ .../communication/internet/server/screen.kt | 79 +++++++++++++++++ .../ui/screen/communication/screen.kt | 77 +++++++++++++++++ .../ui/screen/communication/settings.kt | 6 ++ .../communication/wifiP2p/client/screen.kt | 55 ++++++++++++ .../ui/screen/communication/wifiP2p/screen.kt | 48 +++++++++++ .../communication/wifiP2p/server/screen.kt | 83 ++++++++++++++++++ .../ui/screen/room/RoomClientScreen.kt | 71 --------------- .../ui/screen/room/RoomHostScreen.kt | 86 ------------------- .../ui/screen/room/RoomScreen.kt | 40 --------- .../tasks/{TaskGroupScreen.kt => screen.kt} | 0 20 files changed, 548 insertions(+), 255 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/bwf/error/BwfPermissionException.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/client/screen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/settings.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt rename app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/{TaskGroupScreen.kt => screen.kt} (100%) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 5cd0755..da69b70 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -10,12 +10,32 @@ - + - + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index f9d6e36..266c22b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index b3ef37d..ef25ff9 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -1,20 +1,14 @@ package com.faraphel.tasks_valider -import android.Manifest import android.annotation.SuppressLint -import android.content.Context -import android.content.pm.PackageManager -import android.net.wifi.p2p.WifiP2pManager import android.os.Build import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.RequiresApi import com.faraphel.tasks_valider.connectivity.bwf.BwfManager -import com.faraphel.tasks_valider.connectivity.bwf.error.BwfNotSupportedException import com.faraphel.tasks_valider.database.Database -import com.faraphel.tasks_valider.ui.screen.room.RoomScreen +import com.faraphel.tasks_valider.ui.screen.communication.CommunicationScreen class MainActivity : ComponentActivity() { @@ -28,41 +22,8 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // TODO(Faraphel): more check on permissions - - // check if the system support WiFi-Direct - if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) { - Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature") - throw BwfNotSupportedException() - } - - if ( - this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || - this.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED - ) { - // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check - this.requestPermissions( - arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), - BwfManager.PERMISSION_ACCESS_FINE_LOCATION - ) - } - - // get the WiFi-Direct manager - val manager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? - if (manager == null) { - Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") - throw BwfNotSupportedException() - } - - // create a channel for the manager - val channel = manager.initialize(this, this.mainLooper, null) - - // create a new manager for handling the WiFi-Direct - this.bwfManager = BwfManager(manager, channel) - - // open the room selection screen this.setContent { - RoomScreen(this.bwfManager!!) + CommunicationScreen(this) } } 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 index 19780b3..31f6681 100644 --- 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 @@ -1,10 +1,14 @@ 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.* @@ -28,6 +32,44 @@ class BwfManager( 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 @@ -109,8 +151,7 @@ class BwfManager( * * Note: most of the failure on removal are caused by not having a group already created, which is checked. * - * @param listener: the createGroup listener - * @param onRemoveFailure: error handler for the removeGroup event + * @param callback: the createGroup listener * * @see WifiP2pManager.createGroup * @see WifiP2pManager.removeGroup 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 index 9f11424..3585496 100644 --- 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 @@ -1,4 +1,4 @@ package com.faraphel.tasks_valider.connectivity.bwf.error class BwfNotSupportedException : - BwfException("WiFi-Direct is not supported on this device, or it is disabled.") \ No newline at end of file + 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/room/RoomClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt index 3431bbe..c902fe1 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt @@ -16,10 +16,12 @@ import java.net.Socket class RoomClient( private val address: InetAddress, private val port: Int -) { +) : Thread() { private val server = Socket() - fun start() { + constructor(address: String, port: Int) : this(InetAddress.getByName(address), port) + + override fun run() { Log.d("room-client", "connecting to the server...") try { server.connect(InetSocketAddress(address, port), 10_000) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt index 4e51489..fee1c3e 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt @@ -14,7 +14,7 @@ import java.net.Socket class RoomServer( private val port: Int, private val timeout: Int = 10_000 -) { +) : Thread() { private var server = ServerSocket(port) init { @@ -38,13 +38,18 @@ class RoomServer( /** * Accept connections and treat them */ - fun start() { - Thread { - while (true) { - val client = server.accept() - client.soTimeout = timeout // set the timeout for the communication - this.handleClient(client) - } - }.start() + override fun run() { + while (!server.isClosed) { + val client = server.accept() + client.soTimeout = timeout // set the timeout for the communication + this.handleClient(client) + } + } + + /** + * Close the server + */ + fun close() { + server.close() } } 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..93a5769 --- /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 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.room.RoomClient +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.tasks.TaskGroupScreen + + +@Composable +fun CommunicationInternetClientScreen() { + val client = remember { mutableStateOf(null) } + + if (client.value == null) CommunicationInternetClientContent(client) + else TaskGroupScreen() +} + + +@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 = RoomClient(serverAddress.value, serverPort.intValue) + client.value!!.start() + }) { + 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..5b6995f --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt @@ -0,0 +1,45 @@ +package com.faraphel.tasks_valider.ui.screen.communication.internet + +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() { + val controller = rememberNavController() + + NavHost(navController = controller, startDestination = "mode") { + composable("mode") { + CommunicationInternetSelectContent(controller) + } + composable("client") { + CommunicationInternetClientScreen() + } + composable("server") { + CommunicationInternetServerScreen() + } + } +} + + +@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..478b71f --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt @@ -0,0 +1,79 @@ +package com.faraphel.tasks_valider.ui.screen.communication.internet.server + +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 com.faraphel.tasks_valider.connectivity.bwf.BwfManager +import com.faraphel.tasks_valider.connectivity.room.RoomServer +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.tasks.TaskGroupScreen + + +@Composable +fun CommunicationInternetServerScreen() { + val server = remember { mutableStateOf(null)} + + // if the server is not created, prompt the user for the server configuration + if (server.value == null) CommunicationInternetServerContent(server) + // else, go to the base tasks screen + else TaskGroupScreen() +} + + +@Composable +fun CommunicationInternetServerContent(server: 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 = { + server.value = RoomServer(serverPort.intValue) + server.value!!.start() + // TODO(Faraphel): go to the base tasks screen + }) { + 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..1a92119 --- /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() + } + composable("wifi-p2p") { + val bwfManager = BwfManager.fromActivity(activity) + CommunicationWifiP2pScreen(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..9e9e310 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt @@ -0,0 +1,55 @@ +package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client + +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.screen.tasks.TaskGroupScreen +import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget + + +@Composable +fun CommunicationWifiP2pClientScreen(bwfManager: BwfManager) { + val selectedDevice = remember { mutableStateOf(null) } + val isConnected = remember { mutableStateOf(false) } + + // if connected, show the task group screen + if (isConnected.value) { + TaskGroupScreen() + 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..d9ea6a7 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/screen.kt @@ -0,0 +1,48 @@ +package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p + +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.internet.CommunicationInternetScreen +import com.faraphel.tasks_valider.ui.screen.communication.internet.server.CommunicationInternetServerScreen +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(bwfManager: BwfManager) { + val controller = rememberNavController() + + NavHost(navController = controller, startDestination = "mode") { + composable("mode") { + CommunicationWifiP2pSelectContent(controller) + } + composable("client") { + CommunicationWifiP2pClientScreen(bwfManager) + } + composable("server") { + CommunicationWifiP2pServerScreen(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..eadecdd --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt @@ -0,0 +1,83 @@ +package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server + +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 com.faraphel.tasks_valider.connectivity.bwf.BwfManager +import com.faraphel.tasks_valider.connectivity.room.RoomServer +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.tasks.TaskGroupScreen + + +@Composable +fun CommunicationWifiP2pServerScreen(bwfManager: BwfManager) { + val server = remember { mutableStateOf(null)} + + // if the server is not created, prompt the user for the server configuration + if (server.value == null) CommunicationWifiP2pServerContent(bwfManager, server) + // else, go to the base tasks screen + else TaskGroupScreen() +} + + +@Composable +fun CommunicationWifiP2pServerContent( + bwfManager: BwfManager, + server: 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 = { + bwfManager.recreateGroup { + server.value = RoomServer(serverPort.intValue) + server.value!!.start() + } + }) { + Text("Create") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt deleted file mode 100644 index 44b1b0b..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomClientScreen.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.room - -import android.net.wifi.p2p.WifiP2pConfig -import android.net.wifi.p2p.WifiP2pDevice -import android.net.wifi.p2p.WifiP2pDeviceList -import android.util.Log -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.faraphel.tasks_valider.connectivity.bwf.BwfManager -import com.faraphel.tasks_valider.connectivity.room.RoomClient -import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen -import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget -import kotlinx.coroutines.runBlocking - - -/** - * This screen allow the user to join a room - */ -@Composable -fun RoomClientScreen(bwfManager: BwfManager) { - val navController = rememberNavController() - - val selectedDevice = remember { mutableStateOf(null) } - val connected = remember { mutableStateOf(false) } - - NavHost(navController = navController, startDestination = "roomSearch") { - composable(route = "roomSearch") { - // let the user pick which host he wishes to connect to - Column { - Text(text = "Find a Server") - // display all the devices that are owner of their group - WifiP2pDeviceListWidget( - peers = bwfManager.statePeers.value, - filter = { device: WifiP2pDevice -> device.isGroupOwner }, - selectedDevice, - ) - } - - // update the list when a new device is detected - } - composable(route = "taskGroup") { - TaskGroupScreen() - } - } - - // if a device is selected, connect to it - if (selectedDevice.value != null) { - // configure the connection to point to the selected device - val config = WifiP2pConfig().apply { - deviceAddress = selectedDevice.value!!.deviceAddress - } - - // try to connect to the host - bwfManager.connect(config) { - Log.i("room", "Connection successful to the host !") - - bwfManager.requestConnectionInfo { connectionInfo -> - val client = RoomClient(connectionInfo.groupOwnerAddress, 9876) // TODO(Faraphel): port should be a settings - Thread { client.start() }.start() - - connected.value = true - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt deleted file mode 100644 index bb9ba87..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomHostScreen.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.room - -import android.util.Log -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.input.KeyboardType -import com.faraphel.tasks_valider.connectivity.bwf.BwfManager -import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen -import kotlinx.coroutines.runBlocking -import java.net.ServerSocket - - -var DEFAULT_SERVER_PORT: Int = 9876 - - -/** - * This screen allow the user to create a room - */ -@Composable -fun RoomHostScreen(bwfManager: BwfManager) { - val expanded = remember { mutableStateOf(false) } - val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) } - val isCreated = remember { mutableStateOf(false) } - - // if the group have been created, display the group screen - if (isCreated.value) { - TaskGroupScreen() - return - } - - // ask the user settings about the room - Column { - Box(Modifier.fillMaxWidth()) { - // dropdown button - Button(onClick = { expanded.value = !expanded.value }) { - Text(text = "Select Students List") - } - // dropdown list - DropdownMenu( - expanded = expanded.value, - onDismissRequest = { expanded.value = false } - ) { - DropdownMenuItem( - text = { Text("ISRI") }, - onClick = {} - ) - DropdownMenuItem( - text = { Text("MIAGE") }, - onClick = {} - ) - } - } - - // ask the user what will be the server port - TextField( - value = serverPort.value.toString(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - onValueChange = { value -> serverPort.value = value.toIntOrNull() ?: DEFAULT_SERVER_PORT } - ) - - Button(onClick = { - // create a new WiFi-Direct group - bwfManager.recreateGroup { - Log.i("room", "group created !") - - // open a new server - val server = ServerSocket(serverPort.value) - Thread { - val client = server.accept() // TODO(Faraphel): should run in a thread - client.getInputStream() - } - - // mark the group as created - isCreated.value = true - } - }) { - Text("Create") - } - } - -} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt deleted file mode 100644 index efa95bd..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/room/RoomScreen.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.faraphel.tasks_valider.ui.screen.room - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import com.faraphel.tasks_valider.connectivity.bwf.BwfManager - - -/** - * This screen will allow the user to choose which room is he connecting to, or let him create one. - */ -@Composable -fun RoomScreen(bwfManager: BwfManager) { - val navController = rememberNavController() - - NavHost(navController = navController, startDestination = "pickMode") { - composable(route = "pickMode") { - Column { - Button(onClick = { navController.navigate("client") }) { - Text(text = "Join a room") - } - Button(onClick = { navController.navigate("host") }) { - Text(text = "Create a room") - } - } - } - composable(route = "client") { - RoomClientScreen(bwfManager) - } - composable(route = "host") { - RoomHostScreen(bwfManager) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/screen.kt similarity index 100% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/TaskGroupScreen.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/screen.kt From a604e01c129202041695f95de36db72e771d0705 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sun, 5 May 2024 14:24:05 +0200 Subject: [PATCH 14/24] [WIP] using JSON and HTTP server / client to communicate between the devices --- .idea/deploymentTargetDropDown.xml | 34 ++++----- .idea/misc.xml | 1 - app/build.gradle.kts | 3 + .../faraphel/tasks_valider/MainActivity.kt | 4 +- .../connectivity/packets/BasePacket.kt | 29 -------- .../connectivity/packets/PacketPing.kt | 10 --- .../connectivity/room/RoomClient.kt | 43 ------------ .../connectivity/room/RoomServer.kt | 55 --------------- .../connectivity/task/TaskClient.kt | 49 +++++++++++++ .../connectivity/task/TaskServer.kt | 70 +++++++++++++++++++ .../database/{Database.kt => TaskDatabase.kt} | 27 +++++-- .../communication/internet/client/screen.kt | 10 +-- .../screen/communication/internet/screen.kt | 6 +- .../communication/internet/server/screen.kt | 32 ++++++--- .../ui/screen/communication/screen.kt | 4 +- .../communication/wifiP2p/client/screen.kt | 3 +- .../ui/screen/communication/wifiP2p/screen.kt | 7 +- .../communication/wifiP2p/server/screen.kt | 31 ++++++-- .../ui/screen/{tasks => task}/screen.kt | 2 +- .../ui/widgets/{tasks => task}/Group.kt | 2 +- .../ui/widgets/{tasks => task}/Task.kt | 2 +- .../ui/widgets/{tasks => task}/TaskGroup.kt | 6 +- 22 files changed, 233 insertions(+), 197 deletions(-) delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt rename app/src/main/java/com/faraphel/tasks_valider/database/{Database.kt => TaskDatabase.kt} (67%) rename app/src/main/java/com/faraphel/tasks_valider/ui/screen/{tasks => task}/screen.kt (85%) rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{tasks => task}/Group.kt (88%) rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{tasks => task}/Task.kt (86%) rename app/src/main/java/com/faraphel/tasks_valider/ui/widgets/{tasks => task}/TaskGroup.kt (90%) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index da69b70..f67f999 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -4,34 +4,36 @@ - + - + - - + + - - + + + + + + + + + + + + + - - - - - - - - - - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 266c22b..cf5d4ff 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e1f0009..2568df0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -63,6 +63,9 @@ dependencies { 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/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index ef25ff9..b35c547 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -7,7 +7,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.RequiresApi import com.faraphel.tasks_valider.connectivity.bwf.BwfManager -import com.faraphel.tasks_valider.database.Database +import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.ui.screen.communication.CommunicationScreen @@ -15,7 +15,7 @@ class MainActivity : ComponentActivity() { private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper companion object { - private lateinit var database: Database ///< the database manager + private lateinit var database: TaskDatabase ///< the database manager } @RequiresApi(Build.VERSION_CODES.O) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt deleted file mode 100644 index 6b5a297..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/BasePacket.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.packets - -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - - -/** - * Base for a packet that can be encoded and decoded for a Socket - */ -@Serializable -sealed class BasePacket { - companion object { - /** - * Create a new instance from an array of bytes. - * @param data: data obtained from the toBytes function. - */ - inline fun fromBytes(data: ByteArray): Packet { - return Json.decodeFromString(data.toString()) - } - } - - /** - * Encode the content of the packet into an array of bytes - */ - fun toBytes(): ByteArray { - return Json.encodeToString(this).encodeToByteArray() - } -} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt deleted file mode 100644 index 3d641b2..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/packets/PacketPing.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.packets - -import kotlinx.serialization.Serializable - - -/** - * This is a simple packet class to test a connection - */ -@Serializable -data object PacketPing : BasePacket() diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt deleted file mode 100644 index c902fe1..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.room - -import android.util.Log -import com.faraphel.tasks_valider.connectivity.packets.BasePacket -import com.faraphel.tasks_valider.connectivity.packets.PacketPing -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.Socket - - -/** - * A client to handle the room connection. - * @param address the address of the server - * @param port the port of the server - */ -class RoomClient( - private val address: InetAddress, - private val port: Int -) : Thread() { - private val server = Socket() - - constructor(address: String, port: Int) : this(InetAddress.getByName(address), port) - - override fun run() { - Log.d("room-client", "connecting to the server...") - try { - server.connect(InetSocketAddress(address, port), 10_000) - } catch (exception: Exception) { - Log.e("room-client", "could not connect to the server", exception) - return - } - Log.d("room-client", "connection successful !") - - val serverIn = server.getInputStream() - val serverOut = server.getOutputStream() - - serverOut.write(PacketPing.toBytes()) - val data = serverIn.readBytes() - val packet = BasePacket.fromBytes(data) - - Log.d("room-client", packet.toString()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt deleted file mode 100644 index fee1c3e..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/room/RoomServer.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.faraphel.tasks_valider.connectivity.room - -import android.util.Log -import com.faraphel.tasks_valider.connectivity.packets.PacketPing -import java.net.ServerSocket -import java.net.Socket - - -/** - * A server to handle the room connection. - * @param port the port of the server - * @param timeout the timeout for a client (in milliseconds) - */ -class RoomServer( - private val port: Int, - private val timeout: Int = 10_000 -) : Thread() { - private var server = ServerSocket(port) - - init { - server.soTimeout = 0 // accepting clients take an infinite timeout - } - - /** - * Accept and treat a client - */ - private fun handleClient(client: Socket) { - // TODO(Faraphel): should every client be handled in a new small thread ? - // Create the thread here and handle it until the connection is broken - - val clientIn = client.getInputStream() - val clientOut = client.getOutputStream() - - Log.i("room-server", "data: ${PacketPing.toBytes().toList()}") - clientOut.write(PacketPing.toBytes()) - } - - /** - * Accept connections and treat them - */ - override fun run() { - while (!server.isClosed) { - val client = server.accept() - client.soTimeout = timeout // set the timeout for the communication - this.handleClient(client) - } - } - - /** - * Close the server - */ - fun close() { - server.close() - } -} 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..fc8c1b7 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskClient.kt @@ -0,0 +1,49 @@ +package com.faraphel.tasks_valider.connectivity.task + +import android.util.Log +import com.google.gson.Gson +import okhttp3.OkHttpClient +import java.net.InetAddress +import java.net.URL + + +/** + * A client to handle the room connection. + * @param address the address of the server + * @param port the port of the server + */ +class TaskClient( + private val address: InetAddress, + private val port: Int +) : Thread() { + private val client = OkHttpClient() + private val jsonParser = Gson() + + constructor(address: String, port: Int) : this(InetAddress.getByName(address), port) + + override fun run() { + Log.i("room-client", "started !") + + // send a request to the server + val request = okhttp3.Request.Builder() + .url(URL("http://${address.hostAddress}:$port")) + .build() + + // get the response + val response = client.newCall(request).execute() + + // check if the response is successful + if (!response.isSuccessful) { + Log.e("room-client", "could not connect to the server") + return + } + Log.i("room-client", "connected to the server") + + // parse the response + val body = response.body.string() + val data = jsonParser.fromJson(body, Map::class.java) + + // print the data + data.forEach { (key, value) -> Log.d("room-client", "$key: $value") } + } +} \ 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..211ba61 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt @@ -0,0 +1,70 @@ +package com.faraphel.tasks_valider.connectivity.task + +import android.util.Log +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.entities.Task +import com.google.gson.Gson +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) { + private val jsonParser = Gson() + + override fun serve(session: IHTTPSession): Response { + val method: Method = session.method + val uri: String = session.uri + + // remove the first slash + val daoName: String = uri.substring(1) + + // handle the request + when (method) { + // get the data from the database + Method.GET -> { + return newFixedLengthResponse( + Response.Status.OK, + "application/json", + jsonParser.toJson( database.taskDao().getAll() ) + ) + } + // insert the data into the database + Method.POST -> { + val task = jsonParser.fromJson( + session.inputStream.bufferedReader(), + Task::class.java + ) + database.taskDao().insert(task) + + return newFixedLengthResponse( + Response.Status.CREATED, + "application/json", + jsonParser.toJson(task) + ) + } + // other methods are not allowed + else -> { + return newFixedLengthResponse( + Response.Status.METHOD_NOT_ALLOWED, + "text/plain", + "Method not allowed" + ) + } + // TODO(Faraphel): implement a permission system + } + } + + /** + * 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/database/Database.kt b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt similarity index 67% rename from app/src/main/java/com/faraphel/tasks_valider/database/Database.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt index 17222f1..718b6d1 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/Database.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -1,9 +1,6 @@ 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 @@ -13,15 +10,19 @@ 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.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 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 +/** + * The database for the tasks application. + * Contains the entities and the relations between them. + */ @Database( entities = [ Group::class, @@ -37,7 +38,7 @@ import java.time.Instant @TypeConverters( InstantConverter::class ) -abstract class Database : RoomDatabase() { +abstract class TaskDatabase : RoomDatabase() { // entities abstract fun groupDao(): GroupDao abstract fun studentDao(): StudentDao @@ -47,4 +48,20 @@ abstract class Database : RoomDatabase() { // relations abstract fun groupStudentDao(): GroupStudentDao abstract fun taskGroupDao(): TaskGroupDao + + /** + * Get the DAO from the name of the dao. + */ + @Suppress("UNCHECKED_CAST") + fun daoFromName(name: String): BaseDao? { + return when (name) { + "group" -> groupDao() + "student" -> studentDao() + "teacher" -> teacherDao() + "task" -> taskDao() + "group_student" -> groupStudentDao() + "task_group" -> taskGroupDao() + else -> null + } as BaseDao? + } } 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 index 93a5769..7dad218 100644 --- 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 @@ -11,16 +11,16 @@ 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.room.RoomClient +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.tasks.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen @Composable fun CommunicationInternetClientScreen() { - val client = remember { mutableStateOf(null) } + val client = remember { mutableStateOf(null) } if (client.value == null) CommunicationInternetClientContent(client) else TaskGroupScreen() @@ -28,7 +28,7 @@ fun CommunicationInternetClientScreen() { @Composable -fun CommunicationInternetClientContent(client: MutableState) { +fun CommunicationInternetClientContent(client: MutableState) { val serverAddress = remember { mutableStateOf(DEFAULT_SERVER_ADDRESS) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } @@ -55,7 +55,7 @@ fun CommunicationInternetClientContent(client: MutableState) { Button(onClick = { // TODO(Faraphel): check if the server is reachable - client.value = RoomClient(serverAddress.value, serverPort.intValue) + client.value = TaskClient(serverAddress.value, serverPort.intValue) client.value!!.start() }) { Text("Connect") 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 index 5b6995f..4c2455b 100644 --- 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 @@ -1,10 +1,12 @@ 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.activity import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -13,7 +15,7 @@ import com.faraphel.tasks_valider.ui.screen.communication.internet.server.Commun @Composable -fun CommunicationInternetScreen() { +fun CommunicationInternetScreen(activity: Activity) { val controller = rememberNavController() NavHost(navController = controller, startDestination = "mode") { @@ -24,7 +26,7 @@ fun CommunicationInternetScreen() { CommunicationInternetClientScreen() } composable("server") { - CommunicationInternetServerScreen() + CommunicationInternetServerScreen(activity) } } } 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 index 478b71f..d148633 100644 --- 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 @@ -1,5 +1,7 @@ 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 @@ -13,26 +15,27 @@ 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.bwf.BwfManager -import com.faraphel.tasks_valider.connectivity.room.RoomServer +import androidx.room.Room +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.tasks.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen @Composable -fun CommunicationInternetServerScreen() { - val server = remember { mutableStateOf(null)} +fun CommunicationInternetServerScreen(activity: Activity) { + val server = remember { mutableStateOf(null)} // if the server is not created, prompt the user for the server configuration - if (server.value == null) CommunicationInternetServerContent(server) + if (server.value == null) CommunicationInternetServerContent(activity, server) // else, go to the base tasks screen else TaskGroupScreen() } @Composable -fun CommunicationInternetServerContent(server: MutableState) { +fun CommunicationInternetServerContent(activity: Activity, server: MutableState) { val expandedStudentList = remember { mutableStateOf(false) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } @@ -69,9 +72,20 @@ fun CommunicationInternetServerContent(server: MutableState) { ) Button(onClick = { - server.value = RoomServer(serverPort.intValue) + // 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") + server.value = TaskServer(serverPort.intValue, database) server.value!!.start() - // TODO(Faraphel): go to the base tasks screen }) { 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 index 1a92119..bb260ce 100644 --- 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 @@ -35,11 +35,11 @@ fun CommunicationScreen(activity: Activity) { CommunicationSelectContent(controller, activity) } composable("internet") { - CommunicationInternetScreen() + CommunicationInternetScreen(activity) } composable("wifi-p2p") { val bwfManager = BwfManager.fromActivity(activity) - CommunicationWifiP2pScreen(bwfManager) + CommunicationWifiP2pScreen(activity, bwfManager) } } } 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 index 9e9e310..52e4b34 100644 --- 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 @@ -8,7 +8,7 @@ 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.screen.tasks.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget @@ -23,6 +23,7 @@ fun CommunicationWifiP2pClientScreen(bwfManager: BwfManager) { return } + // if the device is selected but not connected, try to connect if (selectedDevice.value != null) { // TODO(Faraphel): error handling 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 index d9ea6a7..d0ee3f3 100644 --- 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 @@ -1,5 +1,6 @@ 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 @@ -9,14 +10,12 @@ 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.internet.server.CommunicationInternetServerScreen 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(bwfManager: BwfManager) { +fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) { val controller = rememberNavController() NavHost(navController = controller, startDestination = "mode") { @@ -27,7 +26,7 @@ fun CommunicationWifiP2pScreen(bwfManager: BwfManager) { CommunicationWifiP2pClientScreen(bwfManager) } composable("server") { - CommunicationWifiP2pServerScreen(bwfManager) + CommunicationWifiP2pServerScreen(activity, bwfManager) } } } 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 index eadecdd..11125c2 100644 --- 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 @@ -1,5 +1,6 @@ package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server +import android.app.Activity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button @@ -13,19 +14,21 @@ 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.room.RoomServer +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.tasks.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen @Composable -fun CommunicationWifiP2pServerScreen(bwfManager: BwfManager) { - val server = remember { mutableStateOf(null)} +fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) { + val server = remember { mutableStateOf(null)} // if the server is not created, prompt the user for the server configuration - if (server.value == null) CommunicationWifiP2pServerContent(bwfManager, server) + if (server.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, server) // else, go to the base tasks screen else TaskGroupScreen() } @@ -33,8 +36,9 @@ fun CommunicationWifiP2pServerScreen(bwfManager: BwfManager) { @Composable fun CommunicationWifiP2pServerContent( + activity: Activity, bwfManager: BwfManager, - server: MutableState + server: MutableState ) { val expandedStudentList = remember { mutableStateOf(false) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } @@ -72,8 +76,21 @@ fun CommunicationWifiP2pServerContent( ) 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 { - server.value = RoomServer(serverPort.intValue) + // Create the server + server.value = TaskServer(serverPort.intValue, database) server.value!!.start() } }) { diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/screen.kt similarity index 85% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/screen.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/screen.kt index f9b4e26..a8fbfc5 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/tasks/screen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/task/screen.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.screen.tasks +package com.faraphel.tasks_valider.ui.screen.task import androidx.compose.material3.Text import androidx.compose.runtime.Composable diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt similarity index 88% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Group.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt index 79e5c85..95520a7 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Group.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets.tasks +package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Task.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt similarity index 86% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Task.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt index 67b182f..fc604fb 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/Task.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets.tasks +package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt similarity index 90% rename from app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/TaskGroup.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt index 99b3fd0..75e0353 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/tasks/TaskGroup.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.ui.widgets.tasks +package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -11,12 +11,12 @@ 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.TaskDatabase import com.faraphel.tasks_valider.database.entities.TaskGroup @Composable -fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) { +fun WidgetTaskStudent(database: TaskDatabase, taskStudent: TaskGroup) { val teacherDao = database.teacherDao() Column { From 8c11e18e0d3c2008c271042e65b6a08a3c53a6ee Mon Sep 17 00:00:00 2001 From: Faraphel Date: Wed, 8 May 2024 00:10:04 +0200 Subject: [PATCH 15/24] [WIP] implemented API class to connect the database and the HTTP server --- .idea/deploymentTargetDropDown.xml | 12 --- .../connectivity/task/TaskServer.kt | 32 +++----- .../tasks_valider/database/TaskDatabase.kt | 41 +++------- .../tasks_valider/database/api/GroupApi.kt | 10 +++ .../database/api/GroupStudentApi.kt | 10 +++ .../tasks_valider/database/api/StudentApi.kt | 10 +++ .../tasks_valider/database/api/TaskApi.kt | 10 +++ .../database/api/TaskGroupApi.kt | 10 +++ .../tasks_valider/database/api/TeacherApi.kt | 10 +++ .../database/api/base/BaseApi.kt | 33 ++++++++ .../database/api/base/BaseJsonApi.kt | 79 +++++++++++++++++++ .../tasks_valider/database/dao/GroupDao.kt | 11 ++- .../database/dao/GroupStudentDao.kt | 11 +-- .../tasks_valider/database/dao/StudentDao.kt | 13 ++- .../tasks_valider/database/dao/TaskDao.kt | 11 ++- .../database/dao/TaskGroupDao.kt | 8 +- .../tasks_valider/database/dao/TeacherDao.kt | 10 +-- .../database/dao/base/BaseDao.kt | 30 ++++++- .../entities/{Group.kt => GroupEntity.kt} | 5 +- ...{GroupStudent.kt => GroupStudentEntity.kt} | 11 ++- .../entities/{Person.kt => PersonEntity.kt} | 5 +- .../entities/{Student.kt => StudentEntity.kt} | 4 +- .../entities/{Task.kt => TaskEntity.kt} | 5 +- .../{TaskGroup.kt => TaskGroupEntity.kt} | 12 +-- .../entities/{Teacher.kt => TeacherEntity.kt} | 4 +- .../database/entities/base/BaseEntity.kt | 3 + .../tasks_valider/ui/widgets/task/Group.kt | 4 +- .../tasks_valider/ui/widgets/task/Task.kt | 4 +- .../ui/widgets/task/TaskGroup.kt | 4 +- 29 files changed, 280 insertions(+), 132 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{Group.kt => GroupEntity.kt} (73%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{GroupStudent.kt => GroupStudentEntity.kt} (76%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{Person.kt => PersonEntity.kt} (75%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{Student.kt => StudentEntity.kt} (85%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{Task.kt => TaskEntity.kt} (76%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{TaskGroup.kt => TaskGroupEntity.kt} (81%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{Teacher.kt => TeacherEntity.kt} (85%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/base/BaseEntity.kt diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index f67f999..ad3ac93 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -4,18 +4,6 @@ - - - - - - - - - - - - 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 index 211ba61..391e8e0 100644 --- 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 @@ -1,8 +1,6 @@ package com.faraphel.tasks_valider.connectivity.task -import android.util.Log import com.faraphel.tasks_valider.database.TaskDatabase -import com.faraphel.tasks_valider.database.entities.Task import com.google.gson.Gson import fi.iki.elonen.NanoHTTPD @@ -24,31 +22,19 @@ class TaskServer( // remove the first slash val daoName: String = uri.substring(1) + // get the matching DAO in the database + + TODO("Faraphel: get the correct DAO from the database") + TODO("Faraphel: pass more direct arguments to the API handler ?") // handle the request when (method) { - // get the data from the database - Method.GET -> { - return newFixedLengthResponse( - Response.Status.OK, - "application/json", - jsonParser.toJson( database.taskDao().getAll() ) - ) - } + // check if the data is in the database + Method.HEAD -> { TODO() } + // get the data from the dao + Method.GET -> { TODO() } // insert the data into the database - Method.POST -> { - val task = jsonParser.fromJson( - session.inputStream.bufferedReader(), - Task::class.java - ) - database.taskDao().insert(task) - - return newFixedLengthResponse( - Response.Status.CREATED, - "application/json", - jsonParser.toJson(task) - ) - } + Method.POST -> { TODO() } // other methods are not allowed else -> { return newFixedLengthResponse( 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 index 718b6d1..2fa80a1 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -10,13 +10,12 @@ 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.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 -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 com.faraphel.tasks_valider.database.entities.GroupEntity +import com.faraphel.tasks_valider.database.entities.GroupStudentEntity +import com.faraphel.tasks_valider.database.entities.StudentEntity +import com.faraphel.tasks_valider.database.entities.TaskEntity +import com.faraphel.tasks_valider.database.entities.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities.TeacherEntity /** @@ -25,13 +24,13 @@ import com.faraphel.tasks_valider.database.entities.Teacher */ @Database( entities = [ - Group::class, - Student::class, - Teacher::class, - Task::class, + GroupEntity::class, + StudentEntity::class, + TeacherEntity::class, + TaskEntity::class, - GroupStudent::class, - TaskGroup::class, + GroupStudentEntity::class, + TaskGroupEntity::class, ], version = 1 ) @@ -48,20 +47,4 @@ abstract class TaskDatabase : RoomDatabase() { // relations abstract fun groupStudentDao(): GroupStudentDao abstract fun taskGroupDao(): TaskGroupDao - - /** - * Get the DAO from the name of the dao. - */ - @Suppress("UNCHECKED_CAST") - fun daoFromName(name: String): BaseDao? { - return when (name) { - "group" -> groupDao() - "student" -> studentDao() - "teacher" -> teacherDao() - "task" -> taskDao() - "group_student" -> groupStudentDao() - "task_group" -> taskGroupDao() - else -> null - } as BaseDao? - } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt new file mode 100644 index 0000000..06f3668 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.database.api + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +import com.faraphel.tasks_valider.database.dao.GroupDao +import com.faraphel.tasks_valider.database.entities.GroupEntity + +class GroupApi : BaseJsonApi() { + override fun getDao(database: TaskDatabase): GroupDao = database.groupDao() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt new file mode 100644 index 0000000..d5760ad --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.database.api + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +import com.faraphel.tasks_valider.database.dao.GroupStudentDao +import com.faraphel.tasks_valider.database.entities.GroupStudentEntity + +class GroupStudentApi : BaseJsonApi() { + override fun getDao(database: TaskDatabase): GroupStudentDao = database.groupStudentDao() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt new file mode 100644 index 0000000..2e11f54 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.database.api + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +import com.faraphel.tasks_valider.database.dao.StudentDao +import com.faraphel.tasks_valider.database.entities.StudentEntity + +class StudentApi : BaseJsonApi() { + override fun getDao(database: TaskDatabase): StudentDao = database.studentDao() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt new file mode 100644 index 0000000..402d2de --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.database.api + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +import com.faraphel.tasks_valider.database.dao.TaskDao +import com.faraphel.tasks_valider.database.entities.TaskEntity + +class TaskApi : BaseJsonApi() { + override fun getDao(database: TaskDatabase): TaskDao = database.taskDao() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt new file mode 100644 index 0000000..f48f7d6 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.database.api + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +import com.faraphel.tasks_valider.database.dao.TaskGroupDao +import com.faraphel.tasks_valider.database.entities.TaskGroupEntity + +class TaskGroupApi : BaseJsonApi() { + override fun getDao(database: TaskDatabase): TaskGroupDao = database.taskGroupDao() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt new file mode 100644 index 0000000..7a7169a --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.database.api + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +import com.faraphel.tasks_valider.database.dao.TeacherDao +import com.faraphel.tasks_valider.database.entities.TeacherEntity + +class TeacherApi : BaseJsonApi() { + override fun getDao(database: TaskDatabase): TeacherDao = database.teacherDao() +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt new file mode 100644 index 0000000..e05e324 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt @@ -0,0 +1,33 @@ +package com.faraphel.tasks_valider.database.api.base + +import com.faraphel.tasks_valider.database.TaskDatabase +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(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + + /** + * Handle the GET request + * This is used to get data from the database + */ + fun get(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + + /** + * Handle the POST request + * This is used to insert data into the database + */ + fun post(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + + /** + * Handle the PUT request + * This is used to delete data from the database + */ + fun delete(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response +} \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt new file mode 100644 index 0000000..aea579f --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt @@ -0,0 +1,79 @@ +package com.faraphel.tasks_valider.database.api.base + +import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.dao.base.BaseDao +import com.faraphel.tasks_valider.database.entities.GroupEntity +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 : BaseApi { + companion object { + private val parser = Gson() ///< The JSON parser + } + + /** + * Get the DAO for the entity + * @param database the database to get the DAO from + * @return the DAO for the entity + */ + abstract fun getDao(database: TaskDatabase): BaseDao + + /** + * Get the type token for the entity + * @return the type token for the entity + */ + private fun getEntityTypeToken(): TypeToken = object: TypeToken() {} + + // Requests + + override fun head(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { + val obj = parser.fromJson( + session.inputStream.bufferedReader().readText(), + getEntityTypeToken().type + ) + val exists = this.getDao(database).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(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.OK, + "application/json", + parser.toJson(database.groupDao().getAll()) + ) + } + + override fun post(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { + val obj = parser.fromJson(session.inputStream.bufferedReader().readText(), GroupEntity::class.java) + val id = database.groupDao().insert(obj) + + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.CREATED, + "text/plain", + id.toString() + ) + } + + override fun delete(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { + val obj = parser.fromJson(session.inputStream.bufferedReader().readText(), GroupEntity::class.java) + val count = database.groupDao().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/GroupDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupDao.kt index 33511fd..e53764c 100644 --- 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 @@ -4,17 +4,16 @@ 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 +import com.faraphel.tasks_valider.database.entities.GroupEntity @Dao -interface GroupDao : BaseDao { +interface GroupDao : BaseDao { @Query("SELECT * FROM `groups`") - override fun getAll(): List + override fun getAll(): List @Query("SELECT * FROM `groups` WHERE id = :id") - fun getById(id: Long): Group + fun getById(id: Long): GroupEntity /** Allow to get all groups with a specific student @@ -25,5 +24,5 @@ interface GroupDao : BaseDao { "WHERE `group_student`.student_id = :studentId" ) @RewriteQueriesToDropUnusedColumns - fun filterByStudentId(studentId: Long): List + 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 index 2bd355e..6f86ea6 100644 --- 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 @@ -2,18 +2,15 @@ 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 +import com.faraphel.tasks_valider.database.entities.GroupStudentEntity @Dao -interface GroupStudentDao : BaseDao { +interface GroupStudentDao : BaseDao { @Query("SELECT * FROM `group_student`") - override fun getAll(): List + override fun getAll(): List @Query("SELECT * FROM `group_student` WHERE group_id = :groupId AND student_id = :studentId") - fun getById(groupId: Long, studentId: Long): GroupStudent + fun getById(groupId: Long, studentId: Long): GroupStudentEntity } 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 index e4b8dfb..d1451eb 100644 --- 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 @@ -1,22 +1,19 @@ 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 +import com.faraphel.tasks_valider.database.entities.StudentEntity @Dao -interface StudentDao : BaseDao { +interface StudentDao : BaseDao { @Query("SELECT * FROM `students`") - override fun getAll(): List + override fun getAll(): List @Query("SELECT * FROM `students` WHERE id = :id") - fun getById(id: Long): Student + fun getById(id: Long): StudentEntity /** @@ -28,5 +25,5 @@ interface StudentDao : BaseDao { "WHERE `group_student`.group_id = :groupId" ) @RewriteQueriesToDropUnusedColumns - fun filterByGroupId(groupId: Long): List + fun filterByGroupId(groupId: 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..33df321 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 @@ -4,17 +4,16 @@ 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 @Dao -interface TaskDao : BaseDao { +interface TaskDao : BaseDao { @Query("SELECT * FROM `tasks`") - override fun getAll(): List + override fun getAll(): List @Query("SELECT * FROM `tasks` WHERE id = :id") - fun getById(id: Long): Task + fun getById(id: Long): TaskEntity /** Get all the tasks for a specific group @@ -25,5 +24,5 @@ interface TaskDao : BaseDao { "WHERE `task_group`.group_id = :groupId" ) @RewriteQueriesToDropUnusedColumns - fun filterByGroupId(groupId: Long): List + fun filterByGroupId(groupId: 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 index 0caa673..8b2bea6 100644 --- 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 @@ -3,14 +3,14 @@ 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 +import com.faraphel.tasks_valider.database.entities.TaskGroupEntity @Dao -interface TaskGroupDao : BaseDao { +interface TaskGroupDao : BaseDao { @Query("SELECT * FROM `task_group`") - override fun getAll(): List + override fun getAll(): List @Query("SELECT * FROM `task_group` WHERE task_id = :taskId AND group_id = :groupId") - fun getById(taskId: Long, groupId: Long): TaskGroup + fun getById(taskId: Long, groupId: Long): TaskGroupEntity } 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 index f489725..3acba4d 100644 --- 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 @@ -3,16 +3,14 @@ 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 +import com.faraphel.tasks_valider.database.entities.TeacherEntity @Dao -interface TeacherDao : BaseDao { +interface TeacherDao : BaseDao { @Query("SELECT * FROM `teachers`") - override fun getAll(): List + override fun getAll(): List @Query("SELECT * FROM `teachers` WHERE id = :id") - fun getById(id: Long): Teacher + fun getById(id: Long): TeacherEntity } 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/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt similarity index 73% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/Group.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt index d0f3a09..d042662 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Group.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt @@ -3,10 +3,11 @@ 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 = "groups") -data class Group ( +data class GroupEntity ( @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo("name") val name: String? = null, -) +) : BaseEntity() 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/GroupStudentEntity.kt similarity index 76% 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/GroupStudentEntity.kt index 2e830e3..ba003c8 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/GroupStudentEntity.kt @@ -3,8 +3,7 @@ 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", @@ -14,20 +13,20 @@ import com.faraphel.tasks_valider.database.entities.Student ], foreignKeys = [ ForeignKey( - entity = Group::class, + entity = GroupEntity::class, parentColumns = ["id"], childColumns = ["group_id"], onDelete = ForeignKey.CASCADE ), ForeignKey( - entity = Student::class, + entity = StudentEntity::class, parentColumns = ["id"], childColumns = ["student_id"], onDelete = ForeignKey.CASCADE ) ] ) -data class GroupStudent( +data class GroupStudentEntity( @ColumnInfo("group_id", index = true) val groupId: Long, @ColumnInfo("student_id", index = true) val studentId: Long, -) +) : BaseEntity() 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/PersonEntity.kt similarity index 75% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/Person.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt index 347b05d..469c9d2 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Person.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt @@ -1,13 +1,14 @@ package com.faraphel.tasks_valider.database.entities import java.util.Locale +import com.faraphel.tasks_valider.database.entities.base.BaseEntity -open class Person ( +open class PersonEntity ( open val id: Long = 0, open val firstName: String, open val lastName: String, -) { +) : BaseEntity() { /** Get the full name of the person */ 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/StudentEntity.kt similarity index 85% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/Student.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt index 4cff4c6..fdfd648 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Student.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt @@ -5,8 +5,8 @@ import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "students") -class Student( +class StudentEntity( @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) +) : PersonEntity(id, firstName, lastName) 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/TaskEntity.kt similarity index 76% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/Task.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt index 15e1361..7d04402 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Task.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskEntity.kt @@ -3,10 +3,11 @@ 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 = "tasks") -data class Task ( +data class TaskEntity ( @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo("title") val title: String, @ColumnInfo("description") val description: String, -) +) : BaseEntity() 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/TaskGroupEntity.kt similarity index 81% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroup.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroupEntity.kt index 07e9c57..006a6a0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroup.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroupEntity.kt @@ -3,6 +3,7 @@ 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 @@ -14,29 +15,30 @@ import java.time.Instant ], foreignKeys = [ ForeignKey( - entity = Group::class, + entity = GroupEntity::class, parentColumns = ["id"], childColumns = ["group_id"], onDelete = ForeignKey.CASCADE ), ForeignKey( - entity = Task::class, + entity = TaskEntity::class, parentColumns = ["id"], childColumns = ["task_id"], onDelete = ForeignKey.CASCADE ), ForeignKey( - entity = Teacher::class, + entity = TeacherEntity::class, parentColumns = ["id"], childColumns = ["approval_teacher_id"], onDelete = ForeignKey.CASCADE ), ] ) -data class TaskGroup ( +data class TaskGroupEntity ( @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 -) +) : BaseEntity() + 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/TeacherEntity.kt similarity index 85% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/Teacher.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt index b499973..c244edc 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/Teacher.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt @@ -5,8 +5,8 @@ import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "teachers") -class Teacher( +class TeacherEntity( @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) +) : PersonEntity(id, firstName, lastName) 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/widgets/task/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt index 95520a7..fb35f98 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt @@ -3,10 +3,10 @@ package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import com.faraphel.tasks_valider.database.entities.Group +import com.faraphel.tasks_valider.database.entities.GroupEntity @Composable -fun WidgetGroup(group: Group) { +fun WidgetGroup(group: GroupEntity) { // TODO Column { Text(text = group.name!!) diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt index fc604fb..6ed1293 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt @@ -3,10 +3,10 @@ package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import com.faraphel.tasks_valider.database.entities.Task +import com.faraphel.tasks_valider.database.entities.TaskEntity @Composable -fun WidgetTask(task: Task) { +fun WidgetTask(task: TaskEntity) { // task information Column { Text(text = task.title) diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt index 75e0353..d14bb27 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt @@ -12,11 +12,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.faraphel.tasks_valider.database.TaskDatabase -import com.faraphel.tasks_valider.database.entities.TaskGroup +import com.faraphel.tasks_valider.database.entities.TaskGroupEntity @Composable -fun WidgetTaskStudent(database: TaskDatabase, taskStudent: TaskGroup) { +fun WidgetTaskStudent(database: TaskDatabase, taskStudent: TaskGroupEntity) { val teacherDao = database.teacherDao() Column { From b97fb71f8f907e367d7d8e9dc1b401737f4fb618 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Wed, 8 May 2024 21:32:10 +0200 Subject: [PATCH 16/24] [WIP] simplified and fixed the Api system in the database --- .idea/deploymentTargetDropDown.xml | 12 +++++ .idea/misc.xml | 3 +- .../connectivity/task/TaskServer.kt | 27 ++++++----- .../tasks_valider/database/TaskDatabase.kt | 24 +++++++++- .../tasks_valider/database/api/GroupApi.kt | 7 +-- .../database/api/GroupStudentApi.kt | 7 +-- .../tasks_valider/database/api/StudentApi.kt | 7 +-- .../tasks_valider/database/api/TaskApi.kt | 7 +-- .../database/api/TaskGroupApi.kt | 7 +-- .../tasks_valider/database/api/TeacherApi.kt | 7 +-- .../database/api/base/BaseApi.kt | 9 ++-- .../database/api/base/BaseJsonApi.kt | 45 ++++++++----------- 12 files changed, 87 insertions(+), 75 deletions(-) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index ad3ac93..45cc42a 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -4,6 +4,18 @@ + + + + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index cf5d4ff..daa0c6c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,9 +1,10 @@ + - + 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 index 391e8e0..0337103 100644 --- 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 @@ -1,6 +1,7 @@ package com.faraphel.tasks_valider.connectivity.task import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.base.BaseApi import com.google.gson.Gson import fi.iki.elonen.NanoHTTPD @@ -21,28 +22,30 @@ class TaskServer( val uri: String = session.uri // remove the first slash - val daoName: String = uri.substring(1) - // get the matching DAO in the database - - TODO("Faraphel: get the correct DAO from the database") - TODO("Faraphel: pass more direct arguments to the API handler ?") + val entityName: String = uri.substring(1) + // get the matching entity API in the database + val entityApi = database.getApiFromName(entityName) + ?: return newFixedLengthResponse( + Response.Status.NOT_FOUND, + "text/plain", + "Invalid entity name" + ) // handle the request - when (method) { + return when (method) { // check if the data is in the database - Method.HEAD -> { TODO() } + Method.HEAD -> entityApi.head(session) // get the data from the dao - Method.GET -> { TODO() } + Method.GET -> entityApi.get(session) // insert the data into the database - Method.POST -> { TODO() } + Method.POST -> entityApi.post(session) // other methods are not allowed - else -> { - return newFixedLengthResponse( + else -> + newFixedLengthResponse( Response.Status.METHOD_NOT_ALLOWED, "text/plain", "Method not allowed" ) - } // TODO(Faraphel): implement a permission system } } 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 index 2fa80a1..9b84639 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -3,6 +3,8 @@ package com.faraphel.tasks_valider.database import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import com.faraphel.tasks_valider.database.api.* +import com.faraphel.tasks_valider.database.api.base.BaseApi import com.faraphel.tasks_valider.database.converters.InstantConverter import com.faraphel.tasks_valider.database.dao.GroupDao import com.faraphel.tasks_valider.database.dao.GroupStudentDao @@ -39,12 +41,32 @@ import com.faraphel.tasks_valider.database.entities.TeacherEntity ) abstract class TaskDatabase : 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 + + // api + + private val api: Map = mapOf( + "group" to GroupApi(this.groupDao()), + "student" to StudentApi(this.studentDao()), + "teacher" to TeacherApi(this.teacherDao()), + "task" to TaskApi(this.taskDao()), + + "group_student" to GroupStudentApi(this.groupStudentDao()), + "task_group" to TaskGroupApi(this.taskGroupDao()), + ) + + /** + * get the api for an entity from its name. + * @param name the name of the entity + */ + fun getApiFromName(name: String): BaseApi? { + return this.api[name] + } } diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt index 06f3668..dfc2acd 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt @@ -1,10 +1,7 @@ package com.faraphel.tasks_valider.database.api -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.base.BaseJsonApi -import com.faraphel.tasks_valider.database.dao.GroupDao +import com.faraphel.tasks_valider.database.dao.base.BaseDao import com.faraphel.tasks_valider.database.entities.GroupEntity -class GroupApi : BaseJsonApi() { - override fun getDao(database: TaskDatabase): GroupDao = database.groupDao() -} +class GroupApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt index d5760ad..7fdfddd 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt @@ -1,10 +1,7 @@ package com.faraphel.tasks_valider.database.api -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.base.BaseJsonApi -import com.faraphel.tasks_valider.database.dao.GroupStudentDao +import com.faraphel.tasks_valider.database.dao.base.BaseDao import com.faraphel.tasks_valider.database.entities.GroupStudentEntity -class GroupStudentApi : BaseJsonApi() { - override fun getDao(database: TaskDatabase): GroupStudentDao = database.groupStudentDao() -} +class GroupStudentApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt index 2e11f54..8f1f67b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt @@ -1,10 +1,7 @@ package com.faraphel.tasks_valider.database.api -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.base.BaseJsonApi -import com.faraphel.tasks_valider.database.dao.StudentDao +import com.faraphel.tasks_valider.database.dao.base.BaseDao import com.faraphel.tasks_valider.database.entities.StudentEntity -class StudentApi : BaseJsonApi() { - override fun getDao(database: TaskDatabase): StudentDao = database.studentDao() -} +class StudentApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt index 402d2de..1385d1e 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt @@ -1,10 +1,7 @@ package com.faraphel.tasks_valider.database.api -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.base.BaseJsonApi -import com.faraphel.tasks_valider.database.dao.TaskDao +import com.faraphel.tasks_valider.database.dao.base.BaseDao import com.faraphel.tasks_valider.database.entities.TaskEntity -class TaskApi : BaseJsonApi() { - override fun getDao(database: TaskDatabase): TaskDao = database.taskDao() -} +class TaskApi(dao: BaseDao) : BaseJsonApi(dao) \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt index f48f7d6..eeb52f3 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt @@ -1,10 +1,7 @@ package com.faraphel.tasks_valider.database.api -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.base.BaseJsonApi -import com.faraphel.tasks_valider.database.dao.TaskGroupDao +import com.faraphel.tasks_valider.database.dao.base.BaseDao import com.faraphel.tasks_valider.database.entities.TaskGroupEntity -class TaskGroupApi : BaseJsonApi() { - override fun getDao(database: TaskDatabase): TaskGroupDao = database.taskGroupDao() -} +class TaskGroupApi(dao: BaseDao) : BaseJsonApi(dao) \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt index 7a7169a..37cf1c9 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt @@ -1,10 +1,7 @@ package com.faraphel.tasks_valider.database.api -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.api.base.BaseJsonApi -import com.faraphel.tasks_valider.database.dao.TeacherDao +import com.faraphel.tasks_valider.database.dao.base.BaseDao import com.faraphel.tasks_valider.database.entities.TeacherEntity -class TeacherApi : BaseJsonApi() { - override fun getDao(database: TaskDatabase): TeacherDao = database.teacherDao() -} +class TeacherApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt index e05e324..7f338b3 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt @@ -1,6 +1,5 @@ package com.faraphel.tasks_valider.database.api.base -import com.faraphel.tasks_valider.database.TaskDatabase import fi.iki.elonen.NanoHTTPD /** @@ -11,23 +10,23 @@ interface BaseApi { * Handle the HEAD request * This is used to check if a data exists in the database */ - fun head(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response /** * Handle the GET request * This is used to get data from the database */ - fun get(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response /** * Handle the POST request * This is used to insert data into the database */ - fun post(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response /** * Handle the PUT request * This is used to delete data from the database */ - fun delete(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response + 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/base/BaseJsonApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt index aea579f..f947220 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt @@ -1,8 +1,6 @@ package com.faraphel.tasks_valider.database.api.base -import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.dao.base.BaseDao -import com.faraphel.tasks_valider.database.entities.GroupEntity import com.faraphel.tasks_valider.database.entities.base.BaseEntity import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -13,32 +11,21 @@ import fi.iki.elonen.NanoHTTPD * This is preconfigured to handle JSON data. * @param Entity the entity type to handle */ -abstract class BaseJsonApi : BaseApi { +abstract class BaseJsonApi(private val dao: BaseDao) : BaseApi { companion object { private val parser = Gson() ///< The JSON parser } - /** - * Get the DAO for the entity - * @param database the database to get the DAO from - * @return the DAO for the entity - */ - abstract fun getDao(database: TaskDatabase): BaseDao - - /** - * Get the type token for the entity - * @return the type token for the entity - */ - private fun getEntityTypeToken(): TypeToken = object: TypeToken() {} + private val entityTypeToken: TypeToken = object: TypeToken() {} ///< the type of the managed entity // Requests - override fun head(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { + override fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { val obj = parser.fromJson( session.inputStream.bufferedReader().readText(), - getEntityTypeToken().type + this.entityTypeToken.type ) - val exists = this.getDao(database).exists(obj) + val exists = this.dao.exists(obj) return NanoHTTPD.newFixedLengthResponse( if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND, @@ -47,17 +34,20 @@ abstract class BaseJsonApi : BaseApi { ) } - override fun get(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { + override fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.OK, "application/json", - parser.toJson(database.groupDao().getAll()) + parser.toJson(this.dao.getAll()) ) } - override fun post(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { - val obj = parser.fromJson(session.inputStream.bufferedReader().readText(), GroupEntity::class.java) - val id = database.groupDao().insert(obj) + 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, @@ -66,9 +56,12 @@ abstract class BaseJsonApi : BaseApi { ) } - override fun delete(database: TaskDatabase, session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response { - val obj = parser.fromJson(session.inputStream.bufferedReader().readText(), GroupEntity::class.java) - val count = database.groupDao().delete(obj) + 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, From a9a02bcde7815f902f19f2ee47ac76fba8a70eea Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 9 May 2024 21:45:10 +0200 Subject: [PATCH 17/24] [WIP] implemented very basic session, permission and authentication system --- .../connectivity/task/TaskServer.kt | 135 ++++++++++++++---- .../task/session/TaskPermission.kt | 12 ++ .../connectivity/task/session/TaskSession.kt | 10 ++ .../tasks_valider/database/TaskDatabase.kt | 76 +++++++++- 4 files changed, 205 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt 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 index 0337103..f731c92 100644 --- 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 @@ -1,9 +1,11 @@ package com.faraphel.tasks_valider.connectivity.task +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.base.BaseApi import com.google.gson.Gson import fi.iki.elonen.NanoHTTPD +import java.util.* /** @@ -15,39 +17,124 @@ class TaskServer( private val port: Int, private val database: TaskDatabase ) : NanoHTTPD(port) { - private val jsonParser = Gson() - override fun serve(session: IHTTPSession): Response { - val method: Method = session.method - val uri: String = session.uri + // Session - // remove the first slash - val entityName: String = uri.substring(1) - // get the matching entity API in the database - val entityApi = database.getApiFromName(entityName) + // TODO(Faraphel): theses functions should be moved somewhere else for specific session management + + private val sessions = mutableMapOf() ///< sessions specific data + + /** + * Create a new session + * @param session the data for the session (optional) + * @return a new session identifiant + */ + private fun newSessionData(session: TaskSession = TaskSession()): String { + val sessionId = UUID.randomUUID().toString() + this.sessions[sessionId] = session + return sessionId + } + + /** + * Get data from a http session + * @param httpSession the HTTP session + * @return the session data + */ + private fun getSessionData(httpSession: IHTTPSession): TaskSession? { + val sessionId = httpSession.cookies.read("sessionId") ?: return null + val sessionData = this.getSessionData(sessionId) + return sessionData + } + + /** + * Get data from a session identifiant + * @param sessionId the identifiant of the session + * @return the session data + */ + private fun getSessionData(sessionId: String): TaskSession? { + return this.sessions[sessionId] + } + + /** + * Get data from a http session. If it does not exist, create it. + * @param httpSession the HTTP session + */ + private fun getOrCreateSessionData(httpSession: 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 + */ + private fun responseSetSessionData(response: Response, cookies: CookieHandler): Response { + cookies.set(Cookie("sessionId", this.newSessionData())) + cookies.unloadQueue(response) + return response + } + + // HTTP + + override fun serve(httpSession: IHTTPSession): Response { + // get the session data of the client + val taskSession = this.getOrCreateSessionData(httpSession) + + // get the method used + val method: Method = httpSession.method + + // parse the url + val uri: String = httpSession.uri.substring(1) // remove the first slash + val uriComponents = uri.split("/") + + // get the type of the request from the uri + val requestType = uriComponents.getOrNull(0) ?: return newFixedLengthResponse( - Response.Status.NOT_FOUND, + NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "Invalid entity name" + "Invalid request type" ) - // handle the request - return when (method) { - // check if the data is in the database - Method.HEAD -> entityApi.head(session) - // get the data from the dao - Method.GET -> entityApi.get(session) - // insert the data into the database - Method.POST -> entityApi.post(session) - // other methods are not allowed - else -> + // get the response from the correct part of the application + val response = when (requestType) { + // session requests + "sessions" -> { + TODO("Faraphel: handle admin changing permission") + } + + // entities requests + "entities" -> { + val entityName = uriComponents.getOrNull(1) + ?: return newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Invalid entity name" + ) + + this.database.handleApiRequest(taskSession, httpSession, method, entityName) + } + // invalid requests + else -> { newFixedLengthResponse( - Response.Status.METHOD_NOT_ALLOWED, + NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "Method not allowed" + "Invalid request type" ) - // TODO(Faraphel): implement a permission system + } } + + // wrap additional information in the response + return this.responseSetSessionData(response, httpSession.cookies) } /** 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..897591e --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt @@ -0,0 +1,12 @@ +package com.faraphel.tasks_valider.connectivity.task.session + + +/** + * A permission level that can be used for in the task system + */ +enum class TaskPermission { + NONE, + STUDENT, + TEACHER, + ADMIN, +} 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..5b99518 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt @@ -0,0 +1,10 @@ +package com.faraphel.tasks_valider.connectivity.task.session + + +/** + * store the data of a session in the task system + * @param permission the permission accorded to the session + */ +data class TaskSession( + var permission: TaskPermission = TaskPermission.STUDENT +) 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 index 9b84639..e8d8515 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -3,6 +3,8 @@ package com.faraphel.tasks_valider.database import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission +import com.faraphel.tasks_valider.connectivity.task.session.TaskSession import com.faraphel.tasks_valider.database.api.* import com.faraphel.tasks_valider.database.api.base.BaseApi import com.faraphel.tasks_valider.database.converters.InstantConverter @@ -18,6 +20,7 @@ import com.faraphel.tasks_valider.database.entities.StudentEntity import com.faraphel.tasks_valider.database.entities.TaskEntity import com.faraphel.tasks_valider.database.entities.TaskGroupEntity import com.faraphel.tasks_valider.database.entities.TeacherEntity +import fi.iki.elonen.NanoHTTPD /** @@ -63,10 +66,75 @@ abstract class TaskDatabase : RoomDatabase() { ) /** - * get the api for an entity from its name. - * @param name the name of the entity + * dispatch an API request */ - fun getApiFromName(name: String): BaseApi? { - return this.api[name] + fun handleApiRequest( + taskSession: TaskSession, + httpSession: NanoHTTPD.IHTTPSession, + method: NanoHTTPD.Method, + entityName: String + ): NanoHTTPD.Response { + // get the correspond Api object for this entity + val entityApi = this.api[entityName] + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.NOT_FOUND, + "text/plain", + "Invalid entity name" + ) + + // dispatch the request to the correct entity API + return when (method) { + // check if the data is in the database + NanoHTTPD.Method.HEAD -> { + if (taskSession.permission < TaskPermission.STUDENT) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.FORBIDDEN, + "text/plain", + "Forbidden" + ) + + return entityApi.head(httpSession) + } + // get the data from the database + NanoHTTPD.Method.GET -> { + if (taskSession.permission < TaskPermission.STUDENT) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.FORBIDDEN, + "text/plain", + "Forbidden" + ) + + return entityApi.get(httpSession) + } + // insert the data into the database + NanoHTTPD.Method.POST -> { + if (taskSession.permission < TaskPermission.TEACHER) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.FORBIDDEN, + "text/plain", + "Forbidden" + ) + + return entityApi.post(httpSession) + } + // delete the data from the database + NanoHTTPD.Method.DELETE -> { + if (taskSession.permission < TaskPermission.TEACHER) + 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" + ) + } } } From 6b4b420a225d42f6e8704bddfd1328bf9e694508 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 10 May 2024 09:44:49 +0200 Subject: [PATCH 18/24] [WIP] added role system and a basic API for the session management --- .../connectivity/task/TaskServer.kt | 94 ++--------- .../task/session/TaskPermission.kt | 11 +- .../connectivity/task/session/TaskRole.kt | 31 ++++ .../connectivity/task/session/TaskSession.kt | 4 +- .../task/session/TaskSessionManager.kt | 147 ++++++++++++++++++ .../tasks_valider/database/TaskDatabase.kt | 15 +- 6 files changed, 204 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt 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 index f731c92..b92fba3 100644 --- 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 @@ -1,11 +1,8 @@ package com.faraphel.tasks_valider.connectivity.task -import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission -import com.faraphel.tasks_valider.connectivity.task.session.TaskSession +import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager import com.faraphel.tasks_valider.database.TaskDatabase -import com.google.gson.Gson import fi.iki.elonen.NanoHTTPD -import java.util.* /** @@ -17,78 +14,11 @@ class TaskServer( private val port: Int, private val database: TaskDatabase ) : NanoHTTPD(port) { - - // Session - - // TODO(Faraphel): theses functions should be moved somewhere else for specific session management - - private val sessions = mutableMapOf() ///< sessions specific data - - /** - * Create a new session - * @param session the data for the session (optional) - * @return a new session identifiant - */ - private fun newSessionData(session: TaskSession = TaskSession()): String { - val sessionId = UUID.randomUUID().toString() - this.sessions[sessionId] = session - return sessionId - } - - /** - * Get data from a http session - * @param httpSession the HTTP session - * @return the session data - */ - private fun getSessionData(httpSession: IHTTPSession): TaskSession? { - val sessionId = httpSession.cookies.read("sessionId") ?: return null - val sessionData = this.getSessionData(sessionId) - return sessionData - } - - /** - * Get data from a session identifiant - * @param sessionId the identifiant of the session - * @return the session data - */ - private fun getSessionData(sessionId: String): TaskSession? { - return this.sessions[sessionId] - } - - /** - * Get data from a http session. If it does not exist, create it. - * @param httpSession the HTTP session - */ - private fun getOrCreateSessionData(httpSession: 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 - */ - private fun responseSetSessionData(response: Response, cookies: CookieHandler): Response { - cookies.set(Cookie("sessionId", this.newSessionData())) - cookies.unloadQueue(response) - return response - } - - // HTTP + private val sessionManager = TaskSessionManager() ///< the session manager override fun serve(httpSession: IHTTPSession): Response { // get the session data of the client - val taskSession = this.getOrCreateSessionData(httpSession) + val taskSession = this.sessionManager.getOrCreateSessionData(httpSession) // get the method used val method: Method = httpSession.method @@ -108,9 +38,8 @@ class TaskServer( // get the response from the correct part of the application val response = when (requestType) { // session requests - "sessions" -> { - TODO("Faraphel: handle admin changing permission") - } + "sessions" -> + this.sessionManager.handleApiRequest(taskSession, httpSession, method) // entities requests "entities" -> { @@ -121,26 +50,25 @@ class TaskServer( "Invalid entity name" ) - this.database.handleApiRequest(taskSession, httpSession, method, entityName) + // TODO(Faraphel): the uri arguments should be handled in the handleApiRequest + return this.database.handleApiRequest(taskSession, httpSession, method, entityName) } + // invalid requests - else -> { + else -> newFixedLengthResponse( NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", "Invalid request type" ) - } } // wrap additional information in the response - return this.responseSetSessionData(response, httpSession.cookies) + return this.sessionManager.responseSetSessionData(response, httpSession.cookies) } /** * Start the server with the default configuration */ - override fun start() { - super.start(SOCKET_READ_TIMEOUT, false) - } + override fun start() = super.start(SOCKET_READ_TIMEOUT, false) } 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 index 897591e..74d1809 100644 --- 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 @@ -1,12 +1,7 @@ package com.faraphel.tasks_valider.connectivity.task.session - -/** - * A permission level that can be used for in the task system - */ enum class TaskPermission { - NONE, - STUDENT, - TEACHER, - ADMIN, + 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 index 5b99518..8956377 100644 --- 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 @@ -3,8 +3,8 @@ package com.faraphel.tasks_valider.connectivity.task.session /** * store the data of a session in the task system - * @param permission the permission accorded to the session + * @param role the role accorded to the session */ data class TaskSession( - var permission: TaskPermission = TaskPermission.STUDENT + 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..87a7a5d --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt @@ -0,0 +1,147 @@ +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 jsonParser = Gson() ///< the json parser + private val jsonTypeToken = object : TypeToken>(){} ///< the json type + 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 identifiant + */ + 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 identifiant + * @param sessionId the identifiant of the session + * @return the session data + */ + fun getSessionData(sessionId: String): TaskSession? { + return this.sessions[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 { + cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData())) + cookies.unloadQueue(response) + return response + } + + /** + * Handle a HTTP Api request + * @param taskSession the data of the client session + * @param httpSession the data of the http session + * @param method the method used + */ + fun handleApiRequest( + taskSession: TaskSession, + httpSession: NanoHTTPD.IHTTPSession, + method: NanoHTTPD.Method + ): NanoHTTPD.Response { + when (method) { + // get a client session data + NanoHTTPD.Method.GET -> { + TODO("Faraphel: implement get") + } + // change a 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", + "Forbidden" + ) + + // parse the content of the request + val data: Map = jsonParser.fromJson( + httpSession.inputStream.bufferedReader().readText(), + jsonTypeToken.type + ) + + // update the role + data["role"]?.let { role -> + try { + taskSession.role = TaskRole.valueOf(role) + } catch (exception: IllegalArgumentException) { + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Invalid role" + ) + } + // NOTE(Faraphel): does a modification on the object require updating the array ? + } + + // success message + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.OK, + "text/plain", + "Session updated" + ) + } + + // other action are limited + 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/TaskDatabase.kt b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt index e8d8515..17bd5bb 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -4,6 +4,7 @@ import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters 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.database.api.* import com.faraphel.tasks_valider.database.api.base.BaseApi @@ -83,10 +84,11 @@ abstract class TaskDatabase : RoomDatabase() { ) // dispatch the request to the correct entity API - return when (method) { + when (method) { // check if the data is in the database NanoHTTPD.Method.HEAD -> { - if (taskSession.permission < TaskPermission.STUDENT) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.READ)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -97,7 +99,8 @@ abstract class TaskDatabase : RoomDatabase() { } // get the data from the database NanoHTTPD.Method.GET -> { - if (taskSession.permission < TaskPermission.STUDENT) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.READ)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -108,7 +111,8 @@ abstract class TaskDatabase : RoomDatabase() { } // insert the data into the database NanoHTTPD.Method.POST -> { - if (taskSession.permission < TaskPermission.TEACHER) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.WRITE)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -119,7 +123,8 @@ abstract class TaskDatabase : RoomDatabase() { } // delete the data from the database NanoHTTPD.Method.DELETE -> { - if (taskSession.permission < TaskPermission.TEACHER) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.WRITE)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", From d92d57c9e960c9abe35a569398224ce8fce64a9a Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 10 May 2024 13:41:51 +0200 Subject: [PATCH 19/24] [WIP] added api read session data --- .../connectivity/task/session/TaskSession.kt | 3 +++ .../task/session/TaskSessionManager.kt | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) 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 index 8956377..84c4c37 100644 --- 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 @@ -1,10 +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 index 87a7a5d..10be9ed 100644 --- 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 @@ -94,7 +94,20 @@ class TaskSessionManager { when (method) { // get a client session data NanoHTTPD.Method.GET -> { - TODO("Faraphel: implement 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) + ) } // change a client session data NanoHTTPD.Method.POST -> { From c9334c543b7b38ce396fe955a1101b38db235100 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 10 May 2024 17:37:53 +0200 Subject: [PATCH 20/24] [WIP] separated API function and added API for specific session management --- .../connectivity/task/TaskServer.kt | 37 ++--- .../task/api/TaskSessionManagerApi.kt | 139 ++++++++++++++++++ .../task/session/TaskSessionManager.kt | 106 +++---------- .../tasks_valider/database/TaskDatabase.kt | 96 +----------- .../database/api/TaskDatabaseApi.kt | 108 ++++++++++++++ .../database/api/{ => entities}/GroupApi.kt | 4 +- .../api/{ => entities}/GroupStudentApi.kt | 4 +- .../database/api/{ => entities}/StudentApi.kt | 4 +- .../database/api/{ => entities}/TaskApi.kt | 4 +- .../api/{ => entities}/TaskGroupApi.kt | 4 +- .../database/api/{ => entities}/TeacherApi.kt | 4 +- .../api/{ => entities}/base/BaseApi.kt | 2 +- .../api/{ => entities}/base/BaseJsonApi.kt | 2 +- 13 files changed, 301 insertions(+), 213 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/GroupApi.kt (60%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/GroupStudentApi.kt (63%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/StudentApi.kt (61%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/TaskApi.kt (60%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/TaskGroupApi.kt (62%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/TeacherApi.kt (61%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/base/BaseApi.kt (92%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/{ => entities}/base/BaseJsonApi.kt (97%) 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 index b92fba3..fb17dd3 100644 --- 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 @@ -1,7 +1,9 @@ package com.faraphel.tasks_valider.connectivity.task +import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.api.TaskDatabaseApi import fi.iki.elonen.NanoHTTPD @@ -16,50 +18,41 @@ class TaskServer( ) : NanoHTTPD(port) { private val sessionManager = TaskSessionManager() ///< the session manager + private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager) ///< the api of the session manager + private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database + + /** + * 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) - // get the method used - val method: Method = httpSession.method - // parse the url val uri: String = httpSession.uri.substring(1) // remove the first slash - val uriComponents = uri.split("/") + val path = uri.split("/").toMutableList() // get the type of the request from the uri - val requestType = uriComponents.getOrNull(0) + val requestType = path.removeFirstOrNull() ?: return newFixedLengthResponse( NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "Invalid request type" + "Missing request type" ) // get the response from the correct part of the application val response = when (requestType) { // session requests - "sessions" -> - this.sessionManager.handleApiRequest(taskSession, httpSession, method) - + "sessions" -> this.sessionManagerApi.handleRequest(taskSession, httpSession, path) // entities requests - "entities" -> { - val entityName = uriComponents.getOrNull(1) - ?: return newFixedLengthResponse( - NanoHTTPD.Response.Status.BAD_REQUEST, - "text/plain", - "Invalid entity name" - ) - - // TODO(Faraphel): the uri arguments should be handled in the handleApiRequest - return this.database.handleApiRequest(taskSession, httpSession, method, entityName) - } - + "entities" -> return this.databaseApi.handleRequest(taskSession, httpSession, path) // invalid requests else -> newFixedLengthResponse( NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "Invalid request type" + "Unknown request type" ) } 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/TaskSessionManager.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt index 10be9ed..e0552c6 100644 --- 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 @@ -10,15 +10,13 @@ import java.util.* * The manager for the session system */ class TaskSessionManager { - private val jsonParser = Gson() ///< the json parser - private val jsonTypeToken = object : TypeToken>(){} ///< the json type 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 identifiant + * @return a new session identifier */ fun newSessionData( session: TaskSession = TaskSession(), @@ -40,14 +38,31 @@ class TaskSessionManager { } /** - * Get data from a session identifiant - * @param sessionId the identifiant of the session + * 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 @@ -75,86 +90,11 @@ class TaskSessionManager { 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 } - - /** - * Handle a HTTP Api request - * @param taskSession the data of the client session - * @param httpSession the data of the http session - * @param method the method used - */ - fun handleApiRequest( - taskSession: TaskSession, - httpSession: NanoHTTPD.IHTTPSession, - method: NanoHTTPD.Method - ): NanoHTTPD.Response { - when (method) { - // get a client 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) - ) - } - // change a 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", - "Forbidden" - ) - - // parse the content of the request - val data: Map = jsonParser.fromJson( - httpSession.inputStream.bufferedReader().readText(), - jsonTypeToken.type - ) - - // update the role - data["role"]?.let { role -> - try { - taskSession.role = TaskRole.valueOf(role) - } catch (exception: IllegalArgumentException) { - return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.BAD_REQUEST, - "text/plain", - "Invalid role" - ) - } - // NOTE(Faraphel): does a modification on the object require updating the array ? - } - - // success message - return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.OK, - "text/plain", - "Session updated" - ) - } - - // other action are limited - 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/TaskDatabase.kt b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt index 17bd5bb..bd59fe0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -4,10 +4,9 @@ import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters 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.database.api.* -import com.faraphel.tasks_valider.database.api.base.BaseApi +import com.faraphel.tasks_valider.database.api.entities.base.BaseApi +import com.faraphel.tasks_valider.database.api.entities.* import com.faraphel.tasks_valider.database.converters.InstantConverter import com.faraphel.tasks_valider.database.dao.GroupDao import com.faraphel.tasks_valider.database.dao.GroupStudentDao @@ -44,8 +43,6 @@ import fi.iki.elonen.NanoHTTPD InstantConverter::class ) abstract class TaskDatabase : RoomDatabase() { - // entities - abstract fun groupDao(): GroupDao abstract fun studentDao(): StudentDao abstract fun teacherDao(): TeacherDao @@ -53,93 +50,4 @@ abstract class TaskDatabase : RoomDatabase() { abstract fun groupStudentDao(): GroupStudentDao abstract fun taskGroupDao(): TaskGroupDao - - // api - - private val api: Map = mapOf( - "group" to GroupApi(this.groupDao()), - "student" to StudentApi(this.studentDao()), - "teacher" to TeacherApi(this.teacherDao()), - "task" to TaskApi(this.taskDao()), - - "group_student" to GroupStudentApi(this.groupStudentDao()), - "task_group" to TaskGroupApi(this.taskGroupDao()), - ) - - /** - * dispatch an API request - */ - fun handleApiRequest( - taskSession: TaskSession, - httpSession: NanoHTTPD.IHTTPSession, - method: NanoHTTPD.Method, - entityName: String - ): NanoHTTPD.Response { - // get the correspond Api object for this entity - val entityApi = this.api[entityName] - ?: return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.NOT_FOUND, - "text/plain", - "Invalid entity name" - ) - - // dispatch the request to the correct entity API - when (method) { - // check if the data is in the database - 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" - ) - } - } } 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..d1e9133 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt @@ -0,0 +1,108 @@ +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 fi.iki.elonen.NanoHTTPD + +class TaskDatabaseApi(private val database: TaskDatabase) { + private val api: Map = mapOf( + "group" to GroupApi(this.database.groupDao()), + "student" to StudentApi(this.database.studentDao()), + "teacher" to TeacherApi(this.database.teacherDao()), + "task" to TaskApi(this.database.taskDao()), + + "group_student" to GroupStudentApi(this.database.groupStudentDao()), + "task_group" to TaskGroupApi(this.database.taskGroupDao()), + ) + + /** + * 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/GroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt similarity index 60% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt index dfc2acd..e345ddf 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt @@ -1,6 +1,6 @@ -package com.faraphel.tasks_valider.database.api +package com.faraphel.tasks_valider.database.api.entities -import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +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.GroupEntity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt similarity index 63% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt index 7fdfddd..069733f 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/GroupStudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt @@ -1,6 +1,6 @@ -package com.faraphel.tasks_valider.database.api +package com.faraphel.tasks_valider.database.api.entities -import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +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.GroupStudentEntity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt similarity index 61% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt index 8f1f67b..a380111 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/StudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt @@ -1,6 +1,6 @@ -package com.faraphel.tasks_valider.database.api +package com.faraphel.tasks_valider.database.api.entities -import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +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.StudentEntity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskApi.kt similarity index 60% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskApi.kt index 1385d1e..376f240 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskApi.kt @@ -1,6 +1,6 @@ -package com.faraphel.tasks_valider.database.api +package com.faraphel.tasks_valider.database.api.entities -import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +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 diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt similarity index 62% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt index eeb52f3..f1c9f63 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskGroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt @@ -1,6 +1,6 @@ -package com.faraphel.tasks_valider.database.api +package com.faraphel.tasks_valider.database.api.entities -import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +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.TaskGroupEntity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt similarity index 61% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt index 37cf1c9..f412fa3 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TeacherApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt @@ -1,6 +1,6 @@ -package com.faraphel.tasks_valider.database.api +package com.faraphel.tasks_valider.database.api.entities -import com.faraphel.tasks_valider.database.api.base.BaseJsonApi +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.TeacherEntity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseApi.kt similarity index 92% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseApi.kt index 7f338b3..d40665e 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseApi.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.api.base +package com.faraphel.tasks_valider.database.api.entities.base import fi.iki.elonen.NanoHTTPD diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseJsonApi.kt similarity index 97% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseJsonApi.kt index f947220..4668feb 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/base/BaseJsonApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/base/BaseJsonApi.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.api.base +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 From bc8dd0f859de87a0aaa46fc08f2515a8f93f32d4 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 10 May 2024 22:31:11 +0200 Subject: [PATCH 21/24] [WIP] linked client and server system with the UI --- .idea/misc.xml | 1 - app/src/main/AndroidManifest.xml | 2 + .../connectivity/task/TaskClient.kt | 117 +++++++++++++----- .../connectivity/task/TaskServer.kt | 29 ++++- .../database/entities/GroupEntity.kt | 1 + .../communication/internet/client/screen.kt | 6 +- .../screen/communication/internet/screen.kt | 21 +--- .../communication/internet/server/screen.kt | 18 ++- .../communication/wifiP2p/client/screen.kt | 7 +- .../ui/screen/communication/wifiP2p/screen.kt | 20 +-- .../communication/wifiP2p/server/screen.kt | 18 ++- .../tasks_valider/ui/screen/task/screen.kt | 48 ++++++- 12 files changed, 202 insertions(+), 86 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index daa0c6c..9caaea4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4dad71a..051a946 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,7 @@ + = 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 - constructor(address: String, port: Int) : this(InetAddress.getByName(address), port) - - override fun run() { - Log.i("room-client", "started !") - - // send a request to the server - val request = okhttp3.Request.Builder() - .url(URL("http://${address.hostAddress}:$port")) - .build() - - // get the response - val response = client.newCall(request).execute() - - // check if the response is successful - if (!response.isSuccessful) { - Log.e("room-client", "could not connect to the server") - return + override fun loadForRequest(url: HttpUrl): List { + return this.cookies + } + override fun saveFromResponse(url: HttpUrl, cookies: List) { + this.cookies.addAll(cookies) + } } - Log.i("room-client", "connected to the server") + ).build() - // parse the response - val body = response.body.string() - val data = jsonParser.fromJson(body, Map::class.java) + // TODO(Faraphel): automatically convert content to the correct type ? - // print the data - data.forEach { (key, value) -> Log.d("room-client", "$key: $value") } - } + /** + * 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 index fb17dd3..ba2151a 100644 --- 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 @@ -1,6 +1,8 @@ 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 @@ -16,11 +18,36 @@ 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 @@ -30,7 +57,7 @@ class TaskServer( val taskSession = this.sessionManager.getOrCreateSessionData(httpSession) // parse the url - val uri: String = httpSession.uri.substring(1) // remove the first slash + val uri: String = httpSession.uri.trim('/') val path = uri.split("/").toMutableList() // get the type of the request from the uri diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt index d042662..81913df 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt @@ -6,6 +6,7 @@ import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity +// TODO(Faraphel): should be renamed to TeamEntity @Entity(tableName = "groups") data class GroupEntity ( @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, 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 index 7dad218..348c674 100644 --- 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 @@ -1,5 +1,6 @@ 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 @@ -19,11 +20,11 @@ import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen @Composable -fun CommunicationInternetClientScreen() { +fun CommunicationInternetClientScreen(activity: Activity) { val client = remember { mutableStateOf(null) } if (client.value == null) CommunicationInternetClientContent(client) - else TaskGroupScreen() + else TaskGroupScreen(activity, client.value!!) } @@ -56,7 +57,6 @@ fun CommunicationInternetClientContent(client: MutableState) { Button(onClick = { // TODO(Faraphel): check if the server is reachable client.value = TaskClient(serverAddress.value, serverPort.intValue) - client.value!!.start() }) { Text("Connect") } 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 index 4c2455b..335c2f7 100644 --- 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 @@ -6,7 +6,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.navigation.NavController -import androidx.navigation.activity import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -19,15 +18,9 @@ fun CommunicationInternetScreen(activity: Activity) { val controller = rememberNavController() NavHost(navController = controller, startDestination = "mode") { - composable("mode") { - CommunicationInternetSelectContent(controller) - } - composable("client") { - CommunicationInternetClientScreen() - } - composable("server") { - CommunicationInternetServerScreen(activity) - } + composable("mode") { CommunicationInternetSelectContent(controller) } + composable("client") { CommunicationInternetClientScreen(activity) } + composable("server") { CommunicationInternetServerScreen(activity) } } } @@ -36,12 +29,8 @@ fun CommunicationInternetScreen(activity: Activity) { fun CommunicationInternetSelectContent(controller: NavController) { Column { // client mode - Button(onClick = { controller.navigate("client") }) { - Text("Client") - } + Button(onClick = { controller.navigate("client") }) { Text("Client") } // server mode - Button(onClick = { controller.navigate("server") }) { - Text("Server") - } + 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 index d148633..e0a7649 100644 --- 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 @@ -16,6 +16,7 @@ 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 @@ -25,17 +26,17 @@ import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen @Composable fun CommunicationInternetServerScreen(activity: Activity) { - val server = remember { mutableStateOf(null)} + val client = remember { mutableStateOf(null) } // if the server is not created, prompt the user for the server configuration - if (server.value == null) CommunicationInternetServerContent(activity, server) + if (client.value == null) CommunicationInternetServerContent(activity, client) // else, go to the base tasks screen - else TaskGroupScreen() + else TaskGroupScreen(activity, client.value!!) } @Composable -fun CommunicationInternetServerContent(activity: Activity, server: MutableState) { +fun CommunicationInternetServerContent(activity: Activity, client: MutableState) { val expandedStudentList = remember { mutableStateOf(false) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } @@ -84,8 +85,13 @@ fun CommunicationInternetServerContent(activity: Activity, server: MutableState< // Create the server Log.i("room-server", "creating the server") - server.value = TaskServer(serverPort.intValue, database) - server.value!!.start() + 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/wifiP2p/client/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/client/screen.kt index 52e4b34..3ba8cdd 100644 --- 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 @@ -1,5 +1,6 @@ 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 @@ -8,18 +9,18 @@ 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.screen.task.TaskGroupScreen import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget @Composable -fun CommunicationWifiP2pClientScreen(bwfManager: BwfManager) { +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() + // TaskGroupScreen(activity, null) + // TODO(Faraphel): finish the connection return } 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 index d0ee3f3..bd21fef 100644 --- 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 @@ -19,15 +19,9 @@ fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) { val controller = rememberNavController() NavHost(navController = controller, startDestination = "mode") { - composable("mode") { - CommunicationWifiP2pSelectContent(controller) - } - composable("client") { - CommunicationWifiP2pClientScreen(bwfManager) - } - composable("server") { - CommunicationWifiP2pServerScreen(activity, bwfManager) - } + composable("mode") { CommunicationWifiP2pSelectContent(controller) } + composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) } + composable("server") { CommunicationWifiP2pServerScreen(activity, bwfManager) } } } @@ -36,12 +30,8 @@ fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) { fun CommunicationWifiP2pSelectContent(controller: NavController) { Column { // client mode - Button(onClick = { controller.navigate("client") }) { - Text("Client") - } + Button(onClick = { controller.navigate("client") }) { Text("Client") } // server mode - Button(onClick = { controller.navigate("server") }) { - Text("Server") - } + 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 index 11125c2..bcb92d3 100644 --- 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 @@ -1,6 +1,7 @@ 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 @@ -16,6 +17,7 @@ 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 @@ -25,12 +27,12 @@ import com.faraphel.tasks_valider.ui.screen.task.TaskGroupScreen @Composable fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) { - val server = remember { mutableStateOf(null)} + val client = remember { mutableStateOf(null) } // if the server is not created, prompt the user for the server configuration - if (server.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, server) + if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client) // else, go to the base tasks screen - else TaskGroupScreen() + else TaskGroupScreen(activity, client.value!!) } @@ -38,7 +40,7 @@ fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) fun CommunicationWifiP2pServerContent( activity: Activity, bwfManager: BwfManager, - server: MutableState + client: MutableState ) { val expandedStudentList = remember { mutableStateOf(false) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } @@ -90,8 +92,12 @@ fun CommunicationWifiP2pServerContent( bwfManager.recreateGroup { // Create the server - server.value = TaskServer(serverPort.intValue, database) - server.value!!.start() + 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") 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 index a8fbfc5..b517118 100644 --- 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 @@ -1,14 +1,58 @@ package com.faraphel.tasks_valider.ui.screen.task +import android.app.Activity +import android.widget.Toast import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.faraphel.tasks_valider.connectivity.task.TaskClient +import com.faraphel.tasks_valider.database.entities.TaskGroupEntity +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + + +val jsonParser = Gson() /** * This screen let the user decide which student team he wants to interact with + * @param client an HTTP client that can communicate with the server */ @Composable -fun TaskGroupScreen() { - // TODO(Faraphel): should handle connexion with the server +fun TaskGroupScreen(activity: Activity, client: TaskClient) { + val groups = 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 From 834081e557aee95ea89952763a1cc2e318f90958 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 16 May 2024 22:15:29 +0200 Subject: [PATCH 22/24] [WIP] - Updated database --- .../tasks_valider/database/TaskDatabase.kt | 17 +++---- .../database/api/entities/GroupApi.kt | 2 +- .../database/api/entities/GroupStudentApi.kt | 2 +- .../database/api/entities/StudentApi.kt | 2 +- .../database/api/entities/TaskApi.kt | 2 +- .../database/api/entities/TaskGroupApi.kt | 2 +- .../database/api/entities/TeacherApi.kt | 2 +- .../tasks_valider/database/dao/GroupDao.kt | 2 +- .../database/dao/GroupStudentDao.kt | 2 +- .../tasks_valider/database/dao/StudentDao.kt | 2 +- .../tasks_valider/database/dao/TaskDao.kt | 2 +- .../database/dao/TaskGroupDao.kt | 2 +- .../tasks_valider/database/dao/TeacherDao.kt | 2 +- .../database/entities/ClassEntity.kt | 12 +++++ .../database/entities/SessionEntity.kt | 27 ++++++++++++ .../database/entities/StudentEntity.kt | 13 +++--- .../database/entities/SubjectEntity.kt | 12 +++++ .../database/entities/TaskEntity.kt | 17 ++++++- .../database/entities/TeacherEntity.kt | 13 +++--- .../database/entities/TeamEntity.kt | 26 +++++++++++ .../database/entities/ValidationEntity.kt | 44 +++++++++++++++++++ .../entities/{ => _old}/GroupEntity.kt | 2 +- .../entities/{ => _old}/GroupStudentEntity.kt | 2 +- .../entities/{ => _old}/PersonEntity.kt | 2 +- .../database/entities/_old/StudentEntity.kt | 12 +++++ .../database/entities/_old/TaskEntity.kt | 13 ++++++ .../entities/{ => _old}/TaskGroupEntity.kt | 2 +- .../database/entities/_old/TeacherEntity.kt | 12 +++++ .../tasks_valider/ui/screen/task/screen.kt | 2 +- .../tasks_valider/ui/widgets/task/Group.kt | 2 +- .../tasks_valider/ui/widgets/task/Task.kt | 2 +- .../ui/widgets/task/TaskGroup.kt | 2 +- 32 files changed, 215 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{ => _old}/GroupEntity.kt (87%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{ => _old}/GroupStudentEntity.kt (93%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{ => _old}/PersonEntity.kt (87%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{ => _old}/TaskGroupEntity.kt (95%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt 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 index bd59fe0..f439a9d 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -3,10 +3,6 @@ package com.faraphel.tasks_valider.database import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters -import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission -import com.faraphel.tasks_valider.connectivity.task.session.TaskSession -import com.faraphel.tasks_valider.database.api.entities.base.BaseApi -import com.faraphel.tasks_valider.database.api.entities.* import com.faraphel.tasks_valider.database.converters.InstantConverter import com.faraphel.tasks_valider.database.dao.GroupDao import com.faraphel.tasks_valider.database.dao.GroupStudentDao @@ -14,13 +10,12 @@ 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.GroupEntity -import com.faraphel.tasks_valider.database.entities.GroupStudentEntity -import com.faraphel.tasks_valider.database.entities.StudentEntity -import com.faraphel.tasks_valider.database.entities.TaskEntity -import com.faraphel.tasks_valider.database.entities.TaskGroupEntity -import com.faraphel.tasks_valider.database.entities.TeacherEntity -import fi.iki.elonen.NanoHTTPD +import com.faraphel.tasks_valider.database.entities._old.GroupEntity +import com.faraphel.tasks_valider.database.entities._old.GroupStudentEntity +import com.faraphel.tasks_valider.database.entities._old.StudentEntity +import com.faraphel.tasks_valider.database.entities._old.TaskEntity +import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities._old.TeacherEntity /** diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt index e345ddf..0f780f6 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt @@ -2,6 +2,6 @@ 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.GroupEntity +import com.faraphel.tasks_valider.database.entities._old.GroupEntity class GroupApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt index 069733f..2427a35 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt @@ -2,6 +2,6 @@ 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.GroupStudentEntity +import com.faraphel.tasks_valider.database.entities._old.GroupStudentEntity class GroupStudentApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt index a380111..01f0cbd 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt @@ -2,6 +2,6 @@ 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.StudentEntity +import com.faraphel.tasks_valider.database.entities._old.StudentEntity class StudentApi(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 index 376f240..42eee83 100644 --- 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 @@ -2,6 +2,6 @@ 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 +import com.faraphel.tasks_valider.database.entities._old.TaskEntity class TaskApi(dao: BaseDao) : BaseJsonApi(dao) \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt index f1c9f63..02119cf 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt @@ -2,6 +2,6 @@ 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.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity class TaskGroupApi(dao: BaseDao) : BaseJsonApi(dao) \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt index f412fa3..6c65cf4 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt @@ -2,6 +2,6 @@ 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.TeacherEntity +import com.faraphel.tasks_valider.database.entities._old.TeacherEntity class TeacherApi(dao: BaseDao) : BaseJsonApi(dao) 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 index e53764c..c482bb3 100644 --- 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 @@ -4,7 +4,7 @@ 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.GroupEntity +import com.faraphel.tasks_valider.database.entities._old.GroupEntity @Dao 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 index 6f86ea6..85b558b 100644 --- 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 @@ -3,7 +3,7 @@ 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.GroupStudentEntity +import com.faraphel.tasks_valider.database.entities._old.GroupStudentEntity @Dao 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 index d1451eb..6ec9459 100644 --- 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 @@ -4,7 +4,7 @@ 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.StudentEntity +import com.faraphel.tasks_valider.database.entities._old.StudentEntity @Dao 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 33df321..c0e327d 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 @@ -4,7 +4,7 @@ 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.TaskEntity +import com.faraphel.tasks_valider.database.entities._old.TaskEntity @Dao 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 index 8b2bea6..f29b27c 100644 --- 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 @@ -3,7 +3,7 @@ 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.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity @Dao 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 index 3acba4d..39cba2a 100644 --- 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 @@ -3,7 +3,7 @@ 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.TeacherEntity +import com.faraphel.tasks_valider.database.entities._old.TeacherEntity @Dao 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..253262e --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt @@ -0,0 +1,12 @@ +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 = "classes") +data class ClassEntity ( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, + @ColumnInfo("name") val name: String, +) : BaseEntity() 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..1e12419 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SessionEntity.kt @@ -0,0 +1,27 @@ +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 = "sessions", + 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") val classId: Long? = null, +) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt index fdfd648..45ed156 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt @@ -3,10 +3,13 @@ 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 = "students") -class StudentEntity( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0, - @ColumnInfo("first_name") override val firstName: String, - @ColumnInfo("last_name") override val lastName: String -) : PersonEntity(id, firstName, lastName) +data class StudentEntity ( + @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() 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..bd4122d --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt @@ -0,0 +1,12 @@ +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 = "subjects") +data class SubjectEntity ( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, + @ColumnInfo("name") val name: String, +) : BaseEntity() 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 index 7d04402..42b9bf6 100644 --- 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 @@ -2,12 +2,25 @@ 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 = "tasks") +@Entity( + tableName = "tasks", + 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, + @ColumnInfo("description") val description: String? = null, + + @ColumnInfo("subject_id") val subjectId: Long, ) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt index c244edc..cc3512d 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt @@ -3,10 +3,13 @@ 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 + +// TODO(Faraphel): is this class really required ? @Entity(tableName = "teachers") -class TeacherEntity( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0, - @ColumnInfo("first_name") override val firstName: String, - @ColumnInfo("last_name") override val lastName: String -) : PersonEntity(id, firstName, lastName) +data class TeacherEntity ( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, + @ColumnInfo("first_name") val firstName: String, + @ColumnInfo("last_name") val lastName: String, +) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt new file mode 100644 index 0000000..bbdbdf6 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt @@ -0,0 +1,26 @@ +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 = "teams", + foreignKeys = [ + ForeignKey( + entity = TeamEntity::class, + parentColumns = ["id"], + childColumns = ["team_id"], + onDelete = ForeignKey.CASCADE + ), + ] +) +data class TeamEntity ( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, + @ColumnInfo("name") val name: String? = null, + + @ColumnInfo("team_id") val teamId: Long? = null, +) : BaseEntity() 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..2e5f3f4 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ValidationEntity.kt @@ -0,0 +1,44 @@ +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 = "validations", + primaryKeys = [ + "teacher_id", + "task_id", + "student_id", + ], + foreignKeys = [ + ForeignKey( + entity = TeacherEntity::class, + parentColumns = ["id"], + childColumns = ["teacher_id"], + onDelete = ForeignKey.CASCADE + ), + ForeignKey( + entity = TaskEntity::class, + parentColumns = ["id"], + childColumns = ["task_id"], + onDelete = ForeignKey.CASCADE + ), + ForeignKey( + entity = StudentEntity::class, + parentColumns = ["id"], + childColumns = ["student_id"], + onDelete = ForeignKey.CASCADE + ), + ] +) +data class ValidationEntity ( + @ColumnInfo("date") val date: Instant, + + @ColumnInfo("teacher_id") val teacherId: Long, + @ColumnInfo("task_id") val taskId: Long, + @ColumnInfo("student_id") val studentId: Long, +) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupEntity.kt similarity index 87% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupEntity.kt index 81913df..1caf2b0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupEntity.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.entities +package com.faraphel.tasks_valider.database.entities._old import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupStudentEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupStudentEntity.kt similarity index 93% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupStudentEntity.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupStudentEntity.kt index ba003c8..5cfcae3 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/GroupStudentEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupStudentEntity.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.entities +package com.faraphel.tasks_valider.database.entities._old import androidx.room.ColumnInfo import androidx.room.Entity 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/_old/PersonEntity.kt similarity index 87% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/PersonEntity.kt index 469c9d2..29c523c 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/PersonEntity.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.entities +package com.faraphel.tasks_valider.database.entities._old import java.util.Locale import com.faraphel.tasks_valider.database.entities.base.BaseEntity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt new file mode 100644 index 0000000..bddac4c --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt @@ -0,0 +1,12 @@ +package com.faraphel.tasks_valider.database.entities._old + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "students") +class StudentEntity( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0, + @ColumnInfo("first_name") override val firstName: String, + @ColumnInfo("last_name") override val lastName: String +) : PersonEntity(id, firstName, lastName) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt new file mode 100644 index 0000000..a64c3fa --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt @@ -0,0 +1,13 @@ +package com.faraphel.tasks_valider.database.entities._old + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.faraphel.tasks_valider.database.entities.base.BaseEntity + +@Entity(tableName = "tasks") +data class TaskEntity ( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, + @ColumnInfo("title") val title: String, + @ColumnInfo("description") val description: String, +) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroupEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt similarity index 95% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroupEntity.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt index 006a6a0..fa56012 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TaskGroupEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.entities +package com.faraphel.tasks_valider.database.entities._old import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt new file mode 100644 index 0000000..e7e730f --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt @@ -0,0 +1,12 @@ +package com.faraphel.tasks_valider.database.entities._old + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "teachers") +class TeacherEntity( + @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0, + @ColumnInfo("first_name") override val firstName: String, + @ColumnInfo("last_name") override val lastName: String +) : PersonEntity(id, firstName, lastName) 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 index b517118..47183b6 100644 --- 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 @@ -8,7 +8,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import com.faraphel.tasks_valider.connectivity.task.TaskClient -import com.faraphel.tasks_valider.database.entities.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity import com.google.gson.Gson import com.google.gson.reflect.TypeToken diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt index fb35f98..50c8e81 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt @@ -3,7 +3,7 @@ package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import com.faraphel.tasks_valider.database.entities.GroupEntity +import com.faraphel.tasks_valider.database.entities._old.GroupEntity @Composable fun WidgetGroup(group: GroupEntity) { diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt index 6ed1293..b0a85f6 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt @@ -3,7 +3,7 @@ package com.faraphel.tasks_valider.ui.widgets.task import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import com.faraphel.tasks_valider.database.entities.TaskEntity +import com.faraphel.tasks_valider.database.entities._old.TaskEntity @Composable fun WidgetTask(task: TaskEntity) { diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt index d14bb27..bdd7439 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt @@ -12,7 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.faraphel.tasks_valider.database.TaskDatabase -import com.faraphel.tasks_valider.database.entities.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity @Composable From bfeb5f5132108900abce539f2d435c7c85062817 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 17 May 2024 10:55:09 +0200 Subject: [PATCH 23/24] removed IDE specific files to avoid further merge conflicts --- .gitignore | 7 +- .idea/.gitignore | 3 - .idea/.name | 1 - .idea/appInsightsSettings.xml | 6 - .idea/compiler.xml | 6 - .idea/deploymentTargetDropDown.xml | 45 ----- .idea/gradle.xml | 19 -- .idea/inspectionProfiles/Project_Default.xml | 41 ---- .idea/kotlinc.xml | 6 - .idea/migrations.xml | 10 - .idea/misc.xml | 12 -- .idea/uiDesigner.xml | 124 ------------ .idea/vcs.xml | 6 - .../database/entities/TeamEntity.kt | 26 --- .../database/entities/ValidationEntity.kt | 1 - gradlew | 0 gradlew.bat | 178 +++++++++--------- 17 files changed, 90 insertions(+), 401 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/appInsightsSettings.xml delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/deploymentTargetDropDown.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/migrations.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/uiDesigner.xml delete mode 100644 .idea/vcs.xml delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt mode change 100755 => 100644 gradlew 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 45cc42a..0000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,45 +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 fe63bb6..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 9caaea4..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +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/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt deleted file mode 100644 index bbdbdf6..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeamEntity.kt +++ /dev/null @@ -1,26 +0,0 @@ -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 = "teams", - foreignKeys = [ - ForeignKey( - entity = TeamEntity::class, - parentColumns = ["id"], - childColumns = ["team_id"], - onDelete = ForeignKey.CASCADE - ), - ] -) -data class TeamEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("name") val name: String? = null, - - @ColumnInfo("team_id") val teamId: Long? = null, -) : BaseEntity() 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 index 2e5f3f4..8219ec8 100644 --- 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 @@ -3,7 +3,6 @@ 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 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 From a3dc69b23bad5b2e67d3200b7844131c77e791ff Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 17 May 2024 16:57:18 +0200 Subject: [PATCH 24/24] Replaced the database to match the new schema --- .../tasks_valider/database/TaskDatabase.kt | 40 ++++++-------- .../database/api/TaskDatabaseApi.kt | 14 +++-- .../api/entities/{GroupApi.kt => ClassApi.kt} | 4 +- .../database/api/entities/GroupStudentApi.kt | 7 --- .../entities/{TeacherApi.kt => PersonApi.kt} | 4 +- .../api/entities/RelationClassPersonApi.kt | 7 +++ .../entities/{StudentApi.kt => SessionApi.kt} | 4 +- .../{TaskGroupApi.kt => SubjectApi.kt} | 4 +- .../database/api/entities/TaskApi.kt | 4 +- .../database/api/entities/ValidationApi.kt | 7 +++ .../tasks_valider/database/dao/ClassDao.kt | 40 ++++++++++++++ .../tasks_valider/database/dao/GroupDao.kt | 28 ---------- .../database/dao/GroupStudentDao.kt | 16 ------ .../tasks_valider/database/dao/PersonDao.kt | 44 +++++++++++++++ .../database/dao/RelationClassPersonDao.kt | 23 ++++++++ .../tasks_valider/database/dao/SessionDao.kt | 18 ++++++ .../tasks_valider/database/dao/StudentDao.kt | 29 ---------- .../tasks_valider/database/dao/SubjectDao.kt | 27 +++++++++ .../tasks_valider/database/dao/TaskDao.kt | 24 ++++---- .../database/dao/TaskGroupDao.kt | 16 ------ .../tasks_valider/database/dao/TeacherDao.kt | 16 ------ .../database/dao/ValidationDao.kt | 24 ++++++++ .../database/entities/ClassEntity.kt | 9 ++- .../{StudentEntity.kt => PersonEntity.kt} | 10 +++- ...Entity.kt => RelationClassPersonEntity.kt} | 34 +++++++----- .../database/entities/SessionEntity.kt | 10 +++- .../database/entities/SubjectEntity.kt | 8 ++- .../database/entities/TaskEntity.kt | 10 +++- .../database/entities/TeacherEntity.kt | 15 ----- .../database/entities/ValidationEntity.kt | 31 ++++++----- .../database/entities/_old/GroupEntity.kt | 14 ----- .../database/entities/_old/PersonEntity.kt | 19 ------- .../database/entities/_old/StudentEntity.kt | 12 ---- .../database/entities/_old/TaskEntity.kt | 13 ----- .../database/entities/_old/TaskGroupEntity.kt | 44 --------------- .../database/entities/_old/TeacherEntity.kt | 12 ---- .../communication/internet/client/screen.kt | 4 +- .../communication/internet/server/screen.kt | 4 +- .../communication/wifiP2p/server/screen.kt | 4 +- .../tasks_valider/ui/screen/task/screen.kt | 25 ++++----- .../tasks_valider/ui/widgets/task/Group.kt | 18 ------ .../tasks_valider/ui/widgets/task/Task.kt | 15 ----- .../ui/widgets/task/TaskGroup.kt | 55 ------------------- 43 files changed, 323 insertions(+), 443 deletions(-) rename app/src/main/java/com/faraphel/tasks_valider/database/api/entities/{GroupApi.kt => ClassApi.kt} (60%) delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt rename app/src/main/java/com/faraphel/tasks_valider/database/api/entities/{TeacherApi.kt => PersonApi.kt} (55%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/entities/RelationClassPersonApi.kt rename app/src/main/java/com/faraphel/tasks_valider/database/api/entities/{StudentApi.kt => SessionApi.kt} (55%) rename app/src/main/java/com/faraphel/tasks_valider/database/api/entities/{TaskGroupApi.kt => SubjectApi.kt} (54%) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ValidationApi.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupDao.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupStudentDao.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/StudentDao.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskGroupDao.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/TeacherDao.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{StudentEntity.kt => PersonEntity.kt} (73%) rename app/src/main/java/com/faraphel/tasks_valider/database/entities/{_old/GroupStudentEntity.kt => RelationClassPersonEntity.kt} (52%) delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/PersonEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt delete mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt 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 index f439a9d..f2ff03e 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -4,45 +4,37 @@ 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.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._old.GroupEntity -import com.faraphel.tasks_valider.database.entities._old.GroupStudentEntity -import com.faraphel.tasks_valider.database.entities._old.StudentEntity -import com.faraphel.tasks_valider.database.entities._old.TaskEntity -import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity -import com.faraphel.tasks_valider.database.entities._old.TeacherEntity +import com.faraphel.tasks_valider.database.dao.* +import com.faraphel.tasks_valider.database.entities.* /** - * The database for the tasks application. + * The database for the tasks' application. * Contains the entities and the relations between them. */ @Database( entities = [ - GroupEntity::class, - StudentEntity::class, - TeacherEntity::class, + ClassEntity::class, + PersonEntity::class, + SessionEntity::class, + SubjectEntity::class, TaskEntity::class, + ValidationEntity::class, - GroupStudentEntity::class, - TaskGroupEntity::class, + RelationClassPersonEntity::class, ], version = 1 ) @TypeConverters( InstantConverter::class ) -abstract class TaskDatabase : RoomDatabase() { - abstract fun groupDao(): GroupDao - abstract fun studentDao(): StudentDao - abstract fun teacherDao(): TeacherDao +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 groupStudentDao(): GroupStudentDao - abstract fun taskGroupDao(): TaskGroupDao + 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 index d1e9133..2303a34 100644 --- 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 @@ -5,17 +5,19 @@ 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( - "group" to GroupApi(this.database.groupDao()), - "student" to StudentApi(this.database.studentDao()), - "teacher" to TeacherApi(this.database.teacherDao()), - "task" to TaskApi(this.database.taskDao()), + 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()), - "group_student" to GroupStudentApi(this.database.groupStudentDao()), - "task_group" to TaskGroupApi(this.database.taskGroupDao()), + RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao()), ) /** diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ClassApi.kt similarity index 60% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ClassApi.kt index 0f780f6..a012aa2 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/ClassApi.kt @@ -2,6 +2,6 @@ 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._old.GroupEntity +import com.faraphel.tasks_valider.database.entities.ClassEntity -class GroupApi(dao: BaseDao) : BaseJsonApi(dao) +class ClassApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt deleted file mode 100644 index 2427a35..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/GroupStudentApi.kt +++ /dev/null @@ -1,7 +0,0 @@ -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._old.GroupStudentEntity - -class GroupStudentApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/PersonApi.kt similarity index 55% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/PersonApi.kt index 6c65cf4..d9edf18 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TeacherApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/PersonApi.kt @@ -2,6 +2,6 @@ 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._old.TeacherEntity +import com.faraphel.tasks_valider.database.entities.PersonEntity -class TeacherApi(dao: BaseDao) : BaseJsonApi(dao) +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/StudentApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SessionApi.kt similarity index 55% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SessionApi.kt index 01f0cbd..2eb7dc2 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/StudentApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SessionApi.kt @@ -2,6 +2,6 @@ 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._old.StudentEntity +import com.faraphel.tasks_valider.database.entities.SessionEntity -class StudentApi(dao: BaseDao) : BaseJsonApi(dao) +class SessionApi(dao: BaseDao) : BaseJsonApi(dao) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SubjectApi.kt similarity index 54% rename from app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SubjectApi.kt index 02119cf..caca63a 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/TaskGroupApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/entities/SubjectApi.kt @@ -2,6 +2,6 @@ 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._old.TaskGroupEntity +import com.faraphel.tasks_valider.database.entities.SubjectEntity -class TaskGroupApi(dao: BaseDao) : BaseJsonApi(dao) \ No newline at end of file +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 index 42eee83..c331a98 100644 --- 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 @@ -2,6 +2,6 @@ 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._old.TaskEntity +import com.faraphel.tasks_valider.database.entities.TaskEntity -class TaskApi(dao: BaseDao) : BaseJsonApi(dao) \ No newline at end of file +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/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 c482bb3..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupDao.kt +++ /dev/null @@ -1,28 +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._old.GroupEntity - - -@Dao -interface GroupDao : BaseDao { - @Query("SELECT * FROM `groups`") - override fun getAll(): List - - @Query("SELECT * FROM `groups` WHERE id = :id") - fun getById(id: Long): GroupEntity - - /** - 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 85b558b..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/GroupStudentDao.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._old.GroupStudentEntity - - -@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): GroupStudentEntity -} 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 6ec9459..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/StudentDao.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._old.StudentEntity - - -@Dao -interface StudentDao : BaseDao { - @Query("SELECT * FROM `students`") - override fun getAll(): List - - @Query("SELECT * FROM `students` WHERE id = :id") - fun getById(id: Long): StudentEntity - - - /** - 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 c0e327d..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,27 +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._old.TaskEntity - +import com.faraphel.tasks_valider.database.entities.TaskEntity +import com.faraphel.tasks_valider.database.entities.ValidationEntity @Dao interface TaskDao : BaseDao { - @Query("SELECT * FROM `tasks`") + @Query("SELECT * FROM ${TaskEntity.TABLE_NAME}") override fun getAll(): List - @Query("SELECT * FROM `tasks` WHERE id = :id") + /** + * Get the object from its identifier + */ + @Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE id = :id") fun getById(id: Long): TaskEntity /** - Get all the tasks for a specific group + * Get all the validations have been approved for this tasks + * @param id the id of the task */ - @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 ${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 f29b27c..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._old.TaskGroupEntity - - -@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): TaskGroupEntity -} 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 39cba2a..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TeacherDao.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._old.TeacherEntity - - -@Dao -interface TeacherDao : BaseDao { - @Query("SELECT * FROM `teachers`") - override fun getAll(): List - - @Query("SELECT * FROM `teachers` WHERE id = :id") - fun getById(id: Long): TeacherEntity -} 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/entities/ClassEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/ClassEntity.kt index 253262e..97eb47e 100644 --- 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 @@ -5,8 +5,13 @@ import androidx.room.Entity import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity -@Entity(tableName = "classes") + +@Entity(tableName = ClassEntity.TABLE_NAME) data class ClassEntity ( @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo("name") val name: String, -) : BaseEntity() +) : BaseEntity() { + companion object { + const val TABLE_NAME = "classes" + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt similarity index 73% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt index 45ed156..2ee3506 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/StudentEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt @@ -6,10 +6,14 @@ import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity import java.util.* -@Entity(tableName = "students") -data class StudentEntity ( +@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() +) : BaseEntity() { + companion object { + const val TABLE_NAME = "persons" + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupStudentEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt similarity index 52% rename from app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupStudentEntity.kt rename to app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt index 5cfcae3..06c024a 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupStudentEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/RelationClassPersonEntity.kt @@ -1,4 +1,4 @@ -package com.faraphel.tasks_valider.database.entities._old +package com.faraphel.tasks_valider.database.entities import androidx.room.ColumnInfo import androidx.room.Entity @@ -6,27 +6,31 @@ import androidx.room.ForeignKey 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 = GroupEntity::class, - parentColumns = ["id"], - childColumns = ["group_id"], - onDelete = ForeignKey.CASCADE - ), - ForeignKey( - entity = StudentEntity::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 GroupStudentEntity( - @ColumnInfo("group_id", index = true) val groupId: Long, +data class RelationClassPersonEntity ( @ColumnInfo("student_id", index = true) val studentId: Long, -) : BaseEntity() + @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 index 1e12419..0f2edf4 100644 --- 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 @@ -8,7 +8,7 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity import java.time.Instant @Entity( - tableName = "sessions", + tableName = SessionEntity.TABLE_NAME, foreignKeys = [ ForeignKey( entity = ClassEntity::class, @@ -23,5 +23,9 @@ data class SessionEntity ( @ColumnInfo("name") val name: String? = null, @ColumnInfo("start") val start: Instant, - @ColumnInfo("class_id") val classId: Long? = null, -) : BaseEntity() + @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/SubjectEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/SubjectEntity.kt index bd4122d..ee8aa83 100644 --- 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 @@ -5,8 +5,12 @@ import androidx.room.Entity import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity -@Entity(tableName = "subjects") +@Entity(tableName = SubjectEntity.TABLE_NAME) data class SubjectEntity ( @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo("name") val name: String, -) : BaseEntity() +) : BaseEntity() { + companion object { + const val TABLE_NAME = "subjects" + } +} 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 index 42b9bf6..4548c36 100644 --- 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 @@ -7,7 +7,7 @@ import androidx.room.PrimaryKey import com.faraphel.tasks_valider.database.entities.base.BaseEntity @Entity( - tableName = "tasks", + tableName = TaskEntity.TABLE_NAME, foreignKeys = [ ForeignKey( entity = SubjectEntity::class, @@ -22,5 +22,9 @@ data class TaskEntity ( @ColumnInfo("title") val title: String, @ColumnInfo("description") val description: String? = null, - @ColumnInfo("subject_id") val subjectId: Long, -) : BaseEntity() + @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/TeacherEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt deleted file mode 100644 index cc3512d..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/TeacherEntity.kt +++ /dev/null @@ -1,15 +0,0 @@ -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 - - -// TODO(Faraphel): is this class really required ? -@Entity(tableName = "teachers") -data class TeacherEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("first_name") val firstName: String, - @ColumnInfo("last_name") val lastName: String, -) : BaseEntity() 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 index 8219ec8..467f5df 100644 --- 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 @@ -7,37 +7,42 @@ import com.faraphel.tasks_valider.database.entities.base.BaseEntity import java.time.Instant @Entity( - tableName = "validations", + tableName = ValidationEntity.TABLE_NAME, primaryKeys = [ "teacher_id", - "task_id", "student_id", + "task_id", ], foreignKeys = [ ForeignKey( - entity = TeacherEntity::class, + 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 ), - ForeignKey( - entity = StudentEntity::class, - parentColumns = ["id"], - childColumns = ["student_id"], - onDelete = ForeignKey.CASCADE - ), ] ) data class ValidationEntity ( @ColumnInfo("date") val date: Instant, - @ColumnInfo("teacher_id") val teacherId: Long, - @ColumnInfo("task_id") val taskId: Long, - @ColumnInfo("student_id") val studentId: Long, -) : BaseEntity() + @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/_old/GroupEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupEntity.kt deleted file mode 100644 index 1caf2b0..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/GroupEntity.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.faraphel.tasks_valider.database.entities._old - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.faraphel.tasks_valider.database.entities.base.BaseEntity - - -// TODO(Faraphel): should be renamed to TeamEntity -@Entity(tableName = "groups") -data class GroupEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("name") val name: String? = null, -) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/PersonEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/PersonEntity.kt deleted file mode 100644 index 29c523c..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/PersonEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.faraphel.tasks_valider.database.entities._old - -import java.util.Locale -import com.faraphel.tasks_valider.database.entities.base.BaseEntity - - -open class PersonEntity ( - open val id: Long = 0, - open val firstName: String, - open val lastName: String, -) : BaseEntity() { - /** - 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/_old/StudentEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt deleted file mode 100644 index bddac4c..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/StudentEntity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.faraphel.tasks_valider.database.entities._old - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "students") -class StudentEntity( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0, - @ColumnInfo("first_name") override val firstName: String, - @ColumnInfo("last_name") override val lastName: String -) : PersonEntity(id, firstName, lastName) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt deleted file mode 100644 index a64c3fa..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskEntity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.faraphel.tasks_valider.database.entities._old - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.faraphel.tasks_valider.database.entities.base.BaseEntity - -@Entity(tableName = "tasks") -data class TaskEntity ( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo("title") val title: String, - @ColumnInfo("description") val description: String, -) : BaseEntity() diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt deleted file mode 100644 index fa56012..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TaskGroupEntity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.faraphel.tasks_valider.database.entities._old - -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 = "task_group", - primaryKeys = [ - "task_id", - "group_id" - ], - foreignKeys = [ - ForeignKey( - entity = GroupEntity::class, - parentColumns = ["id"], - childColumns = ["group_id"], - onDelete = ForeignKey.CASCADE - ), - ForeignKey( - entity = TaskEntity::class, - parentColumns = ["id"], - childColumns = ["task_id"], - onDelete = ForeignKey.CASCADE - ), - ForeignKey( - entity = TeacherEntity::class, - parentColumns = ["id"], - childColumns = ["approval_teacher_id"], - onDelete = ForeignKey.CASCADE - ), - ] -) -data class TaskGroupEntity ( - @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 -) : BaseEntity() - diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt deleted file mode 100644 index e7e730f..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/_old/TeacherEntity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.faraphel.tasks_valider.database.entities._old - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "teachers") -class TeacherEntity( - @ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0, - @ColumnInfo("first_name") override val firstName: String, - @ColumnInfo("last_name") override val lastName: String -) : PersonEntity(id, firstName, lastName) 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 index 348c674..2cc75b7 100644 --- 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 @@ -16,7 +16,7 @@ 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.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen @Composable @@ -24,7 +24,7 @@ fun CommunicationInternetClientScreen(activity: Activity) { val client = remember { mutableStateOf(null) } if (client.value == null) CommunicationInternetClientContent(client) - else TaskGroupScreen(activity, client.value!!) + else TaskSessionScreen(activity, client.value!!) } 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 index e0a7649..cdf8f13 100644 --- 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 @@ -21,7 +21,7 @@ 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.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen @Composable @@ -31,7 +31,7 @@ fun CommunicationInternetServerScreen(activity: Activity) { // 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 TaskGroupScreen(activity, client.value!!) + else TaskSessionScreen(activity, client.value!!) } 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 index bcb92d3..f608bc0 100644 --- 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 @@ -22,7 +22,7 @@ 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.TaskGroupScreen +import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen @Composable @@ -32,7 +32,7 @@ fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) // 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 TaskGroupScreen(activity, client.value!!) + else TaskSessionScreen(activity, client.value!!) } 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 index 47183b6..b14c204 100644 --- 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 @@ -1,28 +1,22 @@ package com.faraphel.tasks_valider.ui.screen.task import android.app.Activity -import android.widget.Toast import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import com.faraphel.tasks_valider.connectivity.task.TaskClient -import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken - - -val jsonParser = Gson() /** - * This screen let the user decide which student team he wants to interact with + * This screen represent a session + * @param activity the android activity * @param client an HTTP client that can communicate with the server */ @Composable -fun TaskGroupScreen(activity: Activity, client: TaskClient) { - val groups = remember { mutableStateOf?>(null) } +fun TaskSessionScreen(activity: Activity, client: TaskClient) { + Text("WIP : Session Screen") + + /* + val students = remember { mutableStateOf?>(null) } // title Text(text = "Task Group") @@ -37,9 +31,11 @@ fun TaskGroupScreen(activity: Activity, client: TaskClient) { 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") @@ -55,4 +51,5 @@ fun refreshGroups(activity: Activity, client: TaskClient, groups: MutableState>(){} ) -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt deleted file mode 100644 index 50c8e81..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Group.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.faraphel.tasks_valider.ui.widgets.task - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import com.faraphel.tasks_valider.database.entities._old.GroupEntity - -@Composable -fun WidgetGroup(group: GroupEntity) { - // 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/Task.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt deleted file mode 100644 index b0a85f6..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/Task.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.faraphel.tasks_valider.ui.widgets.task - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import com.faraphel.tasks_valider.database.entities._old.TaskEntity - -@Composable -fun WidgetTask(task: TaskEntity) { - // task information - Column { - Text(text = task.title) - Text(text = task.description) - } -} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt deleted file mode 100644 index bdd7439..0000000 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/widgets/task/TaskGroup.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.faraphel.tasks_valider.ui.widgets.task - -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.TaskDatabase -import com.faraphel.tasks_valider.database.entities._old.TaskGroupEntity - - -@Composable -fun WidgetTaskStudent(database: TaskDatabase, taskStudent: TaskGroupEntity) { - 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()) - } - } - } -}