diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 005afb3..cde3e19 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -3,15 +3,19 @@
+
+
+
+
@@ -27,21 +31,27 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b1c2b60..38b4873 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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"))
diff --git a/app/src/main/java/com/jihe/punchnet/MainActivity.kt b/app/src/main/java/com/jihe/punchnet/MainActivity.kt
index 8a937cd..8c1dc88 100644
--- a/app/src/main/java/com/jihe/punchnet/MainActivity.kt
+++ b/app/src/main/java/com/jihe/punchnet/MainActivity.kt
@@ -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 create(modelClass: Class): T {
+ return RouteViewModel(application) as T
}
}
}
+ private val serviceModel: ServiceViewModel by viewModels {
+ object: ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): 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)
)
}
+ */
+
}
}
}
diff --git a/app/src/main/java/com/jihe/punchnet/data/RouteItem.kt b/app/src/main/java/com/jihe/punchnet/data/RouteItem.kt
new file mode 100644
index 0000000..fe36a27
--- /dev/null
+++ b/app/src/main/java/com/jihe/punchnet/data/RouteItem.kt
@@ -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>
+}
+
+@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> = 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> = 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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt b/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt
new file mode 100644
index 0000000..754d438
--- /dev/null
+++ b/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt
@@ -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 = _isRunning
+
+ private val _hasVpnPermission = mutableStateOf(false)
+ val hasVpnPermission: State = _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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt b/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt
new file mode 100644
index 0000000..3d9cbec
--- /dev/null
+++ b/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt
@@ -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)
+ }
+ }
+}
diff --git a/app/src/main/java/com/jihe/punchnet/screen/BottomNavBar.kt b/app/src/main/java/com/jihe/punchnet/screen/BottomNavBar.kt
new file mode 100644
index 0000000..2d10170
--- /dev/null
+++ b/app/src/main/java/com/jihe/punchnet/screen/BottomNavBar.kt
@@ -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}")
+ }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
new file mode 100644
index 0000000..fae10ba
--- /dev/null
+++ b/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
@@ -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,
+ 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()
+}
diff --git a/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt
index c46fba2..b28ae92 100644
--- a/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt
+++ b/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt
@@ -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
- )
- */
-
}
}
-
}
}
diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt
index 8facc70..73660d5 100644
--- a/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt
+++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt
@@ -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,15 +40,17 @@ class ARPTable(
}
}
- fun getMacFromIP(ip: Int): ByteArray? {
- 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)}")
- }
+ fun getMacFromIP(ip: Int): Pair {
+ val gw = routeTable.getGateway(ip)
+ if (gw == null) {
+ // not found in route table, just use the ip
+ val value = content.get(ip)
+ 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) {
diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt
index 0281ae8..bc019aa 100644
--- a/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt
+++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/iface.kt
@@ -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())}")
diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt
index d7c0a27..ea5054b 100644
--- a/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt
+++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt
@@ -90,7 +90,7 @@ class RouteTable() {
}
}
- fun getGeteway(ip: Int): Int? {
+ fun getGateway(ip: Int): Int? {
try {
lock.readLock().lock()
for (item in routeInfo) {
diff --git a/app/src/main/res/drawable/punchnet_log.xml b/app/src/main/res/drawable/punchnet_log.xml
new file mode 100644
index 0000000..98859b7
--- /dev/null
+++ b/app/src/main/res/drawable/punchnet_log.xml
@@ -0,0 +1,648 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+