feat(android): unify capture-first mobile interface
This commit is contained in:
parent
88e0eb0fb7
commit
cb4010dbd5
15 changed files with 276 additions and 182 deletions
|
|
@ -45,6 +45,16 @@ val trainlogVersionCode =
|
|||
?: throw GradleException("TRAINLOG_ANDROID_VERSION_CODE must be a positive integer")
|
||||
} ?: 4
|
||||
|
||||
/* WHY: private in-place candidates can remain on the installed stable product
|
||||
* line while development continues on the next version. CONTRACT: the build-
|
||||
* only override accepts a numeric semantic version and leaves the repository
|
||||
* default untouched. */
|
||||
val trainlogVersionName =
|
||||
providers.environmentVariable("TRAINLOG_ANDROID_VERSION_NAME").orNull?.let { raw ->
|
||||
raw.takeIf { it.matches(Regex("[0-9]+\\.[0-9]+\\.[0-9]+")) }
|
||||
?: throw GradleException("TRAINLOG_ANDROID_VERSION_NAME must be numeric x.y.z")
|
||||
} ?: "0.1.3"
|
||||
|
||||
android {
|
||||
namespace = "com.labfytools.trainlog"
|
||||
compileSdk = 37
|
||||
|
|
@ -55,7 +65,7 @@ android {
|
|||
targetSdk = 36
|
||||
|
||||
versionCode = trainlogVersionCode
|
||||
versionName = "0.1.3"
|
||||
versionName = trainlogVersionName
|
||||
testInstrumentationRunner =
|
||||
"androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,8 +82,6 @@ class DraftUiTestActivity : ComponentActivity() {
|
|||
?: (loaded as?
|
||||
ActiveDraftLoadResult.Loaded)
|
||||
?.warning,
|
||||
latestSession = null,
|
||||
latestMaximum = null,
|
||||
bodyZoneOverview = repository.getBodyZoneHomeOverview(),
|
||||
onSession = {
|
||||
when (
|
||||
|
|
@ -102,9 +100,7 @@ class DraftUiTestActivity : ComponentActivity() {
|
|||
}
|
||||
},
|
||||
onOpenExercises = {},
|
||||
onOpenStatistics = {},
|
||||
onBody = {},
|
||||
onOpenLatestSession = {},
|
||||
onSync = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ private val drawerIcons = mapOf(
|
|||
AppSection.SESSIONS to R.drawable.ic_sessions,
|
||||
AppSection.EXERCISES to R.drawable.ic_exercises,
|
||||
AppSection.EQUIPMENT to R.drawable.ic_equipment,
|
||||
AppSection.STATISTICS to R.drawable.ic_statistics,
|
||||
AppSection.SYNC to R.drawable.ic_sync,
|
||||
AppSection.SETTINGS to R.drawable.ic_settings,
|
||||
)
|
||||
|
|
@ -59,8 +58,7 @@ fun AndroidAppShell(
|
|||
fun sectionLabel(section: AppSection): String = strings.getString(when (section) {
|
||||
AppSection.HOME -> R.string.nav_home; AppSection.SESSIONS -> R.string.nav_sessions
|
||||
AppSection.EXERCISES -> R.string.nav_exercises; AppSection.EQUIPMENT -> R.string.nav_equipment
|
||||
AppSection.STATISTICS -> R.string.nav_statistics; AppSection.SYNC -> R.string.nav_sync
|
||||
AppSection.SETTINGS -> R.string.nav_settings
|
||||
AppSection.SYNC -> R.string.nav_sync; AppSection.SETTINGS -> R.string.nav_settings
|
||||
})
|
||||
fun title(): String = strings.getString(when (route) {
|
||||
AppRoute.Home -> R.string.nav_home; AppRoute.Sessions -> R.string.nav_sessions
|
||||
|
|
@ -71,7 +69,7 @@ fun AndroidAppShell(
|
|||
AppRoute.Exercises -> R.string.nav_exercises; is AppRoute.ExerciseDetail -> R.string.route_exercise_detail
|
||||
is AppRoute.ExerciseEdit -> R.string.route_exercise_edit; is AppRoute.ExerciseCreate -> R.string.route_exercise_create
|
||||
AppRoute.Equipment -> R.string.nav_equipment; is AppRoute.EquipmentDetail -> R.string.route_equipment_detail
|
||||
is AppRoute.EquipmentCreate -> R.string.route_equipment_create; AppRoute.Statistics -> R.string.nav_statistics
|
||||
is AppRoute.EquipmentCreate -> R.string.route_equipment_create
|
||||
AppRoute.BodyMeasurements -> R.string.route_body_measurements; AppRoute.LatestMaxima -> R.string.route_latest_maxima
|
||||
AppRoute.Sync -> R.string.nav_sync; AppRoute.Settings -> R.string.nav_settings
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import androidx.lifecycle.ViewModel
|
|||
|
||||
enum class AppSection {
|
||||
HOME, SESSIONS, EXERCISES,
|
||||
EQUIPMENT, STATISTICS, SYNC, SETTINGS,
|
||||
EQUIPMENT, SYNC, SETTINGS,
|
||||
}
|
||||
|
||||
sealed interface AppRoute {
|
||||
|
|
@ -34,9 +34,8 @@ sealed interface AppRoute {
|
|||
data object Equipment : AppRoute { override val section = AppSection.EQUIPMENT }
|
||||
data class EquipmentDetail(val equipmentId: String) : AppRoute { override val section = AppSection.EQUIPMENT }
|
||||
data class EquipmentCreate(val caller: AppRoute) : AppRoute { override val section = caller.section }
|
||||
data object Statistics : AppRoute { override val section = AppSection.STATISTICS }
|
||||
data object BodyMeasurements : AppRoute { override val section = AppSection.STATISTICS }
|
||||
data object LatestMaxima : AppRoute { override val section = AppSection.STATISTICS }
|
||||
data object BodyMeasurements : AppRoute { override val section = AppSection.HOME }
|
||||
data object LatestMaxima : AppRoute { override val section = AppSection.EXERCISES }
|
||||
data object Sync : AppRoute { override val section = AppSection.SYNC }
|
||||
data object Settings : AppRoute { override val section = AppSection.SETTINGS }
|
||||
}
|
||||
|
|
@ -46,7 +45,6 @@ fun AppSection.rootRoute(): AppRoute = when (this) {
|
|||
AppSection.SESSIONS -> AppRoute.Sessions
|
||||
AppSection.EXERCISES -> AppRoute.Exercises
|
||||
AppSection.EQUIPMENT -> AppRoute.Equipment
|
||||
AppSection.STATISTICS -> AppRoute.Statistics
|
||||
AppSection.SYNC -> AppRoute.Sync
|
||||
AppSection.SETTINGS -> AppRoute.Settings
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ package com.labfytools.trainlog.ui
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -852,7 +855,11 @@ private fun TrainlogChoiceGroup(
|
|||
),
|
||||
)
|
||||
|
||||
content()
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) { content() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -869,15 +876,15 @@ private fun TrainlogChoice(
|
|||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp)
|
||||
.widthIn(min = 132.dp)
|
||||
.heightIn(min = 48.dp)
|
||||
.background(
|
||||
if (selected) {
|
||||
colors.surfaceAlt
|
||||
} else {
|
||||
colors.surface
|
||||
}
|
||||
},
|
||||
androidx.compose.material3.MaterialTheme.shapes.medium,
|
||||
)
|
||||
.semantics { this.selected = selected }
|
||||
.clickable(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.IntrinsicSize
|
|||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -52,8 +51,6 @@ import com.labfytools.trainlog.data.BodyZoneHomeOverview
|
|||
import com.labfytools.trainlog.data.BodyZoneHomeState
|
||||
import com.labfytools.trainlog.data.BodyZoneHomeStatus
|
||||
import com.labfytools.trainlog.model.ActiveSessionDraft
|
||||
import com.labfytools.trainlog.model.LatestExerciseMax
|
||||
import com.labfytools.trainlog.model.SessionSummary
|
||||
import com.labfytools.trainlog.model.SessionType
|
||||
import com.labfytools.trainlog.ui.theme.LocalTrainlogColors
|
||||
import com.labfytools.trainlog.ui.theme.TrainlogColors
|
||||
|
|
@ -64,19 +61,14 @@ import com.labfytools.trainlog.R
|
|||
fun HomeScreen(
|
||||
activeDraft: ActiveSessionDraft?,
|
||||
draftError: String?,
|
||||
latestSession: SessionSummary?,
|
||||
latestMaximum: LatestExerciseMax?,
|
||||
bodyZoneOverview: BodyZoneHomeOverview,
|
||||
onSession: () -> Unit,
|
||||
onOpenExercises: (String) -> Unit,
|
||||
onOpenStatistics: () -> Unit,
|
||||
onBody: () -> Unit,
|
||||
onOpenLatestSession: (String) -> Unit,
|
||||
onSync: () -> Unit,
|
||||
) {
|
||||
val colors = LocalTrainlogColors.current
|
||||
val strings = localizedContext()
|
||||
val locale = presentationLocale()
|
||||
TrainlogScreen(strings.getString(R.string.nav_home), scrollKey = "home") {
|
||||
draftError?.let { TrainlogInfo(it, colors.error) }
|
||||
TrainlogFrame(strings.getString(R.string.home_session)) {
|
||||
|
|
@ -87,17 +79,7 @@ fun HomeScreen(
|
|||
TrainlogPrimaryAction(strings.getString(R.string.new_manual_session), strings.getString(R.string.new_manual_description), onSession)
|
||||
}
|
||||
}
|
||||
BodyZoneHomeSection(bodyZoneOverview, onOpenExercises, onOpenStatistics)
|
||||
latestSession?.let { session ->
|
||||
TrainlogFrame(strings.getString(R.string.latest_session)) {
|
||||
TrainlogAction(formatStartedAt(session.startedAt), sessionTypeLabel(session.sessionType) + " · " +
|
||||
strings.resources.getQuantityString(R.plurals.exercise_count, session.exerciseCount, session.exerciseCount), { onOpenLatestSession(session.sessionId) })
|
||||
}
|
||||
}
|
||||
latestMaximum?.let { maximum ->
|
||||
val weight = "%.2f".format(locale, maximum.maxWeightKg).trimEnd('0').trimEnd(',', '.')
|
||||
TrainlogInfo(strings.getString(R.string.latest_max_value, maximum.exerciseName, weight, formatDate(maximum.startedAt)), colors.warning)
|
||||
}
|
||||
BodyZoneHomeSection(bodyZoneOverview, onOpenExercises)
|
||||
TrainlogFrame(strings.getString(R.string.quick_access), active = false) {
|
||||
Row(Modifier.fillMaxWidth().height(IntrinsicSize.Min).testTag("home-quick-actions-row"),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
|
@ -119,7 +101,6 @@ fun HomeScreen(
|
|||
internal fun BodyZoneHomeSection(
|
||||
overview: BodyZoneHomeOverview,
|
||||
onOpenExercises: (String) -> Unit,
|
||||
onOpenStatistics: () -> Unit,
|
||||
) {
|
||||
val colors = LocalTrainlogColors.current
|
||||
val strings = localizedContext()
|
||||
|
|
@ -159,7 +140,9 @@ internal fun BodyZoneHomeSection(
|
|||
}
|
||||
selected?.let { zone ->
|
||||
Column(
|
||||
Modifier.fillMaxWidth().padding(top = 8.dp).background(colors.surface, RoundedCornerShape(8.dp)).padding(12.dp),
|
||||
Modifier.fillMaxWidth().padding(top = 8.dp)
|
||||
.background(colors.surface, androidx.compose.material3.MaterialTheme.shapes.medium)
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
BasicText(localizedBodyZoneName(strings, zone.zoneId, zone.displayName), style = TrainlogTypography.section.copy(color = colors.text))
|
||||
|
|
@ -179,7 +162,6 @@ internal fun BodyZoneHomeSection(
|
|||
)
|
||||
if (zone.availableExerciseCount == 0) TrainlogInfo(BodyZoneHomeState.UNSUPPORTED.label, colors.muted)
|
||||
TrainlogAction(strings.getString(R.string.view_exercises), "", { onOpenExercises(zone.zoneId) })
|
||||
TrainlogAction(strings.getString(R.string.view_statistics), "", onOpenStatistics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,17 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.labfytools.trainlog.ui.theme.LocalTrainlogColors
|
||||
import com.labfytools.trainlog.R
|
||||
import com.labfytools.trainlog.data.StartProgramSessionResult
|
||||
import com.labfytools.trainlog.data.TrainlogRepository
|
||||
|
|
@ -18,13 +29,34 @@ import com.labfytools.trainlog.model.ProgramSessionExecutionState
|
|||
fun ProgramsScreen(repository: TrainlogRepository, onOpen: (String) -> Unit) {
|
||||
val strings = localizedContext()
|
||||
val programs = repository.listSyncedPrograms()
|
||||
val colors = LocalTrainlogColors.current
|
||||
TrainlogScreen(strings.getString(R.string.route_programs)) {
|
||||
if (programs.isEmpty()) TrainlogInfo(strings.getString(R.string.programs_none))
|
||||
programs.forEach { program ->
|
||||
TrainlogFrame(program.title, active = program.state == "active") {
|
||||
val detail = remember(program.programId) { repository.getSyncedProgram(program.programId) }
|
||||
val completed = detail?.sessions?.count {
|
||||
it.executionState == ProgramSessionExecutionState.COMPLETED
|
||||
} ?: 0
|
||||
Column(
|
||||
Modifier.fillMaxWidth().padding(bottom = 10.dp)
|
||||
.background(colors.surface, MaterialTheme.shapes.medium).padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
program.title,
|
||||
modifier = Modifier.weight(1f).padding(end = 8.dp),
|
||||
color = colors.text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
ProgramStateBadge(
|
||||
if (program.state == "active") strings.getString(R.string.program_active)
|
||||
else strings.getString(R.string.program_archived),
|
||||
program.state == "active",
|
||||
)
|
||||
}
|
||||
TrainlogInfo(
|
||||
listOfNotNull(
|
||||
strings.getString(if (program.state == "active") R.string.program_active else R.string.program_archived),
|
||||
program.startDate?.let { strings.getString(R.string.program_start_date, formatDate(it)) },
|
||||
program.endDate?.let { strings.getString(R.string.program_end_date, formatDate(it)) },
|
||||
strings.resources.getQuantityString(
|
||||
|
|
@ -34,6 +66,7 @@ fun ProgramsScreen(repository: TrainlogRepository, onOpen: (String) -> Unit) {
|
|||
),
|
||||
).joinToString(" · "),
|
||||
)
|
||||
TrainlogInfo("$completed / ${program.sessionCount} · ${strings.getString(R.string.program_session_completed)}", if (completed > 0) colors.success else colors.muted)
|
||||
TrainlogButton(
|
||||
strings.getString(R.string.program_open),
|
||||
{ onOpen(program.programId) },
|
||||
|
|
@ -84,22 +117,41 @@ fun ProgramDetailScreen(
|
|||
program.startDate?.let { TrainlogInfo(strings.getString(R.string.program_start_date, formatDate(it))) }
|
||||
program.endDate?.let { TrainlogInfo(strings.getString(R.string.program_end_date, formatDate(it))) }
|
||||
program.note?.let { TrainlogInfo(it) }
|
||||
val completed = program.sessions.count { it.executionState == ProgramSessionExecutionState.COMPLETED }
|
||||
TrainlogInfo("$completed / ${program.sessions.size} · ${strings.getString(R.string.program_session_completed)}", if (completed > 0) LocalTrainlogColors.current.success else LocalTrainlogColors.current.muted)
|
||||
}
|
||||
message?.let { TrainlogInfo(it) }
|
||||
program.sessions.forEachIndexed { index, session ->
|
||||
var expanded by remember(session.programSessionId) { mutableStateOf(false) }
|
||||
val completed = session.executionState == ProgramSessionExecutionState.COMPLETED
|
||||
TrainlogFrame("${index + 1}. ${session.title}", active = completed) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth().padding(bottom = 10.dp)
|
||||
.background(
|
||||
if (completed) LocalTrainlogColors.current.success.copy(alpha = .12f)
|
||||
else LocalTrainlogColors.current.surface,
|
||||
MaterialTheme.shapes.medium,
|
||||
).padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
val stateLabel = when (session.executionState) {
|
||||
ProgramSessionExecutionState.TODO -> R.string.program_session_todo
|
||||
ProgramSessionExecutionState.IN_PROGRESS -> R.string.program_session_in_progress
|
||||
ProgramSessionExecutionState.COMPLETED -> R.string.program_session_completed
|
||||
}
|
||||
TrainlogInfo(strings.getString(stateLabel))
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
"${index + 1}. ${session.title}",
|
||||
modifier = Modifier.weight(1f).padding(end = 8.dp),
|
||||
color = LocalTrainlogColors.current.text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
ProgramStateBadge(strings.getString(stateLabel), completed)
|
||||
}
|
||||
TrainlogInfo(
|
||||
listOfNotNull(
|
||||
strings.getString(if (session.sessionType == "max_test") R.string.session_max_test else R.string.session_training),
|
||||
session.plannedFor?.let { strings.getString(R.string.planned_for, formatDate(it)) },
|
||||
strings.resources.getQuantityString(R.plurals.exercise_count, session.occurrences.size, session.occurrences.size),
|
||||
).joinToString(" · "),
|
||||
)
|
||||
session.note?.let { TrainlogInfo(it) }
|
||||
|
|
@ -141,3 +193,18 @@ fun ProgramDetailScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Presentation-only state badge; execution truth remains repository-owned. */
|
||||
@Composable
|
||||
private fun ProgramStateBadge(label: String, positive: Boolean) {
|
||||
val colors = LocalTrainlogColors.current
|
||||
Text(
|
||||
label,
|
||||
modifier = Modifier.background(
|
||||
(if (positive) colors.success else colors.surfaceAlt).copy(alpha = .22f),
|
||||
MaterialTheme.shapes.small,
|
||||
).padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
color = if (positive) colors.success else colors.muted,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -188,13 +190,29 @@ fun LatestMaximaScreen(repository: TrainlogRepository) {
|
|||
val locale = presentationLocale()
|
||||
val strings = localizedContext()
|
||||
TrainlogScreen(strings.getString(R.string.route_latest_maxima)) {
|
||||
TrainlogFrame(strings.getString(R.string.max_capacities), active = maxima.isNotEmpty()) {
|
||||
if (maxima.isEmpty()) TrainlogInfo(strings.getString(R.string.max_none))
|
||||
maxima.forEach { max ->
|
||||
val weight = "%.2f".format(locale, max.maxWeightKg).trimEnd('0').trimEnd(',', '.')
|
||||
TrainlogInfo("${max.exerciseName} · $weight kg", colors.warning)
|
||||
TrainlogInfo("${formatDate(max.startedAt)} · " + strings.getString(R.string.equipment_value,
|
||||
max.equipmentDisplayName ?: strings.getString(R.string.value_none)), colors.muted)
|
||||
if (maxima.isEmpty()) TrainlogInfo(strings.getString(R.string.max_none))
|
||||
maxima.forEach { max ->
|
||||
val weight = "%.2f".format(locale, max.maxWeightKg).trimEnd('0').trimEnd(',', '.')
|
||||
androidx.compose.foundation.layout.Column(
|
||||
Modifier.fillMaxWidth().padding(bottom = 8.dp)
|
||||
.background(colors.surface, androidx.compose.material3.MaterialTheme.shapes.medium)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
max.exerciseName,
|
||||
color = colors.text,
|
||||
style = com.labfytools.trainlog.ui.theme.TrainlogTypography.section,
|
||||
)
|
||||
androidx.compose.foundation.layout.Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
androidx.compose.material3.Text("$weight kg", color = colors.warning)
|
||||
androidx.compose.material3.Text(formatDate(max.startedAt), color = colors.muted)
|
||||
}
|
||||
max.equipmentDisplayName?.let {
|
||||
TrainlogInfo(strings.getString(R.string.equipment_value, it), colors.muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
|
|
@ -196,69 +197,34 @@ fun SessionScreen(
|
|||
TrainlogFrame(
|
||||
title = strings.getString(R.string.session_type)
|
||||
) {
|
||||
TrainlogAction(
|
||||
label =
|
||||
if (
|
||||
currentDraft.sessionType ==
|
||||
SessionType.TRAINING
|
||||
) {
|
||||
strings.getString(R.string.training_selected)
|
||||
} else {
|
||||
strings.getString(R.string.training_unselected)
|
||||
},
|
||||
description =
|
||||
strings.getString(R.string.normal_session_description),
|
||||
accent =
|
||||
if (
|
||||
currentDraft.sessionType ==
|
||||
SessionType.TRAINING
|
||||
) {
|
||||
colors.success
|
||||
} else {
|
||||
colors.muted
|
||||
},
|
||||
onClick = {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
val training = currentDraft.sessionType == SessionType.TRAINING
|
||||
TrainlogButton(
|
||||
label = strings.getString(R.string.training),
|
||||
modifier = Modifier.weight(1f),
|
||||
style = if (training) TrainlogButtonStyle.SUCCESS else TrainlogButtonStyle.SECONDARY,
|
||||
maxLines = 2,
|
||||
onClick = {
|
||||
persistDraft(
|
||||
currentDraft.copy(
|
||||
sessionType =
|
||||
SessionType.TRAINING
|
||||
),
|
||||
currentDraft.copy(sessionType = SessionType.TRAINING),
|
||||
null,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
TrainlogAction(
|
||||
label =
|
||||
if (
|
||||
currentDraft.sessionType ==
|
||||
SessionType.MAX_TEST
|
||||
) {
|
||||
strings.getString(R.string.max_test_selected)
|
||||
} else {
|
||||
strings.getString(R.string.max_test_unselected)
|
||||
},
|
||||
description =
|
||||
strings.getString(R.string.max_session_description),
|
||||
accent =
|
||||
if (
|
||||
currentDraft.sessionType ==
|
||||
SessionType.MAX_TEST
|
||||
) {
|
||||
colors.warning
|
||||
} else {
|
||||
colors.muted
|
||||
},
|
||||
onClick = {
|
||||
)
|
||||
val maxTest = currentDraft.sessionType == SessionType.MAX_TEST
|
||||
TrainlogButton(
|
||||
label = strings.getString(R.string.session_max_test),
|
||||
modifier = Modifier.weight(1f),
|
||||
style = if (maxTest) TrainlogButtonStyle.SUCCESS else TrainlogButtonStyle.SECONDARY,
|
||||
maxLines = 2,
|
||||
onClick = {
|
||||
persistDraft(
|
||||
currentDraft.copy(
|
||||
sessionType =
|
||||
SessionType.MAX_TEST
|
||||
),
|
||||
currentDraft.copy(sessionType = SessionType.MAX_TEST),
|
||||
null,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ExercisePicker(
|
||||
|
|
@ -606,7 +572,7 @@ fun SessionScreen(
|
|||
colors.success
|
||||
} else {
|
||||
colors.error
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1151,54 +1117,78 @@ private fun SessionExerciseForm(
|
|||
strings.getString(R.string.field_weight)
|
||||
}
|
||||
setRows.forEachIndexed { index, row ->
|
||||
TrainlogInfo(
|
||||
text = strings.getString(R.string.set_number, index + 1),
|
||||
color = colors.accent,
|
||||
)
|
||||
SessionNumberField(
|
||||
label = strings.getString(R.string.set_reps_field, index + 1),
|
||||
value = row.repsText,
|
||||
testTag = "session-set-$index-reps",
|
||||
onValueChange = { value ->
|
||||
val updated = setRows.replaceAt(index, row.copy(repsText = value))
|
||||
setRows = updated
|
||||
repsText = encodeRawReps(updated)
|
||||
weightText = encodeRawWeights(updated)
|
||||
error = null
|
||||
onFormChanged(
|
||||
currentForm(
|
||||
exercise, setCountText, repsText, durationText,
|
||||
speedText, distanceText, selectedEquipmentId, weightText,
|
||||
)
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp)
|
||||
.background(colors.surface, MaterialTheme.shapes.medium)
|
||||
.padding(10.dp),
|
||||
) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
BasicText(
|
||||
text = strings.getString(R.string.set_number, index + 1),
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
style = TrainlogTypography.small.copy(
|
||||
color = colors.accent,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
SessionNumberField(
|
||||
label = strings.getString(R.string.set_load_field, index + 1, loadLabel),
|
||||
value = row.weightText,
|
||||
decimal = true,
|
||||
testTag = "session-set-$index-weight",
|
||||
onValueChange = { value ->
|
||||
val updated = setRows.replaceAt(index, row.copy(weightText = value))
|
||||
setRows = updated
|
||||
repsText = encodeRawReps(updated)
|
||||
weightText = encodeRawWeights(updated)
|
||||
error = null
|
||||
onFormChanged(
|
||||
currentForm(
|
||||
exercise, setCountText, repsText, durationText,
|
||||
speedText, distanceText, selectedEquipmentId, weightText,
|
||||
)
|
||||
TrainlogIconAction(
|
||||
icon = TrainlogIcons.DeleteOutline,
|
||||
contentDescription = localizedContext().getString(R.string.a11y_delete_set),
|
||||
modifier = Modifier.testTag("delete-set-${index + 1}"),
|
||||
accent = colors.error,
|
||||
onClick = { pendingSetRemoval = index },
|
||||
)
|
||||
},
|
||||
)
|
||||
TrainlogDeleteButton(
|
||||
contentDescription = localizedContext().getString(com.labfytools.trainlog.R.string.a11y_delete_set),
|
||||
modifier = Modifier.testTag("delete-set-${index + 1}"),
|
||||
onClick = {
|
||||
pendingSetRemoval = index
|
||||
},
|
||||
)
|
||||
}
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SessionNumberField(
|
||||
label = strings.getString(R.string.field_reps),
|
||||
value = row.repsText,
|
||||
modifier = Modifier.weight(1f),
|
||||
testTag = "session-set-$index-reps",
|
||||
onValueChange = { value ->
|
||||
val updated = setRows.replaceAt(index, row.copy(repsText = value))
|
||||
setRows = updated
|
||||
repsText = encodeRawReps(updated)
|
||||
weightText = encodeRawWeights(updated)
|
||||
error = null
|
||||
onFormChanged(
|
||||
currentForm(
|
||||
exercise, setCountText, repsText, durationText,
|
||||
speedText, distanceText, selectedEquipmentId, weightText,
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
SessionNumberField(
|
||||
label = loadLabel,
|
||||
value = row.weightText,
|
||||
modifier = Modifier.weight(1f),
|
||||
decimal = true,
|
||||
testTag = "session-set-$index-weight",
|
||||
onValueChange = { value ->
|
||||
val updated = setRows.replaceAt(index, row.copy(weightText = value))
|
||||
setRows = updated
|
||||
repsText = encodeRawReps(updated)
|
||||
weightText = encodeRawWeights(updated)
|
||||
error = null
|
||||
onFormChanged(
|
||||
currentForm(
|
||||
exercise, setCountText, repsText, durationText,
|
||||
speedText, distanceText, selectedEquipmentId, weightText,
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
TrainlogAction(
|
||||
label = strings.getString(R.string.add_set_plain),
|
||||
|
|
@ -1608,6 +1598,7 @@ private fun SessionNumberField(
|
|||
label: String,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
testTag: String? = null,
|
||||
decimal: Boolean = false,
|
||||
) {
|
||||
|
|
@ -1623,6 +1614,7 @@ private fun SessionNumberField(
|
|||
} else {
|
||||
KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ fun TrainlogApp(repository: TrainlogRepository, exporter: SyncExporter, inbox: S
|
|||
when (val route = navigation.route) {
|
||||
AppRoute.Home -> HomeScreen(
|
||||
activeDraft, draftMessage ?: (draftLoad as? ActiveDraftLoadResult.Error)?.let { localizedRepositoryMessage(localized, it.message) } ?: (draftLoad as? ActiveDraftLoadResult.Loaded)?.warning?.let { localizedRepositoryMessage(localized, it) },
|
||||
repository.listSessions().firstOrNull(), repository.listLatestExerciseMaxima().firstOrNull(),
|
||||
repository.getBodyZoneHomeOverview(),
|
||||
::openManualSession,
|
||||
{ zoneId ->
|
||||
|
|
@ -102,8 +101,7 @@ fun TrainlogApp(repository: TrainlogRepository, exporter: SyncExporter, inbox: S
|
|||
exerciseState.searchQuery.value = ""
|
||||
open(AppRoute.Exercises)
|
||||
},
|
||||
{ open(AppRoute.Statistics) },
|
||||
{ open(AppRoute.BodyMeasurements) }, { open(AppRoute.SessionDetail(it)) }, { open(AppRoute.Sync) },
|
||||
{ open(AppRoute.BodyMeasurements) }, { open(AppRoute.Sync) },
|
||||
)
|
||||
AppRoute.Sessions -> SessionsHub(activeDraft, pendingAiDraftCount, { open(AppRoute.SessionEditor) }, ::openManualSession,
|
||||
{ open(AppRoute.AiSessionDrafts) }, { open(AppRoute.CompletedSessions) }, { open(AppRoute.Programs) })
|
||||
|
|
@ -141,7 +139,6 @@ fun TrainlogApp(repository: TrainlogRepository, exporter: SyncExporter, inbox: S
|
|||
AppRoute.Equipment -> EquipmentScreen(repository, equipmentState, { open(AppRoute.EquipmentCreate(AppRoute.Equipment)) }, { open(AppRoute.EquipmentDetail(it)) })
|
||||
is AppRoute.EquipmentDetail -> EquipmentDetailScreen(repository, route.equipmentId) { open(AppRoute.ExerciseDetail(it)) }
|
||||
is AppRoute.EquipmentCreate -> EquipmentCreateScreen(repository, equipmentState) { exportSnapshot(); catalogRevision++; back() }
|
||||
AppRoute.Statistics -> StatisticsDashboard(repository)
|
||||
AppRoute.BodyMeasurements -> BodyScreen(repository, bodyState, { exportSnapshot() }, { back() })
|
||||
AppRoute.LatestMaxima -> LatestMaximaScreen(repository)
|
||||
AppRoute.Sync -> SyncScreen(repository, inbox, exporter, requestOutbox, { exportSnapshot(); catalogRevision++ }, { back() })
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
|
|
@ -37,6 +38,7 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -164,7 +166,7 @@ fun TrainlogAction(
|
|||
.padding(vertical = 3.dp)
|
||||
.heightIn(min = 48.dp)
|
||||
.height(IntrinsicSize.Min)
|
||||
.background(Color.Transparent)
|
||||
.background(colors.surface, MaterialTheme.shapes.medium)
|
||||
.clickable(onClick = onClick)
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -199,6 +201,8 @@ fun TrainlogAction(
|
|||
}
|
||||
}
|
||||
|
||||
enum class TrainlogButtonStyle { PRIMARY, SECONDARY, SUCCESS, DESTRUCTIVE, GHOST }
|
||||
|
||||
/** Compact pictogram action with a mandatory accessible name. */
|
||||
@Composable
|
||||
fun TrainlogIconAction(
|
||||
|
|
@ -230,6 +234,7 @@ fun TrainlogDeleteButton(
|
|||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier.fillMaxWidth().heightIn(min = 52.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = colors.error),
|
||||
) {
|
||||
Icon(TrainlogIcons.DeleteOutline, contentDescription = contentDescription)
|
||||
|
|
@ -240,9 +245,7 @@ fun TrainlogDeleteButton(
|
|||
fun TrainlogPrimaryAction(label: String, description: String, onClick: () -> Unit) {
|
||||
val colors = LocalTrainlogColors.current
|
||||
Column(Modifier.fillMaxWidth().padding(bottom = 12.dp)) {
|
||||
Button(onClick = onClick, modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp)) {
|
||||
Text(label)
|
||||
}
|
||||
TrainlogButton(label, onClick, Modifier.fillMaxWidth())
|
||||
if (description.isNotBlank()) {
|
||||
BasicText(
|
||||
description,
|
||||
|
|
@ -270,7 +273,7 @@ fun TrainlogActionTile(
|
|||
Column(
|
||||
modifier
|
||||
.heightIn(min = 112.dp)
|
||||
.background(colors.surface, RoundedCornerShape(12.dp))
|
||||
.background(colors.surface, MaterialTheme.shapes.medium)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
|
|
@ -291,13 +294,33 @@ fun TrainlogButton(
|
|||
containerColor: Color? = null,
|
||||
maxLines: Int = 1,
|
||||
enabled: Boolean = true,
|
||||
style: TrainlogButtonStyle = TrainlogButtonStyle.PRIMARY,
|
||||
) {
|
||||
val colors = LocalTrainlogColors.current
|
||||
val resolvedContainer = containerColor ?: when (style) {
|
||||
TrainlogButtonStyle.PRIMARY -> colors.accent
|
||||
TrainlogButtonStyle.SECONDARY -> colors.surfaceAlt
|
||||
TrainlogButtonStyle.SUCCESS -> colors.success
|
||||
TrainlogButtonStyle.DESTRUCTIVE -> colors.error
|
||||
TrainlogButtonStyle.GHOST -> Color.Transparent
|
||||
}
|
||||
val resolvedContent = when (style) {
|
||||
TrainlogButtonStyle.PRIMARY, TrainlogButtonStyle.SUCCESS,
|
||||
TrainlogButtonStyle.DESTRUCTIVE -> colors.crust
|
||||
TrainlogButtonStyle.SECONDARY, TrainlogButtonStyle.GHOST -> colors.text
|
||||
}
|
||||
Button(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = modifier.heightIn(min = 52.dp),
|
||||
colors = containerColor?.let { ButtonDefaults.buttonColors(containerColor = it) }
|
||||
?: ButtonDefaults.buttonColors(),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
border = if (style == TrainlogButtonStyle.GHOST) BorderStroke(1.dp, colors.surfaceAlt) else null,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = resolvedContainer,
|
||||
contentColor = resolvedContent,
|
||||
disabledContainerColor = colors.surfaceAlt,
|
||||
disabledContentColor = colors.muted,
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 8.dp),
|
||||
) {
|
||||
icon?.let {
|
||||
|
|
@ -334,6 +357,7 @@ fun TrainlogInputField(
|
|||
label: String,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
keyboardOptions: KeyboardOptions =
|
||||
KeyboardOptions.Default,
|
||||
testTag: String? = null,
|
||||
|
|
@ -350,8 +374,7 @@ fun TrainlogInputField(
|
|||
}
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.padding(bottom = 10.dp)
|
||||
modifier = modifier.padding(bottom = 10.dp)
|
||||
) {
|
||||
BasicText(
|
||||
text = label.uppercase(),
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Shapes
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
data class TrainlogColors(
|
||||
val background: Color,
|
||||
|
|
@ -110,6 +113,19 @@ fun TrainlogTheme(
|
|||
error = TrainlogDarkColors.error,
|
||||
)
|
||||
CompositionLocalProvider(LocalTrainlogColors provides TrainlogDarkColors) {
|
||||
MaterialTheme(colorScheme = scheme, content = content)
|
||||
/* INVARIANT: all Material controls share the same restrained corner
|
||||
* language. Screen code may change size, but must not reintroduce the
|
||||
* former pill-shaped action hierarchy. */
|
||||
MaterialTheme(
|
||||
colorScheme = scheme,
|
||||
shapes = Shapes(
|
||||
extraSmall = RoundedCornerShape(6.dp),
|
||||
small = RoundedCornerShape(8.dp),
|
||||
medium = RoundedCornerShape(10.dp),
|
||||
large = RoundedCornerShape(12.dp),
|
||||
extraLarge = RoundedCornerShape(14.dp),
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,10 @@ class AndroidUiRedesignTest {
|
|||
HomeScreen(
|
||||
activeDraft = null,
|
||||
draftError = null,
|
||||
latestSession = null,
|
||||
latestMaximum = null,
|
||||
bodyZoneOverview = BodyZoneHomeOverview(emptyList(), emptyList(), false, false),
|
||||
onSession = {},
|
||||
onOpenExercises = {},
|
||||
onOpenStatistics = {},
|
||||
onBody = { bodyCalls++ },
|
||||
onOpenLatestSession = {},
|
||||
onSync = { syncCalls++ },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,13 +36,14 @@ class AppNavigationStateTest {
|
|||
|
||||
@After fun tearDown() { context.deleteDatabase(databaseName) }
|
||||
|
||||
@Test fun sevenSectionsHaveTypedRootsAndSelectionComesFromRoute() {
|
||||
assertEquals(7, AppSection.entries.size)
|
||||
@Test fun captureSectionsHaveTypedRootsWithoutMobileStatistics() {
|
||||
assertEquals(6, AppSection.entries.size)
|
||||
assertFalse(AppSection.entries.any { it.name == "STATISTICS" })
|
||||
AppSection.entries.forEach { assertEquals(it, it.rootRoute().section) }
|
||||
assertEquals(AppSection.SESSIONS, AppRoute.SessionDetail("se_1").section)
|
||||
assertEquals(AppSection.EXERCISES, AppRoute.ExerciseDetail("ex_1").section)
|
||||
assertEquals(AppSection.EQUIPMENT, AppRoute.EquipmentDetail("eq_1").section)
|
||||
assertEquals(AppSection.STATISTICS, AppRoute.LatestMaxima.section)
|
||||
assertEquals(AppSection.EXERCISES, AppRoute.LatestMaxima.section)
|
||||
}
|
||||
|
||||
@Test fun catalogueDetailsAndCreatorsReturnToTheirDeclaredCaller() {
|
||||
|
|
|
|||
|
|
@ -110,12 +110,10 @@ class BodyZoneHomeSemanticsTest {
|
|||
|
||||
@Test fun practicalWidthMapSelectionFeedsExistingNavigationActions() {
|
||||
var openedZone: String? = null
|
||||
var statisticsOpened = false
|
||||
setSection(
|
||||
widthDp = 380,
|
||||
fontScale = 1f,
|
||||
onOpenExercises = { openedZone = it },
|
||||
onOpenStatistics = { statisticsOpened = true },
|
||||
)
|
||||
|
||||
assertEquals("core", bodyZoneAtPoint("front", 180f, 236f, Offset(90f, 113.28f), allZoneIds.toSet()))
|
||||
|
|
@ -126,16 +124,12 @@ class BodyZoneHomeSemanticsTest {
|
|||
compose.onNodeWithText("Voir les exercices").assertHasClickAction()
|
||||
.performSemanticsAction(SemanticsActions.OnClick)
|
||||
assertEquals("core", openedZone)
|
||||
compose.onNodeWithText("Voir les statistiques").assertHasClickAction()
|
||||
.performSemanticsAction(SemanticsActions.OnClick)
|
||||
assertTrue(statisticsOpened)
|
||||
}
|
||||
|
||||
private fun setSection(
|
||||
widthDp: Int,
|
||||
fontScale: Float,
|
||||
onOpenExercises: (String) -> Unit = {},
|
||||
onOpenStatistics: () -> Unit = {},
|
||||
) {
|
||||
val zones = allZoneIds.map { id ->
|
||||
status(id, if (id == "chest") BodyZoneHomeState.PRIORITIZE else BodyZoneHomeState.RECENT_WORK)
|
||||
|
|
@ -147,7 +141,6 @@ class BodyZoneHomeSemanticsTest {
|
|||
BodyZoneHomeSection(
|
||||
BodyZoneHomeOverview(zones, listOf(zones.first()), true, false),
|
||||
onOpenExercises,
|
||||
onOpenStatistics,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue