This commit is contained in:
eric
2026-03-26 01:59:04 -05:00
parent aba40c8cb7
commit 3bc1599763
12 changed files with 629 additions and 72 deletions

View File

@@ -1,4 +1,5 @@
import React from 'react'
import { Platform } from 'react-native'
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
@@ -8,17 +9,28 @@ import { useClientOnlyValue } from '@/components/useClientOnlyValue'
export default function TabLayout() {
const colorScheme = useColorScheme()
const dark = colorScheme === 'dark'
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme].tint,
tabBarInactiveTintColor: dark ? 'rgba(255,255,255,0.38)' : 'rgba(0,0,0,0.35)',
tabBarLabelStyle: { fontSize: 11, fontWeight: '600', letterSpacing: 0.2 },
tabBarStyle: {
backgroundColor: colorScheme === 'dark' ? '#1b1f28' : '#fff',
borderTopColor: 'rgba(255,255,255,0.08)',
backgroundColor: dark ? '#161a22' : '#fff',
borderTopWidth: 1,
borderTopColor: dark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.06)',
height: Platform.OS === 'ios' ? 88 : 64,
paddingTop: 6,
},
headerStyle: {
backgroundColor: colorScheme === 'dark' ? '#1b1f28' : '#fff',
backgroundColor: dark ? '#161a22' : '#fff',
shadowColor: '#000',
shadowOpacity: dark ? 0.25 : 0.08,
shadowRadius: 12,
shadowOffset: { width: 0, height: 4 },
elevation: 4,
},
headerTintColor: Colors[colorScheme].text,
headerShown: useClientOnlyValue(false, true),

View File

