mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-27 09:08:31 +01:00
feat(tv): add scalable typography with user-configurable text size
This commit is contained in:
@@ -11,7 +11,7 @@ import heart from "@/assets/icons/heart.fill.png";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { InfiniteScrollingCollectionList } from "@/components/home/InfiniteScrollingCollectionList.tv";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
|
||||
const HORIZONTAL_PADDING = 60;
|
||||
@@ -28,6 +28,7 @@ type FavoriteTypes =
|
||||
type EmptyState = Record<FavoriteTypes, boolean>;
|
||||
|
||||
export const Favorites = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { t } = useTranslation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [api] = useAtom(apiAtom);
|
||||
@@ -148,7 +149,7 @@ export const Favorites = () => {
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
color: "#FFFFFF",
|
||||
@@ -160,7 +161,7 @@ export const Favorites = () => {
|
||||
style={{
|
||||
textAlign: "center",
|
||||
opacity: 0.7,
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { StreamystatsPromotedWatchlists } from "@/components/home/StreamystatsPr
|
||||
import { StreamystatsRecommendations } from "@/components/home/StreamystatsRecommendations.tv";
|
||||
import { TVHeroCarousel } from "@/components/home/TVHeroCarousel";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
|
||||
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
||||
@@ -62,6 +62,7 @@ type Section = InfiniteScrollingCollectionListSection;
|
||||
const BACKDROP_DEBOUNCE_MS = 300;
|
||||
|
||||
export const Home = () => {
|
||||
const typography = useScaledTVTypography();
|
||||
const _router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const api = useAtomValue(apiAtom);
|
||||
@@ -579,7 +580,7 @@ export const Home = () => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
color: "#FFFFFF",
|
||||
@@ -591,7 +592,7 @@ export const Home = () => {
|
||||
style={{
|
||||
textAlign: "center",
|
||||
opacity: 0.7,
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
@@ -632,7 +633,7 @@ export const Home = () => {
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
color: "#FFFFFF",
|
||||
@@ -644,7 +645,7 @@ export const Home = () => {
|
||||
style={{
|
||||
textAlign: "center",
|
||||
opacity: 0.7,
|
||||
fontSize: TVTypography.body,
|
||||
fontSize: typography.body,
|
||||
color: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,7 @@ import MoviePoster, {
|
||||
TV_POSTER_WIDTH,
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { SortByOption, SortOrderOption } from "@/utils/atoms/filters";
|
||||
import ContinueWatchingPoster, {
|
||||
@@ -48,22 +48,27 @@ interface Props extends ViewProps {
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
// TV-specific ItemCardText with larger fonts
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => {
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
{item.Type === "Episode" ? (
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -77,13 +82,13 @@ const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
<>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -103,7 +108,8 @@ const TVSeeAllCard: React.FC<{
|
||||
disabled?: boolean;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
}> = ({ onPress, orientation, disabled, onFocus, onBlur }) => {
|
||||
typography: Typography;
|
||||
}> = ({ onPress, orientation, disabled, onFocus, onBlur, typography }) => {
|
||||
const { t } = useTranslation();
|
||||
const width =
|
||||
orientation === "horizontal" ? TV_LANDSCAPE_WIDTH : TV_POSTER_WIDTH;
|
||||
@@ -137,7 +143,7 @@ const TVSeeAllCard: React.FC<{
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
@@ -165,6 +171,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
parentId,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const effectivePageSize = Math.max(1, pageSize);
|
||||
const hasCalledOnLoaded = useRef(false);
|
||||
const router = useRouter();
|
||||
@@ -343,7 +350,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
>
|
||||
{renderPoster()}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
@@ -354,6 +361,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
handleItemPress,
|
||||
handleItemFocus,
|
||||
handleItemBlur,
|
||||
typography,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -365,7 +373,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
{/* Section Header */}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
@@ -380,7 +388,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
<Text
|
||||
style={{
|
||||
color: "#737373",
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
marginLeft: SCALE_PADDING,
|
||||
}}
|
||||
>
|
||||
@@ -421,7 +429,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
color: "#262626",
|
||||
backgroundColor: "#262626",
|
||||
borderRadius: 6,
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -478,6 +486,7 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
disabled={disabled}
|
||||
onFocus={handleSeeAllFocus}
|
||||
onBlur={handleItemBlur}
|
||||
typography={typography}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -16,7 +16,7 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -26,18 +26,23 @@ import type { StreamystatsWatchlist } from "@/utils/streamystats/types";
|
||||
const ITEM_GAP = 16;
|
||||
const SCALE_PADDING = 20;
|
||||
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => {
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -60,6 +65,7 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
|
||||
onItemFocus,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
const { settings } = useSettings();
|
||||
@@ -142,11 +148,11 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
|
||||
{item.Type === "Movie" && <MoviePoster item={item} />}
|
||||
{item.Type === "Series" && <SeriesPoster item={item} />}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[handleItemPress, onItemFocus],
|
||||
[handleItemPress, onItemFocus, typography],
|
||||
);
|
||||
|
||||
if (!isLoading && (!items || items.length === 0)) return null;
|
||||
@@ -155,7 +161,7 @@ const WatchlistSection: React.FC<WatchlistSectionProps> = ({
|
||||
<View style={{ overflow: "visible" }} {...props}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
|
||||
@@ -16,7 +16,7 @@ import MoviePoster, {
|
||||
} from "@/components/posters/MoviePoster.tv";
|
||||
import SeriesPoster from "@/components/posters/SeriesPoster.tv";
|
||||
import { TVFocusablePoster } from "@/components/tv/TVFocusablePoster";
|
||||
import { TVTypography } from "@/constants/TVTypography";
|
||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -26,6 +26,8 @@ import type { StreamystatsRecommendationsIdsResponse } from "@/utils/streamystat
|
||||
const ITEM_GAP = 16;
|
||||
const SCALE_PADDING = 20;
|
||||
|
||||
type Typography = ReturnType<typeof useScaledTVTypography>;
|
||||
|
||||
interface Props extends ViewProps {
|
||||
title: string;
|
||||
type: "Movie" | "Series";
|
||||
@@ -34,18 +36,21 @@ interface Props extends ViewProps {
|
||||
onItemFocus?: (item: BaseItemDto) => void;
|
||||
}
|
||||
|
||||
const TVItemCardText: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||
const TVItemCardText: React.FC<{
|
||||
item: BaseItemDto;
|
||||
typography: Typography;
|
||||
}> = ({ item, typography }) => {
|
||||
return (
|
||||
<View style={{ marginTop: 12, flexDirection: "column" }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ fontSize: TVTypography.callout, color: "#FFFFFF" }}
|
||||
style={{ fontSize: typography.callout, color: "#FFFFFF" }}
|
||||
>
|
||||
{item.Name}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.callout,
|
||||
fontSize: typography.callout,
|
||||
color: "#9CA3AF",
|
||||
marginTop: 2,
|
||||
}}
|
||||
@@ -64,6 +69,7 @@ export const StreamystatsRecommendations: React.FC<Props> = ({
|
||||
onItemFocus,
|
||||
...props
|
||||
}) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const api = useAtomValue(apiAtom);
|
||||
const user = useAtomValue(userAtom);
|
||||
const { settings } = useSettings();
|
||||
@@ -203,11 +209,11 @@ export const StreamystatsRecommendations: React.FC<Props> = ({
|
||||
{item.Type === "Movie" && <MoviePoster item={item} />}
|
||||
{item.Type === "Series" && <SeriesPoster item={item} />}
|
||||
</TVFocusablePoster>
|
||||
<TVItemCardText item={item} />
|
||||
<TVItemCardText item={item} typography={typography} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[handleItemPress, onItemFocus],
|
||||
[handleItemPress, onItemFocus, typography],
|
||||
);
|
||||
|
||||
if (!streamyStatsEnabled) return null;
|
||||
@@ -218,7 +224,7 @@ export const StreamystatsRecommendations: React.FC<Props> = ({
|
||||
<View style={{ overflow: "visible" }} {...props}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: TVTypography.heading,
|
||||
fontSize: typography.heading,
|
||||
fontWeight: "700",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 20,
|
||||
|
||||
Reference in New Issue
Block a user