From adb6a4fd9ec3a23c04d5e4c2ce799448237915c4 Mon Sep 17 00:00:00 2001 From: JustZvan Date: Fri, 6 Feb 2026 13:38:36 +0100 Subject: feat: initial commit --- app/src/main/java/sh/lajo/buddy/MainActivity.kt | 122 ++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 app/src/main/java/sh/lajo/buddy/MainActivity.kt (limited to 'app/src/main/java/sh/lajo/buddy/MainActivity.kt') 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 -- cgit v1.2.3