's'
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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 ?? '—'}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user