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