diff --git a/android/settings.gradle b/android/settings.gradle index 43810013..a86a0cda 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -3,5 +3,5 @@ apply from: file("../node_modules/@react-native-community/cli-platform-android/n include ':app' includeBuild('../node_modules/@react-native/gradle-plugin') -apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") -useExpoModules() \ No newline at end of file + apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") +useExpoModules() diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 283794c1..d22df8fc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -118,7 +118,7 @@ PODS: - libwebp/sharpyuv (1.3.2) - libwebp/webp (1.3.2): - libwebp/sharpyuv - - lottie-ios (4.3.0) + - lottie-ios (4.3.3) - lottie-react-native (6.3.1): - lottie-ios (~> 4.3.0) - React-Core @@ -424,7 +424,7 @@ PODS: - React-jsinspector (0.72.4) - React-logger (0.72.4): - glog - - react-native-app-auth (7.0.0-rc2): + - react-native-app-auth (7.0.0): - AppAuth (~> 1.6) - React-Core - react-native-background-timer (2.4.1): @@ -449,7 +449,7 @@ PODS: - SVGAPlayer - react-native-track-player (4.0.0-rc08): - React-Core - - SwiftAudioEx (= 1.0.0-rc.7) + - SwiftAudioEx (= 1.0.0-rc.8) - react-native-video (5.2.1): - React-Core - react-native-video/Video (= 5.2.1) @@ -575,9 +575,9 @@ PODS: - SDWebImageWebPCoder (~> 0.8.4) - RNFlashList (1.6.1): - React-Core - - RNGestureHandler (2.12.1): + - RNGestureHandler (2.13.1): - React-Core - - RNReanimated (3.5.2): + - RNReanimated (3.5.4): - DoubleConversion - FBLazyVector - glog @@ -613,7 +613,7 @@ PODS: - React - RNSnackbar (2.6.2): - React-Core - - RNSVG (13.13.0): + - RNSVG (13.14.0): - React-Core - RNVectorIcons (9.2.0): - React-Core @@ -633,7 +633,7 @@ PODS: - SVGAPlayer/ProtoFiles - SVGAPlayer/ProtoFiles (2.5.7): - Protobuf (~> 3.4) - - SwiftAudioEx (1.0.0-rc.7) + - SwiftAudioEx (1.0.0-rc.8) - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) @@ -945,7 +945,7 @@ SPEC CHECKSUMS: hermes-engine: 81191603c4eaa01f5e4ae5737a9efcf64756c7b2 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - lottie-ios: c69214a487e1a1465a231c1918c6ce174e2d45bc + lottie-ios: 25e7b2675dad5c3ddad369ac9baab03560c5bfdd lottie-react-native: c9f1db4f4124dcce9f8159e65d8dc6e8bcb11fb4 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Protobuf: d7f7c8329edf5eb8af65547a8ba3e9c1cee927d5 @@ -964,7 +964,7 @@ SPEC CHECKSUMS: React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594 React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 - react-native-app-auth: 05c3b875b27bb65fe321bd5dc38cc1c04c5158be + react-native-app-auth: 1d12b6874a24152715a381d8e9149398ce7c2c95 react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe react-native-blob-jsi-helper: 13c10135af4614dbc0712afba5960784cd44f043 react-native-blob-util: c5430d091e011b7fc57f888c356da2a2ff8dc8ad @@ -974,7 +974,7 @@ SPEC CHECKSUMS: react-native-pager-view: d211379f61895b6349bd7e571b44a26d005c2975 react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284 react-native-svga-player: f1c7faf0864b22437b9b7cc7d7aa202d56f3906c - react-native-track-player: 2ad3514ee10048615c54f35ad9bb5d847d140807 + react-native-track-player: cb09f9c28361ff11b02732a89f2882102a22977a react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253 react-native-webview: 669ae162965f629a8d6a4bdd3b99a304d36ef1f2 React-NativeModulesApple: edb5ace14f73f4969df6e7b1f3e41bef0012740f @@ -997,19 +997,19 @@ SPEC CHECKSUMS: RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 RNFlashList: 236646d48f224a034f35baa0242e1b77db063b1e - RNGestureHandler: c0d04458598fcb26052494ae23dda8f8f5162b13 - RNReanimated: 2491645f0883526f4470d8b5c761308709ba31f8 + RNGestureHandler: 38aa38413896620338948fbb5c90579a7b1c3fde + RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87 RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7 RNShareMenu: cb9dac548c8bf147d06f0bf07296ad51ea9f5fc3 RNSnackbar: 3727b42bf6c4314a53c18185b5203e915a4ab020 - RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82 + RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef SVGAPlayer: 318b85a78b61292d6ae9dfcd651f3f0d1cdadd86 - SwiftAudioEx: a6ef015f89f49d2ec1c352dc440805375029d5a7 + SwiftAudioEx: ebe6244f6f4d75a4ac5d65b36d3b6a911a2689b8 Yoga: 3efc43e0d48686ce2e8c60f99d4e6bd349aff981 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/ios/example/Info.plist b/ios/example/Info.plist index e6c123bc..bd456533 100644 --- a/ios/example/Info.plist +++ b/ios/example/Info.plist @@ -65,6 +65,8 @@ armv7 + UIStatusBarStyle + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/package.json b/package.json index 3c229d1d..d47faa18 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "fix": "gts fix", "prepare": "yarn run compile", "pretest": "yarn run compile", - "prettify": "prettier --write ./src" + "prettify": "prettier --write ./src", + "ios:build": "cd ios; rm Podfile.lock; pod install; cd .." }, "dependencies": { "@react-native-async-storage/async-storage": "^1.18.1", diff --git a/src/components/dialogs/CopiedPlaylistDialog.tsx b/src/components/dialogs/CopiedPlaylistDialog.tsx index 32ac2ba8..ec160cc3 100644 --- a/src/components/dialogs/CopiedPlaylistDialog.tsx +++ b/src/components/dialogs/CopiedPlaylistDialog.tsx @@ -91,7 +91,6 @@ export default ({ const styles = StyleSheet.create({ dialog: { - maxHeight: '60%', minHeight: '50%', }, dialogTitle: { diff --git a/src/components/dialogs/PlaylistSettingsDialog.tsx b/src/components/dialogs/PlaylistSettingsDialog.tsx index 2303396e..2eaa26ac 100644 --- a/src/components/dialogs/PlaylistSettingsDialog.tsx +++ b/src/components/dialogs/PlaylistSettingsDialog.tsx @@ -32,6 +32,7 @@ export default ({ const updatePlaylist = useNoxSetting(state => state.updatePlaylist); const [useBiliShazam, setUseBiliShazam] = useState(false); const [useBiliSync, setUseBiliSync] = useState(false); + const [useNewSongOverwrite, setUseNewSongOverwrite] = useState(false); // eslint-disable-next-line @typescript-eslint/no-explicit-any const nameRef = useRef(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -45,6 +46,8 @@ export default ({ const toggleBiliShazam = () => setUseBiliShazam(val => !val); const toggleBiliSync = () => setUseBiliSync(val => !val); + const toggleNewSongOverwrite = () => + setUseNewSongOverwrite(val => !val); const handleClose = () => { onClose(); @@ -59,6 +62,8 @@ export default ({ new Set(blacklistRef.current?.name.split(';')) ), useBiliShazam: useBiliShazam, + biliSync: useBiliSync, + newSongOverwrite: useNewSongOverwrite, }; updatePlaylist(newPlaylist, [], []); onSubmit(newPlaylist); @@ -112,6 +117,16 @@ export default ({ {t('PlaylistSettingsDialog.useBiliSyncLabel')} + + + + {t('PlaylistSettingsDialog.useNewSongOverwriteLabel')} + + diff --git a/src/components/player/TrackInfo/SongMenu.tsx b/src/components/player/TrackInfo/SongMenu.tsx index e971b467..9f1fd6de 100644 --- a/src/components/player/TrackInfo/SongMenu.tsx +++ b/src/components/player/TrackInfo/SongMenu.tsx @@ -12,6 +12,7 @@ import useSongOperations from '@hooks/useSongOperations'; import { addR128Gain, getR128Gain } from '@stores/appStore'; import ABSliderMenu from './ABSliderMenu'; import { songlistToTracklist } from '@utils/RNTPUtils'; +import useActiveTrack from '@hooks/useActiveTrack'; enum ICONS { SEND_TO = 'playlist-plus', @@ -54,7 +55,9 @@ export default ({ const setCurrentPlayingList = useNoxSetting( state => state.setCurrentPlayingList ); - const { updateSongIndex } = useUpdatePlaylist(); + const updateTrack = useNoxSetting(state => state.updateTrack); + + const { updateSongIndex, updateSongMetadata } = useUpdatePlaylist(); const { startRadio, radioAvailable } = useSongOperations(); const closeMenu = React.useCallback(() => setSongMenuVisible(false), []); @@ -71,13 +74,30 @@ export default ({ }; }; - const renameSong = (rename: string) => { + const renameSong = async (rename: string) => { const currentPlaylist2 = playlists[currentPlaylist.id]; updateSongIndex( songMenuSongIndexes[0], { name: rename, parsedName: rename }, currentPlaylist2 ); + const index = await TrackPlayer.getActiveTrackIndex(); + index !== undefined && (await TrackPlayer.updateMetadataForTrack(index, { + title: rename, + })); + updateTrack(); + }; + + const reloadSong = async () => { + const currentPlaylist2 = playlists[currentPlaylist.id]; + const metadata = await updateSongMetadata(songMenuSongIndexes[0], currentPlaylist2); + const index = await TrackPlayer.getActiveTrackIndex(); + index !== undefined && (await TrackPlayer.updateMetadataForTrack(index, { + title: metadata.name, + artwork: metadata.cover, + })); + updateTrack(); + return metadata; }; const removeSongs = async (banBVID = false) => { @@ -85,11 +105,11 @@ export default ({ const songs = [song]; const newPlaylist = banBVID ? { - ...currentPlaylist2, - blacklistedUrl: currentPlaylist2.blacklistedUrl.concat( - songs.map(song => song.bvid) - ), - } + ...currentPlaylist2, + blacklistedUrl: currentPlaylist2.blacklistedUrl.concat( + songs.map(song => song.bvid) + ), + } : currentPlaylist2; updatePlaylist(newPlaylist, [], songs); setCurrentPlayingList(newPlaylist); @@ -124,6 +144,14 @@ export default ({ renameSong(rename); }} /> + { + closeMenu(); + reloadSong(); + }} + title={t('SongOperations.reloadSong')} + /> state.playerStyle); - // TODO: component + const setUpdateTrack = useNoxSetting(state => state.setUpdateTrack); + + useEffect(() => setUpdateTrack(updateTrack), []); return ( diff --git a/src/hooks/useActiveTrack.ts b/src/hooks/useActiveTrack.ts new file mode 100644 index 00000000..cbe5e162 --- /dev/null +++ b/src/hooks/useActiveTrack.ts @@ -0,0 +1,22 @@ +import React from 'react'; +import TrackPlayer, { Track, useActiveTrack } from 'react-native-track-player'; + +const useTrack = () => { + const activeTrack = useActiveTrack(); + const [track, setTrack] = React.useState(activeTrack); + + const updateTrack = async () => { + const index = await TrackPlayer.getActiveTrackIndex(); + if (index === undefined) return; + const queue = await TrackPlayer.getQueue(); + setTrack({ ...queue[index] }); + }; + + React.useEffect(() => setTrack(activeTrack), [activeTrack]); + + React.useEffect(() => console.log(track), [track]); + + return { track, updateTrack }; +}; + +export default useTrack; diff --git a/src/hooks/useSetting.ts b/src/hooks/useSetting.ts index b4936d1b..dde73531 100644 --- a/src/hooks/useSetting.ts +++ b/src/hooks/useSetting.ts @@ -40,6 +40,8 @@ interface initializedResults { } interface NoxSetting { + updateTrack: () => void; + setUpdateTrack: (val: () => void) => void; appRefresh: boolean; setAppRefresh: () => void; playlistSearchAutoFocus: boolean; @@ -133,6 +135,9 @@ interface NoxSetting { * as well as saving and loading states to/from asyncStorage. */ export const useNoxSetting = create((set, get) => ({ + updateTrack: () => undefined, + setUpdateTrack: updateTrack => set({ updateTrack }), + appRefresh: false, setAppRefresh: () => set({ appRefresh: true }), playlistSearchAutoFocus: true, diff --git a/src/hooks/useUpdatePlaylist.ts b/src/hooks/useUpdatePlaylist.ts index 1ee660e2..675b4b46 100644 --- a/src/hooks/useUpdatePlaylist.ts +++ b/src/hooks/useUpdatePlaylist.ts @@ -1,5 +1,6 @@ import { useNoxSetting } from '@hooks/useSetting'; import { logger } from '@utils/Logger'; +import { refreshMetadata } from '@utils/mediafetch/resolveURL'; const useUpdatePlaylist = () => { const currentPlaylist = useNoxSetting(state => state.currentPlayingList); @@ -38,7 +39,17 @@ const useUpdatePlaylist = () => { updatePlaylist(newPlaylist, [], []); }; - return { updateSong, updateSongIndex }; + const updateSongMetadata = async ( + index: number, + playlist: NoxMedia.Playlist + ) => { + const song = playlist.songList[index]; + const metadata = await refreshMetadata(song); + updateSongIndex(index, metadata, playlist); + return metadata; + }; + + return { updateSong, updateSongIndex, updateSongMetadata }; }; export default useUpdatePlaylist; diff --git a/src/localization/en/translation.json b/src/localization/en/translation.json index 8fc65e30..ff33cecf 100644 --- a/src/localization/en/translation.json +++ b/src/localization/en/translation.json @@ -23,7 +23,8 @@ "subscribeUrlLabel": "Subscribe URL", "blacklistedUrlLabel": "Banned BVIDs", "useBiliShazamLabel": "Use BiliShazam", - "useBiliSyncLabel": "BiliSync as favorite" + "useBiliSyncLabel": "BiliSync as favorite", + "useNewSongOverwriteLabel": "New songs overwrite" }, "RenameSongDialog": { "title": "Remame {{song.name}} to...", @@ -80,6 +81,7 @@ "abrepeatTitle": "{{song.parsedName}} A-B repeat", "songRemoveTitle": "Remove", "songRemoveNBanTitle": "Remove and BAN", + "reloadSong": "Reload", "selectedSongs": "Selected songs" }, "GeneralSettings": { diff --git a/src/localization/zhcn/translation.json b/src/localization/zhcn/translation.json index 65c3a55b..fbba309e 100644 --- a/src/localization/zhcn/translation.json +++ b/src/localization/zhcn/translation.json @@ -23,7 +23,8 @@ "subscribeUrlLabel": "b站订阅URL", "blacklistedUrlLabel": "黑名单BV号", "useBiliShazamLabel": "使用b站识歌(王胡桃专用)", - "useBiliSyncLabel": "b站收藏夹同步" + "useBiliSyncLabel": "b站收藏夹同步", + "useNewSongOverwriteLabel": "新歌曲同步时覆盖" }, "RenameSongDialog": { "title": "将 {{song.name}} 重命名为...", @@ -80,6 +81,7 @@ "abrepeatTitle": "{{song.parsedName}} A-B重复", "songRemoveTitle": "删除歌曲", "songRemoveNBanTitle": "删除并拉黑", + "reloadSong": "Reload", "selectedSongs": "选择的歌" }, "GeneralSettings": { diff --git a/src/objects/Playlist.ts b/src/objects/Playlist.ts index 620cd7e6..bdf26e9d 100644 --- a/src/objects/Playlist.ts +++ b/src/objects/Playlist.ts @@ -19,6 +19,7 @@ export const dummyPlaylist = ( lastSubscribed: 0, type, biliSync: false, + newSongOverwrite: false, }; }; diff --git a/src/types/media.d.ts b/src/types/media.d.ts index b7ca0920..fe84f2c7 100644 --- a/src/types/media.d.ts +++ b/src/types/media.d.ts @@ -30,6 +30,7 @@ declare namespace NoxMedia { lastSubscribed: number; type: string; biliSync: boolean; + newSongOverwrite?: boolean; } export interface LyricDetail { diff --git a/src/utils/BiliSubscribe.ts b/src/utils/BiliSubscribe.ts index 694e0f25..b4d2177e 100644 --- a/src/utils/BiliSubscribe.ts +++ b/src/utils/BiliSubscribe.ts @@ -17,7 +17,7 @@ export const updateSubscribeFavList = async ({ subscribeUrls, updatePlaylist, progressEmitter = () => undefined, - overwriteOnRefresh = () => listObj.title.includes('live'), + overwriteOnRefresh = () => listObj.newSongOverwrite || listObj.title.includes('live'), }: Props) => { try { const newPlaylist = { ...listObj, lastSubscribed: new Date().getTime() }; diff --git a/src/utils/mediafetch/resolveURL.ts b/src/utils/mediafetch/resolveURL.ts index c7a40009..6956b3ce 100644 --- a/src/utils/mediafetch/resolveURL.ts +++ b/src/utils/mediafetch/resolveURL.ts @@ -66,3 +66,12 @@ export const fetchPlayUrlPromise = async ( regexMatching: song => song.id, }); }; + +export const refreshMetadata = async ( + v: NoxMedia.Song +): Promise> => { + const metadata = await fetchPlayUrlPromise(v); + return { + ...(metadata.cover && { cover: metadata.cover }), + }; +};