mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
feat: generate and display thumbhash blur placeholders for TMDB images
This commit is contained in:
@@ -27,7 +27,6 @@
|
||||
"@react-navigation/elements": "2.9.10",
|
||||
"@shopify/flash-list": "2.3.0",
|
||||
"@sofa/api": "workspace:*",
|
||||
"@sofa/tmdb": "workspace:*",
|
||||
"@tabler/icons-react-native": "3.40.0",
|
||||
"@tanstack/query-async-storage-persister": "5.90.24",
|
||||
"@tanstack/react-form": "1.28.5",
|
||||
|
||||
@@ -91,6 +91,7 @@ export default function PersonDetailScreen() {
|
||||
title={credit.title}
|
||||
type={credit.type}
|
||||
posterPath={credit.posterPath}
|
||||
posterThumbHash={credit.posterThumbHash}
|
||||
releaseDate={credit.releaseDate ?? credit.firstAirDate}
|
||||
voteAverage={credit.voteAverage}
|
||||
userStatus={data?.userStatuses?.[credit.titleId] ?? null}
|
||||
@@ -189,6 +190,7 @@ export default function PersonDetailScreen() {
|
||||
{person.profilePath && (
|
||||
<Image
|
||||
source={{ uri: person.profilePath }}
|
||||
thumbHash={person.profileThumbHash}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="cover"
|
||||
/>
|
||||
|
||||
@@ -165,6 +165,7 @@ export default function TitleDetailScreen() {
|
||||
title: item.title,
|
||||
type: item.type,
|
||||
posterPath: item.posterPath,
|
||||
posterThumbHash: item.posterThumbHash,
|
||||
releaseDate: item.releaseDate,
|
||||
firstAirDate: item.firstAirDate,
|
||||
voteAverage: item.voteAverage,
|
||||
@@ -277,6 +278,7 @@ export default function TitleDetailScreen() {
|
||||
{title.backdropPath && (
|
||||
<Image
|
||||
source={{ uri: title.backdropPath }}
|
||||
thumbHash={title.backdropThumbHash}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
@@ -366,6 +368,7 @@ export default function TitleDetailScreen() {
|
||||
>
|
||||
<Image
|
||||
source={{ uri: title.posterPath }}
|
||||
thumbHash={title.posterThumbHash}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
@@ -546,6 +549,7 @@ export default function TitleDetailScreen() {
|
||||
watchedEpisodeIds={watchedEpisodeIds}
|
||||
userStatus={userInfo.data?.status ?? null}
|
||||
backdropPath={title.backdropPath}
|
||||
backdropThumbHash={title.backdropThumbHash}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ContinueWatchingItem {
|
||||
id: string;
|
||||
title: string;
|
||||
backdropPath: string | null;
|
||||
backdropThumbHash?: string | null;
|
||||
};
|
||||
watchedEpisodes: number;
|
||||
totalEpisodes: number;
|
||||
@@ -23,6 +24,7 @@ export interface ContinueWatchingItem {
|
||||
episodeNumber: number;
|
||||
name: string | null;
|
||||
stillPath: string | null;
|
||||
stillThumbHash?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
@@ -61,6 +63,10 @@ export function ContinueWatchingCard({ item }: { item: ContinueWatchingItem }) {
|
||||
uri: (item.nextEpisode?.stillPath ??
|
||||
item.title.backdropPath) as string,
|
||||
}}
|
||||
thumbHash={
|
||||
item.nextEpisode?.stillThumbHash ??
|
||||
item.title.backdropThumbHash
|
||||
}
|
||||
className="h-full w-full"
|
||||
contentFit="cover"
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface PosterRowItem {
|
||||
title: string;
|
||||
type: string;
|
||||
posterPath: string | null;
|
||||
posterThumbHash?: string | null;
|
||||
releaseDate?: string | null;
|
||||
firstAirDate?: string | null;
|
||||
voteAverage?: number | null;
|
||||
@@ -55,6 +56,7 @@ export function HorizontalPosterRow({
|
||||
title={item.title}
|
||||
type={item.type as "movie" | "tv"}
|
||||
posterPath={item.posterPath}
|
||||
posterThumbHash={item.posterThumbHash}
|
||||
releaseDate={item.releaseDate ?? item.firstAirDate}
|
||||
voteAverage={item.voteAverage}
|
||||
userStatus={item.userStatus}
|
||||
|
||||
@@ -12,6 +12,7 @@ export function CastCard({
|
||||
name: string;
|
||||
character: string | null;
|
||||
profilePath: string | null;
|
||||
profileThumbHash?: string | null;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
@@ -23,6 +24,7 @@ export function CastCard({
|
||||
{person.profilePath && (
|
||||
<Image
|
||||
source={{ uri: person.profilePath }}
|
||||
thumbHash={person.profileThumbHash}
|
||||
className="h-full w-full"
|
||||
contentFit="cover"
|
||||
/>
|
||||
|
||||
@@ -14,11 +14,13 @@ export function ContinueWatchingBanner({
|
||||
watchedEpisodeIds,
|
||||
userStatus,
|
||||
backdropPath,
|
||||
backdropThumbHash,
|
||||
}: {
|
||||
seasons: Season[];
|
||||
watchedEpisodeIds: Set<string>;
|
||||
userStatus: string | null;
|
||||
backdropPath: string | null;
|
||||
backdropThumbHash?: string | null;
|
||||
}) {
|
||||
const titleAccentColor = useCSSVariable("--color-title-accent") as string;
|
||||
|
||||
@@ -54,6 +56,7 @@ export function ContinueWatchingBanner({
|
||||
{stillUrl && (
|
||||
<Image
|
||||
source={{ uri: stillUrl }}
|
||||
thumbHash={nextEpisode.stillThumbHash ?? backdropThumbHash}
|
||||
className="h-full w-full"
|
||||
contentFit="cover"
|
||||
/>
|
||||
|
||||
@@ -5,8 +5,9 @@ import { resolveUrl } from "@/lib/server-url";
|
||||
export function Image({
|
||||
source,
|
||||
className,
|
||||
thumbHash,
|
||||
...props
|
||||
}: ImageProps & { className?: string }) {
|
||||
}: ImageProps & { className?: string; thumbHash?: string | null }) {
|
||||
const resolved =
|
||||
source && typeof source === "object" && "uri" in source && source.uri
|
||||
? { ...source, uri: resolveUrl(source.uri) ?? undefined }
|
||||
@@ -15,6 +16,11 @@ export function Image({
|
||||
const style = useResolveClassNames(className ?? "");
|
||||
|
||||
return (
|
||||
<ExpoImage source={resolved} style={[style, props.style]} {...props} />
|
||||
<ExpoImage
|
||||
source={resolved}
|
||||
placeholder={thumbHash ? { thumbhash: thumbHash } : undefined}
|
||||
style={[style, props.style]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ interface PosterCardProps {
|
||||
title: string;
|
||||
type: "movie" | "tv";
|
||||
posterPath: string | null;
|
||||
posterThumbHash?: string | null;
|
||||
releaseDate?: string | null;
|
||||
voteAverage?: number | null;
|
||||
userStatus?: TitleStatus | null;
|
||||
@@ -55,6 +56,7 @@ export function PosterCard({
|
||||
title,
|
||||
type,
|
||||
posterPath,
|
||||
posterThumbHash,
|
||||
releaseDate,
|
||||
voteAverage,
|
||||
userStatus,
|
||||
@@ -117,6 +119,7 @@ export function PosterCard({
|
||||
{posterPath ? (
|
||||
<Image
|
||||
source={{ uri: posterPath }}
|
||||
thumbHash={posterThumbHash}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="cover"
|
||||
recyclingKey={`poster-${tmdbId}`}
|
||||
|
||||
Reference in New Issue
Block a user