mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-22 14:56:38 +01:00
chore: updated usage of tv scaling, alert text fix (#1587)
Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
1
app.json
1
app.json
@@ -136,6 +136,7 @@
|
||||
"expo-web-browser",
|
||||
["./plugins/with-runtime-framework-headers.js"],
|
||||
["./plugins/withChangeNativeAndroidTextToWhite.js"],
|
||||
["./plugins/withAndroidAlertColors.js"],
|
||||
["./plugins/withAndroidManifest.js"],
|
||||
["./plugins/withTrustLocalCerts.js"],
|
||||
["./plugins/withGradleProperties.js"],
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useHaptic } from "@/hooks/useHaptic";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { Loader } from "./Loader";
|
||||
|
||||
const getColorClasses = (
|
||||
@@ -135,16 +136,26 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
|
||||
shadowColor: "#ffffff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.5 : 0,
|
||||
shadowRadius: focused ? 10 : 0,
|
||||
shadowRadius: focused ? scaleSize(10) : 0,
|
||||
elevation: focused ? 12 : 0, // Android glow
|
||||
}}
|
||||
>
|
||||
<View
|
||||
className={`rounded-2xl py-5 items-center justify-center
|
||||
${colorClasses}
|
||||
${className}`}
|
||||
style={{
|
||||
borderRadius: scaleSize(16),
|
||||
paddingVertical: scaleSize(14),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
className={`${colorClasses} ${className}`}
|
||||
>
|
||||
<Text className={`${textColorClass} text-xl font-bold`}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: scaleSize(20),
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
className={textColorClass}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Animated, Easing, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import type { SavedServerAccount } from "@/utils/secureCredentials";
|
||||
|
||||
interface TVAccountCardProps {
|
||||
@@ -97,9 +98,9 @@ export const TVAccountCard: React.FC<TVAccountCardProps> = ({
|
||||
backgroundColor: isFocused ? "#2a2a2a" : "#262626",
|
||||
borderWidth: 2,
|
||||
borderColor: isFocused ? "#FFFFFF" : "transparent",
|
||||
borderRadius: 16,
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 20,
|
||||
borderRadius: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
paddingVertical: scaleSize(20),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
@@ -107,23 +108,23 @@ export const TVAccountCard: React.FC<TVAccountCardProps> = ({
|
||||
{/* Avatar */}
|
||||
<View
|
||||
style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
width: scaleSize(56),
|
||||
height: scaleSize(56),
|
||||
backgroundColor: "#404040",
|
||||
borderRadius: 28,
|
||||
borderRadius: scaleSize(28),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginRight: 20,
|
||||
marginRight: scaleSize(20),
|
||||
}}
|
||||
>
|
||||
<Ionicons name='person' size={28} color='white' />
|
||||
<Ionicons name='person' size={scaleSize(28)} color='white' />
|
||||
</View>
|
||||
|
||||
{/* Account Info */}
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 22,
|
||||
fontSize: scaleSize(22),
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
@@ -132,9 +133,9 @@ export const TVAccountCard: React.FC<TVAccountCardProps> = ({
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: "#9CA3AF",
|
||||
marginTop: 4,
|
||||
marginTop: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
{getSecurityText()}
|
||||
@@ -142,7 +143,11 @@ export const TVAccountCard: React.FC<TVAccountCardProps> = ({
|
||||
</View>
|
||||
|
||||
{/* Security Icon */}
|
||||
<Ionicons name={getSecurityIcon()} size={24} color='#fff' />
|
||||
<Ionicons
|
||||
name={getSecurityIcon()}
|
||||
size={scaleSize(24)}
|
||||
color='#fff'
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVAddIconProps {
|
||||
label: string;
|
||||
@@ -33,24 +34,24 @@ export const TVAddIcon = React.forwardRef<View, TVAddIconProps>(
|
||||
animatedStyle,
|
||||
{
|
||||
alignItems: "center",
|
||||
width: 160,
|
||||
width: scaleSize(160),
|
||||
shadowColor: "#fff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.5 : 0,
|
||||
shadowRadius: focused ? 16 : 0,
|
||||
shadowRadius: focused ? scaleSize(16) : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 140,
|
||||
height: 140,
|
||||
borderRadius: 70,
|
||||
width: scaleSize(140),
|
||||
height: scaleSize(140),
|
||||
borderRadius: scaleSize(70),
|
||||
backgroundColor: focused
|
||||
? "rgba(255,255,255,0.15)"
|
||||
: "rgba(255,255,255,0.05)",
|
||||
marginBottom: 14,
|
||||
borderWidth: 2,
|
||||
marginBottom: scaleSize(14),
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: focused ? "#fff" : "rgba(255,255,255,0.3)",
|
||||
borderStyle: "dashed",
|
||||
justifyContent: "center",
|
||||
@@ -59,7 +60,7 @@ export const TVAddIcon = React.forwardRef<View, TVAddIconProps>(
|
||||
>
|
||||
<Ionicons
|
||||
name='add'
|
||||
size={56}
|
||||
size={scaleSize(56)}
|
||||
color={focused ? "#fff" : "rgba(255,255,255,0.5)"}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Button } from "@/components/Button";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { TVInput } from "./TVInput";
|
||||
|
||||
interface TVAddServerFormProps {
|
||||
@@ -48,7 +49,7 @@ export const TVAddServerForm: React.FC<TVAddServerFormProps> = ({
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: 60,
|
||||
paddingVertical: scaleSize(60),
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
@@ -56,7 +57,7 @@ export const TVAddServerForm: React.FC<TVAddServerFormProps> = ({
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: 800,
|
||||
paddingHorizontal: 60,
|
||||
paddingHorizontal: scaleSize(60),
|
||||
}}
|
||||
>
|
||||
{/* Title */}
|
||||
@@ -66,15 +67,20 @@ export const TVAddServerForm: React.FC<TVAddServerFormProps> = ({
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
textAlign: "left",
|
||||
marginBottom: 24,
|
||||
paddingHorizontal: 8,
|
||||
marginBottom: scaleSize(24),
|
||||
paddingHorizontal: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
{t("server.enter_url_to_jellyfin_server")}
|
||||
</Text>
|
||||
|
||||
{/* Server URL Input */}
|
||||
<View style={{ marginBottom: 24, paddingHorizontal: 8 }}>
|
||||
<View
|
||||
style={{
|
||||
marginBottom: scaleSize(24),
|
||||
paddingHorizontal: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
<TVInput
|
||||
placeholder={t("server.server_url_placeholder")}
|
||||
value={serverURL}
|
||||
@@ -89,7 +95,7 @@ export const TVAddServerForm: React.FC<TVAddServerFormProps> = ({
|
||||
</View>
|
||||
|
||||
{/* Connect Button */}
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<View style={{ marginBottom: scaleSize(24) }}>
|
||||
<Button
|
||||
onPress={handleConnect}
|
||||
loading={loading}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Button } from "@/components/Button";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { TVInput } from "./TVInput";
|
||||
import { TVSaveAccountToggle } from "./TVSaveAccountToggle";
|
||||
|
||||
@@ -61,7 +62,7 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: 60,
|
||||
paddingVertical: scaleSize(60),
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
@@ -69,7 +70,7 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: 800,
|
||||
paddingHorizontal: 60,
|
||||
paddingHorizontal: scaleSize(60),
|
||||
}}
|
||||
>
|
||||
{/* Title */}
|
||||
@@ -78,7 +79,7 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
fontSize: typography.title,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
{serverName ? (
|
||||
@@ -94,14 +95,19 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginBottom: 40,
|
||||
marginBottom: scaleSize(40),
|
||||
}}
|
||||
>
|
||||
{serverAddress}
|
||||
</Text>
|
||||
|
||||
{/* Username Input */}
|
||||
<View style={{ marginBottom: 24, paddingHorizontal: 8 }}>
|
||||
<View
|
||||
style={{
|
||||
marginBottom: scaleSize(24),
|
||||
paddingHorizontal: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
<TVInput
|
||||
placeholder={t("login.username_placeholder")}
|
||||
value={credentials.username}
|
||||
@@ -118,7 +124,12 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
</View>
|
||||
|
||||
{/* Password Input */}
|
||||
<View style={{ marginBottom: 32, paddingHorizontal: 8 }}>
|
||||
<View
|
||||
style={{
|
||||
marginBottom: scaleSize(32),
|
||||
paddingHorizontal: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
<TVInput
|
||||
placeholder={t("login.password_placeholder")}
|
||||
value={credentials.password}
|
||||
@@ -134,7 +145,12 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
</View>
|
||||
|
||||
{/* Save Account Toggle */}
|
||||
<View style={{ marginBottom: 40, paddingHorizontal: 8 }}>
|
||||
<View
|
||||
style={{
|
||||
marginBottom: scaleSize(40),
|
||||
paddingHorizontal: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
<TVSaveAccountToggle
|
||||
value={saveAccount}
|
||||
onValueChange={setSaveAccount}
|
||||
@@ -144,7 +160,7 @@ export const TVAddUserForm: React.FC<TVAddUserFormProps> = ({
|
||||
</View>
|
||||
|
||||
{/* Login Button */}
|
||||
<View style={{ marginBottom: 16 }}>
|
||||
<View style={{ marginBottom: scaleSize(16) }}>
|
||||
<Button
|
||||
onPress={handleLogin}
|
||||
loading={loading}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVBackIconProps {
|
||||
label: string;
|
||||
@@ -33,24 +34,24 @@ export const TVBackIcon = React.forwardRef<View, TVBackIconProps>(
|
||||
animatedStyle,
|
||||
{
|
||||
alignItems: "center",
|
||||
width: 160,
|
||||
width: scaleSize(160),
|
||||
shadowColor: "#fff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.5 : 0,
|
||||
shadowRadius: focused ? 16 : 0,
|
||||
shadowRadius: focused ? scaleSize(16) : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 140,
|
||||
height: 140,
|
||||
borderRadius: 70,
|
||||
width: scaleSize(140),
|
||||
height: scaleSize(140),
|
||||
borderRadius: scaleSize(70),
|
||||
backgroundColor: focused
|
||||
? "rgba(255,255,255,0.15)"
|
||||
: "rgba(255,255,255,0.05)",
|
||||
marginBottom: 14,
|
||||
borderWidth: 2,
|
||||
marginBottom: scaleSize(14),
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: focused ? "#fff" : "rgba(255,255,255,0.3)",
|
||||
borderStyle: "dashed",
|
||||
justifyContent: "center",
|
||||
@@ -59,7 +60,7 @@ export const TVBackIcon = React.forwardRef<View, TVBackIconProps>(
|
||||
>
|
||||
<Ionicons
|
||||
name='arrow-back'
|
||||
size={56}
|
||||
size={scaleSize(56)}
|
||||
color={focused ? "#fff" : "rgba(255,255,255,0.5)"}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TextInput,
|
||||
type TextInputProps,
|
||||
} from "react-native";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
interface TVInputProps extends TextInputProps {
|
||||
label?: string;
|
||||
@@ -58,7 +59,7 @@ export const TVInput: React.FC<TVInputProps> = ({
|
||||
<Animated.View
|
||||
style={{
|
||||
transform: [{ scale }],
|
||||
borderRadius: 12,
|
||||
borderRadius: scaleSize(12),
|
||||
backgroundColor: isFocused
|
||||
? "rgba(255,255,255,0.15)"
|
||||
: "rgba(255,255,255,0.08)",
|
||||
@@ -73,10 +74,10 @@ export const TVInput: React.FC<TVInputProps> = ({
|
||||
allowFontScaling={false}
|
||||
style={[
|
||||
{
|
||||
height: 64,
|
||||
fontSize: 22,
|
||||
height: scaleSize(64),
|
||||
fontSize: scaleSize(22),
|
||||
color: "#FFFFFF",
|
||||
paddingHorizontal: 20,
|
||||
paddingHorizontal: scaleSize(20),
|
||||
},
|
||||
style,
|
||||
]}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type PairingCredentials,
|
||||
startPairingListener,
|
||||
} from "@/utils/pairingService";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import {
|
||||
type AccountSecurityType,
|
||||
getPreviousServers,
|
||||
@@ -700,17 +701,17 @@ export const TVLogin: React.FC = () => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 24,
|
||||
fontSize: scaleSize(24),
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 12,
|
||||
marginBottom: scaleSize(12),
|
||||
}}
|
||||
>
|
||||
{t("pairing.logging_in")}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: "#9CA3AF",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { verifyAccountPIN } from "@/utils/secureCredentials";
|
||||
|
||||
interface TVPINEntryModalProps {
|
||||
@@ -130,15 +131,15 @@ const ForgotPINLink: React.FC<{
|
||||
<Animated.View
|
||||
style={{
|
||||
transform: [{ scale }],
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: scaleSize(16),
|
||||
paddingVertical: scaleSize(10),
|
||||
borderRadius: scaleSize(8),
|
||||
backgroundColor: focused ? "rgba(255,255,255,0.15)" : "transparent",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.5)",
|
||||
}}
|
||||
>
|
||||
@@ -417,36 +418,36 @@ const styles = StyleSheet.create({
|
||||
maxWidth: 400,
|
||||
},
|
||||
blurContainer: {
|
||||
borderRadius: 24,
|
||||
borderRadius: scaleSize(24),
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
padding: 40,
|
||||
padding: scaleSize(40),
|
||||
alignItems: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
fontSize: scaleSize(28),
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
textAlign: "center",
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 18,
|
||||
fontSize: scaleSize(18),
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 32,
|
||||
marginBottom: scaleSize(32),
|
||||
textAlign: "center",
|
||||
},
|
||||
pinDotsContainer: {
|
||||
flexDirection: "row",
|
||||
gap: 16,
|
||||
marginBottom: 32,
|
||||
gap: scaleSize(16),
|
||||
marginBottom: scaleSize(32),
|
||||
},
|
||||
pinDot: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
width: scaleSize(20),
|
||||
height: scaleSize(20),
|
||||
borderRadius: scaleSize(10),
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: "rgba(255,255,255,0.4)",
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
@@ -459,26 +460,26 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: "#ef4444",
|
||||
},
|
||||
numberPad: {
|
||||
gap: 12,
|
||||
marginBottom: 24,
|
||||
gap: scaleSize(12),
|
||||
marginBottom: scaleSize(24),
|
||||
},
|
||||
numberRow: {
|
||||
flexDirection: "row",
|
||||
gap: 12,
|
||||
gap: scaleSize(12),
|
||||
},
|
||||
numberButton: {
|
||||
width: 72,
|
||||
height: 72,
|
||||
borderRadius: 36,
|
||||
width: scaleSize(72),
|
||||
height: scaleSize(72),
|
||||
borderRadius: scaleSize(36),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
numberButtonPlaceholder: {
|
||||
width: 72,
|
||||
height: 72,
|
||||
width: scaleSize(72),
|
||||
height: scaleSize(72),
|
||||
},
|
||||
numberText: {
|
||||
fontSize: 28,
|
||||
fontSize: scaleSize(28),
|
||||
fontWeight: "600",
|
||||
},
|
||||
forgotContainer: {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
interface TVPasswordEntryModalProps {
|
||||
visible: boolean;
|
||||
@@ -51,14 +52,14 @@ const TVSubmitButton: React.FC<{
|
||||
: isDisabled
|
||||
? "#4a4a4a"
|
||||
: "rgba(255,255,255,0.15)",
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: scaleSize(24),
|
||||
paddingVertical: scaleSize(14),
|
||||
borderRadius: scaleSize(10),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 8,
|
||||
minWidth: 120,
|
||||
gap: scaleSize(8),
|
||||
minWidth: scaleSize(120),
|
||||
opacity: isDisabled ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
@@ -69,12 +70,12 @@ const TVSubmitButton: React.FC<{
|
||||
<>
|
||||
<Ionicons
|
||||
name='log-in-outline'
|
||||
size={20}
|
||||
size={scaleSize(20)}
|
||||
color={focused ? "#000" : "#fff"}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
@@ -121,11 +122,11 @@ const TVPasswordInput: React.FC<{
|
||||
animatedStyle,
|
||||
{
|
||||
backgroundColor: "#1F2937",
|
||||
borderRadius: 12,
|
||||
borderWidth: 2,
|
||||
borderRadius: scaleSize(12),
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: focused ? "#fff" : "#374151",
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: scaleSize(16),
|
||||
paddingVertical: scaleSize(14),
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -140,7 +141,7 @@ const TVPasswordInput: React.FC<{
|
||||
autoCorrect={false}
|
||||
style={{
|
||||
color: "#fff",
|
||||
fontSize: 18,
|
||||
fontSize: scaleSize(18),
|
||||
}}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
returnKeyType='done'
|
||||
@@ -299,45 +300,45 @@ const styles = StyleSheet.create({
|
||||
width: "100%",
|
||||
},
|
||||
blurContainer: {
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
borderTopLeftRadius: scaleSize(24),
|
||||
borderTopRightRadius: scaleSize(24),
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 50,
|
||||
paddingTop: scaleSize(24),
|
||||
paddingBottom: scaleSize(50),
|
||||
overflow: "visible",
|
||||
},
|
||||
header: {
|
||||
paddingHorizontal: 48,
|
||||
marginBottom: 24,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
marginBottom: scaleSize(24),
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
fontSize: scaleSize(28),
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
},
|
||||
inputContainer: {
|
||||
paddingHorizontal: 48,
|
||||
marginBottom: 20,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
marginBottom: scaleSize(20),
|
||||
},
|
||||
inputLabel: {
|
||||
fontSize: 14,
|
||||
fontSize: scaleSize(14),
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
},
|
||||
errorText: {
|
||||
color: "#ef4444",
|
||||
fontSize: 14,
|
||||
marginTop: 8,
|
||||
fontSize: scaleSize(14),
|
||||
marginTop: scaleSize(8),
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingHorizontal: 48,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,7 +57,7 @@ export const TVQRCodeDisplay: React.FC<TVQRCodeDisplayProps> = ({
|
||||
alignItems: "center",
|
||||
paddingVertical: sectionPadding,
|
||||
paddingHorizontal: cardPadding,
|
||||
borderRadius: 16,
|
||||
borderRadius: scaleSize(16),
|
||||
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
||||
}}
|
||||
>
|
||||
@@ -66,7 +66,7 @@ export const TVQRCodeDisplay: React.FC<TVQRCodeDisplayProps> = ({
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
{t("pairing.waiting_for_phone")}
|
||||
@@ -75,7 +75,7 @@ export const TVQRCodeDisplay: React.FC<TVQRCodeDisplayProps> = ({
|
||||
<View
|
||||
style={{
|
||||
padding: cardPadding,
|
||||
borderRadius: 12,
|
||||
borderRadius: scaleSize(12),
|
||||
backgroundColor: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVPinInput, type TVPinInputRef } from "@/components/inputs/TVPinInput";
|
||||
import { TVOptionCard, useTVFocusAnimation } from "@/components/tv";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import type { AccountSecurityType } from "@/utils/secureCredentials";
|
||||
|
||||
interface TVSaveAccountModalProps {
|
||||
@@ -79,24 +80,24 @@ const TVSaveButton: React.FC<{
|
||||
: disabled
|
||||
? "#4a4a4a"
|
||||
: "rgba(255,255,255,0.15)",
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: scaleSize(24),
|
||||
paddingVertical: scaleSize(14),
|
||||
borderRadius: scaleSize(10),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
gap: scaleSize(8),
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name='checkmark'
|
||||
size={20}
|
||||
size={scaleSize(20)}
|
||||
color={focused ? "#000" : "#fff"}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
@@ -129,23 +130,23 @@ const TVBackButton: React.FC<{
|
||||
animatedStyle,
|
||||
{
|
||||
backgroundColor: focused ? "#fff" : "rgba(255,255,255,0.15)",
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: scaleSize(20),
|
||||
paddingVertical: scaleSize(12),
|
||||
borderRadius: scaleSize(10),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
gap: scaleSize(8),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name='chevron-back'
|
||||
size={20}
|
||||
size={scaleSize(20)}
|
||||
color={focused ? "#000" : "rgba(255,255,255,0.8)"}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: focused ? "#000" : "rgba(255,255,255,0.8)",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
@@ -378,35 +379,35 @@ const styles = StyleSheet.create({
|
||||
width: "100%",
|
||||
},
|
||||
blurContainer: {
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
borderTopLeftRadius: scaleSize(24),
|
||||
borderTopRightRadius: scaleSize(24),
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 50,
|
||||
paddingTop: scaleSize(24),
|
||||
paddingBottom: scaleSize(50),
|
||||
overflow: "visible",
|
||||
},
|
||||
header: {
|
||||
paddingHorizontal: 48,
|
||||
marginBottom: 20,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
marginBottom: scaleSize(20),
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
fontSize: scaleSize(28),
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontSize: scaleSize(16),
|
||||
fontWeight: "500",
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 16,
|
||||
paddingHorizontal: 48,
|
||||
marginBottom: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(48),
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
@@ -414,26 +415,26 @@ const styles = StyleSheet.create({
|
||||
overflow: "visible",
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 48,
|
||||
paddingVertical: 10,
|
||||
gap: 12,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
paddingVertical: scaleSize(10),
|
||||
gap: scaleSize(12),
|
||||
},
|
||||
buttonRow: {
|
||||
marginTop: 20,
|
||||
paddingHorizontal: 48,
|
||||
marginTop: scaleSize(20),
|
||||
paddingHorizontal: scaleSize(48),
|
||||
flexDirection: "row",
|
||||
gap: 16,
|
||||
gap: scaleSize(16),
|
||||
alignItems: "center",
|
||||
},
|
||||
pinContainer: {
|
||||
paddingHorizontal: 48,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
alignItems: "center",
|
||||
marginBottom: 10,
|
||||
marginBottom: scaleSize(10),
|
||||
},
|
||||
errorText: {
|
||||
color: "#ef4444",
|
||||
fontSize: 14,
|
||||
marginTop: 12,
|
||||
fontSize: scaleSize(14),
|
||||
marginTop: scaleSize(12),
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Animated, Easing, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
interface TVSaveAccountToggleProps {
|
||||
value: boolean;
|
||||
@@ -74,9 +75,9 @@ export const TVSaveAccountToggle: React.FC<TVSaveAccountToggleProps> = ({
|
||||
backgroundColor: isFocused ? "#2a2a2a" : "#1a1a1a",
|
||||
borderWidth: 2,
|
||||
borderColor: isFocused ? "#FFFFFF" : "transparent",
|
||||
borderRadius: 16,
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 20,
|
||||
borderRadius: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
paddingVertical: scaleSize(20),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -84,7 +85,7 @@ export const TVSaveAccountToggle: React.FC<TVSaveAccountToggleProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontSize: scaleSize(20),
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
@@ -93,19 +94,19 @@ export const TVSaveAccountToggle: React.FC<TVSaveAccountToggleProps> = ({
|
||||
<View
|
||||
pointerEvents='none'
|
||||
style={{
|
||||
width: 60,
|
||||
height: 34,
|
||||
borderRadius: 17,
|
||||
width: scaleSize(60),
|
||||
height: scaleSize(34),
|
||||
borderRadius: scaleSize(17),
|
||||
backgroundColor: value ? "#fff" : "#3f3f46",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 3,
|
||||
paddingHorizontal: scaleSize(3),
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
width: scaleSize(28),
|
||||
height: scaleSize(28),
|
||||
borderRadius: scaleSize(14),
|
||||
backgroundColor: value ? "#000" : "#fff",
|
||||
alignSelf: value ? "flex-end" : "flex-start",
|
||||
}}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
// Sci-fi gradient color pairs (from, to) - cyberpunk/neon vibes
|
||||
const SERVER_GRADIENTS: [string, string][] = [
|
||||
@@ -131,22 +132,22 @@ export const TVServerIcon = React.forwardRef<View, TVServerIconProps>(
|
||||
animatedStyle,
|
||||
{
|
||||
alignItems: "center",
|
||||
width: 160,
|
||||
width: scaleSize(160),
|
||||
shadowColor: gradientStart,
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.7 : 0,
|
||||
shadowRadius: focused ? 24 : 0,
|
||||
shadowRadius: focused ? scaleSize(24) : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 140,
|
||||
height: 140,
|
||||
borderRadius: 70,
|
||||
width: scaleSize(140),
|
||||
height: scaleSize(140),
|
||||
borderRadius: scaleSize(70),
|
||||
overflow: "hidden",
|
||||
marginBottom: 14,
|
||||
borderWidth: focused ? 3 : 0,
|
||||
marginBottom: scaleSize(14),
|
||||
borderWidth: focused ? scaleSize(3) : 0,
|
||||
borderColor: "#fff",
|
||||
}}
|
||||
>
|
||||
@@ -164,7 +165,7 @@ export const TVServerIcon = React.forwardRef<View, TVServerIconProps>(
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 48,
|
||||
fontSize: scaleSize(48),
|
||||
fontWeight: "bold",
|
||||
color: "#fff",
|
||||
textShadowColor: "rgba(0,0,0,0.3)",
|
||||
@@ -183,9 +184,9 @@ export const TVServerIcon = React.forwardRef<View, TVServerIconProps>(
|
||||
fontWeight: "600",
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
textAlign: "center",
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
}}
|
||||
numberOfLines={2}
|
||||
numberOfLines={3}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
@@ -199,7 +200,7 @@ export const TVServerIcon = React.forwardRef<View, TVServerIconProps>(
|
||||
: "rgba(255,255,255,0.5)",
|
||||
textAlign: "center",
|
||||
}}
|
||||
numberOfLines={1}
|
||||
numberOfLines={3}
|
||||
>
|
||||
{address.replace(/^https?:\/\//, "")}
|
||||
</Text>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Alert, ScrollView, View } from "react-native";
|
||||
import { useMMKVString } from "react-native-mmkv";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import type { SavedServer } from "@/utils/secureCredentials";
|
||||
import { TVAddIcon } from "./TVAddIcon";
|
||||
import { TVServerIcon } from "./TVServerIcon";
|
||||
@@ -56,7 +57,7 @@ export const TVServerSelectionScreen: React.FC<
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: 60,
|
||||
paddingVertical: scaleSize(60),
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
@@ -64,14 +65,14 @@ export const TVServerSelectionScreen: React.FC<
|
||||
style={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 60,
|
||||
paddingHorizontal: scaleSize(60),
|
||||
}}
|
||||
>
|
||||
{/* Logo */}
|
||||
<View style={{ alignItems: "center", marginBottom: 16 }}>
|
||||
<View style={{ alignItems: "center", marginBottom: scaleSize(16) }}>
|
||||
<Image
|
||||
source={require("@/assets/images/icon-ios-plain.png")}
|
||||
style={{ width: 150, height: 150 }}
|
||||
style={{ width: scaleSize(150), height: scaleSize(150) }}
|
||||
contentFit='contain'
|
||||
/>
|
||||
</View>
|
||||
@@ -83,7 +84,7 @@ export const TVServerSelectionScreen: React.FC<
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
textAlign: "center",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
Streamyfin
|
||||
@@ -93,7 +94,7 @@ export const TVServerSelectionScreen: React.FC<
|
||||
fontSize: typography.body,
|
||||
color: "#9CA3AF",
|
||||
textAlign: "center",
|
||||
marginBottom: 48,
|
||||
marginBottom: scaleSize(48),
|
||||
}}
|
||||
>
|
||||
{hasServers
|
||||
@@ -106,8 +107,8 @@ export const TVServerSelectionScreen: React.FC<
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 20,
|
||||
gap: 24,
|
||||
paddingHorizontal: scaleSize(20),
|
||||
gap: scaleSize(24),
|
||||
}}
|
||||
style={{ overflow: "visible" }}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { getUserImageUrl } from "@/utils/jellyfin/image/getUserImageUrl";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import type { AccountSecurityType } from "@/utils/secureCredentials";
|
||||
|
||||
export interface TVUserIconProps {
|
||||
@@ -76,27 +77,27 @@ export const TVUserIcon = React.forwardRef<View, TVUserIconProps>(
|
||||
animatedStyle,
|
||||
{
|
||||
alignItems: "center",
|
||||
width: 160,
|
||||
width: scaleSize(160),
|
||||
overflow: "visible",
|
||||
shadowColor: "#fff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.5 : 0,
|
||||
shadowRadius: focused ? 16 : 0,
|
||||
shadowRadius: focused ? scaleSize(16) : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={{ position: "relative" }}>
|
||||
<View
|
||||
style={{
|
||||
width: 140,
|
||||
height: 140,
|
||||
borderRadius: 70,
|
||||
width: scaleSize(140),
|
||||
height: scaleSize(140),
|
||||
borderRadius: scaleSize(70),
|
||||
overflow: "hidden",
|
||||
backgroundColor: focused
|
||||
? "rgba(255,255,255,0.2)"
|
||||
: "rgba(255,255,255,0.1)",
|
||||
marginBottom: 14,
|
||||
borderWidth: focused ? 3 : 0,
|
||||
marginBottom: scaleSize(14),
|
||||
borderWidth: focused ? scaleSize(3) : 0,
|
||||
borderColor: "#fff",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
@@ -105,14 +106,14 @@ export const TVUserIcon = React.forwardRef<View, TVUserIconProps>(
|
||||
{imageUrl ? (
|
||||
<Image
|
||||
source={{ uri: imageUrl }}
|
||||
style={{ width: 140, height: 140 }}
|
||||
style={{ width: scaleSize(140), height: scaleSize(140) }}
|
||||
contentFit='cover'
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
) : (
|
||||
<Ionicons
|
||||
name='person'
|
||||
size={56}
|
||||
size={scaleSize(56)}
|
||||
color={
|
||||
focused ? "rgba(255,255,255,0.6)" : "rgba(255,255,255,0.4)"
|
||||
}
|
||||
@@ -125,21 +126,25 @@ export const TVUserIcon = React.forwardRef<View, TVUserIconProps>(
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
bottom: scaleSize(10),
|
||||
right: scaleSize(10),
|
||||
width: scaleSize(32),
|
||||
height: scaleSize(32),
|
||||
borderRadius: scaleSize(16),
|
||||
backgroundColor: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOffset: { width: 0, height: scaleSize(2) },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4,
|
||||
shadowRadius: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
<Ionicons name={getSecurityIcon()} size={16} color='#000' />
|
||||
<Ionicons
|
||||
name={getSecurityIcon()}
|
||||
size={scaleSize(16)}
|
||||
color='#000'
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ScrollView, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import type {
|
||||
SavedServer,
|
||||
SavedServerAccount,
|
||||
@@ -47,7 +48,7 @@ export const TVUserSelectionScreen: React.FC<TVUserSelectionScreenProps> = ({
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: 60,
|
||||
paddingVertical: scaleSize(60),
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
@@ -55,18 +56,18 @@ export const TVUserSelectionScreen: React.FC<TVUserSelectionScreenProps> = ({
|
||||
style={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 60,
|
||||
paddingHorizontal: scaleSize(60),
|
||||
}}
|
||||
>
|
||||
{/* Server Info Header */}
|
||||
<View style={{ marginBottom: 48, alignItems: "center" }}>
|
||||
<View style={{ marginBottom: scaleSize(48), alignItems: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.title,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
textAlign: "center",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
{server.name || server.address}
|
||||
@@ -87,7 +88,7 @@ export const TVUserSelectionScreen: React.FC<TVUserSelectionScreenProps> = ({
|
||||
fontSize: typography.body,
|
||||
color: "#6B7280",
|
||||
textAlign: "center",
|
||||
marginTop: 16,
|
||||
marginTop: scaleSize(16),
|
||||
}}
|
||||
>
|
||||
{hasAccounts
|
||||
@@ -101,8 +102,8 @@ export const TVUserSelectionScreen: React.FC<TVUserSelectionScreenProps> = ({
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 20,
|
||||
gap: 24,
|
||||
paddingHorizontal: scaleSize(20),
|
||||
gap: scaleSize(24),
|
||||
}}
|
||||
style={{ overflow: "visible" }}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVActorCardProps {
|
||||
@@ -40,23 +41,23 @@ export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
animatedStyle,
|
||||
{
|
||||
alignItems: "center",
|
||||
width: 160,
|
||||
width: scaleSize(160),
|
||||
shadowColor: "#fff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.5 : 0,
|
||||
shadowRadius: focused ? 16 : 0,
|
||||
shadowRadius: focused ? scaleSize(16) : 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 140,
|
||||
height: 140,
|
||||
borderRadius: 70,
|
||||
width: scaleSize(140),
|
||||
height: scaleSize(140),
|
||||
borderRadius: scaleSize(70),
|
||||
overflow: "hidden",
|
||||
backgroundColor: "rgba(255,255,255,0.1)",
|
||||
marginBottom: 14,
|
||||
borderWidth: 2,
|
||||
marginBottom: scaleSize(14),
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: focused ? "#FFFFFF" : "transparent",
|
||||
}}
|
||||
>
|
||||
@@ -76,7 +77,7 @@ export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
>
|
||||
<Ionicons
|
||||
name='person'
|
||||
size={56}
|
||||
size={scaleSize(56)}
|
||||
color='rgba(255,255,255,0.4)'
|
||||
/>
|
||||
</View>
|
||||
@@ -89,9 +90,9 @@ export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
fontWeight: "600",
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
textAlign: "center",
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
}}
|
||||
numberOfLines={1}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{person.Name}
|
||||
</Text>
|
||||
@@ -105,7 +106,7 @@ export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
: "rgba(255,255,255,0.5)",
|
||||
textAlign: "center",
|
||||
}}
|
||||
numberOfLines={1}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{person.Role}
|
||||
</Text>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View, type ViewStyle } from "react-native";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVButtonProps {
|
||||
@@ -98,13 +99,13 @@ export const TVButton: React.FC<TVButtonProps> = ({
|
||||
backgroundColor: buttonStyles.backgroundColor,
|
||||
borderWidth: buttonStyles.borderWidth,
|
||||
borderColor: buttonStyles.borderColor,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 18,
|
||||
paddingHorizontal: square ? 18 : 32,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(18),
|
||||
paddingHorizontal: square ? scaleSize(18) : scaleSize(32),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minWidth: square ? undefined : 180,
|
||||
minWidth: square ? undefined : scaleSize(180),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVCancelButtonProps {
|
||||
@@ -33,18 +34,18 @@ export const TVCancelButton: React.FC<TVCancelButtonProps> = ({
|
||||
animatedStyle,
|
||||
{
|
||||
backgroundColor: focused ? "#fff" : "rgba(255,255,255,0.15)",
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: scaleSize(20),
|
||||
paddingVertical: scaleSize(12),
|
||||
borderRadius: scaleSize(10),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
gap: scaleSize(8),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name='close'
|
||||
size={20}
|
||||
size={scaleSize(20)}
|
||||
color={focused ? "#000" : "rgba(255,255,255,0.8)"}
|
||||
/>
|
||||
<Text
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVCastCrewTextProps {
|
||||
director?: BaseItemPerson | null;
|
||||
@@ -22,18 +23,18 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 32 }}>
|
||||
<View style={{ marginBottom: scaleSize(32) }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
marginBottom: scaleSize(16),
|
||||
}}
|
||||
>
|
||||
{t("item_card.cast_and_crew")}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", gap: 40 }}>
|
||||
<View style={{ flexDirection: "row", gap: scaleSize(40) }}>
|
||||
{director && (
|
||||
<View>
|
||||
<Text
|
||||
@@ -42,7 +43,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
{t("item_card.director")}
|
||||
@@ -60,7 +61,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
{t("item_card.cast")}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
StyleSheet,
|
||||
type View,
|
||||
} from "react-native";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVControlButtonProps {
|
||||
@@ -63,7 +64,7 @@ export const TVControlButton: FC<TVControlButtonProps> = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons name={icon} size={size} color='#fff' />
|
||||
<Ionicons name={icon} size={scaleSize(size)} color='#fff' />
|
||||
</RNAnimated.View>
|
||||
</Pressable>
|
||||
);
|
||||
@@ -71,10 +72,10 @@ export const TVControlButton: FC<TVControlButtonProps> = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
borderWidth: 2,
|
||||
width: scaleSize(64),
|
||||
height: scaleSize(64),
|
||||
borderRadius: scaleSize(32),
|
||||
borderWidth: scaleSize(2),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVFilterButtonProps {
|
||||
@@ -42,13 +43,13 @@ export const TVFilterButton: React.FC<TVFilterButtonProps> = ({
|
||||
: hasActiveFilter
|
||||
? "rgba(255, 255, 255, 0.25)"
|
||||
: "rgba(255,255,255,0.1)",
|
||||
borderRadius: 10,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: scaleSize(10),
|
||||
paddingVertical: scaleSize(10),
|
||||
paddingHorizontal: scaleSize(16),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
borderWidth: hasActiveFilter && !focused ? 1 : 0,
|
||||
gap: scaleSize(8),
|
||||
borderWidth: hasActiveFilter && !focused ? scaleSize(1) : 0,
|
||||
borderColor: "rgba(255, 255, 255, 0.4)",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "react-native";
|
||||
import type { SharedValue } from "react-native-reanimated";
|
||||
import ReanimatedModule, { useAnimatedStyle } from "react-native-reanimated";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
const ReanimatedView = ReanimatedModule.View;
|
||||
@@ -35,7 +36,7 @@ export interface TVFocusableProgressBarProps {
|
||||
style?: ViewStyle;
|
||||
}
|
||||
|
||||
const PROGRESS_BAR_HEIGHT = 14;
|
||||
const PROGRESS_BAR_HEIGHT = scaleSize(14);
|
||||
|
||||
export const TVFocusableProgressBar: React.FC<TVFocusableProgressBarProps> =
|
||||
React.memo(
|
||||
@@ -124,21 +125,21 @@ export const TVFocusableProgressBar: React.FC<TVFocusableProgressBarProps> =
|
||||
const styles = StyleSheet.create({
|
||||
pressableContainer: {
|
||||
// Add padding for focus scale animation to not clip
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: scaleSize(8),
|
||||
paddingHorizontal: scaleSize(4),
|
||||
},
|
||||
animatedContainer: {
|
||||
height: PROGRESS_BAR_HEIGHT + 8,
|
||||
height: PROGRESS_BAR_HEIGHT + scaleSize(8),
|
||||
justifyContent: "center",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 4,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingHorizontal: scaleSize(4),
|
||||
},
|
||||
animatedContainerFocused: {
|
||||
// Subtle glow effect when focused
|
||||
shadowColor: "#fff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 12,
|
||||
shadowRadius: scaleSize(12),
|
||||
},
|
||||
progressTrackWrapper: {
|
||||
position: "relative",
|
||||
@@ -147,7 +148,7 @@ const styles = StyleSheet.create({
|
||||
progressTrack: {
|
||||
height: PROGRESS_BAR_HEIGHT,
|
||||
backgroundColor: "rgba(255,255,255,0.2)",
|
||||
borderRadius: 8,
|
||||
borderRadius: scaleSize(8),
|
||||
overflow: "hidden",
|
||||
},
|
||||
progressTrackFocused: {
|
||||
@@ -160,7 +161,7 @@ const styles = StyleSheet.create({
|
||||
left: 0,
|
||||
height: "100%",
|
||||
backgroundColor: "rgba(255,255,255,0.3)",
|
||||
borderRadius: 8,
|
||||
borderRadius: scaleSize(8),
|
||||
},
|
||||
progressFill: {
|
||||
position: "absolute",
|
||||
@@ -168,7 +169,7 @@ const styles = StyleSheet.create({
|
||||
left: 0,
|
||||
height: "100%",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 8,
|
||||
borderRadius: scaleSize(8),
|
||||
},
|
||||
chapterMarkersContainer: {
|
||||
position: "absolute",
|
||||
@@ -179,11 +180,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
chapterMarker: {
|
||||
position: "absolute",
|
||||
width: 2,
|
||||
height: PROGRESS_BAR_HEIGHT + 5,
|
||||
width: scaleSize(2),
|
||||
height: PROGRESS_BAR_HEIGHT + scaleSize(5),
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.6)",
|
||||
borderRadius: 1,
|
||||
transform: [{ translateX: -1 }],
|
||||
borderRadius: scaleSize(1),
|
||||
transform: [{ translateX: -scaleSize(1) }],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FlatList, ScrollView, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVSizes } from "@/constants/TVSizes";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
interface TVHorizontalListProps<T> {
|
||||
/** Data items to render */
|
||||
@@ -94,7 +95,7 @@ export function TVHorizontalList<T>({
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
marginBottom: scaleSize(20),
|
||||
marginLeft: sizes.padding.scale,
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
@@ -125,7 +126,7 @@ export function TVHorizontalList<T>({
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
marginBottom: scaleSize(20),
|
||||
marginLeft: sizes.padding.scale,
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
@@ -167,7 +168,7 @@ export function TVHorizontalList<T>({
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
marginBottom: scaleSize(20),
|
||||
marginLeft: sizes.padding.scale,
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVItemCardTextProps {
|
||||
item: BaseItemDto;
|
||||
@@ -12,7 +13,7 @@ export const TVItemCardText: React.FC<TVItemCardTextProps> = ({ item }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
return (
|
||||
<View style={{ marginTop: 12 }}>
|
||||
<View style={{ marginTop: scaleSize(12) }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
@@ -23,7 +24,7 @@ export const TVItemCardText: React.FC<TVItemCardTextProps> = ({ item }) => {
|
||||
style={{
|
||||
fontSize: typography.callout - 2,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
marginTop: scaleSize(2),
|
||||
}}
|
||||
>
|
||||
{item.ProductionYear}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, StyleSheet, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVLanguageCardProps {
|
||||
@@ -63,7 +64,7 @@ export const TVLanguageCard = React.forwardRef<View, TVLanguageCardProps>(
|
||||
<View style={styles.checkmark}>
|
||||
<Ionicons
|
||||
name='checkmark'
|
||||
size={16}
|
||||
size={scaleSize(16)}
|
||||
color='rgba(255,255,255,0.8)'
|
||||
/>
|
||||
</View>
|
||||
@@ -77,12 +78,12 @@ export const TVLanguageCard = React.forwardRef<View, TVLanguageCardProps>(
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
languageCard: {
|
||||
width: 120,
|
||||
height: 60,
|
||||
borderRadius: 12,
|
||||
width: scaleSize(120),
|
||||
height: scaleSize(60),
|
||||
borderRadius: scaleSize(12),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
paddingHorizontal: scaleSize(12),
|
||||
},
|
||||
languageCardText: {
|
||||
fontSize: typography.callout,
|
||||
@@ -90,11 +91,11 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
},
|
||||
languageCardCode: {
|
||||
fontSize: typography.callout,
|
||||
marginTop: 2,
|
||||
marginTop: scaleSize(2),
|
||||
},
|
||||
checkmark: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
top: scaleSize(8),
|
||||
right: scaleSize(8),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { View } from "react-native";
|
||||
import { Badge } from "@/components/Badge";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVMetadataBadgesProps {
|
||||
year?: number | null;
|
||||
@@ -22,8 +23,8 @@ export const TVMetadataBadges: React.FC<TVMetadataBadgesProps> = React.memo(
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
gap: 16,
|
||||
marginBottom: 24,
|
||||
gap: scaleSize(16),
|
||||
marginBottom: scaleSize(24),
|
||||
}}
|
||||
>
|
||||
{year != null && (
|
||||
|
||||
@@ -23,6 +23,7 @@ import Animated, {
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVNextEpisodeCountdownProps {
|
||||
@@ -44,8 +45,8 @@ export interface TVNextEpisodeCountdownProps {
|
||||
}
|
||||
|
||||
// Position constants
|
||||
const BOTTOM_WITH_CONTROLS = 300;
|
||||
const BOTTOM_WITHOUT_CONTROLS = 120;
|
||||
const BOTTOM_WITH_CONTROLS = scaleSize(300);
|
||||
const BOTTOM_WITHOUT_CONTROLS = scaleSize(120);
|
||||
|
||||
export const TVNextEpisodeCountdown: FC<TVNextEpisodeCountdownProps> = ({
|
||||
nextItem,
|
||||
@@ -74,7 +75,7 @@ export const TVNextEpisodeCountdown: FC<TVNextEpisodeCountdownProps> = ({
|
||||
const imageUrl = getPrimaryImageUrl({
|
||||
api,
|
||||
item: nextItem,
|
||||
width: 360,
|
||||
width: scaleSize(360),
|
||||
quality: 80,
|
||||
});
|
||||
|
||||
@@ -198,17 +199,17 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
position: "absolute",
|
||||
right: 80,
|
||||
right: scaleSize(80),
|
||||
zIndex: 100,
|
||||
},
|
||||
focusedCard: {
|
||||
shadowColor: "#fff",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: 0.6,
|
||||
shadowRadius: 16,
|
||||
shadowRadius: scaleSize(16),
|
||||
},
|
||||
blur: {
|
||||
borderRadius: 16,
|
||||
borderRadius: scaleSize(16),
|
||||
overflow: "hidden",
|
||||
},
|
||||
innerContainer: {
|
||||
@@ -216,42 +217,42 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
alignItems: "stretch",
|
||||
},
|
||||
thumbnail: {
|
||||
width: 180,
|
||||
width: scaleSize(180),
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
},
|
||||
content: {
|
||||
padding: 16,
|
||||
padding: scaleSize(16),
|
||||
justifyContent: "center",
|
||||
width: 280,
|
||||
width: scaleSize(280),
|
||||
},
|
||||
label: {
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
},
|
||||
seriesName: {
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
marginBottom: 2,
|
||||
marginBottom: scaleSize(2),
|
||||
},
|
||||
episodeInfo: {
|
||||
fontSize: typography.body,
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
marginBottom: 12,
|
||||
marginBottom: scaleSize(12),
|
||||
},
|
||||
progressContainer: {
|
||||
height: 4,
|
||||
height: scaleSize(4),
|
||||
backgroundColor: "rgba(255,255,255,0.2)",
|
||||
borderRadius: 2,
|
||||
borderRadius: scaleSize(2),
|
||||
overflow: "hidden",
|
||||
},
|
||||
progressBar: {
|
||||
height: "100%",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 2,
|
||||
borderRadius: scaleSize(2),
|
||||
},
|
||||
returnFocusGuide: {
|
||||
height: 1,
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVOptionButtonProps {
|
||||
@@ -42,12 +43,12 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 8,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: scaleSize(8),
|
||||
paddingVertical: scaleSize(10),
|
||||
paddingHorizontal: scaleSize(16),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
gap: scaleSize(8),
|
||||
maxWidth,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVOptionCardProps {
|
||||
@@ -49,12 +50,14 @@ export const TVOptionCard = React.forwardRef<View, TVOptionCardProps>(
|
||||
backgroundColor: focused
|
||||
? "#fff"
|
||||
: selected
|
||||
? "rgba(255,255,255,0.2)"
|
||||
? "rgba(255,255,255,0.15)"
|
||||
: "rgba(255,255,255,0.08)",
|
||||
borderRadius: 14,
|
||||
borderRadius: scaleSize(14),
|
||||
borderWidth: focused ? 0 : selected ? scaleSize(2) : 0,
|
||||
borderColor: "rgba(255,255,255,0.6)",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
paddingHorizontal: scaleSize(12),
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -75,7 +78,7 @@ export const TVOptionCard = React.forwardRef<View, TVOptionCardProps>(
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)",
|
||||
textAlign: "center",
|
||||
marginTop: 2,
|
||||
marginTop: scaleSize(2),
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { TVCancelButton } from "./TVCancelButton";
|
||||
import { TVOptionCard } from "./TVOptionCard";
|
||||
|
||||
@@ -170,21 +171,21 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
width: "100%",
|
||||
},
|
||||
blurContainer: {
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
borderTopLeftRadius: scaleSize(24),
|
||||
borderTopRightRadius: scaleSize(24),
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 50,
|
||||
paddingTop: scaleSize(24),
|
||||
paddingBottom: scaleSize(50),
|
||||
overflow: "visible",
|
||||
},
|
||||
title: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "500",
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 16,
|
||||
paddingHorizontal: 48,
|
||||
marginBottom: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(48),
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
@@ -192,13 +193,13 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
overflow: "visible",
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 48,
|
||||
paddingVertical: 20,
|
||||
gap: 12,
|
||||
paddingHorizontal: scaleSize(48),
|
||||
paddingVertical: scaleSize(20),
|
||||
gap: scaleSize(12),
|
||||
},
|
||||
cancelButtonContainer: {
|
||||
marginTop: 16,
|
||||
paddingHorizontal: 48,
|
||||
marginTop: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(48),
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVProgressBarProps {
|
||||
/** Progress value between 0 and 1 */
|
||||
@@ -23,14 +24,16 @@ export const TVProgressBar: React.FC<TVProgressBarProps> = React.memo(
|
||||
height = 4,
|
||||
}) => {
|
||||
const clampedProgress = Math.max(0, Math.min(1, progress));
|
||||
const scaledMaxWidth = scaleSize(maxWidth);
|
||||
const scaledHeight = scaleSize(height);
|
||||
|
||||
return (
|
||||
<View style={{ maxWidth, marginBottom: 24 }}>
|
||||
<View style={{ maxWidth: scaledMaxWidth, marginBottom: scaleSize(24) }}>
|
||||
<View
|
||||
style={{
|
||||
height,
|
||||
height: scaledHeight,
|
||||
backgroundColor: trackColor,
|
||||
borderRadius: height / 2,
|
||||
borderRadius: scaledHeight / 2,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
@@ -39,7 +42,7 @@ export const TVProgressBar: React.FC<TVProgressBarProps> = React.memo(
|
||||
width: `${clampedProgress * 100}%`,
|
||||
height: "100%",
|
||||
backgroundColor: fillColor,
|
||||
borderRadius: height / 2,
|
||||
borderRadius: scaledHeight / 2,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ScrollView, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVSizes } from "@/constants/TVSizes";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { TVSeriesSeasonCard } from "./TVSeriesSeasonCard";
|
||||
|
||||
export interface TVSeriesNavigationProps {
|
||||
@@ -33,7 +34,7 @@ export const TVSeriesNavigation: React.FC<TVSeriesNavigationProps> = React.memo(
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
marginBottom: scaleSize(20),
|
||||
letterSpacing: 0.5,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
GlassPosterView,
|
||||
isGlassEffectAvailable,
|
||||
} from "@/modules/glass-poster";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVSeriesSeasonCardProps {
|
||||
title: string;
|
||||
@@ -66,10 +67,10 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
style={{
|
||||
width: sizes.posters.poster,
|
||||
aspectRatio: 10 / 15,
|
||||
borderRadius: 24,
|
||||
borderRadius: scaleSize(24),
|
||||
overflow: "hidden",
|
||||
backgroundColor: "rgba(255,255,255,0.1)",
|
||||
borderWidth: 2,
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: focused ? "#FFFFFF" : "transparent",
|
||||
}}
|
||||
>
|
||||
@@ -87,7 +88,11 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Ionicons name='film' size={56} color='rgba(255,255,255,0.4)' />
|
||||
<Ionicons
|
||||
name='film'
|
||||
size={scaleSize(56)}
|
||||
color='rgba(255,255,255,0.4)'
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
@@ -129,7 +134,7 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
|
||||
<View style={{ marginTop: 12 }}>
|
||||
<View style={{ marginTop: scaleSize(12) }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.body,
|
||||
@@ -145,7 +150,7 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 4,
|
||||
marginTop: scaleSize(4),
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
|
||||
@@ -16,6 +16,7 @@ import Animated, {
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSkipSegmentCardProps {
|
||||
@@ -102,7 +103,7 @@ export const TVSkipSegmentCard: FC<TVSkipSegmentCardProps> = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons name='play-forward' size={20} color='#fff' />
|
||||
<Ionicons name='play-forward' size={scaleSize(20)} color='#fff' />
|
||||
<Text style={styles.label}>{labelText}</Text>
|
||||
</RNAnimated.View>
|
||||
</Pressable>
|
||||
@@ -119,20 +120,20 @@ export const TVSkipSegmentCard: FC<TVSkipSegmentCardProps> = ({
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: "absolute",
|
||||
right: 80,
|
||||
right: scaleSize(80),
|
||||
zIndex: 100,
|
||||
},
|
||||
button: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 18,
|
||||
borderRadius: 12,
|
||||
borderWidth: 2,
|
||||
gap: 8,
|
||||
paddingVertical: scaleSize(10),
|
||||
paddingHorizontal: scaleSize(18),
|
||||
borderRadius: scaleSize(12),
|
||||
borderWidth: scaleSize(2),
|
||||
gap: scaleSize(8),
|
||||
},
|
||||
label: {
|
||||
fontSize: 20,
|
||||
fontSize: scaleSize(20),
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import type { SubtitleSearchResult } from "@/hooks/useRemoteSubtitles";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSubtitleResultCardProps {
|
||||
@@ -202,18 +203,18 @@ export const TVSubtitleResultCard = React.forwardRef<
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
resultCard: {
|
||||
width: 220,
|
||||
minHeight: 120,
|
||||
borderRadius: 14,
|
||||
padding: 14,
|
||||
borderWidth: 1,
|
||||
width: scaleSize(220),
|
||||
minHeight: scaleSize(120),
|
||||
borderRadius: scaleSize(14),
|
||||
padding: scaleSize(14),
|
||||
borderWidth: scaleSize(1),
|
||||
},
|
||||
providerBadge: {
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
marginBottom: 8,
|
||||
paddingHorizontal: scaleSize(8),
|
||||
paddingVertical: scaleSize(3),
|
||||
borderRadius: scaleSize(6),
|
||||
marginBottom: scaleSize(8),
|
||||
},
|
||||
providerText: {
|
||||
fontSize: typography.callout,
|
||||
@@ -224,14 +225,14 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
resultName: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "500",
|
||||
marginBottom: 8,
|
||||
lineHeight: 18,
|
||||
marginBottom: scaleSize(8),
|
||||
lineHeight: scaleSize(18),
|
||||
},
|
||||
resultMeta: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
marginBottom: 8,
|
||||
gap: scaleSize(12),
|
||||
marginBottom: scaleSize(8),
|
||||
},
|
||||
resultMetaText: {
|
||||
fontSize: typography.callout,
|
||||
@@ -239,22 +240,22 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
ratingContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
gap: scaleSize(3),
|
||||
},
|
||||
downloadCountContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
gap: scaleSize(3),
|
||||
},
|
||||
flagsContainer: {
|
||||
flexDirection: "row",
|
||||
gap: 6,
|
||||
gap: scaleSize(6),
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
flag: {
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: scaleSize(6),
|
||||
paddingVertical: scaleSize(2),
|
||||
borderRadius: scaleSize(4),
|
||||
},
|
||||
flagText: {
|
||||
fontSize: typography.callout,
|
||||
@@ -264,7 +265,7 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
downloadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
borderRadius: 14,
|
||||
borderRadius: scaleSize(14),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVTabButtonProps {
|
||||
@@ -48,10 +49,10 @@ export const TVTabButton: React.FC<TVTabButtonProps> = ({
|
||||
? "rgba(255,255,255,0.2)"
|
||||
: "transparent",
|
||||
borderBottomColor: active ? "#fff" : "transparent",
|
||||
borderBottomWidth: 2,
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 8,
|
||||
borderBottomWidth: scaleSize(2),
|
||||
paddingHorizontal: scaleSize(20),
|
||||
paddingVertical: scaleSize(12),
|
||||
borderRadius: scaleSize(8),
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
export interface TVTechnicalDetailsProps {
|
||||
mediaStreams: MediaStream[];
|
||||
@@ -22,18 +23,18 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 32 }}>
|
||||
<View style={{ marginBottom: scaleSize(32) }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
marginBottom: scaleSize(20),
|
||||
}}
|
||||
>
|
||||
{t("item_card.technical_details")}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", gap: 40 }}>
|
||||
<View style={{ flexDirection: "row", gap: scaleSize(40) }}>
|
||||
{videoStream && (
|
||||
<View>
|
||||
<Text
|
||||
@@ -42,7 +43,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
{t("common.video")}
|
||||
@@ -61,7 +62,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
marginBottom: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
{t("common.audio")}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Animated, Easing, Pressable, View } from "react-native";
|
||||
import { AnimatedEqualizer } from "@/components/music/AnimatedEqualizer";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
|
||||
interface TVThemeMusicIndicatorProps {
|
||||
isPlaying: boolean;
|
||||
@@ -51,24 +52,24 @@ export const TVThemeMusicIndicator: React.FC<TVThemeMusicIndicatorProps> = ({
|
||||
backgroundColor: focused
|
||||
? "rgba(255,255,255,0.25)"
|
||||
: "rgba(255,255,255,0.1)",
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
borderRadius: scaleSize(12),
|
||||
padding: scaleSize(12),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 48,
|
||||
height: 48,
|
||||
width: scaleSize(48),
|
||||
height: scaleSize(48),
|
||||
}}
|
||||
>
|
||||
{isMuted ? (
|
||||
<Ionicons name='volume-mute' size={22} color='#FFFFFF' />
|
||||
<Ionicons name='volume-mute' size={scaleSize(22)} color='#FFFFFF' />
|
||||
) : (
|
||||
<View style={{ marginRight: 0 }}>
|
||||
<AnimatedEqualizer
|
||||
color='#FFFFFF'
|
||||
barWidth={3}
|
||||
barWidth={scaleSize(3)}
|
||||
barCount={3}
|
||||
height={18}
|
||||
gap={2}
|
||||
height={scaleSize(18)}
|
||||
gap={scaleSize(2)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, StyleSheet, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVTrackCardProps {
|
||||
@@ -68,7 +69,7 @@ export const TVTrackCard = React.forwardRef<View, TVTrackCardProps>(
|
||||
<View style={styles.checkmark}>
|
||||
<Ionicons
|
||||
name='checkmark'
|
||||
size={16}
|
||||
size={scaleSize(16)}
|
||||
color='rgba(255,255,255,0.8)'
|
||||
/>
|
||||
</View>
|
||||
@@ -82,12 +83,12 @@ export const TVTrackCard = React.forwardRef<View, TVTrackCardProps>(
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
trackCard: {
|
||||
width: 180,
|
||||
height: 80,
|
||||
borderRadius: 14,
|
||||
width: scaleSize(180),
|
||||
height: scaleSize(80),
|
||||
borderRadius: scaleSize(14),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
paddingHorizontal: scaleSize(12),
|
||||
},
|
||||
trackCardText: {
|
||||
fontSize: typography.callout,
|
||||
@@ -95,11 +96,11 @@ const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
},
|
||||
trackCardSublabel: {
|
||||
fontSize: typography.callout,
|
||||
marginTop: 2,
|
||||
marginTop: scaleSize(2),
|
||||
},
|
||||
checkmark: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
top: scaleSize(8),
|
||||
right: scaleSize(8),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import type { AccountSecurityType } from "@/utils/secureCredentials";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
@@ -89,36 +90,44 @@ export const TVUserCard = React.forwardRef<View, TVUserCardProps>(
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: getBackgroundColor(),
|
||||
borderRadius: 14,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
gap: 14,
|
||||
borderRadius: scaleSize(14),
|
||||
paddingHorizontal: scaleSize(16),
|
||||
paddingVertical: scaleSize(14),
|
||||
gap: scaleSize(14),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{/* User Avatar */}
|
||||
<View
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
width: scaleSize(44),
|
||||
height: scaleSize(44),
|
||||
backgroundColor: isCurrent
|
||||
? "rgba(255,255,255,0.08)"
|
||||
: focused
|
||||
? "rgba(0,0,0,0.1)"
|
||||
: "rgba(255,255,255,0.15)",
|
||||
borderRadius: 22,
|
||||
borderRadius: scaleSize(22),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Ionicons name='person' size={24} color={getTextColor()} />
|
||||
<Ionicons
|
||||
name='person'
|
||||
size={scaleSize(24)}
|
||||
color={getTextColor()}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Text column */}
|
||||
<View style={{ gap: 4 }}>
|
||||
<View style={{ gap: scaleSize(4) }}>
|
||||
{/* Username */}
|
||||
<View
|
||||
style={{ flexDirection: "row", alignItems: "center", gap: 8 }}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
@@ -148,12 +157,12 @@ export const TVUserCard = React.forwardRef<View, TVUserCardProps>(
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
gap: scaleSize(4),
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
name={getSecurityIcon()}
|
||||
size={12}
|
||||
size={scaleSize(12)}
|
||||
color={getSecondaryColor()}
|
||||
/>
|
||||
<Text
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVLogoutButtonProps {
|
||||
@@ -41,9 +42,9 @@ export const TVLogoutButton: React.FC<TVLogoutButtonProps> = ({
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: focused ? "#ef4444" : "rgba(239, 68, 68, 0.8)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 18,
|
||||
paddingHorizontal: 48,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(18),
|
||||
paddingHorizontal: scaleSize(48),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsOptionButtonProps {
|
||||
@@ -40,10 +41,10 @@ export const TVSettingsOptionButton: React.FC<TVSettingsOptionButtonProps> = ({
|
||||
backgroundColor: focused
|
||||
? "rgba(255, 255, 255, 0.15)"
|
||||
: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 8,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
marginBottom: scaleSize(8),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -59,12 +60,16 @@ export const TVSettingsOptionButton: React.FC<TVSettingsOptionButtonProps> = ({
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginRight: 12,
|
||||
marginRight: scaleSize(12),
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
<Ionicons name='chevron-forward' size={20} color='#6B7280' />
|
||||
<Ionicons
|
||||
name='chevron-forward'
|
||||
size={scaleSize(20)}
|
||||
color='#6B7280'
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsRowProps {
|
||||
@@ -42,10 +43,10 @@ export const TVSettingsRow: React.FC<TVSettingsRowProps> = ({
|
||||
backgroundColor: focused
|
||||
? "rgba(255, 255, 255, 0.15)"
|
||||
: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 8,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
marginBottom: scaleSize(8),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -60,13 +61,17 @@ export const TVSettingsRow: React.FC<TVSettingsRowProps> = ({
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginRight: showChevron ? 12 : 0,
|
||||
marginRight: showChevron ? scaleSize(12) : 0,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
{showChevron && (
|
||||
<Ionicons name='chevron-forward' size={20} color='#6B7280' />
|
||||
<Ionicons
|
||||
name='chevron-forward'
|
||||
size={scaleSize(20)}
|
||||
color='#6B7280'
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsStepperProps {
|
||||
@@ -38,10 +39,10 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
labelAnim.focused || minusAnim.focused || plusAnim.focused
|
||||
? "rgba(255, 255, 255, 0.15)"
|
||||
: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 8,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
marginBottom: scaleSize(8),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -72,9 +73,9 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
style={[
|
||||
minusAnim.animatedStyle,
|
||||
{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 10,
|
||||
width: scaleSize(40),
|
||||
height: scaleSize(40),
|
||||
borderRadius: scaleSize(10),
|
||||
backgroundColor: minusAnim.focused ? "#FFFFFF" : "#4B5563",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
@@ -83,7 +84,7 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
>
|
||||
<Ionicons
|
||||
name='remove'
|
||||
size={24}
|
||||
size={scaleSize(24)}
|
||||
color={minusAnim.focused ? "#000000" : "#FFFFFF"}
|
||||
/>
|
||||
</Animated.View>
|
||||
@@ -92,9 +93,9 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
minWidth: 60,
|
||||
minWidth: scaleSize(60),
|
||||
textAlign: "center",
|
||||
marginHorizontal: 16,
|
||||
marginHorizontal: scaleSize(16),
|
||||
}}
|
||||
>
|
||||
{displayValue}
|
||||
@@ -110,9 +111,9 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
style={[
|
||||
plusAnim.animatedStyle,
|
||||
{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 10,
|
||||
width: scaleSize(40),
|
||||
height: scaleSize(40),
|
||||
borderRadius: scaleSize(10),
|
||||
backgroundColor: plusAnim.focused ? "#FFFFFF" : "#4B5563",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
@@ -121,7 +122,7 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
>
|
||||
<Ionicons
|
||||
name='add'
|
||||
size={24}
|
||||
size={scaleSize(24)}
|
||||
color={plusAnim.focused ? "#000000" : "#FFFFFF"}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useRef } from "react";
|
||||
import { Animated, Pressable, TextInput } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsTextInputProps {
|
||||
@@ -48,10 +49,10 @@ export const TVSettingsTextInput: React.FC<TVSettingsTextInputProps> = ({
|
||||
backgroundColor: focused
|
||||
? "rgba(255, 255, 255, 0.15)"
|
||||
: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 8,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
marginBottom: scaleSize(8),
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -59,7 +60,7 @@ export const TVSettingsTextInput: React.FC<TVSettingsTextInputProps> = ({
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginBottom: 8,
|
||||
marginBottom: scaleSize(8),
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
@@ -78,10 +79,10 @@ export const TVSettingsTextInput: React.FC<TVSettingsTextInputProps> = ({
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 8,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderWidth: focused ? 2 : 1,
|
||||
borderRadius: scaleSize(8),
|
||||
paddingVertical: scaleSize(12),
|
||||
paddingHorizontal: scaleSize(16),
|
||||
borderWidth: focused ? scaleSize(2) : scaleSize(1),
|
||||
borderColor: focused ? "#FFFFFF" : "#4B5563",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsToggleProps {
|
||||
@@ -39,10 +40,10 @@ export const TVSettingsToggle: React.FC<TVSettingsToggleProps> = ({
|
||||
backgroundColor: focused
|
||||
? "rgba(255, 255, 255, 0.15)"
|
||||
: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 12,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
marginBottom: 8,
|
||||
borderRadius: scaleSize(12),
|
||||
paddingVertical: scaleSize(16),
|
||||
paddingHorizontal: scaleSize(24),
|
||||
marginBottom: scaleSize(8),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -54,19 +55,19 @@ export const TVSettingsToggle: React.FC<TVSettingsToggleProps> = ({
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
width: 56,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
width: scaleSize(56),
|
||||
height: scaleSize(32),
|
||||
borderRadius: scaleSize(16),
|
||||
backgroundColor: value ? "#FFFFFF" : "#4B5563",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 2,
|
||||
paddingHorizontal: scaleSize(2),
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
width: scaleSize(28),
|
||||
height: scaleSize(28),
|
||||
borderRadius: scaleSize(14),
|
||||
backgroundColor: value ? "#000000" : "#FFFFFF",
|
||||
alignSelf: value ? "flex-end" : "flex-start",
|
||||
}}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const TVAnimation = {
|
||||
* Applied to poster sizes and gaps.
|
||||
*/
|
||||
const sizeScaleMultipliers: Record<TVTypographyScale, number> = {
|
||||
[TVTypographyScale.Small]: 0.8,
|
||||
[TVTypographyScale.Small]: 0.9,
|
||||
[TVTypographyScale.Default]: 1.0,
|
||||
[TVTypographyScale.Large]: 1.1,
|
||||
[TVTypographyScale.ExtraLarge]: 1.2,
|
||||
|
||||
@@ -37,10 +37,10 @@ export type TVTypographyKey = keyof typeof TVTypography;
|
||||
// =============================================================================
|
||||
|
||||
const scaleMultipliers: Record<TVTypographyScale, number> = {
|
||||
[TVTypographyScale.Small]: 0.8,
|
||||
[TVTypographyScale.Small]: 0.85,
|
||||
[TVTypographyScale.Default]: 1.0,
|
||||
[TVTypographyScale.Large]: 1.1,
|
||||
[TVTypographyScale.ExtraLarge]: 1.2,
|
||||
[TVTypographyScale.Large]: 1.2,
|
||||
[TVTypographyScale.ExtraLarge]: 1.4,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -251,6 +251,7 @@ internal object TvRecommendationsPublisher {
|
||||
|
||||
imageUrl.takeIf { it.isNotBlank() }?.let {
|
||||
val imageUri = Uri.parse(it)
|
||||
builder.setPosterArtUri(imageUri)
|
||||
builder.setThumbnailUri(imageUri)
|
||||
}
|
||||
|
||||
|
||||
40
plugins/withAndroidAlertColors.js
Normal file
40
plugins/withAndroidAlertColors.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const {
|
||||
withAndroidColors,
|
||||
withAndroidColorsNight,
|
||||
} = require("expo/config-plugins");
|
||||
|
||||
const withAndroidAlertColors = (config) => {
|
||||
const setColor = (colorsList, name, value) => {
|
||||
const existingColor = colorsList.find(
|
||||
(item) => item.$ && item.$.name === name,
|
||||
);
|
||||
if (existingColor) {
|
||||
existingColor._ = value;
|
||||
} else {
|
||||
colorsList.push({
|
||||
$: { name },
|
||||
_: value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
config = withAndroidColors(config, (config) => {
|
||||
const colors = config.modResults;
|
||||
const colorsList = colors.resources.color || [];
|
||||
setColor(colorsList, "colorPrimary", "#000000");
|
||||
colors.resources.color = colorsList;
|
||||
return config;
|
||||
});
|
||||
|
||||
config = withAndroidColorsNight(config, (config) => {
|
||||
const colors = config.modResults;
|
||||
const colorsList = colors.resources.color || [];
|
||||
setColor(colorsList, "colorPrimary", "#FFFFFF");
|
||||
colors.resources.color = colorsList;
|
||||
return config;
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
module.exports = withAndroidAlertColors;
|
||||
Reference in New Issue
Block a user