Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 119 additions & 116 deletions app/src/main/java/com/example/trackpro/MainActivity.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.example.trackpro.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.trackpro.dataClasses.TrackCoordinatesData
import kotlinx.coroutines.flow.Flow

Expand All @@ -20,6 +21,11 @@ interface TrackCoordinatesDataDAO {
@Insert
suspend fun insertTrack(data: List<TrackCoordinatesData>)

// Bulk-updates existing points in place (matched by primary key) - used to (re)apply
// sector markers to an already-saved track without re-recording it.
@Update
suspend fun updateTrackCoordinates(data: List<TrackCoordinatesData>)

// IF the user whats to recreate the track
//OR
// IF the user filters the coordinates (if the full track is complete)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface TrackMainDataDAO {
@Query("SELECT * FROM track_main_data ORDER BY trackName ASC")
fun getAllTrack(): Flow<List<TrackMainData>>

// One-shot (non-Flow) name lookup used to dedup premade-track seeding on startup.
@Query("SELECT trackName FROM track_main_data")
suspend fun getAllTrackNames(): List<String>

@Query("Select * from track_main_data where trackId =:trackId")
fun getTrack(trackId: Long): Flow<TrackMainData>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.input.KeyboardType
import com.example.trackpro.theme.TrackProType

@Composable
fun CustomTextField(
label: String,
value: String,
isNumber: Boolean = false,
leadingIcon: ImageVector? = null,
accent: Color = TrackProTheme.colors.accentCyan,
onValueChange: (String) -> Unit
) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
label = { Text(label, color = TrackProTheme.colors.textMuted) },
textStyle = TrackProType.body,
label = { Text(label, style = TrackProType.body, color = TrackProTheme.colors.textMuted) },
keyboardOptions = if (isNumber) KeyboardOptions(keyboardType = KeyboardType.Number) else KeyboardOptions.Default,
leadingIcon = if (leadingIcon != null) {
{
Expand All @@ -33,10 +37,10 @@ fun CustomTextField(
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = TrackProTheme.colors.textPrimary,
unfocusedTextColor = TrackProTheme.colors.textPrimary,
focusedBorderColor = TrackProTheme.colors.accentCyan,
focusedBorderColor = accent,
unfocusedBorderColor = TrackProTheme.colors.sectorLine,
focusedLabelColor = TrackProTheme.colors.accentCyan,
cursorColor = TrackProTheme.colors.accentCyan
focusedLabelColor = accent,
cursorColor = accent
),
modifier = Modifier.fillMaxWidth()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,71 @@ import com.example.trackpro.models.VehiclePair



/**
* Generic replacement for [DropdownMenuFieldMulti] / [TrackDropdownMenu] below (originally
* a third, string-only copy existed too - migrated and removed). New call sites should use
* this one; the remaining two are migrated screen-by-screen and then removed.
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T> AppDropdownField(
label: String,
items: List<T>,
selectedLabel: String,
itemLabel: (T) -> String,
onSelect: (T) -> Unit,
modifier: Modifier = Modifier,
accent: Color = TrackProTheme.colors.accentCyan,
emptyMessage: String = "No options available"
) {
var expanded by remember { mutableStateOf(false) }

ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = modifier.fillMaxWidth()
) {
OutlinedTextField(
value = selectedLabel,
onValueChange = {},
readOnly = true,
label = { Text(label, color = TrackProTheme.colors.textMuted) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = TrackProTheme.colors.textPrimary,
unfocusedTextColor = TrackProTheme.colors.textPrimary,
focusedBorderColor = accent,
unfocusedBorderColor = TrackProTheme.colors.sectorLine
),
modifier = Modifier.fillMaxWidth()
)

ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.background(TrackProTheme.colors.bgElevated)
) {
if (items.isEmpty()) {
DropdownMenuItem(
text = { Text(emptyMessage, color = TrackProTheme.colors.textMuted) },
onClick = { expanded = false },
enabled = false
)
} else {
items.forEach { item ->
DropdownMenuItem(
text = { Text(itemLabel(item), color = TrackProTheme.colors.textPrimary) },
onClick = {
expanded = false
onSelect(item)
}
)
}
}
}
}
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DropdownMenuFieldMulti(label: String, options: List<VehiclePair>, selectedOption: String, onOptionSelected: (Long) -> Unit) {
Expand Down Expand Up @@ -121,51 +186,3 @@ fun TrackDropdownMenu(



@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DropdownMenuField(
label: String,
options: List<String>,
selectedOption: String,
textColor: Color = TrackProTheme.colors.textPrimary,
onOptionSelected: (String) -> Unit
) {
var expanded by remember { mutableStateOf(false) }

ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
OutlinedTextField(
value = selectedOption,
onValueChange = {},
readOnly = true,
label = { Text(label, color = TrackProTheme.colors.textMuted) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
colors = OutlinedTextFieldDefaults.colors(
focusedTextColor = textColor,
unfocusedTextColor = textColor,
focusedBorderColor = TrackProTheme.colors.accentCyan,
unfocusedBorderColor = TrackProTheme.colors.sectorLine
),
modifier = Modifier
.fillMaxWidth()
)

ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.background(TrackProTheme.colors.bgElevated)
) {
options.forEach { option ->
DropdownMenuItem(
text = { Text(option, color = TrackProTheme.colors.textPrimary) },
onClick = {
onOptionSelected(option)
expanded = false
}
)
}
}
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.example.trackpro.managerClasses

import android.content.Context
import android.util.Log
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.trackpro.dao.DerivedDataDao
import com.example.trackpro.dao.LapInfoDataDAO
import com.example.trackpro.dao.LapTimeDataDAO
Expand All @@ -24,15 +22,8 @@ import com.example.trackpro.dataClasses.SectorTimeData
import com.example.trackpro.dataClasses.SessionData
import com.example.trackpro.dataClasses.SmoothedGPSData
import com.example.trackpro.dataClasses.TrackCoordinatesData
import com.example.trackpro.dataClasses.TrackJson
import com.example.trackpro.dataClasses.TrackMainData
import com.example.trackpro.dataClasses.VehicleInformationData
import com.example.trackpro.R
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

@Database(entities =
[
Expand Down Expand Up @@ -70,61 +61,18 @@ abstract class ESPDatabase : RoomDatabase() {
ESPDatabase::class.java,
"esp_database"
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)

CoroutineScope(Dispatchers.IO).launch {
try {
val inputStream = context.resources.openRawResource(R.raw.tracks) // your JSON file
val jsonString = inputStream.bufferedReader().use { it.readText() }

val tracks: List<TrackJson> = Gson().fromJson(
jsonString,
object : TypeToken<List<TrackJson>>() {}.type
)

val db = getInstance(context)

for (track in tracks) {
val trackId = db.trackMainDao().insertTrackMainDataDAO(
TrackMainData(
trackName = track.trackName,
totalLength = track.totalLength,
country = track.country,
type = track.type
)
)

val coords = track.coordinates.mapIndexed { index, coord ->
TrackCoordinatesData(
trackId = trackId,
latitude = coord.lat,
longitude = coord.lon,
altitude = null,
isStartPoint = index == 0
)
}

db.trackCoordinatesDao().insertTrack(coords)

Log.d("DB_INIT", "Inserted ${coords.size} points for ${track.trackName}")
}

} catch (e: Exception) {
Log.e("DB_INIT", "Error inserting tracks", e)
}
}

}
})
// No migrations exist yet; without this, any future (or this) schema
// change throws IllegalStateException on every existing install instead
// of recovering. Replace with real Migration objects once the schema
// needs to be preserved across upgrades.
.fallbackToDestructiveMigration()
.build()

// Premade tracks (res/raw/tracks.json) are synced separately on every app
// start via TrackSeeder, called from TrackProApp.onCreate() - not here, since
// this factory can be called from a background thread and seeding is its own
// idempotent, name-deduped operation rather than a one-time DB-creation hook.

INSTANCE = instance
instance
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.example.trackpro.managerClasses.gpsDataManagers

import android.util.Log
import com.example.trackpro.dataClasses.RawGPSData
import com.example.trackpro.managerClasses.calculationClasses.convertToUnixTimestamp
import com.example.trackpro.models.GpsProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -100,7 +99,11 @@ class ESPTcpClient(
altitude = raw.altitude,
speed = raw.speed,
fixQuality = raw.satellites,
timestamp = convertToUnixTimestamp(raw.timestamp)
// Stamped on receipt rather than the ESP32-reported timestamp string:
// elapsed-time math (0-60, quarter mile, etc.) needs consistent
// relative precision between samples, and the module's own timestamp
// has no guaranteed sub-second resolution.
timestamp = System.currentTimeMillis()
)
_gpsFlow.value = parsed // Use .value instead of .emit()
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class PhoneGpsProvider(
altitude = loc.altitude,
speed = loc.speed * 3.6f, // CRITICAL: Convert m/s to km/h
fixQuality = if (loc.accuracy < 10) 3 else 1,
timestamp = loc.time
// Stamped on receipt rather than using loc.time: elapsed-time math (0-60,
// quarter mile, etc.) needs consistent relative precision between samples,
// and this is the same clock the live view already uses for that math.
timestamp = System.currentTimeMillis()
)
}
}
Expand Down
Loading
Loading