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} - - ) -}