This commit is contained in:
eric
2026-03-26 00:48:14 -05:00
parent f16a8a9255
commit aba40c8cb7
9 changed files with 1496 additions and 60 deletions

View File

@@ -10,6 +10,7 @@ import {
Alert,
RefreshControl,
Linking,
Switch,
} from 'react-native'
import { Picker } from '@react-native-picker/picker'
import { SafeAreaView } from 'react-native-safe-area-context'
@@ -23,6 +24,27 @@ import {
proChannelPm2Process,
} from '@/lib/youtubeConfigUtils'
function statusTintBackground(variant: string): string {
switch (variant) {
case 'online':
return 'rgba(61, 214, 140, 0.1)'
case 'stopped':
case 'error':
return 'rgba(245, 101, 101, 0.1)'
case 'warn':
return 'rgba(246, 173, 85, 0.12)'
case 'offline':
return 'rgba(139, 146, 158, 0.11)'
case 'loading':
return 'rgba(91, 157, 255, 0.12)'
case 'unknown':
case 'empty':
return 'rgba(91, 157, 255, 0.09)'
default:
return 'rgba(139, 146, 158, 0.1)'
}
}
function ctrlStyle(action: string): object {
switch (action) {
case 'start':
@@ -183,6 +205,7 @@ export default function RecordScreen() {
const [refreshing, setRefreshing] = useState(false)
const statusColor = useMemo(() => variantColor[r.statusMeta.variant] ?? theme.muted, [r.statusMeta.variant])
const statusBg = useMemo(() => statusTintBackground(r.statusMeta.variant), [r.statusMeta.variant])
const onRefresh = useCallback(async () => {
if (!bind) return
@@ -227,11 +250,6 @@ export default function RecordScreen() {
</View>
) : null}
{unbound && (
<View style={styles.banner}>
<Text style={styles.bannerText}> PC</Text>
</View>
)}
{bind && (
<View style={styles.banner}>
<Text style={styles.bannerText}>
@@ -240,17 +258,61 @@ export default function RecordScreen() {
</View>
)}
<View style={styles.toolbar}>
<View style={[styles.pickerWrap, unbound && styles.pickerDisabled]}>
<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>
<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>
{bind ? (
<View style={[styles.pickerWrap, r.emptyHint && styles.pickerDisabled, styles.pickerRowBelow]}>
<Picker
enabled={!unbound && !r.emptyHint}
selectedValue={unbound ? '' : r.currentProcess}
enabled={!r.emptyHint}
selectedValue={r.currentProcess}
onValueChange={(v) => r.setCurrentProcess(String(v))}
style={styles.picker}
dropdownIconColor={theme.text}
>
{unbound || !r.entries.length ? (
<Picker.Item label="未配对" value="" color={theme.muted} />
{!r.entries.length ? (
<Picker.Item label="无任务" value="" color={theme.muted} />
) : (
r.entries.map((e) => (
<Picker.Item key={e.pm2} label={e.label} value={e.pm2} color={theme.text} />
@@ -258,28 +320,7 @@ export default function RecordScreen() {
)}
</Picker>
</View>
<Pressable
style={[styles.proRow, unbound && styles.pickerDisabled]}
disabled={unbound}
onPress={() => {
if (unbound) {
Alert.alert('未绑定', '配对后可切换多频道 Pro。')
return
}
void r.togglePro(!r.multiChannelPro)
}}
>
<Text style={styles.proLabel}>Pro</Text>
<Text style={[styles.proVal, r.multiChannelPro && { color: theme.accent }]}>
{r.multiChannelPro ? '开' : '关'}
</Text>
</Pressable>
</View>
<View style={[styles.pill, { borderColor: statusColor }]}>
<View style={[styles.dot, { backgroundColor: statusColor }]} />
<Text style={[styles.pillText, { color: statusColor }]}>{r.statusMeta.line}</Text>
</View>
) : null}
{bind && r.emptyHint ? (
<View style={styles.emptyCard}>
@@ -678,7 +719,90 @@ const styles = StyleSheet.create({
marginBottom: 14,
},
bannerText: { fontSize: 13, color: theme.text, lineHeight: 20 },
toolbar: { marginBottom: 12 },
toolbarCard: {
marginBottom: 12,
borderRadius: 14,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.07)',
backgroundColor: theme.bgElevated,
paddingVertical: 12,
paddingHorizontal: 12,
},
toolbarRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 10,
},
toolbarLeft: { flex: 1, minWidth: 0, marginRight: 6 },
statusChip: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
paddingVertical: 10,
paddingHorizontal: 12,
paddingLeft: 11,
borderRadius: 12,
borderLeftWidth: 3,
borderTopWidth: 1,
borderRightWidth: 1,
borderBottomWidth: 1,
borderTopColor: 'rgba(255,255,255,0.06)',
borderRightColor: 'rgba(255,255,255,0.06)',
borderBottomColor: 'rgba(255,255,255,0.06)',
},
statusDotOuter: {
width: 14,
height: 14,
borderRadius: 7,
borderWidth: 2,
alignItems: 'center',
justifyContent: 'center',
},
statusDotInner: { width: 6, height: 6, borderRadius: 3 },
statusLineText: {
flex: 1,
fontSize: 15,
fontWeight: '600',
color: theme.text,
letterSpacing: 0.2,
},
proCluster: {
flexDirection: 'row',
alignItems: 'center',
flexShrink: 0,
gap: 10,
paddingLeft: 10,
paddingVertical: 4,
paddingRight: 4,
borderRadius: 12,
backgroundColor: 'rgba(91, 157, 255, 0.06)',
borderWidth: 1,
borderColor: 'rgba(91, 157, 255, 0.18)',
},
proClusterDisabled: { opacity: 0.45 },
proLabelRow: { flexDirection: 'row', alignItems: 'center', gap: 6 },
proLabelZh: { fontSize: 13, fontWeight: '600', color: theme.text },
proBadge: {
paddingHorizontal: 7,
paddingVertical: 3,
borderRadius: 6,
backgroundColor: 'rgba(255,255,255,0.08)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
},
proBadgeOn: {
backgroundColor: 'rgba(91, 157, 255, 0.22)',
borderColor: 'rgba(91, 157, 255, 0.45)',
},
proBadgeText: {
fontSize: 10,
fontWeight: '800',
color: theme.accent,
letterSpacing: 0.6,
},
proSwitchWrap: { transform: [{ scale: 0.92 }] },
unpairedText: { fontSize: 15, fontWeight: '600', color: theme.muted },
proGrid: { gap: 10 },
proSwitchRow: {
padding: 12,
@@ -712,32 +836,8 @@ const styles = StyleSheet.create({
},
pickerDisabled: { opacity: 0.65 },
picker: { color: theme.text },
proRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 12,
paddingVertical: 10,
paddingHorizontal: 12,
backgroundColor: theme.bgCard,
borderRadius: 10,
borderWidth: 1,
borderColor: theme.border,
},
proLabel: { color: theme.text, fontWeight: '600' },
proVal: { color: theme.muted, fontWeight: '600' },
pill: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'flex-start',
gap: 8,
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 999,
borderWidth: 1,
marginBottom: 14,
},
pickerRowBelow: { marginTop: 0 },
dot: { width: 8, height: 8, borderRadius: 4 },
pillText: { fontSize: 13, fontWeight: '600' },
emptyCard: {
padding: 20,
borderRadius: 12,

View File

@@ -1,7 +1,8 @@
import { useCallback, useEffect, useState } from 'react'
import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native'
import { View, Text, ScrollView, StyleSheet, RefreshControl, Pressable } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { UserAgreementModal } from '@/components/UserAgreementModal'
import { useBind } from '@/context/BindContext'
import { apiFetch } from '@/lib/api'
import type { SystemMetricsPayload } from '@/lib/pcTypes'
@@ -20,6 +21,7 @@ export default function SystemScreen() {
const [data, setData] = useState<SystemMetricsPayload | null>(null)
const [err, setErr] = useState<string | null>(null)
const [refreshing, setRefreshing] = useState(false)
const [agreementOpen, setAgreementOpen] = useState(false)
const load = useCallback(async () => {
if (!bind) {
@@ -147,8 +149,12 @@ export default function SystemScreen() {
<View style={styles.cardMuted}>
<Text style={styles.cardTitle}></Text>
<Text style={styles.muted}> Electron </Text>
<Pressable style={styles.agreementRow} onPress={() => setAgreementOpen(true)} hitSlop={8}>
<Text style={styles.agreementLink}></Text>
</Pressable>
</View>
</ScrollView>
<UserAgreementModal visible={agreementOpen} onClose={() => setAgreementOpen(false)} />
</SafeAreaView>
)
}
@@ -205,4 +211,6 @@ const styles = StyleSheet.create({
ffSec: { marginTop: 12 },
ffLine: { fontSize: 12, color: theme.muted, marginBottom: 4, fontFamily: 'monospace' },
muted: { fontSize: 13, color: theme.muted, lineHeight: 20 },
agreementRow: { marginTop: 10, alignSelf: 'flex-start' },
agreementLink: { fontSize: 12, color: theme.accent, textDecorationLine: 'underline' },
})