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