mirror of
https://github.com/Jellify-Music/App.git
synced 2026-04-21 09:08:56 -05:00
Bugfix/downloads not working after react compiler (#742)
* build out download store in zustand * add download processor use effect to top level of authenticated app
This commit is contained in:
@@ -11,17 +11,17 @@
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-vector-icons/material-design-icons": "12.4.0",
|
||||
"@react-navigation/bottom-tabs": "7.8.6",
|
||||
"@react-navigation/material-top-tabs": "7.4.4",
|
||||
"@react-navigation/native": "7.1.21",
|
||||
"@react-navigation/native-stack": "7.8.0",
|
||||
"@react-navigation/bottom-tabs": "7.8.10",
|
||||
"@react-navigation/material-top-tabs": "7.4.7",
|
||||
"@react-navigation/native": "7.1.23",
|
||||
"@react-navigation/native-stack": "7.8.4",
|
||||
"@sentry/react-native": "7.6.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@tamagui/config": "1.137.1",
|
||||
"@tanstack/query-async-storage-persister": "5.89.0",
|
||||
"@tanstack/react-query": "5.89.0",
|
||||
"@tanstack/react-query-persist-client": "5.89.0",
|
||||
"@testing-library/react-native": "^13.2.3",
|
||||
"@testing-library/react-native": "13.3.3",
|
||||
"@typedigital/telemetrydeck-react": "^0.4.1",
|
||||
"axios": "1.12.2",
|
||||
"bundle": "^2.1.0",
|
||||
@@ -45,8 +45,8 @@
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-mmkv": "3.3.3",
|
||||
"react-native-nitro-fetch": "^0.1.6",
|
||||
"react-native-nitro-modules": "^0.31.9",
|
||||
"react-native-nitro-ota": "^0.4.0",
|
||||
"react-native-nitro-modules": "0.31.10",
|
||||
"react-native-nitro-ota": "0.7.2",
|
||||
"react-native-pager-view": "^6.9.1",
|
||||
"react-native-reanimated": "4.1.5",
|
||||
"react-native-safe-area-context": "5.6.2",
|
||||
@@ -566,17 +566,17 @@
|
||||
|
||||
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.82.1", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-f5zpJg9gzh7JtCbsIwV+4kP3eI0QBuA93JGmwFRd4onQ3DnCjV2J5pYqdWtM95sjSKK1dyik59Gj01lLeKqs1Q=="],
|
||||
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.8.6", "", { "dependencies": { "@react-navigation/elements": "^2.8.3", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.21", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-0wGtU+I1rCUjvAqKtzD2dwQaTICFf5J233vkg20cLrx8LNQPAgSsbnsDSM6S315OOoVLCIL1dcrNv7ExLBlWfw=="],
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.8.10", "", { "dependencies": { "@react-navigation/elements": "^2.9.0", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-NxKjtlRwkGU3O3hPxpS+Aq7mVNfgtLzBe4xpGjQNphLzklRbxa6Me//m2eKzogpitZhLR2xZb1A49HrLuWe2ww=="],
|
||||
|
||||
"@react-navigation/core": ["@react-navigation/core@7.13.2", "", { "dependencies": { "@react-navigation/routers": "^7.5.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-A0pFeZlKp+FJob2lVr7otDt3M4rsSJrnAfXWoWR9JVeFtfEXsH/C0s7xtpDCMRUO58kzSBoTF1GYzoMC5DLD4g=="],
|
||||
"@react-navigation/core": ["@react-navigation/core@7.13.4", "", { "dependencies": { "@react-navigation/routers": "^7.5.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-JM9bkb7fr4P5YUOVEwoAZq3xPeSL9V6Nd1KKTyAwCgGUVhESbSRSy3Ri/PGu6ZcLb/t7/tM1NqP5tV1e1bAwUg=="],
|
||||
|
||||
"@react-navigation/elements": ["@react-navigation/elements@2.8.3", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.21", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-0c5nSDPP3bUFujgkSVqqMShaAup3XIxNe1KTK9LSmwKgWEneyo6OPIjIdiEwPlZvJZKi7ag5hDjacQLGwO0LGA=="],
|
||||
"@react-navigation/elements": ["@react-navigation/elements@2.9.0", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-423uE+q/esaiMbXVLckFOd9MbWG06/vCYOP2hwzEUj9ZwzUgSpsIPovcu78qa8UMuvKD8wkyouM01Wvav1y/KQ=="],
|
||||
|
||||
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.4", "", { "dependencies": { "@react-navigation/elements": "^2.8.3", "color": "^4.2.3", "react-native-tab-view": "^4.2.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.21", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-8OCT+tW4dlkEPhjmQWFEw867CKTL3och5N9TLt56lA+3pm55x1kljsVO6DF6BxF41iHrhIJIr09UrojVJDr5TQ=="],
|
||||
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.7", "", { "dependencies": { "@react-navigation/elements": "^2.9.0", "color": "^4.2.3", "react-native-tab-view": "^4.2.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-0fv+Ym9kOO7DLf8GRmkt9zNKPTbnYU62ATacv0zirNA+vBDT/fhlE67orUXsQa/nORXlUMvllCaKPf/oyD7UcQ=="],
|
||||
|
||||
"@react-navigation/native": ["@react-navigation/native@7.1.21", "", { "dependencies": { "@react-navigation/core": "^7.13.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-mhpAewdivBL01ibErr91FUW9bvKhfAF6Xv/yr6UOJtDhv0jU6iUASUcA3i3T8VJCOB/vxmoke7VDp8M+wBFs/Q=="],
|
||||
"@react-navigation/native": ["@react-navigation/native@7.1.23", "", { "dependencies": { "@react-navigation/core": "^7.13.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-V+drzVkoVA8VO83cJ59UYe7dfdnMFpGDAybp7d5O1ufxt321Z5tOtNDOzhMGzHUENqo9QWc4P/HuCUmz7KMy+A=="],
|
||||
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.8.0", "", { "dependencies": { "@react-navigation/elements": "^2.8.3", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.21", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-iRqQY+IYB610BJY/335/kdNDhXQ8L9nPUlIT+DSk88FA86+C+4/vek8wcKw8IrfwdorT4m+6TY0v7Qnrt+WLKQ=="],
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.8.4", "", { "dependencies": { "@react-navigation/elements": "^2.9.0", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-7kpYHoZZ81SPtDDG9ttZtI4nXR8GbVsLL1KnT/7RiLkFdqHXlriGpVhG5BKJRS1CYXrGEn40NogYW2+OBplglg=="],
|
||||
|
||||
"@react-navigation/routers": ["@react-navigation/routers@7.5.2", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-kymreY5aeTz843E+iPAukrsOtc7nabAH6novtAPREmmGu77dQpfxPB2ZWpKb5nRErIRowp1kYRoN2Ckl+S6JYw=="],
|
||||
|
||||
@@ -1930,9 +1930,9 @@
|
||||
|
||||
"react-native-nitro-fetch": ["react-native-nitro-fetch@0.1.6", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.29.2", "react-native-worklets-core": "^1.6.0" }, "optionalPeers": ["react-native-worklets-core"] }, "sha512-DbE/vN5B67SJM8Q0myHOwSSc7ASqJPaKYXVsWdNGIPS+csr9gygCKILT4RQ+xZ92iJGKn4bfyq+rRsacRWBV9A=="],
|
||||
|
||||
"react-native-nitro-modules": ["react-native-nitro-modules@0.31.9", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-w7NtHq4wP6LZgvDs7zbFU3B2uHpRx/bJlSTckw0By8NyEX39fURPGgHyi4a67q1O7I3iFJvbRNWUiiOBbNvHDg=="],
|
||||
"react-native-nitro-modules": ["react-native-nitro-modules@0.31.10", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-hcvjTu9YJE9fMmnAUvhG8CxvYLpOuMQ/2eyi/S6GyrecezF6Rmk/uRQEL6v09BRFWA/xRVZNQVulQPS+2HS3mQ=="],
|
||||
|
||||
"react-native-nitro-ota": ["react-native-nitro-ota@0.4.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.29.8" } }, "sha512-/JAoM2m3WsvnO7dC51bf5jCghxO78yrP3vHyq3/itK+MqiwU8HPk8bGbXLhE+/GYRPS8DbUHGrzptzO2KOoutQ=="],
|
||||
"react-native-nitro-ota": ["react-native-nitro-ota@0.7.2", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.29.8" } }, "sha512-DUa2/QhFJBhSbzrTHGrc+qm1pSuJctccUcHlHZXjPV4fCEpi+4Y17QqI9U4D9MUnnP77afKEZJKFy+0NQeSAdA=="],
|
||||
|
||||
"react-native-pager-view": ["react-native-pager-view@6.9.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw=="],
|
||||
|
||||
|
||||
+6
-6
@@ -42,7 +42,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- NitroModules (0.31.9):
|
||||
- NitroModules (0.31.10):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -71,7 +71,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- NitroOta (0.4.0):
|
||||
- NitroOta (0.7.2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -102,7 +102,7 @@ PODS:
|
||||
- SocketRocket
|
||||
- SSZipArchive
|
||||
- Yoga
|
||||
- NitroOtaBundleManager (0.4.0):
|
||||
- NitroOtaBundleManager (0.7.2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3449,9 +3449,9 @@ SPEC CHECKSUMS:
|
||||
google-cast-sdk: 1fb6724e94cc5ff23b359176e0cf6360586bb97a
|
||||
hermes-engine: 273e30e7fb618279934b0b95ffab60ecedb7acf5
|
||||
NitroFetch: 660adfb47f84b28db664f97b50e5dc28506ab6c1
|
||||
NitroModules: 224bf833d249b0c7ce32831368f2887008579b13
|
||||
NitroOta: b4f7cdbe660e8f07f80f5eb9f169d70f698ea284
|
||||
NitroOtaBundleManager: 5e7c0f8c3f76cc06f9fe07a63879fe35496c27c7
|
||||
NitroModules: 5bc319d441f4983894ea66b1d392c519536e6d23
|
||||
NitroOta: 7755c4728f7348584cebb2d428480b1ed0cd2679
|
||||
NitroOtaBundleManager: 482abb17f0ca629ad551da43f13e76e59dba9568
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
Protobuf: 164aea2ae380c3951abdc3e195220c01d17400e0
|
||||
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
|
||||
|
||||
+8
-8
@@ -43,19 +43,19 @@
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-vector-icons/material-design-icons": "12.4.0",
|
||||
"@react-navigation/bottom-tabs": "7.8.6",
|
||||
"@react-navigation/material-top-tabs": "7.4.4",
|
||||
"@react-navigation/native": "7.1.21",
|
||||
"@react-navigation/native-stack": "7.8.0",
|
||||
"@react-navigation/bottom-tabs": "7.8.10",
|
||||
"@react-navigation/material-top-tabs": "7.4.7",
|
||||
"@react-navigation/native": "7.1.23",
|
||||
"@react-navigation/native-stack": "7.8.4",
|
||||
"@sentry/react-native": "7.6.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@tamagui/config": "1.137.1",
|
||||
"@tanstack/query-async-storage-persister": "5.89.0",
|
||||
"@tanstack/react-query": "5.89.0",
|
||||
"@tanstack/react-query-persist-client": "5.89.0",
|
||||
"@testing-library/react-native": "^13.2.3",
|
||||
"@testing-library/react-native": "13.3.3",
|
||||
"@typedigital/telemetrydeck-react": "^0.4.1",
|
||||
"axios": "1.12.2",
|
||||
"axios": "1.13.2",
|
||||
"bundle": "^2.1.0",
|
||||
"dlx": "^0.2.1",
|
||||
"invert-color": "^2.0.0",
|
||||
@@ -77,8 +77,8 @@
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-mmkv": "3.3.3",
|
||||
"react-native-nitro-fetch": "^0.1.6",
|
||||
"react-native-nitro-modules": "^0.31.9",
|
||||
"react-native-nitro-ota": "^0.4.0",
|
||||
"react-native-nitro-modules": "0.31.10",
|
||||
"react-native-nitro-ota": "0.7.2",
|
||||
"react-native-pager-view": "^6.9.1",
|
||||
"react-native-reanimated": "4.1.5",
|
||||
"react-native-safe-area-context": "5.6.2",
|
||||
|
||||
@@ -12,8 +12,6 @@ import ItemImage from '../Global/components/image'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
import { useNetworkContext } from '../../providers/Network'
|
||||
import { useNetworkStatus } from '../../stores/network'
|
||||
import { useLoadNewQueue } from '../../providers/Player/hooks/mutations'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
@@ -22,12 +20,13 @@ import HomeStackParamList from '../../screens/Home/types'
|
||||
import LibraryStackParamList from '../../screens/Library/types'
|
||||
import DiscoverStackParamList from '../../screens/Discover/types'
|
||||
import { BaseStackParamList } from '../../screens/types'
|
||||
import useStreamingDeviceProfile, { useDownloadingDeviceProfile } from '../../stores/device-profile'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
|
||||
import { useApi } from '../../stores'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchAlbumDiscs } from '../../api/queries/item'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import useAddToPendingDownloads, { usePendingDownloads } from '../../stores/network/downloads'
|
||||
|
||||
/**
|
||||
* The screen for an Album's track list
|
||||
@@ -47,14 +46,11 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
|
||||
queryFn: () => fetchAlbumDiscs(api, album),
|
||||
})
|
||||
|
||||
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
|
||||
const downloadingDeviceProfile = useDownloadingDeviceProfile()
|
||||
const addToDownloadQueue = useAddToPendingDownloads()
|
||||
|
||||
const downloadAlbum = (item: BaseItemDto[]) => {
|
||||
if (!api) return
|
||||
const jellifyTracks = item.map((item) => mapDtoToTrack(api, item, downloadingDeviceProfile))
|
||||
addToDownloadQueue(jellifyTracks)
|
||||
}
|
||||
const pendingDownloads = usePendingDownloads()
|
||||
|
||||
const downloadAlbum = (item: BaseItemDto[]) => addToDownloadQueue(item)
|
||||
|
||||
const sections = (Array.isArray(discs) ? discs : []).map(({ title, data }) => ({
|
||||
title,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
BaseItemKind,
|
||||
MediaSourceInfo,
|
||||
} from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { ListItem, ScrollView, Spinner, View, YGroup } from 'tamagui'
|
||||
import { ListItem, Spinner, View, YGroup } from 'tamagui'
|
||||
import { BaseStackParamList, RootStackParamList } from '../../screens/types'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import FavoriteContextMenuRow from '../Global/components/favorite-context-menu-row'
|
||||
@@ -25,14 +25,17 @@ import TextTicker from 'react-native-text-ticker'
|
||||
import { TextTickerConfig } from '../Player/component.config'
|
||||
import { useAddToQueue } from '../../providers/Player/hooks/mutations'
|
||||
import { useNetworkStatus } from '../../stores/network'
|
||||
import { useNetworkContext } from '../../providers/Network'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
import useStreamingDeviceProfile, { useDownloadingDeviceProfile } from '../../stores/device-profile'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import { useIsDownloaded } from '../../api/queries/download'
|
||||
import { useDeleteDownloads } from '../../api/mutations/download'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { Platform } from 'react-native'
|
||||
import { useApi } from '../../stores'
|
||||
import useAddToPendingDownloads, {
|
||||
useIsDownloading,
|
||||
usePendingDownloads,
|
||||
} from '../../stores/network/downloads'
|
||||
import { networkStatusTypes } from '../Network/internetConnectionWatcher'
|
||||
|
||||
type StackNavigation = Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
|
||||
@@ -55,6 +58,8 @@ export default function ItemContext({
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
const isArtist = item.Type === BaseItemKind.MusicArtist
|
||||
const isAlbum = item.Type === BaseItemKind.MusicAlbum
|
||||
const isTrack = item.Type === BaseItemKind.Audio
|
||||
@@ -242,29 +247,15 @@ function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Ele
|
||||
}
|
||||
|
||||
function DownloadMenuRow({ items }: { items: BaseItemDto[] }): React.JSX.Element {
|
||||
const api = useApi()
|
||||
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
|
||||
const addToDownloadQueue = useAddToPendingDownloads()
|
||||
|
||||
const useRemoveDownload = useDeleteDownloads()
|
||||
|
||||
const deviceProfile = useDownloadingDeviceProfile()
|
||||
|
||||
const isDownloaded = useIsDownloaded(items.map(({ Id }) => Id))
|
||||
|
||||
const downloadItems = () => {
|
||||
if (!api) return
|
||||
|
||||
const tracks = items.map((item) => mapDtoToTrack(api, item, deviceProfile))
|
||||
addToDownloadQueue(tracks)
|
||||
}
|
||||
|
||||
const removeDownloads = () => useRemoveDownload(items.map(({ Id }) => Id))
|
||||
|
||||
const isPending =
|
||||
items.filter(
|
||||
(item) =>
|
||||
pendingDownloads.filter((download) => download.item.Id === item.Id).length > 0,
|
||||
).length > 0
|
||||
const isPending = useIsDownloading(items)
|
||||
|
||||
return isPending ? (
|
||||
<ListItem
|
||||
@@ -287,7 +278,7 @@ function DownloadMenuRow({ items }: { items: BaseItemDto[] }): React.JSX.Element
|
||||
backgroundColor={'transparent'}
|
||||
gap={'$2.5'}
|
||||
justifyContent='flex-start'
|
||||
onPress={downloadItems}
|
||||
onPress={() => addToDownloadQueue(items)}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -3,22 +3,19 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { H5, Spacer, XStack, YStack } from 'tamagui'
|
||||
import InstantMixButton from '../../Global/components/instant-mix-button'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useNetworkStatus } from '../../../../src/stores/network'
|
||||
import { useNetworkContext } from '../../../../src/providers/Network'
|
||||
import { useNetworkStatus } from '../../../stores/network'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
import { mapDtoToTrack } from '../../../utils/mappings'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import LibraryStackParamList from '@/src/screens/Library/types'
|
||||
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import useStreamingDeviceProfile, {
|
||||
useDownloadingDeviceProfile,
|
||||
} from '../../../stores/device-profile'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useApi } from '../../../stores'
|
||||
import Input from '../../Global/helpers/input'
|
||||
import Animated, { FadeInDown, FadeOutDown } from 'react-native-reanimated'
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
import useAddToPendingDownloads, { usePendingDownloads } from '../../../stores/network/downloads'
|
||||
|
||||
export default function PlaylistTracklistHeader({
|
||||
playlist,
|
||||
@@ -85,7 +82,6 @@ export default function PlaylistTracklistHeader({
|
||||
}
|
||||
|
||||
function PlaylistHeaderControls({
|
||||
editing,
|
||||
playlist,
|
||||
playlistTracks,
|
||||
}: {
|
||||
@@ -93,9 +89,9 @@ function PlaylistHeaderControls({
|
||||
playlist: BaseItemDto
|
||||
playlistTracks: BaseItemDto[]
|
||||
}): React.JSX.Element {
|
||||
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
|
||||
const addToDownloadQueue = useAddToPendingDownloads()
|
||||
const pendingDownloads = usePendingDownloads()
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
const downloadingDeviceProfile = useDownloadingDeviceProfile()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const isDownloading = pendingDownloads.length != 0
|
||||
const api = useApi()
|
||||
@@ -104,13 +100,7 @@ function PlaylistHeaderControls({
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<LibraryStackParamList>>()
|
||||
|
||||
const downloadPlaylist = () => {
|
||||
if (!api) return
|
||||
const jellifyTracks = playlistTracks.map((item) =>
|
||||
mapDtoToTrack(api, item, downloadingDeviceProfile),
|
||||
)
|
||||
addToDownloadQueue(jellifyTracks)
|
||||
}
|
||||
const downloadPlaylist = () => addToDownloadQueue(playlistTracks)
|
||||
|
||||
const playPlaylist = (shuffled: boolean = false) => {
|
||||
if (!playlistTracks || playlistTracks.length === 0) return
|
||||
|
||||
@@ -4,9 +4,9 @@ import RNFS from 'react-native-fs'
|
||||
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
import { deleteAudioCache } from '../../api/mutations/download/offlineModeUtils'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { useNetworkContext } from '../../providers/Network'
|
||||
import { getToken, View } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { useDownloadProgress } from '@/src/stores/network/downloads'
|
||||
|
||||
// 🔹 Single Download Item with animated progress bar
|
||||
function DownloadItem({
|
||||
@@ -43,7 +43,7 @@ export default function StorageBar(): React.JSX.Element {
|
||||
const [used, setUsed] = useState(0)
|
||||
const [total, setTotal] = useState(1)
|
||||
|
||||
const { activeDownloads: activeDownloadsArray } = useNetworkContext()
|
||||
const activeDownloadsArray = useDownloadProgress()
|
||||
|
||||
const usageShared = useSharedValue(0)
|
||||
const percentUsed = used / total
|
||||
|
||||
@@ -2,7 +2,6 @@ import _ from 'lodash'
|
||||
import React, { useEffect } from 'react'
|
||||
import Root from '../screens'
|
||||
import { PlayerProvider } from '../providers/Player'
|
||||
import { NetworkContextProvider } from '../providers/Network'
|
||||
import { DisplayProvider } from '../providers/Display/display-provider'
|
||||
import {
|
||||
createTelemetryDeck,
|
||||
@@ -20,6 +19,7 @@ import { StorageProvider } from '../providers/Storage'
|
||||
import { useSelectPlayerEngine } from '../stores/player/engine'
|
||||
import { useSendMetricsSetting, useThemeSetting } from '../stores/settings/app'
|
||||
import { GLITCHTIP_DSN } from '../configs/config'
|
||||
import useDownloadProcessor from '../hooks/use-download-processor'
|
||||
/**
|
||||
* The main component for the Jellify app. Children are wrapped in the {@link JellifyProvider}
|
||||
* @returns The {@link Jellify} component
|
||||
@@ -76,14 +76,14 @@ function App(): React.JSX.Element {
|
||||
}
|
||||
}, [sendMetrics])
|
||||
|
||||
useDownloadProcessor()
|
||||
|
||||
return (
|
||||
<NetworkContextProvider>
|
||||
<StorageProvider>
|
||||
<CarPlayProvider />
|
||||
<PlayerProvider />
|
||||
<Root />
|
||||
<Toast topOffset={getToken('$12')} config={JellifyToastConfig(theme)} />
|
||||
</StorageProvider>
|
||||
</NetworkContextProvider>
|
||||
<StorageProvider>
|
||||
<CarPlayProvider />
|
||||
<PlayerProvider />
|
||||
<Root />
|
||||
<Toast topOffset={getToken('$12')} config={JellifyToastConfig(theme)} />
|
||||
</StorageProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const MAX_CONCURRENT_DOWNLOADS = 1
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
useAddToCompletedDownloads,
|
||||
useAddToCurrentDownloads,
|
||||
useAddToFailedDownloads,
|
||||
useDownloadsStore,
|
||||
useRemoveFromCurrentDownloads,
|
||||
useRemoveFromPendingDownloads,
|
||||
} from '../stores/network/downloads'
|
||||
import { MAX_CONCURRENT_DOWNLOADS } from '../configs/download.config'
|
||||
import { useAllDownloadedTracks } from '../api/queries/download'
|
||||
import { saveAudio } from '../api/mutations/download/offlineModeUtils'
|
||||
|
||||
const useDownloadProcessor = () => {
|
||||
const { pendingDownloads, currentDownloads } = useDownloadsStore()
|
||||
|
||||
const { data: downloadedTracks } = useAllDownloadedTracks()
|
||||
|
||||
const addToCurrentDownloads = useAddToCurrentDownloads()
|
||||
|
||||
const removeFromCurrentDownloads = useRemoveFromCurrentDownloads()
|
||||
|
||||
const removeFromPendingDownloads = useRemoveFromPendingDownloads()
|
||||
|
||||
const addToCompletedDownloads = useAddToCompletedDownloads()
|
||||
|
||||
const addToFailedDownloads = useAddToFailedDownloads()
|
||||
|
||||
const { refetch: refetchDownloadedTracks } = useAllDownloadedTracks()
|
||||
|
||||
return useEffect(() => {
|
||||
if (pendingDownloads.length > 0 && currentDownloads.length < MAX_CONCURRENT_DOWNLOADS) {
|
||||
const availableSlots = MAX_CONCURRENT_DOWNLOADS - currentDownloads.length
|
||||
const filesToStart = pendingDownloads.slice(0, availableSlots)
|
||||
|
||||
console.debug('Downloading from queue')
|
||||
|
||||
filesToStart.forEach((file) => {
|
||||
addToCurrentDownloads(file)
|
||||
removeFromPendingDownloads(file)
|
||||
if (downloadedTracks?.some((t) => t.item.Id === file.item.Id)) {
|
||||
removeFromCurrentDownloads(file)
|
||||
addToCompletedDownloads(file)
|
||||
return
|
||||
}
|
||||
|
||||
saveAudio(file, () => {}, false).then((success) => {
|
||||
removeFromCurrentDownloads(file)
|
||||
|
||||
if (success) {
|
||||
addToCompletedDownloads(file)
|
||||
} else {
|
||||
addToFailedDownloads(file)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if (pendingDownloads.length === 0 && currentDownloads.length === 0) {
|
||||
refetchDownloadedTracks()
|
||||
}
|
||||
}, [pendingDownloads.length, currentDownloads.length])
|
||||
}
|
||||
|
||||
export default useDownloadProcessor
|
||||
@@ -1,105 +0,0 @@
|
||||
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||
import { JellifyDownloadProgress } from '../../types/JellifyDownload'
|
||||
import { saveAudio } from '../../api/mutations/download/offlineModeUtils'
|
||||
import JellifyTrack from '../../types/JellifyTrack'
|
||||
import { useAllDownloadedTracks } from '../../api/queries/download'
|
||||
import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
|
||||
|
||||
interface NetworkContext {
|
||||
activeDownloads: JellifyDownloadProgress | undefined
|
||||
pendingDownloads: JellifyTrack[]
|
||||
downloadingDownloads: JellifyTrack[]
|
||||
completedDownloads: JellifyTrack[]
|
||||
failedDownloads: JellifyTrack[]
|
||||
addToDownloadQueue: (items: JellifyTrack[]) => boolean
|
||||
}
|
||||
|
||||
const MAX_CONCURRENT_DOWNLOADS = 1
|
||||
|
||||
const COMPONENT_NAME = 'NetworkProvider'
|
||||
|
||||
const NetworkContextInitializer = () => {
|
||||
usePerformanceMonitor(COMPONENT_NAME, 5)
|
||||
const [downloadProgress, setDownloadProgress] = useState<JellifyDownloadProgress>({})
|
||||
|
||||
// Mutiple Downloads
|
||||
const [pending, setPending] = useState<JellifyTrack[]>([])
|
||||
const [downloading, setDownloading] = useState<JellifyTrack[]>([])
|
||||
const [completed, setCompleted] = useState<JellifyTrack[]>([])
|
||||
const [failed, setFailed] = useState<JellifyTrack[]>([])
|
||||
|
||||
const { data: downloadedTracks, refetch: refetchDownloadedTracks } = useAllDownloadedTracks()
|
||||
|
||||
useEffect(() => {
|
||||
if (pending.length > 0 && downloading.length < MAX_CONCURRENT_DOWNLOADS) {
|
||||
const availableSlots = MAX_CONCURRENT_DOWNLOADS - downloading.length
|
||||
const filesToStart = pending.slice(0, availableSlots)
|
||||
|
||||
filesToStart.forEach((file) => {
|
||||
setDownloading((prev) => [...prev, file])
|
||||
setPending((prev) => prev.filter((f) => f.item.Id !== file.item.Id))
|
||||
if (downloadedTracks?.some((t) => t.item.Id === file.item.Id)) {
|
||||
setDownloading((prev) => prev.filter((f) => f.item.Id !== file.item.Id))
|
||||
setCompleted((prev) => [...prev, file])
|
||||
return
|
||||
}
|
||||
|
||||
saveAudio(file, setDownloadProgress, false).then((success) => {
|
||||
setDownloading((prev) => prev.filter((f) => f.item.Id !== file.item.Id))
|
||||
setDownloadProgress((prev) => {
|
||||
const next = { ...prev }
|
||||
delete next[file.url]
|
||||
if (file.artwork) delete next[file.artwork]
|
||||
return next
|
||||
})
|
||||
if (success) {
|
||||
setCompleted((prev) => [...prev, file])
|
||||
} else {
|
||||
setFailed((prev) => [...prev, file])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if (pending.length === 0 && downloading.length === 0) {
|
||||
refetchDownloadedTracks()
|
||||
}
|
||||
}, [pending, downloading])
|
||||
|
||||
const addToDownloadQueue = (items: JellifyTrack[]) => {
|
||||
setPending((prev) => [...prev, ...items])
|
||||
return true
|
||||
}
|
||||
|
||||
return {
|
||||
activeDownloads: downloadProgress,
|
||||
downloadedTracks,
|
||||
pendingDownloads: pending,
|
||||
downloadingDownloads: downloading,
|
||||
completedDownloads: completed,
|
||||
failedDownloads: failed,
|
||||
addToDownloadQueue,
|
||||
}
|
||||
}
|
||||
|
||||
const NetworkContext = createContext<NetworkContext>({
|
||||
activeDownloads: {},
|
||||
pendingDownloads: [],
|
||||
downloadingDownloads: [],
|
||||
completedDownloads: [],
|
||||
failedDownloads: [],
|
||||
addToDownloadQueue: () => true,
|
||||
})
|
||||
|
||||
export const NetworkContextProvider: ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode
|
||||
}) => React.JSX.Element = ({ children }: { children: ReactNode }) => {
|
||||
const context = NetworkContextInitializer()
|
||||
|
||||
const value = context
|
||||
|
||||
return <NetworkContext.Provider value={value}>{children}</NetworkContext.Provider>
|
||||
}
|
||||
|
||||
export const useNetworkContext = () => useContext(NetworkContext)
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { PropsWithChildren, createContext, useContext, useState } from 'react'
|
||||
import React, { PropsWithChildren, createContext, use, useContext, useState } from 'react'
|
||||
import { useAllDownloadedTracks, useStorageInUse } from '../../api/queries/download'
|
||||
import { JellifyDownload, JellifyDownloadProgress } from '../../types/JellifyDownload'
|
||||
import {
|
||||
DeleteDownloadsResult,
|
||||
deleteDownloadsByIds,
|
||||
} from '../../api/mutations/download/offlineModeUtils'
|
||||
import { useNetworkContext } from '../Network'
|
||||
import { useDownloadProgress } from '../../stores/network/downloads'
|
||||
|
||||
export type StorageSummary = {
|
||||
totalSpace: number
|
||||
@@ -67,7 +67,7 @@ export function StorageProvider({ children }: PropsWithChildren): React.JSX.Elem
|
||||
refetch: refetchStorageInfo,
|
||||
isFetching: isFetchingStorage,
|
||||
} = useStorageInUse()
|
||||
const { activeDownloads } = useNetworkContext()
|
||||
const activeDownloads = useDownloadProgress()
|
||||
|
||||
const [selection, setSelection] = useState<StorageSelectionState>({})
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
@@ -226,7 +226,7 @@ export function StorageProvider({ children }: PropsWithChildren): React.JSX.Elem
|
||||
}
|
||||
|
||||
export const useStorageContext = () => {
|
||||
const context = useContext(StorageContext)
|
||||
const context = use(StorageContext)
|
||||
if (!context) throw new Error('StorageContext must be used within a StorageProvider')
|
||||
return context
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { FlashList, ListRenderItem } from '@shopify/flash-list'
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
@@ -47,62 +47,44 @@ export default function StorageManagementScreen(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<SettingsStackParamList>>()
|
||||
const showDeletionToast = useDeletionToast()
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
void refresh()
|
||||
}, [refresh]),
|
||||
)
|
||||
const sortedDownloads = !downloads
|
||||
? []
|
||||
: [...downloads].sort(
|
||||
(a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime(),
|
||||
)
|
||||
|
||||
const sortedDownloads = useMemo(() => {
|
||||
if (!downloads) return []
|
||||
return [...downloads].sort(
|
||||
(a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime(),
|
||||
)
|
||||
}, [downloads])
|
||||
const selectedIds = Object.entries(selection)
|
||||
.filter(([, isSelected]) => isSelected)
|
||||
.map(([id]) => id)
|
||||
|
||||
const selectedIds = useMemo(
|
||||
() =>
|
||||
Object.entries(selection)
|
||||
.filter(([, isSelected]) => isSelected)
|
||||
.map(([id]) => id),
|
||||
[selection],
|
||||
)
|
||||
const selectedBytes =
|
||||
!selectedIds.length || !downloads
|
||||
? 0
|
||||
: downloads.reduce((total, download) => {
|
||||
return new Set(selectedIds).has(download.item.Id as string)
|
||||
? total + getDownloadSize(download)
|
||||
: total
|
||||
}, 0)
|
||||
|
||||
const selectedBytes = useMemo(() => {
|
||||
if (!selectedIds.length || !downloads) return 0
|
||||
const selectedSet = new Set(selectedIds)
|
||||
return downloads.reduce((total, download) => {
|
||||
return selectedSet.has(download.item.Id as string)
|
||||
? total + getDownloadSize(download)
|
||||
: total
|
||||
}, 0)
|
||||
}, [downloads, selectedIds])
|
||||
|
||||
const handleApplySuggestion = useCallback(
|
||||
async (suggestion: CleanupSuggestion) => {
|
||||
if (!suggestion.itemIds.length) return
|
||||
setApplyingSuggestionId(suggestion.id)
|
||||
try {
|
||||
const result = await deleteDownloads(suggestion.itemIds)
|
||||
if (result?.deletedCount)
|
||||
showDeletionToast(`Removed ${result.deletedCount} downloads`, result.freedBytes)
|
||||
} finally {
|
||||
setApplyingSuggestionId(null)
|
||||
}
|
||||
},
|
||||
[deleteDownloads, showDeletionToast],
|
||||
)
|
||||
|
||||
const handleDeleteSingle = useCallback(
|
||||
async (download: JellifyDownload) => {
|
||||
const result = await deleteDownloads([download.item.Id as string])
|
||||
const handleApplySuggestion = async (suggestion: CleanupSuggestion) => {
|
||||
if (!suggestion.itemIds.length) return
|
||||
setApplyingSuggestionId(suggestion.id)
|
||||
try {
|
||||
const result = await deleteDownloads(suggestion.itemIds)
|
||||
if (result?.deletedCount)
|
||||
showDeletionToast(`Removed ${download.title ?? 'track'}`, result.freedBytes)
|
||||
},
|
||||
[deleteDownloads, showDeletionToast],
|
||||
)
|
||||
showDeletionToast(`Removed ${result.deletedCount} downloads`, result.freedBytes)
|
||||
} finally {
|
||||
setApplyingSuggestionId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteAll = useCallback(() => {
|
||||
const handleDeleteSingle = async (download: JellifyDownload) => {
|
||||
const result = await deleteDownloads([download.item.Id as string])
|
||||
if (result?.deletedCount)
|
||||
showDeletionToast(`Removed ${download.title ?? 'track'}`, result.freedBytes)
|
||||
}
|
||||
|
||||
const handleDeleteAll = () =>
|
||||
Alert.alert(
|
||||
'Delete all downloads?',
|
||||
'This will remove all downloaded music from your device. This action cannot be undone.',
|
||||
@@ -124,9 +106,8 @@ export default function StorageManagementScreen(): React.JSX.Element {
|
||||
},
|
||||
],
|
||||
)
|
||||
}, [downloads, deleteDownloads, showDeletionToast])
|
||||
|
||||
const handleDeleteSelection = useCallback(() => {
|
||||
const handleDeleteSelection = () =>
|
||||
Alert.alert(
|
||||
'Delete selected items?',
|
||||
`Are you sure you want to delete ${selectedIds.length} items?`,
|
||||
@@ -148,20 +129,16 @@ export default function StorageManagementScreen(): React.JSX.Element {
|
||||
},
|
||||
],
|
||||
)
|
||||
}, [selectedIds, deleteDownloads, showDeletionToast, clearSelection])
|
||||
|
||||
const renderDownloadItem: ListRenderItem<JellifyDownload> = useCallback(
|
||||
({ item }) => (
|
||||
<DownloadRow
|
||||
download={item}
|
||||
isSelected={Boolean(selection[item.item.Id as string])}
|
||||
onToggle={() => toggleSelection(item.item.Id as string)}
|
||||
onDelete={() => {
|
||||
void handleDeleteSingle(item)
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[selection, toggleSelection, handleDeleteSingle],
|
||||
const renderDownloadItem: ListRenderItem<JellifyDownload> = ({ item }) => (
|
||||
<DownloadRow
|
||||
download={item}
|
||||
isSelected={Boolean(selection[item.item.Id as string])}
|
||||
onToggle={() => toggleSelection(item.item.Id as string)}
|
||||
onDelete={() => {
|
||||
void handleDeleteSingle(item)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
const topPadding = 16
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { mmkvStateStorage } from '../../constants/storage'
|
||||
import { JellifyDownloadProgress } from '@/src/types/JellifyDownload'
|
||||
import JellifyTrack from '@/src/types/JellifyTrack'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, devtools, persist } from 'zustand/middleware'
|
||||
import { useApi } from '..'
|
||||
import { useDownloadingDeviceProfile } from '../device-profile'
|
||||
|
||||
type DownloadsStore = {
|
||||
downloadProgress: JellifyDownloadProgress
|
||||
setDownloadProgress: (progress: JellifyDownloadProgress) => void
|
||||
pendingDownloads: JellifyTrack[]
|
||||
setPendingDownloads: (items: JellifyTrack[]) => void
|
||||
currentDownloads: JellifyTrack[]
|
||||
setCurrentDownloads: (items: JellifyTrack[]) => void
|
||||
completedDownloads: JellifyTrack[]
|
||||
setCompletedDownloads: (items: JellifyTrack[]) => void
|
||||
failedDownloads: JellifyTrack[]
|
||||
setFailedDownloads: (items: JellifyTrack[]) => void
|
||||
}
|
||||
|
||||
export const useDownloadsStore = create<DownloadsStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set) => ({
|
||||
downloadProgress: {},
|
||||
setDownloadProgress: (progress) =>
|
||||
set({
|
||||
downloadProgress: progress,
|
||||
}),
|
||||
pendingDownloads: [],
|
||||
setPendingDownloads: (items) =>
|
||||
set({
|
||||
pendingDownloads: items,
|
||||
}),
|
||||
currentDownloads: [],
|
||||
setCurrentDownloads: (items) => set({ currentDownloads: items }),
|
||||
completedDownloads: [],
|
||||
setCompletedDownloads: (items) => set({ completedDownloads: items }),
|
||||
failedDownloads: [],
|
||||
setFailedDownloads: (items) => set({ failedDownloads: items }),
|
||||
}),
|
||||
{
|
||||
name: 'downloads-store',
|
||||
storage: createJSONStorage(() => mmkvStateStorage),
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
export const useDownloadProgress = () => useDownloadsStore((state) => state.downloadProgress)
|
||||
|
||||
export const usePendingDownloads = () => useDownloadsStore((state) => state.pendingDownloads)
|
||||
|
||||
export const useCurrentDownloads = () => useDownloadsStore((state) => state.currentDownloads)
|
||||
|
||||
export const useFailedDownloads = () => useDownloadsStore((state) => state.failedDownloads)
|
||||
|
||||
export const useIsDownloading = (items: BaseItemDto[]) => {
|
||||
const pendingDownloads = usePendingDownloads()
|
||||
const currentDownloads = useCurrentDownloads()
|
||||
|
||||
const downloadQueue = new Set([
|
||||
...pendingDownloads.map((download) => download.item.Id),
|
||||
...currentDownloads.map((download) => download.item.Id),
|
||||
])
|
||||
|
||||
const itemIds = items.map((item) => item.Id)
|
||||
|
||||
return itemIds.filter((id) => downloadQueue.has(id)).length === items.length
|
||||
}
|
||||
|
||||
export const useAddToCompletedDownloads = () => {
|
||||
const currentDownloads = useCurrentDownloads()
|
||||
const setCompletedDownloads = useDownloadsStore((state) => state.setCompletedDownloads)
|
||||
|
||||
return (item: JellifyTrack) => setCompletedDownloads([...currentDownloads, item])
|
||||
}
|
||||
|
||||
export const useAddToCurrentDownloads = () => {
|
||||
const currentDownloads = useCurrentDownloads()
|
||||
const setCurrentDownloads = useDownloadsStore((state) => state.setCurrentDownloads)
|
||||
|
||||
return (item: JellifyTrack) => setCurrentDownloads([...currentDownloads, item])
|
||||
}
|
||||
|
||||
export const useRemoveFromCurrentDownloads = () => {
|
||||
const currentDownloads = useCurrentDownloads()
|
||||
|
||||
const setCurrentDownloads = useDownloadsStore((state) => state.setCurrentDownloads)
|
||||
|
||||
return (item: JellifyTrack) =>
|
||||
setCurrentDownloads(
|
||||
currentDownloads.filter((download) => download.item.Id !== item.item.Id),
|
||||
)
|
||||
}
|
||||
|
||||
export const useRemoveFromPendingDownloads = () => {
|
||||
const pendingDownloads = usePendingDownloads()
|
||||
|
||||
const setPendingDownloads = useDownloadsStore((state) => state.setPendingDownloads)
|
||||
|
||||
return (item: JellifyTrack) =>
|
||||
setPendingDownloads(
|
||||
pendingDownloads.filter((download) => download.item.Id !== item.item.Id),
|
||||
)
|
||||
}
|
||||
|
||||
export const useAddToFailedDownloads = () => (item: JellifyTrack) => {
|
||||
const failedDownloads = useFailedDownloads()
|
||||
|
||||
const setFailedDownloads = useDownloadsStore((state) => state.setFailedDownloads)
|
||||
|
||||
return setFailedDownloads([...failedDownloads, item])
|
||||
}
|
||||
|
||||
const useAddToPendingDownloads = () => {
|
||||
const api = useApi()
|
||||
|
||||
const downloadingDeviceProfile = useDownloadingDeviceProfile()
|
||||
|
||||
const pendingDownloads = usePendingDownloads()
|
||||
|
||||
const setPendingDownloads = useDownloadsStore((state) => state.setPendingDownloads)
|
||||
|
||||
return (items: BaseItemDto[]) => {
|
||||
const downloads = api
|
||||
? items.map((item) => mapDtoToTrack(api, item, downloadingDeviceProfile))
|
||||
: []
|
||||
|
||||
return setPendingDownloads([...pendingDownloads, ...downloads])
|
||||
}
|
||||
}
|
||||
|
||||
export default useAddToPendingDownloads
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { networkStatusTypes } from '../components/Network/internetConnectionWatcher'
|
||||
import { networkStatusTypes } from '../../components/Network/internetConnectionWatcher'
|
||||
|
||||
type NetworkStore = {
|
||||
networkStatus: networkStatusTypes | null
|
||||
Reference in New Issue
Block a user