Start with no cars so the first one is added on purpose.
This commit is contained in:
1 parent
a164d9c135
commit
13451a9955
8 files changed
+89
-36
No files matched your search
@@ -6,6 +6,8 @@ Pressures are PSI. Tire heat is °F, read inside, middle, then outside.
|
|||||||
|
|
||||||
## On the phone
|
## On the phone
|
||||||
|
|
||||||
|
The phone starts with no cars. Add one from the home screen, or paste a token from another phone.
|
||||||
|
|
||||||
The gear icon opens **Car**. A new install already has the server address filled in:
|
The gear icon opens **Car**. A new install already has the server address filled in:
|
||||||
|
|
||||||
`http://sync.sleepingliongames.com:27017`
|
`http://sync.sleepingliongames.com:27017`
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "com.racingapp.pitlog"
|
applicationId = "com.racingapp.pitlog"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 2
|
versionCode = 3
|
||||||
versionName = "1.1"
|
versionName = "1.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -246,19 +246,6 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
fun create(context: Context): AppDatabase {
|
fun create(context: Context): AppDatabase {
|
||||||
return Room.databaseBuilder(context, AppDatabase::class.java, "pitlog.db")
|
return Room.databaseBuilder(context, AppDatabase::class.java, "pitlog.db")
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
|
||||||
.addCallback(
|
|
||||||
object : Callback() {
|
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
|
||||||
super.onCreate(db)
|
|
||||||
db.execSQL(
|
|
||||||
"""
|
|
||||||
INSERT INTO cars (carName, raceClass, discipline, token, updatedAt, syncRev, deleted, pending)
|
|
||||||
VALUES ('$DEFAULT_CAR', '$DEFAULT_CLASS', '$DEFAULT_DISCIPLINE', printf('%08d', abs(random()) % 100000000), 1, 0, 0, 1)
|
|
||||||
""".trimIndent(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,12 @@ interface RaceDao {
|
|||||||
@Query("SELECT * FROM cars ORDER BY id ASC")
|
@Query("SELECT * FROM cars ORDER BY id ASC")
|
||||||
suspend fun everyCar(): List<Car>
|
suspend fun everyCar(): List<Car>
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM race_nights WHERE carId = :carId AND deleted = 0")
|
||||||
|
suspend fun nightCount(carId: Long): Int
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM setup_sheets WHERE carId = :carId AND deleted = 0")
|
||||||
|
suspend fun sheetCount(carId: Long): Int
|
||||||
|
|
||||||
@Query("SELECT * FROM cars WHERE deleted = 0 ORDER BY id ASC")
|
@Query("SELECT * FROM cars WHERE deleted = 0 ORDER BY id ASC")
|
||||||
suspend fun activeCars(): List<Car>
|
suspend fun activeCars(): List<Car>
|
||||||
|
|
||||||
|
|||||||
@@ -10,22 +10,18 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
suspend fun ensureCars(dao: RaceDao, preferences: SyncPreferences) {
|
suspend fun ensureCars(dao: RaceDao, preferences: SyncPreferences) {
|
||||||
val cars = dao.everyCar()
|
for (car in dao.everyCar()) {
|
||||||
if (cars.isEmpty()) {
|
if (car.deleted) continue
|
||||||
val id = dao.insertCar(
|
val untouched = car.updatedAt == 1L &&
|
||||||
Car(
|
car.carName == DEFAULT_CAR &&
|
||||||
carName = DEFAULT_CAR,
|
car.raceClass == DEFAULT_CLASS &&
|
||||||
raceClass = DEFAULT_CLASS,
|
car.discipline == DEFAULT_DISCIPLINE &&
|
||||||
discipline = DEFAULT_DISCIPLINE,
|
dao.nightCount(car.id) == 0 &&
|
||||||
token = newCarToken(),
|
dao.sheetCount(car.id) == 0
|
||||||
updatedAt = 1,
|
if (untouched) {
|
||||||
pending = true,
|
dao.updateCar(car.copy(deleted = true, pending = false))
|
||||||
),
|
continue
|
||||||
)
|
|
||||||
preferences.selectedCarId = id
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for (car in cars) {
|
|
||||||
if (isEightDigitToken(car.token)) continue
|
if (isEightDigitToken(car.token)) continue
|
||||||
val source = car.token.ifBlank { preferences.token.trim() }
|
val source = car.token.ifBlank { preferences.token.trim() }
|
||||||
val next = if (source.isBlank()) newCarToken() else eightDigitToken(source)
|
val next = if (source.isBlank()) newCarToken() else eightDigitToken(source)
|
||||||
@@ -34,10 +30,14 @@ suspend fun ensureCars(dao: RaceDao, preferences: SyncPreferences) {
|
|||||||
dao.markRunsPending(car.id)
|
dao.markRunsPending(car.id)
|
||||||
dao.markSheetsPending(car.id)
|
dao.markSheetsPending(car.id)
|
||||||
}
|
}
|
||||||
val first = cars.firstOrNull { !it.deleted } ?: cars.first()
|
val active = dao.activeCars()
|
||||||
|
if (active.isEmpty()) {
|
||||||
|
preferences.selectedCarId = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
val selected = preferences.selectedCarId
|
val selected = preferences.selectedCarId
|
||||||
if (selected == 0L || cars.none { it.id == selected && !it.deleted }) {
|
if (selected == 0L || active.none { it.id == selected }) {
|
||||||
preferences.selectedCarId = first.id
|
preferences.selectedCarId = active.first().id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,8 +150,15 @@ fun HomeScreen(
|
|||||||
IconButton(onClick = onSettings) {
|
IconButton(onClick = onSettings) {
|
||||||
Icon(Icons.Filled.Settings, contentDescription = "Car")
|
Icon(Icons.Filled.Settings, contentDescription = "Car")
|
||||||
}
|
}
|
||||||
IconButton(onClick = onNewNight) {
|
IconButton(
|
||||||
Icon(Icons.Filled.Add, contentDescription = "New race night")
|
onClick = {
|
||||||
|
if (state.cars.isEmpty()) viewModel.addCar(onSettings) else onNewNight()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Add,
|
||||||
|
contentDescription = if (state.cars.isEmpty()) "Add a car" else "New race night",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -166,6 +173,9 @@ fun HomeScreen(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = padding,
|
contentPadding = padding,
|
||||||
) {
|
) {
|
||||||
|
if (state.cars.isEmpty()) {
|
||||||
|
item { EmptyGarage(onAddCar = { viewModel.addCar(onSettings) }) }
|
||||||
|
} else {
|
||||||
item {
|
item {
|
||||||
HomeHeader(
|
HomeHeader(
|
||||||
profile = state.profile,
|
profile = state.profile,
|
||||||
@@ -197,6 +207,7 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HomeHeader(
|
private fun HomeHeader(
|
||||||
@@ -283,6 +294,29 @@ private fun HomeHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EmptyGarage(onAddCar: () -> Unit) {
|
||||||
|
Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
|
||||||
|
Text(
|
||||||
|
"No car yet",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Add a car to start a log. Each car gets its own 8-digit token.",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = onAddCar,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
) {
|
||||||
|
Text("Add a car")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun EmptyNights() {
|
private fun EmptyNights() {
|
||||||
Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
|
Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
|
||||||
@@ -366,3 +400,4 @@ private fun CarMark() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ fun SettingsScreen(onBack: () -> Unit) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
if (viewModel.ready) {
|
if (viewModel.ready && viewModel.carId != 0L) {
|
||||||
PitActionButton(
|
PitActionButton(
|
||||||
text = if (viewModel.saving) "Saving…" else "Save",
|
text = if (viewModel.saving) "Saving…" else "Save",
|
||||||
onClick = { viewModel.save(onBack) },
|
onClick = { viewModel.save(onBack) },
|
||||||
@@ -215,6 +215,20 @@ fun SettingsScreen(onBack: () -> Unit) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PitScroll(padding) {
|
PitScroll(padding) {
|
||||||
|
if (viewModel.carId == 0L) {
|
||||||
|
Text(
|
||||||
|
"Add a car before you log a night. Pressures are PSI and tire heat is °F.",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = viewModel::addCar,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 12.dp),
|
||||||
|
) {
|
||||||
|
Text("Add a car")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Text(
|
Text(
|
||||||
"New race nights start with this car. Pressures are PSI and tire heat is °F.",
|
"New race nights start with this car. Pressures are PSI and tire heat is °F.",
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@@ -276,6 +290,7 @@ fun SettingsScreen(onBack: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
Text("Add a car")
|
Text("Add a car")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SectionLabel("Add a car from a token")
|
SectionLabel("Add a car from a token")
|
||||||
WordsField(
|
WordsField(
|
||||||
label = "Token from another phone",
|
label = "Token from another phone",
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ class PreviewApp : RacingApp() {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
repository.saveAddress("")
|
repository.saveAddress("")
|
||||||
runBlocking(Dispatchers.IO) {
|
runBlocking(Dispatchers.IO) {
|
||||||
|
val car = repository.addCar()
|
||||||
|
repository.saveCar(
|
||||||
|
car.copy(
|
||||||
|
carName = DEFAULT_CAR,
|
||||||
|
raceClass = DEFAULT_CLASS,
|
||||||
|
discipline = DEFAULT_DISCIPLINE,
|
||||||
|
),
|
||||||
|
)
|
||||||
val nightId = repository.saveNight(
|
val nightId = repository.saveNight(
|
||||||
RaceNight(
|
RaceNight(
|
||||||
dateEpochDay = LocalDate.now().toEpochDay(),
|
dateEpochDay = LocalDate.now().toEpochDay(),
|
||||||
|
|||||||
Reference in new issue
Block a user