@@ -49,7 +49,7 @@ function SnapshotNet({ data }: { data: unknown }) {
{g?.latency_ms != null && g.latency_ms >= 0 ? `${g.latency_ms.toFixed(0)} ms` : '—'} · {g?.country ?? '—'}
</Text>
<Text style={styles.snapLine}>
{y?.reachable ? '可达' : '不可达'} ·{' '}
{y?.reachable ? '可达' : '不可达'} ·{' '}
{y?.latency_ms != null && y.latency_ms >= 0 ? `${y.latency_ms.toFixed(0)} ms` : '—'} · {y?.country ?? '—'}
</Text>
</View>
@@ -196,7 +196,7 @@ export default function DeskScreen() {
</Text>
<Text style={styles.liveLine}> {row.anchor ?? '—'}</Text>
<Text style={styles.liveLine} numberOfLines={2}>
{row.douyinHint ?? '—'}
{row.douyinHint ?? '—'}
</Text>
<Text style={styles.liveLine} numberOfLines={2}>
{row.streamTitle ?? '—'}

View File

@@ -1,4 +1,7 @@
import { useCallback, useMemo, useState } from 'react'
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'
import { useNavigation } from 'expo-router'
import { LinearGradient } from 'expo-linear-gradient'
import Animated, { FadeInDown } from 'react-native-reanimated'
import {
View,
Text,
@@ -15,6 +18,7 @@ import {
import { Picker } from '@react-native-picker/picker'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useAuth } from '@/context/AuthContext'
import { useBind } from '@/context/BindContext'
import { useRecorder } from '@/hooks/useRecorder'
import { theme } from '@/constants/theme'
@@ -121,7 +125,7 @@ function ProLiveSwitchGrid({
</Pressable>
{href ? (
<Pressable onPress={() => void Linking.openURL(href)}>
<Text style={styles.proLink}></Text>
<Text style={styles.proLink}></Text>
</Pressable>
) : null}
<View style={styles.proActions}>
@@ -199,8 +203,27 @@ function LiveSwitchGrid({
}
export default function RecordScreen() {
const navigation = useNavigation()
const { bind } = useBind()
const { session, openLogin, logout } = useAuth()
const r = useRecorder()
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Pressable
onPress={() => void (session ? logout() : openLogin())}
style={{ paddingHorizontal: 12, paddingVertical: 6 }}
>
<Text style={{ color: theme.accent, fontWeight: '700', fontSize: 14 }}>
{session?.record?.email
? `${String(session.record.email).split('@')[0]}`
: '登录'}
</Text>
</Pressable>
),
})
}, [navigation, session, openLogin, logout])
const unbound = !bind
const [refreshing, setRefreshing] = useState(false)
@@ -258,49 +281,58 @@ export default function RecordScreen() {
</View>
)}
<View style={styles.toolbarCard}>
<View style={styles.toolbarRow}>
<View style={styles.toolbarLeft}>
<View
style={[
styles.statusChip,
{ borderLeftColor: statusColor, backgroundColor: statusBg },
]}
>
<View style={[styles.statusDotOuter, { borderColor: statusColor }]}>
<View style={[styles.statusDotInner, { backgroundColor: statusColor }]} />
<Animated.View entering={FadeInDown.duration(420).springify()}>
<LinearGradient
colors={['rgba(91,157,255,0.14)', 'rgba(20,22,28,0.92)']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.toolbarGradient}
>
<View style={styles.toolbarCard}>
<View style={styles.toolbarRow}>
<View style={styles.toolbarLeft}>
<View
style={[
styles.statusChip,
{ borderLeftColor: statusColor, backgroundColor: statusBg },
]}
>
<View style={[styles.statusDotOuter, { borderColor: statusColor }]}>
<View style={[styles.statusDotInner, { backgroundColor: statusColor }]} />
</View>
<Text style={styles.statusLineText} numberOfLines={1}>
{r.statusMeta.line}
</Text>
</View>
</View>
<Text style={styles.statusLineText} numberOfLines={1}>
{r.statusMeta.line}
</Text>
</View>
</View>
<View style={[styles.proCluster, unbound && styles.proClusterDisabled]}>
<View style={styles.proLabelRow}>
<Text style={styles.proLabelZh}></Text>
<View style={[styles.proBadge, r.multiChannelPro && styles.proBadgeOn]}>
<Text style={styles.proBadgeText}>PRO</Text>
<View style={[styles.proCluster, unbound && styles.proClusterDisabled]}>
<View style={styles.proLabelRow}>
<Text style={styles.proLabelZh}></Text>
<View style={[styles.proBadge, r.multiChannelPro && styles.proBadgeOn]}>
<Text style={styles.proBadgeText}>PRO</Text>
</View>
</View>
<View style={styles.proSwitchWrap}>
<Switch
value={r.multiChannelPro}
disabled={unbound}
onValueChange={(v) => {
if (unbound) return
void r.togglePro(v)
}}
trackColor={{
false: 'rgba(255,255,255,0.12)',
true: 'rgba(91, 157, 255, 0.45)',
}}
thumbColor={r.multiChannelPro ? '#7eb6ff' : '#5c6370'}
ios_backgroundColor="rgba(255,255,255,0.1)"
/>
</View>
</View>
</View>
<View style={styles.proSwitchWrap}>
<Switch
value={r.multiChannelPro}
disabled={unbound}
onValueChange={(v) => {
if (unbound) return
void r.togglePro(v)
}}
trackColor={{
false: 'rgba(255,255,255,0.12)',
true: 'rgba(91, 157, 255, 0.45)',
}}
thumbColor={r.multiChannelPro ? '#7eb6ff' : '#5c6370'}
ios_backgroundColor="rgba(255,255,255,0.1)"
/>
</View>
</View>
</View>
</View>
</LinearGradient>
</Animated.View>
{bind ? (
<View style={[styles.pickerWrap, r.emptyHint && styles.pickerDisabled, styles.pickerRowBelow]}>
@@ -719,9 +751,13 @@ const styles = StyleSheet.create({
marginBottom: 14,
},
bannerText: { fontSize: 13, color: theme.text, lineHeight: 20 },
toolbarCard: {
toolbarGradient: {
marginBottom: 12,
borderRadius: 14,
borderRadius: 16,
padding: 1,
},
toolbarCard: {
borderRadius: 15,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.07)',
backgroundColor: theme.bgElevated,

View File

@@ -246,7 +246,7 @@ export default function NetworkScreen() {
{!bind && (
<View style={styles.banner}>
<Text style={styles.bannerText}> PC Google / </Text>
<Text style={styles.bannerText}> PC Google / </Text>
</View>
)}
@@ -268,7 +268,7 @@ export default function NetworkScreen() {
onSolution={openSolution}
/>
<SiteCard
title="抖音(国内参考)"
title="国内站点(参考)"
site={view.douyin}
speed={view.speed.domestic}
loading={loading && !!bind}

View File

@@ -6,7 +6,9 @@ import { useEffect } from 'react';
import 'react-native-reanimated';
import { useColorScheme } from '@/components/useColorScheme';
import { AuthProvider } from '@/context/AuthContext';
import { BindProvider } from '@/context/BindContext';
import { PocketBaseAuthModal } from '@/components/PocketBaseAuthModal';
export {
// Catch any errors thrown by the Layout component.
@@ -55,12 +57,15 @@ function RootLayoutNav() {
const colorScheme = useColorScheme();
return (
<BindProvider>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
</BindProvider>
<AuthProvider>
<BindProvider>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
<PocketBaseAuthModal />
</ThemeProvider>
</BindProvider>
</AuthProvider>
);
}