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" />
|
||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
@ -27,21 +31,27 @@
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
@ -3,6 +3,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
id("com.google.protobuf") version "0.9.4"
|
||||
id("com.google.devtools.ksp") version "2.0.0-1.0.22"
|
||||
}
|
||||
|
||||
android {
|
||||
@ -85,6 +86,10 @@ dependencies {
|
||||
implementation("androidx.navigation:navigation-compose:2.9.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.bcpkix.jdk18on)
|
||||
// implementation(files("libs/org.asxalex.sdlan/sdlan-1.0-SNAPSHOT.jar"))
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.jihe.punchnet
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
@ -7,9 +8,11 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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 kotlinx.coroutines.flow.onEach
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private val TAG = "MainActivity"
|
||||
|
||||
private val vpnPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
when(result.resultCode) {
|
||||
RESULT_OK -> {
|
||||
// granted permission, start service
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
Toast.makeText(this, "VPN permission denied", Toast.LENGTH_SHORT).show()
|
||||
private val viewModel: RouteViewModel by viewModels {
|
||||
object: ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun<T: ViewModel> create(modelClass: Class<T>): T {
|
||||
return RouteViewModel(application) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent != null) {
|
||||
vpnPermissionLauncher.launch(intent)
|
||||
} else {
|
||||
startVpnService()
|
||||
startVpnService(this)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fun stopVpnService() {
|
||||
Log.d("STOP PUNCHNET", "stopping PUNCHNET")
|
||||
startService(Intent(this@MainActivity, PunchnetService::class.java).also { it.action=PunchnetService.ACTION_DISCONNECT })
|
||||
// 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)
|
||||
/*
|
||||
private fun startVpnService(context: Context) {
|
||||
val intent = Intent(context, PunchnetService::class.java)
|
||||
intent.putExtra("argument", PunchnetServiceArgument(
|
||||
"",
|
||||
arrayOf(
|
||||
@ -77,14 +88,19 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
Toast.makeText(this, "VPN service started", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
*/
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
Log.d("DIR", "filesdir = ${this.filesDir}")
|
||||
|
||||
|
||||
setContent {
|
||||
PunchnetTheme {
|
||||
MainApp(serviceModel, viewModel, rememberNavController())
|
||||
/*
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
StartStop(
|
||||
R.string.start_vpn,
|
||||
@ -93,11 +109,13 @@ class MainActivity : ComponentActivity() {
|
||||
prepareAndStartVPN()
|
||||
},
|
||||
onStop = {
|
||||
stopVpnService()
|
||||
// stopVpnService()
|
||||
},
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.padding
|
||||
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.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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
|
||||
fun MainApp(
|
||||
serviceViewModel: ServiceViewModel = viewModel(),
|
||||
routeModel: RouteViewModel = viewModel(),
|
||||
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
|
||||
@ -38,13 +66,13 @@ fun CustomHeaderScreen(
|
||||
body: @Composable () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Box (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer),
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
//.padding(horizontal = 16.dp),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
@ -78,23 +106,9 @@ fun CustomHeaderScreen(
|
||||
) {
|
||||
if (onMenu != null) {
|
||||
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 java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
const val BroadcastIP = 0xFFFFFFFF
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
val BroadcastMac = ubyteArrayOf(0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu).toByteArray()
|
||||
|
||||
class ARPTable(
|
||||
) {
|
||||
val routeTable: RouteTable = RouteTable()
|
||||
@ -36,16 +40,18 @@ class ARPTable(
|
||||
}
|
||||
}
|
||||
|
||||
fun getMacFromIP(ip: Int): ByteArray? {
|
||||
fun getMacFromIP(ip: Int): Pair<ByteArray?, Int> {
|
||||
val gw = routeTable.getGateway(ip)
|
||||
if (gw == null) {
|
||||
// not found in route table, just use the ip
|
||||
val value = content.get(ip)
|
||||
if (value == null) {
|
||||
println("content table size is: ${content.size}")
|
||||
for (k in content.keys()) {
|
||||
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) {
|
||||
val origin = content.get(ip)
|
||||
|
||||
@ -175,13 +175,13 @@ interface IfaceTun: Iface {
|
||||
return
|
||||
}
|
||||
|
||||
val arpinfo = arpTable.getMacFromIP(dstip)
|
||||
val (arpinfo, gwip) = arpTable.getMacFromIP(dstip)
|
||||
if (arpinfo == null) {
|
||||
println("arp info is null")
|
||||
// arp not found
|
||||
arpWaitList.addToWaitList(dstip, data)
|
||||
arpWaitList.addToWaitList(gwip, data)
|
||||
TerminalLogger.debugf { "added to wait list" }
|
||||
sendArpRequest(node, dstip)
|
||||
sendArpRequest(node, gwip)
|
||||
TerminalLogger.debugf { "sent arp request" }
|
||||
} else {
|
||||
println("mac is ${macToString(arpinfo.toByteString())}")
|
||||
|
||||
@ -90,7 +90,7 @@ class RouteTable() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getGeteway(ip: Int): Int? {
|
||||
fun getGateway(ip: Int): Int? {
|
||||
try {
|
||||
lock.readLock().lock()
|
||||
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