feat:直播 Tab 顶部显示关注主播在线列表

This commit is contained in:
Developer
2026-03-19 20:46:16 +08:00
parent c0b7b8e974
commit f5d49cd3f9
3 changed files with 138 additions and 24 deletions

View File

@@ -39,6 +39,7 @@ import {
type LiveRow,
} from "../utils/videoRows";
import { BigVideoCard } from "../components/BigVideoCard";
import { FollowedLiveStrip } from "../components/FollowedLiveStrip";
import type { LiveRoom } from "../services/types";
const HEADER_H = 44;
@@ -339,33 +340,36 @@ export default function HomeScreen() {
}}
renderItem={renderLiveItem}
ListHeaderComponent={
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.areaTabRow}
contentContainerStyle={styles.areaTabContent}
>
{LIVE_AREAS.map((area) => (
<TouchableOpacity
key={area.id}
style={[
styles.areaTab,
liveAreaId === area.id && styles.areaTabActive,
]}
onPress={() => handleLiveAreaPress(area.id)}
activeOpacity={0.7}
>
<Text
<View>
<FollowedLiveStrip />
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.areaTabRow}
contentContainerStyle={styles.areaTabContent}
>
{LIVE_AREAS.map((area) => (
<TouchableOpacity
key={area.id}
style={[
styles.areaTabText,
liveAreaId === area.id && styles.areaTabTextActive,
styles.areaTab,
liveAreaId === area.id && styles.areaTabActive,
]}
onPress={() => handleLiveAreaPress(area.id)}
activeOpacity={0.7}
>
{area.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
<Text
style={[
styles.areaTabText,
liveAreaId === area.id && styles.areaTabTextActive,
]}
>
{area.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
}
refreshControl={
<RefreshControl

View File

@@ -0,0 +1,92 @@
import React, { useEffect, useState } from 'react';
import { View, Text, ScrollView, TouchableOpacity, Image, StyleSheet } from 'react-native';
import { useRouter } from 'expo-router';
import { useAuthStore } from '../store/authStore';
import { getFollowedLiveRooms } from '../services/bilibili';
import { LivePulse } from './LivePulse';
import { proxyImageUrl } from '../utils/imageUrl';
import type { LiveRoom } from '../services/types';
export function FollowedLiveStrip() {
const { sessdata } = useAuthStore();
const [rooms, setRooms] = useState<LiveRoom[]>([]);
const router = useRouter();
useEffect(() => {
if (!sessdata) return;
getFollowedLiveRooms().then(setRooms).catch(() => {});
}, [sessdata]);
if (!sessdata || rooms.length === 0) return null;
return (
<View style={styles.container}>
<Text style={styles.title}></Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
{rooms.map((room) => (
<TouchableOpacity
key={room.roomid}
style={styles.item}
onPress={() => router.push(`/live/${room.roomid}` as any)}
activeOpacity={0.7}
>
<View style={styles.pulseRow}>
<LivePulse />
</View>
<Image
source={{ uri: proxyImageUrl(room.face) }}
style={styles.avatar}
/>
<Text style={styles.name} numberOfLines={1}>
{room.uname.length > 5 ? room.uname.slice(0, 5) : room.uname}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#f4f4f4',
paddingHorizontal: 12,
paddingVertical: 8,
},
title: {
fontSize: 12,
color: '#999',
marginBottom: 6,
},
scrollContent: {
gap: 12,
alignItems: 'center',
},
item: {
alignItems: 'center',
width: 56,
},
pulseRow: {
height: 16,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 4,
},
avatar: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: '#eee',
},
name: {
fontSize: 11,
color: '#333',
marginTop: 4,
textAlign: 'center',
width: 56,
},
});

View File

@@ -425,3 +425,21 @@ export async function getDanmaku(cid: number): Promise<DanmakuItem[]> {
return [];
}
}
export async function getFollowedLiveRooms(): Promise<LiveRoom[]> {
const res = await api.get(`${LIVE_BASE}/xlive/web-ucenter/v1/xfetter/FeedList`, {
params: { page: 1, page_size: 10, platform: 'web' },
});
const list = res.data?.data?.list ?? [];
return list.map((r: any) => ({
roomid: r.room_id,
uid: r.uid,
title: r.title,
uname: r.uname,
face: r.face,
cover: r.cover || r.keyframe || '',
online: r.online ?? 0,
area_name: r.area_v2_name ?? '',
parent_area_name: r.area_v2_parent_name ?? '',
}));
}