Better dialogs
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 45s

This commit is contained in:
Torsten Schulz (local)
2026-06-09 09:54:53 +02:00
parent 16465fafc8
commit 2fa7f9b537
21 changed files with 559 additions and 115 deletions

View File

@@ -38,7 +38,6 @@ import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Button
@@ -595,7 +594,7 @@ private fun MobileFeedbackDialog(
var sending by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
AlertDialog(
StableAlertDialog(
onDismissRequest = { if (!sending) onDismiss() },
title = { Text("Feedback senden") },
text = {
@@ -1901,7 +1900,7 @@ private fun DiaryListScreen(
}
if (showNewDateDialog) {
AlertDialog(
StableAlertDialog(
onDismissRequest = {
if (!diaryState.isLoading) {
showNewDateDialog = false
@@ -2566,7 +2565,7 @@ private fun DiaryDetailScreen(
ordersForMemberId?.let { memberId ->
val member = activeMembers.firstOrNull { it.id == memberId }
AlertDialog(
StableAlertDialog(
onDismissRequest = {
ordersForMemberId = null
ordersList = emptyList()
@@ -2610,7 +2609,7 @@ private fun DiaryDetailScreen(
statsForMemberId?.let { memberId ->
val member = activeMembers.firstOrNull { it.id == memberId }
AlertDialog(
StableAlertDialog(
onDismissRequest = {
statsForMemberId = null
statsList = emptyList()
@@ -2792,7 +2791,7 @@ private fun DiaryDetailScreen(
!isExcused &&
((m.hasImage == true) || (m.imageUrl != null) || (m.primaryImageId != null) || (m.images.isNotEmpty()))
}
AlertDialog(
StableAlertDialog(
onDismissRequest = { showMembersGalleryDialog = false },
text = {
Column(modifier = Modifier.fillMaxWidth()) {
@@ -3349,7 +3348,7 @@ private fun DiaryDetailScreen(
memberNotesSheetMember?.let { sheetMember ->
val notesScroll = rememberScrollState()
AlertDialog(
StableAlertDialog(
onDismissRequest = {
memberNotesSheetMember = null
memberNotesError = null
@@ -4480,7 +4479,7 @@ private fun DiaryDetailScreen(
val presentParticipantRows = participants
.filter { it.isPresentParticipant() }
.sortedBy { p -> activeMembers.find { it.id == p.memberId }?.fullName()?.lowercase() ?: "" }
AlertDialog(
StableAlertDialog(
onDismissRequest = { if (!assignParticipantsBusy) assigningParticipantsItem = null },
title = { Text(tr("diary.assignParticipants", "Teilnehmer zuordnen")) },
text = {
@@ -4584,7 +4583,7 @@ private fun DiaryDetailScreen(
assigningPlanItem?.let { assignItem ->
var assignGroupMenu by remember { mutableStateOf(false) }
AlertDialog(
StableAlertDialog(
onDismissRequest = { if (!planMutating) assigningPlanItem = null },
title = { Text(tr("diary.planAssignGroup", "Zuordnen")) },
text = {
@@ -4659,7 +4658,7 @@ private fun DiaryDetailScreen(
editingPlanItem?.let { editItem ->
var editGroupMenu by remember { mutableStateOf(false) }
AlertDialog(
StableAlertDialog(
onDismissRequest = { if (!planMutating) editingPlanItem = null },
title = { Text(tr("diary.editPlanItem", "Eintrag bearbeiten")) },
text = {
@@ -4911,7 +4910,7 @@ private fun DiaryDetailScreen(
}
planImageViewerUrl?.let { u ->
val auth = dependencies.diaryAuthHeaders()
AlertDialog(
StableAlertDialog(
onDismissRequest = { planImageViewerUrl = null },
confirmButton = {
TextButton(onClick = { planImageViewerUrl = null }) {
@@ -5111,6 +5110,22 @@ private fun MembersScreen(
var membersActionNote by remember { mutableStateOf<String?>(null) }
var pendingBulkForms by remember { mutableStateOf(false) }
var pendingBulkTestOff by remember { mutableStateOf(false) }
var pendingDeactivateMember by remember { mutableStateOf<Member?>(null) }
var clickTtPendingMemberIds by remember { mutableStateOf<Set<Int>>(emptySet()) }
var ordersForMember by remember { mutableStateOf<Member?>(null) }
var ordersList by remember { mutableStateOf<List<de.tsschulz.tt_tagebuch.shared.api.models.MemberOrderDto>>(emptyList()) }
var ordersLoading by remember { mutableStateOf(false) }
var ordersError by remember { mutableStateOf<String?>(null) }
var statsForMember by remember { mutableStateOf<Member?>(null) }
var statsList by remember { mutableStateOf<List<MemberActivityStatDto>>(emptyList()) }
var statsLastParticipations by remember { mutableStateOf<List<MemberLastParticipationDto>>(emptyList()) }
var statsLoading by remember { mutableStateOf(false) }
var statsError by remember { mutableStateOf<String?>(null) }
var notesForMember by remember { mutableStateOf<Member?>(null) }
var notesList by remember { mutableStateOf<List<DiaryMemberNoteDto>>(emptyList()) }
var notesDraft by rememberSaveable { mutableStateOf("") }
var notesLoading by remember { mutableStateOf(false) }
var notesError by remember { mutableStateOf<String?>(null) }
var seasonStartYear by rememberSaveable { mutableStateOf(getSeasonStartYearFromDateToday()) }
var seasonMenuOpen by remember { mutableStateOf(false) }
var selectedAgeGroup by rememberSaveable { mutableStateOf("") }
@@ -5299,10 +5314,86 @@ private fun MembersScreen(
val filteredEmailsPlain = remember(filteredMembers) {
filteredMembers.flatMap { extractEmailAddressesFromMember(it) }.distinct()
}
fun loadMemberOrders(member: Member) {
ordersForMember = member
ordersLoading = true
ordersError = null
ordersList = emptyList()
dependencies.applicationScope.launch {
try {
ordersList = dependencies.memberOrdersApi.listForMember(clubId, member.id).orders
} catch (t: Throwable) {
ordersError = t.message
} finally {
ordersLoading = false
}
}
}
fun loadMemberStats(member: Member) {
statsForMember = member
statsLoading = true
statsError = null
statsList = emptyList()
statsLastParticipations = emptyList()
dependencies.applicationScope.launch {
try {
coroutineScope {
val allStats = async { dependencies.membersManager.memberActivityStats(clubId, member.id, "all") }
val recent = async { dependencies.membersManager.memberLastParticipations(clubId, member.id, 8) }
statsList = allStats.await()
statsLastParticipations = recent.await()
}
} catch (t: Throwable) {
statsError = t.message
} finally {
statsLoading = false
}
}
}
fun loadMemberNotes(member: Member) {
notesForMember = member
notesDraft = ""
notesLoading = true
notesError = null
notesList = emptyList()
dependencies.applicationScope.launch {
try {
notesList = dependencies.membersManager.listMemberNotes(clubId, member.id)
} catch (t: Throwable) {
notesError = t.message
} finally {
notesLoading = false
}
}
}
fun quickMarkForm(member: Member) {
if (!canWriteMembers) return
dependencies.applicationScope.launch {
val r = runCatching { dependencies.membersManager.quickUpdateMemberFormHandedOver(clubId, member.id) }
r.onSuccess { membersActionNote = it.message ?: trStr("members.formMarkedAsHandedOver", "Formularstatus aktualisiert.") }
.onFailure { membersActionNote = it.message ?: trStr("members.errorMarkingForm", "Formularstatus konnte nicht geändert werden.") }
dependencies.membersManager.loadMembers(clubId)
}
}
fun requestClickTt(member: Member) {
if (!canWriteMembers || clickTtPendingMemberIds.contains(member.id)) return
clickTtPendingMemberIds = clickTtPendingMemberIds + member.id
dependencies.applicationScope.launch {
try {
val r = dependencies.membersManager.requestClickTtRegistration(clubId, member.id)
membersActionNote = r.message ?: r.error ?: trStr("members.clickTtRequestSuccess", "Click-TT-Antrag angestoßen.")
dependencies.membersManager.loadMembers(clubId)
} catch (t: Throwable) {
membersActionNote = t.message ?: trStr("members.clickTtRequestError", "Click-TT-Antrag konnte nicht gestartet werden.")
} finally {
clickTtPendingMemberIds = clickTtPendingMemberIds - member.id
}
}
}
if (pendingBulkForms && canWriteMembers) {
val targets = filteredMembers.filter { it.memberFormHandedOver != true }
AlertDialog(
StableAlertDialog(
onDismissRequest = { pendingBulkForms = false },
title = { Text(tr("members.bulkFormsTitle", "Formulare übergeben")) },
text = {
@@ -5347,7 +5438,7 @@ private fun MembersScreen(
}
if (pendingBulkTestOff && canWriteMembers) {
val targets = filteredMembers.filter { it.testMembership == true }
AlertDialog(
StableAlertDialog(
onDismissRequest = { pendingBulkTestOff = false },
title = { Text(tr("members.bulkTestOffTitle", "Testmitgliedschaften beenden")) },
text = {
@@ -5391,6 +5482,151 @@ private fun MembersScreen(
)
}
pendingDeactivateMember?.let { member ->
StableAlertDialog(
onDismissRequest = { pendingDeactivateMember = null },
title = { Text(tr("members.deactivateMember", "Mitglied deaktivieren")) },
text = { Text(trStr("members.deactivateMemberConfirm", "%s deaktivieren?").replace("%s", member.fullName()).replace("{name}", member.fullName())) },
confirmButton = {
TextButton(
onClick = {
pendingDeactivateMember = null
dependencies.applicationScope.launch {
val r = runCatching { dependencies.membersManager.quickDeactivateMember(clubId, member.id) }
r.onSuccess { membersActionNote = it.message ?: trStr("members.memberDeactivated", "Mitglied deaktiviert.") }
.onFailure { membersActionNote = it.message ?: trStr("members.errorDeactivatingMember", "Mitglied konnte nicht deaktiviert werden.") }
dependencies.membersManager.loadMembers(clubId)
}
},
) { Text(tr("mobile.ok", "OK")) }
},
dismissButton = { TextButton(onClick = { pendingDeactivateMember = null }) { Text(tr("mobile.cancel", "Abbrechen")) } },
)
}
ordersForMember?.let { member ->
StableAlertDialog(
onDismissRequest = { ordersForMember = null; ordersList = emptyList(); ordersError = null },
title = { Text(trStr("orders.memberTitle", "Bestellungen: {name}").replace("{name}", member.fullName())) },
text = {
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 420.dp).verticalScroll(rememberScrollState())) {
when {
ordersLoading -> CircularProgressIndicator(modifier = Modifier.padding(24.dp).align(Alignment.CenterHorizontally))
ordersError != null -> Text(ordersError.orEmpty(), color = MaterialTheme.colors.error)
ordersList.isEmpty() -> Text(tr("orders.noOrdersMember", "Für dieses Mitglied gibt es noch keine Bestellungen."))
else -> ordersList.forEach { order ->
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
Text(order.item, fontWeight = FontWeight.SemiBold)
Text(order.status, style = MaterialTheme.typography.caption)
order.cost?.let { Text("${trStr("orders.cost", "Kosten")}: $it", style = MaterialTheme.typography.caption) }
Divider(modifier = Modifier.padding(top = 6.dp))
}
}
}
}
},
confirmButton = { TextButton(onClick = { ordersForMember = null; ordersList = emptyList() }) { Text(tr("common.close", "Schließen")) } },
)
}
statsForMember?.let { member ->
StableAlertDialog(
onDismissRequest = { statsForMember = null; statsList = emptyList(); statsLastParticipations = emptyList(); statsError = null },
title = { Text(tr("members.exercises", "Übungen") + ": " + member.fullName()) },
text = {
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 460.dp).verticalScroll(rememberScrollState())) {
when {
statsLoading -> CircularProgressIndicator(modifier = Modifier.padding(24.dp).align(Alignment.CenterHorizontally))
statsError != null -> Text(statsError.orEmpty(), color = MaterialTheme.colors.error)
statsList.isEmpty() && statsLastParticipations.isEmpty() -> Text(trStr("memberActivities.noActivities", "Keine Übungen im gewählten Zeitraum gefunden."))
else -> {
if (statsList.isNotEmpty()) {
Text(tr("members.activityStats", "Aktivität (Statistik)"), fontWeight = FontWeight.SemiBold)
statsList.take(20).forEach { row ->
Text("${row.name ?: row.code ?: "?"} - ${row.count}x", style = MaterialTheme.typography.body2, modifier = Modifier.padding(top = 3.dp))
}
}
if (statsLastParticipations.isNotEmpty()) {
Spacer(modifier = Modifier.height(10.dp))
Text(tr("members.lastParticipations", "Letzte Teilnahmen"), fontWeight = FontWeight.SemiBold)
statsLastParticipations.forEach { part ->
Text("${displayActivityDate(part.date)} ${part.activityName ?: part.activityFullName ?: ""}", style = MaterialTheme.typography.body2, modifier = Modifier.padding(top = 3.dp))
}
}
}
}
}
},
confirmButton = { TextButton(onClick = { statsForMember = null; statsList = emptyList(); statsLastParticipations = emptyList() }) { Text(tr("common.close", "Schließen")) } },
)
}
notesForMember?.let { member ->
StableAlertDialog(
onDismissRequest = { notesForMember = null; notesList = emptyList(); notesDraft = ""; notesError = null },
title = { Text(tr("members.notes", "Notizen") + ": " + member.fullName()) },
text = {
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 460.dp).verticalScroll(rememberScrollState())) {
notesError?.let { Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(bottom = 6.dp)) }
if (notesLoading) {
CircularProgressIndicator(modifier = Modifier.padding(24.dp).align(Alignment.CenterHorizontally))
} else if (notesList.isEmpty()) {
Text(trStr("memberNotes.noNotes", "Keine Notizen vorhanden."), style = MaterialTheme.typography.caption)
} else {
notesList.forEach { note ->
Row(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), verticalAlignment = Alignment.Top) {
Text(note.content.orEmpty(), modifier = Modifier.weight(1f), style = MaterialTheme.typography.body2)
if (canWriteMembers) {
TextButton(
onClick = {
notesLoading = true
dependencies.applicationScope.launch {
try {
notesList = dependencies.membersManager.deleteMemberNote(clubId, note.id)
} catch (t: Throwable) {
notesError = t.message
} finally {
notesLoading = false
}
}
},
) { Text(tr("common.delete", "Löschen"), style = MaterialTheme.typography.caption) }
}
}
Divider()
}
}
if (canWriteMembers) {
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = notesDraft,
onValueChange = { notesDraft = it },
label = { Text(tr("mobile.newNote", "Neue Notiz")) },
modifier = Modifier.fillMaxWidth(),
enabled = !notesLoading,
)
TextButton(
onClick = {
val text = notesDraft.trim()
if (text.isEmpty()) return@TextButton
notesLoading = true
dependencies.applicationScope.launch {
try {
notesList = dependencies.membersManager.addMemberNote(clubId, member.id, text)
notesDraft = ""
} catch (t: Throwable) {
notesError = t.message
} finally {
notesLoading = false
}
}
},
enabled = notesDraft.isNotBlank() && !notesLoading,
) { Text(tr("memberNotes.add", "Hinzufügen")) }
}
}
},
confirmButton = { TextButton(onClick = { notesForMember = null; notesList = emptyList(); notesDraft = "" }) { Text(tr("common.close", "Schließen")) } },
)
}
val membersBrowseScroll = rememberScrollState()
Column(
modifier = Modifier
@@ -5891,7 +6127,7 @@ private fun MembersScreen(
EmptyText(if (query.isBlank()) tr("mobile.noMembers", "Keine Mitglieder gefunden") else tr("mobile.noResults", "Keine Treffer"))
}
val tableScroll = rememberScrollState()
val tableWidth = if (showTrainingParticipationsColumn) 1030.dp else 930.dp
val tableWidth = if (showTrainingParticipationsColumn) 1180.dp else 1080.dp
Row(
modifier = Modifier
.fillMaxWidth()
@@ -5910,7 +6146,7 @@ private fun MembersScreen(
if (showTrainingParticipationsColumn) {
Text(tr("members.trainingParticipations", "Trainings-Teilnahmen"), modifier = Modifier.width(100.dp), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.caption)
}
Text(tr("members.actions", "Aktionen"), modifier = Modifier.width(120.dp), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.caption)
Text(tr("members.actions", "Aktionen"), modifier = Modifier.width(270.dp), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.caption)
}
Divider()
Column(modifier = Modifier.fillMaxWidth()) {
@@ -5959,9 +6195,48 @@ private fun MembersScreen(
if (showTrainingParticipationsColumn) {
Text((member.trainingParticipations ?: 0).toString(), modifier = Modifier.width(100.dp), style = MaterialTheme.typography.caption)
}
Row(modifier = Modifier.width(120.dp), horizontalArrangement = Arrangement.spacedBy(4.dp)) {
TextButton(onClick = { stack = MembersStackRoute.Detail(member.id) }, modifier = Modifier.heightIn(min = 28.dp)) {
Text(tr("mobile.detail", "Details"), style = MaterialTheme.typography.caption)
Row(modifier = Modifier.width(270.dp), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically) {
if (member.memberFormHandedOver != true) {
IconButton(
onClick = { quickMarkForm(member) },
enabled = canWriteMembers,
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.markFormReceived", "Formular erhalten") },
) { Text("", color = Color(0xFF2E7D32), fontWeight = FontWeight.Bold) }
}
if (member.active && member.ttr == null && member.qttr == null && member.clickTtApplicationSubmitted != true) {
IconButton(
onClick = { requestClickTt(member) },
enabled = canWriteMembers && !clickTtPendingMemberIds.contains(member.id),
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.clickTtRequestAction", "Click-TT-Antrag") },
) { Text(if (clickTtPendingMemberIds.contains(member.id)) "" else "🏓", fontSize = 16.sp) }
}
IconButton(
onClick = { loadMemberOrders(member) },
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("orders.title", "Bestellungen") },
) { Text("📦", fontSize = 16.sp) }
IconButton(
onClick = { stack = MembersStackRoute.Edit(member.id) },
enabled = canWriteMembers,
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.editMember", "Bearbeiten") },
) { Text("", fontSize = 16.sp) }
IconButton(
onClick = { loadMemberNotes(member) },
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.notes", "Notizen") },
) { Text("📝", fontSize = 16.sp) }
IconButton(
onClick = { loadMemberStats(member) },
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.exercises", "Übungen") },
) { Text("🏃", fontSize = 16.sp) }
IconButton(
onClick = { stack = MembersStackRoute.Detail(member.id) },
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("mobile.detail", "Details") },
) { Icon(Icons.Filled.Visibility, contentDescription = null, modifier = Modifier.size(18.dp)) }
if (member.active) {
IconButton(
onClick = { pendingDeactivateMember = member },
enabled = canWriteMembers,
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.deactivateMember", "Mitglied deaktivieren") },
) { Text("", color = MaterialTheme.colors.error, fontWeight = FontWeight.Bold) }
}
}
}
@@ -6967,7 +7242,7 @@ private fun DeleteAccountDialog(
) {
var password by rememberSaveable { mutableStateOf("") }
var error by rememberSaveable { mutableStateOf<String?>(null) }
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = { Text("Eigenes Konto löschen") },
text = {
@@ -7671,7 +7946,7 @@ private fun DiaryDeleteConfirmDialogs(
onGroupError: (String?) -> Unit,
) {
if (showDateConfirm) {
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismissDate,
title = { Text(tr("diary.confirmDelete", "Trainingstag loeschen")) },
text = { Text(tr("diary.confirmDeleteDateDetails", "Der leere Trainingstag wird unwiderruflich geloescht.")) },
@@ -7691,7 +7966,7 @@ private fun DiaryDeleteConfirmDialogs(
)
}
groupCandidate?.let { group ->
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismissGroup,
title = { Text(tr("diary.deleteGroup", "Trainingsgruppe loeschen")) },
text = { Text("${group.name ?: "Gruppe ${group.id}"} wirklich loeschen?") },
@@ -7735,7 +8010,7 @@ private fun DiaryQuickAddMemberDialog(
var gender by rememberSaveable { mutableStateOf("unknown") }
var busy by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
AlertDialog(
StableAlertDialog(
onDismissRequest = { if (!busy) onDismiss() },
title = { Text(tr("diary.quickAdd", "Testmitglied schnell hinzufuegen")) },
text = {
@@ -7889,7 +8164,7 @@ private fun DiaryPlanSupplementDialogs(
editingNestedItem?.let { nested ->
var duration by remember(nested.id) { mutableStateOf(nested.duration?.toString().orEmpty()) }
var durationText by remember(nested.id) { mutableStateOf(nested.durationText.orEmpty()) }
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismissEditNested,
title = { Text(tr("diary.editGroupActivity", "Gruppen-Aktivitaet bearbeiten")) },
text = {

View File

@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -967,7 +966,7 @@ private fun BillingClubScreen(dependencies: AppDependencies, onBack: () -> Unit)
}
deleteRunTarget?.let { target ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteRunTarget = null },
title = { Text(tr("billing.deleteBilling", "Löschen")) },
text = { Text(tr("billing.deleteConfirm", "Abrechnungslauf wirklich löschen?")) },
@@ -986,7 +985,7 @@ private fun BillingClubScreen(dependencies: AppDependencies, onBack: () -> Unit)
)
}
deleteTemplateTarget?.let { target ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteTemplateTarget = null },
title = { Text(tr("billing.deleteTemplate", "Vorlage löschen")) },
text = { Text(tr("billing.deleteTemplateConfirm", "Vorlage wirklich löschen?")) },

View File

@@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -346,7 +345,7 @@ private fun ClubAdminPermissionsScreen(dependencies: AppDependencies, onBack: ()
val dialogMember = customizeFor
if (dialogMember != null) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { if (!customizeSaving) customizeFor = null },
title = { Text(dialogMember.user?.email ?: "User ${dialogMember.userId}") },
text = {
@@ -538,7 +537,7 @@ private fun ClubAdminLogsScreen(dependencies: AppDependencies, onBack: () -> Uni
}
if (detailId != null) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { detailId = null },
title = { Text(tr("mobile.logDetail", "Log-Detail")) },
text = {

View File

@@ -14,7 +14,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -723,7 +722,7 @@ private fun ClubTrainingGroupsTabContent(
}
if (editing != null) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { editing = null },
title = { Text(tr("trainingGroupsTab.editGroup", "Gruppe bearbeiten")) },
text = {
@@ -758,7 +757,7 @@ private fun ClubTrainingGroupsTabContent(
if (deleteTarget != null) {
val dg = deleteTarget!!
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteTarget = null },
title = { Text(tr("trainingGroupsTab.delete", "Löschen")) },
text = {
@@ -959,7 +958,7 @@ private fun ClubTrainingTimesTabContent(
if (editing != null) {
val et = editing!!
AlertDialog(
StableAlertDialog(
onDismissRequest = { editing = null },
title = { Text(tr("trainingTimesTab.editTime", "Trainingszeit bearbeiten")) },
text = {
@@ -1024,7 +1023,7 @@ private fun ClubTrainingTimesTabContent(
if (deleteTimeId != null) {
val tid = deleteTimeId!!
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteTimeId = null },
title = { Text(tr("trainingTimesTab.delete", "Löschen")) },
text = { Text("Diese Trainingszeit wirklich löschen?") },

View File

@@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -208,7 +207,7 @@ private fun PredefinedActivityEditorDialog(
var err by remember { mutableStateOf<String?>(null) }
var busy by remember { mutableStateOf(false) }
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = { Text(if (isNew) resolve("mobile.newPredefined", "Neue Aktivität") else resolve("mobile.editPredefined", "Bearbeiten")) },
text = {

View File

@@ -16,7 +16,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
@@ -106,7 +105,7 @@ internal fun TournamentEditorClassesTab(
var distributedMatches by remember { mutableStateOf<List<TournamentMatchDto>>(emptyList()) }
var distributedMessage by remember { mutableStateOf<String?>(null) }
if (showAdd) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { showAdd = false },
title = { Text(tr("tournaments.addClass", "Klasse anlegen")) },
text = {
@@ -185,7 +184,7 @@ internal fun TournamentEditorClassesTab(
}
if (showDistributedDialog) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { showDistributedDialog = false },
title = { Text(tr("tournaments.distributeTablesResult", "Tischverteilung")) },
text = {
@@ -512,7 +511,7 @@ internal fun TournamentEditorParticipantsTab(
if (showExternal) {
OutlinedButton(onClick = { extDialog = true }) { Text(tr("tournaments.addExternalParticipant", "Extern hinzufügen")) }
if (extDialog) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { extDialog = false },
title = { Text(tr("tournaments.addExternalParticipant", "Extern hinzufügen")) },
text = {
@@ -940,7 +939,7 @@ internal fun TournamentEditorMatchesTab(
// Confirmation dialog for deleting a set
if (confirmDelete != null) {
val (matchIdToDelete, setToDelete) = confirmDelete!!
AlertDialog(
StableAlertDialog(
onDismissRequest = { confirmDelete = null },
title = { Text(tr("tournaments.confirmDeleteSetTitle", "Satz löschen")) },
text = { Text(tr("tournaments.confirmDeleteSet", "Soll dieser Satz wirklich gelöscht werden?")) },
@@ -970,7 +969,7 @@ internal fun TournamentEditorMatchesTab(
// Edit set dialog
if (editingSet != null) {
val (matchIdToEdit, setToEdit) = editingSet!!
AlertDialog(
StableAlertDialog(
onDismissRequest = { editingSet = null },
title = { Text(tr("tournaments.editSetTitle", "Satz bearbeiten")) },
text = {
@@ -1035,7 +1034,7 @@ internal fun TournamentEditorMatchesTab(
)
}
if (showDistributedDialog) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { showDistributedDialog = false },
title = { Text(tr("tournaments.distributeTablesResult", "Tischverteilung")) },
text = {

View File

@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider

View File

@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Checkbox
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
@@ -148,7 +147,7 @@ fun InternalTournamentStatsDialog(
val scroll = rememberScrollState()
val s = stats
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = { Text(tr("tournaments.internalStatsTitle", "Interne Turnier-Statistik (Einzel)")) },
text = {

View File

@@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.CircularProgressIndicator
@@ -491,7 +490,7 @@ fun MemberGroupPortraitCropRoute(
}
if (memberPickerOpen) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { memberPickerOpen = false },
title = { Text(s("members.groupCropPickMember", "Mitglied wählen")) },
text = {

View File

@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.CircularProgressIndicator
@@ -664,7 +663,7 @@ internal fun OfficialTournamentsWorkspaceScreen(
}
if (showInfoDialog && infoMessage != null) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { showInfoDialog = false },
title = { Text(infoTitle) },
text = { Text(infoMessage!!) },
@@ -681,7 +680,7 @@ internal fun OfficialTournamentsWorkspaceScreen(
"reset" -> participantsRows(pBatch, mapSnapshot(), "participated") { memberNameById(it) }
else -> emptyList()
}
AlertDialog(
StableAlertDialog(
onDismissRequest = { batchAction = null },
title = { Text(tr("officialTournaments.batchTitle", "Sammelaktion")) },
text = { Text(tr("officialTournaments.batchConfirm", "{n} Einträge wirklich ausführen?").replace("{n}", rows.size.toString())) },
@@ -736,7 +735,7 @@ internal fun OfficialTournamentsWorkspaceScreen(
}
deleteTarget?.let { target ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteTarget = null },
title = { Text(tr("officialTournaments.deleteTournamentTitle", "Turnier löschen")) },
text = { Text("${target.title ?: ""} (ID ${target.id})") },

View File

@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -300,7 +299,7 @@ private fun MyTischtennisAccountScreen(dependencies: AppDependencies, onBack: ()
}
if (confirmUnlink) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { confirmUnlink = false },
title = { Text(tr("myTischtennisAccount.unlinkAccountTitle", "Account trennen")) },
text = { Text(tr("myTischtennisAccount.unlinkAccountConfirm", "Wirklich trennen?")) },
@@ -359,7 +358,7 @@ private fun MyTischtennisEditorDialog(
var err by remember { mutableStateOf<String?>(null) }
var busy by remember { mutableStateOf(false) }
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
@@ -578,7 +577,7 @@ private fun ClickTtAccountScreen(dependencies: AppDependencies, onBack: () -> Un
}
if (confirmDelete) {
AlertDialog(
StableAlertDialog(
onDismissRequest = { confirmDelete = false },
title = { Text(ct("mobile.clickTt.deleteTitle", "Account löschen")) },
text = { Text(ct("mobile.clickTt.deleteConfirm", "Wirklich löschen?")) },
@@ -623,7 +622,7 @@ private fun ClickTtEditorDialog(
var err by remember { mutableStateOf<String?>(null) }
var busy by remember { mutableStateOf(false) }
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = {
Text(

View File

@@ -21,7 +21,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Card
import androidx.compose.material.Checkbox
import androidx.compose.material.CircularProgressIndicator
@@ -43,9 +42,11 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import de.tsschulz.tt_tagebuch.app.AppDependencies
import de.tsschulz.tt_tagebuch.app.stats.TrainingStatsDerived
import de.tsschulz.tt_tagebuch.shared.api.models.ScheduleMatchDto
@@ -348,7 +349,7 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
}
detailMatch?.let { m ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { detailMatch = null },
title = { Text("${m.homeTeam?.name ?: "?"} : ${m.guestTeam?.name ?: "?"}") },
text = {
@@ -517,23 +518,42 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
LaunchedEffect(m.id, clubId) {
loadPlayerDialogMembersFor(m)
}
AlertDialog(
onDismissRequest = { if (!playerSaving) playerMatch = null },
title = { Text(tr("schedule.playerSelectionTitle", "Spieler")) },
text = {
Column(modifier = Modifier.heightIn(max = 400.dp)) {
playerError?.let { Text(it, color = MaterialTheme.colors.error) }
val dialogHeight = (LocalConfiguration.current.screenHeightDp * 0.82f).dp
Dialog(onDismissRequest = { if (!playerSaving) playerMatch = null }) {
Card(
modifier = Modifier
.fillMaxWidth()
.widthIn(max = 560.dp)
.height(dialogHeight),
elevation = 8.dp,
) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Text(
tr("schedule.playerSelectionTitle", "Spieler"),
style = MaterialTheme.typography.h6,
fontWeight = FontWeight.SemiBold,
)
Spacer(modifier = Modifier.height(8.dp))
playerError?.let {
Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(bottom = 8.dp))
}
val memberList = playerDialogMembers
if (membersState.isLoading && memberList.isEmpty()) {
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
Box(
modifier = Modifier.fillMaxWidth().weight(1f),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
} else {
val scroll = rememberScrollState()
Column(Modifier.verticalScroll(scroll)) {
memberList.forEach { mem ->
LazyColumn(
modifier = Modifier.fillMaxWidth().weight(1f),
) {
items(memberList, key = { it.id }) { mem ->
val id = mem.id
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp),
) {
Column(Modifier.weight(1f)) {
Text("${mem.firstName} ${mem.lastName}".trim(), maxLines = 1)
@@ -564,39 +584,42 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
}
}
}
}
},
confirmButton = {
TextButton(
enabled = !playerSaving && !isFriendlyMatchLocked(m),
onClick = {
scope.launch {
playerSaving = true
playerError = null
runCatching {
val visibleIds = playerDialogMembers.map { it.id }.toSet()
fun mergeVisible(existing: List<Int>, selected: List<Int>): List<Int> =
(existing.filter { it !in visibleIds } + selected.filter { it in visibleIds }).distinct()
dependencies.scheduleManager.updateMatchPlayersForMatch(
clubId = clubId,
match = m,
ready = mergeVisible(m.playersReady, readyIds),
planned = mergeVisible(m.playersPlanned, plannedIds),
played = mergeVisible(m.playersPlayed, playedIds),
)
playerMatch = null
}.onFailure { playerError = it.message ?: tr("schedule.errorSavingPlayerSelection", "Speichern fehlgeschlagen") }
playerSaving = false
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(enabled = !playerSaving, onClick = { playerMatch = null }) {
Text(tr("common.cancel", "Abbrechen"))
}
},
) { Text(tr("common.save", "Speichern")) }
},
dismissButton = {
TextButton(enabled = !playerSaving, onClick = { playerMatch = null }) {
Text(tr("common.cancel", "Abbrechen"))
TextButton(
enabled = !playerSaving && !isFriendlyMatchLocked(m),
onClick = {
scope.launch {
playerSaving = true
playerError = null
runCatching {
val visibleIds = playerDialogMembers.map { it.id }.toSet()
fun mergeVisible(existing: List<Int>, selected: List<Int>): List<Int> =
(existing.filter { it !in visibleIds } + selected.filter { it in visibleIds }).distinct()
dependencies.scheduleManager.updateMatchPlayersForMatch(
clubId = clubId,
match = m,
ready = mergeVisible(m.playersReady, readyIds),
planned = mergeVisible(m.playersPlanned, plannedIds),
played = mergeVisible(m.playersPlayed, playedIds),
)
playerMatch = null
}.onFailure { playerError = it.message ?: tr("schedule.errorSavingPlayerSelection", "Speichern fehlgeschlagen") }
playerSaving = false
}
},
) { Text(if (playerSaving) "" else tr("common.save", "Speichern")) }
}
}
},
)
}
}
}
}
@@ -639,7 +662,7 @@ private fun FriendlyMatchEditDialog(
return normalized
}
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = { Text(if (match == null) "Freundschaftsspiel anlegen" else "Freundschaftsspiel bearbeiten") },
text = {
@@ -812,7 +835,7 @@ private fun FriendlyResultDialog(
resultDetails = rows,
)
AlertDialog(
StableAlertDialog(
onDismissRequest = onDismiss,
title = { Text("Ergebniseingabe") },
text = {

View File

@@ -0,0 +1,109 @@
package de.tsschulz.tt_tagebuch.app.ui
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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@Composable
fun StableAlertDialog(
onDismissRequest: () -> Unit,
title: @Composable (() -> Unit)? = null,
text: @Composable (() -> Unit)? = null,
confirmButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
dismissButton: @Composable (() -> Unit)? = null,
properties: DialogProperties = DialogProperties(),
) {
StableAlertDialogFrame(
onDismissRequest = onDismissRequest,
title = title,
text = text,
modifier = modifier,
properties = properties,
buttons = {
dismissButton?.invoke()
confirmButton()
},
)
}
@Composable
fun StableAlertDialog(
onDismissRequest: () -> Unit,
buttons: @Composable () -> Unit,
modifier: Modifier = Modifier,
title: @Composable (() -> Unit)? = null,
text: @Composable (() -> Unit)? = null,
properties: DialogProperties = DialogProperties(),
) {
StableAlertDialogFrame(
onDismissRequest = onDismissRequest,
title = title,
text = text,
modifier = modifier,
properties = properties,
buttons = buttons,
)
}
@Composable
private fun StableAlertDialogFrame(
onDismissRequest: () -> Unit,
title: @Composable (() -> Unit)?,
text: @Composable (() -> Unit)?,
modifier: Modifier,
properties: DialogProperties,
buttons: @Composable () -> Unit,
) {
val maxHeight = (LocalConfiguration.current.screenHeightDp * 0.86f).dp
Dialog(onDismissRequest = onDismissRequest, properties = properties) {
Card(
modifier = modifier
.fillMaxWidth()
.widthIn(max = 560.dp)
.heightIn(max = maxHeight),
elevation = 8.dp,
) {
Column(modifier = Modifier.padding(16.dp)) {
if (title != null) {
androidx.compose.material.ProvideTextStyle(
MaterialTheme.typography.h6.copy(fontWeight = FontWeight.SemiBold),
) {
title()
}
}
if (text != null) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = if (title != null) 12.dp else 0.dp)
.heightIn(max = maxHeight - 112.dp),
) {
text()
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(top = 12.dp),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
buttons()
}
}
}
}
}

View File

@@ -17,7 +17,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -697,7 +696,7 @@ internal fun TeamEditorScreen(
}
info?.let { msg ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { info = null },
title = { Text(t("common.info", "Hinweis")) },
text = { Text(msg) },

View File

@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
@@ -362,7 +361,7 @@ internal fun TeamManagementScreen(
}
deleteTarget?.let { target ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteTarget = null },
title = { Text(getMobileString("mobile.teamDeleteTitle", "Mannschaft löschen?")) },
text = { Text(target.name.ifBlank { "#${target.id}" }) },
@@ -395,7 +394,7 @@ internal fun TeamManagementScreen(
}
infoMessage?.let { msg ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { infoMessage = null },
title = { Text(getMobileString("common.error", "Fehler")) },
text = { Text(msg) },

View File

@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
@@ -475,7 +474,7 @@ internal fun TeamPlanningScreen(
}
deleteTeamId?.let { tid ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { deleteTeamId = null },
title = { Text(t("mobile.teamDeleteTitle", "Mannschaft löschen?")) },
text = { Text(teams.find { it.id == tid }?.name.orEmpty()) },
@@ -497,7 +496,7 @@ internal fun TeamPlanningScreen(
}
info?.let { msg ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { info = null },
title = { Text(t("common.error", "Fehler")) },
text = { Text(msg) },

View File

@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Divider
@@ -668,7 +667,7 @@ internal fun TrainingStatsScreen(dependencies: AppDependencies) {
}
detailMember?.let { member ->
AlertDialog(
StableAlertDialog(
onDismissRequest = { detailMember = null },
title = {
Text(

View File

@@ -1,7 +1,7 @@
[versions]
# composeApp (Play Store / „Über die App“-Build)
appVersionCode = "25"
appVersionName = "1.7.5"
appVersionCode = "26"
appVersionName = "1.7.6"
agp = "9.2.1"
android-compileSdk = "35"
android-minSdk = "24"

View File

@@ -9,6 +9,7 @@ import de.tsschulz.tt_tagebuch.shared.api.models.MemberSetBody
import de.tsschulz.tt_tagebuch.shared.api.models.MemberTransferRunBody
import io.ktor.client.call.body
import io.ktor.client.request.forms.formData
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.request.post
@@ -18,8 +19,16 @@ import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.contentType
import io.ktor.client.request.forms.MultiPartFormDataContent
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class MemberNoteMutationBody(
val clubId: Int,
val memberId: Int? = null,
val content: String? = null,
)
class MembersApi(
private val client: AuthedHttpClient,
) {
@@ -58,6 +67,32 @@ class MembersApi(
return client.http.post("/api/clubmembers/quick-update-member-form/$clubId/$memberId").body()
}
suspend fun quickDeactivateMember(clubId: Int, memberId: Int): MemberQuickMutationResponse {
return client.http.post("/api/clubmembers/quick-deactivate/$clubId/$memberId").body()
}
suspend fun requestClickTtRegistration(clubId: Int, memberId: Int): MemberQuickMutationResponse {
return client.http.post("/api/clubmembers/clicktt-registration/$clubId/$memberId").body()
}
suspend fun listMemberNotes(clubId: Int, memberId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> {
return client.http.get("/api/member-notes/$memberId") {
parameter("clubId", clubId)
}.body()
}
suspend fun addMemberNote(clubId: Int, memberId: Int, content: String): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> {
return client.http.post("/api/member-notes") {
setBody(MemberNoteMutationBody(clubId = clubId, memberId = memberId, content = content))
}.body()
}
suspend fun deleteMemberNote(clubId: Int, noteId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> {
return client.http.delete("/api/member-notes/$noteId") {
setBody(MemberNoteMutationBody(clubId = clubId))
}.body()
}
suspend fun transferMembers(clubId: Int, body: MemberTransferRunBody): JsonObject {
return client.http.post("/api/clubmembers/transfer/$clubId") {
setBody(body)

View File

@@ -21,6 +21,7 @@ data class Member(
val testMembership: Boolean? = null,
val picsInInternetAllowed: Boolean? = null,
val memberFormHandedOver: Boolean? = null,
val clickTtApplicationSubmitted: Boolean? = null,
val adultReleaseApproved: Boolean? = null,
val adultReserveApproved: Boolean? = null,
val lastTraining: String? = null,

View File

@@ -77,6 +77,21 @@ class MembersManager(
suspend fun quickUpdateMemberFormHandedOver(clubId: Int, memberId: Int): MemberQuickMutationResponse =
membersApi.quickUpdateMemberFormHandedOver(clubId, memberId)
suspend fun quickDeactivateMember(clubId: Int, memberId: Int): MemberQuickMutationResponse =
membersApi.quickDeactivateMember(clubId, memberId)
suspend fun requestClickTtRegistration(clubId: Int, memberId: Int): MemberQuickMutationResponse =
membersApi.requestClickTtRegistration(clubId, memberId)
suspend fun listMemberNotes(clubId: Int, memberId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> =
membersApi.listMemberNotes(clubId, memberId)
suspend fun addMemberNote(clubId: Int, memberId: Int, content: String): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> =
membersApi.addMemberNote(clubId, memberId, content)
suspend fun deleteMemberNote(clubId: Int, noteId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> =
membersApi.deleteMemberNote(clubId, noteId)
suspend fun transferMembers(clubId: Int, body: MemberTransferRunBody): JsonObject =
membersApi.transferMembers(clubId, body)