mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-26 13:08:23 +00:00
feat(tv): add scalable typography with user-configurable text size
This commit is contained in:
@@ -17,7 +17,11 @@ import {
|
||||
} from "@/components/tv";
|
||||
import { useTVOptionModal } from "@/hooks/useTVOptionModal";
|
||||
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { AudioTranscodeMode, useSettings } from "@/utils/atoms/settings";
|
||||
import {
|
||||
AudioTranscodeMode,
|
||||
TVTypographyScale,
|
||||
useSettings,
|
||||
} from "@/utils/atoms/settings";
|
||||
|
||||
export default function SettingsTV() {
|
||||
const { t } = useTranslation();
|
||||
@@ -39,6 +43,8 @@ export default function SettingsTV() {
|
||||
settings.subtitleMode || SubtitlePlaybackMode.Default;
|
||||
const currentAlignX = settings.mpvSubtitleAlignX ?? "center";
|
||||
const currentAlignY = settings.mpvSubtitleAlignY ?? "bottom";
|
||||
const currentTypographyScale =
|
||||
settings.tvTypographyScale || TVTypographyScale.Default;
|
||||
|
||||
// Audio transcoding options
|
||||
const audioTranscodeModeOptions: TVOptionItem<AudioTranscodeMode>[] = useMemo(
|
||||
@@ -130,6 +136,33 @@ export default function SettingsTV() {
|
||||
[currentAlignY],
|
||||
);
|
||||
|
||||
// Typography scale options
|
||||
const typographyScaleOptions: TVOptionItem<TVTypographyScale>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t("home.settings.appearance.text_size_small"),
|
||||
value: TVTypographyScale.Small,
|
||||
selected: currentTypographyScale === TVTypographyScale.Small,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.appearance.text_size_default"),
|
||||
value: TVTypographyScale.Default,
|
||||
selected: currentTypographyScale === TVTypographyScale.Default,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.appearance.text_size_large"),
|
||||
value: TVTypographyScale.Large,
|
||||
selected: currentTypographyScale === TVTypographyScale.Large,
|
||||
},
|
||||
{
|
||||
label: t("home.settings.appearance.text_size_extra_large"),
|
||||
value: TVTypographyScale.ExtraLarge,
|
||||
selected: currentTypographyScale === TVTypographyScale.ExtraLarge,
|
||||
},
|
||||
],
|
||||
[t, currentTypographyScale],
|
||||
);
|
||||
|
||||
// Get display labels for option buttons
|
||||
const audioTranscodeLabel = useMemo(() => {
|
||||
const option = audioTranscodeModeOptions.find((o) => o.selected);
|
||||
@@ -151,6 +184,11 @@ export default function SettingsTV() {
|
||||
return option?.label || "Bottom";
|
||||
}, [alignYOptions]);
|
||||
|
||||
const typographyScaleLabel = useMemo(() => {
|
||||
const option = typographyScaleOptions.find((o) => o.selected);
|
||||
return option?.label || t("home.settings.appearance.text_size_default");
|
||||
}, [typographyScaleOptions, t]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: "#000000" }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
@@ -344,6 +382,18 @@ export default function SettingsTV() {
|
||||
|
||||
{/* Appearance Section */}
|
||||
<TVSectionHeader title={t("home.settings.appearance.title")} />
|
||||
<TVSettingsOptionButton
|
||||
label={t("home.settings.appearance.text_size")}
|
||||
value={typographyScaleLabel}
|
||||
onPress={() =>
|
||||
showOptions({
|
||||
title: t("home.settings.appearance.text_size"),
|
||||
options: typographyScaleOptions,
|
||||
onSelect: (value) =>
|
||||
updateSettings({ tvTypographyScale: value }),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<TVSettingsToggle
|
||||
label={t(
|
||||
"home.settings.appearance.merge_next_up_continue_watching",
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
TVFocusablePoster,
|
||||
TVItemCardText,
|
||||
} from "@/components/tv";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useOrientation } from "@/hooks/useOrientation";
|
||||
import { useTVOptionModal } from "@/hooks/useTVOptionModal";
|
||||
@@ -83,6 +84,7 @@ const Page = () => {
|
||||
};
|
||||
const { libraryId } = searchParams;
|
||||
|
||||
const typography = useScaledTVTypography();
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
@@ -947,7 +949,7 @@ const Page = () => {
|
||||
paddingTop: 100,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 20, color: "#737373" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#737373" }}>
|
||||
{t("library.no_results")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -29,7 +29,7 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useOrientation } from "@/hooks/useOrientation";
|
||||
import {
|
||||
@@ -46,17 +46,22 @@ import { userAtom } from "@/providers/JellyfinProvider";
|
||||
const TV_ITEM_GAP = 20;
|
||||
const TV_HORIZONTAL_PADDING = 60;
|
||||
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => (
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => (
|
||||
<View style={{ marginTop: 12 }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout - 2,
|
||||
fontSize: typography.callout - 2,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -67,6 +72,7 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => (
|
||||
);
|
||||
|
||||
export default function WatchlistDetailScreen() {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const navigation = useNavigation();
|
||||
@@ -212,11 +218,11 @@ export default function WatchlistDetailScreen() {
|
||||
<SeriesPoster item={item} />
|
||||
)}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[router],
|
||||
[router, typography],
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
@@ -356,7 +362,7 @@ export default function WatchlistDetailScreen() {
|
||||
{watchlist.description && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#9CA3AF",
|
||||
marginBottom: 16,
|
||||
textAlign: "center",
|
||||
@@ -376,9 +382,7 @@ export default function WatchlistDetailScreen() {
|
||||
style={{ flexDirection: "row", alignItems: "center", gap: 8 }}
|
||||
>
|
||||
<Ionicons name='film-outline' size={20} color='#9ca3af' />
|
||||
<Text
|
||||
style={{ fontSize: TVTypography.callout, color: "#9CA3AF" }}
|
||||
>
|
||||
<Text style={{ fontSize: typography.callout, color: "#9CA3AF" }}>
|
||||
{items?.length ?? 0}{" "}
|
||||
{(items?.length ?? 0) === 1
|
||||
? t("watchlists.item")
|
||||
@@ -395,18 +399,14 @@ export default function WatchlistDetailScreen() {
|
||||
size={20}
|
||||
color='#9ca3af'
|
||||
/>
|
||||
<Text
|
||||
style={{ fontSize: TVTypography.callout, color: "#9CA3AF" }}
|
||||
>
|
||||
<Text style={{ fontSize: typography.callout, color: "#9CA3AF" }}>
|
||||
{watchlist.isPublic
|
||||
? t("watchlists.public")
|
||||
: t("watchlists.private")}
|
||||
</Text>
|
||||
</View>
|
||||
{!isOwner && (
|
||||
<Text
|
||||
style={{ fontSize: TVTypography.callout, color: "#737373" }}
|
||||
>
|
||||
<Text style={{ fontSize: typography.callout, color: "#737373" }}>
|
||||
{t("watchlists.by_owner")}
|
||||
</Text>
|
||||
)}
|
||||
@@ -426,7 +426,7 @@ export default function WatchlistDetailScreen() {
|
||||
<Ionicons name='film-outline' size={48} color='#4b5563' />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#9CA3AF",
|
||||
textAlign: "center",
|
||||
marginTop: 16,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { TVRequestOptionRow } from "@/components/jellyseerr/tv/TVRequestOptionRo
|
||||
import { TVToggleOptionRow } from "@/components/jellyseerr/tv/TVToggleOptionRow";
|
||||
import { TVButton, TVOptionSelector } from "@/components/tv";
|
||||
import type { TVOptionItem } from "@/components/tv/TVOptionSelector";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { tvRequestModalAtom } from "@/utils/atoms/tvRequestModal";
|
||||
@@ -30,6 +30,7 @@ import type { MediaRequestBody } from "@/utils/jellyseerr/server/interfaces/api/
|
||||
import { store } from "@/utils/store";
|
||||
|
||||
export default function TVRequestModalPage() {
|
||||
const typography = useScaledTVTypography();
|
||||
const router = useRouter();
|
||||
const modalState = useAtomValue(tvRequestModalAtom);
|
||||
const { t } = useTranslation();
|
||||
@@ -336,8 +337,12 @@ export default function TVRequestModalPage() {
|
||||
trapFocusRight
|
||||
style={styles.content}
|
||||
>
|
||||
<Text style={styles.heading}>{t("jellyseerr.advanced")}</Text>
|
||||
<Text style={styles.subtitle}>{modalState.title}</Text>
|
||||
<Text style={[styles.heading, { fontSize: typography.heading }]}>
|
||||
{t("jellyseerr.advanced")}
|
||||
</Text>
|
||||
<Text style={[styles.subtitle, { fontSize: typography.callout }]}>
|
||||
{modalState.title}
|
||||
</Text>
|
||||
|
||||
{isDataLoaded && isReady ? (
|
||||
<ScrollView
|
||||
@@ -390,7 +395,12 @@ export default function TVRequestModalPage() {
|
||||
color='#FFFFFF'
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.buttonText}>
|
||||
<Text
|
||||
style={[
|
||||
styles.buttonText,
|
||||
{ fontSize: typography.callout },
|
||||
]}
|
||||
>
|
||||
{t("jellyseerr.request_button")}
|
||||
</Text>
|
||||
</TVButton>
|
||||
@@ -451,13 +461,11 @@ const styles = StyleSheet.create({
|
||||
overflow: "visible",
|
||||
},
|
||||
heading: {
|
||||
fontSize: TVTypography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: TVTypography.callout,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 24,
|
||||
},
|
||||
@@ -482,7 +490,6 @@ const styles = StyleSheet.create({
|
||||
marginTop: 24,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVButton } from "@/components/tv";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { useTVRequestModal } from "@/hooks/useTVRequestModal";
|
||||
@@ -162,6 +162,7 @@ const TVSeasonToggleCard: React.FC<TVSeasonToggleCardProps> = ({
|
||||
};
|
||||
|
||||
export default function TVSeasonSelectModalPage() {
|
||||
const typography = useScaledTVTypography();
|
||||
const router = useRouter();
|
||||
const modalState = useAtomValue(tvSeasonSelectModalAtom);
|
||||
const { t } = useTranslation();
|
||||
@@ -305,8 +306,12 @@ export default function TVSeasonSelectModalPage() {
|
||||
trapFocusRight
|
||||
style={styles.content}
|
||||
>
|
||||
<Text style={styles.heading}>{t("jellyseerr.select_seasons")}</Text>
|
||||
<Text style={styles.subtitle}>{modalState.title}</Text>
|
||||
<Text style={[styles.heading, { fontSize: typography.heading }]}>
|
||||
{t("jellyseerr.select_seasons")}
|
||||
</Text>
|
||||
<Text style={[styles.subtitle, { fontSize: typography.callout }]}>
|
||||
{modalState.title}
|
||||
</Text>
|
||||
|
||||
{/* Season cards horizontal scroll */}
|
||||
<ScrollView
|
||||
@@ -343,7 +348,9 @@ export default function TVSeasonSelectModalPage() {
|
||||
color='#FFFFFF'
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.buttonText}>
|
||||
<Text
|
||||
style={[styles.buttonText, { fontSize: typography.callout }]}
|
||||
>
|
||||
{t("jellyseerr.request_selected")}
|
||||
{selectedSeasons.size > 0 && ` (${selectedSeasons.size})`}
|
||||
</Text>
|
||||
@@ -377,13 +384,11 @@ const styles = StyleSheet.create({
|
||||
overflow: "visible",
|
||||
},
|
||||
heading: {
|
||||
fontSize: TVTypography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: TVTypography.callout,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 24,
|
||||
},
|
||||
@@ -413,7 +418,6 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
seasonTitle: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "600",
|
||||
marginBottom: 4,
|
||||
},
|
||||
@@ -436,7 +440,6 @@ const styles = StyleSheet.create({
|
||||
marginTop: 24,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
|
||||
@@ -12,12 +12,13 @@ import {
|
||||
} from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVCancelButton, TVOptionCard } from "@/components/tv";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { tvSeriesSeasonModalAtom } from "@/utils/atoms/tvSeriesSeasonModal";
|
||||
import { store } from "@/utils/store";
|
||||
|
||||
export default function TVSeriesSeasonModalPage() {
|
||||
const typography = useScaledTVTypography();
|
||||
const router = useRouter();
|
||||
const modalState = useAtomValue(tvSeriesSeasonModalAtom);
|
||||
const { t } = useTranslation();
|
||||
@@ -103,7 +104,9 @@ export default function TVSeriesSeasonModalPage() {
|
||||
trapFocusRight
|
||||
style={styles.content}
|
||||
>
|
||||
<Text style={styles.title}>{t("item_card.select_season")}</Text>
|
||||
<Text style={[styles.title, { fontSize: typography.callout }]}>
|
||||
{t("item_card.select_season")}
|
||||
</Text>
|
||||
|
||||
{isReady && (
|
||||
<ScrollView
|
||||
@@ -164,7 +167,6 @@ const styles = StyleSheet.create({
|
||||
overflow: "visible",
|
||||
},
|
||||
title: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "500",
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BlurView } from "expo-blur";
|
||||
import { Platform, StyleSheet, View, type ViewProps } from "react-native";
|
||||
import { GlassEffectView } from "react-native-glass-effect-view";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { Text } from "./common/Text";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
@@ -16,6 +16,8 @@ export const Badge: React.FC<Props> = ({
|
||||
variant = "purple",
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
const content = (
|
||||
<View style={styles.content}>
|
||||
{iconLeft && <View style={styles.iconLeft}>{iconLeft}</View>}
|
||||
@@ -69,7 +71,7 @@ export const Badge: React.FC<Props> = ({
|
||||
{iconLeft && <View style={{ marginRight: 8 }}>{iconLeft}</View>}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#E5E7EB",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type ViewProps,
|
||||
} from "react-native";
|
||||
import { GlassEffectView } from "react-native-glass-effect-view";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { Text } from "./common/Text";
|
||||
|
||||
interface TagProps {
|
||||
@@ -25,6 +25,9 @@ export const Tag: React.FC<
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
} & ViewProps
|
||||
> = ({ text, textClass, textStyle, ...props }) => {
|
||||
// Hook must be called at the top level, before any conditional returns
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
if (Platform.OS === "ios" && !Platform.isTV) {
|
||||
return (
|
||||
<View>
|
||||
@@ -60,7 +63,7 @@ export const Tag: React.FC<
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: TVTypography.callout, color: "#E5E7EB" }}>
|
||||
<Text style={{ fontSize: typography.callout, color: "#E5E7EB" }}>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
TVTechnicalDetails,
|
||||
} from "@/components/tv";
|
||||
import type { Track } from "@/components/video-player/controls/types";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import useDefaultPlaySettings from "@/hooks/useDefaultPlaySettings";
|
||||
import { useImageColorsReturn } from "@/hooks/useImageColorsReturn";
|
||||
@@ -69,6 +69,7 @@ interface ItemContentTVProps {
|
||||
// Export as both ItemContentTV (for direct requires) and ItemContent (for platform-resolved imports)
|
||||
export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
({ item, itemWithSources }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [_user] = useAtom(userAtom);
|
||||
const isOffline = useOfflineMode();
|
||||
@@ -484,7 +485,7 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.display,
|
||||
fontSize: typography.display,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
@@ -500,7 +501,7 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
<View style={{ marginBottom: 16 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.title,
|
||||
fontSize: typography.title,
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
@@ -509,7 +510,7 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "white",
|
||||
marginTop: 6,
|
||||
}}
|
||||
@@ -554,7 +555,7 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#E5E7EB",
|
||||
lineHeight: 32,
|
||||
}}
|
||||
@@ -587,7 +588,7 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "bold",
|
||||
color: "#000000",
|
||||
}}
|
||||
|
||||
@@ -11,7 +11,7 @@ import heart from "@/assets/icons/heart.fill.png";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { InfiniteScrollingCollectionList } from "@/components/home/InfiniteScrollingCollectionList.tv";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
|
||||
const HORIZONTAL_PADDING = 60;
|
||||
@@ -28,6 +28,7 @@ type FavoriteTypes =
|
||||
type EmptyState = Record<FavoriteTypes, boolean>;
|
||||
|
||||
export const Favorites = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [api] = useAtom(apiAtom);
|
||||
@@ -148,7 +149,7 @@ export const Favorites = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
color: "#FFFFFF",
|
||||
@@ -160,7 +161,7 @@ export const Favorites = () => {
|
||||
style={{
|
||||
textAlign: "center",
|
||||
opacity: 0.7,
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { StreamystatsPromotedWatchlists } from "@/components/home/StreamystatsPr
|
||||
import { StreamystatsRecommendations } from "@/components/home/StreamystatsRecommendations.tv";
|
||||
import { TVHeroCarousel } from "@/components/home/TVHeroCarousel";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
|
||||
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
||||
@@ -62,6 +62,7 @@ type Section = InfiniteScrollingCollectionListSection;
|
||||
const BACKDROP_DEBOUNCE_MS = 300;
|
||||
|
||||
export const Home = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const _router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const api = useAtomValue(apiAtom);
|
||||
@@ -579,7 +580,7 @@ export const Home = () => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
color: "#FFFFFF",
|
||||
@@ -591,7 +592,7 @@ export const Home = () => {
|
||||
style={{
|
||||
textAlign: "center",
|
||||
opacity: 0.7,
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
@@ -632,7 +633,7 @@ export const Home = () => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
color: "#FFFFFF",
|
||||
@@ -644,7 +645,7 @@ export const Home = () => {
|
||||
style={{
|
||||
textAlign: "center",
|
||||
opacity: 0.7,
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ import MoviePoster, {
|
||||
TV_POSTER_WIDTH,
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { SortByOption, SortOrderOption } from "@/utils/atoms/filters";
|
||||
import ContinueWatchingPoster, {
|
||||
@@ -48,22 +48,27 @@ interface Props extends ViewProps {
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
// TV-specific ItemCardText with larger fonts
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => {
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
{item.Type === "Episode" ? (
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -77,13 +82,13 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -103,7 +108,8 @@ const TVSeeAllCard: React.FC<{
|
||||
disabled?: boolean;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
}> = ({ onPress, orientation, disabled, onFocus, onBlur }) => {
|
||||
typography: Typography;
|
||||
}> = ({ onPress, orientation, disabled, onFocus, onBlur, typography }) => {
|
||||
const { t } = useTranslation();
|
||||
const width =
|
||||
orientation === "horizontal" ? TV_LANDSCAPE_WIDTH : TV_POSTER_WIDTH;
|
||||
@@ -137,7 +143,7 @@ const TVSeeAllCard: React.FC<{
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
@@ -165,6 +171,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
parentId,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const effectivePageSize = Math.max(1, pageSize);
|
||||
const hasCalledOnLoaded = useRef(false);
|
||||
const router = useRouter();
|
||||
@@ -343,7 +350,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
>
|
||||
{renderPoster()}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
@@ -354,6 +361,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
handleItemPress,
|
||||
handleItemFocus,
|
||||
handleItemBlur,
|
||||
typography,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -365,7 +373,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
{/* Section Header */}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
@@ -380,7 +388,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
<Text
|
||||
style={{
|
||||
color: "#737373",
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
marginLeft: SCALE_PADDING,
|
||||
}}
|
||||
>
|
||||
@@ -421,7 +429,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
color: "#262626",
|
||||
backgroundColor: "#262626",
|
||||
borderRadius: 6,
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -478,6 +486,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
disabled={disabled}
|
||||
onFocus={handleSeeAllFocus}
|
||||
onBlur={handleItemBlur}
|
||||
typography={typography}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -16,7 +16,7 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -26,18 +26,23 @@ import type { StreamystatsWatchlist } from "@/utils/streamystats/types";
|
||||
const ITEM_GAP = 16;
|
||||
const SCALE_PADDING = 20;
|
||||
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => {
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -60,6 +65,7 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
|
||||
onItemFocus,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
const { settings } = useSettings();
|
||||
@@ -142,11 +148,11 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
|
||||
{item.Type === "Movie" && <MoviePoster item={item} />}
|
||||
{item.Type === "Series" && <SeriesPoster item={item} />}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[handleItemPress, onItemFocus],
|
||||
[handleItemPress, onItemFocus, typography],
|
||||
);
|
||||
|
||||
if (!isLoading && (!items || items.length === 0)) return null;
|
||||
@@ -155,7 +161,7 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
|
||||
<View style={{ overflow: "visible" }} {...props}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -16,7 +16,7 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -26,6 +26,8 @@ import type { StreamystatsRecommendationsIdsResponse } from "@/utils/streamystat
|
||||
const ITEM_GAP = 16;
|
||||
const SCALE_PADDING = 20;
|
||||
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
interface Props extends ViewProps {
|
||||
title: string;
|
||||
type: "Movie" | "Series";
|
||||
@@ -34,18 +36,21 @@ interface Props extends ViewProps {
|
||||
onItemFocus?: (item: BaseItemDto) => void;
|
||||
}
|
||||
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => {
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -64,6 +69,7 @@ export const StreamystatsRecommendations: React.FC<Props> = ({
|
||||
onItemFocus,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
const { settings } = useSettings();
|
||||
@@ -203,11 +209,11 @@ export const StreamystatsRecommendations: React.FC<Props> = ({
|
||||
{item.Type === "Movie" && <MoviePoster item={item} />}
|
||||
{item.Type === "Series" && <SeriesPoster item={item} />}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[handleItemPress, onItemFocus],
|
||||
[handleItemPress, onItemFocus, typography],
|
||||
);
|
||||
|
||||
if (!streamyStatsEnabled) return null;
|
||||
@@ -218,7 +224,7 @@ export const StreamystatsRecommendations: React.FC<Props> = ({
|
||||
<View style={{ overflow: "visible" }} {...props}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Animated, FlatList, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import {
|
||||
type DiscoverEndpoint,
|
||||
@@ -33,6 +33,7 @@ const TVDiscoverPoster: React.FC<TVDiscoverPosterProps> = ({
|
||||
item,
|
||||
isFirstItem = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const router = useRouter();
|
||||
const { jellyseerrApi, getTitle, getYear } = useJellyseerr();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
@@ -130,7 +131,7 @@ const TVDiscoverPoster: React.FC<TVDiscoverPosterProps> = ({
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
marginTop: 12,
|
||||
@@ -142,7 +143,7 @@ const TVDiscoverPoster: React.FC<TVDiscoverPosterProps> = ({
|
||||
{year && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -164,6 +165,7 @@ export const TVDiscoverSlide: React.FC<TVDiscoverSlideProps> = ({
|
||||
slide,
|
||||
isFirstSlide = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const { jellyseerrApi, isJellyseerrMovieOrTvResult } = useJellyseerr();
|
||||
|
||||
@@ -232,7 +234,7 @@ export const TVDiscoverSlide: React.FC<TVDiscoverSlideProps> = ({
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Loader } from "@/components/Loader";
|
||||
import { JellyserrRatings } from "@/components/Ratings";
|
||||
import { TVButton } from "@/components/tv";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { useTVRequestModal } from "@/hooks/useTVRequestModal";
|
||||
@@ -68,6 +68,7 @@ const TVCastCard: React.FC<TVCastCardProps> = ({
|
||||
onPress,
|
||||
refSetter,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08 });
|
||||
|
||||
@@ -128,7 +129,7 @@ const TVCastCard: React.FC<TVCastCardProps> = ({
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
@@ -158,6 +159,7 @@ const TVCastCard: React.FC<TVCastCardProps> = ({
|
||||
};
|
||||
|
||||
export const TVJellyseerrPage: React.FC = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const insets = useSafeAreaInsets();
|
||||
const params = useLocalSearchParams();
|
||||
const { t } = useTranslation();
|
||||
@@ -552,7 +554,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
{/* Title */}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.display,
|
||||
fontSize: typography.display,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginTop: 8,
|
||||
@@ -566,7 +568,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
{/* Year */}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
@@ -601,7 +603,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#E5E7EB",
|
||||
lineHeight: 32,
|
||||
}}
|
||||
@@ -636,7 +638,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "bold",
|
||||
color: "#000000",
|
||||
}}
|
||||
@@ -663,7 +665,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
@@ -698,7 +700,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
@@ -732,7 +734,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
@@ -757,7 +759,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
<Ionicons name='person-outline' size={18} color='#9CA3AF' />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginLeft: 8,
|
||||
}}
|
||||
@@ -776,7 +778,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
@@ -794,7 +796,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
@@ -813,7 +815,7 @@ export const TVJellyseerrPage: React.FC = () => {
|
||||
<View style={{ marginTop: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVButton, TVOptionSelector } from "@/components/tv";
|
||||
import type { TVOptionItem } from "@/components/tv/TVOptionSelector";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import type {
|
||||
QualityProfile,
|
||||
@@ -51,6 +51,7 @@ export const TVRequestModal: React.FC<TVRequestModalProps> = ({
|
||||
onClose,
|
||||
onRequested,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const { jellyseerrApi, jellyseerrUser, requestMedia } = useJellyseerr();
|
||||
|
||||
@@ -389,7 +390,7 @@ export const TVRequestModal: React.FC<TVRequestModalProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
@@ -399,7 +400,7 @@ export const TVRequestModal: React.FC<TVRequestModalProps> = ({
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 24,
|
||||
}}
|
||||
@@ -473,7 +474,7 @@ export const TVRequestModal: React.FC<TVRequestModalProps> = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
interface TVRequestOptionRowProps {
|
||||
label: string;
|
||||
@@ -20,6 +20,7 @@ export const TVRequestOptionRow: React.FC<TVRequestOptionRowProps> = ({
|
||||
hasTVPreferredFocus = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({
|
||||
scaleAmount: 1.02,
|
||||
@@ -56,7 +57,7 @@ export const TVRequestOptionRow: React.FC<TVRequestOptionRowProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
}}
|
||||
>
|
||||
@@ -65,7 +66,7 @@ export const TVRequestOptionRow: React.FC<TVRequestOptionRowProps> = ({
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: focused ? "#FFFFFF" : "rgba(255,255,255,0.9)",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable, ScrollView, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
interface ToggleItem {
|
||||
id: number;
|
||||
@@ -21,6 +21,7 @@ const TVToggleChip: React.FC<TVToggleChipProps> = ({
|
||||
onToggle,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({
|
||||
scaleAmount: 1.08,
|
||||
@@ -57,7 +58,7 @@ const TVToggleChip: React.FC<TVToggleChipProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: item.selected || focused ? "600" : "400",
|
||||
}}
|
||||
@@ -82,13 +83,14 @@ export const TVToggleOptionRow: React.FC<TVToggleOptionRowProps> = ({
|
||||
onToggle,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 16 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Animated, Easing, FlatList, Pressable, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -55,6 +56,7 @@ const TVLibraryRow: React.FC<{
|
||||
}> = ({ library, isFirst, onPress }) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const { t } = useTranslation();
|
||||
const typography = useScaledTVTypography();
|
||||
const [focused, setFocused] = useState(false);
|
||||
const scale = useRef(new Animated.Value(1)).current;
|
||||
const opacity = useRef(new Animated.Value(0.7)).current;
|
||||
@@ -190,7 +192,7 @@ const TVLibraryRow: React.FC<{
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: 32,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
textShadowColor: "rgba(0,0,0,0.8)",
|
||||
@@ -203,7 +205,7 @@ const TVLibraryRow: React.FC<{
|
||||
{library.itemCount !== undefined && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontSize: typography.body,
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
marginTop: 4,
|
||||
textShadowColor: "rgba(0,0,0,0.8)",
|
||||
@@ -237,6 +239,7 @@ export const TVLibraries: React.FC = () => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
const { data: userViews, isLoading: viewsLoading } = useQuery({
|
||||
queryKey: ["user-views", user?.Id],
|
||||
@@ -360,7 +363,7 @@ export const TVLibraries: React.FC = () => {
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 20, color: "#737373" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#737373" }}>
|
||||
{t("library.no_libraries_found")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
|
||||
@@ -44,6 +45,7 @@ export const TVLibraryCard: React.FC<Props> = ({ library }) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
const { t } = useTranslation();
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
const url = useMemo(
|
||||
() =>
|
||||
@@ -148,7 +150,7 @@ export const TVLibraryCard: React.FC<Props> = ({ library }) => {
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: 22,
|
||||
fontSize: typography.body,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginTop: 12,
|
||||
@@ -160,7 +162,7 @@ export const TVLibraryCard: React.FC<Props> = ({ library }) => {
|
||||
{itemsCount !== undefined && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 14,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 4,
|
||||
}}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Animated, FlatList, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { MediaStatus } from "@/utils/jellyseerr/server/constants/media";
|
||||
import type {
|
||||
@@ -27,6 +27,7 @@ const TVJellyseerrPoster: React.FC<TVJellyseerrPosterProps> = ({
|
||||
onPress,
|
||||
isFirstItem = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { jellyseerrApi, getTitle, getYear } = useJellyseerr();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
@@ -113,7 +114,7 @@ const TVJellyseerrPoster: React.FC<TVJellyseerrPosterProps> = ({
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
marginTop: 12,
|
||||
@@ -125,7 +126,7 @@ const TVJellyseerrPoster: React.FC<TVJellyseerrPosterProps> = ({
|
||||
{year && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -147,6 +148,7 @@ const TVJellyseerrPersonPoster: React.FC<TVJellyseerrPersonPosterProps> = ({
|
||||
item,
|
||||
onPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { jellyseerrApi } = useJellyseerr();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08 });
|
||||
@@ -202,7 +204,7 @@ const TVJellyseerrPersonPoster: React.FC<TVJellyseerrPersonPosterProps> = ({
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
fontWeight: "600",
|
||||
marginTop: 12,
|
||||
@@ -230,13 +232,14 @@ const TVJellyseerrMovieSection: React.FC<TVJellyseerrMovieSectionProps> = ({
|
||||
isFirstSection = false,
|
||||
onItemPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -281,13 +284,14 @@ const TVJellyseerrTvSection: React.FC<TVJellyseerrTvSectionProps> = ({
|
||||
isFirstSection = false,
|
||||
onItemPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -332,13 +336,14 @@ const TVJellyseerrPersonSection: React.FC<TVJellyseerrPersonSectionProps> = ({
|
||||
isFirstSection: _isFirstSection = false,
|
||||
onItemPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVSearchBadgeProps {
|
||||
label: string;
|
||||
@@ -14,6 +15,7 @@ export const TVSearchBadge: React.FC<TVSearchBadgeProps> = ({
|
||||
onPress,
|
||||
hasTVPreferredFocus = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08, duration: 150 });
|
||||
|
||||
@@ -41,7 +43,7 @@ export const TVSearchBadge: React.FC<TVSearchBadgeProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: focused ? "600" : "400",
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Input } from "@/components/common/Input";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVDiscover } from "@/components/jellyseerr/discover/TVDiscover";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
import type DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
|
||||
@@ -27,6 +27,7 @@ const SCALE_PADDING = 20;
|
||||
|
||||
// Loading skeleton for TV
|
||||
const TVLoadingSkeleton: React.FC = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const itemWidth = 210;
|
||||
return (
|
||||
<View style={{ overflow: "visible" }}>
|
||||
@@ -72,7 +73,7 @@ const TVLoadingSkeleton: React.FC = () => {
|
||||
color: "#262626",
|
||||
backgroundColor: "#262626",
|
||||
borderRadius: 6,
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -150,6 +151,7 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
||||
onJellyseerrPersonPress,
|
||||
discoverSliders,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [api] = useAtom(apiAtom);
|
||||
@@ -308,7 +310,7 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
||||
<View style={{ alignItems: "center", paddingTop: 40 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
@@ -318,7 +320,7 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -11,27 +11,28 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
const ITEM_GAP = 16;
|
||||
const SCALE_PADDING = 20;
|
||||
|
||||
// TV-specific ItemCardText with larger fonts
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
{item.Type === "Episode" ? (
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -45,7 +46,7 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
textAlign: "center",
|
||||
}}
|
||||
@@ -56,14 +57,14 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -75,14 +76,14 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -94,13 +95,13 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -111,7 +112,7 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
) : item.Type === "Person" ? (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
@@ -119,13 +120,13 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -158,6 +159,7 @@ export const TVSearchSection: React.FC<TVSearchSectionProps> = ({
|
||||
imageUrlGetter,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const flatListRef = useRef<FlatList<BaseItemDto>>(null);
|
||||
const [focusedCount, setFocusedCount] = useState(0);
|
||||
const prevFocusedCount = useRef(0);
|
||||
@@ -358,7 +360,7 @@ export const TVSearchSection: React.FC<TVSearchSectionProps> = ({
|
||||
{/* Section Header */}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
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";
|
||||
|
||||
type SearchType = "Library" | "Discover";
|
||||
|
||||
@@ -20,6 +21,7 @@ const TVSearchTabBadge: React.FC<TVSearchTabBadgeProps> = ({
|
||||
hasTVPreferredFocus = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08, duration: 150 });
|
||||
|
||||
@@ -61,7 +63,7 @@ const TVSearchTabBadge: React.FC<TVSearchTabBadgeProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: typography.callout,
|
||||
color: getTextColor(),
|
||||
fontWeight: isSelected || focused ? "600" : "400",
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ProgressBar } from "@/components/common/ProgressBar";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { WatchedIndicator } from "@/components/WatchedIndicator";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||
|
||||
@@ -33,6 +33,7 @@ export const TVEpisodeCard: React.FC<TVEpisodeCardProps> = ({
|
||||
onBlur,
|
||||
refSetter,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const api = useAtomValue(apiAtom);
|
||||
|
||||
const thumbnailUrl = useMemo(() => {
|
||||
@@ -112,7 +113,7 @@ export const TVEpisodeCard: React.FC<TVEpisodeCardProps> = ({
|
||||
{episodeLabel && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
@@ -122,14 +123,10 @@ export const TVEpisodeCard: React.FC<TVEpisodeCardProps> = ({
|
||||
)}
|
||||
{duration && (
|
||||
<>
|
||||
<Text
|
||||
style={{ color: "#6B7280", fontSize: TVTypography.callout }}
|
||||
>
|
||||
<Text style={{ color: "#6B7280", fontSize: typography.callout }}>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={{ fontSize: TVTypography.callout, color: "#9CA3AF" }}
|
||||
>
|
||||
<Text style={{ fontSize: typography.callout, color: "#9CA3AF" }}>
|
||||
{duration}
|
||||
</Text>
|
||||
</>
|
||||
@@ -138,7 +135,7 @@ export const TVEpisodeCard: React.FC<TVEpisodeCardProps> = ({
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
marginTop: 4,
|
||||
fontWeight: "500",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Dimensions, View } from "react-native";
|
||||
import { Badge } from "@/components/Badge";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { GenreTags } from "@/components/GenreTags";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||
|
||||
@@ -19,6 +19,7 @@ interface TVSeriesHeaderProps {
|
||||
}
|
||||
|
||||
export const TVSeriesHeader: React.FC<TVSeriesHeaderProps> = ({ item }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const api = useAtomValue(apiAtom);
|
||||
|
||||
const logoUrl = useMemo(() => {
|
||||
@@ -58,7 +59,7 @@ export const TVSeriesHeader: React.FC<TVSeriesHeaderProps> = ({ item }) => {
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.display,
|
||||
fontSize: typography.display,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -80,7 +81,7 @@ export const TVSeriesHeader: React.FC<TVSeriesHeaderProps> = ({ item }) => {
|
||||
}}
|
||||
>
|
||||
{yearString && (
|
||||
<Text style={{ color: "white", fontSize: TVTypography.body }}>
|
||||
<Text style={{ color: "white", fontSize: typography.body }}>
|
||||
{yearString}
|
||||
</Text>
|
||||
)}
|
||||
@@ -123,7 +124,7 @@ export const TVSeriesHeader: React.FC<TVSeriesHeaderProps> = ({ item }) => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#E5E7EB",
|
||||
lineHeight: 32,
|
||||
}}
|
||||
|
||||
@@ -29,7 +29,7 @@ import { getItemNavigation } from "@/components/common/TouchableItemRouter";
|
||||
import { seasonIndexAtom } from "@/components/series/SeasonPicker";
|
||||
import { TVEpisodeCard } from "@/components/series/TVEpisodeCard";
|
||||
import { TVSeriesHeader } from "@/components/series/TVSeriesHeader";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useTVSeriesSeasonModal } from "@/hooks/useTVSeriesSeasonModal";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
@@ -142,6 +142,7 @@ const TVSeasonButton: React.FC<{
|
||||
onPress: () => void;
|
||||
disabled?: boolean;
|
||||
}> = ({ seasonName, onPress, disabled = false }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const [focused, setFocused] = useState(false);
|
||||
const scale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
@@ -190,7 +191,7 @@ const TVSeasonButton: React.FC<{
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: focused ? "#000" : "#FFFFFF",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
@@ -213,6 +214,7 @@ export const TVSeriesPage: React.FC<TVSeriesPageProps> = ({
|
||||
allEpisodes = [],
|
||||
isLoading: _isLoading,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const router = useRouter();
|
||||
@@ -567,7 +569,7 @@ export const TVSeriesPage: React.FC<TVSeriesPageProps> = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
fontWeight: "bold",
|
||||
color: "#000000",
|
||||
}}
|
||||
@@ -591,7 +593,7 @@ export const TVSeriesPage: React.FC<TVSeriesPageProps> = ({
|
||||
<View style={{ marginTop: 40, overflow: "visible" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -646,7 +648,7 @@ export const TVSeriesPage: React.FC<TVSeriesPageProps> = ({
|
||||
<Text
|
||||
style={{
|
||||
color: "#737373",
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
marginLeft: SCALE_PADDING,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Image } from "expo-image";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVActorCardProps {
|
||||
@@ -19,6 +19,7 @@ export interface TVActorCardProps {
|
||||
|
||||
export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
({ person, apiBasePath, onPress, hasTVPreferredFocus }, ref) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08 });
|
||||
|
||||
@@ -84,7 +85,7 @@ export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
fontWeight: "600",
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
textAlign: "center",
|
||||
@@ -98,7 +99,7 @@ export const TVActorCard = React.forwardRef<View, TVActorCardProps>(
|
||||
{person.Role && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused
|
||||
? "rgba(255,255,255,0.8)"
|
||||
: "rgba(255,255,255,0.5)",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVCancelButtonProps {
|
||||
@@ -16,6 +16,7 @@ export const TVCancelButton: React.FC<TVCancelButtonProps> = ({
|
||||
label = "Cancel",
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05, duration: 120 });
|
||||
|
||||
@@ -48,7 +49,7 @@ export const TVCancelButton: React.FC<TVCancelButtonProps> = ({
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "rgba(255,255,255,0.8)",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVCastCrewTextProps {
|
||||
director?: BaseItemPerson | null;
|
||||
@@ -14,6 +14,7 @@ export interface TVCastCrewTextProps {
|
||||
|
||||
export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
({ director, cast, hideCast = false }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!director && (!cast || cast.length === 0)) {
|
||||
@@ -24,7 +25,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
<View style={{ marginBottom: 32 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -37,7 +38,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
@@ -46,7 +47,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
>
|
||||
{t("item_card.director")}
|
||||
</Text>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{director.Name}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -55,7 +56,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
@@ -64,7 +65,7 @@ export const TVCastCrewText: React.FC<TVCastCrewTextProps> = React.memo(
|
||||
>
|
||||
{t("item_card.cast")}
|
||||
</Text>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{cast.map((c) => c.Name).join(", ")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ScrollView, TVFocusGuideView, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { TVActorCard } from "./TVActorCard";
|
||||
|
||||
export interface TVCastSectionProps {
|
||||
@@ -24,6 +24,7 @@ export const TVCastSection: React.FC<TVCastSectionProps> = React.memo(
|
||||
firstActorRefSetter,
|
||||
upwardFocusDestination,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (cast.length === 0) {
|
||||
@@ -34,7 +35,7 @@ export const TVCastSection: React.FC<TVCastSectionProps> = React.memo(
|
||||
<View style={{ marginBottom: 40 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 24,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVFilterButtonProps {
|
||||
@@ -21,6 +21,7 @@ export const TVFilterButton: React.FC<TVFilterButtonProps> = ({
|
||||
disabled = false,
|
||||
hasActiveFilter = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.04, duration: 120 });
|
||||
|
||||
@@ -54,7 +55,7 @@ export const TVFilterButton: React.FC<TVFilterButtonProps> = ({
|
||||
{label ? (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#444" : "#bbb",
|
||||
}}
|
||||
>
|
||||
@@ -63,7 +64,7 @@ export const TVFilterButton: React.FC<TVFilterButtonProps> = ({
|
||||
) : null}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "#FFFFFF",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
|
||||
@@ -2,28 +2,32 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVItemCardTextProps {
|
||||
item: BaseItemDto;
|
||||
}
|
||||
|
||||
export const TVItemCardText: React.FC<TVItemCardTextProps> = ({ item }) => (
|
||||
<View style={{ marginTop: 12 }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout - 2,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{item.ProductionYear}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
export const TVItemCardText: React.FC<TVItemCardTextProps> = ({ item }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
return (
|
||||
<View style={{ marginTop: 12 }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.callout - 2,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{item.ProductionYear}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, StyleSheet, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVLanguageCardProps {
|
||||
@@ -15,6 +15,8 @@ export interface TVLanguageCardProps {
|
||||
|
||||
export const TVLanguageCard = React.forwardRef<View, TVLanguageCardProps>(
|
||||
({ code, name, selected, hasTVPreferredFocus, onPress }, ref) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const styles = createStyles(typography);
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
|
||||
@@ -72,26 +74,27 @@ export const TVLanguageCard = React.forwardRef<View, TVLanguageCardProps>(
|
||||
},
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
languageCard: {
|
||||
width: 120,
|
||||
height: 60,
|
||||
borderRadius: 12,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
languageCardText: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "500",
|
||||
},
|
||||
languageCardCode: {
|
||||
fontSize: TVTypography.callout,
|
||||
marginTop: 2,
|
||||
},
|
||||
checkmark: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
},
|
||||
});
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
languageCard: {
|
||||
width: 120,
|
||||
height: 60,
|
||||
borderRadius: 12,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
languageCardText: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "500",
|
||||
},
|
||||
languageCardCode: {
|
||||
fontSize: typography.callout,
|
||||
marginTop: 2,
|
||||
},
|
||||
checkmark: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { Badge } from "@/components/Badge";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVMetadataBadgesProps {
|
||||
year?: number | null;
|
||||
@@ -14,6 +14,8 @@ export interface TVMetadataBadgesProps {
|
||||
|
||||
export const TVMetadataBadges: React.FC<TVMetadataBadgesProps> = React.memo(
|
||||
({ year, duration, officialRating, communityRating }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -25,12 +27,12 @@ export const TVMetadataBadges: React.FC<TVMetadataBadgesProps> = React.memo(
|
||||
}}
|
||||
>
|
||||
{year != null && (
|
||||
<Text style={{ color: "white", fontSize: TVTypography.body }}>
|
||||
<Text style={{ color: "white", fontSize: typography.body }}>
|
||||
{year}
|
||||
</Text>
|
||||
)}
|
||||
{duration && (
|
||||
<Text style={{ color: "white", fontSize: TVTypography.body }}>
|
||||
<Text style={{ color: "white", fontSize: typography.body }}>
|
||||
{duration}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Api } from "@jellyfin/sdk";
|
||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { type FC, useEffect, useRef } from "react";
|
||||
import { type FC, useEffect, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Image, StyleSheet, View } from "react-native";
|
||||
import Animated, {
|
||||
@@ -13,7 +13,7 @@ import Animated, {
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
|
||||
export interface TVNextEpisodeCountdownProps {
|
||||
@@ -31,6 +31,7 @@ export const TVNextEpisodeCountdown: FC<TVNextEpisodeCountdownProps> = ({
|
||||
isPlaying,
|
||||
onFinish,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const progress = useSharedValue(0);
|
||||
const onFinishRef = useRef(onFinish);
|
||||
@@ -69,6 +70,8 @@ export const TVNextEpisodeCountdown: FC<TVNextEpisodeCountdownProps> = ({
|
||||
width: `${progress.value * 100}%`,
|
||||
}));
|
||||
|
||||
const styles = useMemo(() => createStyles(typography), [typography]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
@@ -105,57 +108,58 @@ export const TVNextEpisodeCountdown: FC<TVNextEpisodeCountdownProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: "absolute",
|
||||
bottom: 180,
|
||||
right: 80,
|
||||
zIndex: 100,
|
||||
},
|
||||
blur: {
|
||||
borderRadius: 16,
|
||||
overflow: "hidden",
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "stretch",
|
||||
},
|
||||
thumbnail: {
|
||||
width: 180,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
},
|
||||
content: {
|
||||
padding: 16,
|
||||
justifyContent: "center",
|
||||
width: 280,
|
||||
},
|
||||
label: {
|
||||
fontSize: TVTypography.callout,
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
},
|
||||
seriesName: {
|
||||
fontSize: TVTypography.callout,
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
marginBottom: 2,
|
||||
},
|
||||
episodeInfo: {
|
||||
fontSize: TVTypography.body,
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
marginBottom: 12,
|
||||
},
|
||||
progressContainer: {
|
||||
height: 4,
|
||||
backgroundColor: "rgba(255,255,255,0.2)",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
},
|
||||
progressBar: {
|
||||
height: "100%",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 2,
|
||||
},
|
||||
});
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
position: "absolute",
|
||||
bottom: 180,
|
||||
right: 80,
|
||||
zIndex: 100,
|
||||
},
|
||||
blur: {
|
||||
borderRadius: 16,
|
||||
overflow: "hidden",
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "stretch",
|
||||
},
|
||||
thumbnail: {
|
||||
width: 180,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
},
|
||||
content: {
|
||||
padding: 16,
|
||||
justifyContent: "center",
|
||||
width: 280,
|
||||
},
|
||||
label: {
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginBottom: 4,
|
||||
},
|
||||
seriesName: {
|
||||
fontSize: typography.callout,
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
marginBottom: 2,
|
||||
},
|
||||
episodeInfo: {
|
||||
fontSize: typography.body,
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
marginBottom: 12,
|
||||
},
|
||||
progressContainer: {
|
||||
height: 4,
|
||||
backgroundColor: "rgba(255,255,255,0.2)",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
},
|
||||
progressBar: {
|
||||
height: "100%",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 2,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BlurView } from "expo-blur";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVOptionButtonProps {
|
||||
@@ -14,6 +14,7 @@ export interface TVOptionButtonProps {
|
||||
|
||||
export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
({ label, value, onPress, hasTVPreferredFocus }, ref) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.02, duration: 120 });
|
||||
|
||||
@@ -50,7 +51,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#444",
|
||||
}}
|
||||
>
|
||||
@@ -58,7 +59,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#000",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
@@ -88,7 +89,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#bbb",
|
||||
}}
|
||||
>
|
||||
@@ -96,7 +97,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#E5E7EB",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVOptionCardProps {
|
||||
@@ -28,6 +28,7 @@ export const TVOptionCard = React.forwardRef<View, TVOptionCardProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
|
||||
@@ -59,7 +60,7 @@ export const TVOptionCard = React.forwardRef<View, TVOptionCardProps>(
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: focused || selected ? "600" : "400",
|
||||
textAlign: "center",
|
||||
@@ -71,7 +72,7 @@ export const TVOptionCard = React.forwardRef<View, TVOptionCardProps>(
|
||||
{sublabel && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)",
|
||||
textAlign: "center",
|
||||
marginTop: 2,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { TVCancelButton } from "./TVCancelButton";
|
||||
import { TVOptionCard } from "./TVOptionCard";
|
||||
|
||||
@@ -41,6 +41,7 @@ export const TVOptionSelector = <T,>({
|
||||
cardWidth = 160,
|
||||
cardHeight = 75,
|
||||
}: TVOptionSelectorProps<T>) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const firstCardRef = useRef<View>(null);
|
||||
|
||||
@@ -91,6 +92,8 @@ export const TVOptionSelector = <T,>({
|
||||
}
|
||||
}, [isReady]);
|
||||
|
||||
const styles = useMemo(() => createStyles(typography), [typography]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
@@ -151,50 +154,51 @@ export const TVOptionSelector = <T,>({
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
justifyContent: "flex-end",
|
||||
zIndex: 1000,
|
||||
},
|
||||
sheetContainer: {
|
||||
width: "100%",
|
||||
},
|
||||
blurContainer: {
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 50,
|
||||
overflow: "visible",
|
||||
},
|
||||
title: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "500",
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 16,
|
||||
paddingHorizontal: 48,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
scrollView: {
|
||||
overflow: "visible",
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 48,
|
||||
paddingVertical: 20,
|
||||
gap: 12,
|
||||
},
|
||||
cancelButtonContainer: {
|
||||
marginTop: 16,
|
||||
paddingHorizontal: 48,
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
});
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
overlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
justifyContent: "flex-end",
|
||||
zIndex: 1000,
|
||||
},
|
||||
sheetContainer: {
|
||||
width: "100%",
|
||||
},
|
||||
blurContainer: {
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
overflow: "hidden",
|
||||
},
|
||||
content: {
|
||||
paddingTop: 24,
|
||||
paddingBottom: 50,
|
||||
overflow: "visible",
|
||||
},
|
||||
title: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "500",
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
marginBottom: 16,
|
||||
paddingHorizontal: 48,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
},
|
||||
scrollView: {
|
||||
overflow: "visible",
|
||||
},
|
||||
scrollContent: {
|
||||
paddingHorizontal: 48,
|
||||
paddingVertical: 20,
|
||||
gap: 12,
|
||||
},
|
||||
cancelButtonContainer: {
|
||||
marginTop: 16,
|
||||
paddingHorizontal: 48,
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { TVSeriesSeasonCard } from "./TVSeriesSeasonCard";
|
||||
|
||||
export interface TVSeriesNavigationProps {
|
||||
@@ -16,6 +16,7 @@ export interface TVSeriesNavigationProps {
|
||||
|
||||
export const TVSeriesNavigation: React.FC<TVSeriesNavigationProps> = React.memo(
|
||||
({ item, seriesImageUrl, seasonImageUrl, onSeriesPress, onSeasonPress }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Only show for episodes with a series
|
||||
@@ -27,7 +28,7 @@ export const TVSeriesNavigation: React.FC<TVSeriesNavigationProps> = React.memo(
|
||||
<View style={{ marginBottom: 32 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 24,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Image } from "expo-image";
|
||||
import React from "react";
|
||||
import { Animated, Platform, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import {
|
||||
GlassPosterView,
|
||||
isGlassEffectAvailable,
|
||||
@@ -25,6 +25,7 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
onPress,
|
||||
hasTVPreferredFocus,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
|
||||
@@ -104,7 +105,7 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
fontWeight: "600",
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
textAlign: "center",
|
||||
@@ -118,7 +119,7 @@ export const TVSeriesSeasonCard: React.FC<TVSeriesSeasonCardProps> = ({
|
||||
{subtitle && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused
|
||||
? "rgba(255,255,255,0.8)"
|
||||
: "rgba(255,255,255,0.5)",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import type { SubtitleSearchResult } from "@/hooks/useRemoteSubtitles";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
@@ -23,6 +23,8 @@ export const TVSubtitleResultCard = React.forwardRef<
|
||||
View,
|
||||
TVSubtitleResultCardProps
|
||||
>(({ result, hasTVPreferredFocus, isDownloading, onPress }, ref) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const styles = createStyles(typography);
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.03 });
|
||||
|
||||
@@ -197,72 +199,73 @@ export const TVSubtitleResultCard = React.forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
resultCard: {
|
||||
width: 220,
|
||||
minHeight: 120,
|
||||
borderRadius: 14,
|
||||
padding: 14,
|
||||
borderWidth: 1,
|
||||
},
|
||||
providerBadge: {
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
marginBottom: 8,
|
||||
},
|
||||
providerText: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "600",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
resultName: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "500",
|
||||
marginBottom: 8,
|
||||
lineHeight: 18,
|
||||
},
|
||||
resultMeta: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
marginBottom: 8,
|
||||
},
|
||||
resultMetaText: {
|
||||
fontSize: TVTypography.callout,
|
||||
},
|
||||
ratingContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
},
|
||||
downloadCountContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
},
|
||||
flagsContainer: {
|
||||
flexDirection: "row",
|
||||
gap: 6,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
flag: {
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
},
|
||||
flagText: {
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#fff",
|
||||
},
|
||||
downloadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
borderRadius: 14,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
resultCard: {
|
||||
width: 220,
|
||||
minHeight: 120,
|
||||
borderRadius: 14,
|
||||
padding: 14,
|
||||
borderWidth: 1,
|
||||
},
|
||||
providerBadge: {
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
marginBottom: 8,
|
||||
},
|
||||
providerText: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
resultName: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "500",
|
||||
marginBottom: 8,
|
||||
lineHeight: 18,
|
||||
},
|
||||
resultMeta: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
marginBottom: 8,
|
||||
},
|
||||
resultMetaText: {
|
||||
fontSize: typography.callout,
|
||||
},
|
||||
ratingContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
},
|
||||
downloadCountContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
},
|
||||
flagsContainer: {
|
||||
flexDirection: "row",
|
||||
gap: 6,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
flag: {
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
},
|
||||
flagText: {
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#fff",
|
||||
},
|
||||
downloadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
borderRadius: 14,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Animated, Pressable } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVTabButtonProps {
|
||||
@@ -21,6 +21,7 @@ export const TVTabButton: React.FC<TVTabButtonProps> = ({
|
||||
switchOnFocus = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({
|
||||
scaleAmount: 1.05,
|
||||
@@ -56,7 +57,7 @@ export const TVTabButton: React.FC<TVTabButtonProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: focused || active ? "600" : "400",
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVTechnicalDetailsProps {
|
||||
mediaStreams: MediaStream[];
|
||||
@@ -11,6 +11,7 @@ export interface TVTechnicalDetailsProps {
|
||||
|
||||
export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
({ mediaStreams }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const videoStream = mediaStreams.find((s) => s.Type === "Video");
|
||||
@@ -24,7 +25,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
<View style={{ marginBottom: 32 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "600",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
@@ -37,7 +38,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
@@ -46,7 +47,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
>
|
||||
Video
|
||||
</Text>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{videoStream.DisplayTitle ||
|
||||
`${videoStream.Codec?.toUpperCase()} ${videoStream.Width}x${videoStream.Height}`}
|
||||
</Text>
|
||||
@@ -56,7 +57,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#6B7280",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
@@ -65,7 +66,7 @@ export const TVTechnicalDetails: React.FC<TVTechnicalDetailsProps> = React.memo(
|
||||
>
|
||||
Audio
|
||||
</Text>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{audioStream.DisplayTitle ||
|
||||
`${audioStream.Codec?.toUpperCase()} ${audioStream.Channels}ch`}
|
||||
</Text>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, StyleSheet, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVTrackCardProps {
|
||||
@@ -15,6 +15,8 @@ export interface TVTrackCardProps {
|
||||
|
||||
export const TVTrackCard = React.forwardRef<View, TVTrackCardProps>(
|
||||
({ label, sublabel, selected, hasTVPreferredFocus, onPress }, ref) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const styles = createStyles(typography);
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
|
||||
@@ -77,26 +79,27 @@ export const TVTrackCard = React.forwardRef<View, TVTrackCardProps>(
|
||||
},
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
trackCard: {
|
||||
width: 180,
|
||||
height: 80,
|
||||
borderRadius: 14,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
trackCardText: {
|
||||
fontSize: TVTypography.callout,
|
||||
textAlign: "center",
|
||||
},
|
||||
trackCardSublabel: {
|
||||
fontSize: TVTypography.callout,
|
||||
marginTop: 2,
|
||||
},
|
||||
checkmark: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
},
|
||||
});
|
||||
const createStyles = (typography: ReturnType<typeof useScaledTVTypography>) =>
|
||||
StyleSheet.create({
|
||||
trackCard: {
|
||||
width: 180,
|
||||
height: 80,
|
||||
borderRadius: 14,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
trackCardText: {
|
||||
fontSize: typography.callout,
|
||||
textAlign: "center",
|
||||
},
|
||||
trackCardSublabel: {
|
||||
fontSize: typography.callout,
|
||||
marginTop: 2,
|
||||
},
|
||||
checkmark: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVLogoutButtonProps {
|
||||
@@ -15,6 +15,7 @@ export const TVLogoutButton: React.FC<TVLogoutButtonProps> = ({
|
||||
disabled,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
|
||||
@@ -49,7 +50,7 @@ export const TVLogoutButton: React.FC<TVLogoutButtonProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import React from "react";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVSectionHeaderProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const TVSectionHeader: React.FC<TVSectionHeaderProps> = ({ title }) => (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#9CA3AF",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
marginLeft: 8,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
export const TVSectionHeader: React.FC<TVSectionHeaderProps> = ({ title }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
fontWeight: "600",
|
||||
color: "#9CA3AF",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 1,
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
marginLeft: 8,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsOptionButtonProps {
|
||||
@@ -20,6 +20,7 @@ export const TVSettingsOptionButton: React.FC<TVSettingsOptionButtonProps> = ({
|
||||
isFirst,
|
||||
disabled,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.02 });
|
||||
|
||||
@@ -49,13 +50,13 @@ export const TVSettingsOptionButton: React.FC<TVSettingsOptionButtonProps> = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{label}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginRight: 12,
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsRowProps {
|
||||
@@ -22,6 +22,7 @@ export const TVSettingsRow: React.FC<TVSettingsRowProps> = ({
|
||||
showChevron = true,
|
||||
disabled,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.02 });
|
||||
|
||||
@@ -51,13 +52,13 @@ export const TVSettingsRow: React.FC<TVSettingsRowProps> = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{label}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginRight: showChevron ? 12 : 0,
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsStepperProps {
|
||||
@@ -24,6 +24,7 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
isFirst,
|
||||
disabled,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const labelAnim = useTVFocusAnimation({ scaleAmount: 1.02 });
|
||||
const minusAnim = useTVFocusAnimation({ scaleAmount: 1.1 });
|
||||
const plusAnim = useTVFocusAnimation({ scaleAmount: 1.1 });
|
||||
@@ -54,7 +55,7 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
focusable={!disabled}
|
||||
>
|
||||
<Animated.View style={labelAnim.animatedStyle}>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{label}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
@@ -89,7 +90,7 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
||||
</Pressable>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
minWidth: 60,
|
||||
textAlign: "center",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useRef } from "react";
|
||||
import { Animated, Pressable, TextInput } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsTextInputProps {
|
||||
@@ -23,6 +23,7 @@ export const TVSettingsTextInput: React.FC<TVSettingsTextInputProps> = ({
|
||||
secureTextEntry,
|
||||
disabled,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.02 });
|
||||
@@ -56,7 +57,7 @@ export const TVSettingsTextInput: React.FC<TVSettingsTextInputProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginBottom: 8,
|
||||
}}
|
||||
@@ -74,7 +75,7 @@ export const TVSettingsTextInput: React.FC<TVSettingsTextInputProps> = ({
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
||||
borderRadius: 8,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useTVFocusAnimation } from "../hooks/useTVFocusAnimation";
|
||||
|
||||
export interface TVSettingsToggleProps {
|
||||
@@ -19,6 +19,7 @@ export const TVSettingsToggle: React.FC<TVSettingsToggleProps> = ({
|
||||
isFirst,
|
||||
disabled,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.02 });
|
||||
|
||||
@@ -48,7 +49,7 @@ export const TVSettingsToggle: React.FC<TVSettingsToggleProps> = ({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={{ fontSize: TVTypography.body, color: "#FFFFFF" }}>
|
||||
<Text style={{ fontSize: typography.body, color: "#FFFFFF" }}>
|
||||
{label}
|
||||
</Text>
|
||||
<View
|
||||
|
||||
@@ -31,7 +31,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVControlButton, TVNextEpisodeCountdown } from "@/components/tv";
|
||||
import { TVFocusableProgressBar } from "@/components/tv/TVFocusableProgressBar";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { usePlaybackManager } from "@/hooks/usePlaybackManager";
|
||||
import { useTrickplay } from "@/hooks/useTrickplay";
|
||||
@@ -206,6 +206,7 @@ export const Controls: FC<Props> = ({
|
||||
playMethod,
|
||||
transcodeReasons,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const { t } = useTranslation();
|
||||
@@ -973,14 +974,16 @@ export const Controls: FC<Props> = ({
|
||||
</View>
|
||||
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.timeText}>
|
||||
<Text style={[styles.timeText, { fontSize: typography.body }]}>
|
||||
{formatTimeString(currentTime, "ms")}
|
||||
</Text>
|
||||
<View style={styles.timeRight}>
|
||||
<Text style={styles.timeText}>
|
||||
<Text style={[styles.timeText, { fontSize: typography.body }]}>
|
||||
-{formatTimeString(remainingTime, "ms")}
|
||||
</Text>
|
||||
<Text style={styles.endsAtText}>
|
||||
<Text
|
||||
style={[styles.endsAtText, { fontSize: typography.callout }]}
|
||||
>
|
||||
{t("player.ends_at")} {getFinishTime()}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -1006,12 +1009,18 @@ export const Controls: FC<Props> = ({
|
||||
<View style={styles.metadataContainer}>
|
||||
{item?.Type === "Episode" && (
|
||||
<Text
|
||||
style={styles.subtitleText}
|
||||
style={[styles.subtitleText, { fontSize: typography.body }]}
|
||||
>{`${item.SeriesName} - ${item.SeasonName} Episode ${item.IndexNumber}`}</Text>
|
||||
)}
|
||||
<Text style={styles.titleText}>{item?.Name}</Text>
|
||||
<Text style={[styles.titleText, { fontSize: typography.heading }]}>
|
||||
{item?.Name}
|
||||
</Text>
|
||||
{item?.Type === "Movie" && (
|
||||
<Text style={styles.subtitleText}>{item?.ProductionYear}</Text>
|
||||
<Text
|
||||
style={[styles.subtitleText, { fontSize: typography.body }]}
|
||||
>
|
||||
{item?.ProductionYear}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1110,14 +1119,16 @@ export const Controls: FC<Props> = ({
|
||||
</TVFocusGuideView>
|
||||
|
||||
<View style={styles.timeContainer}>
|
||||
<Text style={styles.timeText}>
|
||||
<Text style={[styles.timeText, { fontSize: typography.body }]}>
|
||||
{formatTimeString(currentTime, "ms")}
|
||||
</Text>
|
||||
<View style={styles.timeRight}>
|
||||
<Text style={styles.timeText}>
|
||||
<Text style={[styles.timeText, { fontSize: typography.body }]}>
|
||||
-{formatTimeString(remainingTime, "ms")}
|
||||
</Text>
|
||||
<Text style={styles.endsAtText}>
|
||||
<Text
|
||||
style={[styles.endsAtText, { fontSize: typography.callout }]}
|
||||
>
|
||||
{t("player.ends_at")} {getFinishTime()}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -1151,11 +1162,9 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
subtitleText: {
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
fontSize: TVTypography.body,
|
||||
},
|
||||
titleText: {
|
||||
color: "#fff",
|
||||
fontSize: TVTypography.heading,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
controlButtonsRow: {
|
||||
@@ -1218,7 +1227,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
timeText: {
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
fontSize: TVTypography.body,
|
||||
},
|
||||
timeRight: {
|
||||
flexDirection: "column",
|
||||
@@ -1226,7 +1234,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
endsAtText: {
|
||||
color: "rgba(255,255,255,0.5)",
|
||||
fontSize: TVTypography.callout,
|
||||
marginTop: 2,
|
||||
},
|
||||
// Minimal seek bar styles
|
||||
|
||||
@@ -15,7 +15,7 @@ import Animated, {
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import type { TechnicalInfo } from "@/modules/mpv-player";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { HEADER_LAYOUT } from "./constants";
|
||||
@@ -183,6 +183,7 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
currentSubtitleIndex,
|
||||
currentAudioIndex,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { settings } = useSettings();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [info, setInfo] = useState<TechnicalInfo | null>(null);
|
||||
@@ -277,8 +278,15 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
: HEADER_LAYOUT.CONTAINER_PADDING + 20,
|
||||
};
|
||||
|
||||
const textStyle = Platform.isTV ? styles.infoTextTV : styles.infoText;
|
||||
const reasonStyle = Platform.isTV ? styles.reasonTextTV : styles.reasonText;
|
||||
const textStyle = Platform.isTV
|
||||
? [
|
||||
styles.infoTextTV,
|
||||
{ fontSize: typography.body, lineHeight: typography.body * 1.5 },
|
||||
]
|
||||
: styles.infoText;
|
||||
const reasonStyle = Platform.isTV
|
||||
? [styles.reasonTextTV, { fontSize: typography.callout }]
|
||||
: styles.reasonText;
|
||||
const boxStyle = Platform.isTV ? styles.infoBoxTV : styles.infoBox;
|
||||
|
||||
return (
|
||||
@@ -383,9 +391,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
infoTextTV: {
|
||||
color: "white",
|
||||
fontSize: TVTypography.body,
|
||||
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
|
||||
lineHeight: TVTypography.body * 1.5,
|
||||
},
|
||||
warningText: {
|
||||
color: "#ff9800",
|
||||
@@ -396,6 +402,5 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
reasonTextTV: {
|
||||
color: "#fbbf24",
|
||||
fontSize: TVTypography.callout,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TVTypographyScale, useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
/**
|
||||
* TV Typography Scale
|
||||
*
|
||||
@@ -23,3 +25,29 @@ export const TVTypography = {
|
||||
} as const;
|
||||
|
||||
export type TVTypographyKey = keyof typeof TVTypography;
|
||||
|
||||
const scaleMultipliers: Record<TVTypographyScale, number> = {
|
||||
[TVTypographyScale.Small]: 0.85,
|
||||
[TVTypographyScale.Default]: 1.0,
|
||||
[TVTypographyScale.Large]: 1.15,
|
||||
[TVTypographyScale.ExtraLarge]: 1.3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook that returns scaled TV typography values based on user settings.
|
||||
* Use this instead of the static TVTypography constant for dynamic scaling.
|
||||
*/
|
||||
export const useScaledTVTypography = () => {
|
||||
const { settings } = useSettings();
|
||||
const scale =
|
||||
scaleMultipliers[settings.tvTypographyScale] ??
|
||||
scaleMultipliers[TVTypographyScale.Default];
|
||||
|
||||
return {
|
||||
display: Math.round(TVTypography.display * scale),
|
||||
title: Math.round(TVTypography.title * scale),
|
||||
heading: Math.round(TVTypography.heading * scale),
|
||||
body: Math.round(TVTypography.body * scale),
|
||||
callout: Math.round(TVTypography.callout * scale),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -126,7 +126,12 @@
|
||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||
"hide_remote_session_button": "Hide Remote Session Button",
|
||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||
"show_hero_carousel": "Hero Carousel"
|
||||
"show_hero_carousel": "Hero Carousel",
|
||||
"text_size": "Text Size",
|
||||
"text_size_small": "Small",
|
||||
"text_size_default": "Default",
|
||||
"text_size_large": "Large",
|
||||
"text_size_extra_large": "Extra Large"
|
||||
},
|
||||
"network": {
|
||||
"title": "Network",
|
||||
|
||||
@@ -134,6 +134,14 @@ export enum VideoPlayer {
|
||||
MPV = 0,
|
||||
}
|
||||
|
||||
// TV Typography scale presets
|
||||
export enum TVTypographyScale {
|
||||
Small = "small",
|
||||
Default = "default",
|
||||
Large = "large",
|
||||
ExtraLarge = "extraLarge",
|
||||
}
|
||||
|
||||
// Audio transcoding mode - controls how surround audio is handled
|
||||
// This controls server-side transcoding behavior for audio streams.
|
||||
// MPV decodes via FFmpeg and supports most formats, but mobile devices
|
||||
@@ -202,6 +210,7 @@ export type Settings = {
|
||||
// TV-specific settings
|
||||
showHomeBackdrop: boolean;
|
||||
showTVHeroCarousel: boolean;
|
||||
tvTypographyScale: TVTypographyScale;
|
||||
// Appearance
|
||||
hideRemoteSessionButton: boolean;
|
||||
hideWatchlistsTab: boolean;
|
||||
@@ -291,6 +300,7 @@ export const defaultValues: Settings = {
|
||||
// TV-specific settings
|
||||
showHomeBackdrop: true,
|
||||
showTVHeroCarousel: true,
|
||||
tvTypographyScale: TVTypographyScale.Default,
|
||||
// Appearance
|
||||
hideRemoteSessionButton: false,
|
||||
hideWatchlistsTab: false,
|
||||
|
||||
Reference in New Issue
Block a user