Http Server / Client communication #7

Merged
faraphel merged 24 commits from test-http into main 2024-05-17 17:22:56 +02:00
21 changed files with 438 additions and 150 deletions
Showing only changes of commit ddb63a47ed - Show all commits

View file

@ -5,26 +5,26 @@
<entry key="app">
<State>
<multipleDevicesSelectedInDropDown value="true" />
<targetsSelectedWithDialog>
<runningDeviceTargetsSelectedWithDialog>
<Target>
<type value="QUICK_BOOT_TARGET" />
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\RC606\.android\avd\Small_Phone_API_26.avd" />
<type value="SERIAL_NUMBER" />
<value value="ypee7lnbpv9x7lvs" />
</Key>
</deviceKey>
</Target>
<Target>
<type value="QUICK_BOOT_TARGET" />
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\RC606\.android\avd\Small_Phone_API_26_-_2.avd" />
<type value="SERIAL_NUMBER" />
<value value="2XJDU17923000406" />
</Key>
</deviceKey>
</Target>
</targetsSelectedWithDialog>
</runningDeviceTargetsSelectedWithDialog>
</State>
</entry>
</value>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
<option name="version" value="1.9.23" />
</component>
</project>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

124
.idea/uiDesigner.xml Normal file
View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

View file

@ -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")

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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 <reified Packet: BasePacket> fromBytes(data: ByteArray): Packet {
return Json.decodeFromString<Packet>(data.toString())
}
}
/**
* Encode the content of the packet into an array of bytes
*/
fun toBytes(): ByteArray {
return Json.encodeToString(this).encodeToByteArray()
}
}

View file

@ -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()

View file

@ -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<PacketPing>(data)
Log.d("room-client", packet.toString())
}
}

View file

@ -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()
}
}

View file

@ -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<WifiP2pDeviceList?>(null) }
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(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()
}

View file

@ -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
}

View file

@ -9,5 +9,6 @@ import androidx.compose.runtime.Composable
*/
@Composable
fun TaskGroupScreen() {
// TODO(Faraphel): should handle connexion with the server
Text(text = "Task Group")
}

View file

@ -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<WifiP2pDevice?>? = 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)
}
}
}

View file

@ -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<WifiP2pDevice?>? = 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)
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}
kotlin("android") version "1.9.23" apply false
kotlin("plugin.serialization") version "1.9.23" apply false
}