mirror of
https://github.com/Jellify-Music/App.git
synced 2025-12-30 23:39:51 -06:00
Fix Android? Queue and Player Control Improvements (#293)
I hope this fixes android - restored a dependency that wasnt present in previous versions (.5, .6, .7) Tightening up player refactor, namely around indexes and skipping tracks
This commit is contained in:
@@ -5,4 +5,7 @@ node_modules/
|
||||
*.yaml
|
||||
|
||||
# Ignore Markdown files
|
||||
*.md
|
||||
*.md
|
||||
|
||||
# Ignore iOS Pods directory
|
||||
ios/**/*
|
||||
@@ -4,7 +4,6 @@ import Icon from '../helpers/icon'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { getTokens, Spinner } from 'tamagui'
|
||||
import { usePlayerContext } from '../../..//player/provider'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchUserData } from '../../../api/queries/functions/favorites'
|
||||
import { useJellifyUserDataContext } from '../../../components/user-data-provider'
|
||||
@@ -20,8 +19,6 @@ export default function FavoriteButton({
|
||||
item: BaseItemDto
|
||||
onToggle?: () => void
|
||||
}): React.JSX.Element {
|
||||
usePlayerContext()
|
||||
|
||||
const [isFavorite, setFavorite] = useState<boolean>(isFavoriteItem(item))
|
||||
|
||||
const { toggleFavorite } = useJellifyUserDataContext()
|
||||
|
||||
@@ -9,8 +9,7 @@ import Icon from '../helpers/icon'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { RunTimeTicks } from '../helpers/time-codes'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { State } from 'react-native-track-player'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
|
||||
export default function Item({
|
||||
item,
|
||||
@@ -60,9 +59,7 @@ export default function Item({
|
||||
queuingType: QueuingType.FromSelection,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
useStartPlayback.mutate()
|
||||
},
|
||||
onSuccess: () => useStartPlayback.mutate(),
|
||||
},
|
||||
)
|
||||
break
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import React from 'react'
|
||||
import { getToken, getTokens, Theme, useTheme, XStack, YStack } from 'tamagui'
|
||||
import { Text } from '../helpers/text'
|
||||
@@ -19,7 +19,6 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchMediaInfo } from '../../../api/queries/functions/media'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
import { State } from 'react-native-track-player'
|
||||
|
||||
interface TrackProps {
|
||||
track: BaseItemDto
|
||||
|
||||
@@ -9,7 +9,7 @@ import { trigger } from 'react-native-haptic-feedback'
|
||||
import { H2 } from '../../../components/Global/helpers/text'
|
||||
import Icon from '../../../components/Global/helpers/icon'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
|
||||
export default function FrequentlyPlayedTracks({
|
||||
navigation,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { View, XStack } from 'tamagui'
|
||||
import { useHomeContext } from '../provider'
|
||||
import { H2 } from '../../Global/helpers/text'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import { StackParamList } from '../../../components/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
|
||||
@@ -173,7 +173,8 @@ export default function TrackOptions({
|
||||
name={isDownloaded ? 'delete' : 'download'}
|
||||
title={isDownloaded ? 'Remove Download' : 'Download'}
|
||||
onPress={() => {
|
||||
(isDownloaded ? useRemoveDownload : useDownload).mutate(track)
|
||||
if (isDownloaded) useRemoveDownload.mutate(track)
|
||||
else useDownload.mutate(track)
|
||||
}}
|
||||
size={width / 6}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { State } from 'react-native-track-player'
|
||||
import { Colors } from 'react-native/Libraries/NewAppScreen'
|
||||
import { Circle, Spinner, View } from 'tamagui'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import IconButton from '../../../components/Global/helpers/icon-button'
|
||||
|
||||
export default function PlayPauseButton({
|
||||
|
||||
@@ -2,24 +2,23 @@ import React from 'react'
|
||||
import { XStack, getToken } from 'tamagui'
|
||||
import PlayPauseButton from './buttons'
|
||||
import Icon from '../../../components/Global/helpers/icon'
|
||||
import { getProgress, seekBy, skipToNext } from 'react-native-track-player/lib/src/trackPlayer'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
|
||||
export default function Controls(): React.JSX.Element {
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
const { useSeekTo } = usePlayerContext()
|
||||
const { useSeekBy } = usePlayerContext()
|
||||
|
||||
const { usePrevious } = useQueueContext()
|
||||
const { usePrevious, useSkip } = useQueueContext()
|
||||
|
||||
return (
|
||||
<XStack alignItems='center' justifyContent='space-evenly' marginVertical={'$2'}>
|
||||
<Icon
|
||||
color={getToken('$color.amethyst')}
|
||||
name='rewind-15'
|
||||
onPress={() => seekBy(-15)}
|
||||
onPress={() => useSeekBy.mutate(-15)}
|
||||
/>
|
||||
|
||||
<Icon
|
||||
@@ -35,14 +34,14 @@ export default function Controls(): React.JSX.Element {
|
||||
<Icon
|
||||
color={getToken('$color.amethyst')}
|
||||
name='skip-next'
|
||||
onPress={() => skipToNext()}
|
||||
onPress={() => useSkip.mutate(undefined)}
|
||||
large
|
||||
/>
|
||||
|
||||
<Icon
|
||||
color={getToken('$color.amethyst')}
|
||||
name='fast-forward-15'
|
||||
onPress={() => seekBy(15)}
|
||||
onPress={() => useSeekBy.mutate(15)}
|
||||
/>
|
||||
</XStack>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import { RunTimeSeconds } from '../../../components/Global/helpers/time-codes'
|
||||
import { UPDATE_INTERVAL } from '../../../player/config'
|
||||
import { ProgressMultiplier } from '../component.config'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { getToken, getTokens, useTheme, View, XStack, YStack } from 'tamagui'
|
||||
import { usePlayerContext } from '../../player/provider'
|
||||
import { usePlayerContext } from '../../player/player-provider'
|
||||
import { BottomTabNavigationEventMap } from '@react-navigation/bottom-tabs'
|
||||
import { NavigationHelpers, ParamListBase } from '@react-navigation/native'
|
||||
import Icon from '../Global/helpers/icon'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StackParamList } from '../../../components/types'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import React, { useMemo } from 'react'
|
||||
import { SafeAreaView, useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Icon from '../../../components/Global/helpers/icon'
|
||||
import Track from '../../../components/Global/components/track'
|
||||
import { StackParamList } from '../../../components/types'
|
||||
import { usePlayerContext } from '../../../player/provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import DraggableFlatList from 'react-native-draggable-flatlist'
|
||||
@@ -9,6 +9,7 @@ import { trigger } from 'react-native-haptic-feedback'
|
||||
import { Separator } from 'tamagui'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
export default function Queue({
|
||||
navigation,
|
||||
@@ -77,7 +78,9 @@ export default function Queue({
|
||||
index={getIndex() ?? 0}
|
||||
showArtwork
|
||||
onPress={() => {
|
||||
useSkip.mutate(getIndex() ?? 0)
|
||||
const index = getIndex()
|
||||
console.debug(`Skip triggered on index ${index}`)
|
||||
useSkip.mutate(index)
|
||||
}}
|
||||
onLongPress={() => {
|
||||
trigger('impactLight')
|
||||
|
||||
@@ -23,13 +23,13 @@ export default function TracksScreen({ route, navigation }: TracksProps): React.
|
||||
<Track
|
||||
navigation={navigation}
|
||||
showArtwork
|
||||
index={index}
|
||||
index={0}
|
||||
track={track}
|
||||
tracklist={
|
||||
route.params.tracks
|
||||
? route.params.tracks.slice(index, index + 50)
|
||||
: favoriteTracks
|
||||
? favoriteTracks
|
||||
? favoriteTracks.slice(index, index + 50)
|
||||
: []
|
||||
}
|
||||
queue={route.params.queue}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import Navigation from './navigation'
|
||||
import Login from './Login/component'
|
||||
import { JellyfinAuthenticationProvider } from './Login/provider'
|
||||
import { PlayerProvider } from '../player/provider'
|
||||
import { PlayerProvider } from '../player/player-provider'
|
||||
import { useColorScheme } from 'react-native'
|
||||
import { PortalProvider } from '@tamagui/portal'
|
||||
import { JellifyProvider, useJellifyContext } from './provider'
|
||||
|
||||
@@ -7,7 +7,7 @@ import Settings from './Settings/stack'
|
||||
import { Discover } from './Discover/stack'
|
||||
import { Miniplayer } from './Player/mini-player'
|
||||
import { getToken, getTokens, Separator } from 'tamagui'
|
||||
import { usePlayerContext } from '../player/provider'
|
||||
import { usePlayerContext } from '../player/player-provider'
|
||||
import SearchStack from './Search/stack'
|
||||
import LibraryStack from './Library/stack'
|
||||
import { useColorScheme, View } from 'react-native'
|
||||
|
||||
@@ -519,10 +519,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-resources.sh\"\n";
|
||||
@@ -536,10 +540,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks.sh\"\n";
|
||||
@@ -839,10 +847,7 @@
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
@@ -928,10 +933,7 @@
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
||||
5
jest/queue-provider.test.js
Normal file
5
jest/queue-provider.test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { QueueContext, QueueProvider } from '../player/queue-provider'
|
||||
|
||||
describe(QueueProvider.name, () => {
|
||||
test('Skips to the correct track index')
|
||||
})
|
||||
241
package.json
241
package.json
@@ -1,121 +1,122 @@
|
||||
{
|
||||
"name": "jellify",
|
||||
"version": "0.11.7",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init:ios": "yarn install && yarn pod:install",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
|
||||
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
|
||||
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
|
||||
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
|
||||
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
|
||||
"prepare": "husky",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/cli": "^15.1.3",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/material-top-tabs": "^7.2.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@tamagui/config": "^1.126.1",
|
||||
"@tamagui/toast": "^1.126.1",
|
||||
"@tanstack/query-sync-storage-persister": "^5.74.6",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-persist-client": "^5.74.6",
|
||||
"axios": "^1.8.4",
|
||||
"bundle": "^2.1.0",
|
||||
"burnt": "^0.13.0",
|
||||
"expo": "^52.0.46",
|
||||
"expo-image": "^2.0.7",
|
||||
"gem": "^2.4.3",
|
||||
"invert-color": "^2.0.0",
|
||||
"jest-expo": "^52.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.77.0",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-blurhash": "^2.1.1",
|
||||
"react-native-boost": "^0.5.6",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.2",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.25.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "^2.12.2",
|
||||
"react-native-pager-view": "^6.7.1",
|
||||
"react-native-reanimated": "^3.17.5",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "^4.10.0",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.14.0",
|
||||
"react-native-track-player": "^4.1.1",
|
||||
"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.126.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native-community/cli-platform-android": "15.1.3",
|
||||
"@react-native-community/cli-platform-ios": "15.1.3",
|
||||
"@react-native/babel-preset": "0.77.0",
|
||||
"@react-native/eslint-config": "0.77.0",
|
||||
"@react-native/metro-config": "0.77.0",
|
||||
"@react-native/typescript-config": "0.77.0",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.6.3",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react-native-cli-bump-version": "^1.5.1",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "5.7.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
"name": "jellify",
|
||||
"version": "0.11.7",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init:ios": "yarn install && yarn pod:install",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
|
||||
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
|
||||
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
|
||||
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
|
||||
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
|
||||
"prepare": "husky",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/cli": "^15.1.3",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/material-top-tabs": "^7.2.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@tamagui/config": "^1.126.1",
|
||||
"@tamagui/toast": "^1.126.1",
|
||||
"@tanstack/query-sync-storage-persister": "^5.74.6",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-persist-client": "^5.74.6",
|
||||
"axios": "^1.8.4",
|
||||
"bundle": "^2.1.0",
|
||||
"burnt": "^0.13.0",
|
||||
"expo": "^52.0.46",
|
||||
"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",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.77.0",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-blurhash": "^2.1.1",
|
||||
"react-native-boost": "^0.5.6",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.2",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.25.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "^2.12.2",
|
||||
"react-native-pager-view": "^6.7.1",
|
||||
"react-native-reanimated": "^3.17.5",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "^4.10.0",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.14.0",
|
||||
"react-native-track-player": "^4.1.1",
|
||||
"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.126.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native-community/cli-platform-android": "15.1.3",
|
||||
"@react-native-community/cli-platform-ios": "15.1.3",
|
||||
"@react-native/babel-preset": "0.77.0",
|
||||
"@react-native/eslint-config": "0.77.0",
|
||||
"@react-native/metro-config": "0.77.0",
|
||||
"@react-native/typescript-config": "0.77.0",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.6.3",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react-native-cli-bump-version": "^1.5.1",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "5.7.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@ import TrackPlayer, {
|
||||
usePlaybackState,
|
||||
useTrackPlayerEvents,
|
||||
} from 'react-native-track-player'
|
||||
import { isEqual, isUndefined } from 'lodash'
|
||||
import { handlePlaybackProgressUpdated, handlePlaybackState } from './handlers'
|
||||
import { useUpdateOptions } from '../player/hooks'
|
||||
import { useMutation, UseMutationResult } from '@tanstack/react-query'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import { pause, seekTo } from 'react-native-track-player/lib/src/trackPlayer'
|
||||
import { pause, play, seekBy, seekTo } from 'react-native-track-player/lib/src/trackPlayer'
|
||||
import { convertRunTimeTicksToSeconds } from '../helpers/runtimeticks'
|
||||
import Client from '../api/client'
|
||||
|
||||
@@ -22,13 +20,11 @@ import { useNetworkContext } from '../components/Network/provider'
|
||||
import { useQueueContext } from './queue-provider'
|
||||
|
||||
interface PlayerContext {
|
||||
initialized: boolean
|
||||
nowPlayingIsFavorite: boolean
|
||||
setNowPlayingIsFavorite: React.Dispatch<SetStateAction<boolean>>
|
||||
nowPlaying: JellifyTrack | undefined
|
||||
useStartPlayback: UseMutationResult<void, Error, void, unknown>
|
||||
useTogglePlayback: UseMutationResult<void, Error, void, unknown>
|
||||
useSeekTo: UseMutationResult<void, Error, number, unknown>
|
||||
useSeekBy: UseMutationResult<void, Error, number, unknown>
|
||||
}
|
||||
|
||||
const PlayerContextInitializer = () => {
|
||||
@@ -37,9 +33,6 @@ const PlayerContextInitializer = () => {
|
||||
const playStateApi = getPlaystateApi(Client.api!)
|
||||
|
||||
//#region State
|
||||
const [initialized, setInitialized] = useState<boolean>(false)
|
||||
|
||||
const [nowPlayingIsFavorite, setNowPlayingIsFavorite] = useState<boolean>(false)
|
||||
const [nowPlaying, setNowPlaying] = useState<JellifyTrack | undefined>(
|
||||
nowPlayingJson ? JSON.parse(nowPlayingJson) : undefined,
|
||||
)
|
||||
@@ -48,13 +41,6 @@ const PlayerContextInitializer = () => {
|
||||
|
||||
//#endregion State
|
||||
|
||||
//#region Functions
|
||||
const play = async () => {
|
||||
await TrackPlayer.play()
|
||||
}
|
||||
|
||||
//#endregion Functions
|
||||
|
||||
//#region Hooks
|
||||
const useStartPlayback = useMutation({
|
||||
mutationFn: play,
|
||||
@@ -80,6 +66,14 @@ const PlayerContextInitializer = () => {
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const useSeekBy = useMutation({
|
||||
mutationFn: async (seekSeconds: number) => {
|
||||
trigger('clockTick')
|
||||
|
||||
await seekBy(seekSeconds)
|
||||
},
|
||||
})
|
||||
//#endregion
|
||||
|
||||
//#region RNTP Setup
|
||||
@@ -88,22 +82,14 @@ const PlayerContextInitializer = () => {
|
||||
const { useDownload, downloadedTracks, networkStatus } = useNetworkContext()
|
||||
|
||||
useTrackPlayerEvents(
|
||||
[
|
||||
Event.RemoteLike,
|
||||
Event.RemoteDislike,
|
||||
Event.PlaybackProgressUpdated,
|
||||
Event.PlaybackState,
|
||||
Event.PlaybackActiveTrackChanged,
|
||||
],
|
||||
[Event.RemoteLike, Event.RemoteDislike, Event.PlaybackProgressUpdated, Event.PlaybackState],
|
||||
async (event) => {
|
||||
switch (event.type) {
|
||||
case Event.RemoteLike: {
|
||||
setNowPlayingIsFavorite(true)
|
||||
break
|
||||
}
|
||||
|
||||
case Event.RemoteDislike: {
|
||||
setNowPlayingIsFavorite(false)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -135,36 +121,6 @@ const PlayerContextInitializer = () => {
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case Event.PlaybackActiveTrackChanged: {
|
||||
if (initialized) {
|
||||
const activeTrack = (await TrackPlayer.getActiveTrack()) as
|
||||
| JellifyTrack
|
||||
| undefined
|
||||
if (activeTrack && !isEqual(activeTrack, nowPlaying)) {
|
||||
setNowPlaying(activeTrack)
|
||||
|
||||
// Set player favorite state to user data IsFavorite
|
||||
// This is super nullish so we need to do a lot of
|
||||
// checks on the fields
|
||||
// TODO: Turn this check into a helper function
|
||||
setNowPlayingIsFavorite(
|
||||
isUndefined(activeTrack)
|
||||
? false
|
||||
: isUndefined(activeTrack!.item.UserData)
|
||||
? false
|
||||
: activeTrack.item.UserData.IsFavorite ?? false,
|
||||
)
|
||||
|
||||
await useUpdateOptions(nowPlayingIsFavorite)
|
||||
} else if (!activeTrack) {
|
||||
setNowPlaying(undefined)
|
||||
setNowPlayingIsFavorite(false)
|
||||
} else {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -174,34 +130,24 @@ const PlayerContextInitializer = () => {
|
||||
//#region useEffects
|
||||
|
||||
useEffect(() => {
|
||||
if (initialized && nowPlaying)
|
||||
storage.set(MMKVStorageKeys.NowPlaying, JSON.stringify(nowPlaying))
|
||||
if (nowPlaying) storage.set(MMKVStorageKeys.NowPlaying, JSON.stringify(nowPlaying))
|
||||
}, [nowPlaying])
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized && playQueue.length > 0 && nowPlaying) {
|
||||
TrackPlayer.skip(playQueue.findIndex((track) => track.item.Id! === nowPlaying.item.Id!))
|
||||
}
|
||||
|
||||
setInitialized(true)
|
||||
}, [playQueue, nowPlaying])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentIndex > -1 && playQueue.length > currentIndex)
|
||||
if (currentIndex > -1 && playQueue.length > currentIndex) {
|
||||
console.debug(`Setting now playing to queue index ${currentIndex}`)
|
||||
setNowPlaying(playQueue[currentIndex])
|
||||
setNowPlaying(playQueue[currentIndex])
|
||||
}
|
||||
}, [currentIndex])
|
||||
//#endregion useEffects
|
||||
|
||||
//#region return
|
||||
return {
|
||||
initialized,
|
||||
nowPlayingIsFavorite,
|
||||
setNowPlayingIsFavorite,
|
||||
nowPlaying,
|
||||
useStartPlayback,
|
||||
useTogglePlayback,
|
||||
useSeekTo,
|
||||
useSeekBy,
|
||||
playbackState,
|
||||
}
|
||||
//#endregion return
|
||||
@@ -209,9 +155,6 @@ const PlayerContextInitializer = () => {
|
||||
|
||||
//#region Create PlayerContext
|
||||
export const PlayerContext = createContext<PlayerContext>({
|
||||
initialized: false,
|
||||
nowPlayingIsFavorite: false,
|
||||
setNowPlayingIsFavorite: () => {},
|
||||
nowPlaying: undefined,
|
||||
useStartPlayback: {
|
||||
mutate: () => {},
|
||||
@@ -267,6 +210,24 @@ export const PlayerContext = createContext<PlayerContext>({
|
||||
failureReason: null,
|
||||
submittedAt: 0,
|
||||
},
|
||||
useSeekBy: {
|
||||
mutate: () => {},
|
||||
mutateAsync: async () => {},
|
||||
data: undefined,
|
||||
error: null,
|
||||
variables: undefined,
|
||||
isError: false,
|
||||
isIdle: true,
|
||||
isPaused: false,
|
||||
isPending: false,
|
||||
isSuccess: false,
|
||||
status: 'idle',
|
||||
reset: () => {},
|
||||
context: {},
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
submittedAt: 0,
|
||||
},
|
||||
})
|
||||
//#endregion Create PlayerContext
|
||||
|
||||
@@ -11,14 +11,15 @@ import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { mapDtoToTrack } from '../helpers/mappings'
|
||||
import { useNetworkContext } from '../components/Network/provider'
|
||||
import { QueuingType } from '../enums/queuing-type'
|
||||
import TrackPlayer from 'react-native-track-player'
|
||||
import TrackPlayer, { Event, useTrackPlayerEvents } from 'react-native-track-player'
|
||||
import { findPlayQueueIndexStart } from './helpers'
|
||||
import { getQueue, seekTo } from 'react-native-track-player/lib/src/trackPlayer'
|
||||
import { getQueue, play, seekTo } from 'react-native-track-player/lib/src/trackPlayer'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import * as Burnt from 'burnt'
|
||||
import { markItemPlayed } from '../api/mutations/functions/item'
|
||||
import { filterTracksOnNetworkStatus } from './helpers/queue'
|
||||
import { SKIP_TO_PREVIOUS_THRESHOLD } from './config'
|
||||
import { isNull, isUndefined } from 'lodash'
|
||||
|
||||
interface QueueContext {
|
||||
queueRef: Queue
|
||||
@@ -50,6 +51,13 @@ const QueueContextInitailizer = () => {
|
||||
|
||||
const { downloadedTracks, networkStatus } = useNetworkContext()
|
||||
|
||||
useTrackPlayerEvents([Event.PlaybackActiveTrackChanged], ({ lastIndex, index }) => {
|
||||
const lastTrackFinished =
|
||||
!isUndefined(index) && !isUndefined(lastIndex) && index - lastIndex === 1
|
||||
|
||||
if (lastTrackFinished) setCurrentIndex(currentIndex + 1)
|
||||
})
|
||||
|
||||
//#region Functions
|
||||
const fetchQueueSectionData: () => Section[] = () => {
|
||||
return Object.keys(QueuingType).map((type) => {
|
||||
@@ -97,6 +105,13 @@ const QueueContextInitailizer = () => {
|
||||
) => {
|
||||
console.debug(`Queuing ${audioItems.length} items`)
|
||||
|
||||
/**
|
||||
* If the start index matches the current index,
|
||||
* then our useEffect won't fire - this ensures
|
||||
* it does
|
||||
*/
|
||||
setCurrentIndex(-1)
|
||||
|
||||
const availableAudioItems = filterTracksOnNetworkStatus(
|
||||
networkStatus,
|
||||
audioItems,
|
||||
@@ -118,6 +133,8 @@ const QueueContextInitailizer = () => {
|
||||
setCurrentIndex(startIndex)
|
||||
|
||||
console.debug(`Queued ${queue.length} tracks, starting at ${startIndex}`)
|
||||
|
||||
await play()
|
||||
}
|
||||
|
||||
const playNextInQueue = async (item: BaseItemDto) => {
|
||||
@@ -146,6 +163,31 @@ const QueueContextInitailizer = () => {
|
||||
|
||||
console.debug(`Queue has ${playQueue.length} tracks`)
|
||||
}
|
||||
|
||||
const previous = async () => {
|
||||
trigger('impactMedium')
|
||||
|
||||
const { position } = await TrackPlayer.getProgress()
|
||||
|
||||
console.debug(`Skip to previous triggered. Index is ${currentIndex}`)
|
||||
|
||||
if (currentIndex > 0 && position < SKIP_TO_PREVIOUS_THRESHOLD) {
|
||||
setCurrentIndex(currentIndex - 1)
|
||||
} else await seekTo(0)
|
||||
}
|
||||
|
||||
const skip = async (index?: number | undefined) => {
|
||||
trigger('impactMedium')
|
||||
|
||||
console.debug(
|
||||
`Skip to next triggered. Index is ${`using ${
|
||||
!isUndefined(index) ? index : currentIndex
|
||||
} as index ${!isUndefined(index) ? 'since it was provided' : ''}`}`,
|
||||
)
|
||||
|
||||
if (isUndefined(index)) setCurrentIndex(currentIndex + 1)
|
||||
else if (playQueue.length > index) setCurrentIndex(index)
|
||||
}
|
||||
//#endregion Functions
|
||||
|
||||
//#region Hooks
|
||||
@@ -215,32 +257,11 @@ const QueueContextInitailizer = () => {
|
||||
})
|
||||
|
||||
const useSkip = useMutation({
|
||||
mutationFn: async (index?: number | undefined) => {
|
||||
trigger('impactMedium')
|
||||
|
||||
console.debug(
|
||||
`Skip to next triggered. Index is ${`using ${
|
||||
index ? index : currentIndex
|
||||
} as index ${index ? 'since it was provided' : ''}`}`,
|
||||
)
|
||||
|
||||
if (index && index < playQueue.length - 1) setCurrentIndex(index)
|
||||
else if (playQueue.length - 1 > currentIndex) setCurrentIndex(currentIndex + 1)
|
||||
},
|
||||
mutationFn: skip,
|
||||
})
|
||||
|
||||
const usePrevious = useMutation({
|
||||
mutationFn: async () => {
|
||||
trigger('impactMedium')
|
||||
|
||||
const { position } = await TrackPlayer.getProgress()
|
||||
|
||||
console.debug(`Skip to previous triggered. Index is ${currentIndex}`)
|
||||
|
||||
if (currentIndex > 0 && position < SKIP_TO_PREVIOUS_THRESHOLD) {
|
||||
setCurrentIndex(currentIndex - 1)
|
||||
} else await seekTo(0)
|
||||
},
|
||||
mutationFn: previous,
|
||||
})
|
||||
|
||||
const useUpdateRntpQueue = useMutation({
|
||||
|
||||
48
yarn.lock
48
yarn.lock
@@ -6539,6 +6539,17 @@ glob@^10.2.2, glob@^10.3.10, glob@^10.4.2:
|
||||
package-json-from-dist "^1.0.0"
|
||||
path-scurry "^1.11.1"
|
||||
|
||||
glob@^6.0.1:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
|
||||
integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==
|
||||
dependencies:
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "2 || 3"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
|
||||
@@ -6835,6 +6846,11 @@ ini@~1.3.0:
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
insync@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/insync/-/insync-2.1.1.tgz#22e26c61121303c06f51d35a3ccf6d8fc1e914c4"
|
||||
integrity sha512-UzUhOZFpCMM22Xlig9iUPqalf8n7c4eYScamce1C+jN3ad8FtmVm42ryMwVq0hAxHbwUhWFhPvTFQQpFdDUKkw==
|
||||
|
||||
internal-ip@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz"
|
||||
@@ -8462,6 +8478,13 @@ mimic-function@^5.0.0:
|
||||
resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz"
|
||||
integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==
|
||||
|
||||
"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz"
|
||||
@@ -8469,13 +8492,6 @@ minimatch@^10.0.1:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^8.0.2:
|
||||
version "8.0.4"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz"
|
||||
@@ -8594,6 +8610,11 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
ncp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||
integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==
|
||||
|
||||
negotiator@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz"
|
||||
@@ -8663,6 +8684,17 @@ normalize-path@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
npm-bundle@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-bundle/-/npm-bundle-3.0.3.tgz#464e613f21f14e4918ed93e810b5a9cdeb35acc1"
|
||||
integrity sha512-fHF7FR32YNgjqi0MQMLnE78Ff9/wYd4/7/Cke3dLLi2QzETKotIiWGCxwDoXAZDWVoTuVRYQa2ZdiZPuBL7QnA==
|
||||
dependencies:
|
||||
glob "^6.0.1"
|
||||
insync "^2.1.1"
|
||||
mkdirp "^0.5.1"
|
||||
ncp "^2.0.0"
|
||||
rimraf "^2.4.4"
|
||||
|
||||
npm-package-arg@^11.0.0:
|
||||
version "11.0.3"
|
||||
resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz"
|
||||
@@ -9879,7 +9911,7 @@ rfdc@^1.4.1:
|
||||
resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz"
|
||||
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
|
||||
|
||||
rimraf@^2.6.3:
|
||||
rimraf@^2.4.4, rimraf@^2.6.3:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
|
||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||
|
||||
Reference in New Issue
Block a user