Http Server / Client communication #7
21 changed files with 438 additions and 150 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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
124
.idea/uiDesigner.xml
Normal 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>
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -9,5 +9,6 @@ import androidx.compose.runtime.Composable
|
|||
*/
|
||||
@Composable
|
||||
fun TaskGroupScreen() {
|
||||
// TODO(Faraphel): should handle connexion with the server
|
||||
Text(text = "Task Group")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
Loading…
Reference in a new issue