[WIP] use navcontroller

This commit is contained in:
Faraphel 2024-05-03 21:57:22 +02:00
parent ddb63a47ed
commit 60d5be49f7
19 changed files with 294 additions and 373 deletions

View file

@ -4,27 +4,18 @@
<value> <value>
<entry key="app"> <entry key="app">
<State> <State>
<multipleDevicesSelectedInDropDown value="true" /> <targetSelectedWithDropDown>
<runningDeviceTargetsSelectedWithDialog>
<Target> <Target>
<type value="RUNNING_DEVICE_TARGET" /> <type value="QUICK_BOOT_TARGET" />
<deviceKey> <deviceKey>
<Key> <Key>
<type value="SERIAL_NUMBER" /> <type value="VIRTUAL_DEVICE_PATH" />
<value value="ypee7lnbpv9x7lvs" /> <value value="$USER_HOME$/.android/avd/Very_Small_API_34_2.avd" />
</Key> </Key>
</deviceKey> </deviceKey>
</Target> </Target>
<Target> </targetSelectedWithDropDown>
<type value="RUNNING_DEVICE_TARGET" /> <timeTargetWasSelectedWithDropDown value="2024-05-02T14:19:57.253897049Z" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="2XJDU17923000406" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetsSelectedWithDialog>
</State> </State>
</entry> </entry>
</value> </value>

View file

@ -62,6 +62,7 @@ dependencies {
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation("androidx.room:room-ktx:2.6.1") implementation("androidx.room:room-ktx:2.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("androidx.navigation:navigation-compose:2.7.7")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

View file

@ -5,7 +5,7 @@
<!-- SDK --> <!-- SDK -->
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="24"
tools:ignore="GradleOverrides" /> tools:ignore="GradleOverrides" />
<!-- Permissions --> <!-- Permissions -->

View file

@ -1,25 +1,24 @@
package com.faraphel.tasks_valider package com.faraphel.tasks_valider
import android.content.Context
import android.content.pm.PackageManager
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.wifi.p2p.* import android.content.Context
import android.content.pm.PackageManager
import android.net.wifi.p2p.WifiP2pManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.faraphel.tasks_valider.connectivity.packets.PacketPing import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.connectivity.bwf.error.BwfNotSupportedException
import com.faraphel.tasks_valider.database.Database import com.faraphel.tasks_valider.database.Database
import com.faraphel.tasks_valider.ui.screen.room.RoomScreen import com.faraphel.tasks_valider.ui.screen.room.RoomScreen
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fi private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper
private var p2pHelper: WifiP2pHelper? = null ///< the Wi-Fi Direct helper
companion object { companion object {
private lateinit var database: Database ///< the database manager private lateinit var database: Database ///< the database manager
@ -29,56 +28,56 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// ----- CONNECTION // TODO(Faraphel): more check on permissions
// TOOD: more check on permissions // check if the system support WiFi-Direct
if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) { if (!this.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature") Log.e("wifi-p2p", "this device does not support the WiFi-Direct feature")
return throw BwfNotSupportedException()
} }
if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (
this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
this.checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES) != PackageManager.PERMISSION_GRANTED
) {
// TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check // TODO(Faraphel): should be used with shouldShowRequestPermissionRationale, with a check
this.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ACCESS_FINE_LOCATION) this.requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
BwfManager.PERMISSION_ACCESS_FINE_LOCATION
)
} }
// get the WiFi-Direct manager // get the WiFi-Direct manager
val p2pManager: WifiP2pManager? = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? val manager = this.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
if (p2pManager == null) { if (manager == null) {
Log.e("wifi-p2p", "cannot access the WiFi-Direct manager") Log.e("wifi-p2p", "cannot access the WiFi-Direct manager")
return throw BwfNotSupportedException()
} }
// get the Wifi-Direct channel // create a channel for the manager
val p2pChannel: WifiP2pManager.Channel = p2pManager.initialize(this, this.mainLooper, null) val channel = manager.initialize(this, this.mainLooper, null)
// create a helper for handling the WiFi-Direct // create a new manager for handling the WiFi-Direct
this.p2pHelper = WifiP2pHelper(p2pManager, p2pChannel) this.bwfManager = BwfManager(manager, channel)
// open the room selection screen // open the room selection screen
this.setContent { this.setContent {
RoomScreen(this.p2pHelper!!) RoomScreen(this.bwfManager!!)
} }
} }
@RequiresApi(Build.VERSION_CODES.O)
@SuppressLint("UnspecifiedRegisterReceiverFlag") @SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// enable the WiFi-Direct events // enable the WiFi-Direct events
this.registerReceiver(this.p2pHelper, WifiP2pHelper.ALL_INTENT_FILTER) this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
// disable the WiFi-Direct events // disable the WiFi-Direct events
this.unregisterReceiver(this.p2pHelper) this.unregisterReceiver(this.bwfManager)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// TODO(Faraphel): save the current state to reload it later
} }
} }

