From 2637f2fe8bd824c0d803ec869fe2aeae69956d1c Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sat, 4 May 2024 16:07:47 +0200 Subject: [PATCH] [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