mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-08 23:43:52 +08:00
1
This commit is contained in:
@@ -27,6 +27,14 @@ export default function RootLayout() {
|
||||
gestureDirection: "horizontal",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="live"
|
||||
options={{
|
||||
animation: "slide_from_right",
|
||||
gestureEnabled: true,
|
||||
gestureDirection: "horizontal",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<MiniPlayer />
|
||||
</View>
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
ViewToken,
|
||||
FlatList,
|
||||
ScrollView,
|
||||
Linking,
|
||||
} from "react-native";
|
||||
import PagerView from "react-native-pager-view";
|
||||
import {
|
||||
@@ -214,9 +213,7 @@ export default function HomeScreen() {
|
||||
isLivePulse
|
||||
item={row.left}
|
||||
fullWidth
|
||||
onPress={() =>
|
||||
Linking.openURL(`https://live.bilibili.com/${row.left.roomid}`)
|
||||
}
|
||||
onPress={() => router.push(`/live/${row.left.roomid}` as any)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -248,11 +245,17 @@ export default function HomeScreen() {
|
||||
({ item }: { item: { left: LiveRoom; right?: LiveRoom } }) => (
|
||||
<View style={styles.row}>
|
||||
<View style={styles.leftCol}>
|
||||
<LiveCard item={item.left} />
|
||||
<LiveCard
|
||||
item={item.left}
|
||||
onPress={() => router.push(`/live/${item.left.roomid}` as any)}
|
||||
/>
|
||||
</View>
|
||||
{item.right && (
|
||||
<View style={styles.rightCol}>
|
||||
<LiveCard item={item.right} />
|
||||
<LiveCard
|
||||
item={item.right}
|
||||
onPress={() => router.push(`/live/${item.right!.roomid}` as any)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
187
app/live/[roomId].tsx
Normal file
187
app/live/[roomId].tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Image,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useLiveDetail } from '../../hooks/useLiveDetail';
|
||||
import { LivePlayer } from '../../components/LivePlayer';
|
||||
import { formatCount } from '../../utils/format';
|
||||
import { proxyImageUrl } from '../../utils/imageUrl';
|
||||
|
||||
export default function LiveDetailScreen() {
|
||||
const { roomId } = useLocalSearchParams<{ roomId: string }>();
|
||||
const router = useRouter();
|
||||
const id = parseInt(roomId ?? '0', 10);
|
||||
const { room, anchor, stream, loading, error } = useLiveDetail(id);
|
||||
|
||||
const isLive = room?.live_status === 1;
|
||||
const hlsUrl = stream?.hlsUrl ?? '';
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.safe}>
|
||||
{/* TopBar */}
|
||||
<View style={styles.topBar}>
|
||||
<TouchableOpacity onPress={() => router.back()} style={styles.backBtn}>
|
||||
<Ionicons name="chevron-back" size={24} color="#212121" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.topTitle} numberOfLines={1}>
|
||||
{room?.title ?? '直播间'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Player */}
|
||||
<LivePlayer hlsUrl={hlsUrl} isLive={isLive} />
|
||||
|
||||
{/* Content */}
|
||||
{loading ? (
|
||||
<ActivityIndicator style={styles.loader} color="#00AEEC" />
|
||||
) : error ? (
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
) : room ? (
|
||||
<ScrollView style={styles.scroll} showsVerticalScrollIndicator={false}>
|
||||
{/* Room title */}
|
||||
<View style={styles.titleSection}>
|
||||
<Text style={styles.title}>{room.title}</Text>
|
||||
<View style={styles.metaRow}>
|
||||
{/* Live status */}
|
||||
{isLive ? (
|
||||
<View style={styles.livePill}>
|
||||
<View style={styles.liveDot} />
|
||||
<Text style={styles.livePillText}>直播中</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[styles.livePill, styles.offlinePill]}>
|
||||
<Text style={[styles.livePillText, styles.offlinePillText]}>未开播</Text>
|
||||
</View>
|
||||
)}
|
||||
{/* Online count */}
|
||||
<View style={styles.onlineRow}>
|
||||
<Ionicons name="eye-outline" size={13} color="#999" />
|
||||
<Text style={styles.onlineText}>{formatCount(room.online)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{/* Area tags */}
|
||||
<View style={styles.areaRow}>
|
||||
{room.parent_area_name ? (
|
||||
<Text style={styles.areaTag}>{room.parent_area_name}</Text>
|
||||
) : null}
|
||||
{room.area_name ? (
|
||||
<Text style={styles.areaTag}>{room.area_name}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Divider */}
|
||||
<View style={styles.divider} />
|
||||
|
||||
{/* Anchor row */}
|
||||
{anchor && (
|
||||
<View style={styles.anchorRow}>
|
||||
<Image
|
||||
source={{ uri: proxyImageUrl(anchor.face) }}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
<Text style={styles.anchorName}>{anchor.uname}</Text>
|
||||
<TouchableOpacity style={styles.followBtn}>
|
||||
<Text style={styles.followTxt}>+ 关注</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Room description */}
|
||||
{!!room.description && (
|
||||
<View style={styles.descBox}>
|
||||
<Text style={styles.descText}>{room.description}</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1, backgroundColor: '#fff' },
|
||||
topBar: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#eee',
|
||||
},
|
||||
backBtn: { padding: 4 },
|
||||
topTitle: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
marginLeft: 4,
|
||||
color: '#212121',
|
||||
},
|
||||
loader: { marginVertical: 30 },
|
||||
errorText: { textAlign: 'center', color: '#f00', padding: 20 },
|
||||
scroll: { flex: 1 },
|
||||
titleSection: { padding: 14 },
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#212121',
|
||||
lineHeight: 22,
|
||||
marginBottom: 8,
|
||||
},
|
||||
metaRow: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 8 },
|
||||
livePill: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff0f0',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 4,
|
||||
gap: 4,
|
||||
},
|
||||
offlinePill: { backgroundColor: '#f5f5f5' },
|
||||
liveDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: '#f00' },
|
||||
livePillText: { fontSize: 12, color: '#f00', fontWeight: '600' },
|
||||
offlinePillText: { color: '#999' },
|
||||
onlineRow: { flexDirection: 'row', alignItems: 'center', gap: 3 },
|
||||
onlineText: { fontSize: 12, color: '#999' },
|
||||
areaRow: { flexDirection: 'row', gap: 6 },
|
||||
areaTag: {
|
||||
fontSize: 11,
|
||||
color: '#00AEEC',
|
||||
backgroundColor: '#e8f7fd',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 10,
|
||||
},
|
||||
divider: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#f0f0f0',
|
||||
marginHorizontal: 14,
|
||||
},
|
||||
anchorRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
avatar: { width: 40, height: 40, borderRadius: 20, marginRight: 10 },
|
||||
anchorName: { flex: 1, fontSize: 14, color: '#212121', fontWeight: '500' },
|
||||
followBtn: {
|
||||
backgroundColor: '#00AEEC',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 14,
|
||||
},
|
||||
followTxt: { color: '#fff', fontSize: 12, fontWeight: '600' },
|
||||
descBox: { padding: 14, paddingTop: 4 },
|
||||
descText: { fontSize: 14, color: '#555', lineHeight: 22 },
|
||||
});
|
||||
5
app/live/_layout.tsx
Normal file
5
app/live/_layout.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Slot } from 'expo-router';
|
||||
|
||||
export default function LiveLayout() {
|
||||
return <Slot />;
|
||||
}
|
||||
Reference in New Issue
Block a user