Skip to content

Commit

Permalink
Allow editing max depth
Browse files Browse the repository at this point in the history
  • Loading branch information
Tetr4 committed Nov 12, 2023
1 parent 2a119e6 commit 1dd30a2
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 39 deletions.
2 changes: 0 additions & 2 deletions app/src/main/kotlin/cloud/mike/divelog/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import cloud.mike.divelog.ui.DiveTheme
import cloud.mike.divelog.ui.NavRoot
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/kotlin/cloud/mike/divelog/localization/Parsing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cloud.mike.divelog.localization

import java.text.NumberFormat
import java.text.ParseException
import java.util.Locale

fun String.isValidNumber(locale: Locale) = toNumberOrNull(locale) != null

fun String.toNumberOrNull(locale: Locale): Number? = try {
NumberFormat.getInstance(locale).parse(this)?.toFloat()
} catch (e: ParseException) {
null
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import cloud.mike.divelog.ui.DiveTheme
@Composable
fun FormTextButton(
onClick: () -> Unit,
text: String,
value: String,
placeholder: String,
modifier: Modifier = Modifier,
) {
Surface(
Expand All @@ -37,20 +38,38 @@ fun FormTextButton(
) {
Text(
modifier = Modifier.padding(vertical = 16.dp),
text = text,
color = MaterialTheme.colorScheme.onSurface,
text = value.ifBlank { placeholder },
color = if (value.isBlank()) {
MaterialTheme.colorScheme.onSurfaceVariant
} else {
MaterialTheme.colorScheme.onSurface
},
)
}
}
}

@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun PreviewEmpty() {
DiveTheme {
FormTextButton(
value = "",
placeholder = "Placeholder",
onClick = {},
)
}
}

@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun Preview() {
DiveTheme {
FormTextButton(
text = "Lorem Ipsum",
value = "Lorem Ipsum",
placeholder = "Placeholder",
onClick = {},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import cloud.mike.divelog.ui.common.states.ErrorState
import cloud.mike.divelog.ui.common.states.LoadingState
import cloud.mike.divelog.ui.detail.items.InfoItem
import cloud.mike.divelog.ui.detail.items.LocationItem
import cloud.mike.divelog.ui.detail.items.MaxDepthItem
import cloud.mike.divelog.ui.detail.items.NotesItem
import cloud.mike.divelog.ui.detail.items.ProfileItem
import cloud.mike.divelog.ui.detail.states.EmptyState
Expand Down Expand Up @@ -115,10 +116,15 @@ private fun ContentState(
dive.profile?.let {
ProfileItem(
profile = it,
maxDepthMeters = dive.maxDepthMeters, // TODO show max depth even without profile
maxDepthMeters = dive.maxDepthMeters,
minTemperatureCelsius = dive.minTemperatureCelsius,
)
}
if (dive.profile == null) {
dive.maxDepthMeters?.let {
MaxDepthItem(it)
}
}
dive.notes?.let {
NotesItem(it)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cloud.mike.divelog.ui.detail.items

import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Height
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cloud.mike.divelog.R
import cloud.mike.divelog.data.dives.Dive
import cloud.mike.divelog.localization.formatDepthMeters
import cloud.mike.divelog.ui.DiveTheme
import cloud.mike.divelog.ui.common.card.CardHeadline
import cloud.mike.divelog.ui.spacing

@Composable
fun MaxDepthItem(
maxDepthMeters: Float,
modifier: Modifier = Modifier,
) {
Card(modifier = modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(MaterialTheme.spacing.cardPadding),
) {
CardHeadline(
imageVector = Icons.Default.Height,
title = stringResource(R.string.dive_detail_label_max_depth),
)
Spacer(Modifier.height(8.dp))
Text(
text = maxDepthMeters.formatDepthMeters(),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun Preview() {
DiveTheme {
MaxDepthItem(
maxDepthMeters = Dive.sample.maxDepthMeters ?: 12.34f,
)
}
}
8 changes: 8 additions & 0 deletions app/src/main/kotlin/cloud/mike/divelog/ui/edit/EditScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cloud.mike.divelog.data.dives.Dive
import cloud.mike.divelog.localization.errors.ErrorMessage
import cloud.mike.divelog.localization.primaryLocale
import cloud.mike.divelog.localization.toNumberOrNull
import cloud.mike.divelog.ui.DiveTheme
import cloud.mike.divelog.ui.common.states.ErrorState
import cloud.mike.divelog.ui.common.states.LoadingState
import cloud.mike.divelog.ui.edit.items.DiveDurationItem
import cloud.mike.divelog.ui.edit.items.DiveStartDateItem
import cloud.mike.divelog.ui.edit.items.DiveStartTimeItem
import cloud.mike.divelog.ui.edit.items.LocationItem
import cloud.mike.divelog.ui.edit.items.MaxDepthItem
import cloud.mike.divelog.ui.edit.items.NotesItem
import cloud.mike.divelog.ui.edit.topbar.EditDiveAppBar

Expand All @@ -42,6 +45,7 @@ fun EditScreen(
) {
val formState = rememberFormState(dive = uiState.diveState.dive)
val snackbarHostState = remember { SnackbarHostState() }
val locale = primaryLocale

suspend fun showError(message: ErrorMessage) {
snackbarHostState.showSnackbar(message.content)
Expand All @@ -53,6 +57,8 @@ fun EditScreen(
startTime = formState.startTime,
duration = formState.duration ?: return,
location = formState.location.trim().takeIf { it.isNotBlank() },
maxDepthMeters = formState.maxDepthMeters.trim().takeIf { it.isNotBlank() }
?.toNumberOrNull(locale)?.toFloat(), // validation is done in text field
notes = formState.notes.trim().takeIf { it.isNotBlank() },
)
onSave(data)
Expand Down Expand Up @@ -123,6 +129,8 @@ fun ContentState(
Divider(Modifier.padding(vertical = 4.dp))
LocationItem(formState)
Divider(Modifier.padding(vertical = 4.dp))
MaxDepthItem(formState)
Divider(Modifier.padding(vertical = 4.dp))
NotesItem(formState)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ data class FormData(
val duration: Duration,
val location: String?, // TODO allow selecting GPS coordinates
val notes: String?,
// TODO allow editing max depth
val maxDepthMeters: Float?,
)

@Immutable
Expand Down Expand Up @@ -130,7 +130,7 @@ class EditViewModel(
coordinates = null,
)
},
maxDepthMeters = null,
maxDepthMeters = data.maxDepthMeters,
minTemperatureCelsius = null,
profile = null,
notes = data.notes,
Expand All @@ -153,6 +153,7 @@ class EditViewModel(
coordinates = null,
)
},
maxDepthMeters = data.maxDepthMeters,
notes = data.notes,
)
diveRepo.updateDive(new)
Expand Down
43 changes: 26 additions & 17 deletions app/src/main/kotlin/cloud/mike/divelog/ui/edit/FormState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import cloud.mike.divelog.data.dives.Dive
import cloud.mike.divelog.localization.format
import cloud.mike.divelog.ui.common.DurationSaver
import cloud.mike.divelog.ui.common.LocalDateSaver
import cloud.mike.divelog.ui.common.LocalTimeSaver
Expand All @@ -22,13 +23,15 @@ class FormState(
startTime: MutableState<LocalTime?>,
duration: MutableState<Duration?>,
location: MutableState<String>,
maxDepthMeters: MutableState<String>,
notes: MutableState<String>,
) {
var startDate by startDate
var startTime by startTime
var duration by duration
var location by location
var notes by notes
var maxDepthMeters by maxDepthMeters

val isValid
get() = startDate != null && duration != null
Expand All @@ -39,20 +42,26 @@ fun rememberFormState(
dive: Dive?,
defaultDate: LocalDate? = LocalDate.now(),
defaultDuration: Duration? = 1.hours,
) = FormState(
startDate = rememberSaveable(dive?.startDate, stateSaver = LocalDateSaver) {
mutableStateOf(dive?.startDate ?: defaultDate)
},
startTime = rememberSaveable(dive?.startTime, stateSaver = LocalTimeSaver) {
mutableStateOf(dive?.startTime)
},
duration = rememberSaveable(dive?.duration, stateSaver = DurationSaver) {
mutableStateOf(dive?.duration ?: defaultDuration)
},
location = rememberSaveable(dive?.location) {
mutableStateOf(dive?.location?.name.orEmpty())
},
notes = rememberSaveable(dive?.notes) {
mutableStateOf(dive?.notes.orEmpty())
},
)
): FormState {
val maxDepthMetersFormatted = dive?.maxDepthMeters?.format().orEmpty()
return FormState(
startDate = rememberSaveable(dive?.startDate, stateSaver = LocalDateSaver) {
mutableStateOf(dive?.startDate ?: defaultDate)
},
startTime = rememberSaveable(dive?.startTime, stateSaver = LocalTimeSaver) {
mutableStateOf(dive?.startTime)
},
duration = rememberSaveable(dive?.duration, stateSaver = DurationSaver) {
mutableStateOf(dive?.duration ?: defaultDuration)
},
location = rememberSaveable(dive?.location) {
mutableStateOf(dive?.location?.name.orEmpty())
},
maxDepthMeters = rememberSaveable(maxDepthMetersFormatted) {
mutableStateOf(maxDepthMetersFormatted)
},
notes = rememberSaveable(dive?.notes) {
mutableStateOf(dive?.notes.orEmpty())
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ fun DiveDurationItem(
FormTextButton(
modifier = Modifier.weight(1f),
onClick = { showDurationPicker = true },
text = formState.duration?.format()
?: stringResource(R.string.edit_dive_button_add_duration),
value = formState.duration?.format().orEmpty(),
placeholder = stringResource(R.string.edit_dive_button_add_duration),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ fun DiveStartDateItem(
FormTextButton(
modifier = Modifier.weight(1f),
onClick = { showDatePicker = true },
text = formState.startDate?.format()
?: stringResource(R.string.edit_dive_button_add_date),
value = formState.startDate?.format().orEmpty(),
placeholder = stringResource(R.string.edit_dive_button_add_date),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ fun DiveStartTimeItem(
FormTextButton(
modifier = Modifier.weight(1f),
onClick = { showTimePicker = true },
text = formState.startTime?.format(FormatStyle.SHORT)
?: stringResource(R.string.edit_dive_button_add_time),
value = formState.startTime?.format(FormatStyle.SHORT).orEmpty(),
placeholder = stringResource(R.string.edit_dive_button_add_time),
)
if (formState.startTime != null) {
IconButton(onClick = { formState.startTime = null }) {
Expand Down
Loading

0 comments on commit 1dd30a2

Please sign in to comment.