diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx
index b1aae586b..9b72a80c4 100644
--- a/packages/app/src/components/message.tsx
+++ b/packages/app/src/components/message.tsx
@@ -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 (
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 }) => (
- <>
-
- {iconNode}
+
+ {iconNode}
+
+
+ {label}
+
+ {secondaryLabel ? (
+
+ {secondaryLabel}
+
+ ) : (
+
+ )}
+ {isWebShimmer ? (
{label}
{secondaryLabel ? (
{secondaryLabel}
) : (
)}
- {isWebShimmer ? (
-
-
- {label}
-
- {secondaryLabel ? (
+
+ ) : null}
+ {isNativeShimmer ? (
+
+
- {secondaryLabel}
+ {label}
- ) : (
-
- )}
-
- ) : null}
- {isNativeShimmer ? (
+ {secondaryLabel ? (
+
+ {secondaryLabel}
+
+ ) : (
+
+ )}
+
+ }
+ >
-
-
+
- {secondaryLabel ? (
-
- {secondaryLabel}
-
- ) : (
-
- )}
-
- }
- >
-
-
-
-
-
-
+
+
+
+
+
+
+
+
- ) : null}
+
- {isInteractive && hovered ? (
-
- ) : null}
-
- >
- )}
+ ) : null}
+
+ {isInteractive && isHovered ? (
+
+ ) : null}
+
{detailContent ? (
({
},
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,
diff --git a/packages/app/src/desktop/components/desktop-permission-row.tsx b/packages/app/src/desktop/components/desktop-permission-row.tsx
index bc4240431..628dcaa25 100644
--- a/packages/app/src/desktop/components/desktop-permission-row.tsx
+++ b/packages/app/src/desktop/components/desktop-permission-row.tsx
@@ -41,7 +41,7 @@ export function DesktopPermissionRow({
Granted
) : (
-
-
+
({
- 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",
- },
}));
diff --git a/packages/app/src/desktop/components/desktop-updates-section.tsx b/packages/app/src/desktop/components/desktop-updates-section.tsx
index 68ad0b0ba..e0d77d0a7 100644
--- a/packages/app/src/desktop/components/desktop-updates-section.tsx
+++ b/packages/app/src/desktop/components/desktop-updates-section.tsx
@@ -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(null);
@@ -331,9 +333,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
}
return (
-
+
- Built-in daemon
+ Built-in daemon
void openExternalUrl(ADVANCED_DAEMON_SETTINGS_URL)}
@@ -343,7 +345,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
-
+
Status
@@ -364,9 +366,11 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
+ : }
onPress={handleToggleDaemonManagement}
disabled={isUpdatingDaemonManagement}
>
@@ -388,9 +392,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
) : null}
}
onPress={handleUpdateLocalDaemon}
disabled={isRestartingDaemon}
>
@@ -410,9 +414,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
{cliStatusMessage ? {cliStatusMessage} : null}
}
onPress={handleToggleCliShim}
disabled={isInstallingCli}
>
@@ -434,13 +438,14 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
{(managedLogs?.logPath ?? managedStatus?.logPath) ? (
-
+ } onPress={handleCopyLogPath}>
Copy path
) : null}
}
onPress={handleOpenLogs}
disabled={!managedLogs}
>
@@ -455,7 +460,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
Connect your phone to this computer.
-
+ } onPress={handleOpenPairingModal}>
Pair device
@@ -488,7 +493,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
{cliInstallInstructions?.commands ?? ""}
- setIsCliInstallModalOpen(false)}>
+ setIsCliInstallModalOpen(false)}>
Close
@@ -627,7 +632,7 @@ function PairingOfferDialogContent(input: {
{pairingOffer.url}
-
+
Copy link
@@ -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,
diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx
index 162a7b02d..9ee2c3ded 100644
--- a/packages/app/src/screens/settings-screen.tsx
+++ b/packages/app/src/screens/settings-screen.tsx
@@ -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((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() {
{/* Host Management */}
-
- Hosts
+
+ Hosts
{daemons.length === 0 ? (
-
+
No hosts configured
) : (
@@ -878,9 +849,9 @@ export default function SettingsScreen() {
/>
{/* Appearance */}
-
- Appearance
-
+
+ Appearance
+
Theme
@@ -915,9 +886,9 @@ export default function SettingsScreen() {
{isDesktop ? : null}
{/* About */}
-
- About
-
+
+ About
+
Version
@@ -1332,7 +1303,7 @@ function ConnectionRow({
borderRadius: 12,
borderWidth: 1,
borderColor: theme.colors.border,
- backgroundColor: theme.colors.surface2,
+ backgroundColor: theme.colors.surface1,
}}
>
@@ -1400,7 +1371,7 @@ function DaemonCard({
return (
diff --git a/packages/app/src/styles/settings.ts b/packages/app/src/styles/settings.ts
new file mode 100644
index 000000000..0335c7da4
--- /dev/null
+++ b/packages/app/src/styles/settings.ts
@@ -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",
+ },
+}));