Fix profile PIN verification and restore TV tab underline style.
verifyPassword args were swapped after the avatar rewrite, causing argon2 to throw on PIN select.
This commit is contained in:
parent
e4aba1950b
commit
82742a1ea3
3 changed files with 76 additions and 60 deletions
|
|
@ -20,8 +20,8 @@ android {
|
|||
applicationId = "nl.vonas.mediacluster.tv"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 105
|
||||
versionName = "0.14.42"
|
||||
versionCode = 106
|
||||
versionName = "0.14.43"
|
||||
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -54,15 +52,16 @@ import androidx.compose.ui.layout.ContentScale
|
|||
/**
|
||||
* Berekende hoogte van de Home overlay-topnav (geen subtabs).
|
||||
*
|
||||
* TopNavBar overlay-row (compact, zonder underline):
|
||||
* - vertical padding: 10.dp + 10.dp
|
||||
* - tab-tekst ~21.dp → veilig 56.dp
|
||||
* TopNavBar overlay-row:
|
||||
* - vertical padding: 14.dp + 14.dp
|
||||
* - tab-inhoud: 8.dp + ~21.dp tekst + 4.dp + 3.dp underline + 8.dp ≈ 44.dp
|
||||
* - totaal ≈ 72.dp; TV Surface min. interactive ≈ 48.dp → veilig 76.dp
|
||||
*/
|
||||
object HomeChrome {
|
||||
val TopNavHeight = 56.dp
|
||||
val TopNavHeight = 76.dp
|
||||
/** Lucht tussen onderkant menu en badge. */
|
||||
val BadgeGap = 12.dp
|
||||
val BadgeTop = TopNavHeight + BadgeGap
|
||||
val BadgeTop = TopNavHeight + BadgeGap // 88.dp
|
||||
/** Horizontale schermmarge (links = rechts). */
|
||||
val EdgeInset = 22.dp
|
||||
}
|
||||
|
|
@ -119,7 +118,7 @@ fun TopNavBar(
|
|||
compact -> 0.42f
|
||||
else -> 1f
|
||||
}
|
||||
val vPad = if (compact && !overlay) 6.dp else 10.dp
|
||||
val vPad = if (compact && !overlay) 8.dp else 14.dp
|
||||
val downLink = Modifier.then(
|
||||
if (contentDownFocus != null) {
|
||||
Modifier.focusProperties { down = contentDownFocus }
|
||||
|
|
@ -178,16 +177,13 @@ fun TopNavBar(
|
|||
color = McColors.Brand,
|
||||
fontSize = if (compact && !overlay) 22.sp else 28.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
modifier = Modifier
|
||||
.height(if (compact && !overlay) 32.dp else 36.dp)
|
||||
.padding(end = 12.dp)
|
||||
.wrapContentHeight(Alignment.CenterVertically),
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
)
|
||||
textTabs.forEach { tab ->
|
||||
val active = selected == tab
|
||||
val color by animateColorAsState(
|
||||
when {
|
||||
active -> McColors.Brand
|
||||
active -> Color.White
|
||||
overlay -> Color(0xFFE8E8E8)
|
||||
else -> Color(0xFF808080)
|
||||
},
|
||||
|
|
@ -196,24 +192,21 @@ fun TopNavBar(
|
|||
val fr = tabFocus[tab]
|
||||
Surface(
|
||||
onClick = { onSelect(tab) },
|
||||
modifier = downLink
|
||||
.height(if (compact && !overlay) 32.dp else 36.dp)
|
||||
.then(if (fr != null) Modifier.focusRequester(fr) else Modifier),
|
||||
modifier = downLink.then(
|
||||
if (fr != null) Modifier.focusRequester(fr) else Modifier,
|
||||
),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.04f),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.06f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color.Transparent,
|
||||
focusedContainerColor = Color(0x33FFFFFF),
|
||||
containerColor = if (active) Color(0x33FFFFFF) else Color.Transparent,
|
||||
focusedContainerColor = Color(0x44FFFFFF),
|
||||
contentColor = color,
|
||||
// Actieve tab blijft rood, ook met focus
|
||||
focusedContentColor = if (active) McColors.Brand else Color.White,
|
||||
focusedContentColor = Color.White,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
Column(
|
||||
Modifier.padding(horizontal = 14.dp, vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
tab.label,
|
||||
|
|
@ -221,6 +214,14 @@ fun TopNavBar(
|
|||
fontSize = 16.sp,
|
||||
fontWeight = if (active) FontWeight.Bold else FontWeight.Medium,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Box(
|
||||
Modifier
|
||||
.width(if (active) 28.dp else 0.dp)
|
||||
.height(3.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(if (active) McColors.Brand else Color.Transparent),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -228,29 +229,33 @@ fun TopNavBar(
|
|||
val searchActive = selected == MainTab.Search
|
||||
Surface(
|
||||
onClick = onSearchClick,
|
||||
modifier = downLink
|
||||
.height(if (compact && !overlay) 32.dp else 36.dp)
|
||||
.focusRequester(searchFocus),
|
||||
modifier = downLink.focusRequester(searchFocus),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.04f),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.06f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color.Transparent,
|
||||
focusedContainerColor = Color(0x33FFFFFF),
|
||||
contentColor = if (searchActive) McColors.Brand else Color.White,
|
||||
focusedContentColor = if (searchActive) McColors.Brand else Color.White,
|
||||
containerColor = if (searchActive) Color(0x33FFFFFF) else Color.Transparent,
|
||||
focusedContainerColor = Color(0x44FFFFFF),
|
||||
contentColor = Color.White,
|
||||
focusedContentColor = Color.White,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
Column(
|
||||
Modifier.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = "Zoeken",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.Unspecified,
|
||||
tint = Color.White,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Box(
|
||||
Modifier
|
||||
.width(if (searchActive) 18.dp else 0.dp)
|
||||
.height(3.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(if (searchActive) McColors.Brand else Color.Transparent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -259,29 +264,33 @@ fun TopNavBar(
|
|||
val settingsActive = selected == MainTab.Settings
|
||||
Surface(
|
||||
onClick = onSettingsClick,
|
||||
modifier = downLink
|
||||
.height(if (compact && !overlay) 32.dp else 36.dp)
|
||||
.focusRequester(settingsFocus),
|
||||
modifier = downLink.focusRequester(settingsFocus),
|
||||
shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.04f),
|
||||
scale = ClickableSurfaceDefaults.scale(focusedScale = 1.06f),
|
||||
colors = ClickableSurfaceDefaults.colors(
|
||||
containerColor = Color.Transparent,
|
||||
focusedContainerColor = Color(0x33FFFFFF),
|
||||
contentColor = if (settingsActive) McColors.Brand else Color.White,
|
||||
focusedContentColor = if (settingsActive) McColors.Brand else Color.White,
|
||||
containerColor = if (settingsActive) Color(0x33FFFFFF) else Color.Transparent,
|
||||
focusedContainerColor = Color(0x44FFFFFF),
|
||||
contentColor = Color.White,
|
||||
focusedContentColor = Color.White,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
Column(
|
||||
Modifier.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Settings,
|
||||
contentDescription = "Instellingen",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.Unspecified,
|
||||
tint = Color.White,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Box(
|
||||
Modifier
|
||||
.width(if (settingsActive) 18.dp else 0.dp)
|
||||
.height(3.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(if (settingsActive) McColors.Brand else Color.Transparent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,14 +388,21 @@ export class ViewerProfileService {
|
|||
let bucket = pinAttempts.get(key);
|
||||
if (!bucket || bucket.resetAt < now) {
|
||||
bucket = { count: 0, resetAt: now + PIN_ATTEMPT_WINDOW_MS };
|
||||
pinAttempts.set(key, bucket);
|
||||
}
|
||||
if (bucket.count >= PIN_MAX_ATTEMPTS) {
|
||||
throw new AppError("RATE_LIMIT", "Te veel PIN-pogingen, probeer later", 429);
|
||||
throw new AppError(
|
||||
"RATE_LIMITED",
|
||||
"Te veel PIN-pogingen. Probeer later opnieuw.",
|
||||
429
|
||||
);
|
||||
}
|
||||
const ok = pin ? await verifyPassword(String(pin), profile.pinHash) : false;
|
||||
|
||||
const ok = pin
|
||||
? await verifyPassword(profile.pinHash, String(pin).replace(/\D/g, ""))
|
||||
: false;
|
||||
if (!ok) {
|
||||
bucket.count += 1;
|
||||
pinAttempts.set(key, bucket);
|
||||
throw new AppError("FORBIDDEN", "Onjuiste PIN", 403);
|
||||
}
|
||||
pinAttempts.delete(key);
|
||||
|
|
@ -403,7 +410,7 @@ export class ViewerProfileService {
|
|||
|
||||
await prisma.viewerDevice.update({
|
||||
where: { id: deviceId },
|
||||
data: { activeProfileId: profileId, lastSeenAt: new Date() },
|
||||
data: { activeProfileId: profile.id, lastSeenAt: new Date() },
|
||||
});
|
||||
return { profile: publicProfile(profile) };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue