From 8ebf629121360b55902efd5404e8aa91a38d7945 Mon Sep 17 00:00:00 2001 From: tingyuan <1932294867@qq.com> Date: Sat, 13 Apr 2024 17:42:53 +0800 Subject: [PATCH] - --- assets/tv.png | Bin 1503 -> 0 bytes package-lock.json | 112 ++++++++++-- package.json | 6 +- src/components/CommentList.tsx | 13 +- src/components/ImagesView.tsx | 3 - src/routes/Collect/index.tsx | 35 +++- src/routes/History/index.tsx | 32 +++- src/routes/Play/Header.tsx | 5 +- src/routes/Play/Player.tsx | 32 ++-- src/routes/Play/VideoHeader.tsx | 195 -------------------- src/routes/Play/VideoInfo.tsx | 303 ++++++++++++++++++++++++++------ src/routes/Play/index.tsx | 29 ++- src/routes/Play/inject-play.js | 28 +-- src/routes/VideoList/Header.tsx | 4 +- src/routes/VideoList/Test.tsx | 51 +++++- 15 files changed, 534 insertions(+), 314 deletions(-) delete mode 100644 assets/tv.png delete mode 100644 src/routes/Play/VideoHeader.tsx diff --git a/assets/tv.png b/assets/tv.png deleted file mode 100644 index 69df9d86bb41c0b290b4c5773a4b2de4bf29f5ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1503 zcmV<51t9u~P)P)t-sM{rC4 z000320RR91000051qA^C0RR91|Ns9000000000001qB5I0|WH*^w`+gQc_X_0|Wm4 z{`&g*#l^+!?Cgh!hh=4Dv$L~}G&(;3000?uQchC<6(9@=5E=oq1t%>$G=^zQMyHoi zb5AZd?Vf9C000FlNkl5{7|5P)3*qZpT)KrZ< z{n3cf3t9G0{>lF>e^?Ia+(pFg98kGL@vR(Cxk_D9td0!ju0|w6>k_vzZNh*v)ndC$3?iMKg7Nj9jX!RM{ztV(BagKm;I;BGlwVg6Fl~+6K+%27}cU#u#w; zB5X-g4$sW*-;4prf*z6GC-@SG4{4gF^G2}_ZwIl)fi~b{Y21eupC5@kY(o8U6~iRyaD4;geUhQ z((7J1ggAf!N?#?C*>W64IwL7G8AWiY;($9DrmR&$nJ_1_Qzlk1Pl@4Tce2w4nAB4y zrAO{$))`vxv>0AEcQWV9S|yfM?xv-O5zh3fF|&3@&RVACOgdvWsdUmwjEY4xMmbZA z`+xK9$CH?`Fzz4>+g$>K9!dMbH656>K9pO|JJBMIWTZspJ6;M#AlU|MzPYtpfqyMob?MWaXp)c*z} zDNKt79hkW*7>JP|tdgLwi~AA=A~8*Znd7N3!Wk5D+L@bEV;DrQ5ch&ZyW7H53?u}3 zZX6Pvoo(S722u-RauUrEpe`{6k|=eiTaMW0cw&rBUJysUpDlMTV9Yis&Rm@`!uye! zCr^l>CLS^YHwDZvR_VP_`BquNaub+7<$~VwW^b>)`)*3}ZuYme8w(n*v}I1db>i=a zXiCqk-(Ya@VI6;Q-k?2A)mXaUoStZhrbom2d*@AYG*r8OdJ0(&rw%t_f#pOPeLb@2 z&m;v=%&vi1#c8Jk^@>L^yC%4ZI){__+yzWE_NSA^F@h=BTqTy-PdsEAB`{NJ%*1St zi;}`NHHJlV&nEG#%}oqbuq7>KVIK+m58QDFAz*Cw*^cQbW8>zm#x)z^&X9~V4h@HXub+J%H#0mDOGG-X$#>)V69ixUO%g5{E57m^wtG%PZ=% z;}g_Dn5Gc@Vm_J#t^;KoD(~s70gdjRIBC>>Q}qDF#zBG>f!|oMX(f;v3@A|ZMrtUf zP!F}g{KiTabcswOs<0!s_9^~H4b8b&xRzAdzOk*f07Si^t1vJRgyuBRqd~_ocEHF1&?Q;YEQK3i*HtI+QPdjkj4B@&AZ)I!ML)};#Qd+HG*z#|YuGm52a zu*Ukh(R%>eM(GH_We3M9k>38zj!3^4NxdmCz1=2z?3RWH*t^T|h^Za8G7!BjQhWbm z_eKt1g9*ob=+;o6ck9Z~_N5>1$Kg98hi@IFz6Etx@jr_E2hs(2miGVv002ovPDHLk FV1k5>sjL71 diff --git a/package-lock.json b/package-lock.json index 8af7e084..64190619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "minibili", "version": "0.5.0", "dependencies": { + "@openspacelabs/react-native-zoomable-view": "^2.1.6", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-community/hooks": "^3.0.0", "@react-native-community/netinfo": "11.1.0", @@ -37,12 +38,15 @@ "react": "18.2.0", "react-atomic-context": "^2.0.3", "react-native": "0.73.6", + "react-native-gesture-handler": "~2.14.0", "react-native-material-menu": "^2.0.0", "react-native-pager-view": "6.2.3", + "react-native-reanimated": "~3.6.2", "react-native-root-toast": "^3.5.1", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "react-native-webview": "13.6.4", + "react-native-zoom-reanimated": "^1.4.5", "spark-md5": "^3.0.2", "swr": "^2.2.4", "throttle-debounce": "^5.0.0", @@ -423,9 +427,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "engines": { "node": ">=6.9.0" } @@ -1517,6 +1521,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-object-assign": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.24.1.tgz", + "integrity": "sha512-I1kctor9iKtupb7jv7FyjApHCuKLBKCblVAeHVK9PB6FW7GI0ac6RtobC3MwwJy8CZ1JxuhQmnbrsqI5G8hAIg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", @@ -2294,6 +2312,17 @@ "postcss": "^8.4" } }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", @@ -4528,6 +4557,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@openspacelabs/react-native-zoomable-view": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@openspacelabs/react-native-zoomable-view/-/react-native-zoomable-view-2.1.6.tgz", + "integrity": "sha512-Eb8ro4a1DGkVBT9xIl7RQHSlpKQD7q62JeVXJKtLKZIZ+FNHDVfLGxwyeqUMevLHbBUTbAqX9+zsgOMk/mrHWg==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-native": ">=0.54.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -7389,6 +7430,11 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.45.tgz", + "integrity": "sha512-qkcUlZmX6c4J8q45taBKTL3p+LbITgyx7qhlPYOdOHZB7B31K0mXbP5YA7i7SgDeEGuI9MnumiKPEMrxg8j3KQ==" + }, "node_modules/@types/he": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", @@ -10938,9 +10984,9 @@ } }, "node_modules/expo": { - "version": "50.0.14", - "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.14.tgz", - "integrity": "sha512-yLPdxCMVAbmeEIpzzyAuJ79wvr6ToDDtQmuLDMAgWtjqP8x3CGddXxUe07PpKEQgzwJabdHvCLP5Bv94wMFIjQ==", + "version": "50.0.15", + "resolved": "https://registry.npmjs.org/expo/-/expo-50.0.15.tgz", + "integrity": "sha512-tsyRmMHjA8lPlM7AsqH1smSH8hzmn1+x/vsP+xgbKYJTGtYccdY/wsm6P84VJWeK5peWSVqrWNos+YuPqXKLSQ==", "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.17.8", @@ -10954,7 +11000,7 @@ "expo-font": "~11.10.3", "expo-keep-awake": "~12.8.2", "expo-modules-autolinking": "1.10.3", - "expo-modules-core": "1.11.12", + "expo-modules-core": "1.11.13", "fbemitter": "^3.0.0", "whatwg-url-without-unicode": "8.0.0-3" }, @@ -11198,9 +11244,9 @@ } }, "node_modules/expo-modules-core": { - "version": "1.11.12", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.12.tgz", - "integrity": "sha512-/e8g4kis0pFLer7C0PLyx98AfmztIM6gU9jLkYnB1pU9JAfQf904XEi3bmszO7uoteBQwSL6FLp1m3TePKhDaA==", + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.11.13.tgz", + "integrity": "sha512-2H5qrGUvmLzmJNPDOnovH1Pfk5H/S/V0BifBmOQyDc9aUh9LaDwkqnChZGIXv8ZHDW8JRlUW0QqyWxTggkbw1A==", "dependencies": { "invariant": "^2.2.4" } @@ -16806,6 +16852,22 @@ "react": "18.2.0" } }, + "node_modules/react-native-gesture-handler": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz", + "integrity": "sha512-YiM1BApV4aKeuwsM6O4C2ufwewYEKk6VMXOt0YqEZFMwABBFWhXLySFZYjBSNRU2USGppJbfHP1q1DfFQpKhdA==", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "lodash": "^4.17.21", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-material-menu": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-native-material-menu/-/react-native-material-menu-2.0.0.tgz", @@ -16836,6 +16898,27 @@ "react-native": "*" } }, + "node_modules/react-native-reanimated": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.3.tgz", + "integrity": "sha512-2KkkPozoIvDbJcHuf8qeyoLROXQxizSi+2CTCkuNVkVZOxxY4B0Omvgq61aOQhSZUh/649x1YHoAaTyGMGDJUw==", + "dependencies": { + "@babel/plugin-transform-object-assign": "^7.16.7", + "@babel/preset-typescript": "^7.16.7", + "convert-source-map": "^2.0.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-root-siblings": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/react-native-root-siblings/-/react-native-root-siblings-4.1.1.tgz", @@ -16969,6 +17052,15 @@ "node": ">=8" } }, + "node_modules/react-native-zoom-reanimated": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/react-native-zoom-reanimated/-/react-native-zoom-reanimated-1.4.5.tgz", + "integrity": "sha512-+R/pEItosr5nXIsDJeqFSsv8JNdfLjw0ttRebdZ9oc2/0+sy/XIYt138GFrXtYQJpz7wPOhh0rNA6wmGcUVvOQ==", + "peerDependencies": { + "react-native-gesture-handler": "*", + "react-native-reanimated": ">=2.0.0" + } + }, "node_modules/react-native/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", diff --git a/package.json b/package.json index 76e3cc65..fd2aa77b 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "bundle-analyze": "npx react-native-bundle-visualizer --platform android --entry-file index.js --expo true" }, "dependencies": { + "@openspacelabs/react-native-zoomable-view": "^2.1.6", "@react-native-async-storage/async-storage": "1.21.0", "@react-native-community/hooks": "^3.0.0", "@react-native-community/netinfo": "11.1.0", @@ -66,10 +67,13 @@ "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "react-native-webview": "13.6.4", + "react-native-zoom-reanimated": "^1.4.5", "spark-md5": "^3.0.2", "swr": "^2.2.4", "throttle-debounce": "^5.0.0", - "zod": "^3.21.4" + "zod": "^3.21.4", + "react-native-reanimated": "~3.6.2", + "react-native-gesture-handler": "~2.14.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/components/CommentList.tsx b/src/components/CommentList.tsx index e27eb688..3a111598 100644 --- a/src/components/CommentList.tsx +++ b/src/components/CommentList.tsx @@ -1,13 +1,14 @@ import { Icon, Skeleton, Text } from '@rneui/themed' import { FlashList } from '@shopify/flash-list' import React from 'react' -import { View } from 'react-native' +import { TouchableOpacity, View } from 'react-native' import { colors } from '@/constants/colors.tw' import { type CommentItemType, useComments } from '../api/comments' import { Comment } from './Comment' import ReplyList from './ReplyList' +import clsx from 'clsx' function Loading() { return ( @@ -76,13 +77,15 @@ export default function CommentList( {props.dividerRight} - { setMode(mode === 3 ? 2 : 3) }}> - {mode === 3 ? '按热度' : '按时间'} - + + {mode === 3 ? '按热度' : '按时间'} + + diff --git a/src/components/ImagesView.tsx b/src/components/ImagesView.tsx index fa5082b4..24de56bd 100644 --- a/src/components/ImagesView.tsx +++ b/src/components/ImagesView.tsx @@ -1,4 +1,3 @@ -// import { useNetInfo } from '@react-native-community/netInfo' import { Overlay } from '@rneui/themed' import { Image } from 'expo-image' import React from 'react' @@ -9,7 +8,6 @@ import { parseImgUrl } from '@/utils' import { useStore } from '../store' import Image2 from './Image2' - export default React.memo(ImagesView) const textShadow = { @@ -43,7 +41,6 @@ function ImagesView() { const imageCompCache = React.useRef< Record> >({}) - return ( 0} diff --git a/src/routes/Collect/index.tsx b/src/routes/Collect/index.tsx index 039d9dc0..e1e25c5a 100644 --- a/src/routes/Collect/index.tsx +++ b/src/routes/Collect/index.tsx @@ -12,12 +12,33 @@ import { CollectVideoInfo } from '@/types' function CollectList() { const { $collectedVideos, set$collectedVideos } = useStore() const headerTitle = `我的收藏(${$collectedVideos.length})` + const blackColor = tw(colors.black.text).color + const [searchKeyWord, setSearchKeyWord] = React.useState('') useUpdateNavigationOptions( React.useMemo(() => { return { headerTitle, + headerSearchBarOptions: { + placeholder: '搜索视频', + headerIconColor: blackColor, + hintTextColor: blackColor, + textColor: blackColor, + tintColor: blackColor, + disableBackButtonOverride: false, + shouldShowHintSearchIcon: false, + onClose: () => { + setSearchKeyWord('') + }, + onSearchButtonPress: ({ nativeEvent: { text } }) => { + const keyword = text.trim() + if (!keyword) { + return + } + setSearchKeyWord(keyword) + }, + }, } - }, [headerTitle]), + }, [headerTitle, blackColor]), ) const buttons = (video: CollectVideoInfo) => { @@ -48,10 +69,18 @@ function CollectList() { }, ] } + const collectVideos = React.useMemo(() => { + if (searchKeyWord) { + return $collectedVideos.filter(vi => { + return vi.title.includes(searchKeyWord) + }) + } + return $collectedVideos + }, [searchKeyWord, $collectedVideos]) return ( v.bvid + ''} + data={collectVideos} + keyExtractor={v => v.bvid} renderItem={({ item }: { item: CollectVideoInfo }) => { return }} diff --git a/src/routes/History/index.tsx b/src/routes/History/index.tsx index 3e3ed0d3..525e951b 100644 --- a/src/routes/History/index.tsx +++ b/src/routes/History/index.tsx @@ -12,10 +12,31 @@ import { CollectVideoInfo, HistoryVideoInfo } from '@/types' function HistoryList() { const { $watchedVideos } = useStore() const headerTitle = `观看历史(${Object.keys($watchedVideos).length})` + const blackColor = tw(colors.black.text).color + const [searchKeyWord, setSearchKeyWord] = React.useState('') useUpdateNavigationOptions( React.useMemo(() => { return { headerTitle, + headerSearchBarOptions: { + placeholder: '搜索视频', + headerIconColor: blackColor, + hintTextColor: blackColor, + textColor: blackColor, + tintColor: blackColor, + disableBackButtonOverride: false, + shouldShowHintSearchIcon: false, + onClose: () => { + setSearchKeyWord('') + }, + onSearchButtonPress: ({ nativeEvent: { text } }) => { + const keyword = text.trim() + if (!keyword) { + return + } + setSearchKeyWord(keyword) + }, + }, } }, [headerTitle]), ) @@ -31,10 +52,17 @@ function HistoryList() { ] } const list = React.useMemo(() => { - return Object.values($watchedVideos).sort((a, b) => { + const _list = Object.values($watchedVideos).sort((a, b) => { return b.watchTime - a.watchTime }) - }, [$watchedVideos]) + if (searchKeyWord) { + return _list.filter(vi => { + return vi.title.includes(searchKeyWord) + }) + } + return _list + }, [$watchedVideos, searchKeyWord]) + return ( + )} + onPress={() => { + // + }}> {route.params?.name || vi?.name} void }) { const { getIsWiFi, imagesList } = useStore() const route = useRoute>() const { width, height } = useWindowDimensions() @@ -49,7 +49,7 @@ function Player(props: { currentPage: number; currentCid?: number }) { ...route.params, ...data, } - const cid = props.currentCid || videoInfo.cid + const cid = videoInfo.pages ? videoInfo.pages[props.currentPage - 1].cid : 0 const { videoUrl } = usePlayUrl(videoInfo.bvid, cid, isWifi) const markVideoWatched = useMarkVideoWatched() @@ -81,6 +81,15 @@ function Player(props: { currentPage: number; currentCid?: number }) { `) }, [imagesList.length]) + // React.useEffect(() => { + // if (videoUrl && loadPlayer && webviewRef.current) { + // webviewRef.current.injectJavaScript( + // `window.newVideoUrl = "${videoUrl}"; + // window.replaceVideoUrl && replaceVideoUrl();true;`, + // ) + // } + // }, [videoUrl, loadPlayer]) + useAppStateChange(currentAppState => { if ( currentAppState === 'active' && @@ -93,11 +102,7 @@ function Player(props: { currentPage: number; currentCid?: number }) { KeepAwake.deactivateKeepAwake('PLAY') } }) - // useMounted(() => { - // if (!isWifi) { - // showToast('注意流量') - // } - // }) + const navigation = useNavigation() useFocusEffect( @@ -156,6 +161,7 @@ function Player(props: { currentPage: number; currentCid?: number }) { } if (eventData.payload === 'ended') { setVerticalExpand(false) + props.onPlayEnded() } // 'play', 'ended', 'pause', 'waiting', 'playing' } @@ -205,10 +211,11 @@ function Player(props: { currentPage: number; currentCid?: number }) { // const playUrl = 'https://www.bilibili.com/blackboard/webplayer/mbplayer.html' Object.entries({ bvid: route.params.bvid, + cid, // quality: isWifi ? 64 : 32, portraitFullScreen: true, // highQuality: isWifi ? 1 : 0, - // page: isWifi ? undefined : props.currentPage, + page: props.currentPage, autoplay: 1, // isWifi ? 0 : 1, hasMuteButton: true, }).forEach(([k, v]) => { @@ -216,9 +223,9 @@ function Player(props: { currentPage: number; currentCid?: number }) { search.append(k, v + '') } }) - const uri = `${playUrl}?${search}` + const uri = `${playUrl}?${search}#${encodeURIComponent(videoUrl ?? '')}` const player = - loadPlayer && videoUrl ? ( + loadPlayer && cid ? ( { diff --git a/src/routes/Play/VideoHeader.tsx b/src/routes/Play/VideoHeader.tsx deleted file mode 100644 index 8d1eb01a..00000000 --- a/src/routes/Play/VideoHeader.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { - type RouteProp, - useNavigation, - 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' - -import { useWatchingCount } from '@/api/watching-count' -import { colors } from '@/constants/colors.tw' -import { useStore } from '@/store' - -import { useVideoInfo } from '../../api/video-info' -import type { NavigationProps, RootStackParamList } from '../../types' -import { - handleShareVideo, - parseDate, - parseImgUrl, - parseNumber, - showToast, -} from '../../utils' - -export default React.memo(VideoHeader) - -function VideoHeader() { - const navigation = useNavigation() - const route = useRoute>() - const { data } = useVideoInfo(route.params.bvid) - const videoInfo = { - ...route.params, - ...data, - } - const { name, face, mid, date, title } = videoInfo - const watchingCount = useWatchingCount(videoInfo.bvid, videoInfo.cid!) - const { - _collectedVideosMap, - set$collectedVideos, - get$collectedVideos, - $blackUps, - } = useStore() - const isCollected = videoInfo.bvid && videoInfo.bvid in _collectedVideosMap - const isBlackUp = videoInfo.mid && '_' + videoInfo.mid in $blackUps - const collectVideo = () => { - if (typeof videoInfo?.collectNum !== 'number') { - return - } - if (isCollected) { - Alert.alert('是否取消收藏?', '', [ - { - text: '否', - }, - { - text: '是', - onPress: () => { - const list = get$collectedVideos() - set$collectedVideos(list.filter(vi => vi.bvid !== videoInfo.bvid)) - }, - }, - ]) - } else { - const list = get$collectedVideos() - set$collectedVideos([ - { - bvid: videoInfo.bvid, - name: videoInfo.name!, - title: videoInfo.title, - cover: videoInfo.cover!, - date: videoInfo.date!, - duration: videoInfo.duration!, - mid: videoInfo.mid!, - }, - ...list, - ]) - showToast('已收藏') - } - } - - return ( - - {videoInfo?.argument ? ( - - { - if (videoInfo.argumentLink) { - Linking.openURL(videoInfo.argumentLink) - } - }}> - ⚠️ {videoInfo.argument} - - - ) : null} - - { - if (!mid || !face || !name) { - return - } - const user = { - mid, - face, - name, - sign: '-', - } - navigation.push('Dynamic', { user }) - }} - className="flex-row flex-1 items-center mr-2"> - - - {(name || '') + ' '} - - - - - {parseDate(date, true)} - {watchingCount ? ( - - {watchingCount.total === '1' ? '壹' : watchingCount.total} - 人在看 - - ) : null} - - - - - - {parseNumber(videoInfo?.playNum)} - - - - {parseNumber(videoInfo?.danmuNum)}弹 - - { - showToast(`${videoInfo?.likeNum} 点赞`) - }}> - - {parseNumber(videoInfo?.likeNum)} - - - - - {parseNumber(videoInfo?.collectNum)} - - - { - if (name && title && route.params.bvid) { - handleShareVideo(name, title, route.params.bvid) - } - }}> - - {parseNumber(videoInfo?.shareNum)} - - - - ) -} diff --git a/src/routes/Play/VideoInfo.tsx b/src/routes/Play/VideoInfo.tsx index 0f874c6d..ac30858d 100644 --- a/src/routes/Play/VideoInfo.tsx +++ b/src/routes/Play/VideoInfo.tsx @@ -1,84 +1,289 @@ -import { type RouteProp, useRoute } from '@react-navigation/native' -import { Icon, ListItem, Text } from '@rneui/themed' +import { + type RouteProp, + useRoute, + useNavigation, +} from '@react-navigation/native' +import { Avatar, Dialog, Icon, Text } from '@rneui/themed' import React from 'react' -import { View } from 'react-native' +import { + Alert, + Linking, + Pressable, + ScrollView, + TouchableOpacity, + View, +} from 'react-native' import { colors } from '@/constants/colors.tw' -import type { RootStackParamList } from '@/types' -import { parseDuration } from '@/utils' +import type { NavigationProps, RootStackParamList } from '@/types' +import { + handleShareVideo, + parseDate, + parseDuration, + parseImgUrl, + parseNumber, + showToast, +} from '@/utils' import { useVideoInfo } from '../../api/video-info' +import clsx from 'clsx' +import { useStore } from '@/store' +import { useWatchingCount } from '@/api/watching-count' +import { Image } from 'expo-image' export default React.memo(VideoInfo) function VideoInfo(props: { currentPage: number setCurrentPage: (p: number) => void - setCurrentCid: (c: number) => void + // setCurrentCid: (c: number) => void }) { const route = useRoute>() const { data, isLoading } = useVideoInfo(route.params.bvid) - const [expanded, setExpanded] = React.useState(false) const videoInfo = { ...route.params, ...data, } - - const { title, desc, pages } = videoInfo + const { name, face, mid, date, title, desc, pages } = videoInfo let videoDesc = desc if (videoDesc === '-') { videoDesc = '' } else if (videoDesc && videoDesc === title) { videoDesc = '' } + const [showPagesModal, setShowPagesModal] = React.useState(false) + // const pages = Array.from({ length: 20 }).map(v => { + // return { + // ..._pages?.[0], + // cid: Math.random(), + // } + // }) + const navigation = useNavigation() + const watchingCount = useWatchingCount(videoInfo.bvid, videoInfo.cid!) + const { + _collectedVideosMap, + set$collectedVideos, + get$collectedVideos, + $blackUps, + } = useStore() + const isCollected = videoInfo.bvid && videoInfo.bvid in _collectedVideosMap + const isBlackUp = videoInfo.mid && '_' + videoInfo.mid in $blackUps + const collectVideo = () => { + if (typeof videoInfo?.collectNum !== 'number') { + return + } + if (isCollected) { + Alert.alert('是否取消收藏?', '', [ + { + text: '否', + }, + { + text: '是', + onPress: () => { + const list = get$collectedVideos() + set$collectedVideos(list.filter(vi => vi.bvid !== videoInfo.bvid)) + }, + }, + ]) + } else { + const list = get$collectedVideos() + set$collectedVideos([ + { + bvid: videoInfo.bvid, + name: videoInfo.name!, + title: videoInfo.title, + cover: videoInfo.cover!, + date: videoInfo.date!, + duration: videoInfo.duration!, + mid: videoInfo.mid!, + }, + ...list, + ]) + showToast('已收藏') + } + } return ( + + {videoInfo?.argument ? ( + + { + if (videoInfo.argumentLink) { + Linking.openURL(videoInfo.argumentLink) + } + }}> + ⚠️ {videoInfo.argument} + + + ) : null} + + { + if (!mid || !face || !name) { + return + } + const user = { + mid, + face, + name, + sign: '-', + } + navigation.push('Dynamic', { user }) + }} + className="flex-row flex-1 items-center mr-2"> + + + {(name || '') + ' '} + + + + + {parseDate(date, true)} + {watchingCount ? ( + + {watchingCount.total === '1' ? '壹' : watchingCount.total} + 人在看 + + ) : null} + + + + + + {parseNumber(videoInfo?.playNum)} + + + + + {parseNumber(videoInfo?.danmuNum)}弹 + + + { + showToast(`${videoInfo?.likeNum} 点赞`) + }}> + + {parseNumber(videoInfo?.likeNum)} + + + + + {parseNumber(videoInfo?.collectNum)} + + + { + if (name && title && route.params.bvid) { + handleShareVideo(name, title, route.params.bvid) + } + }}> + + {parseNumber(videoInfo?.shareNum)} + + + {title} {videoDesc ? ( {videoDesc} ) : null} - {(pages?.length || 0) > 1 ? ( - } - containerStyle={tw('py-1 px-3 mt-5')} - content={ - - - 视频分集({videoInfo?.pages?.length}) {props.currentPage}:{' '} - {videoInfo?.pages?.[props.currentPage - 1].title} - - - } - isExpanded={expanded} - onPress={() => { - setExpanded(!expanded) - }}> - {videoInfo?.pages?.map(v => { - const selected = v.page === props.currentPage - return ( - { - props.setCurrentPage(v.page) - props.setCurrentCid(v.cid) - }} - containerStyle={tw('py-3 px-5')} - bottomDivider - topDivider> - - - {v.page}. {v.title} ({parseDuration(v.duration)}) - - - - ) - })} - + {pages && pages.length > 1 ? ( + + { + setShowPagesModal(true) + }}> + + 视频分集【{props.currentPage}/{videoInfo?.pages?.length}】: + + {pages[props.currentPage - 1].title} + + + + { + setShowPagesModal(false) + }} + overlayStyle={tw(clsx(colors.gray2.bg, 'shrink-0'))}> + + + + {pages.map(item => { + const selected = item.page === props.currentPage + return ( + { + props.setCurrentPage(item.page) + // props.setCurrentCid(item.cid) + setShowPagesModal(false) + }} + className="py-2"> + + {item.page}. {item.title} ( + {parseDuration(item.duration)}) + + + ) + })} + + + + ) : null} {!isLoading && videoInfo?.interactive ? ( diff --git a/src/routes/Play/index.tsx b/src/routes/Play/index.tsx index 1e836c58..4dbbbd9d 100644 --- a/src/routes/Play/index.tsx +++ b/src/routes/Play/index.tsx @@ -15,7 +15,6 @@ import { showToast } from '../../utils' import { setViewingVideoId } from '../../utils/report' import { PlayHeaderRight, PlayHeaderTitle } from './Header' import Player from './Player' -import VideoHeader from './VideoHeader' import VideoInfo from './VideoInfo' // https://www.bilibili.com/blackboard/webplayer/mbplayer.html?aid=1501398719&bvid=BV1HS421w7wG&cid=1458260037&p=1 @@ -29,12 +28,15 @@ export default React.memo(Play) function Play({ route }: Props) { const { bvid } = route.params - const [currentPage, setCurrentPage] = React.useState(1) + const { data, error } = useVideoInfo(bvid) const videoInfo = { ...route.params, ...data, } + const [currentPage, setCurrentPage] = React.useState(1) + const cid = videoInfo.pages ? videoInfo.pages[currentPage - 1].cid : 0 + const errorShowedRef = React.useRef(false) React.useEffect(() => { @@ -46,10 +48,6 @@ function Play({ route }: Props) { ) } }, [error]) - const [currentCid, setCurrentCid] = React.useState(videoInfo.cid) - if (!currentCid && videoInfo.cid) { - setCurrentCid(videoInfo.cid) - } const [key, setKey] = React.useState(bvid) @@ -58,7 +56,7 @@ function Play({ route }: Props) { const headerTitle = () => const headerRight = () => ( { setKey(k => k + 1) }} @@ -68,7 +66,7 @@ function Play({ route }: Props) { headerTitle, headerRight, } - }, [currentCid]), + }, [cid]), ) useFocusEffect( @@ -80,9 +78,15 @@ function Play({ route }: Props) { }), ) + const handlePlayEnd = useMemoizedFn(() => { + if (videoInfo.pages && currentPage < videoInfo.pages.length) { + setCurrentPage(currentPage + 1) + } + }) + return ( - + }> - - + ) diff --git a/src/routes/Play/inject-play.js b/src/routes/Play/inject-play.js index e1f1d5ba..7f3ca328 100644 --- a/src/routes/Play/inject-play.js +++ b/src/routes/Play/inject-play.js @@ -7,6 +7,7 @@ function __$hack() { } ` document.head.appendChild(style) + function waitForVideo(callback) { const video = document.querySelector('video[src]') if (video) { @@ -117,6 +118,19 @@ function __$hack() { document.head.appendChild(style) }) + waitForVideo(vi => { + const newVideoUrl = decodeURIComponent(window.location.hash.slice(1)) + if (vi && newVideoUrl.startsWith('https') && vi.src !== newVideoUrl) { + vi.dataset.src = vi.src + vi.setAttribute('autoplay', 'true') + vi.setAttribute('src', newVideoUrl) + vi.dataset.replaced = 'true' + if (newVideoUrl.includes('_high_quality') && document.body) { + document.body.dataset.replaced = 'true' + } + } + }) + const xx = 'x' waitForDom('.mplayer-display', container => { container.addEventListener('dblclick', evt => { @@ -174,20 +188,6 @@ function __$hack() { } }) - waitForVideo(() => { - const newVideoUrl = '@NewVideoUrl' - const vi = document.querySelector('video[src]') - if (vi && vi.src !== newVideoUrl) { - vi.dataset.src = vi.src - vi.setAttribute('autoplay', 'true') - vi.setAttribute('src', newVideoUrl) - vi.dataset.replaced = 'true' - if (newVideoUrl.includes('_high_quality') && document.body) { - document.body.dataset.replaced = 'true' - } - } - }) - const element = document.body let startX = 0 let startY = 0 diff --git a/src/routes/VideoList/Header.tsx b/src/routes/VideoList/Header.tsx index 330732e9..0f04125d 100644 --- a/src/routes/VideoList/Header.tsx +++ b/src/routes/VideoList/Header.tsx @@ -114,7 +114,9 @@ function HeaderLeftComp() { {currentVideosCate.label + diff --git a/src/routes/VideoList/Test.tsx b/src/routes/VideoList/Test.tsx index ad4e17b5..4f7b4c3c 100644 --- a/src/routes/VideoList/Test.tsx +++ b/src/routes/VideoList/Test.tsx @@ -1 +1,50 @@ -export default () => null +import { + ReactNativeZoomableView, + ZoomableViewEvent, +} from '@openspacelabs/react-native-zoomable-view' +import { Image } from 'expo-image' +import React from 'react' +import { GestureResponderEvent, PanResponderGestureState } from 'react-native' +import PagerView from 'react-native-pager-view' +const images = [ + require('../../../assets/tv-l.png'), + require('../../../assets/play.png'), + require('../../../assets/ss.png'), +] + +export default function Test() { + const onShouldBlockNativeResponderHandler = ( + event: GestureResponderEvent, + gestureState: PanResponderGestureState, + zoomableViewEventObject: ZoomableViewEvent, + ): boolean => { + if (zoomableViewEventObject.zoomLevel === 1) { + return false + } else { + return true + } + } + const [current, setCurrent] = React.useState(0) + + return ( + { + setCurrent(e.nativeEvent.position) + }} + offscreenPageLimit={1} + className="flex-1" + initialPage={0}> + {images.map(img => { + return ( + + + + ) + })} + + ) +}