From 3b420ce3757fbd09224c0fc824260bcdac717930 Mon Sep 17 00:00:00 2001
From: Roitium <65794453+roitium@users.noreply.github.com>
Date: Sun, 2 Nov 2025 07:27:52 +0800
Subject: [PATCH 1/5] fix: subscribe boolean instead of currentTrack to reduce
rerender
---
src/app/(tabs)/settings.tsx | 6 +++---
src/app/download.tsx | 6 +++---
src/app/leaderboard.tsx | 2 +-
src/app/player.tsx | 2 +-
src/app/test.tsx | 5 ++---
src/components/NowPlayingBar.tsx | 2 +-
src/components/modals/PlayerQueueModal.tsx | 4 ++--
src/features/library/collection/CollectionList.tsx | 6 +++---
src/features/library/favorite/FavoriteFolderList.tsx | 6 +++---
src/features/library/local/LocalPlaylistList.tsx | 6 +++---
src/features/library/multipage/MultiPageVideosList.tsx | 6 +++---
src/features/player/components/PlayerFunctionalMenu.tsx | 2 +-
src/features/player/components/PlayerHeader.tsx | 2 +-
src/features/player/components/PlayerTrackInfo.tsx | 2 +-
src/features/playlist/local/components/LocalTrackList.tsx | 6 +++---
src/features/playlist/remote/components/RemoteTrackList.tsx | 6 +++---
src/hooks/{stores/playerHooks => player}/useCurrentQueue.ts | 0
src/hooks/{stores/playerHooks => player}/useCurrentTrack.ts | 0
src/lib/player/playerLogic.ts | 4 ----
19 files changed, 34 insertions(+), 39 deletions(-)
rename src/hooks/{stores/playerHooks => player}/useCurrentQueue.ts (100%)
rename src/hooks/{stores/playerHooks => player}/useCurrentTrack.ts (100%)
diff --git a/src/app/(tabs)/settings.tsx b/src/app/(tabs)/settings.tsx
index 923720c4..272c8901 100644
--- a/src/app/(tabs)/settings.tsx
+++ b/src/app/(tabs)/settings.tsx
@@ -1,7 +1,7 @@
import NowPlayingBar from '@/components/NowPlayingBar'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import useAppStore from '@/hooks/stores/useAppStore'
import { useModalStore } from '@/hooks/stores/useModalStore'
+import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { checkForAppUpdate } from '@/lib/services/updateService'
import { toastAndLogError } from '@/utils/log'
import toast from '@/utils/toast'
@@ -24,7 +24,7 @@ const updateTime = Updates.createdAt
export default function SettingsPage() {
const insets = useSafeAreaInsets()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const colors = useTheme().colors
return (
@@ -38,7 +38,7 @@ export default function SettingsPage() {
style={{
flex: 1,
paddingTop: insets.top + 8,
- paddingBottom: currentTrack ? 70 : insets.bottom,
+ paddingBottom: haveTrack ? 70 : insets.bottom,
}}
>
state.startDownload)
const clearAll = useDownloadManagerStore((state) => state.clearAll)
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const renderItem = useCallback(({ item }: { item: DownloadTask }) => {
return
@@ -50,7 +50,7 @@ export default function DownloadPage() {
renderItem={renderItem}
keyExtractor={keyExtractor}
contentContainerStyle={{
- paddingBottom: currentTrack ? 70 + insets.bottom : insets.bottom,
+ paddingBottom: haveTrack ? 70 + insets.bottom : insets.bottom,
}}
/>
diff --git a/src/app/leaderboard.tsx b/src/app/leaderboard.tsx
index e701f2db..9ed7b912 100644
--- a/src/app/leaderboard.tsx
+++ b/src/app/leaderboard.tsx
@@ -1,10 +1,10 @@
import NowPlayingBar from '@/components/NowPlayingBar'
import { LeaderBoardListItem } from '@/features/leaderboard/LeaderBoardItem'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import {
usePlayCountLeaderBoardPaginated,
useTotalPlaybackDuration,
} from '@/hooks/queries/db/track'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import type { Track } from '@/types/core/media'
import { FlashList } from '@shopify/flash-list'
import { useRouter } from 'expo-router'
diff --git a/src/app/player.tsx b/src/app/player.tsx
index 64b6a99c..a395d884 100644
--- a/src/app/player.tsx
+++ b/src/app/player.tsx
@@ -5,7 +5,7 @@ import { PlayerHeader } from '@/features/player/components/PlayerHeader'
import Lyrics from '@/features/player/components/PlayerLyrics'
import { PlayerSlider } from '@/features/player/components/PlayerSlider'
import { TrackInfo } from '@/features/player/components/PlayerTrackInfo'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import * as Haptics from '@/utils/haptics'
import type { BottomSheetMethods } from '@gorhom/bottom-sheet/lib/typescript/types'
import { useImage } from 'expo-image'
diff --git a/src/app/test.tsx b/src/app/test.tsx
index a5672287..f6aff3c4 100644
--- a/src/app/test.tsx
+++ b/src/app/test.tsx
@@ -1,6 +1,5 @@
import { alert } from '@/components/modals/AlertModal'
import NowPlayingBar from '@/components/NowPlayingBar'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import useDownloadManagerStore from '@/hooks/stores/useDownloadManagerStore'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { downloadService } from '@/lib/services/downloadService'
@@ -21,7 +20,7 @@ export default function TestPage() {
const { isUpdatePending } = Updates.useUpdates()
const insets = useSafeAreaInsets()
const { colors } = useTheme()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const testCheckUpdate = async () => {
try {
@@ -149,7 +148,7 @@ export default function TestPage() {
>
diff --git a/src/components/NowPlayingBar.tsx b/src/components/NowPlayingBar.tsx
index fb7c0694..d94c0ddb 100644
--- a/src/components/NowPlayingBar.tsx
+++ b/src/components/NowPlayingBar.tsx
@@ -1,5 +1,5 @@
import useAnimatedTrackProgress from '@/hooks/player/useAnimatedTrackProgress'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import * as Haptics from '@/utils/haptics'
import { Image } from 'expo-image'
diff --git a/src/components/modals/PlayerQueueModal.tsx b/src/components/modals/PlayerQueueModal.tsx
index a3653551..43c63aee 100644
--- a/src/components/modals/PlayerQueueModal.tsx
+++ b/src/components/modals/PlayerQueueModal.tsx
@@ -1,6 +1,6 @@
+import useCurrentQueue from '@/hooks/player/useCurrentQueue'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import usePreventRemove from '@/hooks/router/usePreventRemove'
-import useCurrentQueue from '@/hooks/stores/playerHooks/useCurrentQueue'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { Track } from '@/types/core/media'
import type { BottomSheetFlatListMethods } from '@gorhom/bottom-sheet'
diff --git a/src/features/library/collection/CollectionList.tsx b/src/features/library/collection/CollectionList.tsx
index 829ffa9f..ab364742 100644
--- a/src/features/library/collection/CollectionList.tsx
+++ b/src/features/library/collection/CollectionList.tsx
@@ -3,8 +3,8 @@ import { DataFetchingPending } from '@/features/library/shared/DataFetchingPendi
import TabDisable from '@/features/library/shared/TabDisabled'
import { useInfiniteCollectionsList } from '@/hooks/queries/bilibili/favorite'
import { usePersonalInformation } from '@/hooks/queries/bilibili/user'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import useAppStore from '@/hooks/stores/useAppStore'
+import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { BilibiliCollection } from '@/types/apis/bilibili'
import { FlashList } from '@shopify/flash-list'
import { memo, useCallback, useState } from 'react'
@@ -14,7 +14,7 @@ import CollectionListItem from './CollectionListItem'
const CollectionListComponent = memo(() => {
const { colors } = useTheme()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const [refreshing, setRefreshing] = useState(false)
const enable = useAppStore((state) => state.hasBilibiliCookie())
@@ -95,7 +95,7 @@ const CollectionListComponent = memo(() => {
/>
}
keyExtractor={keyExtractor}
- contentContainerStyle={{ paddingBottom: currentTrack ? 70 : 10 }}
+ contentContainerStyle={{ paddingBottom: haveTrack ? 70 : 10 }}
showsVerticalScrollIndicator={false}
onEndReached={hasNextPage ? () => fetchNextPage() : undefined}
ListFooterComponent={
diff --git a/src/features/library/favorite/FavoriteFolderList.tsx b/src/features/library/favorite/FavoriteFolderList.tsx
index e4fcc024..b56f8ca2 100644
--- a/src/features/library/favorite/FavoriteFolderList.tsx
+++ b/src/features/library/favorite/FavoriteFolderList.tsx
@@ -3,8 +3,8 @@ import { DataFetchingPending } from '@/features/library/shared/DataFetchingPendi
import TabDisable from '@/features/library/shared/TabDisabled'
import { useGetFavoritePlaylists } from '@/hooks/queries/bilibili/favorite'
import { usePersonalInformation } from '@/hooks/queries/bilibili/user'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import useAppStore from '@/hooks/stores/useAppStore'
+import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { BilibiliPlaylist } from '@/types/apis/bilibili'
import { FlashList } from '@shopify/flash-list'
import { useRouter } from 'expo-router'
@@ -16,7 +16,7 @@ import FavoriteFolderListItem from './FavoriteFolderListItem'
const FavoriteFolderListComponent = memo(() => {
const router = useRouter()
const { colors } = useTheme()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const [refreshing, setRefreshing] = useState(false)
const [query, setQuery] = useState('')
const enable = useAppStore((state) => state.hasBilibiliCookie())
@@ -110,7 +110,7 @@ const FavoriteFolderListComponent = memo(() => {
}}
/>
{
const { colors } = useTheme()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const [refreshing, setRefreshing] = useState(false)
const openModal = useModalStore((state) => state.open)
@@ -77,7 +77,7 @@ const LocalPlaylistListComponent = memo(() => {
{
const { colors } = useTheme()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const [refreshing, setRefreshing] = useState(false)
const enable = useAppStore((state) => state.hasBilibiliCookie())
@@ -109,7 +109,7 @@ const MultiPageVideosListComponent = memo(() => {
page.medias ?? []) ?? []}
renderItem={renderPlaylistItem}
diff --git a/src/features/player/components/PlayerFunctionalMenu.tsx b/src/features/player/components/PlayerFunctionalMenu.tsx
index b4028d57..8bd42ae9 100644
--- a/src/features/player/components/PlayerFunctionalMenu.tsx
+++ b/src/features/player/components/PlayerFunctionalMenu.tsx
@@ -1,5 +1,5 @@
import FunctionalMenu from '@/components/common/FunctionalMenu'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import useDownloadManagerStore from '@/hooks/stores/useDownloadManagerStore'
import { useModalStore } from '@/hooks/stores/useModalStore'
import type { Track } from '@/types/core/media'
diff --git a/src/features/player/components/PlayerHeader.tsx b/src/features/player/components/PlayerHeader.tsx
index 34fe9221..ebee3fd7 100644
--- a/src/features/player/components/PlayerHeader.tsx
+++ b/src/features/player/components/PlayerHeader.tsx
@@ -1,4 +1,4 @@
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import { useRouter } from 'expo-router'
import { View } from 'react-native'
import { IconButton, Text } from 'react-native-paper'
diff --git a/src/features/player/components/PlayerTrackInfo.tsx b/src/features/player/components/PlayerTrackInfo.tsx
index 8e4ca985..c3f67ca6 100644
--- a/src/features/player/components/PlayerTrackInfo.tsx
+++ b/src/features/player/components/PlayerTrackInfo.tsx
@@ -1,6 +1,6 @@
import { useThumbUpVideo } from '@/hooks/mutations/bilibili/video'
+import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import { useGetVideoIsThumbUp } from '@/hooks/queries/bilibili/video'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
import { getGradientColors } from '@/utils/color'
import type { ImageRef } from 'expo-image'
import { Image } from 'expo-image'
diff --git a/src/features/playlist/local/components/LocalTrackList.tsx b/src/features/playlist/local/components/LocalTrackList.tsx
index 6df6291d..a6643cd2 100644
--- a/src/features/playlist/local/components/LocalTrackList.tsx
+++ b/src/features/playlist/local/components/LocalTrackList.tsx
@@ -1,5 +1,5 @@
import FunctionalMenu from '@/components/common/FunctionalMenu'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
+import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { Playlist, Track } from '@/types/core/media'
import { FlashList } from '@shopify/flash-list'
import { useCallback, useState } from 'react'
@@ -44,7 +44,7 @@ export function LocalTrackList({
isFetchingNextPage,
hasNextPage,
}: LocalTrackListProps) {
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const insets = useSafeAreaInsets()
const theme = useTheme()
@@ -114,7 +114,7 @@ export function LocalTrackList({
keyExtractor={keyExtractor}
contentContainerStyle={{
pointerEvents: menuState.visible ? 'none' : 'auto',
- paddingBottom: currentTrack ? 70 + insets.bottom : insets.bottom,
+ paddingBottom: haveTrack ? 70 + insets.bottom : insets.bottom,
}}
showsVerticalScrollIndicator={false}
ListFooterComponent={
diff --git a/src/features/playlist/remote/components/RemoteTrackList.tsx b/src/features/playlist/remote/components/RemoteTrackList.tsx
index 02e9ca07..53b9dad6 100644
--- a/src/features/playlist/remote/components/RemoteTrackList.tsx
+++ b/src/features/playlist/remote/components/RemoteTrackList.tsx
@@ -1,5 +1,5 @@
import FunctionalMenu from '@/components/common/FunctionalMenu'
-import useCurrentTrack from '@/hooks/stores/playerHooks/useCurrentTrack'
+import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { BilibiliTrack } from '@/types/core/media'
import * as Haptics from '@/utils/haptics'
import { FlashList } from '@shopify/flash-list'
@@ -53,7 +53,7 @@ export function TrackList({
hasNextPage,
}: TrackListProps) {
const colors = useTheme().colors
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const insets = useSafeAreaInsets()
const [menuState, setMenuState] = useState<{
@@ -139,7 +139,7 @@ export function TrackList({
contentContainerStyle={{
// 实现一个在 menu 弹出时,列表不可触摸的效果
pointerEvents: menuState.visible ? 'none' : 'auto',
- paddingBottom: currentTrack ? 70 + insets.bottom : insets.bottom,
+ paddingBottom: haveTrack ? 70 + insets.bottom : insets.bottom,
}}
onEndReached={onEndReached}
ListFooterComponent={
diff --git a/src/hooks/stores/playerHooks/useCurrentQueue.ts b/src/hooks/player/useCurrentQueue.ts
similarity index 100%
rename from src/hooks/stores/playerHooks/useCurrentQueue.ts
rename to src/hooks/player/useCurrentQueue.ts
diff --git a/src/hooks/stores/playerHooks/useCurrentTrack.ts b/src/hooks/player/useCurrentTrack.ts
similarity index 100%
rename from src/hooks/stores/playerHooks/useCurrentTrack.ts
rename to src/hooks/player/useCurrentTrack.ts
diff --git a/src/lib/player/playerLogic.ts b/src/lib/player/playerLogic.ts
index 9b2bef51..5ef3a877 100644
--- a/src/lib/player/playerLogic.ts
+++ b/src/lib/player/playerLogic.ts
@@ -14,10 +14,6 @@ const logger = log.extend('Player.Init')
const initPlayer = async () => {
logger.info('开始初始化播放器')
- if (global.playerIsReady) {
- logger.warning('播放器已经初始化过了')
- return
- }
await PlayerLogic.preparePlayer()
PlayerLogic.setupEventListeners()
// 初始化后强制将 RNTP 重复模式设为 Off,循环由我们内部管理
From 45495eabc68cfe2ef2ccb52e20ca74c6087d3d6d Mon Sep 17 00:00:00 2001
From: Roitium <65794453+roitium@users.noreply.github.com>
Date: Sun, 2 Nov 2025 07:29:04 +0800
Subject: [PATCH 2/5] fix: functional menu animation
---
CHANGELOG.md | 5 ++
patches/react-native-paper.patch | 63 ------------------------
pnpm-workspace.yaml | 3 --
src/components/common/FunctionalMenu.tsx | 1 +
4 files changed, 6 insertions(+), 66 deletions(-)
delete mode 100644 patches/react-native-paper.patch
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1644ac6d..ddde0c0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,11 @@
- 基于 B 站视频 bgm 识别结果精准搜索歌词
- 切换到 expo-router
+### Fixed
+
+- 一些减少 rerender 次数的优化
+- 使用 [react-native-paper/4807](https://github.com/callstack/react-native-paper/issues/4807) 中提到的 Menu 组件修复方法,移除 patch
+
## [1.3.6] - 2025-10-26
### Added
diff --git a/patches/react-native-paper.patch b/patches/react-native-paper.patch
deleted file mode 100644
index 35074aed..00000000
--- a/patches/react-native-paper.patch
+++ /dev/null
@@ -1,63 +0,0 @@
-diff --git a/lib/module/components/Menu/Menu.js b/lib/module/components/Menu/Menu.js
-index 46a45f468db95f6a2c150d266de2d2cce0482b1d..15323c16dd891e3b6f7d184316e7989576176fd8 100644
---- a/lib/module/components/Menu/Menu.js
-+++ b/lib/module/components/Menu/Menu.js
-@@ -238,10 +238,8 @@ const Menu = ({
- })]).start(({
- finished
- }) => {
-- if (finished) {
- focusFirstDOMNode(menuRef.current);
- prevRendered.current = true;
-- }
- });
- }, [anchor, attachListeners, measureAnchorLayout, theme]);
- const hide = React.useCallback(() => {
-@@ -257,7 +255,6 @@ const Menu = ({
- }).start(({
- finished
- }) => {
-- if (finished) {
- setMenuLayout({
- width: 0,
- height: 0
-@@ -265,7 +262,6 @@ const Menu = ({
- setRendered(false);
- prevRendered.current = false;
- focusFirstDOMNode(anchorRef.current);
-- }
- });
- }, [removeListeners, theme]);
- const updateVisibility = React.useCallback(async display => {
-diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx
-index 55922c1fc27003eea00c77c4dbef180f9016b938..1fc035da37c1ca85c6953b11aa1c8138788d7a8e 100644
---- a/src/components/Menu/Menu.tsx
-+++ b/src/components/Menu/Menu.tsx
-@@ -359,11 +359,9 @@ const Menu = ({
- easing: EASING,
- useNativeDriver: true,
- }),
-- ]).start(({ finished }) => {
-- if (finished) {
-+ ]).start(() => {
- focusFirstDOMNode(menuRef.current);
- prevRendered.current = true;
-- }
- });
- }, [anchor, attachListeners, measureAnchorLayout, theme]);
-
-@@ -377,13 +375,11 @@ const Menu = ({
- duration: ANIMATION_DURATION * animation.scale,
- easing: EASING,
- useNativeDriver: true,
-- }).start(({ finished }) => {
-- if (finished) {
-+ }).start(() => {
- setMenuLayout({ width: 0, height: 0 });
- setRendered(false);
- prevRendered.current = false;
- focusFirstDOMNode(anchorRef.current);
-- }
- });
- }, [removeListeners, theme]);
-
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index ec0b3fb9..c2a98a16 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -3,6 +3,3 @@ onlyBuiltDependencies:
- esbuild
- lefthook
- unrs-resolver
-
-patchedDependencies:
- react-native-paper: patches/react-native-paper.patch
diff --git a/src/components/common/FunctionalMenu.tsx b/src/components/common/FunctionalMenu.tsx
index 227e0c6a..9332d10b 100644
--- a/src/components/common/FunctionalMenu.tsx
+++ b/src/components/common/FunctionalMenu.tsx
@@ -32,6 +32,7 @@ const FunctionalMenu = memo(function FunctionalMenu({
{...props}
onDismiss={onClose}
visible={visible}
+ key={String(visible)}
style={{
opacity: showContent ? 1 : 0,
}}
From c7e8d3ee3d016613e8378c30951dee3da71ad724 Mon Sep 17 00:00:00 2001
From: Roitium <65794453+roitium@users.noreply.github.com>
Date: Sun, 2 Nov 2025 08:37:22 +0800
Subject: [PATCH 3/5] fix: something
---
docs/principles.md | 5 +
src/app/(tabs)/index.tsx | 2 +-
src/app/(tabs)/settings.tsx | 2 +-
src/app/_layout.tsx | 7 +-
src/app/download.tsx | 8 +-
src/app/leaderboard.tsx | 23 ++--
src/app/playlist/local/[id].tsx | 2 +-
src/app/test.tsx | 3 +-
.../editPlaylistMetadataModal.tsx | 3 +-
.../modals/login/CookieLoginModal.tsx | 2 +-
src/components/modals/lyrics/EditLyrics.tsx | 2 +-
.../modals/lyrics/ManualSearchLyrics.tsx | 37 ++++--
.../BatchAddTracksToLocalPlaylist.tsx | 50 ++++---
.../UpdateTrackLocalPlaylistsModal.tsx | 55 +++++---
.../library/collection/CollectionList.tsx | 10 +-
.../library/favorite/FavoriteFolderList.tsx | 10 +-
.../library/local/LocalPlaylistList.tsx | 8 +-
.../library/multipage/MultiPageVideosList.tsx | 12 +-
.../player/components/PlayerLyrics.tsx | 50 +++++--
.../local/components/LocalTrackList.tsx | 92 +++++++++----
.../local/hooks/useLocalPlaylistMenu.ts | 2 +-
.../local/hooks/useLocalPlaylistPlayer.ts | 2 +-
.../remote/components/RemoteTrackList.tsx | 125 ++++++++++++------
.../hooks/useCheckLinkedToLocalPlaylist.ts | 2 +-
src/hooks/mutations/bilibili/favorite.ts | 3 +-
src/hooks/mutations/bilibili/video.ts | 2 +-
src/hooks/mutations/db/playlist.ts | 2 +-
src/hooks/stores/usePlayerStore.ts | 7 +-
src/lib/config/queryClient.ts | 2 +-
src/lib/player/playerLogic.ts | 3 +-
src/lib/services/lyricService.ts | 3 +-
src/types/flashlist.ts | 8 ++
src/utils/error-handling.ts | 41 ++++++
src/utils/log.ts | 39 ------
src/utils/lyrics.ts | 3 +-
src/utils/search.ts | 3 +-
36 files changed, 396 insertions(+), 234 deletions(-)
create mode 100644 docs/principles.md
create mode 100644 src/types/flashlist.ts
create mode 100644 src/utils/error-handling.ts
diff --git a/docs/principles.md b/docs/principles.md
new file mode 100644
index 00000000..ae02abd1
--- /dev/null
+++ b/docs/principles.md
@@ -0,0 +1,5 @@
+## 开发规范(及一些 tips)
+
+### UI 开发
+
+- FlashList 使用到的所有 renderItem 应该在函数外定义,并把所有除了 item 之外的依赖放入 extraData 中,并使用 useMemo 包裹 extraData
diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx
index 52884f03..c54845cd 100644
--- a/src/app/(tabs)/index.tsx
+++ b/src/app/(tabs)/index.tsx
@@ -4,7 +4,7 @@ import SearchSuggestions from '@/features/home/SearchSuggestions'
import { usePersonalInformation } from '@/hooks/queries/bilibili/user'
import useAppStore from '@/hooks/stores/useAppStore'
import { queryClient } from '@/lib/config/queryClient'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import {
matchSearchStrategies,
navigateWithSearchStrategy,
diff --git a/src/app/(tabs)/settings.tsx b/src/app/(tabs)/settings.tsx
index 272c8901..d0a615c0 100644
--- a/src/app/(tabs)/settings.tsx
+++ b/src/app/(tabs)/settings.tsx
@@ -3,7 +3,7 @@ import useAppStore from '@/hooks/stores/useAppStore'
import { useModalStore } from '@/hooks/stores/useModalStore'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { checkForAppUpdate } from '@/lib/services/updateService'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import toast from '@/utils/toast'
import * as Application from 'expo-application'
import * as Clipboard from 'expo-clipboard'
diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index 2c8b721f..46875364 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -8,11 +8,8 @@ import drizzleDb, { expoDb } from '@/lib/db/db'
import { initPlayer } from '@/lib/player/playerLogic'
import lyricService from '@/lib/services/lyricService'
import { ProjectScope } from '@/types/core/scope'
-import log, {
- cleanOldLogFiles,
- reportErrorToSentry,
- toastAndLogError,
-} from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log, { cleanOldLogFiles, reportErrorToSentry } from '@/utils/log'
import { storage } from '@/utils/mmkv'
import toast from '@/utils/toast'
import { useLogger } from '@react-navigation/devtools'
diff --git a/src/app/download.tsx b/src/app/download.tsx
index 440426a3..478ca57c 100644
--- a/src/app/download.tsx
+++ b/src/app/download.tsx
@@ -12,6 +12,10 @@ import { Appbar, useTheme } from 'react-native-paper'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useShallow } from 'zustand/shallow'
+const renderItem = ({ item }: { item: DownloadTask }) => {
+ return
+}
+
export default function DownloadPage() {
const { colors } = useTheme()
const router = useRouter()
@@ -25,10 +29,6 @@ export default function DownloadPage() {
const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
- const renderItem = useCallback(({ item }: { item: DownloadTask }) => {
- return
- }, [])
-
const keyExtractor = useCallback((item: DownloadTask) => item.uniqueKey, [])
return (
diff --git a/src/app/leaderboard.tsx b/src/app/leaderboard.tsx
index 9ed7b912..6101d292 100644
--- a/src/app/leaderboard.tsx
+++ b/src/app/leaderboard.tsx
@@ -40,6 +40,19 @@ const formatDurationToWords = (seconds: number) => {
return parts.join(' ')
}
+const renderItem = ({
+ item,
+ index,
+}: {
+ item: LeaderBoardItemData
+ index: number
+}) => (
+
+)
+
export default function LeaderBoardPage() {
const { colors } = useTheme()
const router = useRouter()
@@ -66,16 +79,6 @@ export default function LeaderBoardPage() {
return formatDurationToWords(totalDurationData)
}, [totalDurationData, isTotalDurationError])
- const renderItem = useCallback(
- ({ item, index }: { item: LeaderBoardItemData; index: number }) => (
-
- ),
- [],
- )
-
const keyExtractor = useCallback(
(item: LeaderBoardItemData) => item.track.uniqueKey,
[],
diff --git a/src/app/playlist/local/[id].tsx b/src/app/playlist/local/[id].tsx
index 53420ce3..37fd6f58 100644
--- a/src/app/playlist/local/[id].tsx
+++ b/src/app/playlist/local/[id].tsx
@@ -23,8 +23,8 @@ import { useModalStore } from '@/hooks/stores/useModalStore'
import { useDebouncedValue } from '@/hooks/utils/useDebouncedValue'
import type { CreateArtistPayload } from '@/types/services/artist'
import type { CreateTrackPayload } from '@/types/services/track'
+import { toastAndLogError } from '@/utils/error-handling'
import * as Haptics from '@/utils/haptics'
-import { toastAndLogError } from '@/utils/log'
import toast from '@/utils/toast'
import { useLocalSearchParams, useRouter } from 'expo-router'
import { useCallback, useEffect, useState } from 'react'
diff --git a/src/app/test.tsx b/src/app/test.tsx
index f6aff3c4..619e488c 100644
--- a/src/app/test.tsx
+++ b/src/app/test.tsx
@@ -4,7 +4,8 @@ import useDownloadManagerStore from '@/hooks/stores/useDownloadManagerStore'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { downloadService } from '@/lib/services/downloadService'
import lyricService from '@/lib/services/lyricService'
-import log, { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log from '@/utils/log'
import toast from '@/utils/toast'
import * as Updates from 'expo-updates'
import { useState } from 'react'
diff --git a/src/components/modals/edit-metadata/editPlaylistMetadataModal.tsx b/src/components/modals/edit-metadata/editPlaylistMetadataModal.tsx
index 431bd659..10619de0 100644
--- a/src/components/modals/edit-metadata/editPlaylistMetadataModal.tsx
+++ b/src/components/modals/edit-metadata/editPlaylistMetadataModal.tsx
@@ -2,7 +2,8 @@ import { useEditPlaylistMetadata } from '@/hooks/mutations/db/playlist'
import { useModalStore } from '@/hooks/stores/useModalStore'
import { bilibiliFacade } from '@/lib/facades/bilibili'
import type { Playlist } from '@/types/core/media'
-import log, { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log from '@/utils/log'
import toast from '@/utils/toast'
import * as DocumentPicker from 'expo-document-picker'
import * as FileSystem from 'expo-file-system'
diff --git a/src/components/modals/login/CookieLoginModal.tsx b/src/components/modals/login/CookieLoginModal.tsx
index 797391d8..23bf5110 100644
--- a/src/components/modals/login/CookieLoginModal.tsx
+++ b/src/components/modals/login/CookieLoginModal.tsx
@@ -2,7 +2,7 @@ import { favoriteListQueryKeys } from '@/hooks/queries/bilibili/favorite'
import { userQueryKeys } from '@/hooks/queries/bilibili/user'
import useAppStore, { serializeCookieObject } from '@/hooks/stores/useAppStore'
import { useModalStore } from '@/hooks/stores/useModalStore'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import toast from '@/utils/toast'
import { useQueryClient } from '@tanstack/react-query'
import { useCallback, useMemo, useState } from 'react'
diff --git a/src/components/modals/lyrics/EditLyrics.tsx b/src/components/modals/lyrics/EditLyrics.tsx
index 63026240..e97c42b3 100644
--- a/src/components/modals/lyrics/EditLyrics.tsx
+++ b/src/components/modals/lyrics/EditLyrics.tsx
@@ -3,7 +3,7 @@ import { useModalStore } from '@/hooks/stores/useModalStore'
import { queryClient } from '@/lib/config/queryClient'
import lyricService from '@/lib/services/lyricService'
import type { ParsedLrc } from '@/types/player/lyrics'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import { mergeLrc, parseLrc } from '@/utils/lyrics'
import toast from '@/utils/toast'
import { useState } from 'react'
diff --git a/src/components/modals/lyrics/ManualSearchLyrics.tsx b/src/components/modals/lyrics/ManualSearchLyrics.tsx
index 99b46106..1f57b7ae 100644
--- a/src/components/modals/lyrics/ManualSearchLyrics.tsx
+++ b/src/components/modals/lyrics/ManualSearchLyrics.tsx
@@ -1,10 +1,11 @@
import { useFetchLyrics } from '@/hooks/mutations/lyrics'
import { useManualSearchLyrics } from '@/hooks/queries/lyrics'
import { useModalStore } from '@/hooks/stores/useModalStore'
+import type { ListRenderItemInfoWithExtraData } from '@/types/flashlist'
import type { LyricSearchResult } from '@/types/player/lyrics'
import { formatDurationToHHMMSS } from '@/utils/time'
import { FlashList } from '@shopify/flash-list'
-import { memo, useCallback, useState } from 'react'
+import { memo, useCallback, useMemo, useState } from 'react'
import { View } from 'react-native'
import {
ActivityIndicator,
@@ -19,6 +20,26 @@ const SOURCE_MAP = {
netease: '网易云',
}
+const renderItem = ({
+ item,
+ extraData,
+}: ListRenderItemInfoWithExtraData<
+ LyricSearchResult[0],
+ {
+ isFetchingLyrics: boolean
+ handlePressItem: (item: LyricSearchResult[0]) => void
+ }
+>) => {
+ if (!extraData) throw new Error('Extradata 不存在')
+ return (
+
+ )
+}
+
const SearchItem = memo(function SearchItem({
item,
onPress,
@@ -70,17 +91,8 @@ const ManualSearchLyricsModal = ({
},
[close, fetchLyrics, uniqueKey],
)
-
- const renderItem = useCallback(
- ({ item }: { item: LyricSearchResult[0] }) => {
- return (
-
- )
- },
+ const extraData = useMemo(
+ () => ({ isFetchingLyrics, handlePressItem }),
[handlePressItem, isFetchingLyrics],
)
@@ -116,6 +128,7 @@ const ManualSearchLyricsModal = ({
data={searchResult}
renderItem={renderItem}
keyExtractor={keyExtractor}
+ extraData={extraData}
/>
)
}
diff --git a/src/components/modals/playlist/BatchAddTracksToLocalPlaylist.tsx b/src/components/modals/playlist/BatchAddTracksToLocalPlaylist.tsx
index e74a7c25..850d82df 100644
--- a/src/components/modals/playlist/BatchAddTracksToLocalPlaylist.tsx
+++ b/src/components/modals/playlist/BatchAddTracksToLocalPlaylist.tsx
@@ -6,10 +6,34 @@ import { useBatchAddTracksToLocalPlaylist } from '@/hooks/mutations/db/playlist'
import { usePlaylistLists } from '@/hooks/queries/db/playlist'
import { useModalStore } from '@/hooks/stores/useModalStore'
import type { Playlist } from '@/types/core/media'
+import type { ListRenderItemInfoWithExtraData } from '@/types/flashlist'
import type { CreateArtistPayload } from '@/types/services/artist'
import type { CreateTrackPayload } from '@/types/services/track'
import { FlashList } from '@shopify/flash-list'
+const renderPlaylistItem = ({
+ item,
+ extraData,
+}: ListRenderItemInfoWithExtraData<
+ Playlist,
+ { selectedPlaylistId: number; setSelectedPlaylistId: (id: number) => void }
+>) => {
+ if (!extraData) throw new Error('Extradata 不存在')
+ const isChecked = extraData.selectedPlaylistId === item.id
+ const isDisabled = item.type !== 'local'
+ const setSelectedPlaylistId = extraData.setSelectedPlaylistId
+
+ return (
+ !isDisabled && setSelectedPlaylistId(item.id)}
+ disabled={isDisabled}
+ />
+ )
+}
+
const BatchAddTracksToLocalPlaylistModal = memo(
function AddTracksToLocalPlaylistModal({
payloads,
@@ -68,26 +92,16 @@ const BatchAddTracksToLocalPlaylistModal = memo(
)
}, [batchAdd, close, isMutating, payloads, selectedPlaylistId])
- const renderPlaylistItem = useCallback(
- ({ item }: { item: Playlist }) => {
- const isChecked = selectedPlaylistId === item.id
- const isDisabled = item.type !== 'local'
+ const keyExtractor = useCallback((item: Playlist) => item.id.toString(), [])
- return (
- !isDisabled && setSelectedPlaylistId(item.id)}
- disabled={isDisabled}
- />
- )
- },
- [selectedPlaylistId],
+ const extraData = useMemo(
+ () => ({
+ selectedPlaylistId,
+ setSelectedPlaylistId,
+ }),
+ [selectedPlaylistId, setSelectedPlaylistId],
)
- const keyExtractor = useCallback((item: Playlist) => item.id.toString(), [])
-
const renderContent = () => {
if (isLoading) {
return (
@@ -120,7 +134,7 @@ const BatchAddTracksToLocalPlaylistModal = memo(
data={filteredPlaylists ?? []}
renderItem={renderPlaylistItem}
keyExtractor={keyExtractor}
- extraData={selectedPlaylistId}
+ extraData={extraData}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
void
+ }
+>) => {
+ if (!extraData) throw new Error('Extradata 不存在')
+ const { checkedPlaylistIds, handleCheckboxPress } = extraData
+ const isChecked = checkedPlaylistIds.includes(item.id)
+ const isDisabled = item.type !== 'local'
+
+ return (
+
+ )
+}
+
const PlaylistListItem = memo(function PlaylistListItem({
id,
title,
@@ -136,6 +163,14 @@ const UpdateTrackLocalPlaylistsModal = memo(
close,
])
+ const extraData = useMemo(
+ () => ({
+ checkedPlaylistIds,
+ handleCheckboxPress,
+ }),
+ [checkedPlaylistIds, handleCheckboxPress],
+ )
+
const handleDismiss = () => {
if (isMutating) return
close()
@@ -146,24 +181,6 @@ const UpdateTrackLocalPlaylistsModal = memo(
if (isContainingTrackError) void refetchContainingTrack()
}
- const renderPlaylistItem = useCallback(
- ({ item }: { item: Playlist }) => {
- const isChecked = checkedPlaylistIds.includes(item.id)
- const isDisabled = item.type !== 'local'
-
- return (
-
- )
- },
- [checkedPlaylistIds, handleCheckboxPress],
- )
-
const keyExtractor = useCallback((item: Playlist) => item.id.toString(), [])
const renderContent = () => {
@@ -198,7 +215,7 @@ const UpdateTrackLocalPlaylistsModal = memo(
data={filteredPlaylists ?? []}
renderItem={renderPlaylistItem}
keyExtractor={keyExtractor}
- extraData={checkedPlaylistIds}
+ extraData={extraData}
ListEmptyComponent={
(
+
+)
+
const CollectionListComponent = memo(() => {
const { colors } = useTheme()
const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
@@ -29,12 +33,6 @@ const CollectionListComponent = memo(() => {
fetchNextPage,
} = useInfiniteCollectionsList(Number(userInfo?.mid))
- const renderCollectionItem = useCallback(
- ({ item }: { item: BilibiliCollection }) => (
-
- ),
- [],
- )
const keyExtractor = useCallback(
(item: BilibiliCollection) => item.id.toString(),
[],
diff --git a/src/features/library/favorite/FavoriteFolderList.tsx b/src/features/library/favorite/FavoriteFolderList.tsx
index b56f8ca2..78d141d4 100644
--- a/src/features/library/favorite/FavoriteFolderList.tsx
+++ b/src/features/library/favorite/FavoriteFolderList.tsx
@@ -13,6 +13,10 @@ import { RefreshControl, View } from 'react-native'
import { Searchbar, Text, useTheme } from 'react-native-paper'
import FavoriteFolderListItem from './FavoriteFolderListItem'
+const renderPlaylistItem = ({ item }: { item: BilibiliPlaylist }) => (
+
+)
+
const FavoriteFolderListComponent = memo(() => {
const router = useRouter()
const { colors } = useTheme()
@@ -30,12 +34,6 @@ const FavoriteFolderListComponent = memo(() => {
isError: playlistsIsError,
} = useGetFavoritePlaylists(userInfo?.mid)
- const renderPlaylistItem = useCallback(
- ({ item }: { item: BilibiliPlaylist }) => (
-
- ),
- [],
- )
const keyExtractor = useCallback(
(item: BilibiliPlaylist) => item.id.toString(),
[],
diff --git a/src/features/library/local/LocalPlaylistList.tsx b/src/features/library/local/LocalPlaylistList.tsx
index abef8624..3eb20e32 100644
--- a/src/features/library/local/LocalPlaylistList.tsx
+++ b/src/features/library/local/LocalPlaylistList.tsx
@@ -10,6 +10,10 @@ import { RefreshControl, View } from 'react-native'
import { IconButton, Text, useTheme } from 'react-native-paper'
import LocalPlaylistItem from './LocalPlaylistItem'
+const renderPlaylistItem = ({ item }: { item: Playlist }) => (
+
+)
+
const LocalPlaylistListComponent = memo(() => {
const { colors } = useTheme()
const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
@@ -24,10 +28,6 @@ const LocalPlaylistListComponent = memo(() => {
isError: playlistsIsError,
} = usePlaylistLists()
- const renderPlaylistItem = useCallback(
- ({ item }: { item: Playlist }) => ,
- [],
- )
const keyExtractor = useCallback((item: Playlist) => item.id.toString(), [])
const onRefresh = async () => {
diff --git a/src/features/library/multipage/MultiPageVideosList.tsx b/src/features/library/multipage/MultiPageVideosList.tsx
index aacb2b3d..ae55ff3f 100644
--- a/src/features/library/multipage/MultiPageVideosList.tsx
+++ b/src/features/library/multipage/MultiPageVideosList.tsx
@@ -15,6 +15,12 @@ import { RefreshControl, View } from 'react-native'
import { ActivityIndicator, Text, useTheme } from 'react-native-paper'
import MultiPageVideosItem from './MultiPageVideosItem'
+const renderPlaylistItem = ({
+ item,
+}: {
+ item: BilibiliFavoriteListContent
+}) =>
+
const MultiPageVideosListComponent = memo(() => {
const { colors } = useTheme()
const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
@@ -41,12 +47,6 @@ const MultiPageVideosListComponent = memo(() => {
playlists?.find((item) => item.title.startsWith('[mp]'))?.id,
)
- const renderPlaylistItem = useCallback(
- ({ item }: { item: BilibiliFavoriteListContent }) => (
-
- ),
- [],
- )
const keyExtractor = useCallback(
(item: BilibiliFavoriteListContent) => item.bvid,
[],
diff --git a/src/features/player/components/PlayerLyrics.tsx b/src/features/player/components/PlayerLyrics.tsx
index 972a6115..1a363883 100644
--- a/src/features/player/components/PlayerLyrics.tsx
+++ b/src/features/player/components/PlayerLyrics.tsx
@@ -5,12 +5,20 @@ import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { queryClient } from '@/lib/config/queryClient'
import lyricService from '@/lib/services/lyricService'
import type { Track } from '@/types/core/media'
+import type { ListRenderItemInfoWithExtraData } from '@/types/flashlist'
import type { LyricLine } from '@/types/player/lyrics'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import type { FlashListRef } from '@shopify/flash-list'
import { FlashList } from '@shopify/flash-list'
import { LinearGradient } from 'expo-linear-gradient'
-import { memo, useCallback, useLayoutEffect, useRef, useState } from 'react'
+import {
+ memo,
+ useCallback,
+ useLayoutEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react'
import { Dimensions, ScrollView, View } from 'react-native'
import { RectButton } from 'react-native-gesture-handler'
import {
@@ -158,6 +166,29 @@ const LyricLineItem = memo(function LyricLineItem({
)
})
+const renderItem = ({
+ item,
+ index,
+ extraData,
+}: ListRenderItemInfoWithExtraData<
+ LyricLine,
+ {
+ currentLyricIndex: number
+ handleJumpToLyric: (index: number) => void
+ }
+>) => {
+ if (!extraData) throw new Error('Extradata 不存在')
+ const { currentLyricIndex, handleJumpToLyric } = extraData
+ return (
+
+ )
+}
+
export default function Lyrics({
onBackPress,
track,
@@ -239,15 +270,11 @@ export default function Lyrics({
[],
)
- const renderItem = useCallback(
- ({ item, index }: { item: LyricLine; index: number }) => (
-
- ),
+ const extraData = useMemo(
+ () => ({
+ currentLyricIndex,
+ handleJumpToLyric,
+ }),
[currentLyricIndex, handleJumpToLyric],
)
@@ -352,6 +379,7 @@ export default function Lyrics({
ref={flashListRef}
data={lyrics.lyrics}
renderItem={renderItem}
+ extraData={extraData}
keyExtractor={keyExtractor}
contentContainerStyle={{
justifyContent: 'center',
diff --git a/src/features/playlist/local/components/LocalTrackList.tsx b/src/features/playlist/local/components/LocalTrackList.tsx
index a6643cd2..090d06fc 100644
--- a/src/features/playlist/local/components/LocalTrackList.tsx
+++ b/src/features/playlist/local/components/LocalTrackList.tsx
@@ -1,8 +1,9 @@
import FunctionalMenu from '@/components/common/FunctionalMenu'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { Playlist, Track } from '@/types/core/media'
+import type { ListRenderItemInfoWithExtraData } from '@/types/flashlist'
import { FlashList } from '@shopify/flash-list'
-import { useCallback, useState } from 'react'
+import { useCallback, useMemo, useState } from 'react'
import { View } from 'react-native'
import {
ActivityIndicator,
@@ -30,6 +31,52 @@ interface LocalTrackListProps {
isFetchingNextPage?: boolean
}
+const renderItem = ({
+ item,
+ index,
+ extraData,
+}: ListRenderItemInfoWithExtraData<
+ Track,
+ {
+ handleTrackPress: (track: Track) => void
+ handleMenuPress: (track: Track, anchor: { x: number; y: number }) => void
+ toggle: (id: number) => void
+ enterSelectMode: (id: number) => void
+ selected: Set
+ selectMode: boolean
+ playlist: Playlist
+ }
+>) => {
+ if (!extraData) throw new Error('Extradata 不存在')
+ const {
+ handleTrackPress,
+ handleMenuPress,
+ toggle,
+ enterSelectMode,
+ selected,
+ selectMode,
+ playlist,
+ } = extraData
+ return (
+ handleTrackPress(item)}
+ onMenuPress={(anchor) => {
+ handleMenuPress(item, anchor)
+ }}
+ disabled={
+ item.source === 'bilibili' && !item.bilibiliMetadata.videoIsValid
+ }
+ data={item}
+ playlist={playlist}
+ toggleSelected={toggle}
+ isSelected={selected.has(item.id)}
+ selectMode={selectMode}
+ enterSelectMode={enterSelectMode}
+ />
+ )
+}
+
export function LocalTrackList({
tracks,
playlist,
@@ -69,46 +116,35 @@ export function LocalTrackList({
setMenuState((prev) => ({ ...prev, visible: false }))
}, [])
- const renderItem = useCallback(
- ({ item, index }: { item: Track; index: number }) => {
- return (
- handleTrackPress(item)}
- onMenuPress={(anchor) => {
- handleMenuPress(item, anchor)
- }}
- disabled={
- item.source === 'bilibili' && !item.bilibiliMetadata.videoIsValid
- }
- data={item}
- playlist={playlist}
- toggleSelected={toggle}
- isSelected={selected.has(item.id)}
- selectMode={selectMode}
- enterSelectMode={enterSelectMode}
- />
- )
- },
- [
- enterSelectMode,
+ const keyExtractor = useCallback((item: Track) => String(item.id), [])
+
+ const extraData = useMemo(
+ () => ({
+ selectMode,
+ selected,
handleTrackPress,
+ handleMenuPress,
+ toggle,
+ enterSelectMode,
playlist,
+ }),
+ [
selectMode,
selected,
- toggle,
+ handleTrackPress,
handleMenuPress,
+ toggle,
+ enterSelectMode,
+ playlist,
],
)
- const keyExtractor = useCallback((item: Track) => String(item.id), [])
-
return (
<>
}
ListHeaderComponent={ListHeaderComponent}
keyExtractor={keyExtractor}
diff --git a/src/features/playlist/local/hooks/useLocalPlaylistMenu.ts b/src/features/playlist/local/hooks/useLocalPlaylistMenu.ts
index 2ca0ae52..d846f9e3 100644
--- a/src/features/playlist/local/hooks/useLocalPlaylistMenu.ts
+++ b/src/features/playlist/local/hooks/useLocalPlaylistMenu.ts
@@ -6,7 +6,7 @@ import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { queryClient } from '@/lib/config/queryClient'
import { downloadService } from '@/lib/services/downloadService'
import type { Playlist, Track } from '@/types/core/media'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import toast from '@/utils/toast'
import * as Clipboard from 'expo-clipboard'
import { useRouter } from 'expo-router'
diff --git a/src/features/playlist/local/hooks/useLocalPlaylistPlayer.ts b/src/features/playlist/local/hooks/useLocalPlaylistPlayer.ts
index 96e98d21..5b7bbc01 100644
--- a/src/features/playlist/local/hooks/useLocalPlaylistPlayer.ts
+++ b/src/features/playlist/local/hooks/useLocalPlaylistPlayer.ts
@@ -1,7 +1,7 @@
import { alert } from '@/components/modals/AlertModal'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { Track } from '@/types/core/media'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import { storage } from '@/utils/mmkv'
import { useCallback } from 'react'
import type { MMKV } from 'react-native-mmkv'
diff --git a/src/features/playlist/remote/components/RemoteTrackList.tsx b/src/features/playlist/remote/components/RemoteTrackList.tsx
index 53b9dad6..01c1ee4d 100644
--- a/src/features/playlist/remote/components/RemoteTrackList.tsx
+++ b/src/features/playlist/remote/components/RemoteTrackList.tsx
@@ -1,9 +1,10 @@
import FunctionalMenu from '@/components/common/FunctionalMenu'
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { BilibiliTrack } from '@/types/core/media'
+import type { ListRenderItemInfoWithExtraData } from '@/types/flashlist'
import * as Haptics from '@/utils/haptics'
import { FlashList } from '@shopify/flash-list'
-import { useCallback, useState } from 'react'
+import { useCallback, useMemo, useState } from 'react'
import { View } from 'react-native'
import {
ActivityIndicator,
@@ -35,6 +36,67 @@ interface TrackListProps {
hasNextPage?: boolean
}
+const renderItem = ({
+ item,
+ index,
+ extraData,
+}: ListRenderItemInfoWithExtraData<
+ BilibiliTrack,
+ {
+ toggle: (id: number) => void
+ playTrack: (track: BilibiliTrack) => void
+ handleMenuPress: (
+ track: BilibiliTrack,
+ anchor: { x: number; y: number },
+ ) => void
+ selected: Set
+ selectMode: boolean
+ enterSelectMode: (id: number) => void
+ showItemCover?: boolean
+ }
+>) => {
+ if (!extraData) throw new Error('Extradata 不存在')
+ const {
+ toggle,
+ playTrack,
+ handleMenuPress,
+ selected,
+ selectMode,
+ enterSelectMode,
+ showItemCover,
+ } = extraData
+ return (
+ playTrack(item)}
+ onMenuPress={(anchor) => handleMenuPress(item, anchor)}
+ showCoverImage={showItemCover ?? true}
+ data={{
+ cover: item.coverUrl ?? undefined,
+ title: item.title,
+ duration: item.duration,
+ id: item.id,
+ artistName: item.artist?.name,
+ uniqueKey: item.uniqueKey,
+ }}
+ toggleSelected={() => {
+ void Haptics.performAndroidHapticsAsync(
+ Haptics.AndroidHaptics.Clock_Tick,
+ )
+ toggle(item.id)
+ }}
+ isSelected={selected.has(item.id)}
+ selectMode={selectMode}
+ enterSelectMode={() => {
+ void Haptics.performAndroidHapticsAsync(
+ Haptics.AndroidHaptics.Long_Press,
+ )
+ enterSelectMode(item.id)
+ }}
+ />
+ )
+}
+
export function TrackList({
tracks,
playTrack,
@@ -77,59 +139,36 @@ export function TrackList({
setMenuState((prev) => ({ ...prev, visible: false }))
}, [])
- const renderItem = useCallback(
- ({ item, index }: { item: BilibiliTrack; index: number }) => {
- return (
- playTrack(item)}
- onMenuPress={(anchor) => handleMenuPress(item, anchor)}
- showCoverImage={showItemCover ?? true}
- data={{
- cover: item.coverUrl ?? undefined,
- title: item.title,
- duration: item.duration,
- id: item.id,
- artistName: item.artist?.name,
- uniqueKey: item.uniqueKey,
- }}
- toggleSelected={() => {
- void Haptics.performAndroidHapticsAsync(
- Haptics.AndroidHaptics.Clock_Tick,
- )
- toggle(item.id)
- }}
- isSelected={selected.has(item.id)}
- selectMode={selectMode}
- enterSelectMode={() => {
- void Haptics.performAndroidHapticsAsync(
- Haptics.AndroidHaptics.Long_Press,
- )
- enterSelectMode(item.id)
- }}
- />
- )
- },
- [
- playTrack,
- toggle,
- selected,
+ const keyExtractor = useCallback((item: BilibiliTrack) => {
+ return String(item.id)
+ }, [])
+
+ const extraData = useMemo(
+ () => ({
selectMode,
+ selected,
+ toggle,
+ playTrack,
+ handleMenuPress,
enterSelectMode,
+ showItemCover,
+ }),
+ [
+ selectMode,
+ selected,
+ toggle,
+ playTrack,
handleMenuPress,
+ enterSelectMode,
showItemCover,
],
)
- const keyExtractor = useCallback((item: BilibiliTrack) => {
- return String(item.id)
- }, [])
-
return (
<>
}
ListHeaderComponent={ListHeaderComponent}
diff --git a/src/features/playlist/remote/hooks/useCheckLinkedToLocalPlaylist.ts b/src/features/playlist/remote/hooks/useCheckLinkedToLocalPlaylist.ts
index 192532ad..3b951800 100644
--- a/src/features/playlist/remote/hooks/useCheckLinkedToLocalPlaylist.ts
+++ b/src/features/playlist/remote/hooks/useCheckLinkedToLocalPlaylist.ts
@@ -1,6 +1,6 @@
import { playlistService } from '@/lib/services/playlistService'
import type { Playlist } from '@/types/core/media'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import { useEffect, useState } from 'react'
/**
diff --git a/src/hooks/mutations/bilibili/favorite.ts b/src/hooks/mutations/bilibili/favorite.ts
index f68a05e8..abf98a5f 100644
--- a/src/hooks/mutations/bilibili/favorite.ts
+++ b/src/hooks/mutations/bilibili/favorite.ts
@@ -1,7 +1,8 @@
import { favoriteListQueryKeys } from '@/hooks/queries/bilibili/favorite'
import { bilibiliApi } from '@/lib/api/bilibili/api'
import { BilibiliApiError } from '@/lib/errors/thirdparty/bilibili'
-import log, { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log from '@/utils/log'
import { returnOrThrowAsync } from '@/utils/neverthrow-utils'
import toast from '@/utils/toast'
import { useMutation, useQueryClient } from '@tanstack/react-query'
diff --git a/src/hooks/mutations/bilibili/video.ts b/src/hooks/mutations/bilibili/video.ts
index 0361e137..ffbda341 100644
--- a/src/hooks/mutations/bilibili/video.ts
+++ b/src/hooks/mutations/bilibili/video.ts
@@ -1,7 +1,7 @@
import { videoDataQueryKeys } from '@/hooks/queries/bilibili/video'
import { bilibiliApi } from '@/lib/api/bilibili/api'
import { queryClient } from '@/lib/config/queryClient'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import { returnOrThrowAsync } from '@/utils/neverthrow-utils'
import toast from '@/utils/toast'
import { useMutation } from '@tanstack/react-query'
diff --git a/src/hooks/mutations/db/playlist.ts b/src/hooks/mutations/db/playlist.ts
index a9b007fb..b26f982b 100644
--- a/src/hooks/mutations/db/playlist.ts
+++ b/src/hooks/mutations/db/playlist.ts
@@ -7,7 +7,7 @@ import type { Playlist } from '@/types/core/media'
import type { CreateArtistPayload } from '@/types/services/artist'
import type { UpdatePlaylistPayload } from '@/types/services/playlist'
import type { CreateTrackPayload } from '@/types/services/track'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import toast from '@/utils/toast'
import { useMutation } from '@tanstack/react-query'
diff --git a/src/hooks/stores/usePlayerStore.ts b/src/hooks/stores/usePlayerStore.ts
index e11f6158..0fe8085d 100644
--- a/src/hooks/stores/usePlayerStore.ts
+++ b/src/hooks/stores/usePlayerStore.ts
@@ -12,11 +12,8 @@ import type {
} from '@/types/core/playerStore'
import { ProjectScope } from '@/types/core/scope'
import type { RNTPTrack } from '@/types/rntp'
-import log, {
- flatErrorMessage,
- reportErrorToSentry,
- toastAndLogError,
-} from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log, { flatErrorMessage, reportErrorToSentry } from '@/utils/log'
import { zustandStorage } from '@/utils/mmkv'
import {
checkAndUpdateAudioStream,
diff --git a/src/lib/config/queryClient.ts b/src/lib/config/queryClient.ts
index e3e2c2b7..8da493e0 100644
--- a/src/lib/config/queryClient.ts
+++ b/src/lib/config/queryClient.ts
@@ -1,7 +1,7 @@
import { useModalStore } from '@/hooks/stores/useModalStore'
import { ThirdPartyError } from '@/lib/errors'
import { BilibiliApiError } from '@/lib/errors/thirdparty/bilibili'
-import { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
import toast from '@/utils/toast'
import * as Sentry from '@sentry/react-native'
import { QueryCache, QueryClient } from '@tanstack/react-query'
diff --git a/src/lib/player/playerLogic.ts b/src/lib/player/playerLogic.ts
index 5ef3a877..5fedd2c1 100644
--- a/src/lib/player/playerLogic.ts
+++ b/src/lib/player/playerLogic.ts
@@ -1,6 +1,7 @@
import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import { ProjectScope } from '@/types/core/scope'
-import log, { reportErrorToSentry, toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log, { reportErrorToSentry } from '@/utils/log'
import toast from '@/utils/toast'
import TrackPlayer, {
AppKilledPlaybackBehavior,
diff --git a/src/lib/services/lyricService.ts b/src/lib/services/lyricService.ts
index b7ffca53..e93cec38 100644
--- a/src/lib/services/lyricService.ts
+++ b/src/lib/services/lyricService.ts
@@ -4,7 +4,8 @@ import type { CustomError } from '@/lib/errors'
import { DataParsingError, FileSystemError } from '@/lib/errors'
import type { BilibiliTrack, Track } from '@/types/core/media'
import type { LyricSearchResult, ParsedLrc } from '@/types/player/lyrics'
-import log, { toastAndLogError } from '@/utils/log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log from '@/utils/log'
import * as FileSystem from 'expo-file-system'
import { errAsync, okAsync, Result, ResultAsync } from 'neverthrow'
diff --git a/src/types/flashlist.ts b/src/types/flashlist.ts
new file mode 100644
index 00000000..69d48d0d
--- /dev/null
+++ b/src/types/flashlist.ts
@@ -0,0 +1,8 @@
+import type { ListRenderItemInfo } from '@shopify/flash-list'
+
+export type ListRenderItemInfoWithExtraData = Omit<
+ ListRenderItemInfo,
+ 'extraData'
+> & {
+ extraData?: TExtraData
+}
diff --git a/src/utils/error-handling.ts b/src/utils/error-handling.ts
new file mode 100644
index 00000000..4e449b2d
--- /dev/null
+++ b/src/utils/error-handling.ts
@@ -0,0 +1,41 @@
+import { CustomError } from '@/lib/errors'
+import log, { flatErrorMessage } from './log'
+import toast from './toast'
+
+/**
+ * 将错误消息和错误堆栈信息显示在 toast 上,并将错误信息记录到日志中(用于最顶端的调用者消费错误)
+ * @param error 原始错误对象
+ * @param message 需要显示的信息
+ * @param scope 日志作用域
+ */
+export function toastAndLogError(
+ message: string,
+ error: unknown,
+ scope: string,
+) {
+ if (error instanceof CustomError) {
+ toast.error(`${message} -- ${error.type}`, {
+ description: flatErrorMessage(error),
+ duration: Number.POSITIVE_INFINITY,
+ })
+ log
+ .extend(scope)
+ .error(`${message} -- ${error.type}: ${flatErrorMessage(error)}`)
+ } else if (error instanceof Error) {
+ toast.error(message, {
+ description: flatErrorMessage(error),
+ duration: Number.POSITIVE_INFINITY,
+ })
+ log.extend(scope).error(`${message}: ${flatErrorMessage(error)}`)
+ } else if (error === undefined) {
+ toast.error(message, {
+ duration: Number.POSITIVE_INFINITY,
+ })
+ } else {
+ toast.error(message, {
+ description: String(error as unknown),
+ duration: Number.POSITIVE_INFINITY,
+ })
+ log.extend(scope).error(`${message}`, error)
+ }
+}
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 1453921b..787a3285 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -9,7 +9,6 @@ import {
logger,
mapConsoleTransport,
} from 'react-native-logs'
-import toast from './toast'
const isDev = __DEV__
@@ -164,44 +163,6 @@ export function reportErrorToSentry(
log.error(`已上报错误到 sentry,id: ${id}`)
}
-/**
- * 将错误消息和错误堆栈信息显示在 toast 上,并将错误信息记录到日志中(用于最顶端的调用者消费错误)
- * @param error 原始错误对象
- * @param message 需要显示的信息
- * @param scope 日志作用域
- */
-export function toastAndLogError(
- message: string,
- error: unknown,
- scope: string,
-) {
- if (error instanceof CustomError) {
- toast.error(`${message} -- ${error.type}`, {
- description: flatErrorMessage(error),
- duration: Number.POSITIVE_INFINITY,
- })
- log
- .extend(scope)
- .error(`${message} -- ${error.type}: ${flatErrorMessage(error)}`)
- } else if (error instanceof Error) {
- toast.error(message, {
- description: flatErrorMessage(error),
- duration: Number.POSITIVE_INFINITY,
- })
- log.extend(scope).error(`${message}: ${flatErrorMessage(error)}`)
- } else if (error === undefined) {
- toast.error(message, {
- duration: Number.POSITIVE_INFINITY,
- })
- } else {
- toast.error(message, {
- description: String(error as unknown),
- duration: Number.POSITIVE_INFINITY,
- })
- log.extend(scope).error(`${message}`, error)
- }
-}
-
try {
new EXPOFS.Directory(EXPOFS.Paths.document, 'logs').create({
intermediates: true,
diff --git a/src/utils/lyrics.ts b/src/utils/lyrics.ts
index 69e7d901..d2c54279 100644
--- a/src/utils/lyrics.ts
+++ b/src/utils/lyrics.ts
@@ -1,5 +1,6 @@
import type { ParsedLrc } from '@/types/player/lyrics'
-import log, { toastAndLogError } from './log'
+import { toastAndLogError } from '@/utils/error-handling'
+import log from './log'
const logger = log.extend('Utils.Lyrics')
diff --git a/src/utils/search.ts b/src/utils/search.ts
index 4b93f8fb..0e243d9a 100644
--- a/src/utils/search.ts
+++ b/src/utils/search.ts
@@ -1,7 +1,8 @@
import { bilibiliApi } from '@/lib/api/bilibili/api'
import { av2bv } from '@/lib/api/bilibili/utils'
import type { Router } from 'expo-router'
-import log, { toastAndLogError } from './log'
+import { toastAndLogError } from './error-handling'
+import log from './log'
import toast from './toast'
const logger = log.extend('Utils.Search')
From ec8d86a3b06deba3a777a466a0edf4f9a2c71b88 Mon Sep 17 00:00:00 2001
From: Roitium <65794453+roitium@users.noreply.github.com>
Date: Sun, 2 Nov 2025 08:40:23 +0800
Subject: [PATCH 4/5] chore: update pnpm-lock
---
pnpm-lock.yaml | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7cf47a18..3f3d5f97 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,11 +4,6 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
-patchedDependencies:
- react-native-paper:
- hash: 6bf0fca413003fb3ebd05efefeaf4290e6a75a6d6ac589187fac82144ac075b4
- path: patches/react-native-paper.patch
-
importers:
.:
@@ -243,7 +238,7 @@ importers:
version: 6.9.1(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
react-native-paper:
specifier: ^5.14.5
- version: 5.14.5(patch_hash=6bf0fca413003fb3ebd05efefeaf4290e6a75a6d6ac589187fac82144ac075b4)(react-native-safe-area-context@5.6.1(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
+ version: 5.14.5(react-native-safe-area-context@5.6.1(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
react-native-qrcode-svg:
specifier: ^6.3.15
version: 6.3.15(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
@@ -14048,7 +14043,7 @@ snapshots:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0)
- react-native-paper@5.14.5(patch_hash=6bf0fca413003fb3ebd05efefeaf4290e6a75a6d6ac589187fac82144ac075b4)(react-native-safe-area-context@5.6.1(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0):
+ react-native-paper@5.14.5(react-native-safe-area-context@5.6.1(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.13)(react@19.1.0))(react@19.1.0):
dependencies:
'@callstack/react-theme-provider': 3.0.9(react@19.1.0)
color: 3.2.1
From d4e64949bae898fc357f1040ad79e47ad6cbb9bd Mon Sep 17 00:00:00 2001
From: Roitium <65794453+roitium@users.noreply.github.com>
Date: Sun, 2 Nov 2025 08:51:14 +0800
Subject: [PATCH 5/5] fix: it
---
src/app/leaderboard.tsx | 6 +++---
src/lib/player/playerLogic.ts | 7 +++++++
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/app/leaderboard.tsx b/src/app/leaderboard.tsx
index 6101d292..89e4f9d3 100644
--- a/src/app/leaderboard.tsx
+++ b/src/app/leaderboard.tsx
@@ -1,10 +1,10 @@
import NowPlayingBar from '@/components/NowPlayingBar'
import { LeaderBoardListItem } from '@/features/leaderboard/LeaderBoardItem'
-import useCurrentTrack from '@/hooks/player/useCurrentTrack'
import {
usePlayCountLeaderBoardPaginated,
useTotalPlaybackDuration,
} from '@/hooks/queries/db/track'
+import { usePlayerStore } from '@/hooks/stores/usePlayerStore'
import type { Track } from '@/types/core/media'
import { FlashList } from '@shopify/flash-list'
import { useRouter } from 'expo-router'
@@ -57,7 +57,7 @@ export default function LeaderBoardPage() {
const { colors } = useTheme()
const router = useRouter()
const insets = useSafeAreaInsets()
- const currentTrack = useCurrentTrack()
+ const haveTrack = usePlayerStore((state) => !!state.currentTrackUniqueKey)
const {
data: leaderBoardData,
@@ -126,7 +126,7 @@ export default function LeaderBoardPage() {
renderItem={renderItem}
keyExtractor={keyExtractor}
contentContainerStyle={{
- paddingBottom: currentTrack ? 70 + insets.bottom : insets.bottom,
+ paddingBottom: haveTrack ? 70 + insets.bottom : insets.bottom,
}}
onEndReached={onEndReached}
onEndReachedThreshold={0.8}
diff --git a/src/lib/player/playerLogic.ts b/src/lib/player/playerLogic.ts
index 5fedd2c1..73581595 100644
--- a/src/lib/player/playerLogic.ts
+++ b/src/lib/player/playerLogic.ts
@@ -24,6 +24,7 @@ const initPlayer = async () => {
}
let isResettingSleepTimer = false
+let listenersAttached = false
const PlayerLogic = {
// 初始化播放器
@@ -71,6 +72,10 @@ const PlayerLogic = {
// 设置事件监听器
setupEventListeners(): void {
+ if (listenersAttached) {
+ logger.debug('事件监听器已经设置过了,跳过。')
+ return // 关键!防止重复绑定
+ }
// 监听播放状态变化
TrackPlayer.addEventListener(
Event.PlaybackState,
@@ -245,6 +250,8 @@ const PlayerLogic = {
})
}
})
+
+ listenersAttached = true
},
}