View file

@ -0,0 +1,151 @@
package com.faraphel.tasks_valider.connectivity.bwf
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.p2p.*
import androidx.compose.runtime.mutableStateOf
import com.faraphel.tasks_valider.connectivity.bwf.error.*
/**
* A helper to wrap the WiFi-Direct manager, the channel and the events.
*
* This avoids certain annoying features such as always specifying the channel as the first argument or
* handling all the events with the base event system.
*
* @param manager The WiFi-Direct manager
* @param channel The WiFi-Direct channel
*/
class BwfManager(
private var manager: WifiP2pManager,
private var channel: WifiP2pManager.Channel,
) : BroadcastReceiver() {
companion object {
var PERMISSION_ACCESS_FINE_LOCATION = 1001 ///< permission code for the fine location
var ALL_INTENT_FILTER = IntentFilter().apply {
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
}
}
// Wrappers
/**
* Connect to another device, allowing for a communication using Sockets
* @see WifiP2pManager.connect
* @throws SecurityException if the permission has not been given
*/
@Throws(SecurityException::class)
fun connect(config: WifiP2pConfig, callback: () -> Unit = {}) =
this.manager.connect(this.channel, config, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfConnectException(reason)
})
/**
* Request the list of peers after a discovery.
* @see WifiP2pManager.requestPeers
* @throws SecurityException if the permission has not been given
*/
@Throws(SecurityException::class)
fun requestPeers(callback: (WifiP2pDeviceList) -> Unit = {}) =
this.manager.requestPeers(this.channel, callback)
/**
* Start discovering peers.
* Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered.
* @see WifiP2pManager.discoverPeers
* @throws SecurityException if the permission has not been given
*/
@Throws(SecurityException::class)
fun discoverPeers(callback: () -> Unit = {}) =
this.manager.discoverPeers(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfDiscoverException(reason)
})
/**
* Obtain information about a connection with another device.
* @see WifiP2pManager.requestConnectionInfo
*/
fun requestConnectionInfo(callback: (WifiP2pInfo) -> Unit = {}) =
this.manager.requestConnectionInfo(this.channel, callback)
/**
* Obtain information about the current group.
* @see WifiP2pManager.requestGroupInfo
* @throws SecurityException if the permission has not been given
*/
@Throws(SecurityException::class)
fun requestGroupInfo(callback: (WifiP2pGroup?) -> Unit = {}) =
this.manager.requestGroupInfo(this.channel, callback)
/**
* Create a new WiFi-Direct group.
* @see WifiP2pManager.createGroup
* @throws SecurityException if the permission has not been given
*/
@Throws(SecurityException::class)
fun createGroup(callback: () -> Unit = {}) =
this.manager.createGroup(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfCreateGroupException(reason)
})
/**
* Disconnect from the current WiFi-Direct group.
* @see WifiP2pManager.removeGroup
*/
fun removeGroup(callback: () -> Unit = {}) =
this.manager.removeGroup(this.channel, object : WifiP2pManager.ActionListener {
override fun onSuccess() { callback() }
override fun onFailure(reason: Int) = throw BwfRemoveGroupException(reason)
})
/**
* Create a new WiFi-Direct group. If already connected to a group, quit it first.
*
* Note: most of the failure on removal are caused by not having a group already created, which is checked.
*
* @param listener: the createGroup listener
* @param onRemoveFailure: error handler for the removeGroup event
*
* @see WifiP2pManager.createGroup
* @see WifiP2pManager.removeGroup
*/
fun recreateGroup(callback: () -> Unit = {}) {
// get the current group information
this.requestGroupInfo { group ->
// if a group exist, quit it
if (group != null)
this.removeGroup { this@BwfManager.createGroup(callback) }
else
// create the group
this.createGroup(callback)
}
}
// Events
val stateConnectionInfo = mutableStateOf<WifiP2pInfo?>(null)
val statePeers = mutableStateOf<WifiP2pDeviceList?>(null)
override fun onReceive(context: Context?, intent: Intent?) {
// ignore empty intent
if (intent == null)
return
// update the action corresponding state
when (intent.action) {
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> this.requestConnectionInfo {
connectionInfo -> stateConnectionInfo.value = connectionInfo
}
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> this.requestPeers {
peers -> statePeers.value = peers
}
}
// TODO(Faraphel): implement event dispatcher
}
}

