From 83ae6f5bf4f4d96396e0f81ec730e3aa7e8cc1db Mon Sep 17 00:00:00 2001 From: "meszaros.martin986@gmail.com" Date: Tue, 11 Aug 2026 14:47:44 +0200 Subject: [PATCH 1/2] Standardized UI interactions and navigation with custom components and enhanced haptic feedback. This commit refactors multiple screens to use a unified `ScreenScaffold` and `pressable` interaction model, replacing standard clickables with scaled, haptic-enabled components. It also includes UI refinements for list views and telemetric displays. **Key Changes:** * **Global UI Components:** * Replaced `Modifier.clickable` with a custom `.pressable()` modifier across most screens (`TrackScreen`, `TrackListView`, `TimeAttackListView`, `DragScreen`, etc.) to provide consistent scaling and haptic feedback. * Introduced `ScreenScaffold` to handle top bar integration and scroll-aware chrome transitions (`isScrolledUnderChrome`). * Integrated `DraggableSheet` in `LapDetailScreen` for stats and lap picking, replacing standard `AnimatedVisibility` blocks. * **Haptics & Motion:** * Added specific haptic triggers for session events, confirmations (e.g., saving a track, deleting a vehicle), and selection changes. * Updated the speedometer animation in `ESPConnectionTestScreen` to use a spring-based `Motion.standard()` for smoother transitions. * **UI Refinements:** * **Typography:** Introduced the `.atSize(sp)` extension for `TrackProType` to simplify font size overrides. * **Track & Vehicle Lists:** Updated `TrackCard` and `VehicleCard` with improved layouts, delete button hit targets, and confirmed delete haptics. * **Telemetric Screens:** Improved layout spacing and font scaling in `DragScreen` and `TimeAttackScreen` for better readability at high speeds. * **Settings & Creator Screens:** Wrapped `SettingsScreen` and `CarCreatorScreen` in `ScreenScaffold` and fixed padding/scroll behaviors. * **Technical Updates:** * Downgraded `bytecodeTargetLevel` and `languageLevel` from 21 to 17 in `.idea` configuration files. --- .idea/compiler.xml | 2 +- .idea/misc.xml | 2 +- .../java/com/example/trackpro/MainActivity.kt | 16 +- .../screens/ESPConnectionTestScreen.kt | 19 +- .../trackpro/screens/SettingsScreen.kt | 31 ++-- .../trackpro/screens/TrackBuilderScreen.kt | 7 +- .../example/trackpro/screens/TrackScreen.kt | 10 +- .../screens/TrackVehicleSelectorScreen.kt | 3 +- .../screens/listViewScreens/CarListView.kt | 118 ++++++------ .../screens/listViewScreens/DragTimesList.kt | 81 +++++---- .../listViewScreens/TimeAttackListView.kt | 77 ++++---- .../screens/listViewScreens/TrackListView.kt | 118 ++++++------ .../listItems/CarViewScreen.kt | 31 ++-- .../listItems/DragScreenListItem.kt | 20 ++- .../listItems/LapDetailScreen.kt | 112 +++++------- .../listItems/TimeAttackListItem.kt | 21 +-- .../screens/telemetricScreens/DragScreen.kt | 21 ++- .../telemetricScreens/TimeAttackScreen.kt | 21 ++- .../vehicleScreens/CarCreatorScreen.kt | 168 +++++++++--------- 19 files changed, 479 insertions(+), 399 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b86273d..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index f7608ed..adb8ae0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/app/src/main/java/com/example/trackpro/MainActivity.kt b/app/src/main/java/com/example/trackpro/MainActivity.kt index 89d0689..d744ce3 100644 --- a/app/src/main/java/com/example/trackpro/MainActivity.kt +++ b/app/src/main/java/com/example/trackpro/MainActivity.kt @@ -10,7 +10,6 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts 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 @@ -96,8 +95,11 @@ import com.example.trackpro.viewModels.VehicleFULLViewModel import com.example.trackpro.viewModels.VehicleFULLViewModelFactory import com.example.trackpro.viewModels.VehicleViewModel import com.example.trackpro.viewModels.VehicleViewModelFactory +import com.example.trackpro.components.pressableRow +import com.example.trackpro.components.pressable import com.example.trackpro.components.AppCard import com.example.trackpro.components.SectionLabel +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes import com.example.trackpro.theme.TrackProType @@ -451,7 +453,7 @@ fun MainScreen( ) Text( text = "Performance Telemetry", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = TrackProTheme.colors.textFaint ) } @@ -544,7 +546,7 @@ fun MainScreen( } Text( text = "TRACKPRO", - style = TrackProType.label.copy(fontSize = 13.sp, letterSpacing = 2.sp), + style = TrackProType.label.atSize(13.sp).copy(letterSpacing = 2.sp), color = TrackProTheme.colors.textPrimary ) // Spacer to balance the row @@ -705,14 +707,14 @@ private fun ActionCard( val alpha = if (disabled) 0.4f else 1f val iconSize = if (halfWidth) 14.dp else if (fullWidth) 18.dp else 16.dp val iconBoxSize = if (halfWidth) 26.dp else if (fullWidth) 34.dp else 30.dp - val titleStyle = if (fullWidth) TrackProType.titleMedium else TrackProType.titleMedium.copy(fontSize = 13.sp) + val titleStyle = if (fullWidth) TrackProType.titleMedium else TrackProType.titleMedium.atSize(13.sp) val subtitleSize = if (halfWidth) 9.sp else 10.sp val vertPadding = if (halfWidth) 10.dp else if (fullWidth) 12.dp else 10.dp AppCard( modifier = Modifier .fillMaxWidth() - .then(if (!disabled) Modifier.clickable(onClick = onClick) else Modifier), + .pressable(onClick = onClick, enabled = !disabled), padding = 0.dp ) { Box { @@ -758,7 +760,7 @@ private fun ActionCard( ) Text( text = subtitle, - style = TrackProType.body.copy(fontSize = subtitleSize), + style = TrackProType.body.atSize(subtitleSize), color = TrackProTheme.colors.textMuted.copy(alpha = alpha), maxLines = 2, overflow = TextOverflow.Ellipsis @@ -799,7 +801,7 @@ private fun DrawerItem( Row( modifier = Modifier .fillMaxWidth() - .clickable(onClick = onClick) + .pressableRow(onClick = onClick) .padding(horizontal = 20.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) diff --git a/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt b/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt index cae53e9..9618948 100644 --- a/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt @@ -2,12 +2,9 @@ package com.example.trackpro.screens import android.annotation.SuppressLint import android.graphics.Typeface -import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas 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 @@ -40,12 +37,15 @@ import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt import com.example.trackpro.TrackProApp import com.example.trackpro.extrasForUI.TrackProTheme +import com.example.trackpro.components.pressable import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.SectionLabel import com.example.trackpro.components.StatCell import com.example.trackpro.components.StatCellDivider import com.example.trackpro.components.StatCellSize +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors +import com.example.trackpro.theme.Motion import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProType import com.example.trackpro.managerClasses.JsonReader @@ -109,7 +109,11 @@ fun ESPConnectionTestScreen(onNavigateToSettings: () -> Unit) { text = "Change", style = TrackProType.label, color = TrackProTheme.colors.accent, - modifier = Modifier.clickable { onNavigateToSettings() } + // Bare text was the smallest tap target on the screen; give it + // real padding and a press response. + modifier = Modifier + .pressable(onClick = onNavigateToSettings, scale = 0.94f) + .padding(horizontal = Spacing.sm, vertical = 4.dp) ) } ) @@ -250,7 +254,10 @@ fun StyledSpeedometer( ) { val animatedSpeed by animateFloatAsState( targetValue = speed, - animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing), + // Was tween(600): every GPS tick restarted a fixed ramp from wherever it had + // got to, so a steadily changing speed animated in visible steps. A spring + // re-targets continuously from the current value instead. + animationSpec = Motion.standard(), label = "speed" ) @@ -396,7 +403,7 @@ private fun TelemetryRow(label: String, value: String, textPrimary: Color, textM Text(label.uppercase(), style = TrackProType.label, color = textMuted) Text( value, - style = TrackProType.body.copy(fontSize = 13.sp, fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace), + style = TrackProType.body.atSize(13.sp).copy(fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace), color = textPrimary ) } diff --git a/app/src/main/java/com/example/trackpro/screens/SettingsScreen.kt b/app/src/main/java/com/example/trackpro/screens/SettingsScreen.kt index 13c388b..5b3ccb4 100644 --- a/app/src/main/java/com/example/trackpro/screens/SettingsScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/SettingsScreen.kt @@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -27,12 +25,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.trackpro.TrackProApp import com.example.trackpro.components.AppCard -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.isScrolledUnderChrome import com.example.trackpro.components.SectionLabel import com.example.trackpro.components.ToggleChip import com.example.trackpro.extrasForUI.AppDropdownField import com.example.trackpro.extrasForUI.TrackProTheme import com.example.trackpro.models.GpsProviderType +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProType @@ -49,17 +49,24 @@ fun SettingsScreen(onBack: () -> Unit, onRequestBluetoothPermission: () -> Unit) val useDarkTheme by app.useDarkTheme.collectAsState() val useMetric by app.useMetricUnits.collectAsState() - Column( - modifier = Modifier - .fillMaxSize() - .background(TrackProTheme.colors.bgDeep) - ) { - AppTopBar(title = "Settings", accent = TrackProTheme.colors.textMuted, onBack = onBack) + val scrollState = rememberScrollState() + val scrolled by scrollState.isScrolledUnderChrome() + ScreenScaffold( + title = "Settings", + accent = TrackProTheme.colors.textMuted, + onBack = onBack, + contentScrolled = scrolled + ) { contentPadding -> Column( modifier = Modifier - .padding(Spacing.md) - .verticalScroll(rememberScrollState()), + .verticalScroll(scrollState) + .padding( + top = contentPadding.calculateTopPadding() + Spacing.md, + start = Spacing.md, + end = Spacing.md, + bottom = Spacing.md + ), verticalArrangement = Arrangement.spacedBy(Spacing.md) ) { // --- Section: Hardware & GPS --- @@ -252,7 +259,7 @@ private fun GpsRateRow(selectedHz: Int, confirmedHz: Int?, onSelect: (Int) -> Un if (confirmedHz != null) { Text( text = if (confirmedHz == selectedHz) "· confirmed" else "· device at ${confirmedHz}Hz", - style = TrackProType.body.copy(fontSize = 10.sp), + style = TrackProType.body.atSize(10.sp), // A mismatch between requested and confirmed rate is a real problem // worth flagging, so this is one of the few places color is earned. color = if (confirmedHz == selectedHz) TrackProTheme.colors.deltaGood diff --git a/app/src/main/java/com/example/trackpro/screens/TrackBuilderScreen.kt b/app/src/main/java/com/example/trackpro/screens/TrackBuilderScreen.kt index ae185a4..a0df0fa 100644 --- a/app/src/main/java/com/example/trackpro/screens/TrackBuilderScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/TrackBuilderScreen.kt @@ -46,10 +46,12 @@ import com.example.trackpro.dataClasses.TrackCoordinatesData import com.example.trackpro.dataClasses.TrackMainData import com.example.trackpro.dataClasses.LatLonOffset import com.example.trackpro.extrasForUI.TrackProTheme +import com.example.trackpro.components.Haptic import com.example.trackpro.components.AppCard import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.PrimaryButton import com.example.trackpro.components.ToggleChip +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes @@ -301,7 +303,7 @@ private fun TrackInfoCard(name: String, country: String, mode: String, onClick: style = TrackProType.titleMedium, color = TrackProTheme.colors.textPrimary ) - Text("Mode: ${mode.uppercase()}", style = TrackProType.body.copy(fontSize = 12.sp), color = TrackProTheme.colors.accent) + Text("Mode: ${mode.uppercase()}", style = TrackProType.body.atSize(12.sp), color = TrackProTheme.colors.accent) } PrimaryButton( text = "Edit", @@ -320,6 +322,7 @@ private fun MarkSectorButton(count: Int, enabled: Boolean, onClick: () -> Unit) text = "Mark Sector ${count + 1}", onClick = onClick, enabled = enabled, + haptic = Haptic.Confirm, accent = TrackProTheme.colors.accent, modifier = Modifier.fillMaxWidth().height(48.dp) ) @@ -395,6 +398,7 @@ private fun LiveControls(isRecording: Boolean, onToggle: () -> Unit) { PrimaryButton( text = if (isRecording) "Stop Recording" else "Start GPS Recording", onClick = onToggle, + haptic = Haptic.Confirm, accent = if (isRecording) TrackProTheme.colors.danger.copy(alpha = 0.18f) else TrackProTheme.colors.bgElevated, contentColor = if (isRecording) TrackProTheme.colors.danger else TrackProTheme.colors.textPrimary, modifier = Modifier.fillMaxWidth().height(56.dp) @@ -415,6 +419,7 @@ private fun ManualControls(onUndo: () -> Unit, onSave: () -> Unit, canSave: Bool text = "Save Track", onClick = onSave, enabled = canSave, + haptic = Haptic.Confirm, accent = TrackProTheme.colors.accent, modifier = Modifier.weight(1f).height(56.dp) ) diff --git a/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt b/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt index eba2eb8..ce664cf 100644 --- a/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt @@ -1,7 +1,6 @@ package com.example.trackpro.screens 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 @@ -37,10 +36,13 @@ import com.example.trackpro.TrackProApp import com.example.trackpro.dataClasses.TrackCoordinatesData import com.example.trackpro.dataClasses.TrackMainData import com.example.trackpro.extrasForUI.TrackProTheme +import com.example.trackpro.components.Haptic +import com.example.trackpro.components.pressable import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.SectionLabel import com.example.trackpro.components.StatCell import com.example.trackpro.components.ToggleChip +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProType @@ -233,7 +235,7 @@ private fun SectorSlicerCard( SectionLabel("Sectors") Text( text = if (sectorCount > 0) "$sectorCount marked" else "None marked", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = if (sectorCount > 0) TrackProTheme.colors.accent else TrackProTheme.colors.textMuted ) } @@ -258,7 +260,9 @@ private fun SectorSlicerCard( text = "Clear sectors", style = TrackProType.label, color = TrackProTheme.colors.danger, - modifier = Modifier.clickable { onClear() } + modifier = Modifier + .pressable(onClick = onClear, scale = 0.94f, haptic = Haptic.Reject) + .padding(vertical = 4.dp) ) } } diff --git a/app/src/main/java/com/example/trackpro/screens/TrackVehicleSelectorScreen.kt b/app/src/main/java/com/example/trackpro/screens/TrackVehicleSelectorScreen.kt index 17f41ee..6c92d48 100644 --- a/app/src/main/java/com/example/trackpro/screens/TrackVehicleSelectorScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/TrackVehicleSelectorScreen.kt @@ -34,6 +34,7 @@ import com.example.trackpro.extrasForUI.TrackProTheme import com.example.trackpro.components.AppCard import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.PrimaryButton +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProType import com.example.trackpro.viewModels.TrackViewModel @@ -111,7 +112,7 @@ fun TrackVehicleSelectorScreen( selectedVehicleName = vehicles.find { it.vehicleId == id }?.manufacturerAndModel ?: "" // Adjust 'name' to your vehicle field } } else { - Text("No vehicles found in garage", style = TrackProType.body.copy(fontSize = 12.sp), color = TrackProTheme.colors.accent) + Text("No vehicles found in garage", style = TrackProType.body.atSize(12.sp), color = TrackProTheme.colors.accent) } } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt index 67eb35b..4823247 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt @@ -3,7 +3,6 @@ package com.example.trackpro.screens.listViewScreens import android.content.Context import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,10 +13,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.AlertDialog import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -40,10 +41,15 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.example.trackpro.dataClasses.VehicleInformationData import com.example.trackpro.extrasForUI.TrackProTheme -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.isScrolledUnderChrome +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.Haptic +import com.example.trackpro.components.pressable +import com.example.trackpro.components.rememberHaptics import com.example.trackpro.components.EmptyState import com.example.trackpro.components.StatCell import com.example.trackpro.components.StatCellDivider +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes import com.example.trackpro.theme.TrackProType @@ -60,55 +66,56 @@ fun CarListScreen(navController: NavController, viewModel: VehicleFULLViewModel) val scope = rememberCoroutineScope() - Box( - modifier = Modifier - .fillMaxSize() - .background(TrackProTheme.colors.bgDeep) - ) { - Column(modifier = Modifier.fillMaxSize()) { + val listState = rememberLazyListState() + val scrolled by listState.isScrolledUnderChrome() - AppTopBar( - title = "My Vehicles", - accent = TrackProTheme.colors.accent, - trailing = { - Text( - text = "${vehicles.size} cars", - style = TrackProType.label, - color = TrackProTheme.colors.textMuted - ) - } - ) - - if (vehicles.isEmpty()) { - EmptyState( - message = "No vehicles yet", - hint = "Add a vehicle from the main screen" + ScreenScaffold( + title = "My Vehicles", + accent = TrackProTheme.colors.accent, + trailing = { + Text( + text = "${vehicles.size} cars", + style = TrackProType.label, + color = TrackProTheme.colors.textMuted ) - } else { - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(Spacing.md), - verticalArrangement = Arrangement.spacedBy(Spacing.sm) - ) { - items(vehicles) { vehicle -> - VehicleCard( - vehicle = vehicle, - navController = navController, - bgCard = TrackProTheme.colors.bgCard, - bgElevated = TrackProTheme.colors.bgElevated, - accent = TrackProTheme.colors.accent, - dangerColor = TrackProTheme.colors.danger, - textPrimary = TrackProTheme.colors.textPrimary, - textMuted = TrackProTheme.colors.textMuted, - sectorLine = TrackProTheme.colors.sectorLine, - onDelete = { vehicleToDelete -> - scope.launch(Dispatchers.IO) { - database.vehicleInformationDAO() - .deleteVehicle(vehicleToDelete.vehicleId) - } + }, + contentScrolled = scrolled + ) { contentPadding -> + if (vehicles.isEmpty()) { + EmptyState( + message = "No vehicles yet", + hint = "Add a vehicle from the main screen" + ) + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues( + top = contentPadding.calculateTopPadding() + Spacing.md, + bottom = Spacing.md, + start = Spacing.md, + end = Spacing.md + ), + verticalArrangement = Arrangement.spacedBy(Spacing.sm) + ) { + items(vehicles) { vehicle -> + VehicleCard( + vehicle = vehicle, + navController = navController, + bgCard = TrackProTheme.colors.bgCard, + bgElevated = TrackProTheme.colors.bgElevated, + accent = TrackProTheme.colors.accent, + dangerColor = TrackProTheme.colors.danger, + textPrimary = TrackProTheme.colors.textPrimary, + textMuted = TrackProTheme.colors.textMuted, + sectorLine = TrackProTheme.colors.sectorLine, + onDelete = { vehicleToDelete -> + scope.launch(Dispatchers.IO) { + database.vehicleInformationDAO() + .deleteVehicle(vehicleToDelete.vehicleId) } - ) - } + } + ) } } } @@ -129,6 +136,7 @@ fun VehicleCard( onDelete: (VehicleInformationData) -> Unit ) { var showDeleteDialog by remember { mutableStateOf(false) } + val haptics = rememberHaptics() if (showDeleteDialog) { AlertDialog( @@ -138,6 +146,7 @@ fun VehicleCard( textContentColor = TrackProTheme.colors.textMuted, confirmButton = { TextButton(onClick = { + haptics.perform(Haptic.Reject) onDelete(vehicle) showDeleteDialog = false }) { @@ -157,9 +166,9 @@ fun VehicleCard( Box( modifier = Modifier .fillMaxWidth() + .pressable(onClick = { navController.navigate("vehicle/${vehicle.vehicleId}") }) .background(bgCard, TrackProShapes.card) .border(1.dp, sectorLine, TrackProShapes.card) - .clickable { navController.navigate("vehicle/${vehicle.vehicleId}") } ) { Column { @@ -189,13 +198,13 @@ fun VehicleCard( ) { Text( text = vehicle.fuelType.uppercase(), - style = TrackProType.label.copy(fontSize = 9.sp), + style = TrackProType.label.atSize(9.sp), color = textMuted ) } Text( text = vehicle.drivetrain.uppercase(), - style = TrackProType.label.copy(fontSize = 9.sp), + style = TrackProType.label.atSize(9.sp), color = textMuted ) } @@ -203,9 +212,10 @@ fun VehicleCard( // Delete button Box( modifier = Modifier + .minimumInteractiveComponentSize() + .pressable(onClick = { showDeleteDialog = true }, scale = 0.90f) .size(26.dp) - .background(dangerColor.copy(alpha = 0.1f), TrackProShapes.badge) - .clickable { showDeleteDialog = true }, + .background(dangerColor.copy(alpha = 0.1f), TrackProShapes.badge), contentAlignment = Alignment.Center ) { Icon( @@ -232,7 +242,7 @@ fun VehicleCard( ) Text( text = "${vehicle.year} · ${vehicle.engineType}", - style = TrackProType.body.copy(fontSize = 12.sp), + style = TrackProType.body.atSize(12.sp), color = textMuted ) } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt index 3351027..403e3f7 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt @@ -2,7 +2,6 @@ package com.example.trackpro.screens.listViewScreens import androidx.compose.foundation.ExperimentalFoundationApi 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 @@ -15,6 +14,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text @@ -31,9 +31,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.example.trackpro.extrasForUI.TrackProTheme -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.isScrolledUnderChrome +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.pressable import com.example.trackpro.components.EmptyState import com.example.trackpro.components.ExpandableGroup +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes import com.example.trackpro.theme.TrackProType @@ -58,40 +61,42 @@ fun DragTimesListView( } - Box( - modifier = Modifier - .fillMaxSize() - .background(TrackProTheme.colors.bgDeep) - ) { - Column(modifier = Modifier.fillMaxSize()) { - AppTopBar( - title = "Drag Records", - accent = TrackProTheme.colors.accent, - trailing = { - Text( - "${dragSessions.size} sessions", - style = TrackProType.label, - color = TrackProTheme.colors.textMuted - ) - } - ) + val listState = rememberLazyListState() + val scrolled by listState.isScrolledUnderChrome() - if (dragSessions.isEmpty()) { - EmptyState(message = "No sessions recorded", hint = "Run a drag session to see it here") - } else { - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(Spacing.md), - verticalArrangement = Arrangement.spacedBy(Spacing.sm) - ) { - groupedSessions.forEach { (groupKey, sessions) -> - item(key = groupKey) { - ExpandableSessionGroup( - groupTitle = groupKey, - sessions = sessions, - navController = navController - ) - } + ScreenScaffold( + title = "Drag Records", + accent = TrackProTheme.colors.accent, + trailing = { + Text( + "${dragSessions.size} sessions", + style = TrackProType.label, + color = TrackProTheme.colors.textMuted + ) + }, + contentScrolled = scrolled + ) { contentPadding -> + if (dragSessions.isEmpty()) { + EmptyState(message = "No sessions recorded", hint = "Run a drag session to see it here") + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues( + top = contentPadding.calculateTopPadding() + Spacing.md, + bottom = Spacing.md, + start = Spacing.md, + end = Spacing.md + ), + verticalArrangement = Arrangement.spacedBy(Spacing.sm) + ) { + groupedSessions.forEach { (groupKey, sessions) -> + item(key = groupKey) { + ExpandableSessionGroup( + groupTitle = groupKey, + sessions = sessions, + navController = navController + ) } } } @@ -108,10 +113,10 @@ fun ExpandableSessionGroup( accent = TrackProTheme.colors.accent, header = { Column(modifier = Modifier.weight(1f)) { - Text(groupTitle, style = TrackProType.titleMedium.copy(fontSize = 13.sp), color = TrackProTheme.colors.textPrimary) + Text(groupTitle, style = TrackProType.titleMedium.atSize(13.sp), color = TrackProTheme.colors.textPrimary) Text( "${sessions.size} runs completed", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = TrackProTheme.colors.textMuted ) } @@ -124,8 +129,8 @@ fun ExpandableSessionGroup( Row( modifier = Modifier .fillMaxWidth() + .pressable(onClick = { navController.navigate("graph/${session.sessionId}") }) .background(TrackProTheme.colors.bgElevated, TrackProShapes.control) - .clickable { navController.navigate("graph/${session.sessionId}") } .padding(Spacing.sm), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt index 4739d16..57b894f 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt @@ -2,7 +2,6 @@ package com.example.trackpro.screens.listViewScreens import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,6 +14,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -31,9 +31,12 @@ import androidx.navigation.NavController import com.example.trackpro.dataClasses.SessionData import com.example.trackpro.dataClasses.VehicleInformationData import com.example.trackpro.extrasForUI.TrackProTheme -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.isScrolledUnderChrome +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.pressable import com.example.trackpro.components.EmptyState import com.example.trackpro.components.ExpandableGroup +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProType import com.example.trackpro.viewModels.SessionViewModel @@ -62,35 +65,41 @@ fun TimeAttackListViewScreen( } - Box(modifier = Modifier.fillMaxSize().background(TrackProTheme.colors.bgDeep)) { - Column(modifier = Modifier.fillMaxSize()) { - AppTopBar( - title = "Track Records", - accent = TrackProTheme.colors.accent, - trailing = { - Text("${trackSessions.size} sessions", style = TrackProType.label, color = TrackProTheme.colors.textMuted) - } - ) + val listState = rememberLazyListState() + val scrolled by listState.isScrolledUnderChrome() - if (trackSessions.isEmpty()) { - EmptyState(message = "No sessions recorded", hint = "Run a track session to see it here") - } else { - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(Spacing.md), - verticalArrangement = Arrangement.spacedBy(Spacing.sm) - ) { - groupedByTrack.forEach { (trackName, sessions) -> - item(key = trackName) { - val track = tracks.find { it.trackName == trackName } - ExpandableTrackGroup( - trackName = trackName, - trackMeta = "${track?.country} · ${track?.type}", - sessions = sessions, - vehicles = vehicles, - navController = navController - ) - } + ScreenScaffold( + title = "Track Records", + accent = TrackProTheme.colors.accent, + trailing = { + Text("${trackSessions.size} sessions", style = TrackProType.label, color = TrackProTheme.colors.textMuted) + }, + contentScrolled = scrolled + ) { contentPadding -> + if (trackSessions.isEmpty()) { + EmptyState(message = "No sessions recorded", hint = "Run a track session to see it here") + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues( + top = contentPadding.calculateTopPadding() + Spacing.md, + bottom = Spacing.md, + start = Spacing.md, + end = Spacing.md + ), + verticalArrangement = Arrangement.spacedBy(Spacing.sm) + ) { + groupedByTrack.forEach { (trackName, sessions) -> + item(key = trackName) { + val track = tracks.find { it.trackName == trackName } + ExpandableTrackGroup( + trackName = trackName, + trackMeta = "${track?.country} · ${track?.type}", + sessions = sessions, + vehicles = vehicles, + navController = navController + ) } } } @@ -110,10 +119,10 @@ fun ExpandableTrackGroup( accent = TrackProTheme.colors.accent, header = { Column(modifier = Modifier.weight(1f)) { - Text(trackName, style = TrackProType.titleMedium.copy(fontSize = 14.sp), color = TrackProTheme.colors.textPrimary) + Text(trackName, style = TrackProType.titleMedium.atSize(14.sp), color = TrackProTheme.colors.textPrimary) Text( "$trackMeta · ${sessions.size} sessions", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = TrackProTheme.colors.textMuted ) } @@ -128,8 +137,8 @@ fun ExpandableTrackGroup( Row( modifier = Modifier .fillMaxWidth() + .pressable(onClick = { navController.navigate("timeattacklistitem/${session.id}") }) .background(TrackProTheme.colors.bgElevated.copy(alpha = 0.4f), RoundedCornerShape(8.dp)) - .clickable { navController.navigate("timeattacklistitem/${session.id}") } .padding(Spacing.sm), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically @@ -146,7 +155,7 @@ fun ExpandableTrackGroup( ) Text( "Session date: $date", - style = TrackProType.body.copy(fontSize = 10.sp), + style = TrackProType.body.atSize(10.sp), color = TrackProTheme.colors.textMuted ) } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt index 897238f..a4b5fdf 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt @@ -2,7 +2,6 @@ package com.example.trackpro.screens.listViewScreens import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -13,8 +12,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.HorizontalDivider import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close @@ -41,9 +42,14 @@ import androidx.navigation.NavController import com.example.trackpro.TrackProApp import com.example.trackpro.dataClasses.TrackMainData import com.example.trackpro.extrasForUI.TrackProTheme -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.isScrolledUnderChrome +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.Haptic +import com.example.trackpro.components.pressable +import com.example.trackpro.components.rememberHaptics import com.example.trackpro.components.EmptyState import com.example.trackpro.components.StatCell +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes import com.example.trackpro.theme.TrackProType @@ -63,53 +69,54 @@ fun TrackListScreen(navController: NavController, viewModel: TrackViewModel) { val database = remember { ESPDatabase.getInstance(context) } val scope = rememberCoroutineScope() - Box( - modifier = Modifier - .fillMaxSize() - .background(TrackProTheme.colors.bgDeep) - ) { - Column(modifier = Modifier.fillMaxSize()) { - - AppTopBar( - title = "My Tracks", - accent = TrackProTheme.colors.accent, - trailing = { - Text( - text = "${tracks.size} tracks", - style = TrackProType.label, - color = TrackProTheme.colors.textMuted - ) - } - ) + val listState = rememberLazyListState() + val scrolled by listState.isScrolledUnderChrome() - if (tracks.isEmpty()) { - EmptyState(message = "No tracks yet", hint = "Build a track to see it here") - } else { - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(Spacing.md), - verticalArrangement = Arrangement.spacedBy(Spacing.sm) - ) { - items(tracks) { track -> - TrackCard( - track = track, - navController = navController, - database = database, - useMetric = useMetric, - bgCard = TrackProTheme.colors.bgCard, - bgElevated = TrackProTheme.colors.bgElevated, - accent = TrackProTheme.colors.accent, - dangerColor = TrackProTheme.colors.danger, - textPrimary = TrackProTheme.colors.textPrimary, - textMuted = TrackProTheme.colors.textMuted, - sectorLine = TrackProTheme.colors.sectorLine, - onDelete = { trackToDelete -> - scope.launch(Dispatchers.IO) { - database.trackMainDao().deleteTrack(trackToDelete.trackId) - } + ScreenScaffold( + title = "My Tracks", + accent = TrackProTheme.colors.accent, + trailing = { + Text( + text = "${tracks.size} tracks", + style = TrackProType.label, + color = TrackProTheme.colors.textMuted + ) + }, + contentScrolled = scrolled + ) { contentPadding -> + if (tracks.isEmpty()) { + EmptyState(message = "No tracks yet", hint = "Build a track to see it here") + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues( + top = contentPadding.calculateTopPadding() + Spacing.md, + bottom = Spacing.md, + start = Spacing.md, + end = Spacing.md + ), + verticalArrangement = Arrangement.spacedBy(Spacing.sm) + ) { + items(tracks) { track -> + TrackCard( + track = track, + navController = navController, + database = database, + useMetric = useMetric, + bgCard = TrackProTheme.colors.bgCard, + bgElevated = TrackProTheme.colors.bgElevated, + accent = TrackProTheme.colors.accent, + dangerColor = TrackProTheme.colors.danger, + textPrimary = TrackProTheme.colors.textPrimary, + textMuted = TrackProTheme.colors.textMuted, + sectorLine = TrackProTheme.colors.sectorLine, + onDelete = { trackToDelete -> + scope.launch(Dispatchers.IO) { + database.trackMainDao().deleteTrack(trackToDelete.trackId) } - ) - } + } + ) } } } @@ -132,6 +139,7 @@ fun TrackCard( onDelete: (TrackMainData) -> Unit ) { var showDeleteDialog by remember { mutableStateOf(false) } + val haptics = rememberHaptics() var bestLapTime by remember(track.trackId) { mutableStateOf(null) } LaunchedEffect(track.trackId) { @@ -147,7 +155,10 @@ fun TrackCard( titleContentColor = TrackProTheme.colors.textPrimary, textContentColor = TrackProTheme.colors.textMuted, confirmButton = { - TextButton(onClick = { onDelete(track); showDeleteDialog = false }) { + TextButton(onClick = { + haptics.perform(Haptic.Reject) + onDelete(track); showDeleteDialog = false + }) { Text("Delete", color = dangerColor, style = TrackProType.titleMedium) } }, @@ -164,9 +175,9 @@ fun TrackCard( Box( modifier = Modifier .fillMaxWidth() + .pressable(onClick = { navController.navigate("track/${track.trackId}") }) .background(bgCard, TrackProShapes.card) .border(1.dp, sectorLine, TrackProShapes.card) - .clickable { navController.navigate("track/${track.trackId}") } ) { Column { // ── Header ──────────────────────────────────── @@ -195,13 +206,13 @@ fun TrackCard( ) { Text( text = track.type.uppercase(), - style = TrackProType.label.copy(fontSize = 9.sp), + style = TrackProType.label.atSize(9.sp), color = textMuted ) } Text( text = track.country.uppercase(), - style = TrackProType.label.copy(fontSize = 9.sp), + style = TrackProType.label.atSize(9.sp), color = textMuted ) } @@ -209,9 +220,10 @@ fun TrackCard( // Delete button Box( modifier = Modifier + .minimumInteractiveComponentSize() + .pressable(onClick = { showDeleteDialog = true }, scale = 0.90f) .size(26.dp) - .background(dangerColor.copy(alpha = 0.1f), TrackProShapes.badge) - .clickable { showDeleteDialog = true }, + .background(dangerColor.copy(alpha = 0.1f), TrackProShapes.badge), contentAlignment = Alignment.Center ) { Icon( diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt index e1e350b..4f5447d 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt @@ -9,9 +9,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text @@ -31,7 +33,8 @@ import androidx.compose.ui.unit.dp import com.example.trackpro.TrackProApp import com.example.trackpro.dataClasses.VehicleInformationData import com.example.trackpro.extrasForUI.TrackProTheme -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.isScrolledUnderChrome import com.example.trackpro.components.SectionLabel import com.example.trackpro.components.StatCell import com.example.trackpro.components.StatCellSize @@ -60,11 +63,16 @@ fun CarViewScreen(vehicleId: Long) { } } - Box( - modifier = Modifier - .fillMaxSize() - .background(TrackProTheme.colors.bgDeep) - ) { + val listState = rememberLazyListState() + val scrolled by listState.isScrolledUnderChrome() + + // The bar now wraps both states - previously it only existed in the loaded branch, + // so the screen had no header (and no back affordance) while loading. + ScreenScaffold( + title = "Vehicle Profile", + accent = TrackProTheme.colors.accent, + contentScrolled = scrolled + ) { contentPadding -> if (vehicleInfo == null) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Column(horizontalAlignment = Alignment.CenterHorizontally) { @@ -79,11 +87,11 @@ fun CarViewScreen(vehicleId: Long) { } } else { val vehicle = vehicleInfo!! - Column(modifier = Modifier.fillMaxSize()) { - - AppTopBar(title = "Vehicle Profile", accent = TrackProTheme.colors.accent) - - LazyColumn(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues(top = contentPadding.calculateTopPadding()) + ) { // ── Hero ────────────────────────────── item { @@ -198,7 +206,6 @@ fun CarViewScreen(vehicleId: Long) { item { Spacer(Modifier.height(Spacing.xl)) } } - } } } } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/DragScreenListItem.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/DragScreenListItem.kt index 957eeca..bde6d02 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/DragScreenListItem.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/DragScreenListItem.kt @@ -3,7 +3,6 @@ package com.example.trackpro.screens.listViewScreens.listItems import android.annotation.SuppressLint import android.view.ViewGroup 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 @@ -40,9 +39,12 @@ import com.example.trackpro.managerClasses.utilities.SpeedColorUtils import com.example.trackpro.managerClasses.utilities.UnitFormatter import com.example.trackpro.screens.telemetricScreens.DragMetricCard import com.example.trackpro.screens.telemetricScreens.DragMetricDisplay +import com.example.trackpro.components.Haptic +import com.example.trackpro.components.pressable import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.StatCell import com.example.trackpro.components.StatCellSize +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProType @@ -296,16 +298,20 @@ fun GraphScreen(onBack: () -> Unit, sessionId: Long) { val active = xAxisInMeters == isMeters Box( modifier = Modifier + .pressable( + onClick = { xAxisInMeters = isMeters }, + scale = 0.96f, + haptic = Haptic.Selection + ) .background( if (active) TrackProTheme.colors.accent else TrackProTheme.colors.sectorLine, RoundedCornerShape(3.dp) ) - .clickable { xAxisInMeters = isMeters } .padding(horizontal = 8.dp, vertical = 3.dp) ) { Text( text = label, - style = TrackProType.body.copy(fontSize = 9.sp), + style = TrackProType.body.atSize(9.sp), color = if (active) TrackProTheme.colors.onAccent else TrackProTheme.colors.textMuted ) } @@ -315,16 +321,20 @@ fun GraphScreen(onBack: () -> Unit, sessionId: Long) { val active = showMap == isMap Box( modifier = Modifier + .pressable( + onClick = { showMap = isMap }, + scale = 0.96f, + haptic = Haptic.Selection + ) .background( if (active) TrackProTheme.colors.accent else TrackProTheme.colors.sectorLine, RoundedCornerShape(3.dp) ) - .clickable { showMap = isMap } .padding(horizontal = 8.dp, vertical = 3.dp) ) { Text( text = label, - style = TrackProType.body.copy(fontSize = 9.sp), + style = TrackProType.body.atSize(9.sp), color = if (active) TrackProTheme.colors.onAccent else TrackProTheme.colors.textMuted ) } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/LapDetailScreen.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/LapDetailScreen.kt index ad2355f..9464683 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/LapDetailScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/LapDetailScreen.kt @@ -3,14 +3,8 @@ package com.example.trackpro.screens.listViewScreens.lapDetail import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -18,6 +12,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -37,8 +32,12 @@ import com.example.trackpro.dataClasses.LapInfoData import com.example.trackpro.dataClasses.LapTimeData import com.example.trackpro.dataClasses.SectorTimeData import com.example.trackpro.extrasForUI.TrackProTheme +import com.example.trackpro.components.Haptic +import com.example.trackpro.components.pressable import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.DraggableSheet import com.example.trackpro.components.SectionLabel +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes @@ -208,12 +207,16 @@ fun LapDetailScreen( val selected = mode == heatmapMode Box( modifier = Modifier + .pressable( + onClick = { heatmapMode = mode }, + scale = 0.96f, + haptic = Haptic.Selection + ) .clip(RoundedCornerShape(8.dp)) .background( if (selected) TrackProTheme.colors.accent else Color.Transparent ) - .clickable { heatmapMode = mode } .padding(horizontal = Spacing.md, vertical = 6.dp), contentAlignment = Alignment.Center ) { @@ -250,13 +253,13 @@ fun LapDetailScreen( .background(COMPARE_COLOR, CircleShape) ) Column { - Text("Compare · Lap ${cl.lapnumber}", style = TrackProType.label.copy(fontSize = 8.sp), color = COMPARE_COLOR) - Text(cl.laptime, style = TrackProType.titleMedium.copy(fontSize = 13.sp), color = TrackProTheme.colors.textPrimary) + Text("Compare · Lap ${cl.lapnumber}", style = TrackProType.label.atSize(8.sp), color = COMPARE_COLOR) + Text(cl.laptime, style = TrackProType.titleMedium.atSize(13.sp), color = TrackProTheme.colors.textPrimary) if (compareMs > 0) { val sign = if (deltaMs > 0) "+" else "" Text( text = "${sign}${deltaMs.toLapTimeString()}", - style = TrackProType.body.copy(fontSize = 10.sp), + style = TrackProType.body.atSize(10.sp), color = if (deltaMs < 0) TrackProTheme.colors.deltaGood else TrackProTheme.colors.deltaBad ) } @@ -266,11 +269,15 @@ fun LapDetailScreen( contentDescription = "Remove compare", tint = TrackProTheme.colors.textMuted, modifier = Modifier + .minimumInteractiveComponentSize() + .pressable( + onClick = { + compareLap = null + compareGps = emptyList() + }, + scale = 0.90f + ) .size(16.dp) - .clickable { - compareLap = null - compareGps = emptyList() - } ) } } @@ -282,11 +289,10 @@ fun LapDetailScreen( .fillMaxWidth() .align(Alignment.BottomCenter) ) { - // Stats panel (expandable) - AnimatedVisibility( + // Stats panel - draggable, so its grab handle means something. + DraggableSheet( visible = showStatsPanel, - enter = slideInVertically { it } + fadeIn(), - exit = slideOutVertically { it } + fadeOut() + onDismiss = { showStatsPanel = false } ) { StatsPanel( primaryLap = lap, @@ -328,10 +334,10 @@ fun LapDetailScreen( } // ── Lap picker bottom sheet ──────────────────── - AnimatedVisibility( + DraggableSheet( visible = showLapPicker, - enter = slideInVertically { it } + fadeIn(), - exit = slideOutVertically { it } + fadeOut() + onDismiss = { showLapPicker = false }, + modifier = Modifier.align(Alignment.BottomCenter) ) { LapPickerSheet( laps = allSessionLaps.filter { it.id != primaryLapId }, @@ -574,25 +580,12 @@ private fun StatsPanel( if (it.isEmpty()) 0f else it.average().toFloat() } + // Surface, corner radius and grab handle all come from DraggableSheet now. Column( modifier = Modifier .fillMaxWidth() - .background( - TrackProTheme.colors.bgCard.copy(alpha = 0.97f), - RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - ) .padding(bottom = 4.dp) ) { - // Handle - Box( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(top = 10.dp, bottom = 6.dp) - .width(40.dp) - .height(3.dp) - .background(TrackProTheme.colors.textMuted.copy(alpha = 0.4f), RoundedCornerShape(2.dp)) - ) - Row( modifier = Modifier .fillMaxWidth() @@ -604,7 +597,10 @@ private fun StatsPanel( Icon( Icons.Default.Close, "dismiss", tint = TrackProTheme.colors.textMuted, - modifier = Modifier.size(16.dp).clickable { onDismiss() } + modifier = Modifier + .minimumInteractiveComponentSize() + .pressable(onClick = onDismiss, scale = 0.90f) + .size(16.dp) ) } @@ -617,7 +613,7 @@ private fun StatsPanel( .padding(horizontal = Spacing.lg, vertical = Spacing.sm), horizontalArrangement = Arrangement.SpaceBetween ) { - Text("Metric", style = TrackProType.label.copy(fontSize = 8.sp), color = TrackProTheme.colors.textMuted, + Text("Metric", style = TrackProType.label.atSize(8.sp), color = TrackProTheme.colors.textMuted, modifier = Modifier.weight(1.4f)) Row( modifier = Modifier.weight(1f), @@ -625,7 +621,7 @@ private fun StatsPanel( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { Box(Modifier.size(6.dp).background(PRIMARY_COLOR, CircleShape)) - Text("Lap ${primaryLap.lapnumber}", style = TrackProType.label.copy(fontSize = 8.sp), color = PRIMARY_COLOR) + Text("Lap ${primaryLap.lapnumber}", style = TrackProType.label.atSize(8.sp), color = PRIMARY_COLOR) } if (compareLap != null) { Row( @@ -634,7 +630,7 @@ private fun StatsPanel( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { Box(Modifier.size(6.dp).background(COMPARE_COLOR, CircleShape)) - Text("Lap ${compareLap.lapnumber}", style = TrackProType.label.copy(fontSize = 8.sp), color = COMPARE_COLOR) + Text("Lap ${compareLap.lapnumber}", style = TrackProType.label.atSize(8.sp), color = COMPARE_COLOR) } } } @@ -676,13 +672,13 @@ private fun StatsPanel( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Text(label.uppercase(), style = TrackProType.label.copy(fontSize = 9.sp), color = TrackProTheme.colors.textMuted, + Text(label.uppercase(), style = TrackProType.label.atSize(9.sp), color = TrackProTheme.colors.textMuted, modifier = Modifier.weight(1.4f)) - Text(v1, style = TrackProType.body.copy(fontSize = 13.sp), color = if (label == "Delta" && deltaMs < 0) TrackProTheme.colors.deltaGood + Text(v1, style = TrackProType.body.atSize(13.sp), color = if (label == "Delta" && deltaMs < 0) TrackProTheme.colors.deltaGood else TrackProTheme.colors.textPrimary, modifier = Modifier.weight(1f)) if (compareLap != null && v2.isNotEmpty()) { - Text(v2, style = TrackProType.body.copy(fontSize = 13.sp), color = TrackProTheme.colors.textPrimary, + Text(v2, style = TrackProType.body.atSize(13.sp), color = TrackProTheme.colors.textPrimary, modifier = Modifier.weight(1f)) } } @@ -710,21 +706,8 @@ private fun LapPickerSheet( Column( modifier = Modifier .fillMaxWidth() - .background( - TrackProTheme.colors.bgCard, - RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - ) .heightIn(max = 360.dp) ) { - // Handle + header - Box( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(top = 10.dp, bottom = 8.dp) - .width(40.dp) - .height(3.dp) - .background(TrackProTheme.colors.textMuted.copy(alpha = 0.4f), RoundedCornerShape(2.dp)) - ) Row( modifier = Modifier .fillMaxWidth() @@ -734,7 +717,10 @@ private fun LapPickerSheet( ) { SectionLabel("Select Lap to Compare") Icon(Icons.Default.Close, "close", tint = TrackProTheme.colors.textMuted, - modifier = Modifier.size(16.dp).clickable { onDismiss() }) + modifier = Modifier + .minimumInteractiveComponentSize() + .pressable(onClick = onDismiss, scale = 0.90f) + .size(16.dp)) } HorizontalDivider(color = TrackProTheme.colors.sectorLine) @@ -749,6 +735,7 @@ private fun LapPickerSheet( modifier = Modifier .fillMaxWidth() .padding(vertical = 3.dp) + .pressable(onClick = { onSelect(lap) }, haptic = Haptic.Selection) .clip(RoundedCornerShape(8.dp)) .background( when { @@ -766,7 +753,6 @@ private fun LapPickerSheet( }, shape = RoundedCornerShape(8.dp) ) - .clickable { onSelect(lap) } .padding(horizontal = Spacing.md, vertical = Spacing.sm), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween @@ -777,7 +763,7 @@ private fun LapPickerSheet( ) { Text( String.format("%02d", lap.lapnumber), - style = TrackProType.statValue.copy(fontSize = 16.sp), + style = TrackProType.statValue.atSize(16.sp), color = if (isBest) TrackProTheme.colors.accent else TrackProTheme.colors.textPrimary ) if (isBest) { @@ -786,7 +772,7 @@ private fun LapPickerSheet( .background(TrackProTheme.colors.accent.copy(alpha = 0.15f), TrackProShapes.badge) .padding(horizontal = 5.dp, vertical = 2.dp) ) { - Text("Best", style = TrackProType.label.copy(fontSize = 7.sp), color = TrackProTheme.colors.accent) + Text("Best", style = TrackProType.label.atSize(7.sp), color = TrackProTheme.colors.accent) } } } @@ -798,10 +784,10 @@ private fun LapPickerSheet( val sign = if (deltaMs > 0) "+" else "" Text( "${sign}${deltaMs.toLapTimeString()}", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = if (deltaMs < 0) TrackProTheme.colors.deltaGood else TrackProTheme.colors.deltaBad ) - Text(lap.laptime, style = TrackProType.titleMedium.copy(fontSize = 15.sp), color = TrackProTheme.colors.textPrimary) + Text(lap.laptime, style = TrackProType.titleMedium.atSize(15.sp), color = TrackProTheme.colors.textPrimary) if (isSelected) { Box(Modifier.size(8.dp).background(COMPARE_COLOR, CircleShape)) } @@ -841,14 +827,14 @@ private fun ActionButton( ) { Box( modifier = modifier + .pressable(onClick = onClick, scale = 0.96f) .clip(RoundedCornerShape(8.dp)) .background(color.copy(alpha = 0.12f)) .border(1.dp, color.copy(alpha = 0.3f), RoundedCornerShape(8.dp)) - .clickable { onClick() } .padding(vertical = Spacing.sm), contentAlignment = Alignment.Center ) { - Text(label, style = TrackProType.titleMedium.copy(fontSize = 12.sp), color = color) + Text(label, style = TrackProType.titleMedium.atSize(12.sp), color = color) } } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt index 0b2d796..4343f25 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt @@ -5,7 +5,6 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -47,10 +46,12 @@ import com.example.trackpro.dataClasses.LapTimeData import com.example.trackpro.dataClasses.SessionData import com.example.trackpro.dataClasses.VehicleInformationData import com.example.trackpro.extrasForUI.TrackProTheme +import com.example.trackpro.components.pressable import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.SectionLabel import com.example.trackpro.components.StatCell import com.example.trackpro.components.StatCellSize +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes import com.example.trackpro.theme.TrackProType @@ -215,14 +216,14 @@ fun TimeAttackListItemScreen( ) Text( text = "${vehicle.engineType} · ${vehicle.horsepower}hp · ${vehicle.drivetrain}", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = TrackProTheme.colors.textMuted.copy(alpha = 0.7f) ) } Spacer(Modifier.height(4.dp)) Text( text = DateFormatterUtil.getDateTimeFormat().format(Date(session.startTime)), - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = TrackProTheme.colors.textMuted ) } @@ -328,9 +329,9 @@ fun TimeAttackListItemScreen( val lapMs = lap.laptime.toLapTimeMillis() val deltaMs = lapMs - bestMs val topSpeed = topSpeedPerLap[lap.lapnumber] ?: 0f - Box(modifier = Modifier.clickable { + Box(modifier = Modifier.pressable(onClick = { navController.navigate("lap_detail/$sessionId/${lap.id}") - }) { + })) { LapRow( lap = lap, isBest = isBest, @@ -411,7 +412,7 @@ private fun LapRow( ) { Text( text = String.format("%02d", lap.lapnumber), - style = TrackProType.statValue.copy(fontSize = 18.sp), + style = TrackProType.statValue.atSize(18.sp), color = accentColor ) Box( @@ -419,7 +420,7 @@ private fun LapRow( .background(accentColor.copy(alpha = 0.15f), TrackProShapes.badge) .padding(horizontal = 6.dp, vertical = 2.dp) ) { - Text(badge.uppercase(), style = TrackProType.label.copy(fontSize = 8.sp), color = accentColor) + Text(badge.uppercase(), style = TrackProType.label.atSize(8.sp), color = accentColor) } } @@ -436,19 +437,19 @@ private fun LapRow( Column(horizontalAlignment = Alignment.End) { Text( text = lap.laptime, - style = TrackProType.statValue.copy(fontSize = 17.sp), + style = TrackProType.statValue.atSize(17.sp), color = accentColor ) if (!isBest && deltaMs > 0) { Text( text = "+${deltaMs.toLapTimeString()}", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = badColor.copy(alpha = 0.8f) ) } else if (isBest) { Text( text = "Reference", - style = TrackProType.label.copy(fontSize = 9.sp), + style = TrackProType.label.atSize(9.sp), color = goodColor.copy(alpha = 0.7f) ) } diff --git a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt index c44ed94..d534d07 100644 --- a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt @@ -1,7 +1,6 @@ package com.example.trackpro.screens.telemetricScreens 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 @@ -61,10 +60,13 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import androidx.core.graphics.toColorInt import com.example.trackpro.extrasForUI.TrackProTheme +import com.example.trackpro.components.pressable +import com.example.trackpro.components.Haptic import com.example.trackpro.components.AppCard import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.PrimaryButton import com.example.trackpro.components.SectionLabel +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes @@ -163,7 +165,7 @@ fun DragRaceScreen( if (isSessionActive) { Text( elapsedTime, - style = TrackProType.statValue.copy(fontSize = 13.sp), + style = TrackProType.statValue.atSize(13.sp), color = TrackProTheme.colors.accent ) } @@ -188,7 +190,7 @@ fun DragRaceScreen( modifier = Modifier .fillMaxWidth() .padding(Spacing.md) - .clickable { showVehicleDropdown = true } + .pressable(onClick = { showVehicleDropdown = true }) ) { Row( Modifier.fillMaxWidth(), @@ -210,7 +212,7 @@ fun DragRaceScreen( selectedVehicle?.let { Text( "${it.horsepower}hp · ${it.drivetrain} · ${it.year}", - style = TrackProType.body.copy(fontSize = 11.sp), + style = TrackProType.body.atSize(11.sp), color = TrackProTheme.colors.textMuted ) } @@ -238,7 +240,7 @@ fun DragRaceScreen( ) Text( "${vehicle.horsepower}hp · ${vehicle.year}", - style = TrackProType.body.copy(fontSize = 12.sp), + style = TrackProType.body.atSize(12.sp), color = TrackProTheme.colors.textMuted ) } @@ -301,7 +303,7 @@ fun DragRaceScreen( ) Text( UnitFormatter.speedUnitLabel(useMetric), - style = TrackProType.body.copy(fontSize = 15.sp), + style = TrackProType.body.atSize(15.sp), color = TrackProTheme.colors.textMuted, modifier = Modifier.padding(bottom = 8.dp) ) @@ -311,7 +313,7 @@ fun DragRaceScreen( Spacer(Modifier.height(8.dp)) Text( "MAX: ${UnitFormatter.formatSpeed(currentMetrics.maxSpeed, useMetric)} ${UnitFormatter.speedUnitLabel(useMetric)} · DIST: ${UnitFormatter.formatDistance(currentMetrics.totalDistance.toDouble(), useMetric)}", - style = TrackProType.body.copy(fontSize = 12.sp), + style = TrackProType.body.atSize(12.sp), color = TrackProTheme.colors.textMuted ) } @@ -504,6 +506,7 @@ fun DragRaceScreen( } }, enabled = selectedVehicle != null, + haptic = Haptic.Confirm, accent = if (isSessionActive) TrackProTheme.colors.bgElevated else TrackProTheme.colors.accent, contentColor = if (isSessionActive) TrackProTheme.colors.accent else null, modifier = Modifier.weight(1f).height(56.dp) @@ -552,13 +555,13 @@ fun DragMetricCard( ) { Text( metric.value, - style = TrackProType.statValue.copy(fontSize = 19.sp), + style = TrackProType.statValue.atSize(19.sp), color = if (metric.achieved) TrackProTheme.colors.textPrimary else TrackProTheme.colors.textMuted.copy(alpha = 0.5f) ) Text( metric.unit, - style = TrackProType.body.copy(fontSize = 10.sp), + style = TrackProType.body.atSize(10.sp), color = TrackProTheme.colors.textMuted, modifier = Modifier.padding(bottom = 2.dp) ) diff --git a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt index 9d6dc62..96a84db 100644 --- a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt @@ -41,10 +41,13 @@ import com.example.trackpro.dataClasses.LatLonOffset import com.example.trackpro.extrasForUI.TrackProTheme import com.example.trackpro.managerClasses.timeAttackManagers.SectorSplit import com.example.trackpro.managerClasses.timeAttackManagers.TimingMode +import com.example.trackpro.components.Haptic +import com.example.trackpro.components.rememberHaptics import com.example.trackpro.components.AppTopBar import com.example.trackpro.components.StatCell import com.example.trackpro.components.StatCellDivider import com.example.trackpro.components.StatCellSize +import com.example.trackpro.theme.atSize import com.example.trackpro.theme.DataVizColors import com.example.trackpro.theme.Spacing import com.example.trackpro.theme.TrackProShapes @@ -159,6 +162,14 @@ fun TimeAttackScreenView( } ?: Log.w("TimeAttackScreen", "GPS data is null") } + // The single most useful haptic in the app: a lap closing is confirmed by feel, so + // the driver doesn't have to look away from the track to know it registered. + // Keyed on the counter itself, so it fires exactly once per lap. + val haptics = rememberHaptics() + LaunchedEffect(eventCount) { + if (eventCount > 0) haptics.perform(Haptic.Confirm) + } + val gpsPoints = fullTrack //+ linesToShow val driverPos = driver ?: LatLonOffset(0.0, 0.0) @@ -264,7 +275,7 @@ fun TimeAttackPortraitLayout( ) { Text( text = "Δ ${String.format("%+.3f", delta)}s", - style = TrackProType.statValue.copy(fontSize = 15.sp), + style = TrackProType.statValue.atSize(15.sp), color = deltaColor ) } @@ -405,7 +416,7 @@ fun TimeAttackLandscapeLayout( ) { Text( text = "Δ ${String.format("%+.3f", delta)}s", - style = TrackProType.statValue.copy(fontSize = 13.sp), + style = TrackProType.statValue.atSize(13.sp), color = deltaColor ) } @@ -516,12 +527,12 @@ private fun SectorSplitsRow(splits: List) { Column { Text( "S${split.sectorIndex + 1}", - style = TrackProType.label.copy(fontSize = 9.sp, letterSpacing = 0.5.sp), + style = TrackProType.label.atSize(9.sp).copy(letterSpacing = 0.5.sp), color = TrackProTheme.colors.textFaint ) Text( String.format("%.2fs", split.splitMs / 1000.0), - style = TrackProType.statValue.copy(fontSize = 13.sp), + style = TrackProType.statValue.atSize(13.sp), color = deltaColor ) if (split.deltaMs != null) { @@ -529,7 +540,7 @@ private fun SectorSplitsRow(splits: List) { val sign = if (deltaSeconds > 0) "+" else "" Text( "$sign${String.format("%.2f", deltaSeconds)}", - style = TrackProType.body.copy(fontSize = 9.sp), + style = TrackProType.body.atSize(9.sp), color = deltaColor ) } diff --git a/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt b/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt index 2c9a2c4..a0fd89c 100644 --- a/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt @@ -1,11 +1,8 @@ package com.example.trackpro.screens.vehicleScreens import android.widget.Toast -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -39,7 +36,8 @@ import com.example.trackpro.extrasForUI.AppDropdownField import com.example.trackpro.extrasForUI.CustomTextField import com.example.trackpro.extrasForUI.TrackProTheme import com.example.trackpro.components.AppCard -import com.example.trackpro.components.AppTopBar +import com.example.trackpro.components.ScreenScaffold +import com.example.trackpro.components.isScrolledUnderChrome import com.example.trackpro.components.PrimaryButton import com.example.trackpro.components.SectionLabel import com.example.trackpro.theme.Spacing @@ -76,88 +74,90 @@ fun CarCreationScreen( val scrollState = rememberScrollState() - Box( + val scrolled by scrollState.isScrolledUnderChrome() + + ScreenScaffold( + title = "Vehicle Setup", + accent = TrackProTheme.colors.accent, + contentScrolled = scrolled + ) { contentPadding -> + Column( modifier = Modifier - .fillMaxSize() - .background(TrackProTheme.colors.bgDeep) + .verticalScroll(scrollState) + .fillMaxWidth() + .padding( + top = contentPadding.calculateTopPadding() + Spacing.md, + start = Spacing.md, + end = Spacing.md, + bottom = Spacing.md + ), + horizontalAlignment = Alignment.CenterHorizontally ) { - Column(modifier = Modifier.fillMaxSize()) { - - AppTopBar(title = "Vehicle Setup", accent = TrackProTheme.colors.accent) - - Column( - modifier = Modifier - .verticalScroll(scrollState) - .fillMaxWidth() - .padding(Spacing.md), - horizontalAlignment = Alignment.CenterHorizontally - ) { - AppCard(modifier = Modifier.fillMaxWidth(), padding = 20.dp) { - - SectionLabel("Basic Info (Required)", modifier = Modifier.padding(vertical = Spacing.sm)) - CustomTextField("Manufacturer", manufacturer, leadingIcon = Icons.Default.Business) { manufacturer = it } - CustomTextField("Model", model, leadingIcon = Icons.Default.DirectionsCar) { model = it } - CustomTextField("Year", year, leadingIcon = Icons.Default.Event) { year = it } - - SectionLabel("Performance", modifier = Modifier.padding(vertical = Spacing.sm)) - CustomTextField("Horsepower", horsepower, true, Icons.Default.FlashOn) { horsepower = it } - CustomTextField("Torque (Nm)", torque, true, Icons.Default.Settings) { torque = it } - CustomTextField("Weight (kg)", weight, true, Icons.Default.FitnessCenter) { weight = it } - CustomTextField("Top Speed (${UnitFormatter.speedUnitLabel(useMetric)})", topSpeed, true, Icons.Default.Speed) { topSpeed = it } - CustomTextField( - if (useMetric) "0-100 KM/H (s)" else "0-60 MPH (s)", - acceleration, true, Icons.Default.Timer - ) { acceleration = it } - CustomTextField("Fuel Capacity (L)", fuelCapacity, true, Icons.Default.LocalGasStation) { fuelCapacity = it } - - SectionLabel("Configuration", modifier = Modifier.padding(vertical = Spacing.sm)) - AppDropdownField("Engine Type", jsonOptions.engineTypes, selectedEngineType, { it }, { selectedEngineType = it }) - AppDropdownField("Drivetrain", jsonOptions.drivetrains, selectedDrivetrain, { it }, { selectedDrivetrain = it }) - AppDropdownField("Fuel Type", jsonOptions.fuelTypes, selectedFuelType, { it }, { selectedFuelType = it }) - AppDropdownField("Tire Type", jsonOptions.tireTypes, selectedTireType, { it }, { selectedTireType = it }) - AppDropdownField("Transmission", jsonOptions.transmissions, selectedTransmission, { it }, { selectedTransmission = it }) - AppDropdownField("Suspension", jsonOptions.suspensionTypes, selectedSuspensionType, { it }, { selectedSuspensionType = it }) - - Spacer(modifier = Modifier.height(Spacing.md)) - - PrimaryButton( - text = "Save Vehicle", - onClick = { - if (manufacturer.isBlank() || model.isBlank() || year.isBlank()) { - Toast.makeText(context, "Fill in required fields.", Toast.LENGTH_SHORT).show() - return@PrimaryButton - } - - val vehicle = VehicleInformationData( - manufacturer = manufacturer, - model = model, - year = year.toIntOrNull() ?: 0, - engineType = selectedEngineType, - horsepower = horsepower.toIntOrNull() ?: 0, - torque = torque.toIntOrNull(), - weight = weight.toDoubleOrNull() ?: 0.0, - // Stored canonically in km/h regardless of the unit the - // user entered it in, matching every other speed value. - topSpeed = topSpeed.toDoubleOrNull()?.let { UnitFormatter.convertSpeedToKmh(it, useMetric) }, - acceleration = acceleration.toDoubleOrNull(), - drivetrain = selectedDrivetrain, - fuelType = selectedFuelType, - tireType = selectedTireType, - fuelCapacity = fuelCapacity.toDoubleOrNull(), - transmission = selectedTransmission, - suspensionType = selectedSuspensionType - ) - - coroutineScope.launch { - database.vehicleInformationDAO().insertVehicle(vehicle) - } - - Toast.makeText(context, "Vehicle saved successfully.", Toast.LENGTH_SHORT).show() - }, - accent = TrackProTheme.colors.deltaGood, - modifier = Modifier.fillMaxWidth() - ) - } + AppCard(modifier = Modifier.fillMaxWidth(), padding = 20.dp) { + + SectionLabel("Basic Info (Required)", modifier = Modifier.padding(vertical = Spacing.sm)) + CustomTextField("Manufacturer", manufacturer, leadingIcon = Icons.Default.Business) { manufacturer = it } + CustomTextField("Model", model, leadingIcon = Icons.Default.DirectionsCar) { model = it } + CustomTextField("Year", year, leadingIcon = Icons.Default.Event) { year = it } + + SectionLabel("Performance", modifier = Modifier.padding(vertical = Spacing.sm)) + CustomTextField("Horsepower", horsepower, true, Icons.Default.FlashOn) { horsepower = it } + CustomTextField("Torque (Nm)", torque, true, Icons.Default.Settings) { torque = it } + CustomTextField("Weight (kg)", weight, true, Icons.Default.FitnessCenter) { weight = it } + CustomTextField("Top Speed (${UnitFormatter.speedUnitLabel(useMetric)})", topSpeed, true, Icons.Default.Speed) { topSpeed = it } + CustomTextField( + if (useMetric) "0-100 KM/H (s)" else "0-60 MPH (s)", + acceleration, true, Icons.Default.Timer + ) { acceleration = it } + CustomTextField("Fuel Capacity (L)", fuelCapacity, true, Icons.Default.LocalGasStation) { fuelCapacity = it } + + SectionLabel("Configuration", modifier = Modifier.padding(vertical = Spacing.sm)) + AppDropdownField("Engine Type", jsonOptions.engineTypes, selectedEngineType, { it }, { selectedEngineType = it }) + AppDropdownField("Drivetrain", jsonOptions.drivetrains, selectedDrivetrain, { it }, { selectedDrivetrain = it }) + AppDropdownField("Fuel Type", jsonOptions.fuelTypes, selectedFuelType, { it }, { selectedFuelType = it }) + AppDropdownField("Tire Type", jsonOptions.tireTypes, selectedTireType, { it }, { selectedTireType = it }) + AppDropdownField("Transmission", jsonOptions.transmissions, selectedTransmission, { it }, { selectedTransmission = it }) + AppDropdownField("Suspension", jsonOptions.suspensionTypes, selectedSuspensionType, { it }, { selectedSuspensionType = it }) + + Spacer(modifier = Modifier.height(Spacing.md)) + + PrimaryButton( + text = "Save Vehicle", + onClick = { + if (manufacturer.isBlank() || model.isBlank() || year.isBlank()) { + Toast.makeText(context, "Fill in required fields.", Toast.LENGTH_SHORT).show() + return@PrimaryButton + } + + val vehicle = VehicleInformationData( + manufacturer = manufacturer, + model = model, + year = year.toIntOrNull() ?: 0, + engineType = selectedEngineType, + horsepower = horsepower.toIntOrNull() ?: 0, + torque = torque.toIntOrNull(), + weight = weight.toDoubleOrNull() ?: 0.0, + // Stored canonically in km/h regardless of the unit the + // user entered it in, matching every other speed value. + topSpeed = topSpeed.toDoubleOrNull()?.let { UnitFormatter.convertSpeedToKmh(it, useMetric) }, + acceleration = acceleration.toDoubleOrNull(), + drivetrain = selectedDrivetrain, + fuelType = selectedFuelType, + tireType = selectedTireType, + fuelCapacity = fuelCapacity.toDoubleOrNull(), + transmission = selectedTransmission, + suspensionType = selectedSuspensionType + ) + + coroutineScope.launch { + database.vehicleInformationDAO().insertVehicle(vehicle) + } + + Toast.makeText(context, "Vehicle saved successfully.", Toast.LENGTH_SHORT).show() + }, + accent = TrackProTheme.colors.deltaGood, + modifier = Modifier.fillMaxWidth() + ) } } } From fa67bf430343b185f3c0cc20fc5eeaf4622cc7b5 Mon Sep 17 00:00:00 2001 From: "meszaros.martin986@gmail.com" Date: Tue, 11 Aug 2026 15:14:58 +0200 Subject: [PATCH 2/2] Implemented navigation back-stack support across multiple screens This commit integrates back navigation functionality by adding `onBack` callbacks to various screen composables and updating the `MainActivity` navigation graph to handle these interactions. **Key Changes:** * **Navigation Updates (`MainActivity.kt`):** * Updated `composable` routes for `drag`, `esptest`, `track/{trackId}`, `vehicle/{vehicleid}`, `createvehicle`, and `timeattack` to pass `navController.popBackStack()` via the new `onBack` parameter. * **List View Screens:** * **`CarListView.kt`**, **`DragTimesList.kt`**, **`TimeAttackListView.kt`**, and **`TrackListView.kt`**: Added `onBack` lambda to `ScreenScaffold` to enable back navigation from these list views. * **`CarViewScreen.kt`** & **`TimeAttackListItem.kt`**: Updated to accept and trigger an `onBack` callback, ensuring individual item detail views can return to the previous screen. * **Telemetric & Functional Screens:** * **`DragScreen.kt`** & **`TimeAttackScreen.kt`**: Updated `DragRaceScreen` and `TimeAttackScreenView` (including its portrait and landscape layouts) to support the `onBack` action within the `AppTopBar`. * **`TrackScreen.kt`**: Modified `TrackScreen` and `TrackView` to propagate the `onBack` event to the top bar. * **`ESPConnectionTestScreen.kt`**: Added back navigation support to the connection test utility. * **`CarCreatorScreen.kt`**: Included `onBack` parameter in `CarCreationScreen` to allow users to cancel vehicle setup. * **UI Component Adjustments:** * Updated `AppTopBar` and `ScreenScaffold` usage across all modified files to explicitly handle the `onBack` event. --- .../java/com/example/trackpro/MainActivity.kt | 15 +++++++++------ .../trackpro/screens/ESPConnectionTestScreen.kt | 6 +++++- .../com/example/trackpro/screens/TrackScreen.kt | 12 ++++++++---- .../screens/listViewScreens/CarListView.kt | 1 + .../screens/listViewScreens/DragTimesList.kt | 1 + .../screens/listViewScreens/TimeAttackListView.kt | 1 + .../screens/listViewScreens/TrackListView.kt | 1 + .../listViewScreens/listItems/CarViewScreen.kt | 6 ++++-- .../listItems/TimeAttackListItem.kt | 1 + .../screens/telemetricScreens/DragScreen.kt | 4 +++- .../screens/telemetricScreens/TimeAttackScreen.kt | 15 +++++++++++---- .../screens/vehicleScreens/CarCreatorScreen.kt | 4 +++- 12 files changed, 48 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/example/trackpro/MainActivity.kt b/app/src/main/java/com/example/trackpro/MainActivity.kt index d744ce3..e21af90 100644 --- a/app/src/main/java/com/example/trackpro/MainActivity.kt +++ b/app/src/main/java/com/example/trackpro/MainActivity.kt @@ -319,17 +319,20 @@ class MainActivity : ComponentActivity() { ) } composable("drag") { - DragRaceScreen(database, sessionManager, vehicleFULLViewModel) + DragRaceScreen(database, sessionManager, vehicleFULLViewModel, onBack = { navController.popBackStack() }) } composable("esptest") { - ESPConnectionTestScreen(onNavigateToSettings = { navController.navigate("settings") }) + ESPConnectionTestScreen( + onNavigateToSettings = { navController.navigate("settings") }, + onBack = { navController.popBackStack() } + ) } composable( "track/{trackId}", arguments = listOf(navArgument("trackId") { type = NavType.LongType }) ) { backStackEntry -> val trackId = backStackEntry.arguments?.getLong("trackId") ?: 0L - TrackScreen(trackId = trackId) + TrackScreen(trackId = trackId, onBack = { navController.popBackStack() }) } composable("dragsessions") { DragTimesListView(viewModel = dragSessionViewModel, navController = navController) @@ -355,7 +358,7 @@ class MainActivity : ComponentActivity() { arguments = listOf(navArgument("vehicleid") { type = NavType.LongType }) ) { backStackEntry -> val vehicleId = backStackEntry.arguments?.getLong("vehicleid") ?: 0L - CarViewScreen(vehicleId = vehicleId) + CarViewScreen(vehicleId = vehicleId, onBack = { navController.popBackStack() }) } composable( route = "timeattacklistitem/{sessionid}", @@ -369,12 +372,12 @@ class MainActivity : ComponentActivity() { ) } composable(route = "createvehicle") { - CarCreationScreen(database) + CarCreationScreen(database, onBack = { navController.popBackStack() }) } composable(route = "timeattack/{vehicleId}/{trackId}") { backStackEntry -> val vehicleId = backStackEntry.arguments?.getString("vehicleId")?.toLongOrNull() ?: -1L val trackId = backStackEntry.arguments?.getString("trackId")?.toLongOrNull() ?: -1L - TimeAttackScreenView(vehicleId = vehicleId, trackId = trackId) + TimeAttackScreenView(vehicleId = vehicleId, trackId = trackId, onBack = { navController.popBackStack() }) } composable(route = "trackandvehicle") { TrackVehicleSelectorScreen(trackViewModel = trackViewModel, vehicleViewModel, navController) diff --git a/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt b/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt index 9618948..a1a8d31 100644 --- a/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/ESPConnectionTestScreen.kt @@ -56,7 +56,10 @@ import kotlin.math.sin @SuppressLint("MissingPermission") @Composable -fun ESPConnectionTestScreen(onNavigateToSettings: () -> Unit) { +fun ESPConnectionTestScreen( + onNavigateToSettings: () -> Unit, + onBack: () -> Unit +) { val context = LocalContext.current val app = context.applicationContext as TrackProApp @@ -104,6 +107,7 @@ fun ESPConnectionTestScreen(onNavigateToSettings: () -> Unit) { GpsProviderType.PHONE_GPS -> "Phone GPS Mode" }, accent = if (isConnected) TrackProTheme.colors.accent else TrackProTheme.colors.textFaint, + onBack = onBack, trailing = { Text( text = "Change", diff --git a/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt b/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt index ce664cf..6bbc7e0 100644 --- a/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/TrackScreen.kt @@ -64,15 +64,15 @@ import org.maplibre.android.style.sources.GeoJsonSource @Composable -fun TrackScreen(trackId: Long) { +fun TrackScreen(trackId: Long, onBack: () -> Unit) { val context = LocalContext.current val app = context.applicationContext as TrackProApp val database = app.database - TrackView(database, trackId) + TrackView(database, trackId, onBack) } @Composable -fun TrackView(database: ESPDatabase, trackId: Long) { +fun TrackView(database: ESPDatabase, trackId: Long, onBack: () -> Unit) { val app = LocalContext.current.applicationContext as TrackProApp val useMetric by app.useMetricUnits.collectAsState() val coroutineScope = rememberCoroutineScope() @@ -107,7 +107,11 @@ fun TrackView(database: ESPDatabase, trackId: Long) { ) { Column(modifier = Modifier.fillMaxSize()) { - AppTopBar(title = "Track Overview", accent = TrackProTheme.colors.accent) + AppTopBar( + title = "Track Overview", + accent = TrackProTheme.colors.accent, + onBack = onBack + ) // ── Track info card ─────────────────────────────── Column( diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt index 4823247..5f9fcbb 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/CarListView.kt @@ -71,6 +71,7 @@ fun CarListScreen(navController: NavController, viewModel: VehicleFULLViewModel) ScreenScaffold( title = "My Vehicles", + onBack = { navController.popBackStack() }, accent = TrackProTheme.colors.accent, trailing = { Text( diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt index 403e3f7..edc3eb5 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/DragTimesList.kt @@ -66,6 +66,7 @@ fun DragTimesListView( ScreenScaffold( title = "Drag Records", + onBack = { navController.popBackStack() }, accent = TrackProTheme.colors.accent, trailing = { Text( diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt index 57b894f..c446309 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TimeAttackListView.kt @@ -70,6 +70,7 @@ fun TimeAttackListViewScreen( ScreenScaffold( title = "Track Records", + onBack = { navController.popBackStack() }, accent = TrackProTheme.colors.accent, trailing = { Text("${trackSessions.size} sessions", style = TrackProType.label, color = TrackProTheme.colors.textMuted) diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt index a4b5fdf..3758e75 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/TrackListView.kt @@ -74,6 +74,7 @@ fun TrackListScreen(navController: NavController, viewModel: TrackViewModel) { ScreenScaffold( title = "My Tracks", + onBack = { navController.popBackStack() }, accent = TrackProTheme.colors.accent, trailing = { Text( diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt index 4f5447d..129bc51 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/CarViewScreen.kt @@ -47,7 +47,7 @@ import kotlinx.coroutines.withContext @Composable -fun CarViewScreen(vehicleId: Long) { +fun CarViewScreen(vehicleId: Long, onBack: () -> Unit) { val context = LocalContext.current val app = context.applicationContext as TrackProApp val useMetric by app.useMetricUnits.collectAsState() @@ -70,6 +70,7 @@ fun CarViewScreen(vehicleId: Long) { // so the screen had no header (and no back affordance) while loading. ScreenScaffold( title = "Vehicle Profile", + onBack = onBack, accent = TrackProTheme.colors.accent, contentScrolled = scrolled ) { contentPadding -> @@ -244,7 +245,8 @@ private fun VehicleInfoRow( fun PreviewCarViewScreen() { CarViewScreen( - vehicleId = 1 + vehicleId = 1, + onBack = {} ) } diff --git a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt index 4343f25..decddb0 100644 --- a/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt +++ b/app/src/main/java/com/example/trackpro/screens/listViewScreens/listItems/TimeAttackListItem.kt @@ -187,6 +187,7 @@ fun TimeAttackListItemScreen( item { AppTopBar( title = "Session Detail", + onBack = { navController.popBackStack() }, accent = TrackProTheme.colors.accent, trailing = { Text("${lapTimes.size} laps", style = TrackProType.label, color = TrackProTheme.colors.textMuted) diff --git a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt index d534d07..9c221dc 100644 --- a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/DragScreen.kt @@ -83,7 +83,8 @@ data class DragMetricDisplay( fun DragRaceScreen( database: ESPDatabase, sessionManager: SessionManager, - vehicleViewModel: VehicleFULLViewModel + vehicleViewModel: VehicleFULLViewModel, + onBack: () -> Unit ) { val app = LocalContext.current.applicationContext as TrackProApp val scope = rememberCoroutineScope() @@ -156,6 +157,7 @@ fun DragRaceScreen( // 1. TOP STATUS BAR AppTopBar( title = "Drag Mode", + onBack = onBack, accent = TrackProTheme.colors.accent, trailing = { Row( diff --git a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt index 96a84db..beebc14 100644 --- a/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/telemetricScreens/TimeAttackScreen.kt @@ -78,6 +78,7 @@ import kotlin.math.sin fun TimeAttackScreenView( trackId: Long? = null, vehicleId: Long? = null, + onBack: () -> Unit ) { val context = LocalContext.current val app = context.applicationContext as TrackProApp @@ -187,7 +188,8 @@ fun TimeAttackScreenView( driver = driverPos, isConnected = isConnected, linesToShow = linesToShow, - lapSplits = lapSplits + lapSplits = lapSplits, + onBack = onBack ) else -> TimeAttackPortraitLayout( timingMode = timingMode, @@ -202,7 +204,8 @@ fun TimeAttackScreenView( driver = driverPos, isConnected = isConnected, linesToShow = linesToShow, - lapSplits = lapSplits + lapSplits = lapSplits, + onBack = onBack ) } @@ -223,7 +226,8 @@ fun TimeAttackPortraitLayout( driver: LatLonOffset, isConnected: Boolean, linesToShow : List, - lapSplits: List = emptyList() + lapSplits: List = emptyList(), + onBack: () -> Unit ) { val deltaColor = if (delta <= 0) TrackProTheme.colors.deltaGood else TrackProTheme.colors.deltaBad val eventName = if (timingMode is TimingMode.Circuit) "LAP" else "RUN" @@ -239,6 +243,7 @@ fun TimeAttackPortraitLayout( AppTopBar( title = "$modeLabel Mode", + onBack = onBack, accent = modeColor, trailing = { Text( @@ -366,7 +371,8 @@ fun TimeAttackLandscapeLayout( driver: LatLonOffset, isConnected: Boolean, linesToShow: List, - lapSplits: List = emptyList() + lapSplits: List = emptyList(), + onBack: () -> Unit ) { val deltaColor = if (delta <= 0) TrackProTheme.colors.deltaGood else TrackProTheme.colors.deltaBad val eventName = if (timingMode is TimingMode.Circuit) "LAP" else "RUN" @@ -387,6 +393,7 @@ fun TimeAttackLandscapeLayout( ) { AppTopBar( title = modeLabel, + onBack = onBack, accent = modeColor, trailing = { Text( diff --git a/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt b/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt index a0fd89c..30e5603 100644 --- a/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt +++ b/app/src/main/java/com/example/trackpro/screens/vehicleScreens/CarCreatorScreen.kt @@ -47,7 +47,8 @@ import kotlinx.coroutines.launch @Composable fun CarCreationScreen( - database: ESPDatabase + database: ESPDatabase, + onBack: () -> Unit ) { val context = LocalContext.current val app = context.applicationContext as TrackProApp @@ -78,6 +79,7 @@ fun CarCreationScreen( ScreenScaffold( title = "Vehicle Setup", + onBack = onBack, accent = TrackProTheme.colors.accent, contentScrolled = scrolled ) { contentPadding ->