search ui changes to prioritize artists

This commit is contained in:
Violet Caulfield
2025-04-06 18:00:08 -05:00
parent c5cfd11208
commit ec26957efc
9 changed files with 110 additions and 48 deletions

View File

@@ -23,12 +23,18 @@ export async function fetchSearchResults(searchString: string | undefined) : Pro
searchTerm: trim(searchString),
recursive: true,
includeItemTypes: [
'MusicArtist',
'Audio',
'MusicAlbum',
'MusicArtist',
'Playlist'
],
limit: QueryConfig.limits.search,
sortBy: [
'IsFolder'
],
sortOrder: [
'Descending'
]
})
.then((response) => {
if (response.data.Items)

View File

@@ -10,7 +10,7 @@ interface HorizontalCardListProps extends FlatListProps<BaseItemDto> {
* we cut it off and display a "Show More" card
*/
cutoff?: number | undefined;
onSeeMore: () => void;
onSeeMore?: () => void | undefined;
}
/**
@@ -31,17 +31,17 @@ export default function HorizontalCardList({
data={props.data}
renderItem={props.renderItem}
ListFooterComponent={() => {
return props.data ? (
<IconCard
name={
squared
? "arrow-right-box"
: "arrow-right-circle"
}
circular={!squared}
caption="See More"
onPress={onSeeMore}
/>
return props.data && onSeeMore ? (
<IconCard
name={
squared
? "arrow-right-box"
: "arrow-right-circle"
}
circular={!squared}
caption="See More"
onPress={onSeeMore}
/>
) : undefined}
}
removeClippedSubviews

View File

@@ -1,6 +1,6 @@
import React from "react";
import type { CardProps as TamaguiCardProps } from "tamagui"
import { getToken, Card as TamaguiCard, View } from "tamagui";
import { getToken, Card as TamaguiCard, View, YStack } from "tamagui";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { Text } from "../helpers/text";
import { Image } from "expo-image";
@@ -16,24 +16,21 @@ interface CardProps extends TamaguiCardProps {
export function ItemCard(props: CardProps) {
const dimensions = props.width && typeof(props.width) === "number" ? { width: props.width, height: props.width } : { width: 150, height: 150 };
const logoDimensions = props.width && typeof(props.width) === "number" ? { width: props.width / 2, height: props.width / 6 }: { width: 100, height: 75 };
return (
<View
alignItems="center"
margin={5}
>
<TamaguiCard
size="$4"
size={"$12"}
height={props.size}
width={props.size}
backgroundColor={getToken("$color.amethyst")}
borderRadius={props.squared ? 5 : dimensions.width}
circular={!props.squared}
borderRadius={props.squared ? 5 : 'unset'}
animation="bouncy"
hoverStyle={props.onPress ? { scale: 0.925 } : {}}
pressStyle={props.onPress ? { scale: 0.875 } : {}}
width={props.width ?? 150}
height={props.width ?? 150}
{...props}
>
<TamaguiCard.Header>
@@ -59,18 +56,18 @@ export function ItemCard(props: CardProps) {
)}
placeholder={props.item.ImageBlurHashes && props.item.ImageBlurHashes["Primary"] ? props.item.ImageBlurHashes["Primary"][0] : undefined}
style={{
width: dimensions.width,
height: dimensions.height,
borderRadius: props.squared ? 5 : dimensions.width
width: '100%',
height: '100%',
borderRadius: props.squared ? 2 : 100
}}
/>
</TamaguiCard.Background>
</TamaguiCard>
{ props.caption && (
<View
<YStack
alignContent="center"
alignItems="center"
width={dimensions.width}
maxWidth={props.size}
>
<Text
bold
@@ -89,7 +86,7 @@ export function ItemCard(props: CardProps) {
{ props.subCaption }
</Text>
)}
</View>
</YStack>
)}
</View>
)

View File

@@ -1,5 +1,5 @@
import { Card, getTokens, View } from "tamagui";
import { H2, H4 } from "./text";
import { H2, H4, H5 } from "./text";
import Icon from "./icon";
interface IconCardProps {
@@ -30,12 +30,12 @@ export default function IconCard({
borderRadius={circular ? 300 : 5}
hoverStyle={{ scale: 0.925 }}
pressStyle={{ scale: 0.875 }}
width={width ? width : 150}
height={width ? width : 150}
width={width ? width : "$12"}
height={width ? width : "$12"}
onPress={onPress}
>
<Card.Header>
<H4 color={getTokens().color.purpleDark}>{ caption ?? "" }</H4>
<H5 color={getTokens().color.purpleDark}>{ caption ?? "" }</H5>
<Icon
color={getTokens().color.purpleDark.val}
name={name}

View File

@@ -26,6 +26,7 @@ export default function Playlists({ navigation }: { navigation: NativeStackNavig
renderItem={({ item: playlist }) =>
<ItemCard
item={playlist}
size={"$11"}
squared
caption={playlist.Name ?? "Untitled Playlist"}
onPress={() => {

View File

@@ -36,10 +36,10 @@ export default function RecentlyPlayed({
}}
renderItem={({ index, item: recentlyPlayedTrack }) =>
<ItemCard
size={"$12"}
caption={recentlyPlayedTrack.Name}
subCaption={`${recentlyPlayedTrack.Artists?.join(", ")}`}
squared
width={150}
item={recentlyPlayedTrack}
onPress={() => {
usePlayNewQueue.mutate({

View File

@@ -14,6 +14,7 @@ import { Image } from "expo-image";
import { getImageApi } from "@jellyfin/sdk/lib/utils/api";
import Client from "../../api/client";
import Icon from "../Global/helpers/icon";
import { Platform } from "react-native";
export default function ItemDetail({
item,
@@ -78,13 +79,21 @@ export default function ItemDetail({
minHeight={width / 1.5}
minWidth={width / 1.5}
>
<Icon
name="chevron-down"
onPress={() => {
navigation.goBack();
}}
small
/>
{/**
* Android needs a dismiss chevron here
*/}
{ Platform.OS === 'android' ? (
<Icon
name="chevron-down"
onPress={() => {
navigation.goBack();
}}
small
/>
) : (
<Spacer />
)}
<Spacer />

View File

@@ -7,11 +7,13 @@ import { QueryKeys } from "../../enums/query-keys";
import { fetchSearchResults } from "../../api/queries/functions/search";
import { useQuery } from "@tanstack/react-query";
import { FlatList } from "react-native";
import { H3, Text } from "../Global/helpers/text";
import { H3 } from "../Global/helpers/text";
import { fetchSearchSuggestions } from "../../api/queries/functions/suggestions";
import { Spinner, YStack } from "tamagui";
import Suggestions from "./suggestions";
import { isEmpty, isUndefined } from "lodash";
import { isEmpty } from "lodash";
import HorizontalCardList from "../Global/components/horizontal-list";
import { ItemCard } from "../Global/components/item-card";
export default function Search({
navigation
@@ -37,14 +39,16 @@ export default function Search({
return () => {
clearTimeout(timeout);
timeout = setTimeout(() => refetch, 1000)
timeout = setTimeout(() => {
refetch();
refetchSuggestions();
}, 1000)
}
}, []);
const handleSearchStringUpdate = (value: string | undefined) => {
setSearchString(value)
search();
refetchSuggestions();
}
return (
@@ -60,7 +64,27 @@ export default function Search({
/>
{ !isEmpty(items) && (
<YStack>
<H3>Results</H3>
<HorizontalCardList
data={items?.filter(result => result.Type === 'MusicArtist')}
renderItem={({ item: artistResult }) => {
return (
<ItemCard
item={artistResult}
onPress={() => {
navigation.push('Artist', {
artist: artistResult
})
}}
size={"$8"}
caption={artistResult.Name ?? "Untitled Artist"}
/>
)
}}
/>
</YStack>
)}
</YStack>
)}
@@ -79,7 +103,8 @@ export default function Search({
</YStack>
)
}}
data={items}
// We're displaying artists separately so we're going to filter them out here
data={items?.filter((result) => result.Type !== 'MusicArtist')}
refreshing={fetchingResults}
renderItem={({ item }) =>
<Item item={item} queueName={searchString ?? "Search"} navigation={navigation} />

View File

@@ -1,9 +1,12 @@
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { FlatList } from "react-native";
import { FlatList, RefreshControl } from "react-native";
import { StackParamList } from "../types";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import Item from "../Global/components/item";
import { H3, Text } from "../Global/helpers/text";
import { YStack } from "tamagui";
import { ItemCard } from "../Global/components/item-card";
import HorizontalCardList from "../Global/components/horizontal-list";
interface SuggestionsProps {
suggestions: BaseItemDto[] | undefined;
@@ -16,9 +19,30 @@ export default function Suggestions(
return (
<FlatList
data={props.suggestions}
// Artists are displayed in the header, so we'll filter them out here
data={props.suggestions?.filter(suggestion => suggestion.Type !== 'MusicArtist')}
ListHeaderComponent={(
<H3>Suggestions</H3>
<YStack>
<H3>Suggestions</H3>
<HorizontalCardList
data={props.suggestions?.filter(suggestion => suggestion.Type === 'MusicArtist')}
renderItem={({ item: suggestedArtist }) => {
return (
<ItemCard
item={suggestedArtist}
onPress={() => {
props.navigation.push('Artist', {
artist: suggestedArtist
})
}}
size={"$8"}
caption={suggestedArtist.Name ?? "Untitled Artist"}
/>
)
}}
/>
</YStack>
)}
ListEmptyComponent={(
<Text textAlign="center">Wake now, discover that you are the eyes of the world...</Text>