View file

@ -0,0 +1,11 @@
# Better WiFi-Direct (BWD)
This package contain code to improve the base WiFi-Direct implementation.
The base have some issue, like an abusive usage of listener, error code and events that make using it
very impractical.
This improved version will instead focus on asynchronous function and exception, allowing for a
cleaner linear code instead.
(Author: https://git.faraphel.fr/Faraphel)

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfConnectException(
reason: Int
) : BwfException("Cannot connect to the peer. Reason: $reason")

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfCreateGroupException (
reason: Int
) : BwfException("Could not create the group : $reason")

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfDiscoverException(
reason: Int
) : BwfException("Could not discover peers : $reason")

View file

@ -1,9 +1,9 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p.error package com.faraphel.tasks_valider.connectivity.bwf.error
/** /**
* Base Exception for everything concerning the WifiP2pHelper class * Base Exception for everything concerning the WifiP2pHelper class
*/ */
open class WifiP2pHelperException( open class BwfException(
override val message: String? override val message: String?
) : Exception(message) ) : Exception(message)

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfInvalidActionException(
action: String
) : BwfException("This WiFi-Direct action is not supported : $action")

View file

@ -0,0 +1,4 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfNotSupportedException :
BwfException("WiFi-Direct is not supported on this device, or it is disabled.")

View file

@ -0,0 +1,5 @@
package com.faraphel.tasks_valider.connectivity.bwf.error
class BwfRemoveGroupException (
reason: Int
) : BwfException("Could not remove the group : $reason")

View file

@ -1,212 +0,0 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.p2p.WifiP2pConfig
import android.net.wifi.p2p.WifiP2pDeviceList
import android.net.wifi.p2p.WifiP2pManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.faraphel.tasks_valider.connectivity.wifi_p2p.error.WifiP2pHelperInvalidActionException
/**
* A helper to wrap the WiFi-Direct manager, the channel and the events.
*
* This avoids certain annoying features such as always specifying the channel as the first argument or
* handling all the events with the base event system.
*
* @param manager: the WiFi-Direct base manager
* @param channel: the WiFi-Direct channel
*/
class WifiP2pHelper(
private val manager: WifiP2pManager,
private val channel: WifiP2pManager.Channel,
) : BroadcastReceiver() {
companion object {
/**
* This IntentFilter can be used to subscribe the helper to every supported WiFi-Direct events.
*/
val ALL_INTENT_FILTER = IntentFilter().apply {
addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
}
}
// Events
/**
* Contains the listeners to call when the peers are updated.
*/
private val listenersPeersChanged = mutableListOf<WifiP2pHelperListenerInfo<(WifiP2pDeviceList?) -> Unit>>()
/**
* Event receiver for the WiFi-Direct events.
* Should not be called manually.
* @param context: the WiFi-Direct context
* @param intent: the event information
*/
@RequiresApi(Build.VERSION_CODES.N)
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
Log.v("wifi-p2p", "Connection Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
Log.v("wifi-p2p", "Peers Changed : $context, $intent, ${intent.extras}")
this.requestPeers { peers ->
this@WifiP2pHelper.listenersPeersChanged.forEach { listenerInfo -> listenerInfo.callback(peers) }
this@WifiP2pHelper.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.once }
}
}
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
Log.v("wifi-p2p", "State Changed : $context, $intent, ${intent.extras}")
}
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
Log.v("wifi-p2p", "This Device Changed : $context, $intent, ${intent.extras}")
}
}
}
/**
* Register a listener to a specific WiFi-Direct event.
* @param action: the name of the WiFi-Direct event
* @param callback: the callback function
* @param once: should the event be only called once ?
*/
fun <CallbackType> registerListener(action: String, callback: CallbackType, once: Boolean = false) {
when (action) {
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
@Suppress("UNCHECKED_CAST")
this.listenersPeersChanged.add(WifiP2pHelperListenerInfo(
callback as ((WifiP2pDeviceList?) -> Unit),
once
))
}
else -> {
// raise an exception if the action is invalid
throw WifiP2pHelperInvalidActionException(action)
}
}
}
@RequiresApi(Build.VERSION_CODES.N)
fun <CallbackType> unregisterListener(action: String, callback: CallbackType) {
when (action) {
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
this.listenersPeersChanged.removeIf { listenerInfo -> listenerInfo.callback == callback }
}
else -> {
// raise an exception if the action is invalid
throw WifiP2pHelperInvalidActionException(action)
}
}
}
// Wrappers
/**
* Connect to another device, allowing for a communication using Sockets
* @see WifiP2pManager.connect
*/
fun connect(config: WifiP2pConfig, listener: WifiP2pManager.ActionListener? = null) {
this.manager.connect(this.channel, config, listener)
}
/**
* Request the list of peers after a discovery.
* @see WifiP2pManager.requestPeers
*/
fun requestPeers(listener: WifiP2pManager.PeerListListener? = null) {
this.manager.requestPeers(this.channel, listener)
}
/**
* Start discovering peers.
* Once founds, the WIFI_P2P_PEERS_CHANGED_ACTION event will be triggered.
* @see WifiP2pManager.discoverPeers
*/
fun discoverPeers(listener: WifiP2pManager.ActionListener? = null) {
this.manager.discoverPeers(this.channel, listener)
}
/**
* Obtain information about a connection with another device.
* @see WifiP2pManager.requestConnectionInfo
*/
fun requestConnectionInfo(listener: WifiP2pManager.ConnectionInfoListener? = null) {
this.manager.requestConnectionInfo(this.channel, listener)
}
/**
* Obtain information about the current group.
* @see WifiP2pManager.requestGroupInfo
*/
fun requestGroupInfo(listener: WifiP2pManager.GroupInfoListener? = null) {
this.manager.requestGroupInfo(this.channel, listener)
}
/**
* Create a new WiFi-Direct group.
* @see WifiP2pManager.createGroup
*/
fun createGroup(listener: WifiP2pManager.ActionListener? = null) {
this.manager.createGroup(this.channel, listener)
}
/**
* Disconnect from the current WiFi-Direct group.
* @see WifiP2pManager.removeGroup
*/
fun removeGroup(listener: WifiP2pManager.ActionListener? = null) {
this.manager.removeGroup(this.channel, listener)
}
// Shortcuts
/**
* Create a new WiFi-Direct group. If already connected to a group, quit it first.
*
* Note: most of the failure on removal are caused by not having a group already created, which is checked.
*
* @param listener: the createGroup listener
* @param onRemoveFailure: error handler for the removeGroup event
*
* @see WifiP2pManager.createGroup
* @see WifiP2pManager.removeGroup
*/
fun recreateGroup(
listener: WifiP2pManager.ActionListener? = null,
onRemoveFailure: ((Int) -> Unit)? = null
) {
// get the current group information
this.requestGroupInfo { group ->
// if a group exist, quit it
if (group != null)
this.removeGroup(object : WifiP2pManager.ActionListener {
override fun onSuccess() {
// once left, create the group
this@WifiP2pHelper.createGroup(listener)
}
override fun onFailure(reason: Int) {
// call the handler
onRemoveFailure?.invoke(reason)
}
})
else
// create the group
this.createGroup(listener)
}
}
}

