Initial Commit
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user