diff --git a/assets/tv.png b/assets/tv.png
deleted file mode 100644
index 69df9d8..0000000
Binary files a/assets/tv.png and /dev/null differ
diff --git a/package-lock.json b/package-lock.json
index 8af7e08..6419061 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 76e3cc6..fd2aa77 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 e27eb68..3a11159 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 fa5082b..24de56b 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 039d9dc..e1e25c5 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 3e3ed0d..525e951 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 8d1eb01..0000000
--- 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 0f874c6..ac30858 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}
+
+
+
+
+
) : null}
{!isLoading && videoInfo?.interactive ? (
diff --git a/src/routes/Play/index.tsx b/src/routes/Play/index.tsx
index 1e836c5..4dbbbd9 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 e1f1d5b..7f3ca32 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 330732e..0f04125 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 ad4e17b..4f7b4c3 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 (
+
+
+
+ )
+ })}
+
+ )
+}