View file

@ -1,15 +0,0 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p
import java.util.concurrent.Callable
/**
* This class contain the information required to store a listener in the WifiP2pHelper class to correctly handle events.
* @param CallbackType: the type of the callback function
* @param callback: the function to call when the event is triggered
* @param once: should the listener be called only once ?
*/
class WifiP2pHelperListenerInfo<CallbackType>(
val callback: CallbackType,
val once: Boolean,
)

View file

@ -1,5 +0,0 @@
package com.faraphel.tasks_valider.connectivity.wifi_p2p.error
class WifiP2pHelperInvalidActionException(
action: String
) : WifiP2pHelperException("This WiFi-Direct action is not supported : $action")

View file

@ -3,32 +3,50 @@ package com.faraphel.tasks_valider.ui.screen.room
import android.net.wifi.p2p.WifiP2pConfig import android.net.wifi.p2p.WifiP2pConfig
import android.net.wifi.p2p.WifiP2pDevice import android.net.wifi.p2p.WifiP2pDevice
import android.net.wifi.p2p.WifiP2pDeviceList import android.net.wifi.p2p.WifiP2pDeviceList
import android.net.wifi.p2p.WifiP2pInfo
import android.net.wifi.p2p.WifiP2pManager
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
import com.faraphel.tasks_valider.connectivity.room.RoomClient import com.faraphel.tasks_valider.connectivity.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.screen.tasks.TaskGroupScreen
import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget import com.faraphel.tasks_valider.ui.widgets.connectivity.WifiP2pDeviceListWidget
import kotlinx.coroutines.runBlocking
/** /**
* This screen allow the user to join a room * This screen allow the user to join a room
*/ */
@Composable @Composable
fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) { fun RoomClientScreen(bwfManager: BwfManager) {
val peers = remember { mutableStateOf<WifiP2pDeviceList?>(null) } val navController = rememberNavController()
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) } val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
val connected = remember { mutableStateOf(false) } val connected = remember { mutableStateOf(false) }
// if the device is connected to a host, display the main screen NavHost(navController = navController, startDestination = "roomSearch") {
if (connected.value) { composable(route = "roomSearch") {
TaskGroupScreen() // 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 a device is selected, connect to it
@ -37,53 +55,17 @@ fun RoomClientScreen(wifiP2pHelper: WifiP2pHelper) {
val config = WifiP2pConfig().apply { val config = WifiP2pConfig().apply {
deviceAddress = selectedDevice.value!!.deviceAddress 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 // try to connect to the host
}, bwfManager.connect(config) {
once = true Log.i("room", "Connection successful to the host !")
)
bwfManager.requestConnectionInfo { connectionInfo ->
val client = RoomClient(connectionInfo.groupOwnerAddress, 9876) // TODO(Faraphel): port should be a settings
Thread { client.start() }.start()
connected.value = true
} }
}
override fun onFailure(reason: Int) {
// TODO(Faraphel): for most of theses messages, shouldn't a toast be used instead ?
Log.e("room", "Could not connect to this host. Reason: $reason")
}
})
return
} }
// let the user pick which host he wishes to connect to
Column {
Text(text = "Find a Server")
// display all the devices that are owner of their group
WifiP2pDeviceListWidget(
peers = peers.value,
filter = { device: WifiP2pDevice -> device.isGroupOwner },
selectedDevice,
)
}
// TODO(Faraphel): might be doubtful. Test with more phones, should it only run once ? refresh button ?
// update the list when a new device is detected
wifiP2pHelper.registerListener(
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION,
{ newPeers: WifiP2pDeviceList? -> peers.value = newPeers },
)
wifiP2pHelper.discoverPeers()
} }

