refactor(design-system): consolidate components and standardize naming with Ms prefix
- Removed redundant files: `AppFooter`, `AppHeader`, `AppScaffold`, `LoadingIndicator`, `MeldestelleButton`, `MeldestelleTextField`, `DashboardCard`. - Introduced `MsFooter`, `MsHeader`, `MsScaffold`, `MsLoadingIndicator`, `MsButton`, `MsTextField`, `MsCard` with standardized implementation and naming. - Updated references in `profile-feature` and `ping-feature` to use the new components. - Aligned with roadmap goals for a consistent, reusable, and high-density design system. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
# Frontend-Komponenten Roadmap: Meldestelle-Biest
|
||||||
|
|
||||||
|
🏗️ **[Lead Architect]** | 31. März 2026
|
||||||
|
|
||||||
|
Diese Roadmap definiert den Weg von der aktuellen Prototypen-Phase hin zu einer professionellen, konsistenten und
|
||||||
|
performanten Desktop-App. Wir setzen auf einen komponentengetriebenen Ansatz (High-Density UI), um die komplexe
|
||||||
|
Datenverwaltung der Turniermeldestelle effizient abzubilden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Cleanup & Konsolidierung (Das Fundament) ✅ [IN ARBEIT]
|
||||||
|
|
||||||
|
Bevor wir neue Features bauen, räumen wir die bestehenden Entwürfe auf, um Redundanzen zu vermeiden.
|
||||||
|
|
||||||
|
* [ ] **Design-System Refactoring:**
|
||||||
|
* [ ] `Buttons.kt` (DenseButton) in `MeldestelleButton.kt` (MsButton) überführen.
|
||||||
|
* [ ] Einheitliches Naming: Alle Basis-Komponenten erhalten das Präfix `Ms` (z.B. `MsButton.kt`, `MsTextField.kt`).
|
||||||
|
* [ ] Redundante Placeholder-Dateien entfernen oder in `core/design-system/models/` bündeln.
|
||||||
|
* [ ] **Theme-Check:**
|
||||||
|
* [ ] Sicherstellen, dass alle Farben aus `AppColors` kommen und nicht hart codiert sind.
|
||||||
|
* [ ] Typografie-Skalen für High-Density optimieren (LabelSmall für Tabellen).
|
||||||
|
|
||||||
|
## Phase 2: Daten-Visualisierungs-Komponenten (Das Herzstück) 🔵 [GEPLANT]
|
||||||
|
|
||||||
|
Turniermanagement bedeutet Arbeit mit Listen. Wir benötigen mächtige, aber kompakte Anzeige-Komponenten.
|
||||||
|
|
||||||
|
* [ ] **`MsDataTable`:**
|
||||||
|
* [ ] KMP-kompatible Tabelle mit Sticky Header.
|
||||||
|
* [ ] Sortier- und Filter-Logik (in-memory & API-driven).
|
||||||
|
* [ ] Zeilen-Selektion (Einzel/Mehrfach) und Kontextmenüs.
|
||||||
|
* [ ] **`MsStatusBadge`:**
|
||||||
|
* [ ] Farbliche Kodierung für Nennungsstatus, Lizenzstatus und Prüfungsstatus.
|
||||||
|
* [ ] Kompaktes Design für die Nutzung innerhalb von Tabellenzellen.
|
||||||
|
* [ ] **`MsFilterBar`:**
|
||||||
|
* [ ] Suchfeld mit Debounce.
|
||||||
|
* [ ] Filter-Chips für schnelle Status-Wechsel.
|
||||||
|
|
||||||
|
## Phase 3: Formular- & Eingabe-System (Die Datenerfassung) ⚪ [ZUKUNFT]
|
||||||
|
|
||||||
|
Eingabe von Stammdaten muss schnell und fehlerfrei erfolgen.
|
||||||
|
|
||||||
|
* [ ] **`MsEnumDropdown`:** Automatisches Mapping von Backend-Enums (ÖTO) auf UI-Auswahl.
|
||||||
|
* [ ] **`MsValidationWrapper`:** Konsistente Anzeige von Fehlern (z.B. "Lizenz für diese Klasse nicht ausreichend").
|
||||||
|
* [ ] **`MsSearchableSelect`:** Für die Verknüpfung von Reitern/Pferden (Autocomplete).
|
||||||
|
|
||||||
|
## Phase 4: Layout-Patterns & Navigation ⚪ [ZUKUNFT]
|
||||||
|
|
||||||
|
Hier bringen wir alles zusammen, bevor das finale Routing implementiert wird.
|
||||||
|
|
||||||
|
* [ ] **`MsMasterDetailLayout`:** Standard-Layout für alle Stammdaten-Screens.
|
||||||
|
* [ ] **`MsActionToolbar`:** Einheitliche Platzierung von Hauptaktionen (Neu, Speichern, Drucken).
|
||||||
|
* [ ] **`MsDialogShell`:** Standardisierter Rahmen für Modale und Bestätigungsdialoge.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Erfolgs-Metriken
|
||||||
|
|
||||||
|
* **Wiederverwendbarkeit:** > 80% der UI besteht aus `Ms`-Komponenten.
|
||||||
|
* **Density:** Alle relevanten Daten eines Reiters/Pferdes sind ohne Scrollen in der Detailansicht sichtbar.
|
||||||
|
* **Performance:** `MsDataTable` rendert 500+ Zeilen flüssig auf ARM64 (Zora/Mac/Linux).
|
||||||
|
|
||||||
|
---
|
||||||
|
🧹 **[Curator]** | 2026-03-31
|
||||||
|
*Dieses Dokument dient als Single Source of Truth für die Frontend-Entwicklung.*
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
package at.mocode.frontend.core.designsystem.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import at.mocode.frontend.core.designsystem.theme.Dimens
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein kompakter Button für unsere High-Density UI.
|
|
||||||
*
|
|
||||||
* Warum ein eigener Button?
|
|
||||||
* Der Standard Material3 Button ist sehr hoch (40dp+) und hat viel Padding.
|
|
||||||
* Das verschwendet Platz in Tabellen oder Toolbars.
|
|
||||||
* Unser 'DenseButton' ist fix 32dp hoch- und hat weniger Innenabstand.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun DenseButton(
|
|
||||||
text: String,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
containerColor: Color = MaterialTheme.colorScheme.primary
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = onClick,
|
|
||||||
enabled = enabled,
|
|
||||||
modifier = modifier.height(32.dp), // Fixe, kompakte Höhe
|
|
||||||
shape = MaterialTheme.shapes.small, // Nutzt unsere 4dp Rundung
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = containerColor),
|
|
||||||
contentPadding = PaddingValues(horizontal = Dimens.SpacingM, vertical = 0.dp) // Wenig Padding
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.labelMedium // Kleinere Schrift
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-3
@@ -17,7 +17,7 @@ enum class ButtonSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MeldestelleButton(
|
fun MsButton(
|
||||||
text: String,
|
text: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -96,7 +96,7 @@ fun PrimaryButton(
|
|||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
isLoading: Boolean = false,
|
isLoading: Boolean = false,
|
||||||
fullWidth: Boolean = false
|
fullWidth: Boolean = false
|
||||||
) = MeldestelleButton(
|
) = MsButton(
|
||||||
text = text,
|
text = text,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -115,7 +115,7 @@ fun SecondaryButton(
|
|||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
isLoading: Boolean = false,
|
isLoading: Boolean = false,
|
||||||
fullWidth: Boolean = false
|
fullWidth: Boolean = false
|
||||||
) = MeldestelleButton(
|
) = MsButton(
|
||||||
text = text,
|
text = text,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
+1
-1
@@ -20,7 +20,7 @@ import at.mocode.frontend.core.designsystem.theme.Dimens
|
|||||||
* Im Enterprise-Kontext sind flache Cards mit dünnem Border (1px) oft sauberer.
|
* Im Enterprise-Kontext sind flache Cards mit dünnem Border (1px) oft sauberer.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun DashboardCard(
|
fun MsCard(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable ColumnScope.() -> Unit
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppFooter() {
|
fun MsFooter() {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
+1
-1
@@ -12,7 +12,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppHeader(
|
fun MsHeader(
|
||||||
isAuthenticated: Boolean,
|
isAuthenticated: Boolean,
|
||||||
username: String?,
|
username: String?,
|
||||||
onNavigateToLogin: (() -> Unit)? = null,
|
onNavigateToLogin: (() -> Unit)? = null,
|
||||||
+7
-4
@@ -1,7 +1,10 @@
|
|||||||
package at.mocode.frontend.core.designsystem.components
|
package at.mocode.frontend.core.designsystem.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -13,7 +16,7 @@ enum class LoadingSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoadingIndicator(
|
fun MsLoadingIndicator(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
size: LoadingSize = LoadingSize.MEDIUM,
|
size: LoadingSize = LoadingSize.MEDIUM,
|
||||||
message: String? = null
|
message: String? = null
|
||||||
@@ -61,7 +64,7 @@ fun FullScreenLoading(
|
|||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
LoadingIndicator(
|
MsLoadingIndicator(
|
||||||
size = LoadingSize.LARGE,
|
size = LoadingSize.LARGE,
|
||||||
message = message
|
message = message
|
||||||
)
|
)
|
||||||
@@ -79,7 +82,7 @@ fun InlineLoading(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
LoadingIndicator(
|
MsLoadingIndicator(
|
||||||
size = LoadingSize.SMALL,
|
size = LoadingSize.SMALL,
|
||||||
message = message
|
message = message
|
||||||
)
|
)
|
||||||
+3
-3
@@ -10,13 +10,13 @@ import androidx.compose.ui.Modifier
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppScaffold(
|
fun MsScaffold(
|
||||||
header: @Composable () -> Unit = {
|
header: @Composable () -> Unit = {
|
||||||
AppHeader(isAuthenticated = false, username = null)
|
MsHeader(isAuthenticated = false, username = null)
|
||||||
},
|
},
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
footer: @Composable () -> Unit = {
|
footer: @Composable () -> Unit = {
|
||||||
AppFooter()
|
MsFooter()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
+3
-3
@@ -16,7 +16,7 @@ import androidx.compose.ui.text.input.VisualTransformation
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MeldestelleTextField(
|
fun MsTextField(
|
||||||
value: String,
|
value: String,
|
||||||
onValueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -109,7 +109,7 @@ fun MeldestellePasswordField(
|
|||||||
) {
|
) {
|
||||||
var passwordVisible by remember { mutableStateOf(false) }
|
var passwordVisible by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
MeldestelleTextField(
|
MsTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@@ -152,7 +152,7 @@ fun MeldestelleEmailField(
|
|||||||
imeAction: ImeAction = ImeAction.Next,
|
imeAction: ImeAction = ImeAction.Next,
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default
|
keyboardActions: KeyboardActions = KeyboardActions.Default
|
||||||
) {
|
) {
|
||||||
MeldestelleTextField(
|
MsTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
+5
-5
@@ -15,8 +15,8 @@ import androidx.compose.ui.text.font.FontFamily
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import at.mocode.frontend.core.designsystem.components.DashboardCard
|
|
||||||
import at.mocode.frontend.core.designsystem.components.DenseButton
|
import at.mocode.frontend.core.designsystem.components.DenseButton
|
||||||
|
import at.mocode.frontend.core.designsystem.components.MsCard
|
||||||
import at.mocode.frontend.core.designsystem.theme.Dimens
|
import at.mocode.frontend.core.designsystem.theme.Dimens
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -58,7 +58,7 @@ fun PingScreen(
|
|||||||
|
|
||||||
// Right Panel: Terminal Log (40%)
|
// Right Panel: Terminal Log (40%)
|
||||||
// Hier nutzen wir bewusst einen dunklen "Terminal"-Look, unabhängig vom Theme
|
// Hier nutzen wir bewusst einen dunklen "Terminal"-Look, unabhängig vom Theme
|
||||||
DashboardCard(
|
MsCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(0.4f)
|
.weight(0.4f)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
@@ -156,7 +156,7 @@ private fun StatusGrid(uiState: PingUiState) {
|
|||||||
Column(verticalArrangement = Arrangement.spacedBy(Dimens.SpacingS)) {
|
Column(verticalArrangement = Arrangement.spacedBy(Dimens.SpacingS)) {
|
||||||
// Row 1
|
// Row 1
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingS)) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingS)) {
|
||||||
DashboardCard(modifier = Modifier.weight(1f)) {
|
MsCard(modifier = Modifier.weight(1f)) {
|
||||||
StatusHeader("SIMPLE / SECURE PING")
|
StatusHeader("SIMPLE / SECURE PING")
|
||||||
if (uiState.simplePingResponse != null) {
|
if (uiState.simplePingResponse != null) {
|
||||||
KeyValueRow("Status", uiState.simplePingResponse.status)
|
KeyValueRow("Status", uiState.simplePingResponse.status)
|
||||||
@@ -167,7 +167,7 @@ private fun StatusGrid(uiState: PingUiState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardCard(modifier = Modifier.weight(1f)) {
|
MsCard(modifier = Modifier.weight(1f)) {
|
||||||
StatusHeader("HEALTH CHECK")
|
StatusHeader("HEALTH CHECK")
|
||||||
if (uiState.healthResponse != null) {
|
if (uiState.healthResponse != null) {
|
||||||
KeyValueRow("Status", uiState.healthResponse.status)
|
KeyValueRow("Status", uiState.healthResponse.status)
|
||||||
@@ -180,7 +180,7 @@ private fun StatusGrid(uiState: PingUiState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Row 2
|
// Row 2
|
||||||
DashboardCard(modifier = Modifier.fillMaxWidth()) {
|
MsCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
StatusHeader("ENHANCED PING (RESILIENCE)")
|
StatusHeader("ENHANCED PING (RESILIENCE)")
|
||||||
if (uiState.enhancedPingResponse != null) {
|
if (uiState.enhancedPingResponse != null) {
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
|||||||
+8
-8
@@ -13,9 +13,9 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import at.mocode.frontend.core.designsystem.components.LoadingIndicator
|
import at.mocode.frontend.core.designsystem.components.MsButton
|
||||||
import at.mocode.frontend.core.designsystem.components.MeldestelleButton
|
import at.mocode.frontend.core.designsystem.components.MsLoadingIndicator
|
||||||
import at.mocode.frontend.core.designsystem.components.MeldestelleTextField
|
import at.mocode.frontend.core.designsystem.components.MsTextField
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileScreen(
|
fun ProfileScreen(
|
||||||
@@ -37,7 +37,7 @@ fun ProfileScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (uiState.isLoading) {
|
if (uiState.isLoading) {
|
||||||
LoadingIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
|
MsLoadingIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||||
} else {
|
} else {
|
||||||
// Fehleranzeige
|
// Fehleranzeige
|
||||||
uiState.errorMessage?.let { error ->
|
uiState.errorMessage?.let { error ->
|
||||||
@@ -100,7 +100,7 @@ fun ZnsLinkSection(
|
|||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
MeldestelleTextField(
|
MsTextField(
|
||||||
value = satznummer,
|
value = satznummer,
|
||||||
onValueChange = { satznummer = it },
|
onValueChange = { satznummer = it },
|
||||||
label = "ZNS Satznummer",
|
label = "ZNS Satznummer",
|
||||||
@@ -112,7 +112,7 @@ fun ZnsLinkSection(
|
|||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
MeldestelleButton(
|
MsButton(
|
||||||
onClick = { onLink(satznummer) },
|
onClick = { onLink(satznummer) },
|
||||||
text = "Jetzt verknüpfen",
|
text = "Jetzt verknüpfen",
|
||||||
isLoading = isLinking,
|
isLoading = isLinking,
|
||||||
@@ -154,7 +154,7 @@ fun ProfileDetailsSection(
|
|||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
MeldestelleTextField(
|
MsTextField(
|
||||||
value = contactEmail,
|
value = contactEmail,
|
||||||
onValueChange = { contactEmail = it },
|
onValueChange = { contactEmail = it },
|
||||||
label = "Kontakt E-Mail",
|
label = "Kontakt E-Mail",
|
||||||
@@ -162,7 +162,7 @@ fun ProfileDetailsSection(
|
|||||||
leadingIcon = Icons.Default.Email
|
leadingIcon = Icons.Default.Email
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
MeldestelleTextField(
|
MsTextField(
|
||||||
value = bio,
|
value = bio,
|
||||||
onValueChange = { bio = it },
|
onValueChange = { bio = it },
|
||||||
label = "Info / Bio",
|
label = "Info / Bio",
|
||||||
|
|||||||
Reference in New Issue
Block a user