diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..80b5c38
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+wifip2photspot
\ No newline at end of file
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..371f2e2
--- /dev/null
+++ b/.idea/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..6030a3d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..e1321d9
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..8e78a60
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..a07bb9d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..fdf8d99
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..ad5b6e6
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/app2.xml b/.idea/runConfigurations/app2.xml
new file mode 100644
index 0000000..4673a41
--- /dev/null
+++ b/.idea/runConfigurations/app2.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml
new file mode 100644
index 0000000..539e3b8
--- /dev/null
+++ b/.idea/studiobot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9431f2f..b71762f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
+
}
android {
@@ -69,6 +70,37 @@ dependencies {
debugImplementation(libs.androidx.ui.test.manifest)
implementation ("androidx.compose.material:material-icons-extended:1.5.0")
+ implementation ("androidx.work:work-runtime-ktx:2.7.1")
+
+
+ // DataStore
+ implementation ("androidx.datastore:datastore-preferences:1.0.0")
+ implementation ("androidx.datastore:datastore-core:1.0.0")
+
+ implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
+
+ implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2") // Version before 1.6.0
+
+ implementation ("androidx.work:work-runtime-ktx:2.7.1")
+
+
+ implementation ("androidx.navigation:navigation-compose:2.8.3")
+
+
+
+// implementation(libs.charts)
+
+
+
+ implementation (libs.kotlinx.serialization.json)
+// implementation ("com.github.bumptech.glide:glide:4.15.1")
+
+ implementation(libs.timber)
+// implementation (libs.mpandroidchart)
+
+
+
+
// implementation ("androidx.core:core-ktx:1.12.0")
// implementation ("androidx.activity:activity-compose:1.7.2")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0998f0b..45492c1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
@@ -6,17 +7,43 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -24,6 +51,15 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/example/wifip2photspot/BandSelection.kt b/app/src/main/java/com/example/wifip2photspot/BandSelection.kt
new file mode 100644
index 0000000..e317607
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/BandSelection.kt
@@ -0,0 +1,58 @@
+package com.example.wifip2photspot
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.*
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.*
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.ContentAlpha
+
+@Composable
+fun BandSelection(
+ selectedBand: String,
+ onBandSelected: (String) -> Unit,
+ bands: List,
+ isHotspotEnabled: Boolean
+) {
+ Column(modifier = Modifier.padding(8.dp)) {
+ Text("Wi-Fi Band Selection", style = MaterialTheme.typography.titleMedium)
+ Spacer(modifier = Modifier.height(8.dp))
+ bands.forEach { band ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(enabled = isHotspotEnabled.not()) { onBandSelected(band) }
+ .padding(vertical = 4.dp)
+ ) {
+ RadioButton(
+ selected = selectedBand == band,
+ onClick = { onBandSelected(band) },
+ enabled = isHotspotEnabled.not()
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(band, style = MaterialTheme.typography.bodyLarge)
+ }
+ }
+ if (isHotspotEnabled) {
+ Text(
+ text = "Cannot change band while hotspot is active.",
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
+ modifier = Modifier.padding(top = 4.dp)
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/wifip2photspot/BlockedDevicesSection.kt b/app/src/main/java/com/example/wifip2photspot/BlockedDevicesSection.kt
new file mode 100644
index 0000000..e4d87b3
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/BlockedDevicesSection.kt
@@ -0,0 +1,133 @@
+package com.example.wifip2photspot
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.Smartphone
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+@Composable
+fun BlockedDevicesSection(
+ devices: List,
+ onUnblock: (String) -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp)
+ ) {
+ Text(
+ text = "Blocked Devices (${devices.size}):",
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+
+ if (devices.isEmpty()) {
+ Text(
+ text = "No blocked devices.",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ } else {
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ items(devices) { deviceInfo ->
+ BlockedDeviceCard(
+ deviceInfo = deviceInfo,
+ onUnblock = onUnblock
+ )
+ Divider()
+ }
+ }
+ }
+ }
+}
+// BlockedDevicesSection.kt
+fun LazyListScope.blockedDevicesSection(
+ devices: List,
+ onUnblock: (String) -> Unit
+) {
+ item {
+ Text(
+ text = "Blocked Devices (${devices.size}):",
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+
+ if (devices.isEmpty()) {
+ item {
+ Text(
+ text = "No blocked devices.",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+ } else {
+ items(devices) { deviceInfo ->
+ BlockedDeviceCard(
+ deviceInfo = deviceInfo,
+ onUnblock = onUnblock
+ )
+ Divider()
+ }
+ }
+}
+
+@Composable
+fun BlockedDeviceCard(
+ deviceInfo: DeviceInfo,
+ onUnblock: (String) -> Unit
+) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 4.dp),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.errorContainer
+ )
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(
+ text = deviceInfo.alias ?: "Unknown Device",
+ style = MaterialTheme.typography.bodyMedium
+ )
+ Text(
+ text = "MAC Address: ${deviceInfo.device.deviceAddress}",
+ style = MaterialTheme.typography.bodySmall
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ OutlinedButton(
+ onClick = { onUnblock(deviceInfo.device.deviceAddress) }
+ ) {
+ Text("Unblock")
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt b/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt
new file mode 100644
index 0000000..5da7686
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt
@@ -0,0 +1,413 @@
+package com.example.wifip2photspot
+
+import android.app.TimePickerDialog
+import android.content.Context
+import android.net.wifi.p2p.WifiP2pDevice
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.Smartphone
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+
+// ConnectedDevicesSection.kt
+
+
+@Composable
+fun ConnectedDevicesSection(
+ devices: List,
+ onDeviceAliasChange: (String, String) -> Unit,
+ onBlockUnblock: (String) -> Unit,
+ onDisconnect: (String) -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp)
+ ) {
+ Text(
+ text = "Connected Devices (${devices.size}):",
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .fillMaxWidth()
+ .semantics { contentDescription = "Connected Devices Header: ${devices.size} devices connected" }
+ )
+
+ if (devices.isEmpty()) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(32.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "No devices connected.",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.semantics { contentDescription = "No devices connected" }
+ )
+ }
+ } else {
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ items(devices) { deviceInfo ->
+ DeviceInfoCard(
+ deviceInfo = deviceInfo,
+ onAliasChange = { alias ->
+ onDeviceAliasChange(deviceInfo.device.deviceAddress, alias)
+ },
+ onBlockUnblock = onBlockUnblock,
+ onDisconnect = onDisconnect
+ )
+ Divider()
+ }
+ }
+ }
+ }
+}
+
+
+
+// Text(
+// text = "Connected Devices (${devices.size}):",
+// style = MaterialTheme.typography.titleMedium,
+// modifier = Modifier
+// .padding(vertical = 8.dp)
+// .fillMaxWidth()
+// .semantics { contentDescription = "Connected Devices Header: ${devices.size} devices connected" }
+// )
+//
+// if (devices.isEmpty()) {
+// Box(
+// modifier = Modifier
+// .fillMaxWidth()
+// .padding(32.dp),
+// contentAlignment = Alignment.Center
+// ) {
+// Text(
+// text = "No devices connected.",
+// style = MaterialTheme.typography.bodyLarge,
+// color = MaterialTheme.colorScheme.onSurfaceVariant,
+// modifier = Modifier.semantics { contentDescription = "No devices connected" }
+// )
+// }
+// } else {
+// devices.forEach { device ->
+// DeviceItem(device = device, onClick = onDeviceClick)
+// }
+// }
+@Composable
+fun DeviceInfoCard(
+ deviceInfo: DeviceInfo,
+ onAliasChange: (String) -> Unit,
+ onBlockUnblock: (String) -> Unit,
+ onDisconnect: (String) -> Unit
+) {
+ var isEditingAlias by remember { mutableStateOf(false) }
+ var aliasText by remember { mutableStateOf(deviceInfo.alias ?: "") }
+
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 4.dp),
+ colors = CardDefaults.cardColors(
+ containerColor = if (deviceInfo.isBlocked) MaterialTheme.colorScheme.errorContainer else MaterialTheme.colorScheme.surfaceVariant
+ )
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ if (isEditingAlias) {
+ OutlinedTextField(
+ value = aliasText,
+ onValueChange = { aliasText = it },
+ label = { Text("Alias") },
+ singleLine = true,
+ trailingIcon = {
+ IconButton(onClick = {
+ onAliasChange(aliasText)
+ isEditingAlias = false
+ }) {
+ Icon(Icons.Default.Check, contentDescription = "Save Alias")
+ }
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+ } else {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = deviceInfo.alias ?: deviceInfo.device.deviceName,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.weight(1f)
+ )
+ IconButton(
+ onClick = { isEditingAlias = true },
+ modifier = Modifier.semantics { contentDescription = "Edit Alias" }
+ ) {
+ Icon(Icons.Default.Edit, contentDescription = "Edit Alias")
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = "MAC Address: ${deviceInfo.device.deviceAddress}",
+ style = MaterialTheme.typography.bodySmall
+ )
+ Text(
+ text = "Connected Since: ${formatTime(deviceInfo.connectionTime)}",
+ style = MaterialTheme.typography.bodySmall
+ )
+ deviceInfo.ipAddress?.let {
+ Text("IP Address: $it", style = MaterialTheme.typography.bodySmall)
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ if (deviceInfo.isBlocked) {
+ OutlinedButton(
+ onClick = { onBlockUnblock(deviceInfo.device.deviceAddress) }
+ ) {
+ Text("Unblock")
+ }
+ } else {
+ OutlinedButton(
+ onClick = { onBlockUnblock(deviceInfo.device.deviceAddress) }
+ ) {
+ Text("Block")
+ }
+ Spacer(modifier = Modifier.width(8.dp))
+ OutlinedButton(
+ onClick = { onDisconnect(deviceInfo.device.deviceAddress) }
+ ) {
+ Text("Disconnect")
+ }
+ }
+ }
+ }
+ }
+}
+
+fun formatTime(timestamp: Long): String {
+ val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
+ return sdf.format(Date(timestamp))
+}
+
+fun getDeviceStatus(status: Int): String {
+ return when (status) {
+ WifiP2pDevice.AVAILABLE -> "Available"
+ WifiP2pDevice.INVITED -> "Invited"
+ WifiP2pDevice.CONNECTED -> "Connected"
+ WifiP2pDevice.FAILED -> "Failed"
+ WifiP2pDevice.UNAVAILABLE -> "Unavailable"
+ else -> "Unknown"
+ }
+}
+// ConnectedDevicesSection.kt
+fun LazyListScope.connectedDevicesSection(
+ devices: List,
+ onDeviceAliasChange: (String, String) -> Unit,
+ onBlockUnblock: (String) -> Unit,
+ onDisconnect: (String) -> Unit
+) {
+ item {
+ Text(
+ text = "Connected Devices (${devices.size}):",
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+
+ if (devices.isEmpty()) {
+ item {
+ Text(
+ text = "No devices connected.",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+ } else {
+ items(devices) { deviceInfo ->
+ DeviceInfoCard(
+ deviceInfo = deviceInfo,
+ onAliasChange = { alias ->
+ onDeviceAliasChange(deviceInfo.device.deviceAddress, alias)
+ },
+ onBlockUnblock = onBlockUnblock,
+ onDisconnect = onDisconnect
+ )
+ Divider()
+ }
+ }
+}
+
+//
+//@Composable
+//fun DeviceItem(
+// device: WifiP2pDevice,
+// onClick: (WifiP2pDevice) -> Unit = {}
+//) {
+// Card(
+// elevation = CardDefaults.cardElevation(2.dp),
+// shape = MaterialTheme.shapes.medium,
+// modifier = Modifier
+// .fillMaxWidth()
+// .padding(vertical = 4.dp)
+// .clickable { onClick(device) }
+// .semantics { contentDescription = "Device: ${device.deviceName.ifBlank { "Unknown Device" }}, Address: ${device.deviceAddress}" }
+// ) {
+// Row(
+// verticalAlignment = Alignment.CenterVertically,
+// modifier = Modifier.padding(12.dp)
+// ) {
+// Icon(
+// imageVector = Icons.Filled.Smartphone,
+// contentDescription = "Device Icon",
+// tint = MaterialTheme.colorScheme.primary,
+// modifier = Modifier.size(40.dp)
+// )
+// Spacer(modifier = Modifier.width(16.dp))
+// Column(
+// verticalArrangement = Arrangement.Center,
+// modifier = Modifier.weight(1f)
+// ) {
+// Text(
+// text = device.deviceName.ifBlank { "Unknown Device" },
+// style = MaterialTheme.typography.titleMedium,
+// color = MaterialTheme.colorScheme.onSurface,
+// modifier = Modifier.semantics { contentDescription = "Device Name: ${device.deviceName.ifBlank { "Unknown Device" }}" }
+// )
+// Text(
+// text = "Address: ${device.deviceAddress}",
+// style = MaterialTheme.typography.bodySmall,
+// color = MaterialTheme.colorScheme.onSurfaceVariant,
+// modifier = Modifier.semantics { contentDescription = "Device Address: ${device.deviceAddress}" }
+// )
+// Text(
+// text = "Status: ${getDeviceStatus(device.status)}",
+// style = MaterialTheme.typography.bodySmall,
+// color = MaterialTheme.colorScheme.onSurfaceVariant,
+// modifier = Modifier.semantics { contentDescription = "Device Status: ${getDeviceStatus(device.status)}" }
+// )
+// }
+// IconButton(
+// onClick = {
+// // Handle device action (e.g., view details, disconnect)
+// },
+// modifier = Modifier.semantics { contentDescription = "View details for ${device.deviceName.ifBlank { "Unknown Device" }}" }
+// ) {
+// Icon(
+// imageVector = Icons.Filled.Info,
+// contentDescription = "Device Info Icon",
+// tint = MaterialTheme.colorScheme.primary
+// )
+// }
+// }
+// }
+//}
+//
+//fun getDeviceStatus(status: Int): String {
+// return when (status) {
+// WifiP2pDevice.AVAILABLE -> "Available"
+// WifiP2pDevice.INVITED -> "Invited"
+// WifiP2pDevice.CONNECTED -> "Connected"
+// WifiP2pDevice.FAILED -> "Failed"
+// WifiP2pDevice.UNAVAILABLE -> "Unavailable"
+// else -> "Unknown"
+// }
+//}
+
+@Composable
+fun HotspotScheduler(
+ onScheduleStart: (Long) -> Unit,
+ onScheduleStop: (Long) -> Unit
+) {
+ val context = LocalContext.current
+ var startTime by remember { mutableStateOf(null) }
+ var stopTime by remember { mutableStateOf(null) }
+
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text("Hotspot Scheduler", style = MaterialTheme.typography.titleMedium)
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(onClick = {
+ showTimePicker(context) { timeInMillis ->
+ startTime = timeInMillis
+ }
+ }) {
+ Text("Set Start Time")
+ }
+ startTime?.let {
+ Text("Start Time: ${formatTime(it)}")
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(onClick = {
+ showTimePicker(context) { timeInMillis ->
+ stopTime = timeInMillis
+ }
+ }) {
+ Text("Set Stop Time")
+ }
+ stopTime?.let {
+ Text("Stop Time: ${formatTimes(it)}")
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(onClick = {
+ startTime?.let { onScheduleStart(it) }
+ stopTime?.let { onScheduleStop(it) }
+ }) {
+ Text("Schedule Hotspot")
+ }
+ }
+}
+
+// Helper functions
+fun showTimePicker(context: Context, onTimeSelected: (Long) -> Unit) {
+ val calendar = Calendar.getInstance()
+ TimePickerDialog(
+ context,
+ { _, hourOfDay, minute ->
+ calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
+ calendar.set(Calendar.MINUTE, minute)
+ onTimeSelected(calendar.timeInMillis)
+ },
+ calendar.get(Calendar.HOUR_OF_DAY),
+ calendar.get(Calendar.MINUTE),
+ true
+ ).show()
+}
+
+fun formatTimes(timeInMillis: Long): String {
+ val sdf = SimpleDateFormat("HH:mm", Locale.getDefault())
+ return sdf.format(Date(timeInMillis))
+}
diff --git a/app/src/main/java/com/example/wifip2photspot/ConnectionStatusBar.kt b/app/src/main/java/com/example/wifip2photspot/ConnectionStatusBar.kt
new file mode 100644
index 0000000..e180c38
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/ConnectionStatusBar.kt
@@ -0,0 +1,366 @@
+// ConnectionStatusBar.kt
+package com.example.wifip2photspot
+
+import android.provider.CalendarContract.Colors
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.*
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.DeviceHub
+import androidx.compose.material.icons.filled.Download
+import androidx.compose.material.icons.filled.Upload
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+
+@Composable
+fun ConnectionStatusBar(
+ uploadSpeed: Int,
+ downloadSpeed: Int,
+ totalDownload: Int,
+ connectedDevicesCount: Int,
+ errorMessage: String? = null
+) {
+ // Main Card encapsulating all metrics
+ Card(
+ modifier = Modifier
+ .fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.secondaryContainer
+ ),
+ shape = MaterialTheme.shapes.medium,
+ elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.Top,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ // Speed Metrics Sub-Card
+ MetricSubCard(
+ title = stringResource(id = R.string.speed),
+ backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
+ modifier = Modifier.weight(1f)
+ ) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.Start
+ ) {
+ // Animated Upload Speed
+ AnimatedMetric(
+ icon = Icons.Default.Upload,
+ tint = Color(0xFF2196F3), // Blue
+ value = uploadSpeed,
+ unit = stringResource(id = R.string.kbps)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ // Animated Download Speed
+ AnimatedMetric(
+ icon = Icons.Default.Download,
+ tint = Color(0xFF4CAF50), // Green
+ value = downloadSpeed,
+ unit = stringResource(id = R.string.kbps)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ // Total Download Metrics Sub-Card
+ MetricSubCard(
+ title = stringResource(id = R.string.download),
+ backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
+ modifier = Modifier.weight(1f)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Download,
+ contentDescription = null,
+ tint = Color(0xFFFF9800), // Orange
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ AnimatedText(
+ text = "$totalDownload",
+ unit = stringResource(id = R.string.kbps)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ // Connected Devices Metrics Sub-Card
+ MetricSubCard(
+ title = stringResource(id = R.string.devices),
+ backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
+ modifier = Modifier.weight(1f)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.DeviceHub,
+ contentDescription = null,
+ tint = Color(0xFF9C27B0), // Purple
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ AnimatedText(
+ text = "$connectedDevicesCount",
+ unit = ""
+ )
+ }
+ }
+ }
+
+ // Error Message Display
+ errorMessage?.let { message ->
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = message,
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodySmall,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun AnimatedMetric(
+ icon: ImageVector,
+ tint: Color,
+ value: Int,
+ unit: String
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = null, // Accessibility handled in parent composable
+ tint = tint,
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ AnimatedText(
+ text = "$value",
+ unit = unit
+ )
+ }
+}
+
+@Composable
+fun AnimatedText(
+ text: String,
+ unit: String
+) {
+ // Animate the numeric value
+ val targetValue = text.toIntOrNull() ?: 0
+ val animatedValue by animateIntAsState(
+ targetValue = targetValue,
+ animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
+ )
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = "$animatedValue ",
+ style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold),
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Text(
+ text = unit,
+ style = MaterialTheme.typography.bodySmall.copy(color = MaterialTheme.colorScheme.onSurfaceVariant)
+ )
+ }
+}
+@Composable
+fun AnimatedButton(
+ text: String,
+ onClick: () -> Unit,
+ enabled: Boolean,
+ modifier: Modifier = Modifier
+) {
+ // Animation for scaling the button when pressed
+ var isPressed by remember { mutableStateOf(false) }
+ val scale by animateFloatAsState(
+ targetValue = if (isPressed) 0.95f else 1f,
+ animationSpec = tween(durationMillis = 100)
+ )
+
+ // Animation for color change
+ val buttonColor by animateColorAsState(
+ targetValue = if (enabled) MaterialTheme.colorScheme.primary else Color.Gray,
+ animationSpec = tween(durationMillis = 300)
+ )
+
+ Button(
+ onClick = onClick,
+ enabled = enabled,
+ modifier = modifier
+ .scale(scale)
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = {
+ isPressed = true
+ tryAwaitRelease()
+ isPressed = false
+ }
+ )
+ },
+ colors = ButtonDefaults.buttonColors(containerColor = buttonColor)
+ ) {
+ Text(text)
+ }
+}
+
+
+@Composable
+fun MetricSubCard(
+ title: String,
+ backgroundColor: Color,
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit
+) {
+ Card(
+ modifier = modifier,
+ colors = CardDefaults.cardColors(
+ containerColor = backgroundColor
+ ),
+ shape = MaterialTheme.shapes.medium,
+ elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(12.dp)
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold),
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ content()
+ }
+ }
+}
+
+@Composable
+fun DataUsageSection(
+ rxBytes: Long,
+ txBytes: Long
+) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text("Session Data Usage", style = MaterialTheme.typography.titleMedium)
+ Spacer(modifier = Modifier.height(8.dp))
+ Text("Download: ${formatBytes(rxBytes)}")
+ Text("Upload: ${formatBytes(txBytes)}")
+ }
+}
+
+fun formatBytes(bytes: Long): String {
+ val kb = bytes / 1024
+ val mb = kb / 1024
+ return when {
+ mb > 0 -> "$mb MB"
+ kb > 0 -> "$kb KB"
+ else -> "$bytes Bytes"
+ }
+}
+
+//@Composable
+//fun HistoricalDataUsageSection(historicalData: List) {
+// Column(modifier = Modifier.padding(16.dp)) {
+// Text("Historical Data Usage", style = MaterialTheme.typography.titleMedium)
+// Spacer(modifier = Modifier.height(8.dp))
+// historicalData.forEach { record ->
+// Text("${record.date}: Download ${formatBytes(record.rxBytes)}, Upload ${formatBytes(record.txBytes)}")
+// }
+// }
+//}
+
+@Composable
+fun SpeedGraphSection(
+ uploadSpeeds: List,
+ downloadSpeeds: List
+) {
+ AndroidView(
+ factory = { context ->
+ LineChart(context).apply {
+ description.isEnabled = false
+ xAxis.position = XAxis.XAxisPosition.BOTTOM
+ axisRight.isEnabled = false
+ }
+ },
+ update = { chart ->
+ val uploadDataSet = LineDataSet(uploadSpeeds, "Upload Speed").apply {
+ color = Color.Red.toArgb()
+ }
+ val downloadDataSet = LineDataSet(downloadSpeeds, "Download Speed").apply {
+ color = Color.Green.toArgb()
+ }
+ val data = LineData(uploadDataSet, downloadDataSet)
+ chart.data = data
+ chart.invalidate()
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ )
+}
+@Composable
+fun BatteryStatusSection(batteryLevel: Int) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text("Battery Status", style = MaterialTheme.typography.titleMedium)
+ Spacer(modifier = Modifier.height(8.dp))
+ Text("Battery Level: $batteryLevel%")
+ }
+}
+//***********************improving*************
+//
+//if (_networkQuality.value == "Poor") {
+// // Reduce video quality or data rate
+//} else if (_networkQuality.value == "Moderate") {
+// // Set to medium quality
+//} else {
+// // High quality
+//}
+
+
+
+
+
diff --git a/app/src/main/java/com/example/wifip2photspot/DataStoreManager.kt b/app/src/main/java/com/example/wifip2photspot/DataStoreManager.kt
new file mode 100644
index 0000000..6d822e9
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/DataStoreManager.kt
@@ -0,0 +1,50 @@
+// DataStoreManager.kt
+package com.example.wifip2photspot
+
+import android.content.Context
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import kotlinx.coroutines.flow.Flow
+import androidx.datastore.preferences.core.edit
+import kotlinx.coroutines.flow.map
+
+object DataStoreManager {
+ private const val DATASTORE_NAME = "hotspot_prefs"
+
+ private val Context.dataStore by preferencesDataStore(
+ name = DATASTORE_NAME
+ )
+
+ // Define keys
+ private val SSID_KEY = stringPreferencesKey("ssid_key")
+ private val PASSWORD_KEY = stringPreferencesKey("password_key")
+
+ // Function to get SSID
+ fun getSsid(context: Context): Flow {
+ return context.dataStore.data.map { preferences ->
+ preferences[SSID_KEY] ?: "TetherGuard" // Default SSID
+ }
+ }
+
+ // Function to get Password
+ fun getPassword(context: Context): Flow {
+ return context.dataStore.data.map { preferences ->
+ preferences[PASSWORD_KEY] ?: "00000000" // Default Password
+ }
+ }
+
+ // Function to save SSID
+ suspend fun saveSsid(context: Context, ssid: String) {
+ context.dataStore.edit { preferences ->
+ preferences[SSID_KEY] = ssid
+ }
+ }
+
+ // Function to save Password
+ suspend fun savePassword(context: Context, password: String) {
+ context.dataStore.edit { preferences ->
+ preferences[PASSWORD_KEY] = password
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/wifip2photspot/DataUsageRecord.kt b/app/src/main/java/com/example/wifip2photspot/DataUsageRecord.kt
new file mode 100644
index 0000000..3d28f35
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/DataUsageRecord.kt
@@ -0,0 +1,9 @@
+package com.example.wifip2photspot
+//
+//import java.time.LocalDate
+//
+//data class DataUsageRecord(
+// val date: LocalDate,
+// val rxBytes: Long,
+// val txBytes: Long
+//)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/wifip2photspot/DeviceInfo.kt b/app/src/main/java/com/example/wifip2photspot/DeviceInfo.kt
new file mode 100644
index 0000000..c6fd6e5
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/DeviceInfo.kt
@@ -0,0 +1,18 @@
+package com.example.wifip2photspot
+
+import android.net.wifi.p2p.WifiP2pDevice
+import java.time.LocalDate
+
+// DeviceInfo.kt
+data class DeviceInfo(
+ val device: WifiP2pDevice,
+ val alias: String? = null,
+ val connectionTime: Long = System.currentTimeMillis(),
+ val ipAddress: String? = null,
+ val isBlocked: Boolean = false
+)
+
+
+
+
+
diff --git a/app/src/main/java/com/example/wifip2photspot/FeedbackForm.kt b/app/src/main/java/com/example/wifip2photspot/FeedbackForm.kt
new file mode 100644
index 0000000..03e3e3d
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/FeedbackForm.kt
@@ -0,0 +1,65 @@
+package com.example.wifip2photspot
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.*
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.*
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.ContentAlpha
+
+@Composable
+fun FeedbackForm(onSubmit: (String) -> Unit) {
+ var feedbackText by remember { mutableStateOf("") }
+
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text("Feedback", style = MaterialTheme.typography.titleMedium)
+ Spacer(modifier = Modifier.height(8.dp))
+
+ OutlinedTextField(
+ value = feedbackText,
+ onValueChange = { feedbackText = it },
+ label = { Text("Your Feedback") },
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = 5
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(
+ onClick = {
+ onSubmit(feedbackText)
+ feedbackText = ""
+ },
+ enabled = feedbackText.isNotBlank()
+ ) {
+ Text("Submit")
+ }
+ }
+}
+
+@Composable
+fun ContactSupportSection(onContactSupport: () -> Unit) {
+ Column(modifier = Modifier.padding(8.dp)) {
+ Text("Need Help?", style = MaterialTheme.typography.titleMedium)
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(onClick = onContactSupport) {
+ Text("Contact Support")
+ }
+ }
+}
+
+
+
diff --git a/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt b/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt
new file mode 100644
index 0000000..980190d
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt
@@ -0,0 +1,94 @@
+// HotspotControlSection.kt
+package com.example.wifip2photspot
+
+import android.content.Intent
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Sync
+import androidx.compose.material.icons.filled.Wifi
+import androidx.compose.material.icons.filled.WifiOff
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import com.example.wifip2photspot.Proxy.ProxyService
+
+@Composable
+fun HotspotControlSection(
+ isHotspotEnabled: Boolean,
+ isProcessing: Boolean,
+ ssidInput: String,
+ proxyPort: Int,
+ passwordInput: String,
+ selectedBand: String,
+ onStartTapped: () -> Unit,
+ onStopTapped: () -> Unit
+) {
+ val context = LocalContext.current
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp),
+ horizontalArrangement = Arrangement.SpaceEvenly
+ ) {
+ Button(
+ onClick = {
+ onStartTapped()
+ // Start ProxyService
+ val intent = Intent(context, ProxyService::class.java)
+ context.startService(intent)
+ },
+ enabled = !isHotspotEnabled && !isProcessing
+ ) {
+ if (isProcessing) {
+ CircularProgressIndicator(
+ modifier = Modifier.size(20.dp),
+ strokeWidth = 2.dp,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ } else {
+ Text("Start Hotspot & Proxy")
+ }
+ }
+
+ val statusIcon = when {
+ isProcessing -> Icons.Default.Sync
+ isHotspotEnabled -> Icons.Default.Wifi
+ else -> Icons.Default.WifiOff
+ }
+ val statusText = when {
+ isProcessing -> if (isHotspotEnabled) "Stopping hotspot..." else "Starting hotspot..."
+ isHotspotEnabled -> "Hotspot & Proxy are active"
+ else -> "Hotspot & Proxy are inactive"
+ }
+ Icon(
+ imageVector = statusIcon,
+ contentDescription = statusText,
+ tint = if (isHotspotEnabled) Color(0xFF4CAF50) else Color(0xFFF44336) // Green or Red
+ )
+ Button(
+ onClick = {
+ onStopTapped()
+ // Stop ProxyService
+ val intent = Intent(context, ProxyService::class.java)
+ context.stopService(intent)
+ },
+ enabled = isHotspotEnabled && !isProcessing
+ ) {
+ if (isProcessing) {
+ CircularProgressIndicator(
+ modifier = Modifier.size(20.dp),
+ strokeWidth = 2.dp,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ } else {
+ Text("Stop Hotspot & Proxy")
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt b/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt
new file mode 100644
index 0000000..644ff92
--- /dev/null
+++ b/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt
@@ -0,0 +1,1252 @@
+// HotspotViewModel.kt
+package com.example.wifip2photspot
+
+import android.app.Application
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.TrafficStats
+import android.net.Uri
+import android.net.wifi.WifiManager
+import android.net.wifi.p2p.WifiP2pConfig
+import android.net.wifi.p2p.WifiP2pDevice
+import android.net.wifi.p2p.WifiP2pManager
+import android.os.BatteryManager
+import android.os.Build
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.core.app.NotificationCompat
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.*
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.github.mikephil.charting.data.Entry
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import java.io.IOException
+import java.time.LocalDate
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration.Companion.seconds
+
+@RequiresApi(Build.VERSION_CODES.Q)
+class HotspotViewModel(application: Application, private val dataStore: DataStore) :
+ AndroidViewModel(application) {
+ // ----- DataStore Keys -----
+ companion object {
+ val SSID_KEY = stringPreferencesKey("ssid")
+ val PASSWORD_KEY = stringPreferencesKey("password")
+ val AUTO_SHUTDOWN_ENABLED_KEY = booleanPreferencesKey("auto_shutdown_enabled")
+ val IDLE_TIMEOUT_MINUTES_KEY = intPreferencesKey("idle_timeout_minutes")
+ private val BLOCKED_MAC_ADDRESSES_KEY = stringSetPreferencesKey("blocked_mac_addresses")
+ private val DEVICE_ALIAS_KEY = stringPreferencesKey("device_aliases")
+ private val WIFI_LOCK_ENABLED_KEY = booleanPreferencesKey("wifi_lock_enabled")
+ private val _deviceAliases = MutableStateFlow