fix: missing images
This commit is contained in:
@@ -28,7 +28,7 @@ interface Props {
|
||||
margin?: number;
|
||||
radius?: number;
|
||||
style?: ViewProps['style'];
|
||||
src: string;
|
||||
src?: string;
|
||||
}
|
||||
|
||||
const emptyAlbumLight = require('@/assets/images/empty-album-light.png');
|
||||
@@ -59,6 +59,8 @@ function CoverImage({
|
||||
return { imageSize, canvasSize };
|
||||
}, [blurRadius, margin]);
|
||||
|
||||
console.log({ src, hasFailed });
|
||||
|
||||
return (
|
||||
<Container size={imageSize} style={style}>
|
||||
<BlurContainer size={canvasSize} offset={blurRadius}>
|
||||
@@ -69,7 +71,7 @@ function CoverImage({
|
||||
<Shadow dx={0} dy={8} blur={16} color="#0000000d" />
|
||||
<Shadow dx={0} dy={16} blur={32} color="#0000000d" />
|
||||
</RoundedRect>
|
||||
{src && (
|
||||
{src ? (
|
||||
<>
|
||||
<SkiaImage
|
||||
image={image}
|
||||
@@ -97,8 +99,7 @@ function CoverImage({
|
||||
</SkiaImage>
|
||||
</Mask>
|
||||
</>
|
||||
)}
|
||||
{(!src || hasFailed) && (
|
||||
) : (
|
||||
<Mask
|
||||
mask={
|
||||
<RoundedRect
|
||||
|
||||
@@ -136,7 +136,7 @@ function Downloads() {
|
||||
<DownloadedTrack>
|
||||
<View style={{ marginRight: 12 }}>
|
||||
<ShadowWrapper size="small">
|
||||
<AlbumImage source={{ uri: getImage(item) }} style={defaultStyles.imageBackground} />
|
||||
<AlbumImage source={{ uri: getImage(tracks[item]) }} style={defaultStyles.imageBackground} />
|
||||
</ShadowWrapper>
|
||||
</View>
|
||||
<View style={{ flexShrink: 1, marginRight: 8 }}>
|
||||
|
||||
@@ -48,7 +48,7 @@ const SectionHeading = React.memo(function SectionHeading(props: {
|
||||
|
||||
interface GeneratedAlbumItemProps {
|
||||
id: ReactText;
|
||||
imageUrl: string;
|
||||
imageUrl: string | null;
|
||||
name: string;
|
||||
artist: string;
|
||||
onPress: (id: string) => void;
|
||||
@@ -66,7 +66,7 @@ const GeneratedAlbumItem = React.memo(function GeneratedAlbumItem(props: Generat
|
||||
<TouchableHandler id={id as string} onPress={onPress}>
|
||||
<AlbumItem>
|
||||
<ShadowWrapper size="medium">
|
||||
<AlbumImage source={{ uri: imageUrl }} style={[defaultStyles.imageBackground]} />
|
||||
<AlbumImage source={imageUrl ? { uri: imageUrl } : undefined} style={[defaultStyles.imageBackground]} />
|
||||
</ShadowWrapper>
|
||||
<Text numberOfLines={1} style={defaultStyles.text}>{name}</Text>
|
||||
<HalfOpacity style={defaultStyles.text} numberOfLines={1}>{artist}</HalfOpacity>
|
||||
@@ -136,7 +136,7 @@ const Albums: React.FC = () => {
|
||||
<GeneratedAlbumItem
|
||||
key={i}
|
||||
id={id}
|
||||
imageUrl={getImage(albums[id])}
|
||||
imageUrl={albums[id].ImageTags.Primary ? getImage(albums[id]) : null}
|
||||
name={albums[id]?.Name || ''}
|
||||
artist={albums[id]?.AlbumArtist || ''}
|
||||
onPress={selectAlbum}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { SafeFlatList, useNavigationOffsets } from '@/components/SafeNavigatorVi
|
||||
|
||||
interface GeneratedAlbumItemProps {
|
||||
id: ReactText;
|
||||
imageUrl: string;
|
||||
imageUrl?: string | null;
|
||||
name: string;
|
||||
onPress: (id: string) => void;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const GeneratedPlaylistItem = React.memo(function GeneratedPlaylistItem(props: G
|
||||
return (
|
||||
<TouchableHandler id={id as string} onPress={onPress}>
|
||||
<AlbumItem>
|
||||
<AlbumImage source={{ uri: imageUrl }} style={defaultStyles.imageBackground} />
|
||||
<AlbumImage source={{ uri: imageUrl || undefined }} style={defaultStyles.imageBackground} />
|
||||
<Text numberOfLines={1} style={defaultStyles.text}>{name}</Text>
|
||||
</AlbumItem>
|
||||
</TouchableHandler>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import FastImage, { FastImageProps } from 'react-native-fast-image';
|
||||
import FastImage, { FastImageProps, Source } from '@d11/react-native-fast-image';
|
||||
import { Dimensions } from 'react-native';
|
||||
import { useUserOrSystemScheme } from '@/components/Colors';
|
||||
|
||||
@@ -22,24 +22,29 @@ const Container = styled(FastImage)`
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
function AlbumImage(props: FastImageProps) {
|
||||
const [hasError, setError] = useState(false);
|
||||
const colorScheme = useUserOrSystemScheme();
|
||||
export interface AlbumImageProps extends FastImageProps {
|
||||
source: number | Omit<Source, 'uri'> & {
|
||||
uri?: string;
|
||||
};
|
||||
}
|
||||
|
||||
if (!props.source || hasError) {
|
||||
return (
|
||||
<Container
|
||||
{...props}
|
||||
source={colorScheme === 'light'
|
||||
? require('@/assets/images/empty-album-light.png')
|
||||
: require('@/assets/images/empty-album-dark.png')
|
||||
}
|
||||
/>
|
||||
);
|
||||
const defaultImageDark = require('@/assets/images/empty-album-dark.png');
|
||||
const defaultImageLight = require('@/assets/images/empty-album-light.png');
|
||||
|
||||
function AlbumImage(props: FastImageProps) {
|
||||
const colorScheme = useUserOrSystemScheme();
|
||||
const defaultImage = colorScheme === 'light' ? defaultImageLight : defaultImageDark;
|
||||
|
||||
// If no source is provided, use the default image as the main source
|
||||
if (!props.source || (typeof props.source === 'object' && 'uri' in props.source && !props.source.uri)) {
|
||||
return <Container {...props} source={defaultImage} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container {...props} onError={() => setError(true)} />
|
||||
<Container
|
||||
{...props}
|
||||
defaultSource={defaultImage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
|
||||
const downloadedTracks = useTypedSelector(selectDownloadedTracks(trackIds));
|
||||
const entity = useTypedSelector((state) => itemDisplayStyle === 'album' ? state.music.albums.entities[entityId] : state.music.playlists.entities[entityId]);
|
||||
const totalDuration = useMemo(() => (
|
||||
trackIds.reduce<number>((sum, trackId) => (
|
||||
sum + (tracks[trackId]?.RunTimeTicks || 0)
|
||||
@@ -173,7 +174,7 @@ const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
>
|
||||
<View style={{ padding: 24, paddingTop: 32, paddingBottom: 32 }}>
|
||||
<AlbumImageContainer>
|
||||
<CoverImage src={getImage(entityId)} />
|
||||
<CoverImage src={getImage(entity)} />
|
||||
</AlbumImageContainer>
|
||||
<Header>{title}</Header>
|
||||
<SubHeader>{artist}</SubHeader>
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function fetchApi<T>(
|
||||
path: PathOrCredentialInserter,
|
||||
providedConfig?: RequestInit,
|
||||
parseResponse = true
|
||||
) {
|
||||
) {
|
||||
// Retrieve the latest credentials from the Redux store
|
||||
const credentials = asyncFetchStore().getState().settings.credentials;
|
||||
|
||||
@@ -67,11 +67,11 @@ export async function fetchApi<T>(
|
||||
...generateConfig(credentials).headers,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Actually perform the request
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (__DEV__) {
|
||||
if (__DEV__) {
|
||||
console.log(`%c[HTTP] → [${response.status}] ${url}`, 'font-weight:bold;');
|
||||
console.log('\t', config);
|
||||
}
|
||||
@@ -96,24 +96,44 @@ export async function fetchApi<T>(
|
||||
if (parseResponse) {
|
||||
// Parse body as JSON
|
||||
const data = await response.json() as Promise<T>;
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatImageUri(ItemId: string | number, baseUri: string): string {
|
||||
return encodeURI(`${baseUri}/Items/${ItemId}/Images/Primary?format=jpeg`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an image URL for a given ItemId
|
||||
*/
|
||||
export function getImage(ItemId: string | number, credentials?: AppState['settings']['credentials']): string {
|
||||
export function getImage(item: string | number | Album | AlbumTrack | Playlist | ArtistItem | null, credentials?: AppState['settings']['credentials']): string | undefined {
|
||||
// Either accept provided credentials, or retrieve them directly from the store
|
||||
const { uri: serverUri } = credentials
|
||||
const { uri: serverUri } = credentials
|
||||
?? asyncFetchStore().getState().settings.credentials ?? {};
|
||||
|
||||
// Generate the uri and return
|
||||
const uri = encodeURI(`${serverUri}/Items/${ItemId}/Images/Primary?format=jpeg`);
|
||||
return uri;
|
||||
|
||||
if (!item || !serverUri) {
|
||||
return undefined;
|
||||
// GUARD: If the item's just the id, we'll pass it on directly.
|
||||
} else if (typeof item === 'string' || typeof item === 'number') {
|
||||
if (__DEV__) {
|
||||
console.warn('useGetImage: supplied item is string or number. Please submit an item object instead.', { item });
|
||||
}
|
||||
return formatImageUri(item, serverUri);
|
||||
// GUARD: If the item has an `PrimaryImageItemId` (for Emby servers),
|
||||
// we'll attemp to return that
|
||||
} else if ('PrimaryImageItemId' in item) {
|
||||
return formatImageUri(item.PrimaryImageItemId || item.Id, serverUri);
|
||||
} else {
|
||||
if ('ImageTags' in item && item.ImageTags.Primary) {
|
||||
return formatImageUri(item.Id, serverUri);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,18 +142,7 @@ export function getImage(ItemId: string | number, credentials?: AppState['settin
|
||||
export function useGetImage() {
|
||||
const credentials = useTypedSelector((state) => state.settings.credentials);
|
||||
|
||||
return (item: string | number | Album | AlbumTrack | Playlist | ArtistItem | null) => {
|
||||
if (!item) {
|
||||
return '';
|
||||
// GUARD: If the item's just the id, we'll pass it on directly.
|
||||
} else if (typeof item === 'string' || typeof item === 'number') {
|
||||
return getImage(item, credentials);
|
||||
// GUARD: If the item has an `PrimaryImageItemId` (for Emby servers),
|
||||
// we'll attemp to return that
|
||||
} else if ('PrimaryImageItemId' in item) {
|
||||
return getImage(item.PrimaryImageItemId || item.Id, credentials);
|
||||
} else {
|
||||
return getImage(item.Id);
|
||||
}
|
||||
return (item: Parameters<typeof getImage>[0]) => {
|
||||
return getImage(item, credentials);
|
||||
};
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export async function generateTrack(track: AlbumTrack): Promise<Track> {
|
||||
artist: track.Artists.join(', '),
|
||||
album: track.Album,
|
||||
duration: track.RunTimeTicks,
|
||||
artwork: getImage(track.Id),
|
||||
artwork: getImage(track),
|
||||
bitRate: baseTrackOptions.audioBitRate,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user