Refactor code structure for improved readability and maintainability
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s

This commit is contained in:
Torsten Schulz (local)
2026-05-22 14:34:16 +02:00
parent 6adf6b73e8
commit 75a17d42b5
5 changed files with 113 additions and 12 deletions

View File

@@ -3245,7 +3245,7 @@ export default {
const now = new Date();
const currentTime = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const currentHHMM = currentTime.slice(0, 5);
if (!this.trainingStart || !this.trainingEnd) {
if (!this.trainingStart) {
return;
}

View File

@@ -1,7 +1,9 @@
package de.tsschulz.tt_tagebuch.app.ui
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.BackHandler
@@ -96,6 +98,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.max
import de.tsschulz.tt_tagebuch.app.AppDependencies
import de.tsschulz.tt_tagebuch.R
import de.tsschulz.tt_tagebuch.app.pdf.sharePdfFile
import de.tsschulz.tt_tagebuch.app.pdf.writeMembersPhoneListPdf
import de.tsschulz.tt_tagebuch.app.pdf.writeTrainingDaySummaryPdf
@@ -156,6 +159,9 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/** Ab dieser Fensterbreite (dp): seitliche Navigation wie auf Tablet/Web. */
private const val MAIN_NAV_RAIL_MIN_WIDTH_DP = 600
@@ -4004,6 +4010,36 @@ private fun DiaryDetailScreen(
val planStartTimes = remember(sortedPlan, entry.trainingStart) {
calculatePlanStartLabels(sortedPlan, entry.trainingStart)
}
LaunchedEffect(entry.id, entry.trainingStart, entry.trainingEnd, sortedPlan, planStartTimes) {
val playedMarks = mutableSetOf<String>()
while (true) {
val currentHHMM = currentMinuteLabel()
val trainingStart = entry.trainingStart?.take(5)
val trainingEnd = entry.trainingEnd?.take(5)
listOfNotNull(trainingStart, trainingEnd).forEach { bellTime ->
val key = "bell:$bellTime"
if (currentHHMM == bellTime && playedMarks.add(key)) {
playRawSound(androidContext, R.raw.training_bell)
}
}
if (trainingStart != null) {
sortedPlan
.mapNotNull { item -> planStartTimes[item.id]?.take(5) }
.filter { it != trainingStart }
.distinct()
.forEach { startTime ->
val key = "item:$startTime"
if (currentHHMM == startTime && playedMarks.add(key)) {
playRawSound(androidContext, R.raw.plan_item_start)
}
}
}
delay(1_000)
}
}
if (expandedPlanActionsItemId != null && sortedPlan.none { it.id == expandedPlanActionsItemId }) {
expandedPlanActionsItemId = null
}
@@ -6816,22 +6852,79 @@ private fun DiaryPlanQuickTextAction(
}
private fun sameTrainingPlanScope(items: List<DiaryDateActivityItem>, item: DiaryDateActivityItem): List<DiaryDateActivityItem> {
return items.filter { it.groupId == item.groupId }.sortedWith(::diaryPlanItemComparator)
return items.sortedWith(::diaryPlanItemComparator)
}
private fun calculatePlanStartLabels(items: List<DiaryDateActivityItem>, trainingStart: String?): Map<Int, String> {
var current = normalizeTime(trainingStart)
var globalCursor = normalizeTime(trainingStart)
val groupCursors = mutableMapOf<Int, String>()
val result = linkedMapOf<Int, String>()
items.forEach { item ->
current?.let { result[item.id] = it.take(5) }
val delta = item.duration
if (delta != null && delta > 0 && current != null) {
current = addMinutesToTime(current, delta)
val duration = item.duration?.takeIf { it > 0 } ?: 0
val groupId = item.groupId
if (groupId == null) {
val start = latestTime(globalCursor, groupCursors.values)
start?.let { result[item.id] = it.take(5) }
val end = if (start != null && duration > 0) addMinutesToTime(start, duration) else start
globalCursor = end
if (end != null) {
groupCursors.keys.toList().forEach { gid ->
val cursor = groupCursors[gid]
if (isTimeAfter(end, cursor)) {
groupCursors[gid] = end
}
}
}
} else {
val start = groupCursors[groupId] ?: globalCursor
start?.let { result[item.id] = it.take(5) }
val end = if (start != null && duration > 0) addMinutesToTime(start, duration) else start
if (end != null) {
groupCursors[groupId] = end
}
}
}
return result
}
private fun currentMinuteLabel(): String {
return SimpleDateFormat("HH:mm", Locale.GERMANY).format(Date())
}
private fun playRawSound(context: Context, resId: Int) {
runCatching {
MediaPlayer.create(context.applicationContext, resId)?.apply {
setOnCompletionListener { player -> player.release() }
setOnErrorListener { player, _, _ ->
player.release()
true
}
start()
}
}
}
private fun latestTime(base: String?, values: Collection<String>): String? {
var latest = base
values.forEach { candidate ->
if (isTimeAfter(candidate, latest)) latest = candidate
}
return latest
}
private fun isTimeAfter(candidate: String?, current: String?): Boolean {
val c = timeToMinutes(candidate) ?: return false
val v = timeToMinutes(current) ?: return true
return c > v
}
private fun timeToMinutes(value: String?): Int? {
val v = normalizeTime(value) ?: return null
val h = v.substring(0, 2).toIntOrNull() ?: return null
val m = v.substring(3, 5).toIntOrNull() ?: return null
return h * 60 + m
}
private fun normalizeTime(value: String?): String? {
val v = value?.trim().orEmpty()
if (v.length >= 5 && v[2] == ':') return if (v.length >= 8) v.substring(0, 8) else "$v:00"
@@ -6878,11 +6971,7 @@ private fun DiaryPlanEditableCard(
val title = item.displayTitle(timeblockLabel).ifBlank {
"${tr("mobile.activityFallback", "Aktivität")} ${item.id}"
}
val duration = when {
!item.durationText.isNullOrBlank() -> item.durationText
item.duration != null -> "${item.duration} min"
else -> null
}
val duration = item.durationText?.trim()?.takeIf { it.isNotEmpty() }
val groupLine = item.planGroup?.name?.takeIf { it.isNotBlank() }
?: item.groupId?.let { gid -> planGroups.find { it.id == gid }?.name ?: "Gruppe $gid" }
val showImageUrl = mainImageUrl ?: nestedImageUrl
@@ -7000,6 +7089,7 @@ private fun DiaryPlanEditableCard(
.forEach { ga ->
val sub = ga.groupPredefinedActivity.displayLabel()
val line = if (sub.isNotEmpty()) sub else "${tr("mobile.activityFallback", "Aktivität")} ${ga.id}"
val nestedDuration = ga.durationText?.trim()?.takeIf { it.isNotEmpty() }
Row(
modifier = Modifier.fillMaxWidth().padding(start = DiaryPlanColStart, top = 0.dp, bottom = 0.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -7011,6 +7101,15 @@ private fun DiaryPlanEditableCard(
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
if (nestedDuration != null) {
Text(
nestedDuration,
style = MaterialTheme.typography.caption,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.width(DiaryPlanColDuration),
)
}
val nid = ga.id
if (nid != null) {
DiaryPlanQuickIconAction(

View File

@@ -36,6 +36,8 @@ data class PredefinedActivitySummary(
data class GroupActivitySummary(
val id: Int? = null,
val orderId: Int? = null,
val duration: Int? = null,
val durationText: String? = null,
val groupPredefinedActivity: PredefinedActivitySummary? = null,
)