View file

@ -1,6 +1,5 @@
package com.faraphel.tasks_valider.ui.screen.room package com.faraphel.tasks_valider.ui.screen.room
import android.net.wifi.p2p.WifiP2pManager
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -10,8 +9,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen import com.faraphel.tasks_valider.ui.screen.tasks.TaskGroupScreen
import kotlinx.coroutines.runBlocking
import java.net.ServerSocket import java.net.ServerSocket
@ -22,7 +22,7 @@ var DEFAULT_SERVER_PORT: Int = 9876
* This screen allow the user to create a room * This screen allow the user to create a room
*/ */
@Composable @Composable
fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) { fun RoomHostScreen(bwfManager: BwfManager) {
val expanded = remember { mutableStateOf(false) } val expanded = remember { mutableStateOf(false) }
val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) } val serverPort = remember { mutableStateOf(DEFAULT_SERVER_PORT) }
val isCreated = remember { mutableStateOf(false) } val isCreated = remember { mutableStateOf(false) }
@ -65,23 +65,19 @@ fun RoomHostScreen(wifiP2pHelper: WifiP2pHelper) {
Button(onClick = { Button(onClick = {
// create a new WiFi-Direct group // create a new WiFi-Direct group
wifiP2pHelper.recreateGroup(object : WifiP2pManager.ActionListener { bwfManager.recreateGroup {
override fun onSuccess() { Log.i("room", "group created !")
Log.i("room", "group created !")
val server = ServerSocket(serverPort.value) // TODO(Faraphel): should the port be a settings ? // open a new server
Thread { val server = ServerSocket(serverPort.value)
val client = server.accept() // TODO(Faraphel): should run in a thread Thread {
client.getInputStream() val client = server.accept() // TODO(Faraphel): should run in a thread
} client.getInputStream()
}
// mark the group as created // mark the group as created
isCreated.value = true isCreated.value = true
} }
override fun onFailure(reason: Int) {
Log.e("room", "error while creating group. Reason: $reason")
}
})
}) { }) {
Text("Create") Text("Create")
} }

