Http Server / Client communication #7
88 changed files with 2204 additions and 690 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,12 +1,7 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
|
|
3
.idea/.gitignore
vendored
3
.idea/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -1 +0,0 @@
|
|||
tasks-valider
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AppInsightsSettings">
|
||||
<option name="selectedTabId" value="Android Vitals" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="app">
|
||||
<State />
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
</project>
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,41 +0,0 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.0" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,9 +0,0 @@
|
|||
<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">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</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,20 @@ 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")
|
||||
implementation("androidx.navigation:navigation-compose:2.7.7")
|
||||
implementation("org.nanohttpd:nanohttpd:2.3.1")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation("com.squareup.okhttp3:okhttp-android:5.0.0-alpha.14")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
|
|
@ -2,6 +2,33 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- SDK -->
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="24"
|
||||
tools:ignore="GradleOverrides" />
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<!-- Internet -->
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!-- Internet < Android 13 (API 33) -->
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
android:maxSdkVersion="32" />
|
||||
<!-- Internet >= Android 13 (API 33) -->
|
||||
<uses-permission
|
||||
android:name="android.permission.NEARBY_WIFI_DEVICES"
|
||||
android:usesPermissionFlags="neverForLocation"
|
||||
tools:targetApi="s" />
|
||||
|
||||
<!-- Applications -->
|
||||
|
||||
<!-- NOTE: usesCleartextTraffic is enabled because of the API system using simple HTTP -->
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
|
@ -10,7 +37,9 @@
|
|||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
tools:ignore="RtlEnabled"
|
||||
android:theme="@style/Theme.Tasksvalider"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
|
@ -1,99 +1,44 @@
|
|||
package com.faraphel.tasks_valider
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.faraphel.tasks_valider.database.Database
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.GroupStudent
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
import com.faraphel.tasks_valider.database.entities.Task
|
||||
import com.faraphel.tasks_valider.database.entities.TaskGroup
|
||||
import com.faraphel.tasks_valider.database.entities.Teacher
|
||||
import com.faraphel.tasks_valider.ui.widgets.WidgetTaskStudent
|
||||
import java.time.Instant
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.CommunicationScreen
|
||||
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private var bwfManager: BwfManager? = null ///< the WiFi-Direct helper
|
||||
|
||||
companion object {
|
||||
lateinit var database: Database
|
||||
private lateinit var database: TaskDatabase ///< the database manager
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Reset the database
|
||||
this.deleteDatabase("local") // TODO: remove
|
||||
|
||||
// Create the database
|
||||
database = Room.databaseBuilder(
|
||||
this.applicationContext,
|
||||
Database::class.java, "local"
|
||||
)
|
||||
.allowMainThreadQueries() // TODO: remove
|
||||
.build()
|
||||
|
||||
|
||||
// Create some data
|
||||
val studentDao = database.studentDao()
|
||||
studentDao.insert(
|
||||
Student(firstName = "John", lastName = "Joe"),
|
||||
Student(firstName = "Evan", lastName = "Doe"),
|
||||
Student(firstName = "Xavier", lastName = "Moe"),
|
||||
)
|
||||
|
||||
val teacherDao = database.teacherDao()
|
||||
teacherDao.insert(
|
||||
Teacher(firstName = "Jean", lastName = "Voe")
|
||||
)
|
||||
|
||||
val groupDao = database.groupDao()
|
||||
groupDao.insert(
|
||||
Group(name = "Group 1"),
|
||||
Group(name = "Group 2"),
|
||||
)
|
||||
|
||||
val groupStudentDao = database.groupStudentDao()
|
||||
groupStudentDao.insert(
|
||||
GroupStudent(1, 1),
|
||||
GroupStudent(1, 2),
|
||||
GroupStudent(2, 3),
|
||||
)
|
||||
|
||||
val taskDao = database.taskDao()
|
||||
taskDao.insert(
|
||||
Task(title = "Task 1", description = "Do something"),
|
||||
Task(title = "Task 2", description = "Do something else"),
|
||||
Task(title = "Task 3", description = "Do something nice"),
|
||||
)
|
||||
|
||||
val taskGroupDao = database.taskGroupDao()
|
||||
taskGroupDao.insert(
|
||||
TaskGroup(1, 1),
|
||||
TaskGroup(2, 2, true, 1, Instant.now()),
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
// display some data
|
||||
this.setContent {
|
||||
Column {
|
||||
database.taskGroupDao().getAll().forEach { taskGroup ->
|
||||
WidgetTaskStudent(database, taskGroup)
|
||||
}
|
||||
}
|
||||
CommunicationScreen(this)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// enable the WiFi-Direct events
|
||||
this.registerReceiver(this.bwfManager, BwfManager.ALL_INTENT_FILTER)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// disable the WiFi-Direct events
|
||||
this.unregisterReceiver(this.bwfManager)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
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.*
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* 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 callback: the createGroup listener
|
||||
*
|
||||
* @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
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfConnectException(
|
||||
reason: Int
|
||||
) : BwfException("Cannot connect to the peer. Reason: $reason")
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfCreateGroupException (
|
||||
reason: Int
|
||||
) : BwfException("Could not create the group : $reason")
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfDiscoverException(
|
||||
reason: Int
|
||||
) : BwfException("Could not discover peers : $reason")
|
|
@ -0,0 +1,9 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
|
||||
/**
|
||||
* Base Exception for everything concerning the WifiP2pHelper class
|
||||
*/
|
||||
open class BwfException(
|
||||
override val message: String?
|
||||
) : Exception(message)
|
|
@ -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")
|
|
@ -0,0 +1,4 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfNotSupportedException :
|
||||
BwfException("WiFi-Direct is not supported on this device.")
|
|
@ -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.")
|
|
@ -0,0 +1,5 @@
|
|||
package com.faraphel.tasks_valider.connectivity.bwf.error
|
||||
|
||||
class BwfRemoveGroupException (
|
||||
reason: Int
|
||||
) : BwfException("Could not remove the group : $reason")
|
|
@ -0,0 +1,100 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
|
||||
/**
|
||||
* A client to handle the room connection.
|
||||
* @param address the address of the server
|
||||
* @param port the port of the server
|
||||
* @param baseCookies list of cookies to use (optional)
|
||||
*/
|
||||
class TaskClient(
|
||||
private val address: String,
|
||||
private val port: Int,
|
||||
private val baseCookies: List<okhttp3.Cookie> = listOf()
|
||||
) {
|
||||
private val baseUrl = "http://$address:$port"
|
||||
private val client = OkHttpClient().newBuilder().cookieJar(
|
||||
// TODO(Faraphel): should be moved into another object
|
||||
object : okhttp3.CookieJar {
|
||||
private val cookies = baseCookies.toMutableList() ///< list of cookies
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<okhttp3.Cookie> {
|
||||
return this.cookies
|
||||
}
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<okhttp3.Cookie>) {
|
||||
this.cookies.addAll(cookies)
|
||||
}
|
||||
}
|
||||
).build()
|
||||
|
||||
// TODO(Faraphel): automatically convert content to the correct type ?
|
||||
|
||||
/**
|
||||
* Return a basic request to the server
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
private fun baseRequestBuilder(endpoint: String): okhttp3.Request.Builder =
|
||||
okhttp3.Request.Builder().url("$baseUrl/$endpoint")
|
||||
|
||||
/**
|
||||
* Run a HEAD request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun head(endpoint: String): okhttp3.Request =
|
||||
this.baseRequestBuilder(endpoint).head().build()
|
||||
|
||||
/**
|
||||
* Run a GET request
|
||||
* @param endpoint the endpoint of the server
|
||||
*/
|
||||
fun get(endpoint: String): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.get()
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a POST request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun post(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.post(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a PATCH request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun patch(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.patch(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
|
||||
/**
|
||||
* Run a DELETE request
|
||||
* @param endpoint the endpoint of the server
|
||||
* @param content the content of the request
|
||||
* @param type the type of the content
|
||||
*/
|
||||
fun delete(endpoint: String, content: String, type: String = "text/plain"): okhttp3.Response =
|
||||
this.client.newCall(
|
||||
this.baseRequestBuilder(endpoint)
|
||||
.delete(content.toRequestBody(type.toMediaType()))
|
||||
.build()
|
||||
).execute()
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.api.TaskDatabaseApi
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
|
||||
/**
|
||||
* A server to handle the task API to allow clients to interact with the database.
|
||||
* @param port the port of the server
|
||||
* @param database the database to interact with
|
||||
*/
|
||||
class TaskServer(
|
||||
private val port: Int,
|
||||
private val database: TaskDatabase
|
||||
) : NanoHTTPD(port) {
|
||||
companion object {
|
||||
private val TASK_SESSION_ADMIN = TaskSession( ///< the admin default session
|
||||
role = TaskRole.ADMIN
|
||||
)
|
||||
}
|
||||
|
||||
private val sessionManager = TaskSessionManager() ///< the session manager
|
||||
private val adminSessionId = this.sessionManager.newSessionData(TASK_SESSION_ADMIN) ///< default admin session id
|
||||
|
||||
private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager) ///< the api of the session manager
|
||||
private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database
|
||||
|
||||
/**
|
||||
* Return a new client that can be used by the admin
|
||||
*/
|
||||
fun getClientAdmin(): TaskClient {
|
||||
// create the session cookie for the admin
|
||||
val cookieSession = okhttp3.Cookie.Builder()
|
||||
.domain("localhost")
|
||||
.name("sessionId")
|
||||
.value(adminSessionId)
|
||||
.build()
|
||||
// create a new client
|
||||
return TaskClient(
|
||||
"localhost",
|
||||
this.port,
|
||||
listOf(cookieSession)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an API request
|
||||
* @param httpSession the http session
|
||||
*/
|
||||
override fun serve(httpSession: IHTTPSession): Response {
|
||||
// get the session data of the client
|
||||
val taskSession = this.sessionManager.getOrCreateSessionData(httpSession)
|
||||
|
||||
// parse the url
|
||||
val uri: String = httpSession.uri.trim('/')
|
||||
val path = uri.split("/").toMutableList()
|
||||
|
||||
// get the type of the request from the uri
|
||||
val requestType = path.removeFirstOrNull()
|
||||
?: return newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Missing request type"
|
||||
)
|
||||
|
||||
// get the response from the correct part of the application
|
||||
val response = when (requestType) {
|
||||
// session requests
|
||||
"sessions" -> this.sessionManagerApi.handleRequest(taskSession, httpSession, path)
|
||||
// entities requests
|
||||
"entities" -> return this.databaseApi.handleRequest(taskSession, httpSession, path)
|
||||
// invalid requests
|
||||
else ->
|
||||
newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Unknown request type"
|
||||
)
|
||||
}
|
||||
|
||||
// wrap additional information in the response
|
||||
return this.sessionManager.responseSetSessionData(response, httpSession.cookies)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the server with the default configuration
|
||||
*/
|
||||
override fun start() = super.start(SOCKET_READ_TIMEOUT, false)
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.api
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
|
||||
/**
|
||||
* the HTTP API for the session manager
|
||||
*/
|
||||
class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) {
|
||||
private val jsonParser = Gson() ///< the json parser
|
||||
|
||||
/**
|
||||
* Handle a HTTP Api request
|
||||
* @param taskSession the data of the client session
|
||||
* @param httpSession the data of the http session
|
||||
* @param path the path of the request
|
||||
*/
|
||||
fun handleRequest(
|
||||
taskSession: TaskSession,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
path: MutableList<String>,
|
||||
): NanoHTTPD.Response {
|
||||
// get the target session id
|
||||
val targetSessionId = path.removeFirstOrNull()
|
||||
|
||||
return if (targetSessionId == null) {
|
||||
// no specific session targeted
|
||||
this.handleRequestGeneric(taskSession, httpSession)
|
||||
} else {
|
||||
// a specific session is targeted
|
||||
this.handleRequestSpecific(taskSession, httpSession, targetSessionId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a request with no specific session targeted
|
||||
*/
|
||||
private fun handleRequestGeneric(
|
||||
taskSession: TaskSession,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
): NanoHTTPD.Response {
|
||||
when (httpSession.method) {
|
||||
// get all the session data
|
||||
NanoHTTPD.Method.GET -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
// return the session data
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"application/json",
|
||||
jsonParser.toJson(taskSession)
|
||||
)
|
||||
}
|
||||
// other action are limited
|
||||
else -> {
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Unknown method"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRequestSpecific(
|
||||
taskSession: TaskSession,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
targetSessionId: String,
|
||||
): NanoHTTPD.Response {
|
||||
when (httpSession.method) {
|
||||
// change a specific client session data
|
||||
NanoHTTPD.Method.POST -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"You are not allowed to update a session"
|
||||
)
|
||||
|
||||
// parse the content of the request
|
||||
val targetSession = jsonParser.fromJson(
|
||||
httpSession.inputStream.bufferedReader().readText(),
|
||||
TaskSession::class.java
|
||||
)
|
||||
|
||||
// update the session
|
||||
this.sessionManager.setSessionData(targetSessionId, targetSession)
|
||||
|
||||
// success message
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"text/plain",
|
||||
"Session updated"
|
||||
)
|
||||
}
|
||||
// delete the session
|
||||
NanoHTTPD.Method.DELETE -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"You are not allowed to delete a session"
|
||||
)
|
||||
|
||||
// delete the target session
|
||||
this.sessionManager.deleteSessionData(targetSessionId)
|
||||
|
||||
// success message
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"text/plain",
|
||||
"Session deleted"
|
||||
)
|
||||
}
|
||||
// ignore other methods
|
||||
else -> {
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Invalid method"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
enum class TaskPermission {
|
||||
READ,
|
||||
WRITE,
|
||||
ADMIN
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
|
||||
/**
|
||||
* A role system that can be used for in the task system
|
||||
*/
|
||||
enum class TaskRole(val value: String) {
|
||||
NONE("none") {
|
||||
override var permissions: List<TaskPermission> = listOf()
|
||||
},
|
||||
STUDENT("student") {
|
||||
override var permissions = listOf(
|
||||
TaskPermission.READ
|
||||
)
|
||||
},
|
||||
TEACHER("teacher") {
|
||||
override var permissions: List<TaskPermission> = listOf(
|
||||
TaskPermission.READ,
|
||||
TaskPermission.WRITE
|
||||
)
|
||||
},
|
||||
ADMIN("admin") {
|
||||
override var permissions: List<TaskPermission> = listOf(
|
||||
TaskPermission.READ,
|
||||
TaskPermission.WRITE,
|
||||
TaskPermission.ADMIN
|
||||
)
|
||||
};
|
||||
|
||||
abstract var permissions: List<TaskPermission>
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/**
|
||||
* store the data of a session in the task system
|
||||
* @param role the role accorded to the session
|
||||
*/
|
||||
@Serializable
|
||||
data class TaskSession(
|
||||
var role: TaskRole = TaskRole.STUDENT
|
||||
)
|
|
@ -0,0 +1,100 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* The manager for the session system
|
||||
*/
|
||||
class TaskSessionManager {
|
||||
private val sessions = mutableMapOf<String, TaskSession>() ///< sessions specific data
|
||||
|
||||
/**
|
||||
* Create a new session
|
||||
* @param session the data for the session (optional)
|
||||
* @param sessionId the session id to use (optional)
|
||||
* @return a new session identifier
|
||||
*/
|
||||
fun newSessionData(
|
||||
session: TaskSession = TaskSession(),
|
||||
sessionId: String = UUID.randomUUID().toString()
|
||||
): String {
|
||||
this.sessions[sessionId] = session
|
||||
return sessionId
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a http session
|
||||
* @param httpSession the HTTP session
|
||||
* @return the session data
|
||||
*/
|
||||
fun getSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession? {
|
||||
val sessionId = httpSession.cookies.read("sessionId") ?: return null
|
||||
val sessionData = this.getSessionData(sessionId)
|
||||
return sessionData
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a session identifier
|
||||
* @param sessionId the identifier of the session
|
||||
* @return the session data
|
||||
*/
|
||||
fun getSessionData(sessionId: String): TaskSession? {
|
||||
return this.sessions[sessionId]
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data of a session
|
||||
* @param sessionId the identifier of the session
|
||||
* @param session the session data
|
||||
*/
|
||||
fun setSessionData(sessionId: String, session: TaskSession) {
|
||||
this.sessions[sessionId] = session
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a session
|
||||
* @param sessionId the identifier of the session
|
||||
*/
|
||||
fun deleteSessionData(sessionId: String): TaskSession? {
|
||||
return this.sessions.remove(sessionId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from a http session. If it does not exist, create it.
|
||||
* @param httpSession the HTTP session
|
||||
*/
|
||||
fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession {
|
||||
// try to get the session directly
|
||||
var session = this.getSessionData(httpSession)
|
||||
|
||||
// if the session does not exist, create it
|
||||
if (session == null) {
|
||||
val sessionId = this.newSessionData()
|
||||
session = this.getSessionData(sessionId)!!
|
||||
}
|
||||
|
||||
// return the session
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new cookies for a session in a response
|
||||
* @param response the response to inject cookies into
|
||||
* @param cookies the cookie handler
|
||||
*/
|
||||
fun responseSetSessionData(
|
||||
response: NanoHTTPD.Response,
|
||||
cookies: NanoHTTPD.CookieHandler
|
||||
): NanoHTTPD.Response {
|
||||
// update the cookie of the user
|
||||
cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData()))
|
||||
// load them in the response
|
||||
cookies.unloadQueue(response)
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.room.Database
|
||||
import androidx.room.DatabaseConfiguration
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.faraphel.tasks_valider.database.converters.InstantConverter
|
||||
import com.faraphel.tasks_valider.database.dao.GroupDao
|
||||
import com.faraphel.tasks_valider.database.dao.GroupStudentDao
|
||||
import com.faraphel.tasks_valider.database.dao.StudentDao
|
||||
import com.faraphel.tasks_valider.database.dao.TaskDao
|
||||
import com.faraphel.tasks_valider.database.dao.TaskGroupDao
|
||||
import com.faraphel.tasks_valider.database.dao.TeacherDao
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.GroupStudent
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
import com.faraphel.tasks_valider.database.entities.Task
|
||||
import com.faraphel.tasks_valider.database.entities.TaskGroup
|
||||
import com.faraphel.tasks_valider.database.entities.Teacher
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
Group::class,
|
||||
Student::class,
|
||||
Teacher::class,
|
||||
Task::class,
|
||||
|
||||
GroupStudent::class,
|
||||
TaskGroup::class,
|
||||
],
|
||||
version = 1
|
||||
)
|
||||
@TypeConverters(
|
||||
InstantConverter::class
|
||||
)
|
||||
abstract class Database : RoomDatabase() {
|
||||
// entities
|
||||
abstract fun groupDao(): GroupDao
|
||||
abstract fun studentDao(): StudentDao
|
||||
abstract fun teacherDao(): TeacherDao
|
||||
abstract fun taskDao(): TaskDao
|
||||
|
||||
// relations
|
||||
abstract fun groupStudentDao(): GroupStudentDao
|
||||
abstract fun taskGroupDao(): TaskGroupDao
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.faraphel.tasks_valider.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.faraphel.tasks_valider.database.converters.InstantConverter
|
||||
import com.faraphel.tasks_valider.database.dao.*
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
|
||||
|
||||
/**
|
||||
* The database for the tasks' application.
|
||||
* Contains the entities and the relations between them.
|
||||
*/
|
||||
@Database(
|
||||
entities = [
|
||||
ClassEntity::class,
|
||||
PersonEntity::class,
|
||||
SessionEntity::class,
|
||||
SubjectEntity::class,
|
||||
TaskEntity::class,
|
||||
ValidationEntity::class,
|
||||
|
||||
RelationClassPersonEntity::class,
|
||||
],
|
||||
version = 1
|
||||
)
|
||||
@TypeConverters(
|
||||
InstantConverter::class
|
||||
)
|
||||
abstract class TaskDatabase: RoomDatabase() {
|
||||
abstract fun classDao(): ClassDao
|
||||
abstract fun personDao(): PersonDao
|
||||
abstract fun sessionDao(): SessionDao
|
||||
abstract fun subjectDao(): SubjectDao
|
||||
abstract fun taskDao(): TaskDao
|
||||
abstract fun validationDao(): ValidationDao
|
||||
|
||||
abstract fun relationClassPersonDao(): RelationClassPersonDao
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package com.faraphel.tasks_valider.database.api
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.api.entities.*
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseApi
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
class TaskDatabaseApi(private val database: TaskDatabase) {
|
||||
private val api: Map<String, BaseApi> = mapOf(
|
||||
ClassEntity.TABLE_NAME to ClassApi(this.database.classDao()),
|
||||
PersonEntity.TABLE_NAME to PersonApi(this.database.personDao()),
|
||||
SessionEntity.TABLE_NAME to SessionApi(this.database.sessionDao()),
|
||||
SubjectEntity.TABLE_NAME to SubjectApi(this.database.subjectDao()),
|
||||
TaskEntity.TABLE_NAME to TaskApi(this.database.taskDao()),
|
||||
ValidationEntity.TABLE_NAME to ValidationApi(this.database.validationDao()),
|
||||
|
||||
RelationClassPersonEntity.TABLE_NAME to RelationClassPersonApi(this.database.relationClassPersonDao()),
|
||||
)
|
||||
|
||||
/**
|
||||
* handle an API request
|
||||
* @param taskSession the current user session
|
||||
* @param httpSession the http session
|
||||
* @param path the path of the request
|
||||
*/
|
||||
fun handleRequest(
|
||||
taskSession: TaskSession,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
path: MutableList<String>
|
||||
): NanoHTTPD.Response {
|
||||
// get the entity name
|
||||
val entityName = path.removeFirstOrNull()
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Missing entity name"
|
||||
)
|
||||
|
||||
// get the correspond Api object for this entity
|
||||
val entityApi = this.api[entityName]
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
"Unknown entity name"
|
||||
)
|
||||
|
||||
// dispatch the request to the correct entity API
|
||||
when (httpSession.method) {
|
||||
// check if the data is in the database
|
||||
// TODO(Faraphel): should only be allowed to read data concerning the current class session
|
||||
NanoHTTPD.Method.HEAD -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.head(httpSession)
|
||||
}
|
||||
// get the data from the database
|
||||
NanoHTTPD.Method.GET -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.get(httpSession)
|
||||
}
|
||||
// insert the data into the database
|
||||
NanoHTTPD.Method.POST -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.WRITE))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.post(httpSession)
|
||||
}
|
||||
// delete the data from the database
|
||||
NanoHTTPD.Method.DELETE -> {
|
||||
// check the permission of the session
|
||||
if (taskSession.role.permissions.contains(TaskPermission.WRITE))
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.delete(httpSession)
|
||||
}
|
||||
// other methods are not allowed
|
||||
else ->
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Method not allowed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
|
||||
class ClassApi(dao: BaseDao<ClassEntity>) : BaseJsonApi<ClassEntity>(dao)
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
|
||||
class PersonApi(dao: BaseDao<PersonEntity>) : BaseJsonApi<PersonEntity>(dao)
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
|
||||
class RelationClassPersonApi(dao: BaseDao<RelationClassPersonEntity>) : BaseJsonApi<RelationClassPersonEntity>(dao)
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
class SessionApi(dao: BaseDao<SessionEntity>) : BaseJsonApi<SessionEntity>(dao)
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SubjectEntity
|
||||
|
||||
class SubjectApi(dao: BaseDao<SubjectEntity>) : BaseJsonApi<SubjectEntity>(dao)
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
|
||||
class TaskApi(dao: BaseDao<TaskEntity>) : BaseJsonApi<TaskEntity>(dao)
|
|
@ -0,0 +1,7 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities
|
||||
|
||||
import com.faraphel.tasks_valider.database.api.entities.base.BaseJsonApi
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
class ValidationApi(dao: BaseDao<ValidationEntity>) : BaseJsonApi<ValidationEntity>(dao)
|
|
@ -0,0 +1,32 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities.base
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
/**
|
||||
* A base for the API to handle the database operations with an HTTP server.
|
||||
*/
|
||||
interface BaseApi {
|
||||
/**
|
||||
* Handle the HEAD request
|
||||
* This is used to check if a data exists in the database
|
||||
*/
|
||||
fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
|
||||
/**
|
||||
* Handle the GET request
|
||||
* This is used to get data from the database
|
||||
*/
|
||||
fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
|
||||
/**
|
||||
* Handle the POST request
|
||||
* This is used to insert data into the database
|
||||
*/
|
||||
fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
|
||||
/**
|
||||
* Handle the PUT request
|
||||
* This is used to delete data from the database
|
||||
*/
|
||||
fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.faraphel.tasks_valider.database.api.entities.base
|
||||
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
/**
|
||||
* A base for the API to handle the database operations.
|
||||
* This is preconfigured to handle JSON data.
|
||||
* @param Entity the entity type to handle
|
||||
*/
|
||||
abstract class BaseJsonApi<Entity: BaseEntity>(private val dao: BaseDao<Entity>) : BaseApi {
|
||||
companion object {
|
||||
private val parser = Gson() ///< The JSON parser
|
||||
}
|
||||
|
||||
private val entityTypeToken: TypeToken<Entity> = object: TypeToken<Entity>() {} ///< the type of the managed entity
|
||||
|
||||
// Requests
|
||||
|
||||
override fun head(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
val obj = parser.fromJson<Entity>(
|
||||
session.inputStream.bufferedReader().readText(),
|
||||
this.entityTypeToken.type
|
||||
)
|
||||
val exists = this.dao.exists(obj)
|
||||
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
if (exists) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
if (exists) "Exists" else "Not found"
|
||||
)
|
||||
}
|
||||
|
||||
override fun get(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.OK,
|
||||
"application/json",
|
||||
parser.toJson(this.dao.getAll())
|
||||
)
|
||||
}
|
||||
|
||||
override fun post(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
val obj = parser.fromJson<Entity>(
|
||||
session.inputStream.bufferedReader().readText(),
|
||||
this.entityTypeToken.type
|
||||
)
|
||||
val id = this.dao.insert(obj)
|
||||
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.CREATED,
|
||||
"text/plain",
|
||||
id.toString()
|
||||
)
|
||||
}
|
||||
|
||||
override fun delete(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response {
|
||||
val obj = parser.fromJson<Entity>(
|
||||
session.inputStream.bufferedReader().readText(),
|
||||
this.entityTypeToken.type
|
||||
)
|
||||
val count = this.dao.delete(obj)
|
||||
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
if (count > 0) NanoHTTPD.Response.Status.OK else NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
count.toString()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.ClassEntity
|
||||
import com.faraphel.tasks_valider.database.entities.PersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
@Dao
|
||||
interface ClassDao : BaseDao<ClassEntity> {
|
||||
@Query("SELECT * FROM ${ClassEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<ClassEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${ClassEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): ClassEntity
|
||||
|
||||
/**
|
||||
* Get all the sessions this class attended
|
||||
* @param id the id of the class
|
||||
*/
|
||||
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE class_id = :id")
|
||||
fun getSessions(id: Long): List<SessionEntity>
|
||||
|
||||
/**
|
||||
* Get all the students in a class
|
||||
* @param id the id of the class
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${PersonEntity.TABLE_NAME} " +
|
||||
"JOIN ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"ON ${PersonEntity.TABLE_NAME}.id = ${RelationClassPersonEntity.TABLE_NAME}.student_id " +
|
||||
"WHERE ${RelationClassPersonEntity.TABLE_NAME}.class_id = :id"
|
||||
)
|
||||
fun getStudents(id: Long): List<PersonEntity>
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
|
||||
|
||||
@Dao
|
||||
interface GroupDao : BaseDao<Group> {
|
||||
@Query("SELECT * FROM `groups`")
|
||||
override fun getAll(): List<Group>
|
||||
|
||||
@Query("SELECT * FROM `groups` WHERE id = :id")
|
||||
fun getById(id: Long): Group
|
||||
|
||||
/**
|
||||
Allow to get all groups with a specific student
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM `groups` " +
|
||||
"JOIN `group_student` ON `groups`.id = `group_student`.student_id " +
|
||||
"WHERE `group_student`.student_id = :studentId"
|
||||
)
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
fun filterByStudentId(studentId: Long): List<Group>
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.GroupStudent
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
|
||||
|
||||
@Dao
|
||||
interface GroupStudentDao : BaseDao<GroupStudent> {
|
||||
@Query("SELECT * FROM `group_student`")
|
||||
override fun getAll(): List<GroupStudent>
|
||||
|
||||
@Query("SELECT * FROM `group_student` WHERE group_id = :groupId AND student_id = :studentId")
|
||||
fun getById(groupId: Long, studentId: Long): GroupStudent
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.*
|
||||
|
||||
@Dao
|
||||
interface PersonDao : BaseDao<PersonEntity> {
|
||||
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<PersonEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): PersonEntity
|
||||
|
||||
/**
|
||||
* Allow to get all the classes the person is attending as a student
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${ClassEntity.TABLE_NAME} " +
|
||||
"JOIN ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"ON ${ClassEntity.TABLE_NAME}.id = ${RelationClassPersonEntity.TABLE_NAME}.student_id " +
|
||||
"WHERE ${RelationClassPersonEntity.TABLE_NAME}.student_id = :id"
|
||||
)
|
||||
fun getClasses(id: Long): List<ClassEntity>
|
||||
|
||||
|
||||
/**
|
||||
* Get all the tasks this user approved as a teacher
|
||||
* @param id the id of the person
|
||||
*/
|
||||
@Query("SELECT * FROM ${ValidationEntity.TABLE_NAME} WHERE teacher_id = :id")
|
||||
fun getTasksApproved(id: Long): List<ValidationEntity>
|
||||
|
||||
/**
|
||||
* Get all the tasks this user validated as a student
|
||||
* @param id the id of the person
|
||||
*/
|
||||
@Query("SELECT * FROM ${ValidationEntity.TABLE_NAME} WHERE student_id = :id")
|
||||
fun getTasksValidated(id: Long): List<ValidationEntity>
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.RelationClassPersonEntity
|
||||
|
||||
@Dao
|
||||
interface RelationClassPersonDao : BaseDao<RelationClassPersonEntity> {
|
||||
@Query("SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<RelationClassPersonEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifiers
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${RelationClassPersonEntity.TABLE_NAME} " +
|
||||
"WHERE " +
|
||||
"class_id = :classId AND " +
|
||||
"student_id = :studentId"
|
||||
)
|
||||
fun getById(classId: Long, studentId: Long): RelationClassPersonEntity
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
|
||||
@Dao
|
||||
interface SessionDao : BaseDao<SessionEntity> {
|
||||
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<SessionEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): SessionEntity
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.Person
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
|
||||
|
||||
@Dao
|
||||
interface StudentDao : BaseDao<Student> {
|
||||
@Query("SELECT * FROM `students`")
|
||||
override fun getAll(): List<Student>
|
||||
|
||||
@Query("SELECT * FROM `students` WHERE id = :id")
|
||||
fun getById(id: Long): Student
|
||||
|
||||
|
||||
/**
|
||||
Allow to get all the students in a group
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM `students` " +
|
||||
"JOIN `group_student` ON `students`.id = `group_student`.student_id " +
|
||||
"WHERE `group_student`.group_id = :groupId"
|
||||
)
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
fun filterByGroupId(groupId: Long): List<Student>
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.SessionEntity
|
||||
import com.faraphel.tasks_valider.database.entities.SubjectEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
|
||||
@Dao
|
||||
interface SubjectDao : BaseDao<SubjectEntity> {
|
||||
@Query("SELECT * FROM ${SubjectEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<SubjectEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query("SELECT * FROM ${SubjectEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): SubjectEntity
|
||||
|
||||
/**
|
||||
* Get all the tasks available in a subject
|
||||
* @param id the id of the subject
|
||||
*/
|
||||
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE subject_id = :id")
|
||||
fun getSessions(id: Long): List<SessionEntity>
|
||||
}
|
|
@ -2,28 +2,25 @@ package com.faraphel.tasks_valider.database.dao
|
|||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.Task
|
||||
|
||||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
@Dao
|
||||
interface TaskDao : BaseDao<Task> {
|
||||
@Query("SELECT * FROM `tasks`")
|
||||
override fun getAll(): List<Task>
|
||||
|
||||
@Query("SELECT * FROM `tasks` WHERE id = :id")
|
||||
fun getById(id: Long): Task
|
||||
interface TaskDao : BaseDao<TaskEntity> {
|
||||
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<TaskEntity>
|
||||
|
||||
/**
|
||||
Get all the tasks for a specific group
|
||||
* Get the object from its identifier
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM `tasks` " +
|
||||
"JOIN `task_group` ON `tasks`.id = `task_group`.task_id " +
|
||||
"WHERE `task_group`.group_id = :groupId"
|
||||
)
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
fun filterByGroupId(groupId: Long): List<Task>
|
||||
@Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE id = :id")
|
||||
fun getById(id: Long): TaskEntity
|
||||
|
||||
/**
|
||||
* Get all the validations have been approved for this tasks
|
||||
* @param id the id of the task
|
||||
*/
|
||||
@Query("SELECT * FROM ${ValidationEntity.TABLE_NAME} WHERE task_id = :id")
|
||||
fun getTasksValidated(id: Long): List<ValidationEntity>
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.TaskGroup
|
||||
|
||||
|
||||
@Dao
|
||||
interface TaskGroupDao : BaseDao<TaskGroup> {
|
||||
@Query("SELECT * FROM `task_group`")
|
||||
override fun getAll(): List<TaskGroup>
|
||||
|
||||
@Query("SELECT * FROM `task_group` WHERE task_id = :taskId AND group_id = :groupId")
|
||||
fun getById(taskId: Long, groupId: Long): TaskGroup
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.Person
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
import com.faraphel.tasks_valider.database.entities.Teacher
|
||||
|
||||
|
||||
@Dao
|
||||
interface TeacherDao : BaseDao<Teacher> {
|
||||
@Query("SELECT * FROM `teachers`")
|
||||
override fun getAll(): List<Teacher>
|
||||
|
||||
@Query("SELECT * FROM `teachers` WHERE id = :id")
|
||||
fun getById(id: Long): Teacher
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.faraphel.tasks_valider.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.faraphel.tasks_valider.database.dao.base.BaseDao
|
||||
import com.faraphel.tasks_valider.database.entities.ValidationEntity
|
||||
|
||||
@Dao
|
||||
interface ValidationDao : BaseDao<ValidationEntity> {
|
||||
@Query("SELECT * FROM ${ValidationEntity.TABLE_NAME}")
|
||||
override fun getAll(): List<ValidationEntity>
|
||||
|
||||
/**
|
||||
* Get the object from its identifiers
|
||||
*/
|
||||
@Query(
|
||||
"SELECT * FROM ${ValidationEntity.TABLE_NAME} " +
|
||||
"WHERE " +
|
||||
"teacher_id = :teacherId and " +
|
||||
"student_id = :studentId and " +
|
||||
"task_id = :taskId"
|
||||
)
|
||||
fun getById(teacherId: Long, studentId: Long, taskId: Long): ValidationEntity
|
||||
}
|
|
@ -2,18 +2,40 @@ package com.faraphel.tasks_valider.database.dao.base
|
|||
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Update
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A base DAO to handle the database operations.
|
||||
* @param Entity the entity to handle
|
||||
*/
|
||||
interface BaseDao<Entity> {
|
||||
@Insert
|
||||
/**
|
||||
* Check if the entities exists in the database.
|
||||
*/
|
||||
fun exists(vararg entities: Entity): Boolean {
|
||||
return this.getAll().containsAll(entities.toList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the entities into the database.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg entities: Entity): List<Long>
|
||||
|
||||
@Update
|
||||
fun update(vararg entities: Entity): Int
|
||||
|
||||
/**
|
||||
* Delete the entities from the database.
|
||||
*/
|
||||
@Delete
|
||||
fun delete(vararg entities: Entity): Int
|
||||
|
||||
/**
|
||||
* Get all the entities from the database.
|
||||
* TODO(Faraphel): support filters ?
|
||||
*/
|
||||
fun getAll(): List<Entity>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
|
||||
|
||||
@Entity(tableName = ClassEntity.TABLE_NAME)
|
||||
data class ClassEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("name") val name: String,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "classes"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
|
||||
@Entity(tableName = "groups")
|
||||
data class Group (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("name") val name: String? = null,
|
||||
)
|
|
@ -1,18 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
open class Person (
|
||||
open val id: Long = 0,
|
||||
open val firstName: String,
|
||||
open val lastName: String,
|
||||
) {
|
||||
/**
|
||||
Get the full name of the person
|
||||
*/
|
||||
val fullName: String
|
||||
get() {
|
||||
return "${firstName.capitalize(Locale.ROOT)} ${lastName.uppercase()}"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import java.util.*
|
||||
|
||||
@Entity(tableName = PersonEntity.TABLE_NAME)
|
||||
data class PersonEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("first_name") val firstName: String,
|
||||
@ColumnInfo("last_name") val lastName: String,
|
||||
@ColumnInfo("card_id") val cardId: UUID,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "persons"
|
||||
}
|
||||
}
|
|
@ -3,31 +3,34 @@ package com.faraphel.tasks_valider.database.entities
|
|||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
import com.faraphel.tasks_valider.database.entities.Student
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
|
||||
@Entity(
|
||||
tableName = "group_student",
|
||||
tableName = RelationClassPersonEntity.TABLE_NAME,
|
||||
primaryKeys = [
|
||||
"group_id",
|
||||
"student_id"
|
||||
"student_id",
|
||||
"class_id",
|
||||
],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Group::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["group_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Student::class,
|
||||
entity = PersonEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["student_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
),
|
||||
ForeignKey(
|
||||
entity = ClassEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["class_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
]
|
||||
)
|
||||
data class GroupStudent(
|
||||
@ColumnInfo("group_id", index = true) val groupId: Long,
|
||||
data class RelationClassPersonEntity (
|
||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
||||
)
|
||||
@ColumnInfo("class_id", index = true) val classId: Long,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "relation_class_person"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(
|
||||
tableName = SessionEntity.TABLE_NAME,
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = ClassEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["class_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
]
|
||||
)
|
||||
data class SessionEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("name") val name: String? = null,
|
||||
@ColumnInfo("start") val start: Instant,
|
||||
|
||||
@ColumnInfo("class_id", index = true) val classId: Long? = null,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "sessions"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "students")
|
||||
class Student(
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0,
|
||||
@ColumnInfo("first_name") override val firstName: String,
|
||||
@ColumnInfo("last_name") override val lastName: String
|
||||
) : Person(id, firstName, lastName)
|
|
@ -0,0 +1,16 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
|
||||
@Entity(tableName = SubjectEntity.TABLE_NAME)
|
||||
data class SubjectEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("name") val name: String,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "subjects"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "tasks")
|
||||
data class Task (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("title") val title: String,
|
||||
@ColumnInfo("description") val description: String,
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
|
||||
@Entity(
|
||||
tableName = TaskEntity.TABLE_NAME,
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = SubjectEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["subject_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
]
|
||||
)
|
||||
data class TaskEntity (
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
@ColumnInfo("title") val title: String,
|
||||
@ColumnInfo("description") val description: String? = null,
|
||||
|
||||
@ColumnInfo("subject_id", index = true) val subjectId: Long,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "tasks"
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
@Entity(
|
||||
tableName = "task_group",
|
||||
primaryKeys = [
|
||||
"task_id",
|
||||
"group_id"
|
||||
],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Group::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["group_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Task::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["task_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Teacher::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["approval_teacher_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
]
|
||||
)
|
||||
data class TaskGroup (
|
||||
@ColumnInfo("task_id") val taskId: Long,
|
||||
@ColumnInfo("group_id") val groupId: Long,
|
||||
@ColumnInfo("approval_status") var approvalStatus: Boolean = false,
|
||||
@ColumnInfo("approval_teacher_id") val approvalTeacherId: Long? = null,
|
||||
@ColumnInfo("approval_time") val approvalTime: Instant? = null
|
||||
)
|
|
@ -1,12 +0,0 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "teachers")
|
||||
class Teacher(
|
||||
@ColumnInfo("id") @PrimaryKey(autoGenerate = true) override val id: Long = 0,
|
||||
@ColumnInfo("first_name") override val firstName: String,
|
||||
@ColumnInfo("last_name") override val lastName: String
|
||||
) : Person(id, firstName, lastName)
|
|
@ -0,0 +1,48 @@
|
|||
package com.faraphel.tasks_valider.database.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import com.faraphel.tasks_valider.database.entities.base.BaseEntity
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(
|
||||
tableName = ValidationEntity.TABLE_NAME,
|
||||
primaryKeys = [
|
||||
"teacher_id",
|
||||
"student_id",
|
||||
"task_id",
|
||||
],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = PersonEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["teacher_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = PersonEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["student_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = TaskEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["task_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
]
|
||||
)
|
||||
data class ValidationEntity (
|
||||
@ColumnInfo("date") val date: Instant,
|
||||
|
||||
@ColumnInfo("teacher_id", index = true) val teacherId: Long,
|
||||
@ColumnInfo("student_id", index = true) val studentId: Long,
|
||||
@ColumnInfo("task_id", index = true) val taskId: Long,
|
||||
) : BaseEntity() {
|
||||
companion object {
|
||||
const val TABLE_NAME = "validations"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.faraphel.tasks_valider.database.entities.base
|
||||
|
||||
open class BaseEntity
|
|
@ -0,0 +1,64 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet.client
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
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.task.TaskClient
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_ADDRESS
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetClientScreen(activity: Activity) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
if (client.value == null) CommunicationInternetClientContent(client)
|
||||
else TaskSessionScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetClientContent(client: MutableState<TaskClient?>) {
|
||||
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 = TaskClient(serverAddress.value, serverPort.intValue)
|
||||
}) {
|
||||
Text("Connect")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.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(activity: Activity) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
NavHost(navController = controller, startDestination = "mode") {
|
||||
composable("mode") { CommunicationInternetSelectContent(controller) }
|
||||
composable("client") { CommunicationInternetClientScreen(activity) }
|
||||
composable("server") { CommunicationInternetServerScreen(activity) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetSelectContent(controller: NavController) {
|
||||
Column {
|
||||
// client mode
|
||||
Button(onClick = { controller.navigate("client") }) { Text("Client") }
|
||||
// server mode
|
||||
Button(onClick = { controller.navigate("server") }) { Text("Server") }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.internet.server
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
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 androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetServerScreen(activity: Activity) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
// if the server is not created, prompt the user for the server configuration
|
||||
if (client.value == null) CommunicationInternetServerContent(activity, client)
|
||||
// else, go to the base tasks screen
|
||||
else TaskSessionScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationInternetServerContent(activity: Activity, client: MutableState<TaskClient?>) {
|
||||
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 = {
|
||||
// Reset the database | TODO(Faraphel): only for testing purpose
|
||||
activity.deleteDatabase("local")
|
||||
|
||||
// Create the database
|
||||
val database = Room.databaseBuilder(
|
||||
activity,
|
||||
TaskDatabase::class.java,
|
||||
"local"
|
||||
).build()
|
||||
|
||||
// Create the server
|
||||
Log.i("room-server", "creating the server")
|
||||
Thread { // a thread is used for networking
|
||||
val server = TaskServer(serverPort.intValue, database)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getClientAdmin()
|
||||
}.start()
|
||||
}) {
|
||||
Text("Create")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(activity)
|
||||
}
|
||||
composable("wifi-p2p") {
|
||||
val bwfManager = BwfManager.fromActivity(activity)
|
||||
CommunicationWifiP2pScreen(activity, 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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,57 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.client
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.wifi.p2p.WifiP2pConfig
|
||||
import android.net.wifi.p2p.WifiP2pDevice
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.widgets.connectivity.WifiP2pDeviceListWidget
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pClientScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
val selectedDevice = remember { mutableStateOf<WifiP2pDevice?>(null) }
|
||||
val isConnected = remember { mutableStateOf(false) }
|
||||
|
||||
// if connected, show the task group screen
|
||||
if (isConnected.value) {
|
||||
// TaskGroupScreen(activity, null)
|
||||
// TODO(Faraphel): finish the connection
|
||||
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<WifiP2pDevice?>
|
||||
) {
|
||||
Column {
|
||||
WifiP2pDeviceListWidget(
|
||||
peers = bwfManager.statePeers.value,
|
||||
filter = { device: WifiP2pDevice -> device.isGroupOwner },
|
||||
selectedDevice,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
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.wifiP2p.client.CommunicationWifiP2pClientScreen
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server.CommunicationWifiP2pServerScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
val controller = rememberNavController()
|
||||
|
||||
NavHost(navController = controller, startDestination = "mode") {
|
||||
composable("mode") { CommunicationWifiP2pSelectContent(controller) }
|
||||
composable("client") { CommunicationWifiP2pClientScreen(activity, bwfManager) }
|
||||
composable("server") { CommunicationWifiP2pServerScreen(activity, 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") }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.server
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
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 androidx.room.Room
|
||||
import com.faraphel.tasks_valider.connectivity.bwf.BwfManager
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskServer
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT
|
||||
import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pServerScreen(activity: Activity, bwfManager: BwfManager) {
|
||||
val client = remember { mutableStateOf<TaskClient?>(null) }
|
||||
|
||||
// if the server is not created, prompt the user for the server configuration
|
||||
if (client.value == null) CommunicationWifiP2pServerContent(activity, bwfManager, client)
|
||||
// else, go to the base tasks screen
|
||||
else TaskSessionScreen(activity, client.value!!)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CommunicationWifiP2pServerContent(
|
||||
activity: Activity,
|
||||
bwfManager: BwfManager,
|
||||
client: MutableState<TaskClient?>
|
||||
) {
|
||||
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 = {
|
||||
// TODO(Faraphel): should be merged with the internet server
|
||||
|
||||
// Reset the database | TODO(Faraphel): only for testing purpose
|
||||
activity.deleteDatabase("local")
|
||||
|
||||
// Create the database
|
||||
val database = Room.databaseBuilder(
|
||||
activity,
|
||||
TaskDatabase::class.java,
|
||||
"local"
|
||||
).build()
|
||||
|
||||
bwfManager.recreateGroup {
|
||||
// Create the server
|
||||
Log.i("room-server", "creating the server")
|
||||
val server = TaskServer(serverPort.intValue, database)
|
||||
server.start()
|
||||
|
||||
// Get the client from the server
|
||||
client.value = server.getClientAdmin()
|
||||
}
|
||||
}) {
|
||||
Text("Create")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.faraphel.tasks_valider.ui.screen.task
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.faraphel.tasks_valider.connectivity.task.TaskClient
|
||||
|
||||
|
||||
/**
|
||||
* This screen represent a session
|
||||
* @param activity the android activity
|
||||
* @param client an HTTP client that can communicate with the server
|
||||
*/
|
||||
@Composable
|
||||
fun TaskSessionScreen(activity: Activity, client: TaskClient) {
|
||||
Text("WIP : Session Screen")
|
||||
|
||||
/*
|
||||
val students = remember { mutableStateOf<List<TaskGroupEntity>?>(null) }
|
||||
|
||||
// title
|
||||
Text(text = "Task Group")
|
||||
|
||||
// if the groups are not yet defined, refresh the list
|
||||
if (groups.value == null) {
|
||||
Thread { refreshGroups(activity, client, groups) }.start()
|
||||
return
|
||||
}
|
||||
|
||||
// if the groups have already been defined, display them
|
||||
for (group in groups.value!!) {
|
||||
Text(text = group.toString())
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
fun refreshGroups(activity: Activity, client: TaskClient, groups: MutableState<List<TaskGroupEntity>?>) {
|
||||
// try to obtain the list of groups
|
||||
val response = client.get("entities/group")
|
||||
|
||||
// in case of error, notify it
|
||||
if (!response.isSuccessful) {
|
||||
Toast.makeText(activity, response.message, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
// parse the list of groups
|
||||
groups.value = jsonParser.fromJson(
|
||||
response.body.toString(),
|
||||
object : TypeToken<List<TaskGroupEntity>>(){}
|
||||
)
|
||||
}
|
||||
*/
|
|
@ -1,18 +0,0 @@
|
|||
package com.faraphel.tasks_valider.ui.widgets
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.faraphel.tasks_valider.database.entities.Group
|
||||
|
||||
@Composable
|
||||
fun WidgetGroup(group: Group) {
|
||||
// TODO
|
||||
Column {
|
||||
Text(text = group.name!!)
|
||||
|
||||
// group.tasks.forEach { task ->
|
||||
// WidgetTask(task)
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.faraphel.tasks_valider.ui.widgets
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.faraphel.tasks_valider.database.entities.Task
|
||||
|
||||
@Composable
|
||||
fun WidgetTask(task: Task) {
|
||||
// task information
|
||||
Column {
|
||||
Text(text = task.title)
|
||||
Text(text = task.description)
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package com.faraphel.tasks_valider.ui.widgets
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.faraphel.tasks_valider.database.Database
|
||||
import com.faraphel.tasks_valider.database.entities.TaskGroup
|
||||
|
||||
@Composable
|
||||
fun WidgetTaskStudent(database: Database, taskStudent: TaskGroup) {
|
||||
val teacherDao = database.teacherDao()
|
||||
|
||||
Column {
|
||||
// row for this task
|
||||
Row {
|
||||
// task information
|
||||
// TODO: WidgetTask(task = taskStudent.task)
|
||||
|
||||
// align the other columns to the right
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// task status
|
||||
Checkbox(
|
||||
checked = taskStudent.approvalStatus,
|
||||
onCheckedChange = { status -> taskStudent.approvalStatus = status }
|
||||
)
|
||||
}
|
||||
|
||||
// if the task has been approved
|
||||
if (taskStudent.approvalStatus) {
|
||||
Row (
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
// teacher who approved the task
|
||||
Text(text = teacherDao.getById(taskStudent.approvalTeacherId!!).fullName)
|
||||
|
||||
// align the other columns to the right
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
// date of approval
|
||||
Text(text = taskStudent.approvalTime.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
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?,
|
||||
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) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
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, 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,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
|
||||
}
|
0
gradlew
vendored
Executable file → Normal file
0
gradlew
vendored
Executable file → Normal file
Loading…
Reference in a new issue