release
This commit is contained in:
parent
91042c083f
commit
f24dd7cfe9
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
punchnet
|
||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
4
.idea/inspectionProfiles/Project_Default.xml
generated
4
.idea/inspectionProfiles/Project_Default.xml
generated
@ -49,6 +49,10 @@
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" 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" />
|
||||
|
||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
||||
BIN
app/release/app-release.apk
Normal file
BIN
app/release/app-release.apk
Normal file
Binary file not shown.
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
Binary file not shown.
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
Binary file not shown.
37
app/release/output-metadata.json
Normal file
37
app/release/output-metadata.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.jihe.punchnet",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/app-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/app-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 26
|
||||
}
|
||||
@ -11,7 +11,7 @@
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@drawable/punchnet_log"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@ -12,6 +12,9 @@ import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.jihe.punchnet.data.RouteItem
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.sdlan.config.Arguments
|
||||
import com.jihe.punchnet.sdlan.config.toIPV4String
|
||||
import com.jihe.punchnet.sdlan.logs.TerminalLogger
|
||||
@ -23,7 +26,9 @@ import com.jihe.punchnet.sdlan.network.RouteDetail
|
||||
import com.jihe.punchnet.sdlan.network.RouteTable
|
||||
import com.jihe.punchnet.sdlan.network.cidrToRouteDetail
|
||||
import com.jihe.punchnet.sdlan.network.ipInt2ByteArray
|
||||
import com.jihe.punchnet.sdlan.network.maskIPToDigit
|
||||
import com.jihe.punchnet.sdlan.network.run_sdlan
|
||||
import com.jihe.punchnet.sdlan.utils.ipToString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
@ -34,10 +39,13 @@ import java.io.FileOutputStream
|
||||
|
||||
class PunchnetService : VpnService() , IfaceTun {
|
||||
private val TAG = "PunchnetService"
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
private val routes: MutableList<RouteItem> = mutableListOf()
|
||||
|
||||
private var vpnDescriptor: ParcelFileDescriptor? = null
|
||||
|
||||
|
||||
var input: FileInputStream? = null
|
||||
var output: FileOutputStream? = null
|
||||
|
||||
@ -91,10 +99,16 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
}
|
||||
vpnDescriptor?.close()
|
||||
|
||||
vpnDescriptor = Builder()
|
||||
var tempVpnDescriptor = Builder()
|
||||
.setMtu(config.mtu)
|
||||
.addAddress(ip, config.ip.netBitLen.toInt())
|
||||
.setBlocking(true)
|
||||
|
||||
for (route in routes) {
|
||||
tempVpnDescriptor = tempVpnDescriptor.addRoute(ipToString(route.net_ip), maskIPToDigit(route.mask_ip))
|
||||
}
|
||||
|
||||
vpnDescriptor = tempVpnDescriptor
|
||||
.establish()
|
||||
|
||||
input = FileInputStream(vpnDescriptor!!.fileDescriptor)
|
||||
@ -107,7 +121,7 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
output?.close()
|
||||
vpnDescriptor?.close()
|
||||
|
||||
// stopForeground(true)
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
@ -121,12 +135,17 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
Log.d("DIR", "filesdir = ${this.filesDir.path}")
|
||||
|
||||
arpTable.routeTable.clearRoute()
|
||||
routes.clear()
|
||||
for (route in startArg?.routes?.toList()?:listOf()) {
|
||||
val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway)
|
||||
if (r != null) {
|
||||
val r = RouteDetail(
|
||||
mask = route.mask_ip,
|
||||
gw = route.gateway,
|
||||
maskedAddr = route.net_ip,
|
||||
)
|
||||
routes.add(route)
|
||||
// val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway)
|
||||
arpTable.routeTable.addRoute(r)
|
||||
}
|
||||
}
|
||||
|
||||
val argument = Arguments(
|
||||
baseDir = this.filesDir.path,
|
||||
|
||||
@ -2,6 +2,7 @@ package com.jihe.punchnet
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.jihe.punchnet.data.RouteItem
|
||||
|
||||
data class RouteInfo(
|
||||
// 192.168.80.0/24
|
||||
@ -12,14 +13,16 @@ data class RouteInfo(
|
||||
|
||||
data class PunchnetServiceArgument(
|
||||
val token: String,
|
||||
val routes: Array<RouteInfo>,
|
||||
val routes: Array<RouteItem>,
|
||||
): Parcelable {
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(token)
|
||||
dest.writeInt(routes.size)
|
||||
routes.forEach { item ->
|
||||
dest.writeString(item.targetNetCIDR)
|
||||
dest.writeString(item.gateway)
|
||||
dest.writeLong(item.id)
|
||||
dest.writeInt(item.net_ip)
|
||||
dest.writeInt(item.mask_ip)
|
||||
dest.writeInt(item.gateway)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,13 +34,18 @@ data class PunchnetServiceArgument(
|
||||
override fun createFromParcel(source: Parcel): PunchnetServiceArgument {
|
||||
val token = source.readString()!!
|
||||
val size = source.readInt()
|
||||
var routes = mutableListOf<RouteInfo>()
|
||||
var routes = mutableListOf<RouteItem>()
|
||||
for (i in 0..<size) {
|
||||
val net = source.readString()!!
|
||||
val gw = source.readString()!!
|
||||
routes.add(RouteInfo(
|
||||
targetNetCIDR = net,
|
||||
gateway = gw,
|
||||
val id = source.readLong()!!
|
||||
val netIP = source.readInt()!!
|
||||
val maskIP = source.readInt()!!
|
||||
val gateway = source.readInt()!!
|
||||
|
||||
routes.add(RouteItem(
|
||||
id = id,
|
||||
net_ip = netIP,
|
||||
mask_ip = maskIP,
|
||||
gateway = gateway,
|
||||
))
|
||||
}
|
||||
return PunchnetServiceArgument(token, routes.toTypedArray())
|
||||
|
||||
@ -16,44 +16,34 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.content.ContextCompat.startForegroundService
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.jihe.punchnet.PunchnetService
|
||||
import com.jihe.punchnet.PunchnetServiceArgument
|
||||
import com.jihe.punchnet.RouteInfo
|
||||
import com.jihe.punchnet.sdlan.utils.ipToString
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.forEach
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
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)
|
||||
fun startService(context: Context, routes: Array<RouteItem>) {
|
||||
// should has the permission
|
||||
startVpnService(context, routes)
|
||||
_isRunning.value = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpnService(context: Context) {
|
||||
private fun startVpnService(context: Context, routes: Array<RouteItem>) {
|
||||
val intent = Intent(context, PunchnetService::class.java)
|
||||
intent.putExtra("argument", PunchnetServiceArgument(
|
||||
"",
|
||||
arrayOf(
|
||||
RouteInfo(
|
||||
"192.168.80.0/24", "10.211.188.2"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
routes,
|
||||
))
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
|
||||
@ -1,6 +1,80 @@
|
||||
package com.jihe.punchnet.helper
|
||||
|
||||
import com.jihe.punchnet.data.RouteItem
|
||||
import com.jihe.punchnet.sdlan.network.maskDigitToInt
|
||||
|
||||
sealed class Screen(val route: String) {
|
||||
object ScreenRoutes: Screen("routes")
|
||||
object ScreenMain: Screen("main")
|
||||
}
|
||||
|
||||
fun IntToIPString(ip: Int): String {
|
||||
val one = ((ip ushr 24) and 0x000000ff)
|
||||
val two = ((ip ushr 16) and 0x000000ff)
|
||||
val three = ((ip ushr 8) and 0x000000ff)
|
||||
val four = ((ip) and 0x000000ff)
|
||||
return "${one}.${two}.${three}.${four}"
|
||||
}
|
||||
|
||||
fun IPStringToInt(ipStr: String): Int? {
|
||||
val digits = ipStr.split('.').filter{
|
||||
val single = try {
|
||||
it.toInt()
|
||||
} catch(e: Exception) {
|
||||
return@filter false
|
||||
}
|
||||
single in 0..255
|
||||
}
|
||||
if (digits.size != 4) {
|
||||
return null
|
||||
}
|
||||
val result: Int
|
||||
try {
|
||||
result = (digits[0].toInt() shl 24) +
|
||||
(digits[1].toInt() shl 16) +
|
||||
(digits[2].toInt() shl 8) +
|
||||
(digits[3].toInt())
|
||||
} catch(e: Exception) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun parseCIDRAndGW(cidr: String, gw: String): RouteItem? {
|
||||
val cidrSplit = cidr.split('/')
|
||||
var ip = 0
|
||||
var digit = 0
|
||||
|
||||
if (cidrSplit.size == 1) {
|
||||
digit = 32
|
||||
} else if (cidrSplit.size == 2) {
|
||||
try {
|
||||
val k = cidrSplit[1].toInt()
|
||||
if (k in 1..32) {
|
||||
digit = k
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
val ipvalue = IPStringToInt(cidrSplit[0])
|
||||
val gateway = IPStringToInt(gw)
|
||||
if (ipvalue == null) {
|
||||
return null
|
||||
}
|
||||
if (gateway == null) {
|
||||
return null
|
||||
}
|
||||
val mask = maskDigitToInt(digit)!!
|
||||
return RouteItem(
|
||||
net_ip = ipvalue,
|
||||
gateway = gateway,
|
||||
mask_ip = mask,
|
||||
)
|
||||
}
|
||||
@ -54,12 +54,22 @@ fun AppNavHost2(
|
||||
}
|
||||
composable(Screen.RouteScreen.route) {
|
||||
CustomHeaderScreen(
|
||||
"光强计"
|
||||
"路由设置"
|
||||
) {
|
||||
RouteScreen(!serviceViewModel.isRunning.value, routeViewModel)
|
||||
// LightScreen()
|
||||
}
|
||||
}
|
||||
composable(Screen.ProfileScreen.route) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
"TODO",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
// Profile(dbdao, navController)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import android.app.Activity.RESULT_CANCELED
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
@ -25,6 +26,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@ -46,17 +48,18 @@ fun HomeScreen(
|
||||
// started: MutableState<Boolean>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val allroutes = routeViewModel.allRoutes.collectAsState(emptyList())
|
||||
val vpnLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) {result ->
|
||||
when(result.resultCode) {
|
||||
RESULT_OK -> {
|
||||
serviceViewModel.setVPNPermission(true)
|
||||
// serviceViewModel.startService(context)
|
||||
// serviceViewModel.setVPNPermission(true)
|
||||
serviceViewModel.startService(context, allroutes.value.toTypedArray())
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
serviceViewModel.setVPNPermission(false)
|
||||
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
@ -115,12 +118,12 @@ fun HomeScreen(
|
||||
// if is running, should stop service
|
||||
serviceViewModel.stopVpnService(context)
|
||||
} else {
|
||||
serviceViewModel.startService(
|
||||
context,
|
||||
{intent->
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
vpnLauncher.launch(intent)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
serviceViewModel.startService(context, allroutes.value.toTypedArray())
|
||||
}
|
||||
}
|
||||
// started.value = !started.value
|
||||
},
|
||||
@ -142,23 +145,3 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
211
app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt
Normal file
211
app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt
Normal file
@ -0,0 +1,211 @@
|
||||
package com.jihe.punchnet.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.collection.emptyLongSet
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.room.util.TableInfo
|
||||
import com.jihe.punchnet.data.RouteItem
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.helper.IntToIPString
|
||||
import com.jihe.punchnet.helper.parseCIDRAndGW
|
||||
import com.jihe.punchnet.sdlan.network.maskIPToDigit
|
||||
|
||||
@Composable
|
||||
fun AddOrModifyRoute(
|
||||
routeViewModel: RouteViewModel,
|
||||
initRoute: MutableState<RouteItem?>,
|
||||
shouldShowRoute: MutableState<Boolean>,
|
||||
|
||||
) {
|
||||
if (shouldShowRoute.value) {
|
||||
val context = LocalContext.current
|
||||
var cidr by remember {
|
||||
mutableStateOf(
|
||||
if (initRoute.value == null)
|
||||
""
|
||||
else
|
||||
""
|
||||
)
|
||||
}
|
||||
|
||||
var gw by remember {
|
||||
mutableStateOf(
|
||||
if (initRoute.value == null)
|
||||
""
|
||||
else
|
||||
""
|
||||
)
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
shouldShowRoute.value = false
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
val item = parseCIDRAndGW(cidr, gw)
|
||||
if (item == null) {
|
||||
Toast.makeText(context, "数据解析出错", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
routeViewModel.insert(item)
|
||||
shouldShowRoute.value = false
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("确定")
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
"添加新路由"
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
TextField(
|
||||
value = cidr,
|
||||
onValueChange = { newValue ->
|
||||
cidr = newValue
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
"192.168.0.0/24",
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(.5f)
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text("CIDR")
|
||||
},
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
|
||||
|
||||
TextField(
|
||||
value = gw,
|
||||
onValueChange = { newValue ->
|
||||
gw = newValue
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
"10.167.69.13",
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(.5f)
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text("gateway")
|
||||
},
|
||||
modifier = Modifier.padding(start = 16.dp, top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun RouteScreen(
|
||||
enabled: Boolean,
|
||||
routeViewModel: RouteViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val showAddRoute: MutableState<RouteItem?> = remember { mutableStateOf(null) }
|
||||
val shouldShow: MutableState<Boolean> = remember { mutableStateOf(false) }
|
||||
|
||||
AddOrModifyRoute(routeViewModel, showAddRoute, shouldShow)
|
||||
|
||||
val routeItems by routeViewModel.allRoutes.collectAsState(initial = emptyList())
|
||||
LazyColumn {
|
||||
item {
|
||||
Row(
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
items(routeItems) {item ->
|
||||
RouteCard(routeViewModel, item, enabled)
|
||||
}
|
||||
item {
|
||||
Button(
|
||||
enabled = enabled,
|
||||
shape = RectangleShape,
|
||||
onClick = {
|
||||
shouldShow.value = true
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("添加路由")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RouteCard(
|
||||
routeViewModel: RouteViewModel,
|
||||
item: RouteItem,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card (
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
||||
){
|
||||
Row (
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.height(36.dp)
|
||||
){
|
||||
Icon(
|
||||
Icons.Default.Clear,
|
||||
contentDescription = "delete route",
|
||||
modifier = Modifier.size(30.dp).clickable(
|
||||
enabled = enabled,
|
||||
) {
|
||||
routeViewModel.deleteById(item.id)
|
||||
}
|
||||
)
|
||||
Text(
|
||||
"${IntToIPString(item.net_ip)}/${maskIPToDigit(item.mask_ip)}",
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
IntToIPString(item.gateway),
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,11 +45,21 @@ fun cidrToRouteDetail(cidr: String, gw: String): RouteDetail? {
|
||||
)
|
||||
}
|
||||
|
||||
fun maskIPToDigit(mask: Int): Int {
|
||||
var count = 0
|
||||
var temp = mask
|
||||
while (temp != 0) {
|
||||
temp = temp shl 1
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
fun maskDigitToInt(digit: Int): Int? {
|
||||
if (digit <= 0 || digit >= 32) {
|
||||
return null
|
||||
}
|
||||
return ((2 shl (32-digit)) - 1)
|
||||
return ((1 shl (32-digit)) - 1).inv()
|
||||
}
|
||||
|
||||
fun ipStringToInt(ip: String): Int? {
|
||||
|
||||
BIN
key-store/punchnet.jks
Normal file
BIN
key-store/punchnet.jks
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user