Merge branch '193-implement-jest-unit-testing' of git@github.com:anultravioletaurora/Jellify.git

This commit is contained in:
Violet Caulfield
2025-04-09 09:03:15 -05:00
34 changed files with 656 additions and 143 deletions

11
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,11 @@
### What is the change
### What does this address
### Issue number / link
### Tag reviewers
@anultravioletaurora

View File

@@ -44,11 +44,14 @@ jobs:
with:
commit_message: "[skip actions]"
file_pattern: "ios/Jellify.xcodeproj/project.pbxproj"
- name: 🔢 Set artifact version numbers
run: mkdir artifacts && mv ./ios/Jellify.ipa ./artifacts/Jellify-${{ env.VERSION_NUMBER }}.ipa && mv ./android/app/build/outputs/apk/release/app-release.apk ./artifacts/Jellify-${{ env.VERSION_NUMBER }}.apk
- name: 🎉 Create Github release
uses: ncipollo/release-action@v1
with:
artifacts: "ios/Jellify.ipa,android/app/build/outputs/apk/release/app-release.apk"
artifacts: "./artifacts/*"
name: ${{ env.VERSION_NUMBER }}
prerelease: true
tag: ${{ env.VERSION_NUMBER }}

View File

@@ -42,7 +42,7 @@ This app was designed with me and my dad in mind, since I wanted to give him a s
- [Shared, Public, and Collaborative Playlists](https://github.com/anultravioletaurora/Jellify/issues/175)
- [Web / Desktop support](https://github.com/anultravioletaurora/Jellify/issues/71)
- [Watch (Apple Watch / WearOS) Support](https://github.com/anultravioletaurora/Jellify/issues/61)
- [TV (Android, Samsung) Support](https://github.com/anultravioletaurora/Jellify/issues/85)
- [TV (Android, Apple, Samsung) Support](https://github.com/anultravioletaurora/Jellify/issues/85)
## 👀 Lemme see!
### Home
@@ -99,23 +99,27 @@ Playlist
### On the Server
<img src="https://github.com/user-attachments/assets/741884a2-b9b7-4081-b3a0-6655d08071dc" alt="Playback Tracking" width="300" height="200">
## 🏗 Built with:
## 🏗 Built with good stuff
[![Made with React](https://img.shields.io/badge/React-18-blue?logo=react&logoColor=white)](https://reactjs.org “Go to React homepage”) [![Made with TypeScript](https://img.shields.io/badge/TypeScript-5-blue?logo=typescript&logoColor=white)](https://typescriptlang.org “Go to TypeScript homepage”)
### 🎨 Frontend
[Tamagui](https://tamagui.dev/)\
[React Navigation](https://reactnavigation.org/)\
[Burnt](https://github.com/nandorojo/burnt)\
[React Navigation](https://reactnavigation.org/)\
[React Native CarPlay](https://github.com/birkir/react-native-carplay)\
[React Native Draggable Flatlist](https://github.com/computerjazz/react-native-draggable-flatlist)\
[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)\
[React Native Vector Icons](https://github.com/oblador/react-native-vector-icons)
- Specifically Material Community Icons
- Specifically using [Material Community Icons](https://oblador.github.io/react-native-vector-icons/#MaterialCommunityIcons)
### 🎛️ Backend
[Jellyfin SDK](https://typescript-sdk.jellyfin.org/)\
[Expo SDK](https://expo.dev/)\
[Jellyfin SDK](https://typescript-sdk.jellyfin.org/)\
[Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/react-native)\
[React Native Track Player](https://github.com/doublesymmetry/react-native-track-player)\
[React Native MMKV](https://github.com/mrousavy/react-native-mmkv)\
[React Native Boost](https://github.com/kuatsu/react-native-boost)\
[React Native File Access](https://github.com/alpha0010/react-native-file-access)\
[React Native Boost](https://github.com/kuatsu/react-native-boost)
[React Native MMKV](https://github.com/mrousavy/react-native-mmkv)\
[React Native Track Player](https://github.com/doublesymmetry/react-native-track-player)\
[React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill)
### 👩‍💻 Monitoring
[GlitchTip](https://glitchtip.com/)
@@ -125,33 +129,62 @@ This is undoubtedly a passion project of [mine](https://github.com/anultraviolet
## 🏃Running Locally
#### Universal Dependencies
- Ruby
- NodeJS
### ⚛️ Universal Dependencies
- [Ruby](https://www.ruby-lang.org/en/documentation/installation/) for Fastlane
- [NodeJS v22](https://nodejs.org/en/download) for React Native
#### iOS Instructions
### 🍎 iOS
#### Dependencies
- [Xcode](https://developer.apple.com/xcode/) for building
#### Instructions
##### Setup
- Clone this repository
- Run `npm run init` to initialize the project
- This will install `npm` packages, install `bundler` and required gems, and installs CocoaPods
- In the `ios` directory, run `fastlane match development --readonly` to fetch the development signing certificates
- You will need access to the *Jellify Signing* private repository
- To run locally, run `npm run start` then run the app on your device or in the simulator
- Make sure you open the `Jellify.xcodeworkspace`, *not* the `Jellify.xcodeproject`
- *You will need access to the *Jellify Signing* private repository*
##### Running
- Run `npm run start` to start the dev server
- Open the `Jellify.xcodeworkspace` with Xcode, *not* the `Jellify.xcodeproject`
- Run either on a device or in the simulator
- *You will need to wait for Xcode to finish it's "Indexing" step*
##### Building
- To create a build, run `npm run fastlane:ios:build` to use fastlane to compile an `.ipa` for you
#### Android Instructions
### 🤖 Android
#### Dependencies
- [Android Studio](https://developer.android.com/studio)
- [Java Development Kit](https://www.oracle.com/th/java/technologies/downloads/)
#### Instructions
##### Setup
- Clone this repository
- Run `npm i` to install `npm` packages
- To run locally, run `npm run start`, then run the app on your devvice or in the emulator
##### Running
- Run `npm run start` to start the dev server
- Open the `android` folder with Android Studio
- *Android Studio should automatically grab the "Run Configurations" and initialize Gradle*
- Run either on a device or in the simulator
##### Building
- To create a build, run `npm run fastlane:android:build` to use fastlane to compile an `.apk` for you
#### References
- [Setting up Android SDK](https://developer.android.com/about/versions/14/setup-sdk)
- [ANDROID_HOME not being set](https://stackoverflow.com/questions/26356359/error-android-home-is-not-set-and-android-command-not-in-your-path-you-must/54888107#54888107)
## 🙏 Special Thanks To
- The [Jellyfin Team](https://jellyfin.org/) for making this possible with their software, SDKs, and unequivocal helpfulness.
- Extra thanks to [Niels](https://github.com/nielsvanvelzen) and [Bill](https://github.com/thornbill)
- [James](https://github.com/jmshrv) and all other contributors of [Finamp](https://github.com/jmshrv/finamp). *Jellify* draws inspiration and wisdom from it, and is another fantastic music app for Jellyfin.
- James [API Blog Post](https://jmshrv.com/posts/jellyfin-api/) proved to be exceptionally valuable during development
- The folks in the [Margelo Community Discord](https://discord.com/invite/6CSHz2qAvA) for their assistance
- Extra thanks to [Ritesh](https://github.com/riteshshukla04) for your help, knowledge, and guidance
- Extra thanks to [Ritesh](https://github.com/riteshshukla04) for your help, knowledge, and guidance
- [Nicolas Charpentier](https://github.com/charpeni) for his [React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill) module and for his assistance with getting Jest working
- My fellow [contributors](https://github.com/anultravioletaurora/Jellify/graphs/contributors) who have poured so much heart and a lot of sweat into making *Jellify* a great experience
- Extra thanks to [John Grant](https://github.com/johngrantdev) for shaping and designing the user experience in many places
- My dear friends that have heard me talk about *Jellify* for literally **eons**. Thank you for testing *Jellify* during it's infancy and for supporting me all the way back at the beginning of this project

View File

@@ -9,17 +9,19 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.1046.0)
aws-sdk-core (3.217.1)
aws-eventstream (1.3.2)
aws-partitions (1.1082.0)
aws-sdk-core (3.222.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.97.0)
logger
aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.179.0)
aws-sdk-s3 (1.183.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@@ -68,7 +70,7 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.226.0)
fastlane (2.227.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -129,12 +131,12 @@ GEM
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.7.1)
google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.4.0)
google-cloud-errors (1.5.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
@@ -152,15 +154,18 @@ GEM
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.8.3)
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.9.1)
json (2.10.2)
jwt (2.10.1)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.2.1)
nkf (0.2.0)
@@ -174,7 +179,7 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.0)
rexml (3.4.1)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
@@ -206,14 +211,14 @@ GEM
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.0)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
x86_64-darwin-23
x64-mingw-ucrt
x86_64-linux
DEPENDENCIES
fastlane

View File

@@ -1,11 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!-- Allow cleartext network traffic -->
<base-config
cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration">
</base-config>
</network-security-config>

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
limit: QueryConfig.limits.search,
sortBy: [
'IsFolder'
],
sortOrder: [
'Descending'
]
})
.then((response) => {
if (response.data.Items)

View File

@@ -1,31 +1,40 @@
import { getItemsApi, getSuggestionsApi } from "@jellyfin/sdk/lib/utils/api";
import Client from "../../../api/client";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getSuggestionsApi } from "@jellyfin/sdk/lib/utils/api";
import { BaseItemDto, BaseItemKind } from "@jellyfin/sdk/lib/generated-client/models";
export async function fetchSearchSuggestions() : Promise<BaseItemDto[]> {
return new Promise((resolve, reject) => {
getSuggestionsApi(Client.api!)
.getSuggestions({
userId: Client.user!.id,
type: [
'MusicArtist',
'MusicAlbum',
'Audio',
'Playlist'
getItemsApi(Client.api!)
.getItems({
recursive: true,
limit: 10,
includeItemTypes: [
BaseItemKind.MusicArtist,
BaseItemKind.Playlist,
BaseItemKind.Audio,
BaseItemKind.MusicAlbum
],
sortBy: [
"IsFavoriteOrLiked",
"Random"
]
})
.then((response) => {
if (response.data.Items)
resolve(response.data.Items)
.then(({ data }) => {
if (data.Items)
resolve(data.Items);
else
resolve([]);
})
.catch((error) => {
reject(error);
})
})
})
}
/**
* @deprecated Use Items API based functions instead of Suggestions API
* @returns
*/
export async function fetchSuggestedArtists() : Promise<BaseItemDto[]> {
return new Promise((resolve, reject) => {
getSuggestionsApi(Client.api!)
@@ -47,6 +56,10 @@ export async function fetchSuggestedArtists() : Promise<BaseItemDto[]> {
})
}
/**
* @deprecated Use Items API based functions instead of Suggestions API
* @returns
*/
export async function fetchSuggestedAlbums() : Promise<BaseItemDto[]> {
return new Promise((resolve, reject) => {
getSuggestionsApi(Client.api!)
@@ -68,6 +81,10 @@ export async function fetchSuggestedAlbums() : Promise<BaseItemDto[]> {
})
}
/**
* @deprecated Use Items API based functions instead of Suggestions API
* @returns
*/
export async function fetchSuggestedTracks() : Promise<BaseItemDto[]> {
return new Promise((resolve, reject) => {
getSuggestionsApi(Client.api!)

View File

@@ -85,7 +85,7 @@ export function ArtistScreen({
<FlatList
contentContainerStyle={{
flexGrow: 1,
alignContent: 'center'
alignItems: "center"
}}
data={albums}
numColumns={columns} // TODO: Make this adjustable
@@ -93,7 +93,7 @@ export function ArtistScreen({
<ItemCard
caption={album.Name}
subCaption={album.ProductionYear?.toString()}
width={(width / 1.1) / columns}
size={"$14"}
squared
item={album}
onPress={() => {

View File

@@ -27,7 +27,7 @@ export default function RecentlyAdded({
caption={item.Name}
subCaption={`${item.Artists?.join(", ")}`}
squared
width={150}
size={"$12"}
item={item}
onPress={() => {
navigation.navigate("Album", {

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

@@ -29,7 +29,7 @@ export default function ServerAuthentication({
const useApiMutation = useMutation({
mutationFn: async (credentials: JellyfinCredentials) => {
return await Client.api!.authenticateUserByName(credentials.username, credentials.password!);
return await Client.api!.authenticateUserByName(credentials.username, credentials.password);
},
onSuccess: async (authResult) => {
@@ -107,7 +107,7 @@ export default function ServerAuthentication({
)}
<Button
disabled={_.isEmpty(username) || _.isEmpty(password) || useApiMutation.isPending}
disabled={_.isEmpty(username) || useApiMutation.isPending}
onPress={() => {
if (!_.isUndefined(username)) {

View File

@@ -7,8 +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 { 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 } from "lodash";
import HorizontalCardList from "../Global/components/horizontal-list";
import { ItemCard } from "../Global/components/item-card";
export default function Search({
navigation
@@ -18,12 +23,12 @@ export default function Search({
const [searchString, setSearchString] = useState<string | undefined>(undefined);
const { data: items, refetch, isFetching } = useQuery({
const { data: items, refetch, isFetching: fetchingResults } = useQuery({
queryKey: [QueryKeys.Search, searchString],
queryFn: () => fetchSearchResults(searchString)
});
const { data } = useQuery({
const { data: suggestions, isFetching: fetchingSuggestions, refetch: refetchSuggestions } = useQuery({
queryKey: [QueryKeys.SearchSuggestions],
queryFn: () => fetchSearchSuggestions()
});
@@ -34,7 +39,10 @@ export default function Search({
return () => {
clearTimeout(timeout);
timeout = setTimeout(() => refetch, 1000)
timeout = setTimeout(() => {
refetch();
refetchSuggestions();
}, 1000)
}
}, []);
@@ -48,17 +56,56 @@ export default function Search({
contentInsetAdjustmentBehavior="automatic"
progressViewOffset={10}
ListHeaderComponent={(
<Input
placeholder="Seek and ye shall find..."
onChangeText={(value) => handleSearchStringUpdate(value)}
value={searchString}
/>
<YStack>
<Input
placeholder="Seek and ye shall find"
onChangeText={(value) => handleSearchStringUpdate(value)}
value={searchString}
/>
{ !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>
)}
ListEmptyComponent={(
<Text>No results found</Text>
)}
data={items}
refreshing={isFetching}
ListEmptyComponent={() => {
return (
<YStack
alignContent="center"
justifyContent="flex-end"
marginTop={"$4"}
>
{ fetchingResults ? (
<Spinner />
) : (
<Suggestions suggestions={suggestions} navigation={navigation} />
)}
</YStack>
)
}}
// 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

@@ -0,0 +1,55 @@
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
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;
navigation: NativeStackNavigationProp<StackParamList>;
}
export default function Suggestions(
props: SuggestionsProps
) : React.JSX.Element {
return (
<FlatList
// Artists are displayed in the header, so we'll filter them out here
data={props.suggestions?.filter(suggestion => suggestion.Type !== 'MusicArtist')}
ListHeaderComponent={(
<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>
)}
renderItem={({ item }) => {
return <Item item={item} queueName={"Suggestions"} navigation={props.navigation} />
}}
/>
)
}

View File

@@ -2,19 +2,23 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack";
import Player from "./Player/stack";
import { Tabs } from "./tabs";
import { StackParamList } from "./types";
import { useTheme } from 'tamagui'
import DetailsScreen from "./ItemDetail/screen";
export default function Navigation(): React.JSX.Element {
const RootStack = createNativeStackNavigator<StackParamList>()
const theme = useTheme()
return (
<RootStack.Navigator>
<RootStack.Screen
name="Tabs"
component={Tabs}
options={{
headerShown: false
headerShown: false,
navigationBarColor: theme.background.val,
}}
/>
<RootStack.Screen

View File

@@ -718,7 +718,7 @@
CODE_SIGN_IDENTITY = "Apple Development: Jack Caulfield (66Z9J9NX2X)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development: Jack Caulfield (66Z9J9NX2X)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 98;
CURRENT_PROJECT_VERSION = 107;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_BITCODE = NO;
@@ -759,7 +759,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 98;
CURRENT_PROJECT_VERSION = 107;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
INFOPLIST_FILE = Jellify/Info.plist;

View File

@@ -56,7 +56,7 @@ platform :ios do
api_key_path: "fastlane/appstore_connect_api_key.json",
beta_app_feedback_email: "violet@cosmonautical.cloud",
beta_app_description: "A music app for Jellyfin",
expire_previous_builds: true,
expire_previous_builds: false,
distribute_external: true,
changelog: "Updated Sign In components, Player Optimizations, 'See More' buttons",
groups: [

367
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "jellify",
"version": "0.10.74",
"version": "0.10.80",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jellify",
"version": "0.10.74",
"version": "0.10.80",
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
@@ -22,12 +22,16 @@
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-persist-client": "^5.66.0",
"axios": "^1.7.9",
"bundle": "^2.1.0",
"bundler": "^0.8.0",
"burnt": "^0.12.2",
"expo": "^52.0.0",
"expo-image": "^2.0.7",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"jest-expo": "^52.0.6",
"lodash": "^4.17.21",
"npm-bundle": "^3.0.3",
"react": "18.3.1",
"react-freeze": "^1.0.4",
"react-native": "0.77.0",
@@ -50,6 +54,7 @@
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1",
"tamagui": "^1.124.17"
},
"devDependencies": {
@@ -457,13 +462,13 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz",
"integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.7",
"@babel/types": "^7.25.7"
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
@@ -556,12 +561,12 @@
}
},
"node_modules/@babel/parser": {
"version": "7.26.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz",
"integrity": "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.8"
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -2212,9 +2217,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz",
"integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@@ -2224,14 +2229,14 @@
}
},
"node_modules/@babel/template": {
"version": "7.26.8",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.8.tgz",
"integrity": "sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.26.8",
"@babel/types": "^7.26.8"
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
@@ -2275,9 +2280,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz",
"integrity": "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@@ -7350,6 +7355,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/q": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
"integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
@@ -8161,9 +8172,9 @@
}
},
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -8826,6 +8837,26 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/bundle": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bundle/-/bundle-2.1.0.tgz",
"integrity": "sha512-d7TeT8m2HuymDjSEmMppWe/h5SSPPUZkaWKrAofx6gNXDdZ3FL/81oOTGPG+LIaZbNr9m4rtUi98Yw0Q1vHIIw==",
"license": "MIT"
},
"node_modules/bundler": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/bundler/-/bundler-0.8.0.tgz",
"integrity": "sha512-vHl3CsqgXtfJnEe61oOYRoevN0Tl3P/Z6e6Z9Sr3xif8oaV6L4bV7m4cYCiOWT1oPvOyl3j9Lten4/GrIeIupA==",
"engines": [
"node ~0.4.0"
],
"dependencies": {
"coa": ">= 0.1.1"
},
"bin": {
"bundler": "bin/bundler"
}
},
"node_modules/burnt": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/burnt/-/burnt-0.12.2.tgz",
@@ -9168,6 +9199,91 @@
"node": ">= 0.12.0"
}
},
"node_modules/coa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
"integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==",
"license": "MIT",
"dependencies": {
"@types/q": "^1.5.1",
"chalk": "^2.4.1",
"q": "^1.1.2"
},
"engines": {
"node": ">= 4.0"
}
},
"node_modules/coa/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/coa/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/coa/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/coa/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/coa/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/coa/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/coa/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/collect-v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
@@ -10078,6 +10194,18 @@
"integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==",
"license": "ISC"
},
"node_modules/emitter-b": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/emitter-b/-/emitter-b-1.0.0.tgz",
"integrity": "sha512-Ug5yFHs+cj511x0hsP8TO2nbXiJ+c8Adol9xiob/qXD8ptihxZ1mr1z3D5Cph2TJoQgUNH9fXwrBNhHxL6KJ+w==",
"dependencies": {
"proto": ">=1.0.17"
},
"license": {
"name": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
},
"node_modules/emittery": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
@@ -11975,6 +12103,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gem": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/gem/-/gem-2.4.3.tgz",
"integrity": "sha512-PkXm35oFCg9/Sb7RvYar3JlYuPBkBBgiH5G3Z97Xy9gIU8Zy5hBA/K9rk3AxeCiQRNm/n4koItA7zb/irbcvlA==",
"dependencies": {
"emitter-b": "*",
"hashmap": "https://github.com/Tixit/hashmap/archive/c666bbe32a8d23bbafc96c5d8f18e385b1b00f27.tar.gz",
"observe": "1",
"proto": ">=1.0.17",
"trimArguments": "https://github.com/fresheneesz/trimArguments/archive/c9c023c9ff1e0aec4b67370a80a92dacadcf8b11.tar.gz"
},
"license": {
"name": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -12281,6 +12425,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hashmap": {
"version": "2.0.5-minfiedBugFix",
"resolved": "https://github.com/Tixit/hashmap/archive/c666bbe32a8d23bbafc96c5d8f18e385b1b00f27.tar.gz",
"integrity": "sha512-VYmmBJ/KuQgX7flE0edHE7ke0PXIi/wbpcv+s7mrMkOJSoE/L/BNCWyODSWSznT5spxwInc4iymCa8VGisC3Ew==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -12463,9 +12616,9 @@
}
},
"node_modules/image-size": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.0.tgz",
"integrity": "sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
@@ -12563,6 +12716,12 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/insync": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/insync/-/insync-2.1.1.tgz",
"integrity": "sha512-UzUhOZFpCMM22Xlig9iUPqalf8n7c4eYScamce1C+jN3ad8FtmVm42ryMwVq0hAxHbwUhWFhPvTFQQpFdDUKkw==",
"license": "BSD-3-Clause"
},
"node_modules/internal-ip": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
@@ -15482,6 +15641,15 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"license": "MIT"
},
"node_modules/ncp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==",
"license": "MIT",
"bin": {
"ncp": "bin/ncp"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -15615,6 +15783,107 @@
"node": ">=0.10.0"
}
},
"node_modules/npm-bundle": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/npm-bundle/-/npm-bundle-3.0.3.tgz",
"integrity": "sha512-fHF7FR32YNgjqi0MQMLnE78Ff9/wYd4/7/Cke3dLLi2QzETKotIiWGCxwDoXAZDWVoTuVRYQa2ZdiZPuBL7QnA==",
"license": "ISC",
"dependencies": {
"glob": "^6.0.1",
"insync": "^2.1.1",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"rimraf": "^2.4.4"
},
"bin": {
"npm-bundle": "bin/cli.js"
}
},
"node_modules/npm-bundle/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/npm-bundle/node_modules/glob": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"dependencies": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
}
},
"node_modules/npm-bundle/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/npm-bundle/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/npm-bundle/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/npm-bundle/node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/npm-package-arg": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz",
@@ -15781,6 +16050,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/observe": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/observe/-/observe-1.4.1.tgz",
"integrity": "sha512-xq1XHEwjJm5Vqpvpy0/RknqF+rDsg8o6CApoWxfGizHEjFCruOU4HnScM2wQesoug4hMg07nLinTCBaZK6aAPA==",
"dependencies": {
"proto": "*"
},
"license": {
"name": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -16450,6 +16731,11 @@
"react-is": "^16.13.1"
}
},
"node_modules/proto": {
"version": "1.0.19",
"resolved": "https://registry.npmjs.org/proto/-/proto-1.0.19.tgz",
"integrity": "sha512-NNRn4T3FAcMbiRZAr6HlAP3PyqSv79bcYdfiZbY1nognRTVeGAd5Ncg4yHZdM2ey9ovP/sOg/0lfalvaj3ZpHA=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -16504,6 +16790,17 @@
],
"license": "MIT"
},
"node_modules/q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
"deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
"license": "MIT",
"engines": {
"node": ">=0.6.0",
"teleport": ">=0.2.0"
}
},
"node_modules/qrcode-terminal": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz",
@@ -17489,6 +17786,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ruby": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/ruby/-/ruby-0.6.1.tgz",
"integrity": "sha512-TjMB+HyxathhcgvtQ3jkYWWjW3t9q+3VPLe0jpsm1x8tryXJ3626rO6gQ+0S12Oyac+Glp6KDuy1JBdKND+eSQ==",
"license": "ISC",
"dependencies": {
"lodash": "^4.13.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -18897,6 +19203,11 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/trimArguments": {
"version": "1.0.1",
"resolved": "https://github.com/fresheneesz/trimArguments/archive/c9c023c9ff1e0aec4b67370a80a92dacadcf8b11.tar.gz",
"integrity": "sha512-SC6VIl5Lbma02frRD70cQg/IFW2czQrTiyhUqzCWUWxm80VxCR9i9HD0GkSe8gaGUlyc7YVhdMa6wccli1j/TA=="
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "jellify",
"version": "0.10.74",
"version": "0.10.80",
"private": true,
"scripts": {
"init": "npm i && npm run pod:install",
@@ -32,12 +32,16 @@
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-persist-client": "^5.66.0",
"axios": "^1.7.9",
"bundle": "^2.1.0",
"bundler": "^0.8.0",
"burnt": "^0.12.2",
"expo": "^52.0.0",
"expo-image": "^2.0.7",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"jest-expo": "^52.0.6",
"lodash": "^4.17.21",
"npm-bundle": "^3.0.3",
"react": "18.3.1",
"react-freeze": "^1.0.4",
"react-native": "0.77.0",
@@ -60,6 +64,7 @@
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1",
"tamagui": "^1.124.17"
},
"devDependencies": {