diff --git a/package-lock.json b/package-lock.json index 8905ad6..cd6959a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@sentry/react-native": "5.19.1", "@shopify/flash-list": "1.6.3", "@total-typescript/ts-reset": "^0.5.1", + "clsx": "^2.1.0", "expo": "~50.0.8", "expo-application": "~5.8.3", "expo-asset": "~9.0.2", @@ -45,6 +46,7 @@ "react-native-webview": "13.6.4", "spark-md5": "^3.0.2", "swr": "^2.2.4", + "throttle-debounce": "^5.0.0", "zod": "^3.21.4" }, "devDependencies": { @@ -54,6 +56,7 @@ "@types/he": "^1.2.3", "@types/react": "~18.2.14", "@types/spark-md5": "^3.0.4", + "@types/throttle-debounce": "^5.0.2", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", "babel-plugin-transform-remove-console": "^6.9.4", @@ -7415,6 +7418,12 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, + "node_modules/@types/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==", + "dev": true + }, "node_modules/@types/which": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.3.tgz", @@ -9030,6 +9039,14 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -18825,6 +18842,14 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, + "node_modules/throttle-debounce": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz", + "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==", + "engines": { + "node": ">=12.22" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index 66fae1c..651294c 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,10 @@ "@sentry/react-native": "5.19.1", "@shopify/flash-list": "1.6.3", "@total-typescript/ts-reset": "^0.5.1", + "clsx": "^2.1.0", "expo": "~50.0.8", "expo-application": "~5.8.3", + "expo-asset": "~9.0.2", "expo-background-fetch": "~11.8.1", "expo-clipboard": "~5.0.1", "expo-constants": "~15.4.5", @@ -67,8 +69,8 @@ "react-native-webview": "13.6.4", "spark-md5": "^3.0.2", "swr": "^2.2.4", - "zod": "^3.21.4", - "expo-asset": "~9.0.2" + "throttle-debounce": "^5.0.0", + "zod": "^3.21.4" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -77,6 +79,7 @@ "@types/he": "^1.2.3", "@types/react": "~18.2.14", "@types/spark-md5": "^3.0.4", + "@types/throttle-debounce": "^5.0.2", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", "babel-plugin-transform-remove-console": "^6.9.4", diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index 76f8199..2a2e4e8 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -1,6 +1,8 @@ import { useNavigation, useRoute } from '@react-navigation/native' import { NativeStackScreenProps } from '@react-navigation/native-stack' import { Button, Text } from '@rneui/themed' +import clsx from 'clsx' +import * as Clipboard from 'expo-clipboard' import React from 'react' import { Image, Linking, View } from 'react-native' @@ -9,7 +11,7 @@ import { colors } from '@/constants/colors.tw' import type { CommentItemType, CommentMessageContent } from '../api/comments' import { useStore } from '../store' import type { NavigationProps, RootStackParamList } from '../types' -import { parseImgUrl } from '../utils' +import { parseImgUrl, showToast } from '../utils' function CommentText(props: { nodes: CommentMessageContent @@ -39,7 +41,10 @@ function CommentText(props: { return ( { navigation.push('Dynamic', { user: { @@ -59,10 +64,15 @@ function CommentText(props: { { + Clipboard.setStringAsync(node.url).then(() => { + showToast('已复制链接') + }) + }} onPress={() => { Linking.openURL(node.url) }}> - {node.url} + {'🔗 链接 '} ) } @@ -108,7 +118,7 @@ function CommentText(props: { ) } return ( - + {node.text} ) @@ -117,8 +127,9 @@ function CommentText(props: { ) } -function CommentItem(props: { +export function CommentItem(props: { comment: CommentItemType | CommentItemType['replies'][0] + smallFont?: boolean }) { const { comment } = props const { setImagesList, setCurrentImageIndex } = useStore() @@ -135,15 +146,15 @@ function CommentItem(props: { : route.params.name : '' const navigation = useNavigation() - const fontSize = 'rcount' in comment ? 'text-base' : 'text-sm' + const fontSize = props.smallFont ? 'text-sm' : 'text-base' return ( { navigation.push('Dynamic', { user: { @@ -160,7 +171,9 @@ function CommentItem(props: { {comment.sex === '男' ? '♂:' : comment.sex === '女' ? '♀:' : ':'} {'top' in comment && comment.top ? ( - 🔝 + + {' 置顶 '} + ) : null} {comment.message?.length ? ( {comment.replies?.length ? ( {comment.replies.map(reply => { - return + return })} {comment.moreText && comment.rcount > comment.replies.length ? ( - ) : null} ) } diff --git a/src/routes/Follow/FollowItem.tsx b/src/routes/Follow/FollowItem.tsx index f5eb760..870e39a 100644 --- a/src/routes/Follow/FollowItem.tsx +++ b/src/routes/Follow/FollowItem.tsx @@ -192,30 +192,6 @@ function FollowItem(props: { item: UpInfo; index?: number }) { /> ) : null} - {/* {livingUps[mid] ? ( - - ) : ( - - {name} - - )} */} - 暂无更多(只记录最近400条) + 暂无更多(最近约400条) ) : null } diff --git a/src/routes/Play/VideoHeader.tsx b/src/routes/Play/VideoHeader.tsx index f690210..31101f2 100644 --- a/src/routes/Play/VideoHeader.tsx +++ b/src/routes/Play/VideoHeader.tsx @@ -4,6 +4,7 @@ import { useRoute, } from '@react-navigation/native' import { Avatar, Icon, Text } from '@rneui/themed' +import clsx from 'clsx' import { Image } from 'expo-image' import React from 'react' import { Alert, Linking, Pressable, TouchableOpacity, View } from 'react-native' @@ -161,7 +162,12 @@ function VideoHeader() { } /> + className={clsx( + 'text-sm', + isCollected + ? [colors.warning.text, 'font-bold'] + : colors.gray8.text, + )}> {parseNumber(videoInfo?.collectNum)} diff --git a/src/routes/Play/VideoInfo.tsx b/src/routes/Play/VideoInfo.tsx index c68bb53..0f874c6 100644 --- a/src/routes/Play/VideoInfo.tsx +++ b/src/routes/Play/VideoInfo.tsx @@ -81,7 +81,7 @@ function VideoInfo(props: { ) : null} {!isLoading && videoInfo?.interactive ? ( - + 【该视频为交互视频,暂不支持】 ) : null} diff --git a/src/routes/Play/inject-play.js b/src/routes/Play/inject-play.js index e9947e9..0ed3a70 100644 --- a/src/routes/Play/inject-play.js +++ b/src/routes/Play/inject-play.js @@ -96,7 +96,7 @@ function __$hack() { } const style = document.createElement('style') style.textContent = ` - body, #bilibiliPlayer, .mplayer { + html, body, #bilibiliPlayer, .mplayer { background-color: black!important; } .b-danmaku { @@ -128,32 +128,6 @@ function __$hack() { }) }) waitForDom('.mplayer-right', right => { - // if (!document.getElementById('download-button')) { - // const downloadBtn = document.createElement('div') - // downloadBtn.id = 'download-button' - // // downloadBtn.innerHTML = '⇓' - // downloadBtn.innerHTML = '⇩' - // downloadBtn.style.cssText = ` - // width: 24px; - // height: 24px; - // color: white; - // font-size: 20px; - // line-height: 28px; - // text-align: center; - // background: rgba(0,0,0,.2); - // border-radius: 50%; - // font-weight: 500; - // ` - // downloadBtn.addEventListener('click', () => { - // window.ReactNativeWebView.postMessage( - // JSON.stringify({ - // action: 'downloadVideo', - // payload: null, - // }), - // ) - // }) - // right.appendChild(downloadBtn) - // } if (!document.getElementById('play-rate-button')) { const rateBtn = document.createElement('div') rateBtn.id = 'play-rate-button' diff --git a/src/routes/Play/update-playurl.js b/src/routes/Play/update-playurl.js index dee4c1a..6e13295 100644 --- a/src/routes/Play/update-playurl.js +++ b/src/routes/Play/update-playurl.js @@ -33,10 +33,10 @@ function __$hack() { // }), // ) video.load() - setTimeout(() => { - video.muted = false - video.play() - }, 10) + // setTimeout(() => { + video.muted = false + video.play() + // }, 10) } } window.setNewVideoUrl = function (url) { @@ -48,6 +48,7 @@ function __$hack() { evt => { const video = evt.target if (video && video.tagName === 'VIDEO') { + video.pause() replacePlayUrl() } }, diff --git a/src/routes/VideoList/VideoItem.tsx b/src/routes/VideoList/VideoItem.tsx index 3c5792f..b3de58b 100644 --- a/src/routes/VideoList/VideoItem.tsx +++ b/src/routes/VideoList/VideoItem.tsx @@ -1,4 +1,5 @@ import { Icon, Text } from '@rneui/themed' +import clsx from 'clsx' import { Image } from 'expo-image' import React from 'react' import { View } from 'react-native' @@ -54,14 +55,20 @@ function VideoItem({ video }: { video: VideoItemType }) { + className={clsx( + 'text-white text-xs', + isBlackTag && 'line-through opacity-60', + )}> {video.tag} ) : null} {video.title} @@ -83,11 +90,12 @@ function VideoItem({ video }: { video: VideoItemType }) { + ? ['font-bold', colors.secondary.text] + : colors.primary.text, + )}> {video.name} diff --git a/src/utils/index.ts b/src/utils/index.ts index c1ae818..405b832 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ import * as Updates from 'expo-updates' import { Alert, Linking, Platform, Share, ToastAndroid } from 'react-native' import Toast from 'react-native-root-toast' +import { throttle } from 'throttle-debounce' import { checkUpdate } from '@/api/check-update' @@ -120,22 +121,27 @@ export function delay(ms: number) { }) } -const showedMessage: Record void> = {} - +const toastFuncMap: Record void> = {} export function showToast(message: string, long = false) { - if (!(message in showedMessage)) { - showedMessage[message] = () => { - Platform.OS === 'android' - ? ToastAndroid.show( - message, - long ? ToastAndroid.LONG : ToastAndroid.SHORT, - ) - : Toast.show(message, { - duration: long ? Toast.durations.LONG : Toast.durations.SHORT, - }) - } + if (!(message in toastFuncMap)) { + toastFuncMap[message] = throttle( + 5000, + () => { + Platform.OS === 'android' + ? ToastAndroid.show( + message, + long ? ToastAndroid.LONG : ToastAndroid.SHORT, + ) + : Toast.show(message, { + duration: long ? Toast.durations.LONG : Toast.durations.SHORT, + }) + }, + { + noLeading: false, + }, + ) } - showedMessage[message]() + toastFuncMap[message]() } let showedFatalError = false