View file

@ -6,42 +6,35 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import com.faraphel.tasks_valider.connectivity.wifi_p2p.WifiP2pHelper import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
enum class RoomScreenType { import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
HOST,
CLIENT
}
/** /**
* This screen will allow the user to choose which room is he connecting to, or let him create one. * This screen will allow the user to choose which room is he connecting to, or let him create one.
*/ */
@Composable @Composable
fun RoomScreen(wifiP2pHelper: WifiP2pHelper) { fun RoomScreen(bwfManager: BwfManager) {
val roomMode = remember { mutableStateOf<RoomScreenType?>(null) } val navController = rememberNavController()
when (roomMode.value) { NavHost(navController = navController, startDestination = "pickMode") {
RoomScreenType.CLIENT -> { composable(route = "pickMode") {
// Client mode
RoomClientScreen(wifiP2pHelper)
}
RoomScreenType.HOST -> {
// Host mode
RoomHostScreen(wifiP2pHelper)
}
null -> {
// let the user decide which room mode he which to use
Column { Column {
Button(onClick = { roomMode.value = RoomScreenType.CLIENT }) { Button(onClick = { navController.navigate("client") }) {
Text(text = "Join a room") Text(text = "Join a room")
} }
Button(onClick = { roomMode.value = RoomScreenType.HOST }) { Button(onClick = { navController.navigate("host") }) {
Text(text = "Create a room") Text(text = "Create a room")
} }
} }
} }
composable(route = "client") {
RoomClientScreen(bwfManager)
}
composable(route = "host") {
RoomHostScreen(bwfManager)
}
} }
} }