mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-28 19:36:29 +01:00
feat(tv): add scalable typography with user-configurable text size
This commit is contained in:
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Animated, FlatList, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { MediaStatus } from "@/utils/jellyseerr/server/constants/media";
|
||||
import type {
|
||||
@@ -27,6 +27,7 @@ const TVJellyseerrPoster: React.FC<TVJellyseerrPosterProps> = ({
|
||||
onPress,
|
||||
isFirstItem = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { jellyseerrApi, getTitle, getYear } = useJellyseerr();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.05 });
|
||||
@@ -113,7 +114,7 @@ const TVJellyseerrPoster: React.FC<TVJellyseerrPosterProps> = ({
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#fff",
|
||||
fontWeight: "600",
|
||||
marginTop: 12,
|
||||
@@ -125,7 +126,7 @@ const TVJellyseerrPoster: React.FC<TVJellyseerrPosterProps> = ({
|
||||
{year && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -147,6 +148,7 @@ const TVJellyseerrPersonPoster: React.FC<TVJellyseerrPersonPosterProps> = ({
|
||||
item,
|
||||
onPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { jellyseerrApi } = useJellyseerr();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08 });
|
||||
@@ -202,7 +204,7 @@ const TVJellyseerrPersonPoster: React.FC<TVJellyseerrPersonPosterProps> = ({
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#fff" : "rgba(255,255,255,0.9)",
|
||||
fontWeight: "600",
|
||||
marginTop: 12,
|
||||
@@ -230,13 +232,14 @@ const TVJellyseerrMovieSection: React.FC<TVJellyseerrMovieSectionProps> = ({
|
||||
isFirstSection = false,
|
||||
onItemPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -281,13 +284,14 @@ const TVJellyseerrTvSection: React.FC<TVJellyseerrTvSectionProps> = ({
|
||||
isFirstSection = false,
|
||||
onItemPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
@@ -332,13 +336,14 @@ const TVJellyseerrPersonSection: React.FC<TVJellyseerrPersonSectionProps> = ({
|
||||
isFirstSection: _isFirstSection = false,
|
||||
onItemPress,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<View style={{ marginBottom: 24 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 16,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
export interface TVSearchBadgeProps {
|
||||
label: string;
|
||||
@@ -14,6 +15,7 @@ export const TVSearchBadge: React.FC<TVSearchBadgeProps> = ({
|
||||
onPress,
|
||||
hasTVPreferredFocus = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08, duration: 150 });
|
||||
|
||||
@@ -41,7 +43,7 @@ export const TVSearchBadge: React.FC<TVSearchBadgeProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: typography.callout,
|
||||
color: focused ? "#000" : "#fff",
|
||||
fontWeight: focused ? "600" : "400",
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Input } from "@/components/common/Input";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TVDiscover } from "@/components/jellyseerr/discover/TVDiscover";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
import type DiscoverSlider from "@/utils/jellyseerr/server/entity/DiscoverSlider";
|
||||
@@ -27,6 +27,7 @@ const SCALE_PADDING = 20;
|
||||
|
||||
// Loading skeleton for TV
|
||||
const TVLoadingSkeleton: React.FC = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const itemWidth = 210;
|
||||
return (
|
||||
<View style={{ overflow: "visible" }}>
|
||||
@@ -72,7 +73,7 @@ const TVLoadingSkeleton: React.FC = () => {
|
||||
color: "#262626",
|
||||
backgroundColor: "#262626",
|
||||
borderRadius: 6,
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -150,6 +151,7 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
||||
onJellyseerrPersonPress,
|
||||
discoverSliders,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [api] = useAtom(apiAtom);
|
||||
@@ -308,7 +310,7 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
||||
<View style={{ alignItems: "center", paddingTop: 40 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 8,
|
||||
@@ -318,7 +320,7 @@ export const TVSearchPage: React.FC<TVSearchPageProps> = ({
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -11,27 +11,28 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
const ITEM_GAP = 16;
|
||||
const SCALE_PADDING = 20;
|
||||
|
||||
// TV-specific ItemCardText with larger fonts
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const typography = useScaledTVTypography();
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
{item.Type === "Episode" ? (
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -45,7 +46,7 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
textAlign: "center",
|
||||
}}
|
||||
@@ -56,14 +57,14 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -75,14 +76,14 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -94,13 +95,13 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -111,7 +112,7 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
) : item.Type === "Person" ? (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
@@ -119,13 +120,13 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -158,6 +159,7 @@ export const TVSearchSection: React.FC<TVSearchSectionProps> = ({
|
||||
imageUrlGetter,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const flatListRef = useRef<FlatList<BaseItemDto>>(null);
|
||||
const [focusedCount, setFocusedCount] = useState(0);
|
||||
const prevFocusedCount = useRef(0);
|
||||
@@ -358,7 +360,7 @@ export const TVSearchSection: React.FC<TVSearchSectionProps> = ({
|
||||
{/* Section Header */}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Animated, Pressable, View } from "react-native";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
|
||||
type SearchType = "Library" | "Discover";
|
||||
|
||||
@@ -20,6 +21,7 @@ const TVSearchTabBadge: React.FC<TVSearchTabBadgeProps> = ({
|
||||
hasTVPreferredFocus = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.08, duration: 150 });
|
||||
|
||||
@@ -61,7 +63,7 @@ const TVSearchTabBadge: React.FC<TVSearchTabBadgeProps> = ({
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontSize: typography.callout,
|
||||
color: getTextColor(),
|
||||
fontWeight: isSelected || focused ? "600" : "400",
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user