diff --git a/src/App.tsx b/src/App.tsx index a6a37389..027147a6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import ImagesView from './components/ImagesView' import type { ProviderConfiguration, SWRConfiguration } from 'swr/_internal' import ErrorFallback from './components/ErrorFallback' import useIsDark from './hooks/useIsDark' +// import { useCheckLivingUps } from './api/living-info' let online = true let focus = true @@ -88,6 +89,7 @@ export default function App() { mode: dark ? 'dark' : 'light', }) }, [dark]) + // useCheckLivingUps() return ( diff --git a/src/api/dynamic-items.ts b/src/api/dynamic-items.ts index 5baba72e..13eb82de 100644 --- a/src/api/dynamic-items.ts +++ b/src/api/dynamic-items.ts @@ -492,9 +492,12 @@ export function useDynamicItems(mid?: string | number) { ) const dynamicItems: DynamicListResponse['items'] = - data?.reduce((a, b) => { - return a.concat(b.items) - }, [] as DynamicListResponse['items']) || [] + data?.reduce( + (a, b) => { + return a.concat(b.items) + }, + [] as DynamicListResponse['items'], + ) || [] const isEmpty = data?.[0]?.items.length === 0 const isReachingEnd = isEmpty || (data && !data[data.length - 1]?.has_more) @@ -580,3 +583,18 @@ export async function checkUpdateUps(first: boolean) { } }) } + +let checkUpdateUpsTimer: number | null = null + +export function startCheckUpdateUps() { + if (typeof checkUpdateUpsTimer === 'number') { + window.clearInterval(checkUpdateUpsTimer) + } + checkUpdateUps(false) + checkUpdateUpsTimer = window.setInterval( + () => { + checkUpdateUps(false) + }, + 10 * 60 * 1000, + ) +} diff --git a/src/api/fetcher.ts b/src/api/fetcher.ts index acd0871f..4d4a10a6 100644 --- a/src/api/fetcher.ts +++ b/src/api/fetcher.ts @@ -30,7 +30,7 @@ export default function request(url: string) { ? url : 'https://api.bilibili.com' + url - // __DEV__ && console.log('request url: ', url.slice(0, 150)) + __DEV__ && console.log('request url: ', url.slice(0, 150)) // console.log(444, requestUrl) return fetch(requestUrl, { headers: { diff --git a/src/api/living-info.ts b/src/api/living-info.ts index 1522e3b9..0d393f9a 100644 --- a/src/api/living-info.ts +++ b/src/api/living-info.ts @@ -1,7 +1,7 @@ import { z } from 'zod' import request from './fetcher' import { LiveInfoBatchItemSchema } from './living-info.schema' -import store from '../store' +import store, { useStore } from '../store' import { Vibration } from 'react-native' import useSWR from 'swr' @@ -67,6 +67,43 @@ export const checkLivingUps = () => { }) } +export const useCheckLivingUps = () => { + const { $followedUps } = useStore() + const url = + $followedUps.length > 0 + ? `https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids?${$followedUps + .map(user => `uids[]=${user.mid}`) + .sort() + .join('&')}` + : null + const { data } = useSWR< + Record> + >(url, request, { + refreshInterval: 5 * 60 * 1000, + }) + const livingMap: Record = {} + if (!data) { + return + } + 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 + store.livingUps = livingMap +} + let checkLivingTimer: number | null = null export function startCheckLivingUps() { @@ -74,7 +111,10 @@ export function startCheckLivingUps() { window.clearInterval(checkLivingTimer) } checkLivingUps() - checkLivingTimer = window.setInterval(() => { - checkLivingUps() - }, 5 * 60 * 1000) + checkLivingTimer = window.setInterval( + () => { + checkLivingUps() + }, + 5 * 60 * 1000, + ) } diff --git a/src/routes/About/index.tsx b/src/routes/About/index.tsx index 95c4c67f..52509810 100644 --- a/src/routes/About/index.tsx +++ b/src/routes/About/index.tsx @@ -9,10 +9,13 @@ import BlackUps from './BlackUps' import BlackTags from './BlackTags' import SortCate from './SortCate' import Backup from './Backup' +import { useStore } from '../../store' export default React.memo(function About() { + // const { testid } = useStore() const content = ( + {/* testid: {testid} */}
diff --git a/src/routes/Follow/AddFollow.tsx b/src/routes/Follow/AddFollow.tsx index 2b118044..1be6250b 100644 --- a/src/routes/Follow/AddFollow.tsx +++ b/src/routes/Follow/AddFollow.tsx @@ -33,6 +33,7 @@ function SearchedItem(props: { up: SearchedUpType }) { } const handler = () => { store.$followedUps.unshift(user) + // store.$followedUps = [user, ...store.$followedUps] } const goToDynamic = () => { Linking.openURL(`https://m.bilibili.com/space/${up.mid}`) diff --git a/src/routes/Follow/index.tsx b/src/routes/Follow/index.tsx index c845c801..cf82d011 100644 --- a/src/routes/Follow/index.tsx +++ b/src/routes/Follow/index.tsx @@ -16,6 +16,7 @@ import commonStyles from '../../styles' import AddFollow from './AddFollow' import useMounted from '../../hooks/useMounted' import useIsDark from '../../hooks/useIsDark' +import { checkLivingUps } from '../../api/living-info' const renderItem = ({ item, @@ -30,8 +31,6 @@ const renderItem = ({ return } -let firstRender = true - const tvL = require('../../../assets/tv-l.png') const tvR = require('../../../assets/tv-r.png') @@ -60,25 +59,18 @@ const TvImg: React.FC = () => { /> ) } - -export default function Follow() { +let checkLiveTime = 0 +function Follow() { // eslint-disable-next-line no-console __DEV__ && console.log('Follow page') const { $followedUps, $upUpdateMap, livingUps } = useStore() const followListRef = React.useRef(null) const dark = useIsDark() - // 检查用户更新,由于组件会随路由重新创建,这里保证只运行一次 useMounted(() => { - if (firstRender) { - checkUpdateUps(true) - firstRender = false - window.setInterval( - () => { - checkUpdateUps(false) - }, - 10 * 60 * 1000, - ) + if (Date.now() - checkLiveTime > 60 * 1000) { + checkLivingUps() + checkLiveTime = Date.now() } }) @@ -168,6 +160,8 @@ export default function Follow() { ) } +export default React.memo(Follow) + const styles = StyleSheet.create({ container: { flex: 1, diff --git a/src/routes/VideoList/VideoItem.tsx b/src/routes/VideoList/VideoItem.tsx index 6314fddc..1484458b 100644 --- a/src/routes/VideoList/VideoItem.tsx +++ b/src/routes/VideoList/VideoItem.tsx @@ -13,7 +13,7 @@ export default React.memo(function HotItem({ video }: { video: VideoItem }) { const { theme } = useTheme() const { width } = useWindowDimensions() const itemWidth = (width - 24) / 2 - const { isWiFi } = useStore() + const { isWiFi, followedUpsMap } = useStore() return ( @@ -65,7 +65,11 @@ export default React.memo(function HotItem({ video }: { video: VideoItem }) { style={[ styles.upNameText, { - color: theme.colors.primary, + color: + video.mid in followedUpsMap + ? theme.colors.secondary + : theme.colors.primary, + fontWeight: video.mid in followedUpsMap ? 'bold' : 'normal', }, ]}> {video.name} diff --git a/src/store/index.ts b/src/store/index.ts index f050bb78..b653b5b4 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,9 +2,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage' import { proxy, subscribe, useSnapshot } from 'valtio' import { RanksConfig } from '../constants' import { checkUpdate } from '../api/check-update' -import type { VideoItem } from '../api/hot-videos' -import { startCheckLivingUps } from '../api/living-info' import { UpInfo } from '../types' +// import { registerCheckLivingUps } from '../utils/check-live' +// import { showToast } from '../utils' +// import { subscribeKey } from 'valtio/utils' +import { startCheckLivingUps } from '../api/living-info' +import { startCheckUpdateUps } from '../api/dynamic-items' interface UpdateUpInfo { latestId: string @@ -22,6 +25,7 @@ const store = proxy<{ // $cachedHotVideos: VideoItem[] // $pinUps: Record // ---------------------------- + followedUpsMap: Record initialed: boolean isWiFi: boolean webViewMode: 'PC' | 'MOBILE' @@ -40,9 +44,18 @@ const store = proxy<{ showCaptcha: boolean updatedCount: number moreRepliesUrl: string + testid: number }>({ $blackUps: {}, $followedUps: [], + get followedUpsMap() { + // console.log('followedUpsMapfollowedUpsMapfollowedUpsMap') + const ups: Record = {} + for (const up of this.$followedUps) { + ups[up.mid] = up + } + return ups + }, $blackTags: {}, $upUpdateMap: {}, $ignoredVersions: [], @@ -62,6 +75,7 @@ const store = proxy<{ currentImageIndex: 0, overlayButtons: [], showCaptcha: false, + testid: 0, get updatedCount() { const aa = Object.values(this.$upUpdateMap) return aa.filter(item => { @@ -90,6 +104,9 @@ Promise.all( ) .then(() => { store.initialed = true + // subscribeKey(store, '$followedUps', v => + // console.log('$followedUps has changed to', v), + // ) subscribe(store, changes => { const changedKeys = new Set() for (const op of changes) { @@ -98,6 +115,7 @@ Promise.all( changedKeys.add(ck as StoredKeys) } } + // console.log('chencged', [...changedKeys]) for (const ck of changedKeys) { AsyncStorage.setItem(StoragePrefix + ck, JSON.stringify(store[ck])) } @@ -105,6 +123,12 @@ Promise.all( }) .then(() => { startCheckLivingUps() + startCheckUpdateUps() + // return registerCheckLivingUps().then(good => { + // if (!good) { + // showToast('检查直播失败') + // } + // }) }) export function useStore() { diff --git a/src/utils/check-live.ts b/src/utils/check-live.ts new file mode 100644 index 00000000..d550b680 --- /dev/null +++ b/src/utils/check-live.ts @@ -0,0 +1,46 @@ +import * as BackgroundFetch from 'expo-background-fetch' +import * as TaskManager from 'expo-task-manager' +import { checkLivingUps } from '../api/living-info' +import store from '../store' + +const CHECK_LIVING_UPS = 'check-living-ups' + +// 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(CHECK_LIVING_UPS, async () => { + // const now = Date.now(); + console.log(4353453453) + store.testid++ + // console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`); + // await checkLivingUps() + // Be sure to return the successful result type! + return BackgroundFetch.BackgroundFetchResult.NewData +}) + +// 2. Register the task at some point in your app by providing the same name, +// and some configuration options for how the background fetch should behave +// Note: This does NOT need to be in the global scope and CAN be used in your React components! +export async function registerCheckLivingUps() { + return BackgroundFetch.registerTaskAsync(CHECK_LIVING_UPS, { + minimumInterval: 60 * 10, // 10 minutes + stopOnTerminate: false, // android only, + startOnBoot: true, // android only + }).then(() => { + return isCheckLivingUpsTaskGood() + }) +} + +// 3. (Optional) Unregister tasks by specifying the task name +// This will cancel any future background fetch calls that match the given name +// Note: This does NOT need to be in the global scope and CAN be used in your React components! +export async function unregisterCheckLivingUps() { + return BackgroundFetch.unregisterTaskAsync(CHECK_LIVING_UPS) +} + +async function isCheckLivingUpsTaskGood() { + const status = await BackgroundFetch.getStatusAsync() + const isRegistered = await TaskManager.isTaskRegisteredAsync(CHECK_LIVING_UPS) + return ( + status === BackgroundFetch.BackgroundFetchStatus.Available && isRegistered + ) +}