diff --git a/android/app/src/main/java/com/labfytools/trainlog/ui/AndroidAppShell.kt b/android/app/src/main/java/com/labfytools/trainlog/ui/AndroidAppShell.kt index 0c3783e..3a135bb 100644 --- a/android/app/src/main/java/com/labfytools/trainlog/ui/AndroidAppShell.kt +++ b/android/app/src/main/java/com/labfytools/trainlog/ui/AndroidAppShell.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -32,6 +33,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.compose.foundation.shape.RoundedCornerShape import com.labfytools.trainlog.R import com.labfytools.trainlog.ui.theme.LocalTrainlogColors import kotlinx.coroutines.launch @@ -105,7 +107,16 @@ fun AndroidAppShell( onOpenSection(section) scope.launch { drawerState.close() } }, - modifier = Modifier.padding(horizontal = 12.dp), + modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp), + shape = RoundedCornerShape(10.dp), + colors = NavigationDrawerItemDefaults.colors( + selectedContainerColor = colors.surfaceAlt, + selectedIconColor = colors.accent, + selectedTextColor = colors.text, + unselectedContainerColor = colors.mantle, + unselectedIconColor = colors.muted, + unselectedTextColor = colors.muted, + ), ) } } diff --git a/android/app/src/main/java/com/labfytools/trainlog/ui/BodyScreen.kt b/android/app/src/main/java/com/labfytools/trainlog/ui/BodyScreen.kt index c3d6b89..32e790a 100644 --- a/android/app/src/main/java/com/labfytools/trainlog/ui/BodyScreen.kt +++ b/android/app/src/main/java/com/labfytools/trainlog/ui/BodyScreen.kt @@ -8,6 +8,12 @@ package com.labfytools.trainlog.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.ui.unit.dp import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -89,148 +95,74 @@ fun BodyScreen( TrainlogFrame( title = strings.getString(R.string.general) ) { - BodyMetricField( - label = strings.getString(R.string.weight), - unit = "kg", - value = bodyWeight, - onValueChange = { - bodyWeight = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.neck), - unit = "cm", - value = neck, - onValueChange = { - neck = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.shoulders), - unit = "cm", - value = shoulders, - onValueChange = { - shoulders = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.chest), - unit = "cm", - value = chest, - onValueChange = { - chest = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.waist), - unit = "cm", - value = waist, - onValueChange = { - waist = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.hips), - unit = "cm", - value = hips, - onValueChange = { - hips = it - message = null - }, + BodyMetricGrid( + listOf( + BodyMetricItem(strings.getString(R.string.weight), "kg", bodyWeight) { + bodyWeight = it + message = null + }, + BodyMetricItem(strings.getString(R.string.neck), "cm", neck) { + neck = it + message = null + }, + BodyMetricItem(strings.getString(R.string.shoulders), "cm", shoulders) { + shoulders = it + message = null + }, + BodyMetricItem(strings.getString(R.string.chest), "cm", chest) { + chest = it + message = null + }, + BodyMetricItem(strings.getString(R.string.waist), "cm", waist) { + waist = it + message = null + }, + BodyMetricItem(strings.getString(R.string.hips), "cm", hips) { + hips = it + message = null + }, + ), ) } TrainlogFrame( title = strings.getString(R.string.limbs) ) { - BodyMetricField( - label = strings.getString(R.string.left_arm), - unit = "cm", - value = leftArm, - onValueChange = { - leftArm = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.right_arm), - unit = "cm", - value = rightArm, - onValueChange = { - rightArm = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.left_forearm), - unit = "cm", - value = leftForearm, - onValueChange = { - leftForearm = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.right_forearm), - unit = "cm", - value = rightForearm, - onValueChange = { - rightForearm = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.left_thigh), - unit = "cm", - value = leftThigh, - onValueChange = { - leftThigh = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.right_thigh), - unit = "cm", - value = rightThigh, - onValueChange = { - rightThigh = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.left_calf), - unit = "cm", - value = leftCalf, - onValueChange = { - leftCalf = it - message = null - }, - ) - - BodyMetricField( - label = strings.getString(R.string.right_calf), - unit = "cm", - value = rightCalf, - onValueChange = { - rightCalf = it - message = null - }, + BodyMetricGrid( + listOf( + BodyMetricItem(strings.getString(R.string.left_arm), "cm", leftArm) { + leftArm = it + message = null + }, + BodyMetricItem(strings.getString(R.string.right_arm), "cm", rightArm) { + rightArm = it + message = null + }, + BodyMetricItem(strings.getString(R.string.left_forearm), "cm", leftForearm) { + leftForearm = it + message = null + }, + BodyMetricItem(strings.getString(R.string.right_forearm), "cm", rightForearm) { + rightForearm = it + message = null + }, + BodyMetricItem(strings.getString(R.string.left_thigh), "cm", leftThigh) { + leftThigh = it + message = null + }, + BodyMetricItem(strings.getString(R.string.right_thigh), "cm", rightThigh) { + rightThigh = it + message = null + }, + BodyMetricItem(strings.getString(R.string.left_calf), "cm", leftCalf) { + leftCalf = it + message = null + }, + BodyMetricItem(strings.getString(R.string.right_calf), "cm", rightCalf) { + rightCalf = it + message = null + }, + ), ) } @@ -426,12 +358,14 @@ private fun BodyMetricField( unit: String, value: String, onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, ) { TrainlogInputField( - label = "$label ($unit)", + label = label, value = value, onValueChange = onValueChange, + modifier = modifier, keyboardOptions = KeyboardOptions( keyboardType = @@ -439,9 +373,42 @@ private fun BodyMetricField( imeAction = ImeAction.Next, ), + suffix = unit, + compact = true, ) } +private data class BodyMetricItem( + val label: String, + val unit: String, + val value: String, + val onValueChange: (String) -> Unit, +) + +/** Two columns retain scanability; very narrow accessibility layouts stack. */ +@Composable +private fun ColumnScope.BodyMetricGrid(items: List) { + BoxWithConstraints(Modifier.fillMaxWidth()) { + val columns = if (maxWidth < 330.dp) 1 else 2 + Column(verticalArrangement = Arrangement.spacedBy(0.dp)) { + items.chunked(columns).forEach { rowItems -> + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + rowItems.forEach { item -> + BodyMetricField( + item.label, + item.unit, + item.value, + item.onValueChange, + Modifier.weight(1f), + ) + } + repeat(columns - rowItems.size) { androidx.compose.foundation.layout.Spacer(Modifier.weight(1f)) } + } + } + } + } +} + private sealed interface MetricParse { data object Empty : MetricParse diff --git a/android/app/src/main/java/com/labfytools/trainlog/ui/HistoryScreen.kt b/android/app/src/main/java/com/labfytools/trainlog/ui/HistoryScreen.kt index a2d10de..705362c 100644 --- a/android/app/src/main/java/com/labfytools/trainlog/ui/HistoryScreen.kt +++ b/android/app/src/main/java/com/labfytools/trainlog/ui/HistoryScreen.kt @@ -12,6 +12,7 @@ import com.labfytools.trainlog.model.SessionType import com.labfytools.trainlog.ui.theme.LocalTrainlogColors import com.labfytools.trainlog.R import java.time.OffsetDateTime +import java.time.LocalDate import java.time.format.DateTimeFormatter @Composable @@ -133,7 +134,8 @@ internal fun formatStartedAt( internal fun formatDate(value: String): String { val locale = presentationLocale() val pattern = uiString(R.string.date_pattern) - return runCatching { - OffsetDateTime.parse(value).format(DateTimeFormatter.ofPattern(pattern, locale)) - }.getOrElse { value.take(10) } + val formatter = DateTimeFormatter.ofPattern(pattern, locale) + return runCatching { LocalDate.parse(value.take(10)).format(formatter) } + .recoverCatching { OffsetDateTime.parse(value).format(formatter) } + .getOrElse { value.take(10) } } diff --git a/android/app/src/main/java/com/labfytools/trainlog/ui/SectionScreens.kt b/android/app/src/main/java/com/labfytools/trainlog/ui/SectionScreens.kt index 813b27c..227deb1 100644 --- a/android/app/src/main/java/com/labfytools/trainlog/ui/SectionScreens.kt +++ b/android/app/src/main/java/com/labfytools/trainlog/ui/SectionScreens.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxHeight @@ -21,7 +22,9 @@ 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.foundation.layout.widthIn import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.compose.ui.platform.LocalContext @@ -38,6 +41,8 @@ import com.labfytools.trainlog.data.AndroidBackupService import com.labfytools.trainlog.data.AndroidBackupResult import com.labfytools.trainlog.data.directStoragePermissionIntent import com.labfytools.trainlog.model.ActiveSessionDraft +import com.labfytools.trainlog.model.SessionExerciseDraft +import com.labfytools.trainlog.ui.theme.TrainlogTypography import com.labfytools.trainlog.ui.theme.LocalTrainlogColors import com.labfytools.trainlog.R @@ -95,21 +100,14 @@ fun AiSessionDraftsScreen( message?.let { TrainlogInfo(it) } if (drafts.isEmpty() && preparations.isEmpty()) TrainlogInfo(strings.getString(R.string.draft_none)) preparations.forEach { preparation -> - TrainlogFrame(preparation.title, active = true) { - TrainlogInfo(listOfNotNull( - strings.getString(R.string.manual_preparation), - preparation.plannedFor?.let { strings.getString(R.string.planned_for, formatDate(it)) }, - strings.resources.getQuantityString(R.plurals.exercise_count, preparation.entries.size, preparation.entries.size), - ).joinToString(" · ")) - preparation.notes?.let { TrainlogInfo(it) } - preparation.entries.forEach { entry -> - val plan = checkNotNull(entry.plan) - val metric = plan.reps?.let { strings.resources.getQuantityString(R.plurals.repetition_count, it, it) } - ?: plan.durationSeconds?.let { strings.resources.getQuantityString(R.plurals.seconds_count, it, it) }.orEmpty() - val weight = plan.weightKg?.let { " · $it kg" }.orEmpty() - TrainlogInfo(strings.getString(R.string.plan_draft_summary, entry.exercise.name, plan.sets, metric, weight, plan.restSeconds)) - } - TrainlogPrimaryAction(strings.getString(R.string.start), strings.getString(R.string.start_preparation_description)) { + DraftSummaryCard( + title = preparation.title, + provenance = strings.getString(R.string.manual_preparation), + plannedFor = preparation.plannedFor, + notes = preparation.notes, + entries = preparation.entries, + startDescription = strings.getString(R.string.start_preparation_description), + onStart = { when (val result = repository.startPreparedSession(preparation.deliveryId)) { StartAiSessionDraftResult.Started -> { onPendingChanged(); onStarted() } StartAiSessionDraftResult.ExistingActiveDraft -> @@ -117,24 +115,18 @@ fun AiSessionDraftsScreen( StartAiSessionDraftResult.NotPending -> { message = strings.getString(R.string.draft_missing); revision++; onPendingChanged() } is StartAiSessionDraftResult.Error -> message = localizedRepositoryMessage(strings, result.message) } - } - } + }, + ) } drafts.forEach { draft -> - TrainlogFrame(draft.title ?: strings.getString(R.string.session_proposal), active = true) { - TrainlogInfo(listOfNotNull( - draft.plannedFor?.let { strings.getString(R.string.planned_for, formatDate(it)) }, - strings.resources.getQuantityString(R.plurals.exercise_count, draft.entries.size, draft.entries.size), - ).joinToString(" · ")) - draft.notes?.let { TrainlogInfo(it) } - draft.entries.forEach { entry -> - val plan = checkNotNull(entry.plan) - val metric = plan.reps?.let { strings.resources.getQuantityString(R.plurals.repetition_count, it, it) } - ?: plan.durationSeconds?.let { strings.resources.getQuantityString(R.plurals.seconds_count, it, it) }.orEmpty() - val weight = plan.weightKg?.let { " · $it kg" }.orEmpty() - TrainlogInfo(strings.getString(R.string.plan_draft_summary, entry.exercise.name, plan.sets, metric, weight, plan.restSeconds)) - } - TrainlogPrimaryAction(strings.getString(R.string.start), strings.getString(R.string.start_proposal_description)) { + DraftSummaryCard( + title = draft.title ?: strings.getString(R.string.session_proposal), + provenance = strings.getString(R.string.session_proposal), + plannedFor = draft.plannedFor, + notes = draft.notes, + entries = draft.entries, + startDescription = strings.getString(R.string.start_proposal_description), + onStart = { when (val result = repository.startAiSessionDraft(draft.draftId)) { StartAiSessionDraftResult.Started -> { onPendingChanged() @@ -149,15 +141,24 @@ fun AiSessionDraftsScreen( } is StartAiSessionDraftResult.Error -> message = localizedRepositoryMessage(strings, result.message) } - } - TrainlogDeleteButton( - contentDescription = localizedContext().getString(R.string.a11y_delete_draft), - modifier = Modifier.testTag("delete-ai-draft-${draft.draftId}"), - onClick = { - pendingDeletion = draft.draftId to (draft.title ?: strings.getString(R.string.session_proposal)) - }, - ) - } + }, + footer = { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TrainlogDeleteButton( + contentDescription = localizedContext().getString(R.string.a11y_delete_draft), + modifier = Modifier.testTag("delete-ai-draft-${draft.draftId}"), + fullWidth = false, + onClick = { + pendingDeletion = draft.draftId to + (draft.title ?: strings.getString(R.string.session_proposal)) + }, + ) + } + }, + ) } } pendingDeletion?.let { (draftId, title) -> @@ -183,6 +184,138 @@ fun AiSessionDraftsScreen( } } +/** Compact presentation of an inert preparation; starting remains caller-owned. */ +@Composable +private fun DraftSummaryCard( + title: String, + provenance: String, + plannedFor: String?, + notes: String?, + entries: List, + startDescription: String, + onStart: () -> Unit, + footer: (@Composable () -> Unit)? = null, +) { + val colors = LocalTrainlogColors.current + val strings = localizedContext() + Column(Modifier.fillMaxWidth().padding(bottom = 12.dp)) { + TrainlogCompactCard { + androidx.compose.material3.Text( + title, + color = colors.text, + style = TrainlogTypography.section, + fontWeight = FontWeight.Bold, + ) + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + TrainlogMetaBadge(provenance, Modifier.weight(1f)) + TrainlogMetaBadge( + strings.resources.getQuantityString( + R.plurals.exercise_count, + entries.size, + entries.size, + ), + ) + } + plannedFor?.let { + androidx.compose.material3.Text( + strings.getString(R.string.planned_for, formatDate(it)), + color = colors.muted, + style = TrainlogTypography.small, + ) + } + notes?.takeIf { it.isNotBlank() }?.let { + androidx.compose.material3.Text( + it, + color = colors.muted, + style = TrainlogTypography.small, + maxLines = 2, + ) + } + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + entries.forEach { DraftExerciseRow(it) } + } + Row( + Modifier.fillMaxWidth().padding(top = 4.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + TrainlogButton( + strings.getString(R.string.start), + onStart, + Modifier.widthIn(min = 128.dp), + style = TrainlogButtonStyle.SUCCESS, + ) + androidx.compose.material3.Text( + startDescription, + modifier = Modifier.weight(1f), + color = colors.muted, + style = TrainlogTypography.small, + maxLines = 2, + ) + } + footer?.invoke() + } + } +} + +@Composable +private fun DraftExerciseRow(entry: SessionExerciseDraft) { + val colors = LocalTrainlogColors.current + val plan = checkNotNull(entry.plan) + Column( + Modifier.fillMaxWidth() + .background(colors.background, androidx.compose.material3.MaterialTheme.shapes.small) + .padding(horizontal = 10.dp, vertical = 8.dp), + ) { + androidx.compose.material3.Text( + entry.exercise.name, + color = colors.text, + style = TrainlogTypography.small, + fontWeight = FontWeight.SemiBold, + ) + androidx.compose.material3.Text( + draftPlanSummary(plan.sets, plan.reps, plan.durationSeconds, plan.weightKg, plan.restSeconds), + color = colors.muted, + style = TrainlogTypography.small, + ) + } +} + +@Composable +private fun draftPlanSummary( + sets: Int, + reps: Int?, + durationSeconds: Int?, + weightKg: Double?, + restSeconds: Int, +): String { + val strings = localizedContext() + val metric = when { + reps != null -> "$sets × $reps" + durationSeconds != null && sets > 0 -> "$sets × ${humanDuration(durationSeconds)}" + durationSeconds != null -> humanDuration(durationSeconds) + else -> "—" + } + return buildList { + add(metric) + weightKg?.let { add("${it.toString().trimEnd('0').trimEnd('.')} kg") } + if (restSeconds > 0) add(strings.getString(R.string.draft_rest_short, restSeconds)) + }.joinToString(" · ") +} + +@Composable +private fun humanDuration(seconds: Int): String { + val strings = localizedContext() + return if (seconds % 60 == 0) { + val minutes = seconds / 60 + strings.resources.getQuantityString(R.plurals.duration_minutes_value, minutes, minutes) + } else { + strings.resources.getQuantityString(R.plurals.duration_seconds_value, seconds, seconds) + } +} + @Composable fun LatestMaximaScreen(repository: TrainlogRepository) { val colors = LocalTrainlogColors.current diff --git a/android/app/src/main/java/com/labfytools/trainlog/ui/TrainlogComponents.kt b/android/app/src/main/java/com/labfytools/trainlog/ui/TrainlogComponents.kt index f14f78d..af50102 100644 --- a/android/app/src/main/java/com/labfytools/trainlog/ui/TrainlogComponents.kt +++ b/android/app/src/main/java/com/labfytools/trainlog/ui/TrainlogComponents.kt @@ -201,6 +201,44 @@ fun TrainlogAction( } } +/** Presentation-only surface for dense object summaries and repeated rows. */ +@Composable +fun TrainlogCompactCard( + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + content: @Composable ColumnScope.() -> Unit, +) { + val colors = LocalTrainlogColors.current + val interaction = if (onClick == null) Modifier else Modifier.clickable(onClick = onClick) + Column( + modifier + .fillMaxWidth() + .background(colors.surface, MaterialTheme.shapes.medium) + .then(interaction) + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + content = content, + ) +} + +/** Small non-interactive metadata token; business state remains caller-owned. */ +@Composable +fun TrainlogMetaBadge( + text: String, + modifier: Modifier = Modifier, + accent: Color? = null, +) { + val colors = LocalTrainlogColors.current + Text( + text = text, + modifier = modifier.background(colors.surfaceAlt, MaterialTheme.shapes.small) + .padding(horizontal = 8.dp, vertical = 4.dp), + color = accent ?: colors.text, + style = TrainlogTypography.small, + maxLines = 1, + ) +} + enum class TrainlogButtonStyle { PRIMARY, SECONDARY, SUCCESS, DESTRUCTIVE, GHOST } /** Compact pictogram action with a mandatory accessible name. */ @@ -229,11 +267,12 @@ fun TrainlogDeleteButton( contentDescription: String, onClick: () -> Unit, modifier: Modifier = Modifier, + fullWidth: Boolean = true, ) { val colors = LocalTrainlogColors.current Button( onClick = onClick, - modifier = modifier.fillMaxWidth().heightIn(min = 52.dp), + modifier = (if (fullWidth) modifier.fillMaxWidth() else modifier).heightIn(min = 48.dp), shape = MaterialTheme.shapes.medium, colors = ButtonDefaults.buttonColors(containerColor = colors.error), ) { @@ -364,6 +403,8 @@ fun TrainlogInputField( singleLine: Boolean = true, minLines: Int = 1, maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + suffix: String? = null, + compact: Boolean = false, ) { val colors = LocalTrainlogColors.current @@ -418,7 +459,7 @@ fun TrainlogInputField( modifier = Modifier .fillMaxWidth() - .heightIn(min = 48.dp) + .heightIn(min = if (compact) 44.dp else 48.dp) .onFocusChanged { focused = it.isFocused } @@ -429,11 +470,22 @@ fun TrainlogInputField( Modifier.testTag(testTag) } ) - .background(colors.surface) + .background(colors.surface, MaterialTheme.shapes.medium) .padding( - horizontal = 12.dp, - vertical = 10.dp, + horizontal = if (compact) 10.dp else 12.dp, + vertical = if (compact) 8.dp else 10.dp, ), + decorationBox = { innerTextField -> + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Box(Modifier.weight(1f)) { innerTextField() } + suffix?.let { + BasicText(it, style = TrainlogTypography.small.copy(color = colors.muted)) + } + } + }, ) Box( diff --git a/android/app/src/main/res/values-en/strings.xml b/android/app/src/main/res/values-en/strings.xml index 20cc0a2..3f9891b 100644 --- a/android/app/src/main/res/values-en/strings.xml +++ b/android/app/src/main/res/values-en/strings.xml @@ -334,7 +334,8 @@ %d repetition%d repetitions StartCopy this proposal into the current session. Manual preparation - Start this preparation without replacing an active session. + Creates a protected active session. + rest %1$d s A session is already in progress. The imported draft was preserved. Draft “%1$s” will be deleted.\nThis action cannot be undone. TrainingResume current session diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4a888ac..3a21413 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -334,7 +334,8 @@ %d répétition%d répétitions DémarrerCopier cette proposition dans la séance en cours. Préparation manuelle - Démarrer cette préparation sans écraser une séance en cours. + Crée une séance en cours protégée. + repos %1$d s Une séance est déjà en cours. Le brouillon importé a été conservé. Le brouillon « %1$s » sera supprimé.\nCette action est irréversible. EntraînementReprendre la séance en cours diff --git a/android/app/src/test/java/com/labfytools/trainlog/ui/AiSessionDraftUiWiringTest.kt b/android/app/src/test/java/com/labfytools/trainlog/ui/AiSessionDraftUiWiringTest.kt index bcdfab2..feb6869 100644 --- a/android/app/src/test/java/com/labfytools/trainlog/ui/AiSessionDraftUiWiringTest.kt +++ b/android/app/src/test/java/com/labfytools/trainlog/ui/AiSessionDraftUiWiringTest.kt @@ -139,8 +139,8 @@ class AiSessionDraftUiWiringTest { } } compose.onNodeWithText("Dos + biceps").assertIsDisplayed() - compose.onNodeWithText("Tractions supination · 3 × 10 répétitions · repos 90 s") - .assertIsDisplayed() + compose.onNodeWithText("Tractions supination").assertIsDisplayed() + compose.onNodeWithText("3 × 10 · repos 90 s").assertIsDisplayed() compose.onNodeWithText("Accent sur le dos et les biceps.").assertIsDisplayed() }