changed vpn service to ServiceViewModel
This commit is contained in:
parent
e0061c1277
commit
91042c083f
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@ -3,15 +3,19 @@
|
|||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
@ -27,21 +31,27 @@
|
|||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@ -3,6 +3,7 @@ plugins {
|
|||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
id("com.google.protobuf") version "0.9.4"
|
id("com.google.protobuf") version "0.9.4"
|
||||||
|
id("com.google.devtools.ksp") version "2.0.0-1.0.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -85,6 +86,10 @@ dependencies {
|
|||||||
implementation("androidx.navigation:navigation-compose:2.9.0")
|
implementation("androidx.navigation:navigation-compose:2.9.0")
|
||||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||||
|
|
||||||
|
implementation("androidx.room:room-runtime:2.7.2")
|
||||||
|
ksp("androidx.room:room-compiler:2.7.2")
|
||||||
|
implementation("androidx.room:room-ktx:2.7.2")
|
||||||
|
|
||||||
// implementation(libs.bcprov.jdk18on)
|
// implementation(libs.bcprov.jdk18on)
|
||||||
implementation(libs.bcpkix.jdk18on)
|
implementation(libs.bcpkix.jdk18on)
|
||||||
// implementation(files("libs/org.asxalex.sdlan/sdlan-1.0-SNAPSHOT.jar"))
|
// implementation(files("libs/org.asxalex.sdlan/sdlan-1.0-SNAPSHOT.jar"))
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.jihe.punchnet
|
package com.jihe.punchnet
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -7,9 +8,11 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -22,46 +25,54 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
|
import com.jihe.punchnet.screen.MainApp
|
||||||
import com.jihe.punchnet.ui.theme.PunchnetTheme
|
import com.jihe.punchnet.ui.theme.PunchnetTheme
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private val TAG = "MainActivity"
|
private val TAG = "MainActivity"
|
||||||
|
|
||||||
private val vpnPermissionLauncher = registerForActivityResult(
|
private val viewModel: RouteViewModel by viewModels {
|
||||||
ActivityResultContracts.StartActivityForResult()
|
object: ViewModelProvider.Factory {
|
||||||
) { result ->
|
@Suppress("UNCHECKED_CAST")
|
||||||
when(result.resultCode) {
|
override fun<T: ViewModel> create(modelClass: Class<T>): T {
|
||||||
RESULT_OK -> {
|
return RouteViewModel(application) as T
|
||||||
// granted permission, start service
|
|
||||||
}
|
|
||||||
RESULT_CANCELED -> {
|
|
||||||
Toast.makeText(this, "VPN permission denied", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val serviceModel: ServiceViewModel by viewModels {
|
||||||
|
object: ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun<T: ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return ServiceViewModel(application) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
fun prepareAndStartVPN() {
|
fun prepareAndStartVPN() {
|
||||||
val intent = VpnService.prepare(this)
|
val intent = VpnService.prepare(this)
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
vpnPermissionLauncher.launch(intent)
|
vpnPermissionLauncher.launch(intent)
|
||||||
} else {
|
} else {
|
||||||
startVpnService()
|
startVpnService(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
fun stopVpnService() {
|
/*
|
||||||
Log.d("STOP PUNCHNET", "stopping PUNCHNET")
|
private fun startVpnService(context: Context) {
|
||||||
startService(Intent(this@MainActivity, PunchnetService::class.java).also { it.action=PunchnetService.ACTION_DISCONNECT })
|
val intent = Intent(context, PunchnetService::class.java)
|
||||||
// stopService(Intent(this, PunchnetService::class.java))
|
|
||||||
Log.d("STOPPED PUNCHNET", "stopping PUNCHNET")
|
|
||||||
// Toast.makeText(this, "VPN service stopped", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startVpnService() {
|
|
||||||
val intent = Intent(this, PunchnetService::class.java)
|
|
||||||
intent.putExtra("argument", PunchnetServiceArgument(
|
intent.putExtra("argument", PunchnetServiceArgument(
|
||||||
"",
|
"",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@ -77,14 +88,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
Toast.makeText(this, "VPN service started", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "VPN service started", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
Log.d("DIR", "filesdir = ${this.filesDir}")
|
Log.d("DIR", "filesdir = ${this.filesDir}")
|
||||||
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
PunchnetTheme {
|
PunchnetTheme {
|
||||||
|
MainApp(serviceModel, viewModel, rememberNavController())
|
||||||
|
/*
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
StartStop(
|
StartStop(
|
||||||
R.string.start_vpn,
|
R.string.start_vpn,
|
||||||
@ -93,11 +109,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
prepareAndStartVPN()
|
prepareAndStartVPN()
|
||||||
},
|
},
|
||||||
onStop = {
|
onStop = {
|
||||||
stopVpnService()
|
// stopVpnService()
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(innerPadding)
|
modifier = Modifier.padding(innerPadding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
app/src/main/java/com/jihe/punchnet/data/RouteItem.kt
Normal file
109
app/src/main/java/com/jihe/punchnet/data/RouteItem.kt
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package com.jihe.punchnet.data
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.Update
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Entity(tableName = "routes")
|
||||||
|
data class RouteItem (
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val id: Long = 0,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "net_ip")
|
||||||
|
val net_ip: Int,
|
||||||
|
// mask int, Intof(255,255,255,0
|
||||||
|
@ColumnInfo(name = "mask_ip")
|
||||||
|
val mask_ip: Int,
|
||||||
|
@ColumnInfo(name = "gateway")
|
||||||
|
val gateway: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RouteDAO {
|
||||||
|
@Insert
|
||||||
|
suspend fun insert(item: RouteItem): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun update(item: RouteItem)
|
||||||
|
|
||||||
|
@Query("DELETE FROM routes WHERE id = :id")
|
||||||
|
suspend fun deleteById(id: Long)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM routes")
|
||||||
|
fun getAll(): Flow<List<RouteItem>>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Database(entities = [RouteItem::class], version = 1)
|
||||||
|
abstract class AppDatabase: RoomDatabase() {
|
||||||
|
abstract fun routeDao(): RouteDAO
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: AppDatabase? = null
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): AppDatabase {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val instance = Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
"app_database"
|
||||||
|
).build()
|
||||||
|
INSTANCE = instance
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RouteRepository(private val routeDao: RouteDAO) {
|
||||||
|
val all_routes: Flow<List<RouteItem>> = routeDao.getAll()
|
||||||
|
|
||||||
|
suspend fun insert(item: RouteItem) {
|
||||||
|
routeDao.insert(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun update(item: RouteItem) {
|
||||||
|
routeDao.update(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteById(id: Long) {
|
||||||
|
routeDao.deleteById(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RouteViewModel(application: Application): AndroidViewModel(application) {
|
||||||
|
private val repository: RouteRepository
|
||||||
|
|
||||||
|
init {
|
||||||
|
val routeDao = AppDatabase.getDatabase(application).routeDao()
|
||||||
|
repository = RouteRepository(routeDao)
|
||||||
|
}
|
||||||
|
|
||||||
|
val allRoutes: Flow<List<RouteItem>> = repository.all_routes
|
||||||
|
|
||||||
|
fun insert(item: RouteItem) = viewModelScope.launch {
|
||||||
|
repository.insert(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(item: RouteItem) = viewModelScope.launch {
|
||||||
|
repository.update(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteById(id: Long) = viewModelScope.launch {
|
||||||
|
repository.deleteById(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
75
app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt
Normal file
75
app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package com.jihe.punchnet.data
|
||||||
|
|
||||||
|
import android.app.Activity.RESULT_CANCELED
|
||||||
|
import android.app.Activity.RESULT_OK
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.VpnService
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.core.content.ContextCompat.startForegroundService
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import com.jihe.punchnet.PunchnetService
|
||||||
|
import com.jihe.punchnet.PunchnetServiceArgument
|
||||||
|
import com.jihe.punchnet.RouteInfo
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
class ServiceViewModel(application: Application): AndroidViewModel(application) {
|
||||||
|
private val _isRunning = mutableStateOf(false)
|
||||||
|
val isRunning: State<Boolean> = _isRunning
|
||||||
|
|
||||||
|
private val _hasVpnPermission = mutableStateOf(false)
|
||||||
|
val hasVpnPermission: State<Boolean> = _hasVpnPermission
|
||||||
|
|
||||||
|
fun setVPNPermission(isGranted: Boolean) {
|
||||||
|
_hasVpnPermission.value = isGranted
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startService(context: Context, noPermissionCallback: (Intent)->Unit) {
|
||||||
|
val intent = VpnService.prepare(context)
|
||||||
|
if (intent != null) {
|
||||||
|
noPermissionCallback(intent)
|
||||||
|
} else {
|
||||||
|
startVpnService(context)
|
||||||
|
_isRunning.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startVpnService(context: Context) {
|
||||||
|
val intent = Intent(context, PunchnetService::class.java)
|
||||||
|
intent.putExtra("argument", PunchnetServiceArgument(
|
||||||
|
"",
|
||||||
|
arrayOf(
|
||||||
|
RouteInfo(
|
||||||
|
"192.168.80.0/24", "10.211.188.2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
Toast.makeText(context, "VPN service started", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun stopVpnService(context: Context) {
|
||||||
|
Log.d("STOP PUNCHNET", "stopping PUNCHNET")
|
||||||
|
context.startService(Intent(context, PunchnetService::class.java).also { it.action=
|
||||||
|
PunchnetService.ACTION_DISCONNECT })
|
||||||
|
// stopService(Intent(this, PunchnetService::class.java))
|
||||||
|
Log.d("STOPPED PUNCHNET", "stopping PUNCHNET")
|
||||||
|
_isRunning.value = false
|
||||||
|
// Toast.makeText(this, "VPN service stopped", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package com.jihe.punchnet.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.jihe.punchnet.R
|
||||||
|
import com.jihe.punchnet.RouteInfo
|
||||||
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppNavHost2(
|
||||||
|
serviceViewModel: ServiceViewModel,
|
||||||
|
routeViewModel: RouteViewModel = viewModel(),
|
||||||
|
navController: NavHostController,
|
||||||
|
paddingValues: PaddingValues
|
||||||
|
) {
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = Screen.MainScreen.route,
|
||||||
|
modifier = Modifier.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
composable(Screen.MainScreen.route) {
|
||||||
|
HomeScreen(serviceViewModel, routeViewModel)
|
||||||
|
}
|
||||||
|
composable(Screen.RouteScreen.route) {
|
||||||
|
CustomHeaderScreen(
|
||||||
|
"光强计"
|
||||||
|
) {
|
||||||
|
// LightScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composable(Screen.ProfileScreen.route) {
|
||||||
|
// Profile(dbdao, navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/src/main/java/com/jihe/punchnet/screen/BottomNavBar.kt
Normal file
66
app/src/main/java/com/jihe/punchnet/screen/BottomNavBar.kt
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package com.jihe.punchnet.screen
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Build
|
||||||
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppBottomNavigation(
|
||||||
|
navController: NavHostController
|
||||||
|
) {
|
||||||
|
// val currentRoute = navController.currentBackStackEntry?.destination?.route
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentRoute by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
navBackStackEntry?.destination?.route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationBar {
|
||||||
|
bottomNavItems.forEach { screen ->
|
||||||
|
NavigationBarItem(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = when (screen) {
|
||||||
|
Screen.MainScreen -> Icons.Default.Home
|
||||||
|
Screen.RouteScreen -> Icons.Default.Menu
|
||||||
|
// Screen.Search -> Icons.Default.Search
|
||||||
|
Screen.ProfileScreen -> Icons.Default.Person
|
||||||
|
},
|
||||||
|
contentDescription = screen.route
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = screen.route.replaceFirstChar { it.uppercase() },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = currentRoute == screen.route,
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(screen.route) {
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState= true
|
||||||
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("navigate to ${screen.route}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
164
app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
Normal file
164
app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package com.jihe.punchnet.screen
|
||||||
|
|
||||||
|
import android.app.Activity.RESULT_CANCELED
|
||||||
|
import android.app.Activity.RESULT_OK
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.jihe.punchnet.PunchnetService
|
||||||
|
import com.jihe.punchnet.PunchnetServiceArgument
|
||||||
|
import com.jihe.punchnet.R
|
||||||
|
import com.jihe.punchnet.RouteInfo
|
||||||
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomeScreen(
|
||||||
|
serviceViewModel: ServiceViewModel,
|
||||||
|
routeViewModel: RouteViewModel,
|
||||||
|
// started: MutableState<Boolean>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val vpnLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) {result ->
|
||||||
|
when(result.resultCode) {
|
||||||
|
RESULT_OK -> {
|
||||||
|
serviceViewModel.setVPNPermission(true)
|
||||||
|
}
|
||||||
|
RESULT_CANCELED -> {
|
||||||
|
serviceViewModel.setVPNPermission(false)
|
||||||
|
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomHeaderScreen(
|
||||||
|
"",
|
||||||
|
onBack = null,
|
||||||
|
onMenu = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Settings,
|
||||||
|
contentDescription = "settings",
|
||||||
|
modifier = Modifier.fillMaxHeight()
|
||||||
|
)
|
||||||
|
// NavListMenu(navController, dbdao)
|
||||||
|
/*
|
||||||
|
navController.navigate(
|
||||||
|
Screen.ActionDetail.route.replace(
|
||||||
|
"{action_name}",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.padding(top=50.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.punchnet_log),
|
||||||
|
contentDescription = "logo",
|
||||||
|
modifier = Modifier.size(150.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Connecting the Infinite",
|
||||||
|
fontSize = 30.sp,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(top=24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Welcome to PunchNet",
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = Modifier.padding(top=8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (serviceViewModel.isRunning.value) {
|
||||||
|
// if is running, should stop service
|
||||||
|
serviceViewModel.stopVpnService(context)
|
||||||
|
} else {
|
||||||
|
serviceViewModel.startService(
|
||||||
|
context,
|
||||||
|
{intent->
|
||||||
|
vpnLauncher.launch(intent)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// started.value = !started.value
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
modifier = Modifier.padding(top=48.dp)
|
||||||
|
.width(120.dp)
|
||||||
|
.height(40.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
if (serviceViewModel.isRunning.value) {
|
||||||
|
"停止"
|
||||||
|
} else {
|
||||||
|
"启动"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// BasicCardList(dbdao = dbdao, navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun startVpnService(context: Context) {
|
||||||
|
val intent = Intent(context, PunchnetService::class.java)
|
||||||
|
|
||||||
|
intent.putExtra("argument", PunchnetServiceArgument(
|
||||||
|
"",
|
||||||
|
arrayOf(
|
||||||
|
RouteInfo(
|
||||||
|
"192.168.80.0/24", "10.211.188.2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
Toast.makeText(context, "VPN service started", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package com.jihe.punchnet.screen
|
package com.jihe.punchnet.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -12,22 +11,51 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
|
|
||||||
|
sealed class Screen(val route: String) {
|
||||||
|
object MainScreen: Screen("main");
|
||||||
|
object RouteScreen: Screen("routes")
|
||||||
|
object ProfileScreen: Screen("profile")
|
||||||
|
}
|
||||||
|
|
||||||
|
val bottomNavItems = listOf(
|
||||||
|
Screen.MainScreen,
|
||||||
|
Screen.RouteScreen,
|
||||||
|
Screen.ProfileScreen,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainApp(
|
fun MainApp(
|
||||||
|
serviceViewModel: ServiceViewModel = viewModel(),
|
||||||
|
routeModel: RouteViewModel = viewModel(),
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
AppBottomNavigation(navController = navController)
|
||||||
|
}
|
||||||
|
){ paddingValues ->
|
||||||
|
/*
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.punchnet_log),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(paddingValues).size(200.dp)
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
AppNavHost2(serviceViewModel, routeModel, navController, paddingValues)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -38,13 +66,13 @@ fun CustomHeaderScreen(
|
|||||||
body: @Composable () -> Unit,
|
body: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Box (
|
Box (
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp)
|
.height(56.dp)
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer),
|
.background(MaterialTheme.colorScheme.background),
|
||||||
//.padding(horizontal = 16.dp),
|
//.padding(horizontal = 16.dp),
|
||||||
contentAlignment = Alignment.CenterStart
|
contentAlignment = Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
@ -78,23 +106,9 @@ fun CustomHeaderScreen(
|
|||||||
) {
|
) {
|
||||||
if (onMenu != null) {
|
if (onMenu != null) {
|
||||||
onMenu()
|
onMenu()
|
||||||
/*
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
onMenu()
|
|
||||||
}
|
|
||||||
.fillMaxSize(),
|
|
||||||
imageVector = Icons.Filled.Menu,
|
|
||||||
contentDescription = "setting",
|
|
||||||
// tint = MaterialTheme.colorScheme.onPrimary
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,10 @@ import kotlinx.coroutines.async
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
const val BroadcastIP = 0xFFFFFFFF
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
val BroadcastMac = ubyteArrayOf(0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu).toByteArray()
|
||||||
|
|
||||||
class ARPTable(
|
class ARPTable(
|
||||||
) {
|
) {
|
||||||
val routeTable: RouteTable = RouteTable()
|
val routeTable: RouteTable = RouteTable()
|
||||||
@ -36,15 +40,17 @@ class ARPTable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMacFromIP(ip: Int): ByteArray? {
|
fun getMacFromIP(ip: Int): Pair<ByteArray?, Int> {
|
||||||
val value = content.get(ip)
|
val gw = routeTable.getGateway(ip)
|
||||||
if (value == null) {
|
if (gw == null) {
|
||||||
println("content table size is: ${content.size}")
|
// not found in route table, just use the ip
|
||||||
for (k in content.keys()) {
|
val value = content.get(ip)
|
||||||
println("content has key: ${ipToString(k)}")
|
return Pair(value?.mac, ip)
|
||||||
}
|
} else {
|
||||||
|
// gw not null, try find the gw's mac
|
||||||
|
val value = content.get(gw)
|
||||||
|
return Pair(value?.mac, gw)
|
||||||
}
|
}
|
||||||
return value?.mac
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToARPTable(ip: Int, mac: ByteArray) {
|
fun addToARPTable(ip: Int, mac: ByteArray) {
|
||||||
|
|||||||
@ -175,13 +175,13 @@ interface IfaceTun: Iface {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val arpinfo = arpTable.getMacFromIP(dstip)
|
val (arpinfo, gwip) = arpTable.getMacFromIP(dstip)
|
||||||
if (arpinfo == null) {
|
if (arpinfo == null) {
|
||||||
println("arp info is null")
|
println("arp info is null")
|
||||||
// arp not found
|
// arp not found
|
||||||
arpWaitList.addToWaitList(dstip, data)
|
arpWaitList.addToWaitList(gwip, data)
|
||||||
TerminalLogger.debugf { "added to wait list" }
|
TerminalLogger.debugf { "added to wait list" }
|
||||||
sendArpRequest(node, dstip)
|
sendArpRequest(node, gwip)
|
||||||
TerminalLogger.debugf { "sent arp request" }
|
TerminalLogger.debugf { "sent arp request" }
|
||||||
} else {
|
} else {
|
||||||
println("mac is ${macToString(arpinfo.toByteString())}")
|
println("mac is ${macToString(arpinfo.toByteString())}")
|
||||||
|
|||||||
@ -90,7 +90,7 @@ class RouteTable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGeteway(ip: Int): Int? {
|
fun getGateway(ip: Int): Int? {
|
||||||
try {
|
try {
|
||||||
lock.readLock().lock()
|
lock.readLock().lock()
|
||||||
for (item in routeInfo) {
|
for (item in routeInfo) {
|
||||||
|
|||||||
648
app/src/main/res/drawable/punchnet_log.xml
Normal file
648
app/src/main/res/drawable/punchnet_log.xml
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user