Improve Android profile management and account settings UI.
Add profile update API calls and richer edit/manage flows on TV and mobile, with version bumps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6a0eee0313
commit
2eabd76d93
8 changed files with 1168 additions and 579 deletions
|
|
@ -20,8 +20,8 @@ android {
|
|||
applicationId = "nl.vonas.mediacluster"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 9
|
||||
versionName = "1.0.8"
|
||||
versionCode = 11
|
||||
versionName = "1.0.10"
|
||||
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
||||
|
|
|
|||
|
|
@ -244,6 +244,26 @@ class ApiClient(
|
|||
parseObject(execute(req)) { o -> o.getJSONObject("profile").toProfile() }
|
||||
}
|
||||
|
||||
suspend fun updateProfile(
|
||||
profileId: String,
|
||||
name: String? = null,
|
||||
avatarId: String? = null,
|
||||
isKids: Boolean? = null,
|
||||
pin: String? = null,
|
||||
clearPin: Boolean = false,
|
||||
): ViewerProfile = withContext(Dispatchers.IO) {
|
||||
val body = JSONObject()
|
||||
if (name != null) body.put("name", name)
|
||||
if (avatarId != null) body.put("avatarId", avatarId)
|
||||
if (isKids != null) body.put("isKids", isKids)
|
||||
if (clearPin) body.put("clearPin", true)
|
||||
else if (!pin.isNullOrBlank()) body.put("pin", pin)
|
||||
val req = authed("$baseUrl/api/v1/client/profiles/$profileId")
|
||||
.patch(body.toString().toRequestBody(jsonMedia))
|
||||
.build()
|
||||
parseObject(execute(req)) { o -> o.getJSONObject("profile").toProfile() }
|
||||
}
|
||||
|
||||
suspend fun deleteProfile(profileId: String) = withContext(Dispatchers.IO) {
|
||||
val req = authed("$baseUrl/api/v1/client/profiles/$profileId").delete().build()
|
||||
execute(req)
|
||||
|
|
|
|||
|
|
@ -498,6 +498,8 @@ private fun MainHub(
|
|||
)
|
||||
MainTab.Settings -> SettingsScreen(
|
||||
api = api,
|
||||
profileAvatarId = profileAvatarId,
|
||||
profileName = profileName,
|
||||
onBack = {
|
||||
onTabChange(MainTab.Home)
|
||||
focusTopNav()
|
||||
|
|
@ -509,38 +511,6 @@ private fun MainHub(
|
|||
}
|
||||
}
|
||||
StatusBarGlassOverlay(Modifier.align(Alignment.TopCenter))
|
||||
// Profiel-avatar rechtsboven → terug naar switcher
|
||||
Row(
|
||||
Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.statusBarsPadding()
|
||||
.padding(top = 8.dp, end = 12.dp)
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.background(Color(0xCC1A1A22))
|
||||
.clickable(onClick = onSwitchProfile)
|
||||
.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(28.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF2E2E3A)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(avatarEmoji(profileAvatarId ?: "fox"), fontSize = 16.sp)
|
||||
}
|
||||
if (!profileName.isNullOrBlank()) {
|
||||
Text(
|
||||
profileName,
|
||||
color = Color.White,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(Modifier.focusRequester(navFocus)) {
|
||||
TopNavBar(
|
||||
|
|
@ -1117,6 +1087,8 @@ private fun ShelfBlock(
|
|||
@Composable
|
||||
private fun SettingsScreen(
|
||||
api: ApiClient,
|
||||
profileAvatarId: String? = null,
|
||||
profileName: String? = null,
|
||||
onBack: () -> Unit,
|
||||
onSwitchProfile: () -> Unit = {},
|
||||
onManageProfiles: () -> Unit = {},
|
||||
|
|
@ -1189,20 +1161,40 @@ private fun SettingsScreen(
|
|||
)
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Box(
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(McColors.BgElevated)
|
||||
.clickable(onClick = onSwitchProfile)
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(
|
||||
"Profiel wisselen",
|
||||
color = Color.White,
|
||||
fontSize = McDimens.body,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.size(44.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF2E2E3A)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(avatarEmoji(profileAvatarId ?: "fox"), fontSize = 22.sp)
|
||||
}
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
profileName?.takeIf { it.isNotBlank() } ?: "Profiel",
|
||||
color = Color.White,
|
||||
fontSize = McDimens.body,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
)
|
||||
Text(
|
||||
"Tik om van profiel te wisselen",
|
||||
color = McColors.TextDim,
|
||||
fontSize = McDimens.meta,
|
||||
)
|
||||
}
|
||||
Text("›", color = Color(0xFF888888), fontSize = 22.sp)
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Box(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,8 +20,8 @@ android {
|
|||
applicationId = "nl.vonas.mediacluster.tv"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 71
|
||||
versionName = "0.14.8"
|
||||
versionCode = 74
|
||||
versionName = "0.14.11"
|
||||
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
||||
|
|
|
|||
|
|
@ -244,6 +244,26 @@ class ApiClient(
|
|||
parseObject(execute(req)) { o -> o.getJSONObject("profile").toProfile() }
|
||||
}
|
||||
|
||||
suspend fun updateProfile(
|
||||
profileId: String,
|
||||
name: String? = null,
|
||||
avatarId: String? = null,
|
||||
isKids: Boolean? = null,
|
||||
pin: String? = null,
|
||||
clearPin: Boolean = false,
|
||||
): ViewerProfile = withContext(Dispatchers.IO) {
|
||||
val body = JSONObject()
|
||||
if (name != null) body.put("name", name)
|
||||
if (avatarId != null) body.put("avatarId", avatarId)
|
||||
if (isKids != null) body.put("isKids", isKids)
|
||||
if (clearPin) body.put("clearPin", true)
|
||||
else if (!pin.isNullOrBlank()) body.put("pin", pin)
|
||||
val req = authed("$baseUrl/api/v1/client/profiles/$profileId")
|
||||
.patch(body.toString().toRequestBody(jsonMedia))
|
||||
.build()
|
||||
parseObject(execute(req)) { o -> o.getJSONObject("profile").toProfile() }
|
||||
}
|
||||
|
||||
suspend fun deleteProfile(profileId: String) = withContext(Dispatchers.IO) {
|
||||
val req = authed("$baseUrl/api/v1/client/profiles/$profileId").delete().build()
|
||||
execute(req)
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
|
|
@ -57,6 +59,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -628,6 +631,8 @@ private fun MainHub(
|
|||
)
|
||||
MainTab.Settings -> SettingsScreen(
|
||||
api = api,
|
||||
profileAvatarId = profileAvatarId,
|
||||
profileName = profileName,
|
||||
onBack = {
|
||||
onTabChange(MainTab.Home)
|
||||
focusTopNav()
|
||||
|
|
@ -1329,6 +1334,8 @@ private fun ShelfBlock(
|
|||
@Composable
|
||||
private fun SettingsScreen(
|
||||
api: ApiClient,
|
||||
profileAvatarId: String? = null,
|
||||
profileName: String? = null,
|
||||
onBack: () -> Unit,
|
||||
onSwitchProfile: () -> Unit = {},
|
||||
onManageProfiles: () -> Unit = {},
|
||||
|
|
@ -1337,6 +1344,7 @@ private fun SettingsScreen(
|
|||
var prefs by remember { mutableStateOf<List<ShelfPref>>(emptyList()) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val firstFocus = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
|
|
@ -1344,6 +1352,7 @@ private fun SettingsScreen(
|
|||
} catch (e: Exception) {
|
||||
error = e.message
|
||||
}
|
||||
runCatching { firstFocus.requestFocus() }
|
||||
}
|
||||
|
||||
fun persist(next: List<ShelfPref>) {
|
||||
|
|
@ -1357,101 +1366,271 @@ private fun SettingsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize().background(McColors.Bg).padding(40.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(Color(0xFF0B0B0F), Color(0xFF12121A), Color(0xFF0B0B0F)),
|
||||
),
|
||||
),
|
||||
) {
|
||||
item {
|
||||
Text("Instellingen", color = Color.White, fontSize = 30.sp, fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
SectionLabel("Account")
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.background(McColors.BgElevated)
|
||||
.padding(20.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text("Vonas Media Cluster", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
|
||||
Text("Versie ${BuildConfig.VERSION_NAME}", color = McColors.TextDim, fontSize = 14.sp)
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 48.dp, vertical = 28.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
contentPadding = PaddingValues(bottom = 40.dp),
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
"Instellingen",
|
||||
color = Color.White,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
"Profiel, account en home-indeling",
|
||||
color = Color(0xFF8E8E9A),
|
||||
fontSize = 15.sp,
|
||||
)
|
||||
Spacer(Modifier.height(22.dp))
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
McButton("Profiel wisselen", onClick = onSwitchProfile)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
McButton("Profielen beheren", onClick = onManageProfiles)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
McButton("Uitloggen", onClick = onLogout)
|
||||
Spacer(Modifier.height(20.dp))
|
||||
SectionLabel("Home-planken")
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
"Kies welke rijen op Home staan en in welke volgorde.",
|
||||
color = McColors.TextDim,
|
||||
fontSize = 15.sp,
|
||||
)
|
||||
if (error != null) {
|
||||
|
||||
item {
|
||||
SectionLabel("Profiel")
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Surface(
|
||||
onClick = onSwitchProfile,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(firstFocus),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(16.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.02f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF1A1A22),
|
||||
focusedContainerColor = Color(0xFF262632),
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 22.dp, vertical = 18.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(18.dp),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(64.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF2E2E3A)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
avatarEmoji(profileAvatarId ?: "fox"),
|
||||
fontSize = 30.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
profileName?.takeIf { it.isNotBlank() } ?: "Profiel",
|
||||
color = Color.White,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
"Wissel van kijkprofiel",
|
||||
color = Color(0xFF9A9AA8),
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
Text("›", color = Color(0xFF888888), fontSize = 28.sp, fontWeight = FontWeight.Light)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
SettingsActionRow(
|
||||
title = "Profielen beheren",
|
||||
subtitle = "Toevoegen, bewerken of verwijderen",
|
||||
onClick = onManageProfiles,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
SectionLabel("Account")
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(Color(0xFF1A1A22))
|
||||
.padding(horizontal = 22.dp, vertical = 18.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
"Vonas Media Cluster",
|
||||
color = Color.White,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
"Versie ${BuildConfig.VERSION_NAME}",
|
||||
color = Color(0xFF8E8E9A),
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
SettingsActionRow(
|
||||
title = "Uitloggen",
|
||||
subtitle = "Apparaat ontkoppelen van dit account",
|
||||
danger = true,
|
||||
onClick = onLogout,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
SectionLabel("Home-planken")
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
"Kies welke rijen op Home staan en in welke volgorde.",
|
||||
color = Color(0xFF8E8E9A),
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
if (error != null) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(error!!, color = McColors.Danger, fontSize = 14.sp)
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(error!!, color = McColors.Danger)
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
}
|
||||
itemsIndexed(prefs) { index, p ->
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.background(McColors.BgElevated)
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(p.name, color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Medium)
|
||||
Text(
|
||||
if (p.visible) "Zichtbaar op Home" else "Verborgen",
|
||||
color = if (p.visible) Color(0xFF888888) else Color(0xFF555555),
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
McButton("↑") {
|
||||
if (index > 0) {
|
||||
val next = prefs.toMutableList()
|
||||
val tmp = next[index - 1]
|
||||
next[index - 1] = next[index]
|
||||
next[index] = tmp
|
||||
persist(next)
|
||||
|
||||
itemsIndexed(prefs) { index, p ->
|
||||
Surface(
|
||||
onClick = {
|
||||
val next = prefs.toMutableList()
|
||||
next[index] = p.copy(visible = !p.visible)
|
||||
persist(next)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(14.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.015f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF1A1A22),
|
||||
focusedContainerColor = Color(0xFF262632),
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 18.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
p.name,
|
||||
color = Color.White,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
)
|
||||
Spacer(Modifier.height(3.dp))
|
||||
Text(
|
||||
if (p.visible) "Zichtbaar op Home" else "Verborgen",
|
||||
color = if (p.visible) Color(0xFF8E8E9A) else Color(0xFF555555),
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
McButton("↑") {
|
||||
if (index > 0) {
|
||||
val next = prefs.toMutableList()
|
||||
val tmp = next[index - 1]
|
||||
next[index - 1] = next[index]
|
||||
next[index] = tmp
|
||||
persist(next)
|
||||
}
|
||||
}
|
||||
McButton("↓") {
|
||||
if (index < prefs.lastIndex) {
|
||||
val next = prefs.toMutableList()
|
||||
val tmp = next[index + 1]
|
||||
next[index + 1] = next[index]
|
||||
next[index] = tmp
|
||||
persist(next)
|
||||
}
|
||||
}
|
||||
Switch(
|
||||
checked = p.visible,
|
||||
onCheckedChange = { checked ->
|
||||
val next = prefs.toMutableList()
|
||||
next[index] = p.copy(visible = checked)
|
||||
persist(next)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
McButton("↓") {
|
||||
if (index < prefs.lastIndex) {
|
||||
val next = prefs.toMutableList()
|
||||
val tmp = next[index + 1]
|
||||
next[index + 1] = next[index]
|
||||
next[index] = tmp
|
||||
persist(next)
|
||||
}
|
||||
}
|
||||
Switch(
|
||||
checked = p.visible,
|
||||
onCheckedChange = { checked ->
|
||||
val next = prefs.toMutableList()
|
||||
next[index] = p.copy(visible = checked)
|
||||
persist(next)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text(
|
||||
"Vonas Media Cluster · ${BuildConfig.VERSION_NAME}",
|
||||
color = Color(0xFF44444E),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Text(
|
||||
"Vonas Media Cluster · ${BuildConfig.VERSION_NAME}",
|
||||
color = Color(0xFF555555),
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SettingsActionRow(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
danger: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(14.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.02f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF1A1A22),
|
||||
focusedContainerColor = Color(0xFF262632),
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 22.dp, vertical = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
title,
|
||||
color = if (danger) McColors.Danger else Color.White,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(Modifier.height(3.dp))
|
||||
Text(subtitle, color = Color(0xFF8E8E9A), fontSize = 13.sp)
|
||||
}
|
||||
Text("›", color = Color(0xFF888888), fontSize = 26.sp, fontWeight = FontWeight.Light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -35,21 +36,21 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.ClickableSurfaceDefaults
|
||||
import androidx.tv.material3.ExperimentalTvMaterial3Api
|
||||
import androidx.tv.material3.Surface
|
||||
import androidx.tv.material3.Text
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import nl.vonas.mediacluster.tv.data.ApiClient
|
||||
import nl.vonas.mediacluster.tv.data.SessionStore
|
||||
import nl.vonas.mediacluster.tv.data.ViewerProfile
|
||||
import nl.vonas.mediacluster.tv.ui.theme.McColors
|
||||
|
||||
private enum class ProfilesMode { Pick, Pin, Create, DeleteConfirm }
|
||||
private enum class ProfilesMode { Pick, Pin, Create, Edit, DeleteConfirm }
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -71,8 +72,22 @@ fun ProfilesScreen(
|
|||
var createAvatar by remember { mutableStateOf("fox") }
|
||||
var createKids by remember { mutableStateOf(false) }
|
||||
var createPin by remember { mutableStateOf("") }
|
||||
var clearPin by remember { mutableStateOf(false) }
|
||||
var editingId by remember { mutableStateOf<String?>(null) }
|
||||
val firstFocus = remember { FocusRequester() }
|
||||
|
||||
fun openEdit(profile: ViewerProfile) {
|
||||
editingId = profile.id
|
||||
pending = profile
|
||||
createName = profile.name
|
||||
createAvatar = profile.avatarId
|
||||
createKids = profile.isKids
|
||||
createPin = ""
|
||||
clearPin = false
|
||||
error = null
|
||||
mode = ProfilesMode.Edit
|
||||
}
|
||||
|
||||
fun reload() {
|
||||
scope.launch {
|
||||
loading = true
|
||||
|
|
@ -127,6 +142,7 @@ fun ProfilesScreen(
|
|||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 48.dp, vertical = 36.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
|
|
@ -139,7 +155,7 @@ fun ProfilesScreen(
|
|||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
when {
|
||||
!switcherMode && canManage -> "Voeg toe of verwijder (lang drukken)"
|
||||
!switcherMode && canManage -> "OK = bewerken · lang drukken = verwijderen"
|
||||
switcherMode -> "Kies je profiel"
|
||||
else -> "Alleen het hoofdprofiel mag beheren"
|
||||
},
|
||||
|
|
@ -153,50 +169,59 @@ fun ProfilesScreen(
|
|||
if (loading) {
|
||||
Text("Profielen laden…", color = Color.White)
|
||||
} else {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 24.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
Box(
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
items(profiles, key = { it.id }) { p ->
|
||||
ProfileTile(
|
||||
profile = p,
|
||||
modifier = if (profiles.firstOrNull()?.id == p.id) {
|
||||
Modifier.focusRequester(firstFocus)
|
||||
} else Modifier,
|
||||
onClick = {
|
||||
if (p.hasPin) {
|
||||
pending = p
|
||||
pin = ""
|
||||
mode = ProfilesMode.Pin
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
profiles.forEachIndexed { index, p ->
|
||||
ProfileTile(
|
||||
profile = p,
|
||||
modifier = if (index == 0) {
|
||||
Modifier.focusRequester(firstFocus)
|
||||
} else {
|
||||
select(p)
|
||||
}
|
||||
},
|
||||
onLongDelete = if (canManage && !p.isOwner) {
|
||||
{
|
||||
pending = p
|
||||
mode = ProfilesMode.DeleteConfirm
|
||||
}
|
||||
} else null,
|
||||
)
|
||||
}
|
||||
if (canManage && profiles.size < 8) {
|
||||
item {
|
||||
Modifier
|
||||
},
|
||||
onClick = {
|
||||
if (!switcherMode && canManage) {
|
||||
openEdit(p)
|
||||
} else if (p.hasPin) {
|
||||
pending = p
|
||||
pin = ""
|
||||
mode = ProfilesMode.Pin
|
||||
} else {
|
||||
select(p)
|
||||
}
|
||||
},
|
||||
onLongDelete = if (!switcherMode && canManage && !p.isOwner) {
|
||||
{
|
||||
pending = p
|
||||
mode = ProfilesMode.DeleteConfirm
|
||||
}
|
||||
} else null,
|
||||
)
|
||||
}
|
||||
if (!switcherMode && canManage && profiles.size < 8) {
|
||||
AddProfileTile(onClick = {
|
||||
editingId = null
|
||||
pending = null
|
||||
createName = ""
|
||||
createAvatar = "fox"
|
||||
createKids = false
|
||||
createPin = ""
|
||||
clearPin = false
|
||||
mode = ProfilesMode.Create
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canManage) {
|
||||
if (!switcherMode && canManage) {
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text(
|
||||
"Houd OK lang ingedrukt op een profiel om te verwijderen",
|
||||
"Houd OK lang ingedrukt om te verwijderen",
|
||||
color = Color(0xFF666666),
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
|
|
@ -213,38 +238,29 @@ fun ProfilesScreen(
|
|||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
PinNumpad(
|
||||
value = pin,
|
||||
onValueChange = { v -> pin = v.filter { it.isDigit() }.take(4) },
|
||||
label = { androidx.compose.material3.Text("4-cijferige PIN") },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
|
||||
singleLine = true,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedTextColor = Color.White,
|
||||
unfocusedTextColor = Color.White,
|
||||
focusedBorderColor = McColors.Brand,
|
||||
unfocusedBorderColor = Color(0xFF555555),
|
||||
focusedLabelColor = McColors.Brand,
|
||||
unfocusedLabelColor = Color(0xFF888888),
|
||||
cursorColor = McColors.Brand,
|
||||
),
|
||||
onValueChange = { next ->
|
||||
pin = next
|
||||
if (next.length == 4 && target != null) select(target, next)
|
||||
},
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
ActionChip("Terug") {
|
||||
mode = ProfilesMode.Pick
|
||||
pending = null
|
||||
pin = ""
|
||||
}
|
||||
ActionChip("Ontgrendel", primary = true) {
|
||||
if (target != null) select(target, pin)
|
||||
}
|
||||
ActionChip("Terug") {
|
||||
mode = ProfilesMode.Pick
|
||||
pending = null
|
||||
pin = ""
|
||||
}
|
||||
}
|
||||
|
||||
ProfilesMode.Create -> {
|
||||
Text("Nieuw profiel", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.SemiBold)
|
||||
ProfilesMode.Create, ProfilesMode.Edit -> {
|
||||
val isEdit = mode == ProfilesMode.Edit
|
||||
Text(
|
||||
if (isEdit) "Profiel bewerken" else "Nieuw profiel",
|
||||
color = Color.White,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
OutlinedTextField(
|
||||
value = createName,
|
||||
|
|
@ -284,52 +300,86 @@ fun ProfilesScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
ActionChip(if (createKids) "Kids: aan" else "Kids: uit") {
|
||||
createKids = !createKids
|
||||
if (canManage && (!isEdit || pending?.isOwner != true)) {
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
ActionChip(if (createKids) "Kids: aan" else "Kids: uit") {
|
||||
createKids = !createKids
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
value = createPin,
|
||||
onValueChange = { v -> createPin = v.filter { it.isDigit() }.take(4) },
|
||||
label = { androidx.compose.material3.Text("PIN (optioneel)") },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
|
||||
singleLine = true,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedTextColor = Color.White,
|
||||
unfocusedTextColor = Color.White,
|
||||
focusedBorderColor = McColors.Brand,
|
||||
unfocusedBorderColor = Color(0xFF555555),
|
||||
focusedLabelColor = McColors.Brand,
|
||||
unfocusedLabelColor = Color(0xFF888888),
|
||||
cursorColor = McColors.Brand,
|
||||
),
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
ActionChip("Annuleer") { mode = ProfilesMode.Pick }
|
||||
ActionChip("Aanmaken", primary = true) {
|
||||
ActionChip("Annuleer") {
|
||||
mode = ProfilesMode.Pick
|
||||
editingId = null
|
||||
pending = null
|
||||
error = null
|
||||
}
|
||||
ActionChip(if (isEdit) "Opslaan" else "Aanmaken", primary = true) {
|
||||
scope.launch {
|
||||
error = null
|
||||
val editId = editingId
|
||||
runCatching {
|
||||
api.createProfile(
|
||||
name = createName.trim(),
|
||||
avatarId = createAvatar,
|
||||
isKids = createKids,
|
||||
pin = createPin.ifBlank { null },
|
||||
)
|
||||
}.onSuccess {
|
||||
if (editId != null) {
|
||||
api.updateProfile(
|
||||
profileId = editId,
|
||||
name = createName.trim(),
|
||||
avatarId = createAvatar,
|
||||
isKids = if (canManage && pending?.isOwner != true) createKids else null,
|
||||
pin = createPin.ifBlank { null },
|
||||
clearPin = clearPin,
|
||||
)
|
||||
} else {
|
||||
api.createProfile(
|
||||
name = createName.trim(),
|
||||
avatarId = createAvatar,
|
||||
isKids = createKids,
|
||||
pin = createPin.ifBlank { null },
|
||||
)
|
||||
}
|
||||
}.onSuccess { saved ->
|
||||
val localId = store.profileId.first()
|
||||
if (localId == saved.id || editId == localId) {
|
||||
store.setProfile(saved.id, saved.name, saved.avatarId)
|
||||
}
|
||||
mode = ProfilesMode.Pick
|
||||
editingId = null
|
||||
pending = null
|
||||
reload()
|
||||
}.onFailure {
|
||||
error = it.message ?: "Aanmaken mislukt"
|
||||
error = it.message ?: if (isEdit) "Opslaan mislukt" else "Aanmaken mislukt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(18.dp))
|
||||
Text(
|
||||
when {
|
||||
clearPin -> "PIN wordt verwijderd bij opslaan"
|
||||
isEdit && pending?.hasPin == true -> "Nieuwe PIN (optioneel)"
|
||||
else -> "PIN (optioneel)"
|
||||
},
|
||||
color = Color(0xFFCCCCCC),
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
if (isEdit && pending?.hasPin == true) {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
ActionChip(if (clearPin) "PIN verwijderen: aan" else "PIN verwijderen") {
|
||||
clearPin = !clearPin
|
||||
if (clearPin) createPin = ""
|
||||
}
|
||||
}
|
||||
if (!clearPin) {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
PinNumpad(
|
||||
value = createPin,
|
||||
onValueChange = { next ->
|
||||
createPin = next
|
||||
if (next.isNotEmpty()) clearPin = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ProfilesMode.DeleteConfirm -> {
|
||||
|
|
@ -387,8 +437,8 @@ private fun ProfileTile(
|
|||
Surface(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongDelete,
|
||||
modifier = modifier.width(140.dp),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(12.dp)),
|
||||
modifier = modifier.width(152.dp),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(14.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.08f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF1C1C24),
|
||||
|
|
@ -396,25 +446,34 @@ private fun ProfileTile(
|
|||
),
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(16.dp),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 18.dp, horizontal = 12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(88.dp)
|
||||
.size(92.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF2E2E3A)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(avatarEmoji(profile.avatarId), fontSize = 40.sp)
|
||||
Text(
|
||||
avatarEmoji(profile.avatarId),
|
||||
fontSize = 42.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Text(
|
||||
profile.name,
|
||||
color = Color.White,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
val badge = when {
|
||||
profile.isKids -> "Kids"
|
||||
|
|
@ -422,10 +481,14 @@ private fun ProfileTile(
|
|||
profile.hasPin -> "PIN"
|
||||
else -> null
|
||||
}
|
||||
if (badge != null) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(badge, color = Color(0xFF999999), fontSize = 12.sp)
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
badge ?: " ",
|
||||
color = if (badge != null) Color(0xFF999999) else Color.Transparent,
|
||||
fontSize = 12.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -435,8 +498,8 @@ private fun ProfileTile(
|
|||
private fun AddProfileTile(onClick: () -> Unit) {
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.width(140.dp),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(12.dp)),
|
||||
modifier = Modifier.width(152.dp),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(14.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.08f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF16161E),
|
||||
|
|
@ -444,20 +507,43 @@ private fun AddProfileTile(onClick: () -> Unit) {
|
|||
),
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(16.dp),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 18.dp, horizontal = 12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(88.dp)
|
||||
.size(92.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF2A2A32)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text("+", color = Color.White, fontSize = 36.sp, fontWeight = FontWeight.Light)
|
||||
Text(
|
||||
"+",
|
||||
color = Color.White,
|
||||
fontSize = 36.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Text("Toevoegen", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Text(
|
||||
"Toevoegen",
|
||||
color = Color.White,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
" ",
|
||||
color = Color.Transparent,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -485,3 +571,71 @@ private fun ActionChip(label: String, primary: Boolean = false, onClick: () -> U
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PinNumpad(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
maxLen: Int = 4,
|
||||
) {
|
||||
fun append(digit: String) {
|
||||
if (value.length < maxLen) onValueChange(value + digit)
|
||||
}
|
||||
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||
repeat(maxLen) { index ->
|
||||
Box(
|
||||
Modifier
|
||||
.size(16.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
if (index < value.length) Color.White else Color(0xFF3A3A48),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(14.dp))
|
||||
listOf(
|
||||
listOf("1", "2", "3"),
|
||||
listOf("4", "5", "6"),
|
||||
listOf("7", "8", "9"),
|
||||
listOf("⌫", "0", "C"),
|
||||
).forEach { row ->
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
row.forEach { key ->
|
||||
Surface(
|
||||
onClick = {
|
||||
when (key) {
|
||||
"⌫" -> if (value.isNotEmpty()) onValueChange(value.dropLast(1))
|
||||
"C" -> onValueChange("")
|
||||
else -> append(key)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.size(56.dp),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(10.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.1f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color(0xFF2A2A32),
|
||||
focusedContainerColor = McColors.Brand,
|
||||
contentColor = Color.White,
|
||||
focusedContentColor = Color.White,
|
||||
),
|
||||
) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
key,
|
||||
color = Color.Unspecified,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue