Initial Commit

This commit is contained in:
Ashin Walpola
2026-06-03 14:51:31 +02:00
parent ee122765e1
commit ae17d4fcba
74 changed files with 7126 additions and 0 deletions
@@ -0,0 +1,275 @@
package com.hawhamburg.micr0bu
import android.Manifest
import android.os.Build
import android.os.Bundle
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.SystemBarStyle
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.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.hawhamburg.micr0bu.ui.components.StatusTopBar
import com.hawhamburg.micr0bu.ui.navigation.BottomNavBar
import com.hawhamburg.micr0bu.ui.navigation.Screen
import com.hawhamburg.micr0bu.ui.screens.ConnectionSetupScreen
import com.hawhamburg.micr0bu.ui.screens.DashboardScreen
import com.hawhamburg.micr0bu.ui.screens.MqttTopicViewerScreen
import com.hawhamburg.micr0bu.ui.screens.RecordingScreen
import com.hawhamburg.micr0bu.ui.screens.SensorScreen
import com.hawhamburg.micr0bu.ui.screens.SessionLogScreen
import com.hawhamburg.micr0bu.ui.screens.MapScreen
import com.hawhamburg.micr0bu.ui.screens.TripHistoryScreen
import com.hawhamburg.micr0bu.ui.screens.TripReviewScreen
import com.hawhamburg.micr0bu.ui.screens.AboutSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.AppearanceSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.ConnectionSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.DeveloperSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.LanguageSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.MqttSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.SensorsSettingsScreen
import com.hawhamburg.micr0bu.ui.screens.SettingsScreen
import com.hawhamburg.micr0bu.ui.screens.UnitsSettingsScreen
import com.hawhamburg.micr0bu.ui.theme.MicrOBUTheme
import com.hawhamburg.micr0bu.viewmodel.MqttViewModel
import com.hawhamburg.micr0bu.viewmodel.SensorViewModel
import com.hawhamburg.micr0bu.viewmodel.TripRecordingViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: SensorViewModel by viewModels()
private val mqttViewModel: MqttViewModel by viewModels()
private val tripViewModel: TripRecordingViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT),
navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT),
)
setContent {
val state by viewModel.state.collectAsState()
val mqttPrefs by mqttViewModel.mqttPrefs.collectAsState()
val mqttConnectionState by mqttViewModel.connectionState.collectAsState()
val activeTransport by mqttViewModel.activeTransport.collectAsState()
val usbConnected by mqttViewModel.usbConnected.collectAsState()
val obuStationTypeWarning by mqttViewModel.obuStationTypeWarning.collectAsState()
val tripServiceState by tripViewModel.serviceState.collectAsState()
MicrOBUTheme(darkTheme = state.darkTheme) {
val view = LocalView.current
SideEffect {
WindowCompat.getInsetsController(window, view).apply {
isAppearanceLightStatusBars = !state.darkTheme
isAppearanceLightNavigationBars = !state.darkTheme
}
}
val navController = rememberNavController()
val locationLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val granted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true
if (granted) viewModel.onLocationPermissionGranted()
}
// Request POST_NOTIFICATIONS on Android 13+
val notificationLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { /* ignored — notification permission is best-effort */ }
LaunchedEffect(Unit) {
viewModel.startImuStreams()
locationLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
notificationLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
Scaffold(
topBar = { StatusTopBar(state, mqttConnectionState) },
bottomBar = { BottomNavBar(navController) },
) { innerPadding ->
NavHost(
navController = navController,
startDestination = Screen.Dashboard.route,
modifier = Modifier.padding(innerPadding),
) {
composable(Screen.Dashboard.route) {
DashboardScreen(
state = state,
mqttConnectionState = mqttConnectionState,
activeTransport = activeTransport,
usbCableConnected = usbConnected,
obuStationTypeWarning = obuStationTypeWarning,
onNavigateToConnection = { navController.navigate(Screen.Connection.route) },
onNavigateToSensors = {
navController.navigate(Screen.Sensors.route) {
popUpTo(Screen.Dashboard.route) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
onNavigateToMap = { navController.navigate(Screen.Map.route) },
)
}
composable(Screen.Sensors.route) {
SensorScreen(state = state)
}
composable(Screen.Record.route) {
RecordingScreen(
state = state,
mqttConnectionState = mqttConnectionState,
tripServiceState = tripServiceState,
onToggleRecording = {
// Start / stop both CSV recording and the event detection service
viewModel.toggleRecording()
tripViewModel.toggleRecording()
},
onOpenSessionLog = {
navController.navigate(Screen.Log.route)
},
)
}
composable(Screen.Log.route) {
SessionLogScreen(
state = state,
onDeleteSession = viewModel::deleteSession,
)
}
// ── Phase A: Trip History ─────────────────────────────
composable(Screen.TripHistory.route) {
TripHistoryScreen(
viewModel = tripViewModel,
onOpenTrip = { tripId ->
navController.navigate(Screen.TripReview.route(tripId))
},
)
}
composable(
route = Screen.TripReview.route,
arguments = listOf(
navArgument("tripId") { type = NavType.LongType }
),
) { backStackEntry ->
val tripId = backStackEntry.arguments
?.getLong("tripId", -1L) ?: -1L
if (tripId < 0L) return@composable
val trips by tripViewModel.trips.collectAsState(initial = emptyList())
val trip = trips.firstOrNull { it.id == tripId }
if (trip != null) {
TripReviewScreen(
trip = trip,
viewModel = tripViewModel,
)
}
}
composable(Screen.MqttViewer.route) {
MqttTopicViewerScreen(viewModel = mqttViewModel)
}
composable(Screen.Settings.route) {
SettingsScreen(
onOpenLanguage = { navController.navigate(Screen.SettingsLanguage.route) },
onOpenAppearance = { navController.navigate(Screen.SettingsAppearance.route) },
onOpenSensors = { navController.navigate(Screen.SettingsSensors.route) },
onOpenUnits = { navController.navigate(Screen.SettingsUnits.route) },
onOpenConnection = { navController.navigate(Screen.SettingsConnection.route) },
onOpenMqtt = { navController.navigate(Screen.SettingsMqtt.route) },
onOpenDeveloper = { navController.navigate(Screen.SettingsDeveloper.route) },
onOpenAbout = { navController.navigate(Screen.SettingsAbout.route) },
)
}
composable(Screen.SettingsConnection.route) {
ConnectionSettingsScreen(
mqttPrefs = mqttPrefs,
onMqttPrefsChange = mqttViewModel::updatePrefs,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsLanguage.route) {
LanguageSettingsScreen(onBack = { navController.popBackStack() })
}
composable(Screen.SettingsAppearance.route) {
AppearanceSettingsScreen(
darkTheme = state.darkTheme,
onDarkTheme = viewModel::setDarkTheme,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsSensors.route) {
SensorsSettingsScreen(
state = state,
onAccelEnabled = viewModel::setAccelEnabled,
onGyroEnabled = viewModel::setGyroEnabled,
onMagnetEnabled = viewModel::setMagnetEnabled,
onBarometerEnabled = viewModel::setBarometerEnabled,
onGnssEnabled = viewModel::setGnssEnabled,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsUnits.route) {
UnitsSettingsScreen(
useMetric = state.useMetric,
onUseMetric = viewModel::setUseMetric,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsMqtt.route) {
MqttSettingsScreen(
mqttPrefs = mqttPrefs,
onMqttPrefsChange = mqttViewModel::updatePrefs,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsDeveloper.route) {
DeveloperSettingsScreen(
state = state,
onDeveloperMode = viewModel::setDeveloperMode,
onBack = { navController.popBackStack() },
)
}
composable(Screen.SettingsAbout.route) {
AboutSettingsScreen(onBack = { navController.popBackStack() })
}
composable(Screen.Connection.route) {
ConnectionSetupScreen()
}
composable(Screen.Map.route) {
MapScreen(gnss = state.gnss)
}
}
}
}
}
}
}