summaryrefslogtreecommitdiff
path: root/app/src/main/java/sh/lajo/buddy/MainActivity.kt
diff options
context:
space:
mode:
authorJustZvan <justzvan@justzvan.xyz>2026-02-06 13:38:36 +0100
committerJustZvan <justzvan@justzvan.xyz>2026-02-06 13:38:36 +0100
commitadb6a4fd9ec3a23c04d5e4c2ce799448237915c4 (patch)
tree786edcf5888788e0667a90fae96d7ebec68c507a /app/src/main/java/sh/lajo/buddy/MainActivity.kt
feat: initial commit
Diffstat (limited to 'app/src/main/java/sh/lajo/buddy/MainActivity.kt')
-rw-r--r--app/src/main/java/sh/lajo/buddy/MainActivity.kt122
1 files changed, 122 insertions, 0 deletions
diff --git a/app/src/main/java/sh/lajo/buddy/MainActivity.kt b/app/src/main/java/sh/lajo/buddy/MainActivity.kt
new file mode 100644
index 0000000..3f398b3
--- /dev/null
+++ b/app/src/main/java/sh/lajo/buddy/MainActivity.kt
@@ -0,0 +1,122 @@
+package sh.lajo.buddy
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.VpnService
+import android.os.Build
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import sh.lajo.buddy.ui.theme.BuddyTheme
+
+class MainActivity : ComponentActivity() {
+ private val vpnPrepareLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ startBuddyVpnServiceIfAllowed()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+
+ if (ConfigManager.shouldFetchConfig(this)) {
+ ConfigManager.fetchConfig(this)
+ }
+
+ maybePrepareAndStartVpn()
+
+ setContent {
+ BuddyTheme {
+ val context = LocalContext.current
+ val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
+ val navController = rememberNavController()
+ val startDestination = if (prefs.getBoolean("onboardingFinished", false)) "home" else "onboarding/step1"
+ val navBackStackEntry by navController.currentBackStackEntryAsState()
+ val currentRoute = navBackStackEntry?.destination?.route
+
+ if (prefs.getBoolean("onboardingFinished", false)) {
+ val wsIntent = Intent(context, WebSocketService::class.java)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(wsIntent)
+ } else {
+ context.startService(wsIntent)
+ }
+ }
+
+ // Determine which destinations should show the bottom bar
+ val showBottomBar = currentRoute in listOf("main", "home", "settings")
+
+ Scaffold(
+ bottomBar = {
+ // Only show the bottom bar on the main screens
+ if (showBottomBar) {
+ NavigationBar {
+ Destination.entries.forEach { destination ->
+ NavigationBarItem(
+ selected = currentRoute == destination.route,
+ onClick = {
+ navController.navigate(destination.route) {
+ // Pop up to main to avoid building up a large stack
+ popUpTo("main") {
+ saveState = true
+ }
+ // Avoid multiple copies of the same destination when
+ // reselecting the same item
+ launchSingleTop = true
+ // Restore state when reselecting a previously selected item
+ restoreState = true
+ }
+ },
+ icon = { Icon(destination.icon, contentDescription = destination.contentDescription) },
+ label = { Text(destination.label) }
+ )
+ }
+ }
+ }
+ }
+ ) { contentPadding ->
+ AppNavHost(
+ navController = navController,
+ startDestination = startDestination,
+ modifier = Modifier.padding(contentPadding)
+ )
+ }
+ }
+ }
+ }
+
+ private fun maybePrepareAndStartVpn() {
+ // If the user already granted VPN permission, prepare() returns null.
+ val prepareIntent = VpnService.prepare(this)
+ if (prepareIntent != null) {
+ vpnPrepareLauncher.launch(prepareIntent)
+ } else {
+ startBuddyVpnServiceIfAllowed()
+ }
+ }
+
+ private fun startBuddyVpnServiceIfAllowed() {
+ // Even on Android O+, VpnService isn't started via startForegroundService like typical
+ // foreground services; establishing the VPN tunnel is controlled by VpnService APIs.
+ // Using startService here is the recommended pattern.
+ val intent = Intent(this, BuddyVPNService::class.java)
+ startService(intent)
+ }
+} \ No newline at end of file