feat(TrainingCancellation): enhance cancellation functionality and localization support
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
- Updated the training cancellation controller to accept training group IDs, improving the cancellation process. - Modified the database schema to include a JSON field for training group IDs in the training cancellations table. - Enhanced the TrainingCancellation model to support the new training group IDs field. - Updated the training cancellation service to normalize and handle training group IDs effectively. - Added localization support for training cancellation features across multiple languages, improving user experience.
This commit is contained in:
@@ -50,8 +50,12 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@@ -74,10 +78,13 @@ import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlin.math.max
|
||||
import de.tt_tagebuch.app.AppDependencies
|
||||
import de.tt_tagebuch.app.pdf.sharePdfFile
|
||||
import de.tt_tagebuch.app.pdf.writeTrainingDaySummaryPdf
|
||||
@@ -1455,6 +1462,7 @@ private fun DiaryDetailScreen(
|
||||
var planGroups by remember { mutableStateOf<List<DiaryPlanGroup>>(emptyList()) }
|
||||
var planMutating by remember { mutableStateOf(false) }
|
||||
var planActionError by remember { mutableStateOf<String?>(null) }
|
||||
var expandedPlanActionsItemId by remember { mutableStateOf<Int?>(null) }
|
||||
var showAddPlanActivity by rememberSaveable { mutableStateOf(false) }
|
||||
var showAddPlanGroupActivity by rememberSaveable { mutableStateOf(false) }
|
||||
var showAddTrainingGroup by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -1474,6 +1482,8 @@ private fun DiaryDetailScreen(
|
||||
var editPlanDuration by remember { mutableStateOf("") }
|
||||
var editPlanDurationText by remember { mutableStateOf("") }
|
||||
var editPlanGroupId by remember { mutableStateOf<Int?>(null) }
|
||||
var assigningPlanItem by remember { mutableStateOf<DiaryDateActivityItem?>(null) }
|
||||
var assignPlanGroupId by remember { mutableStateOf<Int?>(null) }
|
||||
var participants by remember { mutableStateOf<List<DiaryTrainingParticipant>>(emptyList()) }
|
||||
var participantsLoading by remember { mutableStateOf(false) }
|
||||
var participantsError by remember { mutableStateOf<String?>(null) }
|
||||
@@ -3161,103 +3171,220 @@ private fun DiaryDetailScreen(
|
||||
val planStartTimes = remember(sortedPlan, entry.trainingStart) {
|
||||
calculatePlanStartLabels(sortedPlan, entry.trainingStart)
|
||||
}
|
||||
if (expandedPlanActionsItemId != null && sortedPlan.none { it.id == expandedPlanActionsItemId }) {
|
||||
expandedPlanActionsItemId = null
|
||||
}
|
||||
val planTableScroll = rememberScrollState()
|
||||
val planTableWidth = max(LocalConfiguration.current.screenWidthDp, 380)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp, bottom = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
.horizontalScroll(planTableScroll)
|
||||
.clickable { expandedPlanActionsItemId = null },
|
||||
) {
|
||||
Text("STARTZEIT", style = MaterialTheme.typography.caption, fontWeight = FontWeight.Bold, modifier = Modifier.width(72.dp))
|
||||
Text("AKTIVITÄT / ZEITBLOCK", style = MaterialTheme.typography.caption, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||
Text("GRUPPE", style = MaterialTheme.typography.caption, fontWeight = FontWeight.Bold, modifier = Modifier.width(90.dp))
|
||||
Text("DAUER", style = MaterialTheme.typography.caption, fontWeight = FontWeight.Bold, modifier = Modifier.width(64.dp))
|
||||
Spacer(modifier = Modifier.width(140.dp))
|
||||
}
|
||||
Divider()
|
||||
sortedPlan.forEach { item ->
|
||||
val cfg = dependencies.apiConfig
|
||||
val mainImg = item.mainActivityImagePath()?.let { cfg.toAbsoluteUrl(it) }
|
||||
val nestedImg = item.groupActivities.firstNotNullOfOrNull { ga ->
|
||||
ga.nestedActivityImagePath()?.let { cfg.toAbsoluteUrl(it) }
|
||||
Column(modifier = Modifier.width(planTableWidth.dp)) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp, bottom = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"STARTZEIT",
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(DiaryPlanColStart),
|
||||
)
|
||||
Text(
|
||||
"AKTIVITÄT / ZEITBLOCK",
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Text(
|
||||
"GRUPPE",
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(DiaryPlanColGroup),
|
||||
)
|
||||
Text(
|
||||
"DAUER",
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(DiaryPlanColDuration),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(26.dp))
|
||||
}
|
||||
Divider()
|
||||
sortedPlan.forEach { item ->
|
||||
val cfg = dependencies.apiConfig
|
||||
val mainImg = item.mainActivityImagePath()?.let { cfg.toAbsoluteUrl(it) }
|
||||
val nestedImg = item.groupActivities.firstNotNullOfOrNull { ga ->
|
||||
ga.nestedActivityImagePath()?.let { cfg.toAbsoluteUrl(it) }
|
||||
}
|
||||
DiaryPlanEditableCard(
|
||||
item = item,
|
||||
allPlanItems = planItems,
|
||||
scheduledStart = planStartTimes[item.id],
|
||||
planGroups = planGroups,
|
||||
planMutating = planMutating,
|
||||
canWriteDiary = canWriteDiary,
|
||||
mainImageUrl = mainImg,
|
||||
nestedImageUrl = nestedImg,
|
||||
canReadImages = canReadDiary,
|
||||
isExpanded = expandedPlanActionsItemId == item.id,
|
||||
onToggleExpand = {
|
||||
expandedPlanActionsItemId = if (expandedPlanActionsItemId == item.id) null else item.id
|
||||
},
|
||||
onAssign = {
|
||||
assignPlanGroupId = item.groupId
|
||||
assigningPlanItem = item
|
||||
},
|
||||
onOpenImage = { url -> planImageViewerUrl = url },
|
||||
onEdit = { editingPlanItem = item },
|
||||
onDelete = {
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.deletePlanActivity(clubId, item.id)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
planGroups = dependencies.diaryManager.listTrainingGroups(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onMoveUp = {
|
||||
val scope = sameTrainingPlanScope(planItems, item)
|
||||
val idx = scope.indexOfFirst { it.id == item.id }
|
||||
if (idx > 0) {
|
||||
val targetOrder = scope[idx - 1].orderId
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.updatePlanActivityOrder(clubId, item.id, targetOrder)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onMoveDown = {
|
||||
val scope = sameTrainingPlanScope(planItems, item)
|
||||
val idx = scope.indexOfFirst { it.id == item.id }
|
||||
if (idx >= 0 && idx < scope.lastIndex) {
|
||||
val targetOrder = scope[idx + 1].orderId
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.updatePlanActivityOrder(clubId, item.id, targetOrder)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onDeleteNested = { nestedId ->
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.deletePlanNestedGroupActivity(clubId, nestedId)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
DiaryPlanEditableCard(
|
||||
item = item,
|
||||
allPlanItems = planItems,
|
||||
scheduledStart = planStartTimes[item.id],
|
||||
planGroups = planGroups,
|
||||
planMutating = planMutating,
|
||||
canWriteDiary = canWriteDiary,
|
||||
mainImageUrl = mainImg,
|
||||
nestedImageUrl = nestedImg,
|
||||
canReadImages = canReadDiary,
|
||||
onOpenImage = { url -> planImageViewerUrl = url },
|
||||
onEdit = { editingPlanItem = item },
|
||||
onDelete = {
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.deletePlanActivity(clubId, item.id)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
planGroups = dependencies.diaryManager.listTrainingGroups(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onMoveUp = {
|
||||
val scope = sameTrainingPlanScope(planItems, item)
|
||||
val idx = scope.indexOfFirst { it.id == item.id }
|
||||
if (idx > 0) {
|
||||
val targetOrder = scope[idx - 1].orderId
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.updatePlanActivityOrder(clubId, item.id, targetOrder)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
|
||||
assigningPlanItem?.let { assignItem ->
|
||||
var assignGroupMenu by remember { mutableStateOf(false) }
|
||||
AlertDialog(
|
||||
onDismissRequest = { if (!planMutating) assigningPlanItem = null },
|
||||
title = { Text(tr("diary.planAssignGroup", "Zuordnen")) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
assignItem.displayTitle(tr("diary.timeblock", "Zeitblock")),
|
||||
style = MaterialTheme.typography.body2,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
val selectedLabel = assignPlanGroupId?.let { id ->
|
||||
planGroups.find { it.id == id }?.name ?: "Gruppe $id"
|
||||
} ?: tr("diary.planGroupGlobal", "Alle / keine Gruppe")
|
||||
OutlinedButton(
|
||||
onClick = { assignGroupMenu = true },
|
||||
enabled = canWriteDiary && !planMutating,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("${tr("diary.planAssignGroup", "Zuordnen")}: $selectedLabel") }
|
||||
DropdownMenu(expanded = assignGroupMenu, onDismissRequest = { assignGroupMenu = false }) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
assignPlanGroupId = null
|
||||
assignGroupMenu = false
|
||||
},
|
||||
) { Text(tr("diary.planGroupGlobal", "Alle / keine Gruppe")) }
|
||||
planGroups.forEach { g ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
assignPlanGroupId = g.id
|
||||
assignGroupMenu = false
|
||||
},
|
||||
) { Text(g.name ?: "Gruppe ${g.id}") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onMoveDown = {
|
||||
val scope = sameTrainingPlanScope(planItems, item)
|
||||
val idx = scope.indexOfFirst { it.id == item.id }
|
||||
if (idx >= 0 && idx < scope.lastIndex) {
|
||||
val targetOrder = scope[idx + 1].orderId
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.updatePlanActivityOrder(clubId, item.id, targetOrder)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = canWriteDiary && !planMutating,
|
||||
onClick = {
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.updatePlanActivity(
|
||||
clubId,
|
||||
assignItem.id,
|
||||
UpdateDiaryPlanActivityRequest(groupId = assignPlanGroupId),
|
||||
)
|
||||
assigningPlanItem = null
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
planGroups = dependencies.diaryManager.listTrainingGroups(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) { Text(tr("common.save", "Speichern")) }
|
||||
},
|
||||
onDeleteNested = { nestedId ->
|
||||
dependencies.applicationScope.launch {
|
||||
planMutating = true
|
||||
planActionError = null
|
||||
try {
|
||||
dependencies.diaryManager.deletePlanNestedGroupActivity(clubId, nestedId)
|
||||
planItems = dependencies.diaryManager.fetchDateActivities(clubId, entry.id)
|
||||
} catch (t: Throwable) {
|
||||
planActionError = t.message
|
||||
} finally {
|
||||
planMutating = false
|
||||
}
|
||||
}
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
enabled = canWriteDiary && !planMutating,
|
||||
onClick = { assigningPlanItem = null },
|
||||
) { Text(tr("mobile.cancel", "Abbrechen")) }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -4735,6 +4862,48 @@ private fun diaryPlanItemComparator(a: DiaryDateActivityItem, b: DiaryDateActivi
|
||||
return a.id.compareTo(b.id)
|
||||
}
|
||||
|
||||
private val DiaryPlanColStart = 60.dp
|
||||
private val DiaryPlanColGroup = 68.dp
|
||||
private val DiaryPlanColDuration = 48.dp
|
||||
|
||||
@Composable
|
||||
private fun DiaryPlanQuickIconAction(
|
||||
imageVector: ImageVector,
|
||||
contentDescription: String,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 33.dp, height = 30.dp)
|
||||
.clickable(enabled = enabled, onClick = onClick),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = if (enabled) MaterialTheme.colors.primary
|
||||
else MaterialTheme.colors.onSurface.copy(alpha = 0.38f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DiaryPlanQuickTextAction(
|
||||
label: String,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = Modifier.heightIn(min = 30.dp),
|
||||
) {
|
||||
Text(label, style = MaterialTheme.typography.caption, maxLines = 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sameTrainingPlanScope(items: List<DiaryDateActivityItem>, item: DiaryDateActivityItem): List<DiaryDateActivityItem> {
|
||||
return items.filter { it.groupId == item.groupId }.sortedWith(::diaryPlanItemComparator)
|
||||
}
|
||||
@@ -4778,6 +4947,9 @@ private fun DiaryPlanEditableCard(
|
||||
mainImageUrl: String?,
|
||||
nestedImageUrl: String?,
|
||||
canReadImages: Boolean,
|
||||
isExpanded: Boolean,
|
||||
onToggleExpand: () -> Unit,
|
||||
onAssign: () -> Unit,
|
||||
onOpenImage: (String) -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
@@ -4803,42 +4975,108 @@ private fun DiaryPlanEditableCard(
|
||||
?: item.groupId?.let { gid -> planGroups.find { it.id == gid }?.name ?: "Gruppe $gid" }
|
||||
val showImageUrl = mainImageUrl ?: nestedImageUrl
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
|
||||
val canMutate = canWriteDiary && !planMutating
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 1.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onToggleExpand() }
|
||||
.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
scheduledStart ?: "—",
|
||||
style = MaterialTheme.typography.body2,
|
||||
modifier = Modifier.width(72.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier.width(DiaryPlanColStart),
|
||||
)
|
||||
Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(title, fontWeight = FontWeight.SemiBold, maxLines = 1)
|
||||
Text(
|
||||
title,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.body2,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (item.isTimeblock) {
|
||||
Text(" · $timeblockLabel", style = MaterialTheme.typography.caption, color = MaterialTheme.colors.primary)
|
||||
Text(
|
||||
" · $timeblockLabel",
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = MaterialTheme.colors.primary,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
groupLine ?: "—",
|
||||
style = MaterialTheme.typography.caption,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.width(90.dp),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.width(DiaryPlanColGroup),
|
||||
)
|
||||
Text(duration ?: "—", style = MaterialTheme.typography.body2, modifier = Modifier.width(64.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier.width(140.dp)) {
|
||||
TextButton(onClick = onMoveUp, enabled = canWriteDiary && !planMutating && canUp) { Text("↑") }
|
||||
TextButton(onClick = onMoveDown, enabled = canWriteDiary && !planMutating && canDown) { Text("↓") }
|
||||
TextButton(onClick = onEdit, enabled = canWriteDiary && !planMutating) { Text(tr("common.edit", "Bearbeiten")) }
|
||||
TextButton(onClick = onDelete, enabled = canWriteDiary && !planMutating) { Text(tr("common.delete", "Löschen")) }
|
||||
Text(
|
||||
duration ?: "—",
|
||||
style = MaterialTheme.typography.caption,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.width(DiaryPlanColDuration),
|
||||
)
|
||||
Icon(
|
||||
imageVector = if (isExpanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
|
||||
contentDescription = tr("mobile.more", "Mehr"),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.75f),
|
||||
)
|
||||
}
|
||||
if (isExpanded) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = DiaryPlanColStart, top = 3.dp, bottom = 3.dp, end = 4.dp)
|
||||
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.04f))
|
||||
.padding(horizontal = 6.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
DiaryPlanQuickTextAction(
|
||||
label = tr("diary.planActionUp", "↑ Hoch"),
|
||||
enabled = canMutate && canUp,
|
||||
onClick = onMoveUp,
|
||||
)
|
||||
DiaryPlanQuickTextAction(
|
||||
label = tr("diary.planActionDown", "↓ Runter"),
|
||||
enabled = canMutate && canDown,
|
||||
onClick = onMoveDown,
|
||||
)
|
||||
DiaryPlanQuickTextAction(
|
||||
label = tr("diary.planAssignGroup", "Zuordnen"),
|
||||
enabled = canMutate,
|
||||
onClick = onAssign,
|
||||
)
|
||||
DiaryPlanQuickTextAction(
|
||||
label = tr("common.edit", "Bearbeiten"),
|
||||
enabled = canMutate,
|
||||
onClick = onEdit,
|
||||
)
|
||||
DiaryPlanQuickTextAction(
|
||||
label = tr("common.delete", "Löschen"),
|
||||
enabled = canMutate,
|
||||
onClick = onDelete,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (canReadImages && showImageUrl != null) {
|
||||
TextButton(
|
||||
onClick = { onOpenImage(showImageUrl) },
|
||||
enabled = !planMutating,
|
||||
modifier = Modifier.padding(start = 72.dp),
|
||||
) { Text(tr("diary.planShowImage", "Übungsbild anzeigen")) }
|
||||
Text(
|
||||
tr("diary.planShowImage", "Übungsbild anzeigen"),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.padding(start = DiaryPlanColStart, top = 0.dp, bottom = 0.dp)
|
||||
.clickable(enabled = !planMutating) { onOpenImage(showImageUrl) },
|
||||
)
|
||||
}
|
||||
item.groupActivities
|
||||
.sortedBy { it.orderId ?: it.id ?: 0 }
|
||||
@@ -4846,15 +5084,24 @@ private fun DiaryPlanEditableCard(
|
||||
val sub = ga.groupPredefinedActivity.displayLabel()
|
||||
val line = if (sub.isNotEmpty()) sub else "${tr("mobile.activityFallback", "Aktivität")} ${ga.id}"
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 72.dp, top = 2.dp, bottom = 2.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(start = DiaryPlanColStart, top = 0.dp, bottom = 0.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("· $line", style = MaterialTheme.typography.caption, modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
"· $line",
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
val nid = ga.id
|
||||
if (nid != null) {
|
||||
TextButton(onClick = { onDeleteNested(nid) }, enabled = canWriteDiary && !planMutating) {
|
||||
Text(tr("common.delete", "Löschen"))
|
||||
}
|
||||
DiaryPlanQuickIconAction(
|
||||
imageVector = Icons.Filled.Delete,
|
||||
contentDescription = tr("diary.planDeleteNested", "Gruppenübung löschen"),
|
||||
enabled = canWriteDiary && !planMutating,
|
||||
onClick = { onDeleteNested(nid) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user