From 1404db8a49e2aec1c08c301b451ab37e38ee4dae Mon Sep 17 00:00:00 2001
From: tingyuan <1932294867@qq.com>
Date: Mon, 11 Nov 2024 14:09:01 +0800
Subject: [PATCH] -
---
.eslintrc.js | 3 +-
package-lock.json | 65 +++++++++++++++++
package.json | 1 +
src/components/ButtonsOverlay.tsx | 2 +-
src/components/CheckLiveUps.tsx | 66 +++++++----------
src/components/ImagesView.tsx | 116 ++++++++++++++++++++----------
src/components/ReplyList.tsx | 12 ++--
src/hooks/useAppState.ts | 6 +-
src/hooks/useBackgroundTask.ts | 53 --------------
src/hooks/useDataChange.ts | 13 ----
src/hooks/useLatest.ts | 10 +++
src/hooks/useMounted.ts | 1 +
src/routes/Music/Player.tsx | 20 ++----
src/routes/Music/RotateImg.tsx | 45 ------------
src/routes/Music/index.tsx | 11 +--
src/routes/VideoList/Header.tsx | 10 ++-
src/routes/VideoList/Test.tsx | 34 ---------
17 files changed, 204 insertions(+), 264 deletions(-)
delete mode 100644 src/hooks/useBackgroundTask.ts
delete mode 100644 src/hooks/useDataChange.ts
create mode 100644 src/hooks/useLatest.ts
delete mode 100644 src/routes/Music/RotateImg.tsx
delete mode 100644 src/routes/VideoList/Test.tsx
diff --git a/.eslintrc.js b/.eslintrc.js
index d41993e7..fde89b04 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -5,7 +5,7 @@ module.exports = {
},
extends: ['@react-native'],
ignorePatterns: ['docs/**'],
- plugins: ['sonarjs', 'simple-import-sort'],
+ plugins: ['sonarjs', 'simple-import-sort', 'eslint-plugin-react-compiler'],
parserOptions: {
ecmaVersion: 2022,
},
@@ -19,6 +19,7 @@ module.exports = {
'@typescript-eslint/no-import-type-side-effects': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
+ 'react-compiler/react-compiler': 'error',
},
overrides: [
{
diff --git a/package-lock.json b/package-lock.json
index 34ad5c45..949c1194 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -73,6 +73,7 @@
"download-cli": "^1.1.1",
"eslint": "^8.37.0",
"eslint-plugin-prettier": "^5.1.2",
+ "eslint-plugin-react-compiler": "^19.0.0-beta-6fc168f-20241025",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-sonarjs": "^0.24.0",
"open": "8.4.2",
@@ -801,6 +802,23 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-proposal-private-methods": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
+ "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
@@ -11690,6 +11708,53 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
+ "node_modules/eslint-plugin-react-compiler": {
+ "version": "19.0.0-beta-6fc168f-20241025",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-6fc168f-20241025.tgz",
+ "integrity": "sha512-mHn5tYt9dT4GiXHF5muiz6p+4Lirgi0Oc87N2KrbB/ciSkT+VZ8iJA+6bbS4//ljYzYbxBbPMHWS/dZWhQrbpQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "@babel/plugin-proposal-private-methods": "^7.18.6",
+ "hermes-parser": "^0.20.1",
+ "zod": "^3.22.4",
+ "zod-validation-error": "^3.0.3"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=7"
+ }
+ },
+ "node_modules/eslint-plugin-react-compiler/node_modules/hermes-estree": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz",
+ "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==",
+ "dev": true
+ },
+ "node_modules/eslint-plugin-react-compiler/node_modules/hermes-parser": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz",
+ "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==",
+ "dev": true,
+ "dependencies": {
+ "hermes-estree": "0.20.1"
+ }
+ },
+ "node_modules/eslint-plugin-react-compiler/node_modules/zod-validation-error": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz",
+ "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.18.0"
+ }
+ },
"node_modules/eslint-plugin-react-hooks": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
diff --git a/package.json b/package.json
index 0fea715d..e173e902 100644
--- a/package.json
+++ b/package.json
@@ -98,6 +98,7 @@
"download-cli": "^1.1.1",
"eslint": "^8.37.0",
"eslint-plugin-prettier": "^5.1.2",
+ "eslint-plugin-react-compiler": "^19.0.0-beta-6fc168f-20241025",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-sonarjs": "^0.24.0",
"open": "8.4.2",
diff --git a/src/components/ButtonsOverlay.tsx b/src/components/ButtonsOverlay.tsx
index f0717d49..74f508ff 100644
--- a/src/components/ButtonsOverlay.tsx
+++ b/src/components/ButtonsOverlay.tsx
@@ -38,7 +38,7 @@ function ButtonsOverlay() {
0}
ModalComponent={Modal2}
- overlayStyle={tw(`px-0 py-3 min-w-[70%] ${colors.gray2.bg}`)}
+ overlayStyle={tw(`px-0 py-3 min-w-[70%] max-w-[90%] ${colors.gray2.bg}`)}
onBackdropPress={dismiss}>
{Buttons}
diff --git a/src/components/CheckLiveUps.tsx b/src/components/CheckLiveUps.tsx
index 6289cbce..a5661442 100644
--- a/src/components/CheckLiveUps.tsx
+++ b/src/components/CheckLiveUps.tsx
@@ -3,15 +3,9 @@ import { Vibration } from 'react-native'
import useSWR from 'swr'
import type { z } from 'zod'
-import useBackgroundTask from '@/hooks/useBackgroundTask'
-import useMemoizedFn from '@/hooks/useMemoizedFn'
-
-import request from '../api/fetcher'
import type { LiveInfoBatchItemSchema } from '../api/living-info.schema'
import { useStore } from '../store'
-let prevLivingMap = {}
-
type LivingUpsData = Record>
const useCheckLivingUps = (time?: number) => {
@@ -20,46 +14,34 @@ const useCheckLivingUps = (time?: number) => {
const url = uids
? `https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids?${uids}&_t=${checkLiveTimeStamp}`
: null
- const checkLivingUps = (data: LivingUpsData) => {
- if (!data) {
- return
- }
- const livingMap: Record = {}
- Object.keys(data).forEach((mid) => {
- // https://live.bilibili.com/h5/24446464
- const { live_status, room_id } = data[mid]
- if (live_status === 1) {
- livingMap[mid] = `https://live.bilibili.com/h5/${room_id}`
- }
- })
- let notVibrate = true
- for (const id in livingMap) {
- if (!(id in prevLivingMap) && notVibrate) {
- Vibration.vibrate(900)
- notVibrate = false
- break
- }
- }
- prevLivingMap = livingMap
- setLivingUps(livingMap)
- }
- useBackgroundTask(
- 'CheckLivingUps',
- useMemoizedFn(() => {
- if (url) {
- request(url).then((data) => {
- checkLivingUps(data)
- })
- }
- return null
- }),
- )
+ const prevLivingMapRef = React.useRef({})
+
useSWR(url, {
refreshInterval: time || 5 * 60 * 1000,
errorRetryCount: 2,
refreshWhenHidden: true, // 在移动端后台刷新不能保证会一直按时执行
- onSuccess(data) {
- checkLivingUps(data)
+ onSuccess: (data: LivingUpsData) => {
+ if (!data) {
+ return
+ }
+ const livingMap: Record = {}
+ Object.keys(data).forEach((mid) => {
+ // https://live.bilibili.com/h5/24446464
+ const { live_status, room_id } = data[mid]
+ if (live_status === 1) {
+ livingMap[mid] = `https://live.bilibili.com/h5/${room_id}`
+ }
+ })
+ let notVibrate = true
+ for (const id in livingMap) {
+ if (!(id in prevLivingMapRef.current) && notVibrate) {
+ Vibration.vibrate(900)
+ notVibrate = false
+ break
+ }
+ }
+ prevLivingMapRef.current = livingMap
+ setLivingUps(livingMap)
},
})
}
diff --git a/src/components/ImagesView.tsx b/src/components/ImagesView.tsx
index 5133d193..2b973bc9 100644
--- a/src/components/ImagesView.tsx
+++ b/src/components/ImagesView.tsx
@@ -1,9 +1,16 @@
import { Overlay } from '@rneui/themed'
import { Image } from 'expo-image'
import React from 'react'
-import { Linking, Text, useWindowDimensions, View } from 'react-native'
+import {
+ Linking,
+ ScrollView,
+ Text,
+ useWindowDimensions,
+ View,
+} from 'react-native'
import PagerView from 'react-native-pager-view'
+import useLatest from '@/hooks/useLatest'
import { parseImgUrl } from '@/utils'
import { useStore } from '../store'
@@ -38,9 +45,76 @@ function ImagesView() {
Linking.openURL(img.split('@')[0])
}
// avoid view pager render all pages at same time.
- const imageCompCache = React.useRef<
+ const imageCompCache = useLatest<
Record>
>({})
+ // const imageCompCache = React.useRef<
+ // Record>
+ // >({})
+ const imageNodes = React.useMemo(() => {
+ return images.map((v, i) => {
+ let imgWidth = Math.min(width, v.width)
+ let imgHeight = (imgWidth * v.height) / v.width
+ let overflow = false
+ if (imgHeight > height) {
+ imgWidth = width
+ imgHeight = (width * v.height) / v.width
+ overflow = true
+ // imgHeight = Math.min(height, v.height)
+ // imgWidth = (v.width * imgHeight) / v.height
+ }
+ let imageView = null
+ if (imageCompCache.current[v.url]) {
+ imageView = imageCompCache.current[v.url]
+ } else if (current === i) {
+ Object.assign(imageCompCache.current, {
+ [v.url]: (
+
+ ),
+ })
+ imageView = imageCompCache.current[v.url]
+ } else {
+ imageView = (
+
+ )
+ }
+ // if (overflow) {
+ // return (
+ //
+ // {imageView}
+ //
+ // )
+ // }
+ return (
+
+ {overflow ? (
+
+ {imageView}
+
+ ) : (
+ imageView
+ )}
+
+ )
+ })
+ }, [images, width, height, imageCompCache, current])
return (
0}
@@ -74,43 +148,7 @@ function ImagesView() {
offscreenPageLimit={1}
className="flex-1"
initialPage={currentImageIndex}>
- {images.map((v, i) => {
- let imgWidth = Math.min(width, v.width)
- let imgHeight = (imgWidth * v.height) / v.width
- if (imgHeight > height) {
- imgHeight = Math.min(height, v.height)
- imgWidth = (v.width * imgHeight) / v.height
- }
- let imageView = null
- if (imageCompCache.current[v.url]) {
- imageView = imageCompCache.current[v.url]
- } else if (current === i) {
- imageCompCache.current[v.url] = (
-
- )
- imageView = imageCompCache.current[v.url]
- } else {
- imageView = (
-
- )
- }
- return (
-
- {imageView}
-
- )
- })}
+ {imageNodes}
diff --git a/src/components/ReplyList.tsx b/src/components/ReplyList.tsx
index 2636f29d..a0cb0c24 100644
--- a/src/components/ReplyList.tsx
+++ b/src/components/ReplyList.tsx
@@ -6,6 +6,7 @@ import { ActivityIndicator, View } from 'react-native'
import { type ReplyItemType, useReplies } from '@/api/replies'
import { colors } from '@/constants/colors.tw'
+import useMemoizedFn from '@/hooks/useMemoizedFn'
import { useStore } from '@/store'
import { CommentItem } from './Comment'
@@ -19,16 +20,11 @@ export default function ReplyList() {
update,
} = useReplies()
const { setRepliesInfo, repliesInfo } = useStore()
- const handleClose = () => {
+ const handleClose = useMemoizedFn(() => {
setRepliesInfo(null)
- }
+ })
- useFocusEffect(
- React.useCallback(() => {
- return handleClose
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []),
- )
+ useFocusEffect(handleClose)
return (
void) {
const currentState = AppState.currentState
const [appState, setAppState] = useState(currentState)
- const callbackRef = useRef(callback)
- callbackRef.current = callback
+ const callbackRef = useLatest(callback)
useMounted(() => {
function onChange(newState: AppStateStatus) {
diff --git a/src/hooks/useBackgroundTask.ts b/src/hooks/useBackgroundTask.ts
deleted file mode 100644
index 175aadde..00000000
--- a/src/hooks/useBackgroundTask.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as BackgroundFetch from 'expo-background-fetch'
-import * as TaskManager from 'expo-task-manager'
-import React from 'react'
-
-const TaskMap = {
- KeepMusicPlay: () => {},
- test: () => null,
- CheckLivingUps: () => null,
- KeepVideoPlay: () => null,
-}
-
-type TaskMapType = typeof TaskMap
-
-Object.keys(TaskMap).forEach((n) => {
- const name = n as keyof typeof TaskMap
- // 1. Define the task by providing a name and the function that should be executed
- // Note: This needs to be called in the global scope (e.g outside of your React components)
- TaskManager.defineTask(name, async () => {
- // const now = Date.now()
- // console.log(
- // `Got background fetch call at date: ${new Date(now).toISOString()}`,
- // )
- TaskMap[name]()
- // Be sure to return the successful result type!
- return BackgroundFetch.BackgroundFetchResult.NewData
- })
-})
-type CallbackType = T extends keyof TaskMapType
- ? TaskMapType[T]
- : never
-
-export default function useBackgroundTask(
- name: T,
- callback: CallbackType,
-) {
- TaskMap[name] = callback
- React.useEffect(() => {
- BackgroundFetch.unregisterTaskAsync(name)
- .catch(() => {
- //noop
- })
- .then(() => {
- BackgroundFetch.registerTaskAsync(name, {
- minimumInterval: 5, // 15 minutes
- stopOnTerminate: false, // android only,
- startOnBoot: true, // android only
- })
- })
- return () => {
- BackgroundFetch.unregisterTaskAsync(name)
- }
- }, [name])
-}
diff --git a/src/hooks/useDataChange.ts b/src/hooks/useDataChange.ts
deleted file mode 100644
index eb6390e4..00000000
--- a/src/hooks/useDataChange.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react'
-
-export default function useDataChange(
- callback: () => void,
- deps: React.DependencyList,
-) {
- const callbackRef = React.useRef(callback)
- callbackRef.current = callback
- React.useEffect(() => {
- return callbackRef.current?.()
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, deps)
-}
diff --git a/src/hooks/useLatest.ts b/src/hooks/useLatest.ts
new file mode 100644
index 00000000..346f6302
--- /dev/null
+++ b/src/hooks/useLatest.ts
@@ -0,0 +1,10 @@
+import { useRef } from 'react'
+
+function useLatest(value: T) {
+ const ref = useRef(value)
+ // eslint-disable-next-line react-compiler/react-compiler
+ ref.current = value
+ return ref
+}
+
+export default useLatest
diff --git a/src/hooks/useMounted.ts b/src/hooks/useMounted.ts
index 5a2b7da1..b2fb4c85 100644
--- a/src/hooks/useMounted.ts
+++ b/src/hooks/useMounted.ts
@@ -6,6 +6,7 @@ export default function useMounted(callback: () => void) {
if (typeof clean === 'function') {
return clean
}
+ // eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}
diff --git a/src/routes/Music/Player.tsx b/src/routes/Music/Player.tsx
index 7f444ea7..b2d890e3 100644
--- a/src/routes/Music/Player.tsx
+++ b/src/routes/Music/Player.tsx
@@ -1,21 +1,12 @@
import { Icon, Slider, Text } from '@rneui/themed'
import clsx from 'clsx'
-import {
- Audio,
- type AVPlaybackStatus,
- // InterruptionModeAndroid,
- // InterruptionModeIOS,
-} from 'expo-av'
-// import { Image } from 'expo-image'
+import { Audio, type AVPlaybackStatus } from 'expo-av'
import React, { useEffect } from 'react'
import { ImageBackground, TouchableOpacity, View } from 'react-native'
-// import { throttle } from 'throttle-debounce'
import { useAudioUrl } from '@/api/play-url'
import { UA } from '@/constants'
import { colors } from '@/constants/colors.tw'
-// import useBackgroundTask from '@/hooks/useBackgroundTask'
-import useDataChange from '@/hooks/useDataChange'
import useIsDark from '@/hooks/useIsDark'
import useMemoizedFn from '@/hooks/useMemoizedFn'
import { useStore } from '@/store'
@@ -90,7 +81,10 @@ function PlayerBar(props: { url?: string; time?: number; error?: boolean }) {
}
}, [playMode, nextSong, setPlayingSong]),
)
- useDataChange(() => {
+ const getPlayMode = useMemoizedFn(() => {
+ return playMode
+ })
+ React.useEffect(() => {
if (!props.url) {
return
}
@@ -111,7 +105,7 @@ function PlayerBar(props: { url?: string; time?: number; error?: boolean }) {
},
},
{
- isLooping: playMode === 'loop' || playMode === 'order',
+ isLooping: getPlayMode() === 'loop' || getPlayMode() === 'order',
shouldPlay: true,
},
handlePlayStatusChange,
@@ -130,7 +124,7 @@ function PlayerBar(props: { url?: string; time?: number; error?: boolean }) {
return () => {
stop()
}
- }, [props.url])
+ }, [props.url, handlePlayStatusChange, getPlayMode])
useEffect(() => {
return () => {
diff --git a/src/routes/Music/RotateImg.tsx b/src/routes/Music/RotateImg.tsx
deleted file mode 100644
index 20a2f0aa..00000000
--- a/src/routes/Music/RotateImg.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { useEffect, useRef } from 'react'
-import { Animated, Easing, type ImageProps, View } from 'react-native'
-
-const RotateImage = (props: ImageProps) => {
- const rotation = useRef(new Animated.Value(0))
-
- useEffect(() => {
- startRotation()
-
- return () => {
- stopRotation()
- }
- }, [])
-
- const startRotation = () => {
- Animated.loop(
- Animated.timing(rotation.current, {
- toValue: 1,
- duration: 2000,
- easing: Easing.linear,
- useNativeDriver: true,
- }),
- ).start()
- }
-
- const stopRotation = () => {
- rotation.current.stopAnimation()
- }
-
- const interpolatedRotation = rotation.current.interpolate({
- inputRange: [0, 1],
- outputRange: ['0deg', '360deg'],
- })
-
- return (
-
-
-
- )
-}
-
-export default RotateImage
diff --git a/src/routes/Music/index.tsx b/src/routes/Music/index.tsx
index a1517913..d560e76d 100644
--- a/src/routes/Music/index.tsx
+++ b/src/routes/Music/index.tsx
@@ -6,13 +6,7 @@ import clsx from 'clsx'
import * as Clipboard from 'expo-clipboard'
import { Image } from 'expo-image'
import React from 'react'
-import {
- Alert,
- ImageBackground,
- Linking,
- useWindowDimensions,
- View,
-} from 'react-native'
+import { Alert, ImageBackground, Linking, View } from 'react-native'
import type { SearchBarCommands } from 'react-native-screens'
import { colors } from '@/constants/colors.tw'
@@ -100,7 +94,6 @@ function MusicItem(props: {
},
]
}
- const { width } = useWindowDimensions()
if (props.type === 'brief') {
return (
@@ -117,7 +110,7 @@ function MusicItem(props: {
setOverlayButtons(buttons())
}}
ellipsizeMode="tail">
- {song.name}bjkkjljljl福建高考会考立刻就垃圾垃圾
+ {song.name}
{song.singer ? (
👤{song.singer}
diff --git a/src/routes/VideoList/Header.tsx b/src/routes/VideoList/Header.tsx
index c7561dfb..929e4ddc 100644
--- a/src/routes/VideoList/Header.tsx
+++ b/src/routes/VideoList/Header.tsx
@@ -19,7 +19,7 @@ import { useStore } from '../../store'
import type { NavigationProps } from '../../types'
function HeaderTitleComp() {
- const opacityValue = React.useRef(new Animated.Value(0)).current
+ const { current: opacityValue } = React.useRef(new Animated.Value(0))
const appUpdateInfo = useAppUpdateInfo()
useMounted(() => {
@@ -54,8 +54,12 @@ function HeaderTitleComp() {
onPress={() => {
Linking.openURL(appUpdateInfo.downloadLink!)
}}>
- 有新版本
-
+ {'有新版本'}
+
{
- // console.log(99)
- // const time = setInterval(() => {
- // setTime(getTime())
- // }, 1000)
- // }),
- // )
- // useMounted(() => {
- // console.log(99)
- // const timer = setInterval(() => {
- // setCount(c => c + 1)
- // }, 1000)
- // return () => clearInterval(timer)
- // })
- return (
-
- {time}: {count}
-
- )
-}