refactor: extract settings styles and improve badge/button UI
This commit is contained in:
@@ -646,6 +646,9 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
||||
marginLeft: theme.spacing[1],
|
||||
flexShrink: 0,
|
||||
},
|
||||
chevronExpanded: {
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
detailWrapper: {
|
||||
borderBottomLeftRadius: theme.borderRadius.lg,
|
||||
borderBottomRightRadius: theme.borderRadius.lg,
|
||||
@@ -1289,6 +1292,8 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
const { theme } = useUnistyles();
|
||||
const resolvedDisableOuterSpacing =
|
||||
useDisableOuterSpacing(disableOuterSpacing);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
const isInteractive = Boolean(onToggle);
|
||||
const hasDetailContent = Boolean(renderDetails);
|
||||
const detailContent =
|
||||
@@ -1479,6 +1484,107 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
} as never)
|
||||
: null;
|
||||
|
||||
const containerStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.container,
|
||||
!resolvedDisableOuterSpacing &&
|
||||
(isLastInSequence
|
||||
? expandableBadgeStylesheet.containerLastInSequence
|
||||
: expandableBadgeStylesheet.containerSpacing),
|
||||
style,
|
||||
],
|
||||
[isLastInSequence, resolvedDisableOuterSpacing, style]
|
||||
);
|
||||
|
||||
const pressableStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.pressable,
|
||||
isPressed && isInteractive
|
||||
? expandableBadgeStylesheet.pressablePressed
|
||||
: null,
|
||||
isExpanded && expandableBadgeStylesheet.pressableExpanded,
|
||||
],
|
||||
[isExpanded, isInteractive, isPressed]
|
||||
);
|
||||
|
||||
const accessibilityState = useMemo(
|
||||
() => (isInteractive ? { expanded: isExpanded } : undefined),
|
||||
[isExpanded, isInteractive]
|
||||
);
|
||||
|
||||
const labelStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.label,
|
||||
isLoading && expandableBadgeStylesheet.labelLoading,
|
||||
],
|
||||
[isLoading]
|
||||
);
|
||||
|
||||
const shimmerLabelTextStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.label,
|
||||
isLoading && expandableBadgeStylesheet.labelLoading,
|
||||
expandableBadgeStylesheet.shimmerText,
|
||||
shimmerLabelStyle,
|
||||
],
|
||||
[isLoading, shimmerLabelStyle]
|
||||
);
|
||||
|
||||
const shimmerSecondaryTextStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.secondaryLabel,
|
||||
expandableBadgeStylesheet.shimmerText,
|
||||
shimmerSecondaryStyle,
|
||||
],
|
||||
[shimmerSecondaryStyle]
|
||||
);
|
||||
|
||||
const nativeShimmerTrackStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.nativeShimmerTrack,
|
||||
{ width: labelRowWidth, height: labelRowHeight },
|
||||
],
|
||||
[labelRowHeight, labelRowWidth]
|
||||
);
|
||||
|
||||
const nativeShimmerMaskStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.shimmerMaskRow,
|
||||
{ width: labelRowWidth, height: labelRowHeight },
|
||||
],
|
||||
[labelRowHeight, labelRowWidth]
|
||||
);
|
||||
|
||||
const nativeLabelMaskStyle = useMemo(
|
||||
() => [expandableBadgeStylesheet.label, { color: "#000000", opacity: 1 }],
|
||||
[]
|
||||
);
|
||||
|
||||
const nativeSecondaryMaskStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.secondaryLabel,
|
||||
{ color: "#000000", opacity: 1 },
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const nativeShimmerPeakCombinedStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.nativeShimmerPeak,
|
||||
nativeShimmerPeakStyle,
|
||||
{ width: nativeShimmerPeakWidth, height: labelRowHeight },
|
||||
],
|
||||
[labelRowHeight, nativeShimmerPeakStyle, nativeShimmerPeakWidth]
|
||||
);
|
||||
|
||||
const chevronStyle = useMemo(
|
||||
() => [
|
||||
expandableBadgeStylesheet.chevron,
|
||||
isExpanded && expandableBadgeStylesheet.chevronExpanded,
|
||||
],
|
||||
[isExpanded]
|
||||
);
|
||||
|
||||
const IconComponent = icon;
|
||||
const iconColor = isError
|
||||
? theme.colors.destructive
|
||||
@@ -1493,186 +1599,142 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
expandableBadgeStylesheet.container,
|
||||
!resolvedDisableOuterSpacing &&
|
||||
(isLastInSequence
|
||||
? expandableBadgeStylesheet.containerLastInSequence
|
||||
: expandableBadgeStylesheet.containerSpacing),
|
||||
style,
|
||||
]}
|
||||
style={containerStyle}
|
||||
testID={testID}
|
||||
>
|
||||
<Pressable
|
||||
onPress={isInteractive ? onToggle : undefined}
|
||||
onHoverIn={isInteractive ? () => setIsHovered(true) : undefined}
|
||||
onHoverOut={
|
||||
isInteractive
|
||||
? () => {
|
||||
setIsHovered(false);
|
||||
setIsPressed(false);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onPressIn={isInteractive ? () => setIsPressed(true) : undefined}
|
||||
onPressOut={isInteractive ? () => setIsPressed(false) : undefined}
|
||||
disabled={!isInteractive}
|
||||
accessibilityRole={isInteractive ? "button" : undefined}
|
||||
accessibilityState={isInteractive ? { expanded: isExpanded } : undefined}
|
||||
style={({ pressed }) => [
|
||||
expandableBadgeStylesheet.pressable,
|
||||
pressed && isInteractive
|
||||
? expandableBadgeStylesheet.pressablePressed
|
||||
: null,
|
||||
isExpanded && expandableBadgeStylesheet.pressableExpanded,
|
||||
]}
|
||||
accessibilityState={accessibilityState}
|
||||
style={pressableStyle}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<View style={expandableBadgeStylesheet.headerRow}>
|
||||
<View style={expandableBadgeStylesheet.iconBadge}>{iconNode}</View>
|
||||
<View style={expandableBadgeStylesheet.headerRow}>
|
||||
<View style={expandableBadgeStylesheet.iconBadge}>{iconNode}</View>
|
||||
<View
|
||||
style={expandableBadgeStylesheet.labelRow}
|
||||
onLayout={shouldMeasureNativeShimmer ? handleLabelRowLayout : undefined}
|
||||
>
|
||||
<Text
|
||||
style={labelStyle}
|
||||
numberOfLines={1}
|
||||
onLayout={shouldMeasureWebShimmer ? handleLabelLayout : undefined}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{secondaryLabel ? (
|
||||
<Text
|
||||
style={expandableBadgeStylesheet.secondaryLabel}
|
||||
numberOfLines={1}
|
||||
onLayout={shouldMeasureWebShimmer ? handleSecondaryLayout : undefined}
|
||||
>
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
{isWebShimmer ? (
|
||||
<View
|
||||
style={expandableBadgeStylesheet.labelRow}
|
||||
onLayout={shouldMeasureNativeShimmer ? handleLabelRowLayout : undefined}
|
||||
style={expandableBadgeStylesheet.shimmerOverlay}
|
||||
pointerEvents="none"
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
expandableBadgeStylesheet.label,
|
||||
isLoading && expandableBadgeStylesheet.labelLoading,
|
||||
]}
|
||||
style={shimmerLabelTextStyle}
|
||||
numberOfLines={1}
|
||||
onLayout={shouldMeasureWebShimmer ? handleLabelLayout : undefined}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{secondaryLabel ? (
|
||||
<Text
|
||||
style={expandableBadgeStylesheet.secondaryLabel}
|
||||
style={shimmerSecondaryTextStyle}
|
||||
numberOfLines={1}
|
||||
onLayout={shouldMeasureWebShimmer ? handleSecondaryLayout : undefined}
|
||||
>
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
{isWebShimmer ? (
|
||||
<View
|
||||
style={expandableBadgeStylesheet.shimmerOverlay}
|
||||
pointerEvents="none"
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
expandableBadgeStylesheet.label,
|
||||
isLoading && expandableBadgeStylesheet.labelLoading,
|
||||
expandableBadgeStylesheet.shimmerText,
|
||||
shimmerLabelStyle,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{secondaryLabel ? (
|
||||
</View>
|
||||
) : null}
|
||||
{isNativeShimmer ? (
|
||||
<View
|
||||
style={expandableBadgeStylesheet.shimmerOverlay}
|
||||
pointerEvents="none"
|
||||
>
|
||||
<MaskedView
|
||||
style={nativeShimmerTrackStyle}
|
||||
maskElement={
|
||||
<View style={nativeShimmerMaskStyle}>
|
||||
<Text
|
||||
style={[
|
||||
expandableBadgeStylesheet.secondaryLabel,
|
||||
expandableBadgeStylesheet.shimmerText,
|
||||
shimmerSecondaryStyle,
|
||||
]}
|
||||
style={nativeLabelMaskStyle}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{secondaryLabel}
|
||||
{label}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
</View>
|
||||
) : null}
|
||||
{isNativeShimmer ? (
|
||||
{secondaryLabel ? (
|
||||
<Text
|
||||
style={nativeSecondaryMaskStyle}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={expandableBadgeStylesheet.shimmerOverlay}
|
||||
pointerEvents="none"
|
||||
style={nativeShimmerTrackStyle}
|
||||
>
|
||||
<MaskedView
|
||||
style={[
|
||||
expandableBadgeStylesheet.nativeShimmerTrack,
|
||||
{ width: labelRowWidth, height: labelRowHeight },
|
||||
]}
|
||||
maskElement={
|
||||
<View
|
||||
style={[
|
||||
expandableBadgeStylesheet.shimmerMaskRow,
|
||||
{ width: labelRowWidth, height: labelRowHeight },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
expandableBadgeStylesheet.label,
|
||||
{ color: "#000000", opacity: 1 },
|
||||
]}
|
||||
numberOfLines={1}
|
||||
<Animated.View style={nativeShimmerPeakCombinedStyle}>
|
||||
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
||||
<Defs>
|
||||
<SvgLinearGradient
|
||||
id={nativeGradientIdRef.current}
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{secondaryLabel ? (
|
||||
<Text
|
||||
style={[
|
||||
expandableBadgeStylesheet.secondaryLabel,
|
||||
{ color: "#000000", opacity: 1 },
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
expandableBadgeStylesheet.nativeShimmerTrack,
|
||||
{ width: labelRowWidth, height: labelRowHeight },
|
||||
]}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
expandableBadgeStylesheet.nativeShimmerPeak,
|
||||
nativeShimmerPeakStyle,
|
||||
{ width: nativeShimmerPeakWidth, height: labelRowHeight },
|
||||
]}
|
||||
>
|
||||
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
||||
<Defs>
|
||||
<SvgLinearGradient
|
||||
id={nativeGradientIdRef.current}
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<Stop offset="0%" stopColor="#ffffff" stopOpacity={0} />
|
||||
<Stop offset="50%" stopColor="#ffffff" stopOpacity={1} />
|
||||
<Stop offset="100%" stopColor="#ffffff" stopOpacity={0} />
|
||||
</SvgLinearGradient>
|
||||
</Defs>
|
||||
<Rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill={`url(#${nativeGradientIdRef.current})`}
|
||||
/>
|
||||
</Svg>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</MaskedView>
|
||||
<Stop offset="0%" stopColor="#ffffff" stopOpacity={0} />
|
||||
<Stop offset="50%" stopColor="#ffffff" stopOpacity={1} />
|
||||
<Stop offset="100%" stopColor="#ffffff" stopOpacity={0} />
|
||||
</SvgLinearGradient>
|
||||
</Defs>
|
||||
<Rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill={`url(#${nativeGradientIdRef.current})`}
|
||||
/>
|
||||
</Svg>
|
||||
</Animated.View>
|
||||
</View>
|
||||
) : null}
|
||||
</MaskedView>
|
||||
</View>
|
||||
{isInteractive && hovered ? (
|
||||
<ChevronRight
|
||||
size={14}
|
||||
color={theme.colors.foregroundMuted}
|
||||
style={[
|
||||
expandableBadgeStylesheet.chevron,
|
||||
{ transform: [{ rotate: isExpanded ? "90deg" : "0deg" }] },
|
||||
]}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
{isInteractive && isHovered ? (
|
||||
<ChevronRight
|
||||
size={14}
|
||||
color={theme.colors.foregroundMuted}
|
||||
style={chevronStyle}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
{detailContent ? (
|
||||
<Pressable
|
||||
|
||||
@@ -40,7 +40,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
outline: {
|
||||
backgroundColor: "transparent",
|
||||
borderColor: theme.colors.border,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
ghost: {
|
||||
backgroundColor: "transparent",
|
||||
@@ -59,7 +59,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
text: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
textDefault: {
|
||||
color: theme.colors.palette.white,
|
||||
|
||||
@@ -41,7 +41,7 @@ export function DesktopPermissionRow({
|
||||
<Text style={styles.permissionStatusText}>Granted</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Button variant="secondary" size="sm" onPress={onRequest} disabled={isRequesting}>
|
||||
<Button variant="outline" size="sm" onPress={onRequest} disabled={isRequesting}>
|
||||
{isRequesting ? "Requesting..." : "Request"}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { View, Text, Pressable } from "react-native";
|
||||
import { View, Text } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { RotateCw } from "lucide-react-native";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row";
|
||||
import { useDesktopPermissions } from "@/desktop/permissions/use-desktop-permissions";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
|
||||
export function DesktopPermissionsSection() {
|
||||
const { theme } = useUnistyles();
|
||||
@@ -22,26 +24,23 @@ export function DesktopPermissionsSection() {
|
||||
const isBusy = isRefreshing || requestingPermission !== null;
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<View style={settingsStyles.section}>
|
||||
<View style={styles.permissionSectionHeader}>
|
||||
<Text style={styles.sectionTitle}>Desktop Permissions</Text>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.permissionRefreshButton,
|
||||
isBusy && styles.permissionRefreshButtonDisabled,
|
||||
pressed && { opacity: 0.85 },
|
||||
]}
|
||||
<Text style={settingsStyles.sectionTitle}>Desktop Permissions</Text>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
leftIcon={<RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />}
|
||||
onPress={() => {
|
||||
void refreshPermissions();
|
||||
}}
|
||||
disabled={isBusy}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Refresh desktop permissions"
|
||||
>
|
||||
<RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
</Pressable>
|
||||
{isRefreshing ? "Refreshing..." : "Refresh"}
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.audioCard}>
|
||||
<View style={settingsStyles.card}>
|
||||
<DesktopPermissionRow
|
||||
title="Notifications"
|
||||
status={snapshot?.notifications ?? null}
|
||||
@@ -65,16 +64,6 @@ export function DesktopPermissionsSection() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
section: {
|
||||
marginBottom: theme.spacing[6],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
marginBottom: 0,
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
permissionSectionHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@@ -82,24 +71,4 @@ const styles = StyleSheet.create((theme) => ({
|
||||
gap: theme.spacing[2],
|
||||
marginBottom: theme.spacing[3],
|
||||
},
|
||||
permissionRefreshButton: {
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
permissionRefreshButtonDisabled: {
|
||||
opacity: theme.opacity[50],
|
||||
},
|
||||
audioCard: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -3,8 +3,9 @@ import { ActivityIndicator, Alert, Image, Pressable, Text, View } from "react-na
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import * as QRCode from "qrcode";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { ArrowUpRight } from "lucide-react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
import { ArrowUpRight, Play, Pause, RotateCw, Terminal, Copy, FileText, Smartphone } from "lucide-react-native";
|
||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAppSettings } from "@/hooks/use-settings";
|
||||
@@ -35,6 +36,7 @@ export interface LocalDaemonSectionProps {
|
||||
}
|
||||
|
||||
export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const showSection = shouldUseManagedDesktopDaemon();
|
||||
const { settings, updateSettings } = useAppSettings();
|
||||
const [managedStatus, setManagedStatus] = useState<ManagedDaemonStatus | null>(null);
|
||||
@@ -331,9 +333,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<View style={settingsStyles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>Built-in daemon</Text>
|
||||
<Text style={settingsStyles.sectionTitle}>Built-in daemon</Text>
|
||||
<Pressable
|
||||
accessibilityRole="link"
|
||||
onPress={() => void openExternalUrl(ADVANCED_DAEMON_SETTINGS_URL)}
|
||||
@@ -343,7 +345,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
<ArrowUpRight size={14} color={styles.sectionLinkText.color} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<View style={settingsStyles.card}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowTitle}>Status</Text>
|
||||
@@ -364,9 +366,11 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
style={styles.primaryActionButton}
|
||||
leftIcon={isDaemonManagementPaused
|
||||
? <Play size={theme.iconSize.sm} color={theme.colors.foreground} />
|
||||
: <Pause size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||
onPress={handleToggleDaemonManagement}
|
||||
disabled={isUpdatingDaemonManagement}
|
||||
>
|
||||
@@ -388,9 +392,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
) : null}
|
||||
</View>
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
style={styles.primaryActionButton}
|
||||
leftIcon={<RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||
onPress={handleUpdateLocalDaemon}
|
||||
disabled={isRestartingDaemon}
|
||||
>
|
||||
@@ -410,9 +414,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
{cliStatusMessage ? <Text style={styles.statusText}>{cliStatusMessage}</Text> : null}
|
||||
</View>
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
style={styles.secondaryActionButton}
|
||||
leftIcon={<Terminal size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||
onPress={handleToggleCliShim}
|
||||
disabled={isInstallingCli}
|
||||
>
|
||||
@@ -434,13 +438,14 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
</View>
|
||||
<View style={styles.actionGroup}>
|
||||
{(managedLogs?.logPath ?? managedStatus?.logPath) ? (
|
||||
<Button variant="secondary" size="sm" onPress={handleCopyLogPath}>
|
||||
<Button variant="outline" size="sm" leftIcon={<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />} onPress={handleCopyLogPath}>
|
||||
Copy path
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
leftIcon={<FileText size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||
onPress={handleOpenLogs}
|
||||
disabled={!managedLogs}
|
||||
>
|
||||
@@ -455,7 +460,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
Connect your phone to this computer.
|
||||
</Text>
|
||||
</View>
|
||||
<Button variant="secondary" size="sm" style={styles.secondaryActionButton} onPress={handleOpenPairingModal}>
|
||||
<Button variant="outline" size="sm" leftIcon={<Smartphone size={theme.iconSize.sm} color={theme.colors.foreground} />} onPress={handleOpenPairingModal}>
|
||||
Pair device
|
||||
</Button>
|
||||
</View>
|
||||
@@ -488,7 +493,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||
{cliInstallInstructions?.commands ?? ""}
|
||||
</Text>
|
||||
<View style={styles.modalActions}>
|
||||
<Button variant="secondary" size="sm" onPress={() => setIsCliInstallModalOpen(false)}>
|
||||
<Button variant="outline" size="sm" onPress={() => setIsCliInstallModalOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button size="sm" onPress={handleCopyCliInstallCommands}>
|
||||
@@ -627,7 +632,7 @@ function PairingOfferDialogContent(input: {
|
||||
{pairingOffer.url}
|
||||
</Text>
|
||||
<View style={styles.modalActions}>
|
||||
<Button variant="secondary" size="sm" onPress={onCopyLink}>
|
||||
<Button variant="outline" size="sm" onPress={onCopyLink}>
|
||||
Copy link
|
||||
</Button>
|
||||
</View>
|
||||
@@ -636,9 +641,6 @@ function PairingOfferDialogContent(input: {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
section: {
|
||||
marginBottom: theme.spacing[6],
|
||||
},
|
||||
sectionHeader: {
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
@@ -646,11 +648,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
marginBottom: theme.spacing[3],
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
sectionLink: {
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
@@ -660,13 +657,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
overflow: "hidden",
|
||||
},
|
||||
row: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@@ -692,12 +682,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "flex-end",
|
||||
gap: 2,
|
||||
},
|
||||
primaryActionButton: {
|
||||
minWidth: 124,
|
||||
},
|
||||
secondaryActionButton: {
|
||||
minWidth: 112,
|
||||
},
|
||||
rowTitle: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.base,
|
||||
|
||||
@@ -42,6 +42,7 @@ import { LocalDaemonSection } from "@/desktop/components/desktop-updates-section
|
||||
import { useDesktopAppUpdater } from "@/desktop/updates/use-desktop-app-updater";
|
||||
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
||||
import { resolveAppVersion } from "@/utils/app-version";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
|
||||
const delay = (ms: number) =>
|
||||
new Promise<void>((resolve) => {
|
||||
@@ -112,16 +113,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
maxWidth: 720,
|
||||
alignSelf: "center",
|
||||
},
|
||||
section: {
|
||||
marginBottom: theme.spacing[6],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
marginBottom: theme.spacing[3],
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
label: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
@@ -139,10 +130,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
// Host card styles
|
||||
hostCard: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
marginBottom: theme.spacing[3],
|
||||
overflow: "hidden",
|
||||
},
|
||||
@@ -286,10 +273,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
// Add/Edit form
|
||||
formCard: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
padding: theme.spacing[4],
|
||||
marginBottom: theme.spacing[3],
|
||||
gap: theme.spacing[4],
|
||||
@@ -328,10 +311,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
// Audio settings card
|
||||
audioCard: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
overflow: "hidden",
|
||||
},
|
||||
audioRow: {
|
||||
@@ -375,10 +354,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
// Empty state
|
||||
emptyCard: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
padding: theme.spacing[4],
|
||||
marginBottom: theme.spacing[3],
|
||||
},
|
||||
@@ -389,10 +364,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
// Dev section
|
||||
devCard: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
overflow: "hidden",
|
||||
},
|
||||
devButton: {
|
||||
@@ -501,7 +472,7 @@ function DesktopAppUpdateRow() {
|
||||
</View>
|
||||
<View style={styles.aboutUpdateActions}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onPress={handleCheckForUpdates}
|
||||
disabled={isChecking || isInstalling}
|
||||
@@ -722,11 +693,11 @@ export default function SettingsScreen() {
|
||||
<ScrollView style={styles.scrollView} contentContainerStyle={{ paddingBottom: insets.bottom }}>
|
||||
<View style={styles.content}>
|
||||
{/* Host Management */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Hosts</Text>
|
||||
<View style={settingsStyles.section}>
|
||||
<Text style={settingsStyles.sectionTitle}>Hosts</Text>
|
||||
|
||||
{daemons.length === 0 ? (
|
||||
<View style={styles.emptyCard}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>No hosts configured</Text>
|
||||
</View>
|
||||
) : (
|
||||
@@ -878,9 +849,9 @@ export default function SettingsScreen() {
|
||||
/>
|
||||
|
||||
{/* Appearance */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Appearance</Text>
|
||||
<View style={styles.audioCard}>
|
||||
<View style={settingsStyles.section}>
|
||||
<Text style={settingsStyles.sectionTitle}>Appearance</Text>
|
||||
<View style={[settingsStyles.card, styles.audioCard]}>
|
||||
<View style={styles.audioRow}>
|
||||
<View style={styles.audioRowContent}>
|
||||
<Text style={styles.audioRowTitle}>Theme</Text>
|
||||
@@ -915,9 +886,9 @@ export default function SettingsScreen() {
|
||||
{isDesktop ? <LocalDaemonSection appVersion={appVersion} /> : null}
|
||||
|
||||
{/* About */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>About</Text>
|
||||
<View style={styles.audioCard}>
|
||||
<View style={settingsStyles.section}>
|
||||
<Text style={settingsStyles.sectionTitle}>About</Text>
|
||||
<View style={[settingsStyles.card, styles.audioCard]}>
|
||||
<View style={styles.audioRow}>
|
||||
<View style={styles.audioRowContent}>
|
||||
<Text style={styles.audioRowTitle}>Version</Text>
|
||||
@@ -1332,7 +1303,7 @@ function ConnectionRow({
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
backgroundColor: theme.colors.surface1,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: theme.colors.foreground, fontSize: 12, flex: 1 }}>
|
||||
@@ -1400,7 +1371,7 @@ function DaemonCard({
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.hostCard}
|
||||
style={[settingsStyles.card, styles.hostCard]}
|
||||
testID={`daemon-card-${daemon.serverId}`}
|
||||
>
|
||||
<View style={styles.hostCardContent}>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
export const settingsStyles = StyleSheet.create((theme) => ({
|
||||
section: {
|
||||
marginBottom: theme.spacing[6],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
marginBottom: theme.spacing[3],
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
card: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user