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..b268ef3
--- /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/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/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /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 b71762f..351ae6e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,8 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
+// id "kotlin-serialization' // Make sure this is included
+
}
@@ -46,6 +48,8 @@ android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ excludes += "META-INF/INDEX.LIST"
+ excludes += "META-INF/io.netty.versions.properties"
}
}
}
@@ -61,6 +65,7 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.compose.material)
+ implementation(libs.play.services.dtdi)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -84,19 +89,30 @@ dependencies {
implementation ("androidx.work:work-runtime-ktx:2.7.1")
- implementation ("androidx.navigation:navigation-compose:2.8.3")
-
-
-
-// implementation(libs.charts)
+ implementation ("androidx.navigation:navigation-compose:2.8.3")
implementation (libs.kotlinx.serialization.json)
// implementation ("com.github.bumptech.glide:glide:4.15.1")
implementation(libs.timber)
// implementation (libs.mpandroidchart)
+ //proxy
+ // Netty dependencies
+// implementation ("io.netty:netty-all:4.1.95.Final")
+// implementation ("io.netty:netty-all:4.1.68.Final")
+ // Netty dependencies
+ implementation ("io.netty:netty-handler:4.1.68.Final")
+ implementation ("io.netty:netty-codec-socks:4.1.68.Final")
+ implementation ("io.netty:netty-transport:4.1.68.Final")
+ implementation ("io.netty:netty-transport-native-epoll:4.1.68.Final")
+
+
+
+// implementation(libs.charts)
+
+
diff --git a/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt b/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt
index 9f38982..15dd8c5 100644
--- a/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt
+++ b/app/src/main/java/com/example/wifip2photspot/ConnectedDevicesSection.kt
@@ -33,92 +33,6 @@ 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,
@@ -267,147 +181,3 @@ fun LazyListScope.connectedDevicesSection(
}
}
-//
-//@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))
-}
\ 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
index c6fd6e5..99ed3e2 100644
--- a/app/src/main/java/com/example/wifip2photspot/DeviceInfo.kt
+++ b/app/src/main/java/com/example/wifip2photspot/DeviceInfo.kt
@@ -9,7 +9,11 @@ data class DeviceInfo(
val alias: String? = null,
val connectionTime: Long = System.currentTimeMillis(),
val ipAddress: String? = null,
- val isBlocked: Boolean = false
+ val isBlocked: Boolean = false,
+// val connectionTime: Long,
+ var disconnectionTime: Long = 0L,
+ var dataSent: Long = 0L,
+ var dataReceived: Long = 0L
)
diff --git a/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt b/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt
index 3f9cb6f..0e690f4 100644
--- a/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt
+++ b/app/src/main/java/com/example/wifip2photspot/HotspotControlSection.kt
@@ -25,8 +25,9 @@ fun HotspotControlSection(
ssidInput: String,
passwordInput: String,
selectedBand: String,
+ socksPortInput: String,
onStartTapped: () -> Unit,
- onStopTapped: () -> Unit,
+ onStopTapped: () -> Unit
) {
val context = LocalContext.current
@@ -45,11 +46,11 @@ fun HotspotControlSection(
if (isProcessing) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
- color = MaterialTheme.colorScheme.onPrimary,
- strokeWidth = 2.dp
+ strokeWidth = 2.dp,
+ color = MaterialTheme.colorScheme.onPrimary
)
} else {
- Text("Start Hotspot")
+ Text("Start Tethering")
}
}
@@ -78,11 +79,11 @@ fun HotspotControlSection(
if (isProcessing) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
- color = MaterialTheme.colorScheme.onPrimary,
- strokeWidth = 2.dp
+ strokeWidth = 2.dp,
+ color = MaterialTheme.colorScheme.onPrimary
)
} else {
- Text("Stop Hotspot")
+ Text("Stop Tethering")
}
}
}
diff --git a/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt b/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt
index ce5e85f..8eb5e54 100644
--- a/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt
+++ b/app/src/main/java/com/example/wifip2photspot/HotspotViewModel.kt
@@ -15,6 +15,7 @@ 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.WifiP2pInfo
import android.net.wifi.p2p.WifiP2pManager
import android.os.BatteryManager
import android.os.Build
@@ -31,20 +32,18 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
+import com.example.wifip2photspot.Proxy.SocksProxyServer
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
+import com.example.wifip2photspot.ClientInfo
@RequiresApi(Build.VERSION_CODES.Q)
class HotspotViewModel(application: Application, private val dataStore: DataStore) :
@@ -72,6 +71,7 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
// Other preference keys...
}
+
// Enum for themes
enum class AppTheme {
DEFAULT, DARK, AMOLED, CUSTOM
@@ -121,12 +121,11 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
val isDarkTheme: StateFlow = _isDarkTheme.asStateFlow()
-
// ----- Log Entries -----
private val _logEntries = MutableStateFlow>(emptyList())
val logEntries: StateFlow> = _logEntries.asStateFlow()
-// // ----- UI Events -----
+ // // ----- UI Events -----
private val _eventFlow = MutableSharedFlow()
val eventFlow: SharedFlow = _eventFlow.asSharedFlow()
@@ -135,6 +134,7 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
val connectedDevices: StateFlow> = _connectedDevices.asStateFlow()
private val _connectedDeviceInfos = MutableStateFlow>(emptyList())
val connectedDeviceInfos: StateFlow> = _connectedDeviceInfos.asStateFlow()
+
//visible password
private var passwordVisible by mutableStateOf(false)
@@ -154,7 +154,6 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
val proxyPort: StateFlow = _proxyPort.asStateFlow()
-
// StateFlows to hold the lists
private val _allowedMacAddresses = MutableStateFlow>(emptySet())
@@ -196,7 +195,8 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
val notificationSoundEnabled: StateFlow = _notificationSoundEnabled.asStateFlow()
private val _notificationVibrationEnabled = MutableStateFlow(true)
- val notificationVibrationEnabled: StateFlow = _notificationVibrationEnabled.asStateFlow()
+ val notificationVibrationEnabled: StateFlow =
+ _notificationVibrationEnabled.asStateFlow()
//battery level
private val _batteryLevel = MutableStateFlow(100)
@@ -238,61 +238,29 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
// Wi-Fi Lock Variables
private var wifiLock: WifiManager.WifiLock? = null
+
// Wi-Fi Lock Enabled StateFlow
private val _wifiLockEnabled = MutableStateFlow(false)
val wifiLockEnabled: StateFlow = _wifiLockEnabled.asStateFlow()
+ private val _socksPort = MutableStateFlow("1080")
+ val socksPort: StateFlow = _socksPort
+ private var socksProxyServer: SocksProxyServer? = null
-// private var proxyServer: proxyServer? = null
-//
-// // Start Proxy Server
-// fun startProxyServer() {
-// if (_isProxyRunning.value) {
-// updateLog("Proxy server is already running.")
-// return
-// }
-//
-// proxyServer = proxyServer(_proxyPort.value)
-// try {
-// proxyServer?.start()
-// _isProxyRunning.value = true
-// updateLog("Proxy server started on port ${_proxyPort.value}")
-// } catch (e: IOException) {
-// updateLog("Failed to start proxy server: ${e.message}")
-// }
-// }
-
- // Stop Proxy Server
-// fun stopProxyServer() {
-// if (!_isProxyRunning.value) {
-// updateLog("Proxy server is not running.")
-// return
-// }
-//
-// proxyServer?.stop()
-// proxyServer = null
-// _isProxyRunning.value = false
-// updateLog("Proxy server stopped.")
-// }
-
- // Update Proxy Port
- fun updateProxyPort(port: Int) {
- _proxyPort.value = port
+ fun updateSocksPort(newPort: String) {
+ _socksPort.value = newPort
}
-// fun startVpn(context: Context) {
-// val intent = Intent(context, MyVpnService::class.java)
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-// context.startForegroundService(intent)
-// } else {
-// context.startService(intent)
-// }
-// }
+ // Create a StateFlow to hold the IP address
+ private val _serverIpAddress = MutableStateFlow("")
+ val serverIpAddress: StateFlow = _serverIpAddress
+ private val _connectedClients = MutableStateFlow>(emptyList())
+ val connectedClients: StateFlow> = _connectedClients
+ /////****************************************************************************************////////////////////////////////
init {
-
// ----- Load SSID and Password from DataStore -----
viewModelScope.launch {
dataStore.data
@@ -317,7 +285,6 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
}
}
// ----- Start Monitoring Network Speeds -----
-// In the coroutine where you update uploadSpeed and downloadSpeed
viewModelScope.launch {
var time = 0f
while (true) {
@@ -366,21 +333,14 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
_deviceAliases.value = Json.decodeFromString(aliasesJson)
}
}
-// Load historical data usage from DataStore
-// viewModelScope.launch {
-// dataStore.data.collect { preferences ->
-// val json = preferences[DATA_USAGE_KEY] ?: "[]"
-// _historicalDataUsage.value = Json.decodeFromString(json)
-// }
-// }
+ // Periodically update clients' data usage
+ viewModelScope.launch {
+ while (true) {
+ delay(1000) // Update every second
+ _connectedClients.value = _connectedClients.value.toList()
+ }
+ }
monitorNetworkSpeeds()
-//
-// viewModelScope.launch {
-// dataStore.data.collect { preferences ->
-// _dataUsageThreshold.value = preferences[DATA_USAGE_THRESHOLD_KEY] ?: 0L
-// }
-// }
-
}
@Suppress("EXPERIMENTAL_API_USAGE")
@@ -467,6 +427,19 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
// ----- Function to Set Wi-Fi P2P Enabled State -----
+ fun updateConnectionInfo(wifiP2pInfo: WifiP2pInfo) {
+ if (wifiP2pInfo.groupFormed && wifiP2pInfo.isGroupOwner) {
+ _serverIpAddress.value = wifiP2pInfo.groupOwnerAddress.hostAddress
+ }
+ }
+
+ // Add this function to retrieve the IP address
+ private fun getGroupOwnerIpAddress(): String {
+ // The Group Owner's IP is typically 192.168.49.1
+ // Alternatively, retrieve it from the WifiP2pInfo
+ return "192.168.49.1"
+ }
+
fun setWifiP2pEnabled(enabled: Boolean) {
_isWifiP2pEnabled.value = enabled
updateLog("Wi-Fi P2P Enabled: $enabled")
@@ -505,7 +478,8 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
fun onButtonStartTapped(
ssidInput: String,
passwordInput: String,
- selectedBand: String
+ selectedBand: String,
+ socksPortInput: String
) {
onButtonStopTapped()
viewModelScope.launch {
@@ -547,6 +521,13 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
else -> WifiP2pConfig.GROUP_OWNER_BAND_AUTO
}
+ // Validate SOCKS Proxy Port
+ val port = socksPortInput.toIntOrNull()
+ if (port == null || port !in 1024..65535) {
+ _eventFlow.emit(UiEvent.ShowToast("Invalid SOCKS Proxy Port"))
+ return@launch
+ }
+
val config = WifiP2pConfig.Builder()
.setNetworkName(ssid)
.setPassphrase(passwordTrimmed)
@@ -573,6 +554,24 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
_isProcessing.value = false
_eventFlow.emit(UiEvent.ShowToast("Hotspot started successfully."))
// _eventFlow.emit(UiEvent.StartProxyService)
+ // Start the SOCKS Proxy Server with callbacks
+ val port = socksPortInput.toIntOrNull() ?: 1080
+ socksProxyServer = SocksProxyServer(port,
+ onClientConnected = { clientInfo ->
+ viewModelScope.launch {
+ _connectedClients.value += clientInfo
+ updateLog("Client connected: ${clientInfo.ipAddress}")
+ }
+ },
+ onClientDisconnected = { clientInfo ->
+ viewModelScope.launch {
+ _connectedClients.value -= clientInfo
+ updateLog("Client disconnected: ${clientInfo.ipAddress}")
+ }
+ })
+ socksProxyServer?.start()
+ updateLog("SOCKS Proxy Server started on port $port")
+
acquireWifiLock()
showHotspotStatusNotification()
@@ -584,6 +583,7 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
}
}
+
override fun onFailure(reason: Int) {
val reasonStr = when (reason) {
WifiP2pManager.ERROR -> "General error"
@@ -605,6 +605,8 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
_isHotspotEnabled.value = false
_isProcessing.value = false
_eventFlow.emit(UiEvent.ShowToast("Exception occurred: ${e.message}"))
+ updateLog("Failed to start SOCKS Proxy Server: ${e.message}")
+ _eventFlow.emit(UiEvent.ShowToast("Failed to start SOCKS Proxy Server"))
}
}
}
@@ -632,12 +634,18 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
_isProcessing.value = false
_eventFlow.emit(UiEvent.ShowToast("Hotspot stopped successfully."))
// _eventFlow.emit(UiEvent.StopProxyService)
- releaseWifiLock()
+
+ // Stop the SOCKS Proxy Server
+ socksProxyServer?.stop()
+ socksProxyServer = null
+ updateLog("SOCKS Proxy Server stopped")
+
+ // Clear the server IP address
+ _serverIpAddress.value = ""
removeHotspotStatusNotification()
- // Stop monitoring
- // Cancel idle monitoring coroutine if necessary
_remainingIdleTime.value = 0L
+
}
// saveSessionDataUsage()
}
@@ -675,10 +683,14 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
//*****************************************************Fine****************************************
fun startNetworkMonitoring() {
- val connectivityManager = getApplication().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val connectivityManager =
+ getApplication().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback = object : ConnectivityManager.NetworkCallback() {
- override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
val linkDownstreamBandwidthKbps = networkCapabilities.linkDownstreamBandwidthKbps
val linkUpstreamBandwidthKbps = networkCapabilities.linkUpstreamBandwidthKbps
@@ -700,7 +712,8 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
}
fun startBatteryMonitoring() {
- val batteryManager = getApplication().getSystemService(Context.BATTERY_SERVICE) as BatteryManager
+ val batteryManager =
+ getApplication().getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
val batteryStatus = getApplication().registerReceiver(null, intentFilter)
batteryStatus?.let {
@@ -757,8 +770,12 @@ class HotspotViewModel(application: Application, private val dataStore: DataStor
fun acquireWifiLock() {
if (_wifiLockEnabled.value) {
- val wifiManager = getApplication().getSystemService(Context.WIFI_SERVICE) as WifiManager
- wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "WiFiP2PHotspotLock")
+ val wifiManager =
+ getApplication().getSystemService(Context.WIFI_SERVICE) as WifiManager
+ wifiLock = wifiManager.createWifiLock(
+ WifiManager.WIFI_MODE_FULL_HIGH_PERF,
+ "WiFiP2PHotspotLock"
+ )
wifiLock?.acquire()
}
}
diff --git a/app/src/main/java/com/example/wifip2photspot/ImprovedHeader.kt b/app/src/main/java/com/example/wifip2photspot/ImprovedHeader.kt
index fcf0e0f..a9db878 100644
--- a/app/src/main/java/com/example/wifip2photspot/ImprovedHeader.kt
+++ b/app/src/main/java/com/example/wifip2photspot/ImprovedHeader.kt
@@ -1,8 +1,8 @@
package com.example.wifip2photspot
-
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.*
@@ -17,6 +17,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.filled.HelpOutline
//@OptIn(ExperimentalMaterial3Api::class)
//@Composable
@@ -27,7 +29,9 @@ import androidx.compose.foundation.layout.padding
fun ImprovedHeader(
isHotspotEnabled: Boolean,
viewModel: HotspotViewModel,
- onSettingsClick: () -> Unit
+ onSettingsClick: () -> Unit,
+ onHelpClick: () -> Unit
+
) {
var showMenu by remember { mutableStateOf(false) }
@@ -47,8 +51,16 @@ fun ImprovedHeader(
}
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem(
- text = { Text("Help") },
- onClick = { /* Navigate to help */ }
+ text = {
+ Row {
+ Icon(Icons.Default.HelpOutline, contentDescription = "Help")
+ Spacer(Modifier.width(8.dp))
+ Text("Help")
+ }
+ },
+ onClick = {
+ onHelpClick()
+ }
)
DropdownMenuItem(
text = { Text("About") },
diff --git a/app/src/main/java/com/example/wifip2photspot/InputFieldsSection.kt b/app/src/main/java/com/example/wifip2photspot/InputFieldsSection.kt
index d535e18..c50c35f 100644
--- a/app/src/main/java/com/example/wifip2photspot/InputFieldsSection.kt
+++ b/app/src/main/java/com/example/wifip2photspot/InputFieldsSection.kt
@@ -26,6 +26,8 @@ fun InputFieldsSection(
passwordInput: TextFieldValue,
onPasswordChange: (TextFieldValue) -> Unit,
isHotspotEnabled: Boolean,
+ socksPortInput: String,
+ onSocksPortChange: (String) -> Unit,
// proxyPort: Int,
// onProxyPortChange: (Int) -> Unit,
// selectedBand: String,
@@ -34,6 +36,11 @@ fun InputFieldsSection(
) {
// State for password visibility
var passwordVisible by rememberSaveable { mutableStateOf(false) }
+ // Compute error states
+// val ssidErrorState = ssidInput.isEmpty()
+// val passwordErrorState = passwordInput.length !in 8..63
+ val socksPortErrorState = socksPortInput.isEmpty() || socksPortInput.toIntOrNull() == null
+
// Compute error states
val ssidErrorState = ssidInput.text.isEmpty()
@@ -131,28 +138,41 @@ fun InputFieldsSection(
}
)
- // Proxy Port Input Field
-
-// // Proxy Port Field
-// if (!isHotspotEnabled) {
-// Spacer(modifier = Modifier.height(8.dp))
-// Row(verticalAlignment = Alignment.CenterVertically) {
-// Text("Proxy Port:", style = MaterialTheme.typography.bodyMedium)
-// Spacer(modifier = Modifier.width(8.dp))
-// OutlinedTextField(
-// value = proxyPort.toString(),
-// onValueChange = { newPort ->
-// newPort.toIntOrNull()?.let {
-// onProxyPortChange(it)
-// }
-// },
-// label = { Text("Port") },
-// singleLine = true,
-// modifier = Modifier.width(100.dp)
-// )
-// }
+ // SOCKS Proxy Port Input Field
+ OutlinedTextField(
+ value = socksPortInput,
+ onValueChange = onSocksPortChange,
+ label = { Text("SOCKS Proxy Port") },
+ placeholder = { Text("1080") },
+ leadingIcon = {
+ Icon(
+ Icons.Default.Dns,
+ contentDescription = "Port Icon"
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp)
+ .alpha(if (!isHotspotEnabled) 1f else ContentAlpha.disabled),
+ enabled = !isHotspotEnabled,
+ singleLine = true,
+ isError = socksPortErrorState,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number
+ ),
+ supportingText = {
+ if (socksPortErrorState) {
+ Text(
+ text = "Invalid port number",
+ color = MaterialTheme.colorScheme.error
+ )
+ }
+ }
+ )
}
}
+ }
+}
// // Band Selection Section
// BandSelection(
@@ -160,9 +180,8 @@ fun InputFieldsSection(
// onBandSelected = onBandSelected,
// bands = bands,
// isHotspotEnabled = isHotspotEnabled
-// )
- }
-}
+// ) }
+
//
//@Composable
diff --git a/app/src/main/java/com/example/wifip2photspot/WiFiP2PHotspotApp.kt b/app/src/main/java/com/example/wifip2photspot/WiFiP2PHotspotApp.kt
index 17328ec..e45af07 100644
--- a/app/src/main/java/com/example/wifip2photspot/WiFiP2PHotspotApp.kt
+++ b/app/src/main/java/com/example/wifip2photspot/WiFiP2PHotspotApp.kt
@@ -44,20 +44,23 @@ import com.example.wifip2photspot.ui.screen.MainScreen
@Composable
fun WiFiP2PHotspotApp(viewModel: HotspotViewModel) {
val navController = rememberNavController()
-
WiFiP2PHotspotNavHost(navController = navController, viewModel = viewModel)
+
+
}
@Composable
fun WiFiP2PHotspotNavHost(navController: NavHostController, viewModel: HotspotViewModel) {
NavHost(navController = navController, startDestination = "main_screen") {
composable("main_screen") {
- MainScreen(navController = navController, viewModel = viewModel)
+ MainScreen(navController = navController, viewModel = viewModel, onHelpClick = { navController.navigate("help") })
}
composable("settings_screen") {
SettingsScreen(navController = navController, viewModel = viewModel)
}
- // Add other destinations if needed
+ composable("help") {
+ HelpScreen(onBack = { navController.popBackStack() })
+ }
}
}
//
diff --git a/app/src/main/java/com/example/wifip2photspot/ui/screen/MainScreen.kt b/app/src/main/java/com/example/wifip2photspot/ui/screen/MainScreen.kt
index feb8396..15710ee 100644
--- a/app/src/main/java/com/example/wifip2photspot/ui/screen/MainScreen.kt
+++ b/app/src/main/java/com/example/wifip2photspot/ui/screen/MainScreen.kt
@@ -36,6 +36,8 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.wifip2photspot.BatteryStatusSection
+import com.example.wifip2photspot.ClientMonitoringSection
+import com.example.wifip2photspot.ConnectionInfoSection
import com.example.wifip2photspot.ConnectionStatusBar
import com.example.wifip2photspot.HotspotControlSection
import com.example.wifip2photspot.HotspotViewModel
@@ -53,11 +55,15 @@ import com.example.wifip2photspot.ui.SettingsScreen
@SuppressLint("StateFlowValueCalledInComposition")
@Composable
-fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel) {
+fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel, onHelpClick: () -> Unit) {
val context = LocalContext.current
val ssid by viewModel.ssid.collectAsState()
val password by viewModel.password.collectAsState()
val selectedBand by viewModel.selectedBand.collectAsState()
+// val socksPort by viewModel.socksPort.collectAsState()
+//
+// val socksPortInput by viewModel.socksPortInput.collectAsState()
+// val onSocksPortChange by viewModel.onSocksPortChange.collectAsState()
val isHotspotEnabled by viewModel.isHotspotEnabled.collectAsState()
val isProcessing by viewModel.isProcessing.collectAsState()
val uploadSpeed by viewModel.uploadSpeed.collectAsState()
@@ -88,8 +94,14 @@ fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel) {
val downloadSpeedEntries by viewModel.downloadSpeedEntries.collectAsState()
val batteryLevel by viewModel.batteryLevel.collectAsState()
- // Proxy server state
- val proxyPort by viewModel.proxyPort.collectAsState()
+ // Collect the server IP address and socks port
+ val serverIpAddress by viewModel.serverIpAddress.collectAsState()
+ val socksPort by viewModel.socksPort.collectAsState()
+
+ val connectedClientsInfo by viewModel.connectedClients.collectAsState()
+// val scaffoldState = rememberScaffoldState()
+
+
// Update ViewModel when text changes
LaunchedEffect(ssidFieldState.text) {
@@ -128,11 +140,14 @@ fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel) {
// Scaffold for overall layout
Scaffold(
+// scaffoldState = scaffoldState,
topBar = {
ImprovedHeader(
isHotspotEnabled = isHotspotEnabled,
viewModel = viewModel,
- onSettingsClick = { navController.navigate("settings_screen") }
+ onSettingsClick = { navController.navigate("settings_screen") },
+ onHelpClick = onHelpClick
+
)
},
content = { paddingValues ->
@@ -152,16 +167,31 @@ fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel) {
item {
Spacer(modifier = Modifier.height(16.dp))
}
-
- if (connectedDeviceInfos.isNotEmpty()) {
+ // Inside your LazyColumn or Column
+ if (isHotspotEnabled) {
item {
- SpeedGraphSection(
- uploadSpeeds = uploadSpeedEntries,
- downloadSpeeds = downloadSpeedEntries
+ ConnectionInfoSection(
+ serverIpAddress = serverIpAddress,
+ socksPort = socksPort
)
}
}
+// if (connectedDeviceInfos.isNotEmpty()) {
+// item {
+// SpeedGraphSection(
+// uploadSpeeds = uploadSpeedEntries,
+// downloadSpeeds = downloadSpeedEntries
+// )
+// }
+// }
+ // Inside your LazyColumn or Column
+ if (connectedClientsInfo.isNotEmpty()) {
+ item {
+ ClientMonitoringSection(clients = connectedClientsInfo)
+ }
+ }
+
// Input Fields and Band Selection
if (connectedDeviceInfos.isEmpty()) {
item {
@@ -174,8 +204,12 @@ fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel) {
onPasswordChange = { newValue ->
passwordFieldState = newValue
},
- isHotspotEnabled = isHotspotEnabled
- )
+ socksPortInput = socksPort,
+ onSocksPortChange = { viewModel.updateSocksPort(it) },
+ isHotspotEnabled = isHotspotEnabled,
+
+
+ )
}
} else {
item {
@@ -203,12 +237,16 @@ fun MainScreen(navController: NavHostController, viewModel: HotspotViewModel) {
ssidInput = ssidFieldState.text,
passwordInput = passwordFieldState.text,
selectedBand = selectedBand,
+ socksPortInput = socksPort,
onStartTapped = {
viewModel.onButtonStartTapped(
ssidInput = ssidFieldState.text.ifBlank { "TetherGuard" },
passwordInput = passwordFieldState.text.ifBlank { "00000000" },
+ socksPortInput = if (socksPort.isBlank()) "1080" else socksPort,
selectedBand = selectedBand,
- )
+
+
+ )
},
onStopTapped = {
viewModel.onButtonStopTapped()
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7b3f839..e66cdec 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,6 +13,7 @@ composeBom = "2024.04.01"
composeMaterial = "1.4.0"
mpandroidchart = "v3.1.0"
timber = "5.0.1"
+playServicesDtdi = "16.0.0-beta02"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -34,6 +35,7 @@ androidx-compose-material = { group = "androidx.wear.compose", name = "compose-m
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
mpandroidchart = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchart" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
+play-services-dtdi = { group = "com.google.android.gms", name = "play-services-dtdi", version.ref = "playServicesDtdi" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }