| author | |
| committer | |
| log | 4664c572a78231611491f63f677f0e007ebaab03 |
| tree | 0311f651532fc164acfd4b419fed1d6de3b12763 |
| parent | 579266caa89238611ddb974ffebe9968c6a6e5d5 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Pixel Perfect <me@pixp.cc>26 files changed, 688 insertions(+), 172 deletions(-)
apps/mobile/generated-uniwind-default-theme-variables.json+14| ... | @@ -6,6 +6,13 @@ | ... | @@ -6,6 +6,13 @@ |
| 6 | "--color-card": "#ffffff", | 6 | "--color-card": "#ffffff", |
| 7 | "--color-card-alt": "#f5f5f5", | 7 | "--color-card-alt": "#f5f5f5", |
| 8 | "--color-card-translucent": "rgba(255, 255, 255, 0.8)", | 8 | "--color-card-translucent": "rgba(255, 255, 255, 0.8)", |
| 9 | "--color-thread-canvas": "#f5f5f5", | ||
| 10 | "--color-thread-selected": "#ffffff", | ||
| 11 | "--color-thread-selected-foreground": "#262626", | ||
| 12 | "--color-thread-selected-foreground-muted": "#737373", | ||
| 13 | "--color-composer-panel": "rgba(245, 245, 245, 0.88)", | ||
| 14 | "--color-composer-surface": "rgba(255, 255, 255, 0.94)", | ||
| 15 | "--color-composer-border": "rgba(0, 0, 0, 0.1)", | ||
| 9 | "--color-foreground": "#262626", | 16 | "--color-foreground": "#262626", |
| 10 | "--color-foreground-secondary": "#525252", | 17 | "--color-foreground-secondary": "#525252", |
| 11 | "--color-foreground-muted": "#737373", | 18 | "--color-foreground-muted": "#737373", |
| ... | @@ -76,6 +83,13 @@ | ... | @@ -76,6 +83,13 @@ |
| 76 | "--color-card": "#171717", | 83 | "--color-card": "#171717", |
| 77 | "--color-card-alt": "#1c1c1c", | 84 | "--color-card-alt": "#1c1c1c", |
| 78 | "--color-card-translucent": "rgba(17, 17, 17, 0.8)", | 85 | "--color-card-translucent": "rgba(17, 17, 17, 0.8)", |
| 86 | "--color-thread-canvas": "#1c1c1c", | ||
| 87 | "--color-thread-selected": "#171717", | ||
| 88 | "--color-thread-selected-foreground": "#f5f5f5", | ||
| 89 | "--color-thread-selected-foreground-muted": "#8e8e93", | ||
| 90 | "--color-composer-panel": "rgba(28, 28, 28, 0.92)", | ||
| 91 | "--color-composer-surface": "rgba(23, 23, 23, 0.9)", | ||
| 92 | "--color-composer-border": "rgba(255, 255, 255, 0.08)", | ||
| 79 | "--color-foreground": "#f5f5f5", | 93 | "--color-foreground": "#f5f5f5", |
| 80 | "--color-foreground-secondary": "#a3a3a3", | 94 | "--color-foreground-secondary": "#a3a3a3", |
| 81 | "--color-foreground-muted": "#8e8e93", | 95 | "--color-foreground-muted": "#8e8e93", |
apps/mobile/generated-uniwind-themes.css+70| ... | @@ -128,6 +128,13 @@ | ... | @@ -128,6 +128,13 @@ |
| 128 | --color-card: #fdfafd; | 128 | --color-card: #fdfafd; |
| 129 | --color-card-alt: #faf3fb; | 129 | --color-card-alt: #faf3fb; |
| 130 | --color-card-translucent: rgba(253, 250, 253, 0.8); | 130 | --color-card-translucent: rgba(253, 250, 253, 0.8); |
| 131 | --color-thread-canvas: #faf3fb; | ||
| 132 | --color-thread-selected: #fdfafd; | ||
| 133 | --color-thread-selected-foreground: #501854; | ||
| 134 | --color-thread-selected-foreground-muted: #ac1668; | ||
| 135 | --color-composer-panel: rgba(250, 243, 251, 0.88); | ||
| 136 | --color-composer-surface: rgba(253, 250, 253, 0.94); | ||
| 137 | --color-composer-border: rgba(238, 225, 237, 0.54); | ||
| 131 | --color-foreground: #501854; | 138 | --color-foreground: #501854; |
| 132 | --color-foreground-secondary: #ac1668; | 139 | --color-foreground-secondary: #ac1668; |
| 133 | --color-foreground-muted: #8d1255; | 140 | --color-foreground-muted: #8d1255; |
| ... | @@ -256,6 +263,13 @@ | ... | @@ -256,6 +263,13 @@ |
| 256 | --color-card: #2c2631; | 263 | --color-card: #2c2631; |
| 257 | --color-card-alt: #29232d; | 264 | --color-card-alt: #29232d; |
| 258 | --color-card-translucent: rgba(44, 38, 49, 0.8); | 265 | --color-card-translucent: rgba(44, 38, 49, 0.8); |
| 266 | --color-thread-canvas: #29232d; | ||
| 267 | --color-thread-selected: #2c2631; | ||
| 268 | --color-thread-selected-foreground: #f9f8fb; | ||
| 269 | --color-thread-selected-foreground-muted: #e7d0dd; | ||
| 270 | --color-composer-panel: rgba(41, 35, 45, 0.92); | ||
| 271 | --color-composer-surface: rgba(44, 38, 49, 0.9); | ||
| 272 | --color-composer-border: rgba(39, 36, 44, 0.46); | ||
| 259 | --color-foreground: #f9f8fb; | 273 | --color-foreground: #f9f8fb; |
| 260 | --color-foreground-secondary: #e7d0dd; | 274 | --color-foreground-secondary: #e7d0dd; |
| 261 | --color-foreground-muted: #e7d0dd; | 275 | --color-foreground-muted: #e7d0dd; |
| ... | @@ -384,6 +398,13 @@ | ... | @@ -384,6 +398,13 @@ |
| 384 | --color-card: #ecefed; | 398 | --color-card: #ecefed; |
| 385 | --color-card-alt: #f3f7f4; | 399 | --color-card-alt: #f3f7f4; |
| 386 | --color-card-translucent: rgba(236, 239, 237, 0.8); | 400 | --color-card-translucent: rgba(236, 239, 237, 0.8); |
| 401 | --color-thread-canvas: #f3f7f4; | ||
| 402 | --color-thread-selected: #ecefed; | ||
| 403 | --color-thread-selected-foreground: #241523; | ||
| 404 | --color-thread-selected-foreground-muted: #746c73; | ||
| 405 | --color-composer-panel: rgba(243, 247, 244, 0.88); | ||
| 406 | --color-composer-surface: rgba(236, 239, 237, 0.94); | ||
| 407 | --color-composer-border: rgba(203, 213, 209, 0.54); | ||
| 387 | --color-foreground: #241523; | 408 | --color-foreground: #241523; |
| 388 | --color-foreground-secondary: #746c73; | 409 | --color-foreground-secondary: #746c73; |
| 389 | --color-foreground-muted: #6e696f; | 410 | --color-foreground-muted: #6e696f; |
| ... | @@ -512,6 +533,13 @@ | ... | @@ -512,6 +533,13 @@ |
| 512 | --color-card: #36413c; | 533 | --color-card: #36413c; |
| 513 | --color-card-alt: #1b2821; | 534 | --color-card-alt: #1b2821; |
| 514 | --color-card-translucent: rgba(54, 65, 60, 0.8); | 535 | --color-card-translucent: rgba(54, 65, 60, 0.8); |
| 536 | --color-thread-canvas: #1b2821; | ||
| 537 | --color-thread-selected: #36413c; | ||
| 538 | --color-thread-selected-foreground: #fffaff; | ||
| 539 | --color-thread-selected-foreground-muted: #919595; | ||
| 540 | --color-composer-panel: rgba(27, 40, 33, 0.92); | ||
| 541 | --color-composer-surface: rgba(54, 65, 60, 0.9); | ||
| 542 | --color-composer-border: rgba(65, 95, 79, 0.46); | ||
| 515 | --color-foreground: #fffaff; | 543 | --color-foreground: #fffaff; |
| 516 | --color-foreground-secondary: #919595; | 544 | --color-foreground-secondary: #919595; |
| 517 | --color-foreground-muted: #9da5a2; | 545 | --color-foreground-muted: #9da5a2; |
| ... | @@ -640,6 +668,13 @@ | ... | @@ -640,6 +668,13 @@ |
| 640 | --color-card: #edeff1; | 668 | --color-card: #edeff1; |
| 641 | --color-card-alt: #f5f7f8; | 669 | --color-card-alt: #f5f7f8; |
| 642 | --color-card-translucent: rgba(237, 239, 241, 0.8); | 670 | --color-card-translucent: rgba(237, 239, 241, 0.8); |
| 671 | --color-thread-canvas: #f5f7f8; | ||
| 672 | --color-thread-selected: #edeff1; | ||
| 673 | --color-thread-selected-foreground: #241523; | ||
| 674 | --color-thread-selected-foreground-muted: #746c75; | ||
| 675 | --color-composer-panel: rgba(245, 247, 248, 0.88); | ||
| 676 | --color-composer-surface: rgba(237, 239, 241, 0.94); | ||
| 677 | --color-composer-border: rgba(205, 212, 220, 0.54); | ||
| 643 | --color-foreground: #241523; | 678 | --color-foreground: #241523; |
| 644 | --color-foreground-secondary: #746c75; | 679 | --color-foreground-secondary: #746c75; |
| 645 | --color-foreground-muted: #6f6873; | 680 | --color-foreground-muted: #6f6873; |
| ... | @@ -768,6 +803,13 @@ | ... | @@ -768,6 +803,13 @@ |
| 768 | --color-card: #333b45; | 803 | --color-card: #333b45; |
| 769 | --color-card-alt: #17212b; | 804 | --color-card-alt: #17212b; |
| 770 | --color-card-translucent: rgba(51, 59, 69, 0.8); | 805 | --color-card-translucent: rgba(51, 59, 69, 0.8); |
| 806 | --color-thread-canvas: #17212b; | ||
| 807 | --color-thread-selected: #333b45; | ||
| 808 | --color-thread-selected-foreground: #fffaff; | ||
| 809 | --color-thread-selected-foreground-muted: #8d8f97; | ||
| 810 | --color-composer-panel: rgba(23, 33, 43, 0.92); | ||
| 811 | --color-composer-surface: rgba(51, 59, 69, 0.9); | ||
| 812 | --color-composer-border: rgba(64, 85, 103, 0.46); | ||
| 771 | --color-foreground: #fffaff; | 813 | --color-foreground: #fffaff; |
| 772 | --color-foreground-secondary: #8d8f97; | 814 | --color-foreground-secondary: #8d8f97; |
| 773 | --color-foreground-muted: #969ca6; | 815 | --color-foreground-muted: #969ca6; |
| ... | @@ -896,6 +938,13 @@ | ... | @@ -896,6 +938,13 @@ |
| 896 | --color-card: #f1efee; | 938 | --color-card: #f1efee; |
| 897 | --color-card-alt: #f9f7f5; | 939 | --color-card-alt: #f9f7f5; |
| 898 | --color-card-translucent: rgba(241, 239, 238, 0.8); | 940 | --color-card-translucent: rgba(241, 239, 238, 0.8); |
| 941 | --color-thread-canvas: #f9f7f5; | ||
| 942 | --color-thread-selected: #f1efee; | ||
| 943 | --color-thread-selected-foreground: #241523; | ||
| 944 | --color-thread-selected-foreground-muted: #766c74; | ||
| 945 | --color-composer-panel: rgba(249, 247, 245, 0.88); | ||
| 946 | --color-composer-surface: rgba(241, 239, 238, 0.94); | ||
| 947 | --color-composer-border: rgba(221, 210, 206, 0.54); | ||
| 899 | --color-foreground: #241523; | 948 | --color-foreground: #241523; |
| 900 | --color-foreground-secondary: #766c74; | 949 | --color-foreground-secondary: #766c74; |
| 901 | --color-foreground-muted: #74686f; | 950 | --color-foreground-muted: #74686f; |
| ... | @@ -1024,6 +1073,13 @@ | ... | @@ -1024,6 +1073,13 @@ |
| 1024 | --color-card: #433835; | 1073 | --color-card: #433835; |
| 1025 | --color-card-alt: #291e1a; | 1074 | --color-card-alt: #291e1a; |
| 1026 | --color-card-translucent: rgba(67, 56, 53, 0.8); | 1075 | --color-card-translucent: rgba(67, 56, 53, 0.8); |
| 1076 | --color-thread-canvas: #291e1a; | ||
| 1077 | --color-thread-selected: #433835; | ||
| 1078 | --color-thread-selected-foreground: #fffaff; | ||
| 1079 | --color-thread-selected-foreground-muted: #968e8f; | ||
| 1080 | --color-composer-panel: rgba(41, 30, 26, 0.92); | ||
| 1081 | --color-composer-surface: rgba(67, 56, 53, 0.9); | ||
| 1082 | --color-composer-border: rgba(102, 76, 63, 0.46); | ||
| 1027 | --color-foreground: #fffaff; | 1083 | --color-foreground: #fffaff; |
| 1028 | --color-foreground-secondary: #968e8f; | 1084 | --color-foreground-secondary: #968e8f; |
| 1029 | --color-foreground-muted: #a59996; | 1085 | --color-foreground-muted: #a59996; |
| ... | @@ -1152,6 +1208,13 @@ | ... | @@ -1152,6 +1208,13 @@ |
| 1152 | --color-card: #f0eff2; | 1208 | --color-card: #f0eff2; |
| 1153 | --color-card-alt: #f8f7f9; | 1209 | --color-card-alt: #f8f7f9; |
| 1154 | --color-card-translucent: rgba(240, 239, 242, 0.8); | 1210 | --color-card-translucent: rgba(240, 239, 242, 0.8); |
| 1211 | --color-thread-canvas: #f8f7f9; | ||
| 1212 | --color-thread-selected: #f0eff2; | ||
| 1213 | --color-thread-selected-foreground: #241523; | ||
| 1214 | --color-thread-selected-foreground-muted: #766c76; | ||
| 1215 | --color-composer-panel: rgba(248, 247, 249, 0.88); | ||
| 1216 | --color-composer-surface: rgba(240, 239, 242, 0.94); | ||
| 1217 | --color-composer-border: rgba(214, 209, 222, 0.54); | ||
| 1155 | --color-foreground: #241523; | 1218 | --color-foreground: #241523; |
| 1156 | --color-foreground-secondary: #766c76; | 1219 | --color-foreground-secondary: #766c76; |
| 1157 | --color-foreground-muted: #726874; | 1220 | --color-foreground-muted: #726874; |
| ... | @@ -1280,6 +1343,13 @@ | ... | @@ -1280,6 +1343,13 @@ |
| 1280 | --color-card: #383443; | 1343 | --color-card: #383443; |
| 1281 | --color-card-alt: #1d1929; | 1344 | --color-card-alt: #1d1929; |
| 1282 | --color-card-translucent: rgba(56, 52, 67, 0.8); | 1345 | --color-card-translucent: rgba(56, 52, 67, 0.8); |
| 1346 | --color-thread-canvas: #1d1929; | ||
| 1347 | --color-thread-selected: #383443; | ||
| 1348 | --color-thread-selected-foreground: #fffaff; | ||
| 1349 | --color-thread-selected-foreground-muted: #8e8a95; | ||
| 1350 | --color-composer-panel: rgba(29, 25, 41, 0.92); | ||
| 1351 | --color-composer-surface: rgba(56, 52, 67, 0.9); | ||
| 1352 | --color-composer-border: rgba(77, 67, 102, 0.46); | ||
| 1283 | --color-foreground: #fffaff; | 1353 | --color-foreground: #fffaff; |
| 1284 | --color-foreground-secondary: #8e8a95; | 1354 | --color-foreground-secondary: #8e8a95; |
| 1285 | --color-foreground-muted: #9690a1; | 1355 | --color-foreground-muted: #9690a1; |
apps/mobile/global.css+14| ... | @@ -19,6 +19,13 @@ | ... | @@ -19,6 +19,13 @@ |
| 19 | --color-card: #ffffff; | 19 | --color-card: #ffffff; |
| 20 | --color-card-alt: #f5f5f5; | 20 | --color-card-alt: #f5f5f5; |
| 21 | --color-card-translucent: rgba(255, 255, 255, 0.8); | 21 | --color-card-translucent: rgba(255, 255, 255, 0.8); |
| 22 | --color-thread-canvas: #f5f5f5; | ||
| 23 | --color-thread-selected: #ffffff; | ||
| 24 | --color-thread-selected-foreground: #262626; | ||
| 25 | --color-thread-selected-foreground-muted: #737373; | ||
| 26 | --color-composer-panel: rgba(245, 245, 245, 0.88); | ||
| 27 | --color-composer-surface: rgba(255, 255, 255, 0.94); | ||
| 28 | --color-composer-border: rgba(0, 0, 0, 0.1); | ||
| 22 | 29 | ||
| 23 | /* Text */ | 30 | /* Text */ |
| 24 | --color-foreground: #262626; | 31 | --color-foreground: #262626; |
| ... | @@ -123,6 +130,13 @@ | ... | @@ -123,6 +130,13 @@ |
| 123 | --color-card: #171717; | 130 | --color-card: #171717; |
| 124 | --color-card-alt: #1c1c1c; | 131 | --color-card-alt: #1c1c1c; |
| 125 | --color-card-translucent: rgba(17, 17, 17, 0.8); | 132 | --color-card-translucent: rgba(17, 17, 17, 0.8); |
| 133 | --color-thread-canvas: #1c1c1c; | ||
| 134 | --color-thread-selected: #171717; | ||
| 135 | --color-thread-selected-foreground: #f5f5f5; | ||
| 136 | --color-thread-selected-foreground-muted: #8e8e93; | ||
| 137 | --color-composer-panel: rgba(28, 28, 28, 0.92); | ||
| 138 | --color-composer-surface: rgba(23, 23, 23, 0.9); | ||
| 139 | --color-composer-border: rgba(255, 255, 255, 0.08); | ||
| 126 | 140 | ||
| 127 | /* Text */ | 141 | /* Text */ |
| 128 | --color-foreground: #f5f5f5; | 142 | --color-foreground: #f5f5f5; |
apps/mobile/src/components/AndroidScreenHeader.tsx+2| ... | @@ -48,6 +48,7 @@ export function AndroidScreenHeader(props: { | ... | @@ -48,6 +48,7 @@ export function AndroidScreenHeader(props: { |
| 48 | readonly trailing?: ReactNode; | 48 | readonly trailing?: ReactNode; |
| 49 | readonly onBack?: () => void; | 49 | readonly onBack?: () => void; |
| 50 | readonly embedded?: boolean; | 50 | readonly embedded?: boolean; |
| 51 | readonly hideBottomBorder?: boolean; | ||
| 51 | }) { | 52 | }) { |
| 52 | const insets = useSafeAreaInsets(); | 53 | const insets = useSafeAreaInsets(); |
| 53 | 54 | ||
| ... | @@ -56,6 +57,7 @@ export function AndroidScreenHeader(props: { | ... | @@ -56,6 +57,7 @@ export function AndroidScreenHeader(props: { |
| 56 | className="border-b border-header-border bg-header px-3 pb-2.5" | 57 | className="border-b border-header-border bg-header px-3 pb-2.5" |
| 57 | style={{ | 58 | style={{ |
| 58 | paddingTop: props.embedded ? 8 : Math.max(insets.top, 12), | 59 | paddingTop: props.embedded ? 8 : Math.max(insets.top, 12), |
| 60 | borderBottomWidth: props.hideBottomBorder ? 0 : undefined, | ||
| 59 | }} | 61 | }} |
| 60 | > | 62 | > |
| 61 | <View className="min-h-12 flex-row items-center gap-2"> | 63 | <View className="min-h-12 flex-row items-center gap-2"> |
apps/mobile/src/components/ThemedSwitch.tsx+47-1| ... | @@ -1,6 +1,52 @@ | ... | @@ -1,6 +1,52 @@ |
| 1 | import { Platform, Switch, type SwitchProps } from "react-native"; | 1 | import { Platform, Pressable, Switch, View, type SwitchProps } from "react-native"; |
| 2 | |||
| 3 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | ||
| 4 | import { SymbolView } from "./AppSymbol"; | ||
| 2 | 5 | ||
| 3 | export function ThemedSwitch(props: SwitchProps) { | 6 | export function ThemedSwitch(props: SwitchProps) { |
| 7 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 8 | if (materialYouStyleLayoutActive) { | ||
| 9 | return ( | ||
| 10 | <Pressable | ||
| 11 | accessibilityHint={props.accessibilityHint} | ||
| 12 | accessibilityLabel={props.accessibilityLabel} | ||
| 13 | accessibilityRole="switch" | ||
| 14 | accessibilityState={{ checked: Boolean(props.value), disabled: props.disabled }} | ||
| 15 | disabled={props.disabled} | ||
| 16 | hitSlop={8} | ||
| 17 | onPress={() => props.onValueChange?.(!props.value)} | ||
| 18 | style={props.style} | ||
| 19 | testID={props.testID} | ||
| 20 | className={props.disabled ? "opacity-40" : "active:opacity-70"} | ||
| 21 | > | ||
| 22 | <View | ||
| 23 | className={ | ||
| 24 | props.value | ||
| 25 | ? "h-8 w-[52px] items-end justify-center rounded-full border-2 border-switch-active-track bg-switch-active-track px-0.5" | ||
| 26 | : "h-8 w-[52px] items-start justify-center rounded-full border-2 border-switch-inactive-thumb bg-switch-inactive-track px-0.5" | ||
| 27 | } | ||
| 28 | > | ||
| 29 | <View | ||
| 30 | className={ | ||
| 31 | props.value | ||
| 32 | ? "size-6 items-center justify-center rounded-full bg-switch-active-thumb" | ||
| 33 | : "size-6 items-center justify-center rounded-full bg-switch-inactive-thumb" | ||
| 34 | } | ||
| 35 | > | ||
| 36 | <SymbolView | ||
| 37 | name={props.value ? "checkmark" : "xmark"} | ||
| 38 | size={13} | ||
| 39 | tintColorClassName={ | ||
| 40 | props.value ? "accent-switch-active-track" : "accent-switch-inactive-track" | ||
| 41 | } | ||
| 42 | type="monochrome" | ||
| 43 | /> | ||
| 44 | </View> | ||
| 45 | </View> | ||
| 46 | </Pressable> | ||
| 47 | ); | ||
| 48 | } | ||
| 49 | |||
| 4 | return ( | 50 | return ( |
| 5 | <Switch | 51 | <Switch |
| 6 | {...props} | 52 | {...props} |
apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx+28-5| ... | @@ -306,8 +306,10 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -306,8 +306,10 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 306 | useAdaptiveWorkspaceLayout(); | 306 | useAdaptiveWorkspaceLayout(); |
| 307 | const [searchQuery, setSearchQuery] = useState(""); | 307 | const [searchQuery, setSearchQuery] = useState(""); |
| 308 | const isAndroid = Platform.OS === "android"; | 308 | const isAndroid = Platform.OS === "android"; |
| 309 | const { themeAppearance: highlightTheme } = useAppearancePreferences(); | 309 | const { themeAppearance: highlightTheme, materialYouStyleLayoutActive } = |
| 310 | useAppearancePreferences(); | ||
| 310 | const theme = useUniwindTheme(); | 311 | const theme = useUniwindTheme(); |
| 312 | const screenColor = theme["--color-screen"]; | ||
| 311 | const sheetSurfaceColor = theme["--color-sheet-solid"]; | 313 | const sheetSurfaceColor = theme["--color-sheet-solid"]; |
| 312 | const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace( | 314 | const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace( |
| 313 | props.route.params, | 315 | props.route.params, |
| ... | @@ -423,14 +425,16 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -423,14 +425,16 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 423 | const usesCompactMailToolbar = | 425 | const usesCompactMailToolbar = |
| 424 | Platform.OS === "ios" && !layout.usesSplitView && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED; | 426 | Platform.OS === "ios" && !layout.usesSplitView && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED; |
| 425 | 427 | ||
| 426 | return ( | 428 | const content = ( |
| 427 | <> | 429 | <> |
| 428 | {/* Static header config (glass preset and title) lives in Stack.tsx. The | 430 | {/* Static header config (glass preset and title) lives in Stack.tsx. The |
| 429 | live sheet color stays dynamic here so the FlatList can remain the | 431 | live sheet color stays dynamic here so the FlatList can remain the |
| 430 | direct scene child for native scroll-edge sampling. */} | 432 | direct scene child for native scroll-edge sampling. */} |
| 431 | <NativeStackScreenOptions | 433 | <NativeStackScreenOptions |
| 432 | options={{ | 434 | options={{ |
| 433 | contentStyle: { backgroundColor: sheetSurfaceColor }, | 435 | contentStyle: { |
| 436 | backgroundColor: materialYouStyleLayoutActive ? screenColor : sheetSurfaceColor, | ||
| 437 | }, | ||
| 434 | headerShown: !isAndroid, | 438 | headerShown: !isAndroid, |
| 435 | unstable_headerSubtitle: | 439 | unstable_headerSubtitle: |
| 436 | Platform.OS === "ios" && projectName.length > 0 ? projectName : undefined, | 440 | Platform.OS === "ios" && projectName.length > 0 ? projectName : undefined, |
| ... | @@ -466,6 +470,7 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -466,6 +470,7 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 466 | title="Files" | 470 | title="Files" |
| 467 | subtitle={projectName} | 471 | subtitle={projectName} |
| 468 | onBack={handleReturnToThread} | 472 | onBack={handleReturnToThread} |
| 473 | hideBottomBorder={materialYouStyleLayoutActive} | ||
| 469 | actions={[ | 474 | actions={[ |
| 470 | { | 475 | { |
| 471 | accessibilityLabel: "Refresh files", | 476 | accessibilityLabel: "Refresh files", |
| ... | @@ -474,7 +479,13 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -474,7 +479,13 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 474 | }, | 479 | }, |
| 475 | ]} | 480 | ]} |
| 476 | /> | 481 | /> |
| 477 | <View className="flex-row items-center gap-2 border-b border-border px-3 py-2"> | 482 | <View |
| 483 | className={ | ||
| 484 | materialYouStyleLayoutActive | ||
| 485 | ? "mx-4 my-2 min-h-12 flex-row items-center gap-2 rounded-full border border-input-border bg-input px-3.5" | ||
| 486 | : "flex-row items-center gap-2 border-b border-border px-3 py-2" | ||
| 487 | } | ||
| 488 | > | ||
| 478 | <SymbolView | 489 | <SymbolView |
| 479 | name="magnifyingglass" | 490 | name="magnifyingglass" |
| 480 | size={17} | 491 | size={17} |
| ... | @@ -485,7 +496,11 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -485,7 +496,11 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 485 | accessibilityLabel="Search files" | 496 | accessibilityLabel="Search files" |
| 486 | autoCapitalize="none" | 497 | autoCapitalize="none" |
| 487 | autoCorrect={false} | 498 | autoCorrect={false} |
| 488 | className="min-h-10 flex-1 rounded-xl py-2 text-sm" | 499 | className={ |
| 500 | materialYouStyleLayoutActive | ||
| 501 | ? "min-h-10 flex-1 py-2 text-sm text-foreground" | ||
| 502 | : "min-h-10 flex-1 rounded-xl py-2 text-sm" | ||
| 503 | } | ||
| 489 | placeholder="Search files" | 504 | placeholder="Search files" |
| 490 | value={searchQuery} | 505 | value={searchQuery} |
| 491 | onChangeText={setSearchQuery} | 506 | onChangeText={setSearchQuery} |
| ... | @@ -528,6 +543,14 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -528,6 +543,14 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 528 | <FilesToolbarBottomFade /> | 543 | <FilesToolbarBottomFade /> |
| 529 | </> | 544 | </> |
| 530 | ); | 545 | ); |
| 546 | |||
| 547 | return materialYouStyleLayoutActive ? ( | ||
| 548 | <View className="flex-1" style={{ backgroundColor: screenColor }}> | ||
| 549 | {content} | ||
| 550 | </View> | ||
| 551 | ) : ( | ||
| 552 | content | ||
| 553 | ); | ||
| 531 | } | 554 | } |
| 532 | 555 | ||
| 533 | export function ThreadFileScreen(props: ThreadFileRouteScreenProps) { | 556 | export function ThreadFileScreen(props: ThreadFileRouteScreenProps) { |
apps/mobile/src/features/home/HomeHeader.tsx+14-2| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 1 | import type { EnvironmentId, SidebarThreadSortOrder } from "@t3tools/contracts"; | 2 | import type { EnvironmentId, SidebarThreadSortOrder } from "@t3tools/contracts"; |
| 2 | import type { MenuAction } from "@react-native-menu/menu"; | 3 | import type { MenuAction } from "@react-native-menu/menu"; |
| 3 | import Constants from "expo-constants"; | 4 | import Constants from "expo-constants"; |
| ... | @@ -66,6 +67,7 @@ function checkedMenuState(checked: boolean) { | ... | @@ -66,6 +67,7 @@ function checkedMenuState(checked: boolean) { |
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | function AndroidHomeHeader(props: HomeHeaderProps) { | 69 | function AndroidHomeHeader(props: HomeHeaderProps) { |
| 70 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 69 | const insets = useSafeAreaInsets(); | 71 | const insets = useSafeAreaInsets(); |
| 70 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); | 72 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); |
| 71 | // Thread List v2 lays the list out in fixed creation order, so the | 73 | // Thread List v2 lays the list out in fixed creation order, so the |
| ... | @@ -199,7 +201,11 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -199,7 +201,11 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 199 | <> | 201 | <> |
| 200 | <NativeStackScreenOptions options={{ headerShown: false }} /> | 202 | <NativeStackScreenOptions options={{ headerShown: false }} /> |
| 201 | <View | 203 | <View |
| 202 | className="border-b border-header-border bg-header pb-3" | 204 | className={ |
| 205 | materialYouStyleLayoutActive | ||
| 206 | ? "bg-header pb-3" | ||
| 207 | : "border-b border-header-border bg-header pb-3" | ||
| 208 | } | ||
| 203 | style={{ | 209 | style={{ |
| 204 | paddingHorizontal: HOME_HORIZONTAL_INSET, | 210 | paddingHorizontal: HOME_HORIZONTAL_INSET, |
| 205 | paddingTop: Math.max(insets.top, 12), | 211 | paddingTop: Math.max(insets.top, 12), |
| ... | @@ -269,7 +275,13 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -269,7 +275,13 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 269 | </Pressable> | 275 | </Pressable> |
| 270 | </View> | 276 | </View> |
| 271 | 277 | ||
| 272 | <View className="min-h-12 flex-row items-center gap-2.5 rounded-2xl border border-input-border bg-input px-3.5"> | 278 | <View |
| 279 | className={ | ||
| 280 | materialYouStyleLayoutActive | ||
| 281 | ? "min-h-12 flex-row items-center gap-2.5 rounded-full border border-input-border bg-input px-3.5" | ||
| 282 | : "min-h-12 flex-row items-center gap-2.5 rounded-2xl border border-input-border bg-input px-3.5" | ||
| 283 | } | ||
| 284 | > | ||
| 273 | <SymbolView | 285 | <SymbolView |
| 274 | name="magnifyingglass" | 286 | name="magnifyingglass" |
| 275 | size={17} | 287 | size={17} |
apps/mobile/src/features/home/HomeScreen.tsx+123-99| ... | @@ -26,6 +26,7 @@ import { ActivityIndicator, FlatList, Platform, Pressable, View } from "react-na | ... | @@ -26,6 +26,7 @@ import { ActivityIndicator, FlatList, Platform, Pressable, View } from "react-na |
| 26 | import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; | 26 | import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; |
| 27 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 27 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 28 | 28 | ||
| 29 | import { cn } from "../../lib/cn"; | ||
| 29 | import { AppText as Text } from "../../components/AppText"; | 30 | import { AppText as Text } from "../../components/AppText"; |
| 30 | import { EmptyState } from "../../components/EmptyState"; | 31 | import { EmptyState } from "../../components/EmptyState"; |
| 31 | import type { WorkspaceEnvironment, WorkspaceState } from "../../state/workspaceModel"; | 32 | import type { WorkspaceEnvironment, WorkspaceState } from "../../state/workspaceModel"; |
| ... | @@ -34,6 +35,7 @@ import { scopedProjectKey } from "../../lib/scopedEntities"; | ... | @@ -34,6 +35,7 @@ import { scopedProjectKey } from "../../lib/scopedEntities"; |
| 34 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | 35 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 35 | import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences"; | 36 | import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences"; |
| 36 | import { useThreadSearch } from "../../state/queries"; | 37 | import { useThreadSearch } from "../../state/queries"; |
| 38 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 37 | import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled"; | 39 | import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled"; |
| 38 | import { usePendingThreadOrder } from "../../state/thread-order"; | 40 | import { usePendingThreadOrder } from "../../state/thread-order"; |
| 39 | import { environmentServerConfigsAtom } from "../../state/server"; | 41 | import { environmentServerConfigsAtom } from "../../state/server"; |
| ... | @@ -210,6 +212,7 @@ function HomeTopContentSpacer() { | ... | @@ -210,6 +212,7 @@ function HomeTopContentSpacer() { |
| 210 | /* ─── Main screen ────────────────────────────────────────────────────── */ | 212 | /* ─── Main screen ────────────────────────────────────────────────────── */ |
| 211 | 213 | ||
| 212 | export function HomeScreen(props: HomeScreenProps) { | 214 | export function HomeScreen(props: HomeScreenProps) { |
| 215 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 213 | const [groupDisplayStates, setGroupDisplayStates] = useState< | 216 | const [groupDisplayStates, setGroupDisplayStates] = useState< |
| 214 | ReadonlyMap<string, HomeGroupDisplayState> | 217 | ReadonlyMap<string, HomeGroupDisplayState> |
| 215 | >(() => new Map()); | 218 | >(() => new Map()); |
| ... | @@ -1081,26 +1084,31 @@ export function HomeScreen(props: HomeScreenProps) { | ... | @@ -1081,26 +1084,31 @@ export function HomeScreen(props: HomeScreenProps) { |
| 1081 | 1084 | ||
| 1082 | if (!hasAnyThreads) { | 1085 | if (!hasAnyThreads) { |
| 1083 | return ( | 1086 | return ( |
| 1084 | <View | 1087 | <View className={materialYouStyleLayoutActive ? "flex-1 bg-header" : "flex-1 bg-screen"}> |
| 1085 | className="flex-1 items-center justify-center bg-screen px-8" | 1088 | <View |
| 1086 | style={{ | 1089 | className={cn( |
| 1087 | paddingBottom: Math.max(insets.bottom, 24) + iosBottomToolbarClearance, | 1090 | "flex-1 items-center justify-center bg-screen px-8", |
| 1088 | paddingTop: NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 72 : 0, | 1091 | materialYouStyleLayoutActive && "overflow-hidden rounded-t-[28px]", |
| 1089 | }} | 1092 | )} |
| 1090 | > | 1093 | style={{ |
| 1091 | <View className="w-full max-w-[430px]"> | 1094 | paddingBottom: Math.max(insets.bottom, 24) + iosBottomToolbarClearance, |
| 1092 | <EmptyState | 1095 | paddingTop: NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 72 : 0, |
| 1093 | title={emptyState.title} | 1096 | }} |
| 1094 | detail={emptyState.detail} | 1097 | > |
| 1095 | actionLabel={!props.catalogState.hasReadyEnvironment ? "Add environment" : undefined} | 1098 | <View className="w-full max-w-[430px]"> |
| 1096 | onAction={!props.catalogState.hasReadyEnvironment ? props.onAddConnection : undefined} | 1099 | <EmptyState |
| 1097 | variant="plain" | 1100 | title={emptyState.title} |
| 1098 | /> | 1101 | detail={emptyState.detail} |
| 1099 | {emptyState.loading ? ( | 1102 | actionLabel={!props.catalogState.hasReadyEnvironment ? "Add environment" : undefined} |
| 1100 | <View className="mt-4 items-center"> | 1103 | onAction={!props.catalogState.hasReadyEnvironment ? props.onAddConnection : undefined} |
| 1101 | <ActivityIndicator colorClassName={"accent-icon-muted"} /> | 1104 | variant="plain" |
| 1102 | </View> | 1105 | /> |
| 1103 | ) : null} | 1106 | {emptyState.loading ? ( |
| 1107 | <View className="mt-4 items-center"> | ||
| 1108 | <ActivityIndicator colorClassName={"accent-icon-muted"} /> | ||
| 1109 | </View> | ||
| 1110 | ) : null} | ||
| 1111 | </View> | ||
| 1104 | </View> | 1112 | </View> |
| 1105 | </View> | 1113 | </View> |
| 1106 | ); | 1114 | ); |
| ... | @@ -1145,100 +1153,116 @@ export function HomeScreen(props: HomeScreenProps) { | ... | @@ -1145,100 +1153,116 @@ export function HomeScreen(props: HomeScreenProps) { |
| 1145 | 1153 | ||
| 1146 | if (threadListV2Enabled) { | 1154 | if (threadListV2Enabled) { |
| 1147 | return ( | 1155 | return ( |
| 1148 | <View className="flex-1 bg-screen"> | 1156 | <View className={materialYouStyleLayoutActive ? "flex-1 bg-header" : "flex-1 bg-screen"}> |
| 1157 | <View | ||
| 1158 | className={ | ||
| 1159 | materialYouStyleLayoutActive | ||
| 1160 | ? "flex-1 overflow-hidden rounded-t-[28px] bg-screen" | ||
| 1161 | : "flex-1 bg-screen" | ||
| 1162 | } | ||
| 1163 | > | ||
| 1164 | <SwipeableScrollGateProvider enabled={swipeEnabled}> | ||
| 1165 | <FlatList | ||
| 1166 | data={threadListV2Items} | ||
| 1167 | renderItem={renderV2Item} | ||
| 1168 | keyExtractor={v2KeyExtractor} | ||
| 1169 | extraData={v2ExtraData} | ||
| 1170 | ListHeaderComponent={v2ListHeader} | ||
| 1171 | ListFooterComponent={ | ||
| 1172 | settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? ( | ||
| 1173 | <Pressable | ||
| 1174 | accessibilityRole="button" | ||
| 1175 | accessibilityLabel={`Show ${Math.min(threadListV2Layout.hiddenSettledCount, THREAD_LIST_V2_SETTLED_PAGE_COUNT)} more settled threads`} | ||
| 1176 | onPress={showMoreSettled} | ||
| 1177 | className="mx-4 mt-2 items-center rounded-lg border border-dashed border-border py-2.5" | ||
| 1178 | style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} | ||
| 1179 | > | ||
| 1180 | <Text className="text-xs font-t3-medium text-foreground-muted"> | ||
| 1181 | Show more ({threadListV2Layout.hiddenSettledCount} settled hidden) | ||
| 1182 | </Text> | ||
| 1183 | </Pressable> | ||
| 1184 | ) : null | ||
| 1185 | } | ||
| 1186 | ListEmptyComponent={v2ListEmpty} | ||
| 1187 | style={{ flex: 1 }} | ||
| 1188 | automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"} | ||
| 1189 | contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"} | ||
| 1190 | showsVerticalScrollIndicator={false} | ||
| 1191 | keyboardDismissMode="on-drag" | ||
| 1192 | keyboardShouldPersistTaps="handled" | ||
| 1193 | {...scrollGateHandlers} | ||
| 1194 | scrollEventThrottle={16} | ||
| 1195 | contentContainerStyle={{ | ||
| 1196 | paddingBottom: | ||
| 1197 | Platform.OS === "ios" | ||
| 1198 | ? Math.max(insets.bottom, 24) + 96 + iosBottomToolbarClearance | ||
| 1199 | : Math.max(insets.bottom, 16) + 88, | ||
| 1200 | }} | ||
| 1201 | /> | ||
| 1202 | </SwipeableScrollGateProvider> | ||
| 1203 | </View> | ||
| 1204 | </View> | ||
| 1205 | ); | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | return ( | ||
| 1209 | <View className={materialYouStyleLayoutActive ? "flex-1 bg-header" : "flex-1 bg-screen"}> | ||
| 1210 | <View | ||
| 1211 | className={ | ||
| 1212 | materialYouStyleLayoutActive | ||
| 1213 | ? "flex-1 overflow-hidden rounded-t-[28px] bg-screen" | ||
| 1214 | : "flex-1 bg-screen" | ||
| 1215 | } | ||
| 1216 | > | ||
| 1217 | {/* Sticky headers are deliberately not wired up: LegendList's JS sticky | ||
| 1218 | implementation mispositions pinned headers at mount under iOS | ||
| 1219 | automatic content insets (headers render one nav-inset too low until | ||
| 1220 | the first scroll event) and blanks non-pinned headers after | ||
| 1221 | collapse/expand data changes. The flattened layout still exposes | ||
| 1222 | `stickyHeaderIndices` if this gets revisited. */} | ||
| 1149 | <SwipeableScrollGateProvider enabled={swipeEnabled}> | 1223 | <SwipeableScrollGateProvider enabled={swipeEnabled}> |
| 1150 | <FlatList | 1224 | <LegendList |
| 1151 | data={threadListV2Items} | 1225 | ref={listRef} |
| 1152 | renderItem={renderV2Item} | 1226 | data={listLayout.items} |
| 1153 | keyExtractor={v2KeyExtractor} | 1227 | renderItem={renderItem} |
| 1154 | extraData={v2ExtraData} | 1228 | keyExtractor={keyExtractor} |
| 1155 | ListHeaderComponent={v2ListHeader} | 1229 | itemsAreEqual={homeListItemsAreEqual} |
| 1156 | ListFooterComponent={ | 1230 | drawDistance={500} |
| 1157 | settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? ( | 1231 | estimatedItemSize={ESTIMATED_THREAD_ROW_HEIGHT} |
| 1158 | <Pressable | 1232 | extraData={extraData} |
| 1159 | accessibilityRole="button" | 1233 | ListHeaderComponent={listHeader} |
| 1160 | accessibilityLabel={`Show ${Math.min(threadListV2Layout.hiddenSettledCount, THREAD_LIST_V2_SETTLED_PAGE_COUNT)} more settled threads`} | 1234 | ListEmptyComponent={listEmpty} |
| 1161 | onPress={showMoreSettled} | ||
| 1162 | className="mx-4 mt-2 items-center rounded-lg border border-dashed border-border py-2.5" | ||
| 1163 | style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} | ||
| 1164 | > | ||
| 1165 | <Text className="text-xs font-t3-medium text-foreground-muted"> | ||
| 1166 | Show more ({threadListV2Layout.hiddenSettledCount} settled hidden) | ||
| 1167 | </Text> | ||
| 1168 | </Pressable> | ||
| 1169 | ) : null | ||
| 1170 | } | ||
| 1171 | ListEmptyComponent={v2ListEmpty} | ||
| 1172 | style={{ flex: 1 }} | 1235 | style={{ flex: 1 }} |
| 1173 | automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"} | 1236 | automaticallyAdjustsScrollIndicatorInsets={NATIVE_LIQUID_GLASS_SUPPORTED} |
| 1174 | contentInsetAdjustmentBehavior={Platform.OS === "ios" ? "automatic" : "never"} | 1237 | contentInsetAdjustmentBehavior={NATIVE_LIQUID_GLASS_SUPPORTED ? "automatic" : "never"} |
| 1175 | showsVerticalScrollIndicator={false} | 1238 | showsVerticalScrollIndicator={false} |
| 1176 | keyboardDismissMode="on-drag" | 1239 | keyboardDismissMode="on-drag" |
| 1177 | keyboardShouldPersistTaps="handled" | 1240 | keyboardShouldPersistTaps="handled" |
| 1178 | {...scrollGateHandlers} | 1241 | {...scrollGateHandlers} |
| 1242 | recycleItems | ||
| 1179 | scrollEventThrottle={16} | 1243 | scrollEventThrottle={16} |
| 1180 | contentContainerStyle={{ | 1244 | contentContainerStyle={{ |
| 1245 | // Android reserves room for the floating new-task FAB | ||
| 1246 | // (56 button + 16 gap + bottom inset). Pre-glass iOS shows a | ||
| 1247 | // standard 44pt bottom toolbar that overlays the list and is not | ||
| 1248 | // reflected in insets while contentInsetAdjustmentBehavior is | ||
| 1249 | // "never". | ||
| 1181 | paddingBottom: | 1250 | paddingBottom: |
| 1182 | Platform.OS === "ios" | 1251 | Platform.OS === "ios" |
| 1183 | ? Math.max(insets.bottom, 24) + 96 + iosBottomToolbarClearance | 1252 | ? Math.max(insets.bottom, 24) + 24 + iosBottomToolbarClearance |
| 1184 | : Math.max(insets.bottom, 16) + 88, | 1253 | : Math.max(insets.bottom, 16) + 88, |
| 1185 | }} | 1254 | }} |
| 1255 | scrollIndicatorInsets={ | ||
| 1256 | Platform.OS === "ios" | ||
| 1257 | ? { | ||
| 1258 | bottom: Math.max(insets.bottom, 16) + 24 + iosBottomToolbarClearance, | ||
| 1259 | top: 0, | ||
| 1260 | } | ||
| 1261 | : undefined | ||
| 1262 | } | ||
| 1186 | /> | 1263 | /> |
| 1187 | </SwipeableScrollGateProvider> | 1264 | </SwipeableScrollGateProvider> |
| 1188 | </View> | 1265 | </View> |
| 1189 | ); | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | return ( | ||
| 1193 | <View className="flex-1 bg-screen"> | ||
| 1194 | {/* Sticky headers are deliberately not wired up: LegendList's JS sticky | ||
| 1195 | implementation mispositions pinned headers at mount under iOS | ||
| 1196 | automatic content insets (headers render one nav-inset too low until | ||
| 1197 | the first scroll event) and blanks non-pinned headers after | ||
| 1198 | collapse/expand data changes. The flattened layout still exposes | ||
| 1199 | `stickyHeaderIndices` if this gets revisited. */} | ||
| 1200 | <SwipeableScrollGateProvider enabled={swipeEnabled}> | ||
| 1201 | <LegendList | ||
| 1202 | ref={listRef} | ||
| 1203 | data={listLayout.items} | ||
| 1204 | renderItem={renderItem} | ||
| 1205 | keyExtractor={keyExtractor} | ||
| 1206 | itemsAreEqual={homeListItemsAreEqual} | ||
| 1207 | drawDistance={500} | ||
| 1208 | estimatedItemSize={ESTIMATED_THREAD_ROW_HEIGHT} | ||
| 1209 | extraData={extraData} | ||
| 1210 | ListHeaderComponent={listHeader} | ||
| 1211 | ListEmptyComponent={listEmpty} | ||
| 1212 | style={{ flex: 1 }} | ||
| 1213 | automaticallyAdjustsScrollIndicatorInsets={NATIVE_LIQUID_GLASS_SUPPORTED} | ||
| 1214 | contentInsetAdjustmentBehavior={NATIVE_LIQUID_GLASS_SUPPORTED ? "automatic" : "never"} | ||
| 1215 | showsVerticalScrollIndicator={false} | ||
| 1216 | keyboardDismissMode="on-drag" | ||
| 1217 | keyboardShouldPersistTaps="handled" | ||
| 1218 | {...scrollGateHandlers} | ||
| 1219 | recycleItems | ||
| 1220 | scrollEventThrottle={16} | ||
| 1221 | contentContainerStyle={{ | ||
| 1222 | // Android reserves room for the floating new-task FAB | ||
| 1223 | // (56 button + 16 gap + bottom inset). Pre-glass iOS shows a | ||
| 1224 | // standard 44pt bottom toolbar that overlays the list and is not | ||
| 1225 | // reflected in insets while contentInsetAdjustmentBehavior is | ||
| 1226 | // "never". | ||
| 1227 | paddingBottom: | ||
| 1228 | Platform.OS === "ios" | ||
| 1229 | ? Math.max(insets.bottom, 24) + 24 + iosBottomToolbarClearance | ||
| 1230 | : Math.max(insets.bottom, 16) + 88, | ||
| 1231 | }} | ||
| 1232 | scrollIndicatorInsets={ | ||
| 1233 | Platform.OS === "ios" | ||
| 1234 | ? { | ||
| 1235 | bottom: Math.max(insets.bottom, 16) + 24 + iosBottomToolbarClearance, | ||
| 1236 | top: 0, | ||
| 1237 | } | ||
| 1238 | : undefined | ||
| 1239 | } | ||
| 1240 | /> | ||
| 1241 | </SwipeableScrollGateProvider> | ||
| 1242 | </View> | 1266 | </View> |
| 1243 | ); | 1267 | ); |
| 1244 | } | 1268 | } |
apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx+10-1| ... | @@ -47,6 +47,7 @@ import { | ... | @@ -47,6 +47,7 @@ import { |
| 47 | } from "../keyboard/hardwareKeyboardCommands"; | 47 | } from "../keyboard/hardwareKeyboardCommands"; |
| 48 | import { AndroidHomeFabLayout } from "../home/AndroidHomeFab"; | 48 | import { AndroidHomeFabLayout } from "../home/AndroidHomeFab"; |
| 49 | import { HomeListOptionsProvider } from "../home/home-list-options"; | 49 | import { HomeListOptionsProvider } from "../home/home-list-options"; |
| 50 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 50 | import { ThreadNavigationSidebar } from "../threads/ThreadNavigationSidebar"; | 51 | import { ThreadNavigationSidebar } from "../threads/ThreadNavigationSidebar"; |
| 51 | import { WORKSPACE_PANE_TIMING } from "./workspace-pane-animation"; | 52 | import { WORKSPACE_PANE_TIMING } from "./workspace-pane-animation"; |
| 52 | import { WorkspaceInspectorPane } from "./workspace-inspector-pane"; | 53 | import { WorkspaceInspectorPane } from "./workspace-inspector-pane"; |
| ... | @@ -219,6 +220,7 @@ function AdaptiveWorkspaceLayoutContent( | ... | @@ -219,6 +220,7 @@ function AdaptiveWorkspaceLayoutContent( |
| 219 | }, | 220 | }, |
| 220 | ) { | 221 | ) { |
| 221 | const projectGroupingMode = props.projectGroupingMode; | 222 | const projectGroupingMode = props.projectGroupingMode; |
| 223 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 222 | const { width, height } = useWindowDimensions(); | 224 | const { width, height } = useWindowDimensions(); |
| 223 | const pathname = props.pathname; | 225 | const pathname = props.pathname; |
| 224 | const navigation = useNavigation(); | 226 | const navigation = useNavigation(); |
| ... | @@ -567,7 +569,14 @@ function AdaptiveWorkspaceLayoutContent( | ... | @@ -567,7 +569,14 @@ function AdaptiveWorkspaceLayoutContent( |
| 567 | </View> | 569 | </View> |
| 568 | </Animated.View> | 570 | </Animated.View> |
| 569 | ) : null} | 571 | ) : null} |
| 570 | <View className="flex-1 overflow-hidden bg-screen" collapsable={false}> | 572 | <View |
| 573 | className={ | ||
| 574 | materialYouStyleLayoutActive | ||
| 575 | ? "flex-1 overflow-hidden bg-header" | ||
| 576 | : "flex-1 overflow-hidden bg-screen" | ||
| 577 | } | ||
| 578 | collapsable={false} | ||
| 579 | > | ||
| 571 | <View | 580 | <View |
| 572 | collapsable={false} | 581 | collapsable={false} |
| 573 | style={ | 582 | style={ |
apps/mobile/src/features/layout/WorkspaceEmptyDetail.tsx+9-1| ... | @@ -1,11 +1,19 @@ | ... | @@ -1,11 +1,19 @@ |
| 1 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 1 | import { SymbolView } from "../../components/AppSymbol"; | 2 | import { SymbolView } from "../../components/AppSymbol"; |
| 2 | import { Pressable, View } from "react-native"; | 3 | import { Pressable, View } from "react-native"; |
| 3 | 4 | ||
| 4 | import { AppText as Text } from "../../components/AppText"; | 5 | import { AppText as Text } from "../../components/AppText"; |
| 5 | 6 | ||
| 6 | export function WorkspaceEmptyDetail(props: { readonly onStartNewTask?: () => void }) { | 7 | export function WorkspaceEmptyDetail(props: { readonly onStartNewTask?: () => void }) { |
| 8 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 7 | return ( | 9 | return ( |
| 8 | <View className="flex-1 items-center justify-center bg-screen px-10"> | 10 | <View |
| 11 | className={ | ||
| 12 | materialYouStyleLayoutActive | ||
| 13 | ? "flex-1 items-center justify-center px-10" | ||
| 14 | : "flex-1 items-center justify-center bg-screen px-10" | ||
| 15 | } | ||
| 16 | > | ||
| 9 | <View className="max-w-[360px] items-center gap-3"> | 17 | <View className="max-w-[360px] items-center gap-3"> |
| 10 | <SymbolView | 18 | <SymbolView |
| 11 | name="sidebar.left" | 19 | name="sidebar.left" |
apps/mobile/src/features/settings/appearance/AppearancePreferencesProvider.tsx+19-1| ... | @@ -10,7 +10,7 @@ import { | ... | @@ -10,7 +10,7 @@ import { |
| 10 | useRef, | 10 | useRef, |
| 11 | type ReactNode, | 11 | type ReactNode, |
| 12 | } from "react"; | 12 | } from "react"; |
| 13 | import { AppState, Appearance, useColorScheme } from "react-native"; | 13 | import { AppState, Appearance, Platform, useColorScheme } from "react-native"; |
| 14 | 14 | ||
| 15 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; | 15 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; |
| 16 | import { AsyncResult } from "effect/unstable/reactivity"; | 16 | import { AsyncResult } from "effect/unstable/reactivity"; |
| ... | @@ -51,6 +51,9 @@ interface AppearancePreferencesContextValue { | ... | @@ -51,6 +51,9 @@ interface AppearancePreferencesContextValue { |
| 51 | readonly themeIds: MobileThemeIds; | 51 | readonly themeIds: MobileThemeIds; |
| 52 | readonly themeMode: MobileThemeMode; | 52 | readonly themeMode: MobileThemeMode; |
| 53 | readonly themeAppearance: MobileThemeAppearance; | 53 | readonly themeAppearance: MobileThemeAppearance; |
| 54 | readonly materialYouStyleLayoutEnabled: boolean; | ||
| 55 | readonly materialYouStyleLayoutActive: boolean; | ||
| 56 | readonly setMaterialYouStyleLayoutEnabled: (value: boolean) => void; | ||
| 54 | readonly systemColorsAvailable: boolean; | 57 | readonly systemColorsAvailable: boolean; |
| 55 | readonly systemColorsActive: boolean; | 58 | readonly systemColorsActive: boolean; |
| 56 | readonly themeVariables: MobileThemeVariables; | 59 | readonly themeVariables: MobileThemeVariables; |
| ... | @@ -94,6 +97,8 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -94,6 +97,8 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 94 | [resolvedThemeIds.dark, resolvedThemeIds.light], | 97 | [resolvedThemeIds.dark, resolvedThemeIds.light], |
| 95 | ); | 98 | ); |
| 96 | const themeId = themeIds[themeAppearance]; | 99 | const themeId = themeIds[themeAppearance]; |
| 100 | const materialYouStyleLayoutEnabled = storedPreferences?.materialYouStyleLayoutEnabled ?? false; | ||
| 101 | const materialYouStyleLayoutActive = Platform.OS === "android" && materialYouStyleLayoutEnabled; | ||
| 97 | const systemColorsActive = themeId === "material-you" && isSystemColorsAvailable; | 102 | const systemColorsActive = themeId === "material-you" && isSystemColorsAvailable; |
| 98 | const [systemColorPalettes, setSystemColorPalettes] = useState(readSystemColorPalettes); | 103 | const [systemColorPalettes, setSystemColorPalettes] = useState(readSystemColorPalettes); |
| 99 | useEffect(() => { | 104 | useEffect(() => { |
| ... | @@ -242,6 +247,13 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -242,6 +247,13 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 242 | [runtimeState, syncThemeRuntime, updateThemePreferences], | 247 | [runtimeState, syncThemeRuntime, updateThemePreferences], |
| 243 | ); | 248 | ); |
| 244 | 249 | ||
| 250 | const setMaterialYouStyleLayoutEnabled = useCallback( | ||
| 251 | (value: boolean) => { | ||
| 252 | updatePreferences({ materialYouStyleLayoutEnabled: value }); | ||
| 253 | }, | ||
| 254 | [updatePreferences], | ||
| 255 | ); | ||
| 256 | |||
| 245 | const setBaseFontSize = useCallback( | 257 | const setBaseFontSize = useCallback( |
| 246 | (value: number) => { | 258 | (value: number) => { |
| 247 | const current = appliedRuntimeStateRef.current ?? runtimeState; | 259 | const current = appliedRuntimeStateRef.current ?? runtimeState; |
| ... | @@ -281,6 +293,9 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -281,6 +293,9 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 281 | themeAppearance, | 293 | themeAppearance, |
| 282 | systemColorsAvailable: isSystemColorsAvailable, | 294 | systemColorsAvailable: isSystemColorsAvailable, |
| 283 | systemColorsActive, | 295 | systemColorsActive, |
| 296 | materialYouStyleLayoutEnabled, | ||
| 297 | materialYouStyleLayoutActive, | ||
| 298 | setMaterialYouStyleLayoutEnabled, | ||
| 284 | themeVariables, | 299 | themeVariables, |
| 285 | themeVariablesByAppearance, | 300 | themeVariablesByAppearance, |
| 286 | systemColorPalettes, | 301 | systemColorPalettes, |
| ... | @@ -300,6 +315,9 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -300,6 +315,9 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 300 | themeMode, | 315 | themeMode, |
| 301 | themeAppearance, | 316 | themeAppearance, |
| 302 | systemColorsActive, | 317 | systemColorsActive, |
| 318 | materialYouStyleLayoutEnabled, | ||
| 319 | materialYouStyleLayoutActive, | ||
| 320 | setMaterialYouStyleLayoutEnabled, | ||
| 303 | themeVariables, | 321 | themeVariables, |
| 304 | themeVariablesByAppearance, | 322 | themeVariablesByAppearance, |
| 305 | systemColorPalettes, | 323 | systemColorPalettes, |
apps/mobile/src/features/settings/appearance/sections/ThemeAppearanceSection.tsx+18-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import { memo, useId } from "react"; | 1 | import { memo, useId } from "react"; |
| 2 | import { Pressable, View } from "react-native"; | 2 | import { Platform, Pressable, View } from "react-native"; |
| 3 | import Svg, { Circle, Defs, RadialGradient, Stop } from "react-native-svg"; | 3 | import Svg, { Circle, Defs, RadialGradient, Stop } from "react-native-svg"; |
| 4 | import { ScopedTheme, ScopedVariables } from "uniwind"; | 4 | import { ScopedTheme, ScopedVariables } from "uniwind"; |
| 5 | 5 | ||
| ... | @@ -19,6 +19,9 @@ import { getMobileUniwindThemeName } from "../../../../lib/mobileThemeRuntime"; | ... | @@ -19,6 +19,9 @@ import { getMobileUniwindThemeName } from "../../../../lib/mobileThemeRuntime"; |
| 19 | import { cn } from "../../../../lib/cn"; | 19 | import { cn } from "../../../../lib/cn"; |
| 20 | import { useAppearancePreferences } from "../AppearancePreferencesProvider"; | 20 | import { useAppearancePreferences } from "../AppearancePreferencesProvider"; |
| 21 | 21 | ||
| 22 | import { SettingsSection } from "../../components/SettingsSection"; | ||
| 23 | import { SettingsSwitchRow } from "../../components/SettingsSwitchRow"; | ||
| 24 | |||
| 22 | const APPEARANCE_MODES: ReadonlyArray<{ | 25 | const APPEARANCE_MODES: ReadonlyArray<{ |
| 23 | readonly id: MobileThemeMode; | 26 | readonly id: MobileThemeMode; |
| 24 | readonly label: string; | 27 | readonly label: string; |
| ... | @@ -290,11 +293,25 @@ export function ThemeAppearanceSection() { | ... | @@ -290,11 +293,25 @@ export function ThemeAppearanceSection() { |
| 290 | setThemeMode, | 293 | setThemeMode, |
| 291 | themeIds, | 294 | themeIds, |
| 292 | themeMode, | 295 | themeMode, |
| 296 | materialYouStyleLayoutEnabled, | ||
| 297 | setMaterialYouStyleLayoutEnabled, | ||
| 293 | systemColorsAvailable, | 298 | systemColorsAvailable, |
| 294 | } = useAppearancePreferences(); | 299 | } = useAppearancePreferences(); |
| 295 | 300 | ||
| 296 | return ( | 301 | return ( |
| 297 | <View className="gap-6"> | 302 | <View className="gap-6"> |
| 303 | {Platform.OS === "android" ? ( | ||
| 304 | <SettingsSection card title="Android"> | ||
| 305 | <SettingsSwitchRow | ||
| 306 | disabled={!isReady} | ||
| 307 | icon="square.grid.2x2" | ||
| 308 | label="Material You Layout" | ||
| 309 | onValueChange={setMaterialYouStyleLayoutEnabled} | ||
| 310 | subtitle="Use Material You surfaces, shapes, and component styling." | ||
| 311 | value={materialYouStyleLayoutEnabled} | ||
| 312 | /> | ||
| 313 | </SettingsSection> | ||
| 314 | ) : null} | ||
| 298 | <View className="gap-2"> | 315 | <View className="gap-2"> |
| 299 | <SectionLabel>Color scheme</SectionLabel> | 316 | <SectionLabel>Color scheme</SectionLabel> |
| 300 | <View accessibilityRole="radiogroup" className="flex-row gap-2"> | 317 | <View accessibilityRole="radiogroup" className="flex-row gap-2"> |
apps/mobile/src/features/threads/ThreadComposer.tsx+21-3| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 1 | import { useAtomValue } from "@effect/atom-react"; | 2 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import type { | 3 | import type { |
| 3 | EnvironmentId, | 4 | EnvironmentId, |
| ... | @@ -170,6 +171,7 @@ export function ComposerSurface(props: { | ... | @@ -170,6 +171,7 @@ export function ComposerSurface(props: { |
| 170 | /** Morphs between the compact and expanded composer layouts. */ | 171 | /** Morphs between the compact and expanded composer layouts. */ |
| 171 | readonly animateLayout?: boolean; | 172 | readonly animateLayout?: boolean; |
| 172 | }) { | 173 | }) { |
| 174 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 173 | const targetBorderRadius = | 175 | const targetBorderRadius = |
| 174 | typeof props.style.borderRadius === "number" ? props.style.borderRadius : 0; | 176 | typeof props.style.borderRadius === "number" ? props.style.borderRadius : 0; |
| 175 | const animatedBorderRadius = useSharedValue(targetBorderRadius); | 177 | const animatedBorderRadius = useSharedValue(targetBorderRadius); |
| ... | @@ -191,7 +193,11 @@ export function ComposerSurface(props: { | ... | @@ -191,7 +193,11 @@ export function ComposerSurface(props: { |
| 191 | // clip leaves the glass and content at their final height on the first frame. | 193 | // clip leaves the glass and content at their final height on the first frame. |
| 192 | return ( | 194 | return ( |
| 193 | <Animated.View | 195 | <Animated.View |
| 194 | className="shadow-[0_6px_28px] shadow-adaptive-black-a15-a35" | 196 | className={ |
| 197 | materialYouStyleLayoutActive | ||
| 198 | ? undefined | ||
| 199 | : "shadow-[0_6px_28px] shadow-adaptive-black-a15-a35" | ||
| 200 | } | ||
| 195 | layout={layoutTransition} | 201 | layout={layoutTransition} |
| 196 | style={[ | 202 | style={[ |
| 197 | animatedShapeStyle, | 203 | animatedShapeStyle, |
| ... | @@ -204,7 +210,11 @@ export function ComposerSurface(props: { | ... | @@ -204,7 +210,11 @@ export function ComposerSurface(props: { |
| 204 | > | 210 | > |
| 205 | <AnimatedGlassSurface | 211 | <AnimatedGlassSurface |
| 206 | chrome="none" | 212 | chrome="none" |
| 207 | fallbackClassName="border border-border bg-card-translucent" | 213 | fallbackClassName={ |
| 214 | materialYouStyleLayoutActive | ||
| 215 | ? "border border-composer-border bg-composer-surface" | ||
| 216 | : "border border-border bg-card-translucent" | ||
| 217 | } | ||
| 208 | glassEffectStyle="regular" | 218 | glassEffectStyle="regular" |
| 209 | // The composer is a passive material containing interactive controls. | 219 | // The composer is a passive material containing interactive controls. |
| 210 | // Keep native glass out of the interactive content's layout path. | 220 | // Keep native glass out of the interactive content's layout path. |
| ... | @@ -227,6 +237,9 @@ export function ComposerSurface(props: { | ... | @@ -227,6 +237,9 @@ export function ComposerSurface(props: { |
| 227 | } | 237 | } |
| 228 | 238 | ||
| 229 | export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposerProps) { | 239 | export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposerProps) { |
| 240 | const { materialYouStyleLayoutActive, themeVariables: materialTheme } = | ||
| 241 | useAppearancePreferences(); | ||
| 242 | const composerPanel = materialTheme["--color-composer-panel"]; | ||
| 230 | const navigation = useNavigation(); | 243 | const navigation = useNavigation(); |
| 231 | const foregroundColor = useUniwindTheme()["--color-foreground"]; | 244 | const foregroundColor = useUniwindTheme()["--color-foreground"]; |
| 232 | const bodyText = useScaledTextRole("body"); | 245 | const bodyText = useScaledTextRole("body"); |
| ... | @@ -543,13 +556,18 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer | ... | @@ -543,13 +556,18 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 543 | style={{ | 556 | style={{ |
| 544 | paddingTop: isExpanded ? 8 : 6, | 557 | paddingTop: isExpanded ? 8 : 6, |
| 545 | paddingBottom: (props.bottomInset ?? 0) + (isExpanded ? 8 : 6), | 558 | paddingBottom: (props.bottomInset ?? 0) + (isExpanded ? 8 : 6), |
| 559 | backgroundColor: materialYouStyleLayoutActive ? composerPanel : undefined, | ||
| 546 | }} | 560 | }} |
| 547 | > | 561 | > |
| 548 | {/* The backdrop gradient lives on a plain View: Reanimated's Animated.View | 562 | {/* The backdrop gradient lives on a plain View: Reanimated's Animated.View |
| 549 | silently drops experimental_backgroundImage on Android, which left this | 563 | silently drops experimental_backgroundImage on Android, which left this |
| 550 | strip fully transparent and the feed text legible through the composer. */} | 564 | strip fully transparent and the feed text legible through the composer. */} |
| 551 | <View | 565 | <View |
| 552 | className="absolute inset-0 bg-linear-to-b from-screen/0 via-screen/60 to-screen/90" | 566 | className={ |
| 567 | materialYouStyleLayoutActive | ||
| 568 | ? "hidden" | ||
| 569 | : "absolute inset-0 bg-linear-to-b from-screen/0 via-screen/60 to-screen/90" | ||
| 570 | } | ||
| 553 | pointerEvents="none" | 571 | pointerEvents="none" |
| 554 | /> | 572 | /> |
| 555 | <Animated.View | 573 | <Animated.View |
apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx+58-11| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 1 | import { createThreadMovePlanner } from "./threadOrder"; | 2 | import { createThreadMovePlanner } from "./threadOrder"; |
| 2 | import type { | 3 | import type { |
| 3 | EnvironmentProject, | 4 | EnvironmentProject, |
| ... | @@ -145,6 +146,11 @@ function NativeSidebarContainer(props: ThreadNavigationSidebarProps) { | ... | @@ -145,6 +146,11 @@ function NativeSidebarContainer(props: ThreadNavigationSidebarProps) { |
| 145 | function ThreadNavigationSidebarPane( | 146 | function ThreadNavigationSidebarPane( |
| 146 | props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean }, | 147 | props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean }, |
| 147 | ) { | 148 | ) { |
| 149 | const { materialYouStyleLayoutActive, themeVariables: materialTheme } = | ||
| 150 | useAppearancePreferences(); | ||
| 151 | const screenColor = materialTheme["--color-screen"]; | ||
| 152 | const mutedColor = materialTheme["--color-foreground-muted"]; | ||
| 153 | |||
| 148 | const insets = useSafeAreaInsets(); | 154 | const insets = useSafeAreaInsets(); |
| 149 | const projects = useProjects(); | 155 | const projects = useProjects(); |
| 150 | const threads = useThreadShells(); | 156 | const threads = useThreadShells(); |
| ... | @@ -916,7 +922,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -916,7 +922,7 @@ function ThreadNavigationSidebarPane( |
| 916 | }), | 922 | }), |
| 917 | )} | 923 | )} |
| 918 | searchQuery={props.searchQuery} | 924 | searchQuery={props.searchQuery} |
| 919 | pane="sidebar" | 925 | pane={materialYouStyleLayoutActive ? "screen" : "sidebar"} |
| 920 | selected={ | 926 | selected={ |
| 921 | scopedThreadKey(thread.environmentId, thread.id) === props.selectedThreadKey | 927 | scopedThreadKey(thread.environmentId, thread.id) === props.selectedThreadKey |
| 922 | } | 928 | } |
| ... | @@ -956,7 +962,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -956,7 +962,7 @@ function ThreadNavigationSidebarPane( |
| 956 | disabled={!shelfPreferencesLoaded} | 962 | disabled={!shelfPreferencesLoaded} |
| 957 | expanded={item.expanded} | 963 | expanded={item.expanded} |
| 958 | onToggle={toggleSnoozedShelf} | 964 | onToggle={toggleSnoozedShelf} |
| 959 | pane="sidebar" | 965 | pane={materialYouStyleLayoutActive ? "screen" : "sidebar"} |
| 960 | /> | 966 | /> |
| 961 | ); | 967 | ); |
| 962 | case "v2-settled-shelf": | 968 | case "v2-settled-shelf": |
| ... | @@ -966,7 +972,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -966,7 +972,7 @@ function ThreadNavigationSidebarPane( |
| 966 | disabled={!shelfPreferencesLoaded} | 972 | disabled={!shelfPreferencesLoaded} |
| 967 | expanded={item.expanded} | 973 | expanded={item.expanded} |
| 968 | onToggle={toggleSettledShelf} | 974 | onToggle={toggleSettledShelf} |
| 969 | pane="sidebar" | 975 | pane={materialYouStyleLayoutActive ? "screen" : "sidebar"} |
| 970 | /> | 976 | /> |
| 971 | ); | 977 | ); |
| 972 | case "v2-show-more": | 978 | case "v2-show-more": |
| ... | @@ -986,7 +992,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -986,7 +992,7 @@ function ThreadNavigationSidebarPane( |
| 986 | case "header": | 992 | case "header": |
| 987 | return ( | 993 | return ( |
| 988 | <ThreadListGroupHeader | 994 | <ThreadListGroupHeader |
| 989 | variant="sidebar" | 995 | variant={materialYouStyleLayoutActive ? "compact" : "sidebar"} |
| 990 | collapsed={item.collapsed} | 996 | collapsed={item.collapsed} |
| 991 | isFirst={item.isFirst} | 997 | isFirst={item.isFirst} |
| 992 | groupKey={item.group.key} | 998 | groupKey={item.group.key} |
| ... | @@ -1004,7 +1010,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -1004,7 +1010,7 @@ function ThreadNavigationSidebarPane( |
| 1004 | case "pending-task": | 1010 | case "pending-task": |
| 1005 | return ( | 1011 | return ( |
| 1006 | <PendingTaskListRow | 1012 | <PendingTaskListRow |
| 1007 | variant="sidebar" | 1013 | variant={materialYouStyleLayoutActive ? "compact" : "sidebar"} |
| 1008 | pendingTask={item.pendingTask} | 1014 | pendingTask={item.pendingTask} |
| 1009 | environmentLabel={ | 1015 | environmentLabel={ |
| 1010 | savedConnectionsById[item.pendingTask.environmentId]?.environmentLabel ?? null | 1016 | savedConnectionsById[item.pendingTask.environmentId]?.environmentLabel ?? null |
| ... | @@ -1053,7 +1059,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -1053,7 +1059,7 @@ function ThreadNavigationSidebarPane( |
| 1053 | case "show-more": | 1059 | case "show-more": |
| 1054 | return ( | 1060 | return ( |
| 1055 | <ThreadListShowMoreRow | 1061 | <ThreadListShowMoreRow |
| 1056 | variant="sidebar" | 1062 | variant={materialYouStyleLayoutActive ? "compact" : "sidebar"} |
| 1057 | hiddenCount={item.hiddenCount} | 1063 | hiddenCount={item.hiddenCount} |
| 1058 | canShowLess={item.canShowLess} | 1064 | canShowLess={item.canShowLess} |
| 1059 | groupKey={item.groupKey} | 1065 | groupKey={item.groupKey} |
| ... | @@ -1063,6 +1069,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -1063,6 +1069,7 @@ function ThreadNavigationSidebarPane( |
| 1063 | } | 1069 | } |
| 1064 | }, | 1070 | }, |
| 1065 | [ | 1071 | [ |
| 1072 | materialYouStyleLayoutActive, | ||
| 1066 | archiveThread, | 1073 | archiveThread, |
| 1067 | activeReorderEnvironmentIds, | 1074 | activeReorderEnvironmentIds, |
| 1068 | threadMovePlanners, | 1075 | threadMovePlanners, |
| ... | @@ -1219,6 +1226,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -1219,6 +1226,7 @@ function ThreadNavigationSidebarPane( |
| 1219 | } | 1226 | } |
| 1220 | contentContainerStyle={[ | 1227 | contentContainerStyle={[ |
| 1221 | styles.threadListContent, | 1228 | styles.threadListContent, |
| 1229 | materialYouStyleLayoutActive ? { paddingHorizontal: 0 } : null, | ||
| 1222 | { | 1230 | { |
| 1223 | paddingBottom: Math.max(insets.bottom, 16) + 16, | 1231 | paddingBottom: Math.max(insets.bottom, 16) + 16, |
| 1224 | paddingTop: 6, | 1232 | paddingTop: 6, |
| ... | @@ -1246,7 +1254,21 @@ function ThreadNavigationSidebarPane( | ... | @@ -1246,7 +1254,21 @@ function ThreadNavigationSidebarPane( |
| 1246 | className="flex-1 border-r border-border bg-drawer" | 1254 | className="flex-1 border-r border-border bg-drawer" |
| 1247 | style={{ width: props.width }} | 1255 | style={{ width: props.width }} |
| 1248 | > | 1256 | > |
| 1249 | <View className="flex-1" style={{ paddingBottom: insets.bottom }}> | 1257 | <View |
| 1258 | className="flex-1" | ||
| 1259 | style={ | ||
| 1260 | materialYouStyleLayoutActive | ||
| 1261 | ? { | ||
| 1262 | marginTop: stickyHeaderHeight, | ||
| 1263 | paddingBottom: insets.bottom, | ||
| 1264 | backgroundColor: screenColor, | ||
| 1265 | borderTopLeftRadius: 28, | ||
| 1266 | borderTopRightRadius: 28, | ||
| 1267 | overflow: "hidden", | ||
| 1268 | } | ||
| 1269 | : { paddingBottom: insets.bottom } | ||
| 1270 | } | ||
| 1271 | > | ||
| 1250 | <SwipeableScrollGateProvider enabled={swipeEnabled}> | 1272 | <SwipeableScrollGateProvider enabled={swipeEnabled}> |
| 1251 | <GestureDetector gesture={sidebarScrollGesture}> | 1273 | <GestureDetector gesture={sidebarScrollGesture}> |
| 1252 | <LegendList | 1274 | <LegendList |
| ... | @@ -1260,12 +1282,13 @@ function ThreadNavigationSidebarPane( | ... | @@ -1260,12 +1282,13 @@ function ThreadNavigationSidebarPane( |
| 1260 | renderItem={renderListItem} | 1282 | renderItem={renderListItem} |
| 1261 | contentContainerStyle={[ | 1283 | contentContainerStyle={[ |
| 1262 | styles.threadListContent, | 1284 | styles.threadListContent, |
| 1285 | materialYouStyleLayoutActive ? { paddingHorizontal: 0 } : null, | ||
| 1263 | { | 1286 | { |
| 1264 | paddingBottom: | 1287 | paddingBottom: |
| 1265 | Platform.OS === "android" | 1288 | Platform.OS === "android" |
| 1266 | ? Math.max(insets.bottom, 16) + 88 - insets.bottom | 1289 | ? Math.max(insets.bottom, 16) + 88 - insets.bottom |
| 1267 | : 16 + insets.bottom, | 1290 | : 16 + insets.bottom, |
| 1268 | paddingTop: topListInset, | 1291 | paddingTop: materialYouStyleLayoutActive ? 6 : topListInset, |
| 1269 | }, | 1292 | }, |
| 1270 | ]} | 1293 | ]} |
| 1271 | keyboardDismissMode="on-drag" | 1294 | keyboardDismissMode="on-drag" |
| ... | @@ -1310,7 +1333,13 @@ function ThreadNavigationSidebarPane( | ... | @@ -1310,7 +1333,13 @@ function ThreadNavigationSidebarPane( |
| 1310 | </View> | 1333 | </View> |
| 1311 | </View> | 1334 | </View> |
| 1312 | 1335 | ||
| 1313 | <View className="mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]"> | 1336 | <View |
| 1337 | className={ | ||
| 1338 | materialYouStyleLayoutActive | ||
| 1339 | ? "mx-4 mt-[9px] min-h-12 flex-row items-center gap-2.5 rounded-full border border-input-border bg-input px-3.5" | ||
| 1340 | : "mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]" | ||
| 1341 | } | ||
| 1342 | > | ||
| 1314 | <SymbolView | 1343 | <SymbolView |
| 1315 | name="magnifyingglass" | 1344 | name="magnifyingglass" |
| 1316 | size={15} | 1345 | size={15} |
| ... | @@ -1322,14 +1351,32 @@ function ThreadNavigationSidebarPane( | ... | @@ -1322,14 +1351,32 @@ function ThreadNavigationSidebarPane( |
| 1322 | accessibilityLabel="Search threads" | 1351 | accessibilityLabel="Search threads" |
| 1323 | autoCapitalize="none" | 1352 | autoCapitalize="none" |
| 1324 | autoCorrect={false} | 1353 | autoCorrect={false} |
| 1325 | clearButtonMode="while-editing" | 1354 | clearButtonMode={materialYouStyleLayoutActive ? "never" : "while-editing"} |
| 1326 | onChangeText={props.onSearchQueryChange} | 1355 | onChangeText={props.onSearchQueryChange} |
| 1327 | placeholder="Search" | 1356 | placeholder="Search" |
| 1328 | placeholderTextColorClassName={"accent-placeholder"} | 1357 | placeholderTextColorClassName={"accent-placeholder"} |
| 1329 | returnKeyType="search" | 1358 | returnKeyType="search" |
| 1330 | className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground" | 1359 | className={ |
| 1360 | materialYouStyleLayoutActive | ||
| 1361 | ? "flex-1 px-0 py-2.5 font-sans text-base text-foreground" | ||
| 1362 | : "h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground" | ||
| 1363 | } | ||
| 1331 | value={props.searchQuery} | 1364 | value={props.searchQuery} |
| 1332 | /> | 1365 | /> |
| 1366 | {materialYouStyleLayoutActive && props.searchQuery.length > 0 ? ( | ||
| 1367 | <Pressable | ||
| 1368 | accessibilityLabel="Clear search" | ||
| 1369 | hitSlop={10} | ||
| 1370 | onPress={() => props.onSearchQueryChange("")} | ||
| 1371 | > | ||
| 1372 | <SymbolView | ||
| 1373 | name="xmark.circle.fill" | ||
| 1374 | size={17} | ||
| 1375 | tintColor={mutedColor} | ||
| 1376 | type="monochrome" | ||
| 1377 | /> | ||
| 1378 | </Pressable> | ||
| 1379 | ) : null} | ||
| 1333 | </View> | 1380 | </View> |
| 1334 | </View> | 1381 | </View> |
| 1335 | </View> | 1382 | </View> |
apps/mobile/src/features/threads/ThreadRouteScreen.tsx+21-1| ... | @@ -76,6 +76,7 @@ import { useSelectedThreadWorktree } from "../../state/use-selected-thread-workt | ... | @@ -76,6 +76,7 @@ import { useSelectedThreadWorktree } from "../../state/use-selected-thread-workt |
| 76 | import { useThreadComposerState } from "../../state/use-thread-composer-state"; | 76 | import { useThreadComposerState } from "../../state/use-thread-composer-state"; |
| 77 | import { threadEnvironment } from "../../state/threads"; | 77 | import { threadEnvironment } from "../../state/threads"; |
| 78 | import { projectThreadContentPresentation } from "./threadContentPresentation"; | 78 | import { projectThreadContentPresentation } from "./threadContentPresentation"; |
| 79 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 79 | import { | 80 | import { |
| 80 | useAdaptiveWorkspaceLayout, | 81 | useAdaptiveWorkspaceLayout, |
| 81 | useAdaptiveWorkspacePaneRole, | 82 | useAdaptiveWorkspacePaneRole, |
| ... | @@ -193,6 +194,8 @@ function ThreadRouteContent( | ... | @@ -193,6 +194,8 @@ function ThreadRouteContent( |
| 193 | readonly selectedThreadDetailState: ReturnType<typeof useSelectedThreadDetailState>; | 194 | readonly selectedThreadDetailState: ReturnType<typeof useSelectedThreadDetailState>; |
| 194 | }, | 195 | }, |
| 195 | ) { | 196 | ) { |
| 197 | const { materialYouStyleLayoutActive, themeVariables } = useAppearancePreferences(); | ||
| 198 | const headerColor = themeVariables["--color-header"]; | ||
| 196 | const { | 199 | const { |
| 197 | fileInspector, | 200 | fileInspector, |
| 198 | layout, | 201 | layout, |
| ... | @@ -839,7 +842,19 @@ function ThreadRouteContent( | ... | @@ -839,7 +842,19 @@ function ThreadRouteContent( |
| 839 | 842 | ||
| 840 | <GitActionProgressOverlay progress={gitActionProgress} onDismiss={dismissGitActionResult} /> | 843 | <GitActionProgressOverlay progress={gitActionProgress} onDismiss={dismissGitActionResult} /> |
| 841 | 844 | ||
| 842 | <View className="flex-1 bg-screen"> | 845 | <View |
| 846 | className={materialYouStyleLayoutActive ? "flex-1 bg-thread-canvas" : "flex-1 bg-screen"} | ||
| 847 | style={ | ||
| 848 | materialYouStyleLayoutActive | ||
| 849 | ? { | ||
| 850 | borderTopLeftRadius: 28, | ||
| 851 | borderTopRightRadius: 28, | ||
| 852 | marginRight: layout.usesSplitView ? 8 : 0, | ||
| 853 | overflow: "hidden", | ||
| 854 | } | ||
| 855 | : undefined | ||
| 856 | } | ||
| 857 | > | ||
| 843 | <ThreadDetailScreen | 858 | <ThreadDetailScreen |
| 844 | selectedThread={selectedThreadWithDraftSettings ?? selectedThread} | 859 | selectedThread={selectedThreadWithDraftSettings ?? selectedThread} |
| 845 | contentPresentation={contentPresentation} | 860 | contentPresentation={contentPresentation} |
| ... | @@ -931,6 +946,10 @@ function ThreadRouteContent( | ... | @@ -931,6 +946,10 @@ function ThreadRouteContent( |
| 931 | ? () => (layout.usesSplitView ? threadCenterHeaderItems : compactRightHeaderItems) | 946 | ? () => (layout.usesSplitView ? threadCenterHeaderItems : compactRightHeaderItems) |
| 932 | : undefined, | 947 | : undefined, |
| 933 | unstable_headerSubtitle: usesNativeHeaderGlass ? headerSubtitle : undefined, | 948 | unstable_headerSubtitle: usesNativeHeaderGlass ? headerSubtitle : undefined, |
| 949 | contentStyle: | ||
| 950 | Platform.OS === "android" && materialYouStyleLayoutActive | ||
| 951 | ? { backgroundColor: headerColor } | ||
| 952 | : undefined, | ||
| 934 | }} | 953 | }} |
| 935 | /> | 954 | /> |
| 936 | 955 | ||
| ... | @@ -940,6 +959,7 @@ function ThreadRouteContent( | ... | @@ -940,6 +959,7 @@ function ThreadRouteContent( |
| 940 | subtitle={headerSubtitle} | 959 | subtitle={headerSubtitle} |
| 941 | onBack={layout.usesSplitView ? undefined : () => navigation.goBack()} | 960 | onBack={layout.usesSplitView ? undefined : () => navigation.goBack()} |
| 942 | actions={androidHeaderActions} | 961 | actions={androidHeaderActions} |
| 962 | hideBottomBorder={materialYouStyleLayoutActive} | ||
| 943 | /> | 963 | /> |
| 944 | ) : null} | 964 | ) : null} |
| 945 | 965 |
apps/mobile/src/features/threads/ThreadSettingsSheet.tsx+4| ... | @@ -52,6 +52,7 @@ import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | ... | @@ -52,6 +52,7 @@ import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 52 | import { serverEnvironment } from "../../state/server"; | 52 | import { serverEnvironment } from "../../state/server"; |
| 53 | import { useAtomCommand } from "../../state/use-atom-command"; | 53 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 54 | import { useNewTaskFlow } from "./new-task-flow-provider"; | 54 | import { useNewTaskFlow } from "./new-task-flow-provider"; |
| 55 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 55 | import { | 56 | import { |
| 56 | createProviderCatalogRefreshRunner, | 57 | createProviderCatalogRefreshRunner, |
| 57 | providerCatalogRefreshError, | 58 | providerCatalogRefreshError, |
| ... | @@ -104,6 +105,8 @@ function ModelRow(props: { | ... | @@ -104,6 +105,8 @@ function ModelRow(props: { |
| 104 | readonly isFirst: boolean; | 105 | readonly isFirst: boolean; |
| 105 | readonly isLast: boolean; | 106 | readonly isLast: boolean; |
| 106 | }) { | 107 | }) { |
| 108 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 109 | const selectedMaterialRow = materialYouStyleLayoutActive && props.selected; | ||
| 107 | return ( | 110 | return ( |
| 108 | <Pressable | 111 | <Pressable |
| 109 | accessibilityLabel={[props.option.label, props.option.subtitle].filter(Boolean).join(", ")} | 112 | accessibilityLabel={[props.option.label, props.option.subtitle].filter(Boolean).join(", ")} |
| ... | @@ -116,6 +119,7 @@ function ModelRow(props: { | ... | @@ -116,6 +119,7 @@ function ModelRow(props: { |
| 116 | onPress={props.onPress} | 119 | onPress={props.onPress} |
| 117 | className={cn( | 120 | className={cn( |
| 118 | "mx-4 min-h-11 flex-row items-center gap-2 bg-card px-4 py-2 active:bg-subtle", | 121 | "mx-4 min-h-11 flex-row items-center gap-2 bg-card px-4 py-2 active:bg-subtle", |
| 122 | selectedMaterialRow && "bg-thread-selected", | ||
| 119 | props.isFirst && "rounded-t-2xl", | 123 | props.isFirst && "rounded-t-2xl", |
| 120 | props.isLast ? "rounded-b-2xl" : "border-b border-border-subtle", | 124 | props.isLast ? "rounded-b-2xl" : "border-b border-border-subtle", |
| 121 | )} | 125 | )} |
apps/mobile/src/features/threads/thread-list-items.tsx+90-22| ... | @@ -463,9 +463,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -463,9 +463,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 463 | >["simultaneousWithExternalGesture"]; | 463 | >["simultaneousWithExternalGesture"]; |
| 464 | }) { | 464 | }) { |
| 465 | const { width: windowWidth } = useWindowDimensions(); | 465 | const { width: windowWidth } = useWindowDimensions(); |
| 466 | const { themeAppearance: colorScheme } = useAppearancePreferences(); | 466 | const { themeAppearance: colorScheme, materialYouStyleLayoutActive } = useAppearancePreferences(); |
| 467 | const compact = props.variant === "compact"; | 467 | const compact = props.variant === "compact"; |
| 468 | const selected = props.selected === true; | 468 | const selected = props.selected === true; |
| 469 | const visuallySelected = selected && (!compact || materialYouStyleLayoutActive); | ||
| 469 | // Recycling-safe: resets when the list container is reused for another | 470 | // Recycling-safe: resets when the list container is reused for another |
| 470 | // thread, so a hover highlight can't leak across rows. | 471 | // thread, so a hover highlight can't leak across rows. |
| 471 | const [hovered, setHovered] = useRecyclingState(false); | 472 | const [hovered, setHovered] = useRecyclingState(false); |
| ... | @@ -475,6 +476,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -475,6 +476,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 475 | const drawerColor = theme["--color-drawer"]; | 476 | const drawerColor = theme["--color-drawer"]; |
| 476 | const pressedBackgroundColor = theme["--color-subtle"]; | 477 | const pressedBackgroundColor = theme["--color-subtle"]; |
| 477 | const selectedBackgroundColor = theme["--color-user-bubble"]; | 478 | const selectedBackgroundColor = theme["--color-user-bubble"]; |
| 479 | const materialSelectedBackgroundColor = theme["--color-thread-selected"]; | ||
| 480 | const materialSelectedForegroundColor = theme["--color-thread-selected-foreground"]; | ||
| 478 | const selectedForegroundColor = theme["--color-user-bubble-foreground"]; | 481 | const selectedForegroundColor = theme["--color-user-bubble-foreground"]; |
| 479 | 482 | ||
| 480 | const { | 483 | const { |
| ... | @@ -502,15 +505,25 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -502,15 +505,25 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 502 | ); | 505 | ); |
| 503 | 506 | ||
| 504 | const backgroundColor = compact ? screenColor : drawerColor; | 507 | const backgroundColor = compact ? screenColor : drawerColor; |
| 505 | const effectivePressedBackground = selected | 508 | const effectiveSelectedBackground = materialYouStyleLayoutActive |
| 506 | ? themeColorWithAlpha(String(selectedForegroundColor), 0.16) | 509 | ? materialSelectedBackgroundColor |
| 510 | : selectedBackgroundColor; | ||
| 511 | const effectiveSelectedForeground = materialYouStyleLayoutActive | ||
| 512 | ? materialSelectedForegroundColor | ||
| 513 | : selectedForegroundColor; | ||
| 514 | const effectivePressedBackground = visuallySelected | ||
| 515 | ? themeColorWithAlpha(String(effectiveSelectedForeground), 0.16) | ||
| 507 | : pressedBackgroundColor; | 516 | : pressedBackgroundColor; |
| 508 | const effectiveStatus = | 517 | const effectiveStatus = |
| 509 | selected && status | 518 | visuallySelected && status |
| 510 | ? { | 519 | ? { |
| 511 | ...status, | 520 | ...status, |
| 512 | pillClassName: "bg-user-bubble-foreground/20", | 521 | pillClassName: materialYouStyleLayoutActive |
| 513 | textClassName: "text-user-bubble-foreground", | 522 | ? "bg-thread-selected-foreground/20" |
| 523 | : "bg-user-bubble-foreground/20", | ||
| 524 | textClassName: materialYouStyleLayoutActive | ||
| 525 | ? "text-thread-selected-foreground" | ||
| 526 | : "text-user-bubble-foreground", | ||
| 514 | } | 527 | } |
| 515 | : status; | 528 | : status; |
| 516 | 529 | ||
| ... | @@ -581,7 +594,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -581,7 +594,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 581 | compact | 594 | compact |
| 582 | ? "accent-icon-subtle" | 595 | ? "accent-icon-subtle" |
| 583 | : selected | 596 | : selected |
| 584 | ? "accent-user-bubble-foreground-muted" | 597 | ? materialYouStyleLayoutActive |
| 598 | ? "accent-thread-selected-foreground-muted" | ||
| 599 | : "accent-user-bubble-foreground-muted" | ||
| 585 | : "accent-foreground-muted" | 600 | : "accent-foreground-muted" |
| 586 | } | 601 | } |
| 587 | /> | 602 | /> |
| ... | @@ -589,9 +604,12 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -589,9 +604,12 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 589 | <Text | 604 | <Text |
| 590 | className={cn( | 605 | className={cn( |
| 591 | "shrink", | 606 | "shrink", |
| 592 | compact ? "text-sm text-foreground-muted" : "text-xs", | 607 | compact ? "text-sm" : "text-xs", |
| 593 | !compact && | 608 | visuallySelected |
| 594 | (selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted"), | 609 | ? materialYouStyleLayoutActive |
| 610 | ? "text-thread-selected-foreground-muted" | ||
| 611 | : "text-user-bubble-foreground-muted" | ||
| 612 | : "text-foreground-muted", | ||
| 595 | )} | 613 | )} |
| 596 | numberOfLines={1} | 614 | numberOfLines={1} |
| 597 | > | 615 | > |
| ... | @@ -604,12 +622,18 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -604,12 +622,18 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 604 | <PullRequestIcon | 622 | <PullRequestIcon |
| 605 | size={compact ? 13 : 11} | 623 | size={compact ? 13 : 11} |
| 606 | color={ | 624 | color={ |
| 607 | selected ? String(selectedForegroundColor) : pullRequestTintColor(pr, colorScheme) | 625 | visuallySelected |
| 626 | ? String(effectiveSelectedForeground) | ||
| 627 | : pullRequestTintColor(pr, colorScheme) | ||
| 608 | } | 628 | } |
| 609 | /> | 629 | /> |
| 610 | <Text | 630 | <Text |
| 611 | className={`${compact ? "text-sm" : "text-xs"} font-t3-medium ${ | 631 | className={`${compact ? "text-sm" : "text-xs"} font-t3-medium ${ |
| 612 | selected ? "text-user-bubble-foreground" : pr.textClassName | 632 | visuallySelected |
| 633 | ? materialYouStyleLayoutActive | ||
| 634 | ? "text-thread-selected-foreground" | ||
| 635 | : "text-user-bubble-foreground" | ||
| 636 | : pr.textClassName | ||
| 613 | }`} | 637 | }`} |
| 614 | > | 638 | > |
| 615 | {pr.label} | 639 | {pr.label} |
| ... | @@ -626,6 +650,14 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -626,6 +650,14 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 626 | accessibilityLabel={threadAccessibilityLabel} | 650 | accessibilityLabel={threadAccessibilityLabel} |
| 627 | accessibilityRole="button" | 651 | accessibilityRole="button" |
| 628 | className="bg-screen active:opacity-70" | 652 | className="bg-screen active:opacity-70" |
| 653 | style={ | ||
| 654 | materialYouStyleLayoutActive | ||
| 655 | ? { | ||
| 656 | backgroundColor: visuallySelected ? effectiveSelectedBackground : backgroundColor, | ||
| 657 | borderRadius: SIDEBAR_ROW_RADIUS, | ||
| 658 | } | ||
| 659 | : undefined | ||
| 660 | } | ||
| 629 | onPress={() => { | 661 | onPress={() => { |
| 630 | close(); | 662 | close(); |
| 631 | onSelectThread(thread); | 663 | onSelectThread(thread); |
| ... | @@ -634,13 +666,36 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -634,13 +666,36 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 634 | <View className="pr-[18px] pt-[10px]" style={{ paddingLeft: THREAD_LIST_COMPACT_INSET }}> | 666 | <View className="pr-[18px] pt-[10px]" style={{ paddingLeft: THREAD_LIST_COMPACT_INSET }}> |
| 635 | <View className={cn("gap-[3px] pb-[10px]", !props.isLast && "border-b border-separator")}> | 667 | <View className={cn("gap-[3px] pb-[10px]", !props.isLast && "border-b border-separator")}> |
| 636 | <View className="flex-row items-center justify-between gap-2"> | 668 | <View className="flex-row items-center justify-between gap-2"> |
| 637 | <Text className="flex-1 text-lg font-t3-bold text-foreground" numberOfLines={1}> | 669 | <Text |
| 670 | className={cn( | ||
| 671 | "flex-1 text-lg font-t3-bold", | ||
| 672 | visuallySelected | ||
| 673 | ? materialYouStyleLayoutActive | ||
| 674 | ? "text-thread-selected-foreground" | ||
| 675 | : "text-user-bubble-foreground" | ||
| 676 | : "text-foreground", | ||
| 677 | )} | ||
| 678 | numberOfLines={1} | ||
| 679 | > | ||
| 638 | {thread.title} | 680 | {thread.title} |
| 639 | </Text> | 681 | </Text> |
| 640 | <View className="flex-row items-center gap-2"> | 682 | <View className="flex-row items-center gap-2"> |
| 641 | {props.hasQueuedMessages ? <QueuedMessageIcon selected={selected} /> : null} | 683 | {props.hasQueuedMessages ? ( |
| 684 | <QueuedMessageIcon selected={visuallySelected && !materialYouStyleLayoutActive} /> | ||
| 685 | ) : null} | ||
| 642 | {statusPill} | 686 | {statusPill} |
| 643 | <Text className="text-base tabular-nums text-foreground-tertiary">{timestamp}</Text> | 687 | <Text |
| 688 | className={cn( | ||
| 689 | "text-base tabular-nums", | ||
| 690 | visuallySelected | ||
| 691 | ? materialYouStyleLayoutActive | ||
| 692 | ? "text-thread-selected-foreground-muted" | ||
| 693 | : "text-user-bubble-foreground-muted" | ||
| 694 | : "text-foreground-tertiary", | ||
| 695 | )} | ||
| 696 | > | ||
| 697 | {timestamp} | ||
| 698 | </Text> | ||
| 644 | <SymbolView | 699 | <SymbolView |
| 645 | name="chevron.right" | 700 | name="chevron.right" |
| 646 | size={13} | 701 | size={13} |
| ... | @@ -654,6 +709,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -654,6 +709,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 654 | compact | 709 | compact |
| 655 | match={props.searchMatch} | 710 | match={props.searchMatch} |
| 656 | query={props.searchQuery ?? ""} | 711 | query={props.searchQuery ?? ""} |
| 712 | selected={visuallySelected} | ||
| 657 | /> | 713 | /> |
| 658 | ) : null} | 714 | ) : null} |
| 659 | {subtitleRow} | 715 | {subtitleRow} |
| ... | @@ -673,8 +729,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -673,8 +729,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 673 | onSelectThread(thread); | 729 | onSelectThread(thread); |
| 674 | }} | 730 | }} |
| 675 | style={({ pressed }) => ({ | 731 | style={({ pressed }) => ({ |
| 676 | backgroundColor: selected | 732 | backgroundColor: visuallySelected |
| 677 | ? selectedBackgroundColor | 733 | ? effectiveSelectedBackground |
| 678 | : pressed || hovered | 734 | : pressed || hovered |
| 679 | ? effectivePressedBackground | 735 | ? effectivePressedBackground |
| 680 | : backgroundColor, | 736 | : backgroundColor, |
| ... | @@ -691,19 +747,29 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -691,19 +747,29 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 691 | <Text | 747 | <Text |
| 692 | className={cn( | 748 | className={cn( |
| 693 | "flex-1 text-base font-t3-medium", | 749 | "flex-1 text-base font-t3-medium", |
| 694 | selected ? "text-user-bubble-foreground" : "text-foreground", | 750 | visuallySelected |
| 751 | ? materialYouStyleLayoutActive | ||
| 752 | ? "text-thread-selected-foreground" | ||
| 753 | : "text-user-bubble-foreground" | ||
| 754 | : "text-foreground", | ||
| 695 | )} | 755 | )} |
| 696 | numberOfLines={1} | 756 | numberOfLines={1} |
| 697 | > | 757 | > |
| 698 | {thread.title} | 758 | {thread.title} |
| 699 | </Text> | 759 | </Text> |
| 700 | <View className="flex-row items-center gap-2"> | 760 | <View className="flex-row items-center gap-2"> |
| 701 | {props.hasQueuedMessages ? <QueuedMessageIcon selected={selected} /> : null} | 761 | {props.hasQueuedMessages ? ( |
| 762 | <QueuedMessageIcon selected={visuallySelected && !materialYouStyleLayoutActive} /> | ||
| 763 | ) : null} | ||
| 702 | {statusPill} | 764 | {statusPill} |
| 703 | <Text | 765 | <Text |
| 704 | className={cn( | 766 | className={cn( |
| 705 | "text-xs tabular-nums", | 767 | "text-xs tabular-nums", |
| 706 | selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted", | 768 | visuallySelected |
| 769 | ? materialYouStyleLayoutActive | ||
| 770 | ? "text-thread-selected-foreground-muted" | ||
| 771 | : "text-user-bubble-foreground-muted" | ||
| 772 | : "text-foreground-muted", | ||
| 707 | )} | 773 | )} |
| 708 | numberOfLines={1} | 774 | numberOfLines={1} |
| 709 | > | 775 | > |
| ... | @@ -715,7 +781,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -715,7 +781,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 715 | <ThreadSearchMatchExcerpt | 781 | <ThreadSearchMatchExcerpt |
| 716 | match={props.searchMatch} | 782 | match={props.searchMatch} |
| 717 | query={props.searchQuery ?? ""} | 783 | query={props.searchQuery ?? ""} |
| 718 | selected={selected} | 784 | selected={visuallySelected} |
| 719 | /> | 785 | /> |
| 720 | ) : null} | 786 | ) : null} |
| 721 | {subtitleRow} | 787 | {subtitleRow} |
| ... | @@ -728,7 +794,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -728,7 +794,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 728 | threadKey={`${thread.environmentId}:${thread.id}`} | 794 | threadKey={`${thread.environmentId}:${thread.id}`} |
| 729 | backgroundColor={backgroundColor} | 795 | backgroundColor={backgroundColor} |
| 730 | containerStyle={ | 796 | containerStyle={ |
| 731 | compact ? undefined : { borderRadius: SIDEBAR_ROW_RADIUS, overflow: "hidden" } | 797 | compact && !materialYouStyleLayoutActive |
| 798 | ? undefined | ||
| 799 | : { borderRadius: SIDEBAR_ROW_RADIUS, overflow: "hidden" } | ||
| 732 | } | 800 | } |
| 733 | enableTrackpadSwipe | 801 | enableTrackpadSwipe |
| 734 | fullSwipeWidth={props.fullSwipeWidth ?? windowWidth - 32} | 802 | fullSwipeWidth={props.fullSwipeWidth ?? windowWidth - 32} |
apps/mobile/src/features/threads/thread-list-v2-items.tsx+69-20| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 1 | import type { | 2 | import type { |
| 2 | EnvironmentProject, | 3 | EnvironmentProject, |
| 3 | EnvironmentThreadShell, | 4 | EnvironmentThreadShell, |
| ... | @@ -429,11 +430,13 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -429,11 +430,13 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 429 | 430 | ||
| 430 | const pr = useThreadPr(thread); | 431 | const pr = useThreadPr(thread); |
| 431 | 432 | ||
| 433 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 432 | const theme = useUniwindTheme(); | 434 | const theme = useUniwindTheme(); |
| 433 | const screenColor = theme["--color-screen"]; | 435 | const screenColor = theme["--color-screen"]; |
| 434 | const drawerColor = theme["--color-drawer"]; | 436 | const drawerColor = theme["--color-drawer"]; |
| 435 | const pressedBackgroundColor = theme["--color-subtle"]; | 437 | const pressedBackgroundColor = theme["--color-subtle"]; |
| 436 | const selectedBackgroundColor = theme["--color-user-bubble"]; | 438 | const selectedBackgroundColor = |
| 439 | theme[materialYouStyleLayoutActive ? "--color-thread-selected" : "--color-user-bubble"]; | ||
| 437 | const sidebarPane = props.pane === "sidebar"; | 440 | const sidebarPane = props.pane === "sidebar"; |
| 438 | const selected = props.selected === true; | 441 | const selected = props.selected === true; |
| 439 | // The provider badge's border blends into the row's own surface, which | 442 | // The provider badge's border blends into the row's own surface, which |
| ... | @@ -720,7 +723,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -720,7 +723,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 720 | <Text | 723 | <Text |
| 721 | className={cn( | 724 | className={cn( |
| 722 | "flex-1 text-sm font-t3-medium", | 725 | "flex-1 text-sm font-t3-medium", |
| 723 | selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted", | 726 | selected |
| 727 | ? materialYouStyleLayoutActive | ||
| 728 | ? "text-thread-selected-foreground-muted" | ||
| 729 | : "text-user-bubble-foreground-muted" | ||
| 730 | : "text-foreground-muted", | ||
| 724 | )} | 731 | )} |
| 725 | numberOfLines={1} | 732 | numberOfLines={1} |
| 726 | > | 733 | > |
| ... | @@ -739,7 +746,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -739,7 +746,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 739 | className={cn( | 746 | className={cn( |
| 740 | "text-xs tabular-nums", | 747 | "text-xs tabular-nums", |
| 741 | selected | 748 | selected |
| 742 | ? "text-user-bubble-foreground" | 749 | ? materialYouStyleLayoutActive |
| 750 | ? "text-thread-selected-foreground" | ||
| 751 | : "text-user-bubble-foreground" | ||
| 743 | : (statusLabel?.className ?? "text-foreground-tertiary"), | 752 | : (statusLabel?.className ?? "text-foreground-tertiary"), |
| 744 | )} | 753 | )} |
| 745 | > | 754 | > |
| ... | @@ -749,7 +758,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -749,7 +758,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 749 | <Text | 758 | <Text |
| 750 | className={cn( | 759 | className={cn( |
| 751 | "mt-1 text-base font-t3-medium", | 760 | "mt-1 text-base font-t3-medium", |
| 752 | selected ? "text-user-bubble-foreground" : "text-foreground", | 761 | selected |
| 762 | ? materialYouStyleLayoutActive | ||
| 763 | ? "text-thread-selected-foreground" | ||
| 764 | : "text-user-bubble-foreground" | ||
| 765 | : "text-foreground", | ||
| 753 | )} | 766 | )} |
| 754 | numberOfLines={2} | 767 | numberOfLines={2} |
| 755 | > | 768 | > |
| ... | @@ -769,7 +782,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -769,7 +782,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 769 | <Text | 782 | <Text |
| 770 | className={cn( | 783 | className={cn( |
| 771 | "flex-1 text-xs", | 784 | "flex-1 text-xs", |
| 772 | selected ? "text-user-bubble-foreground-muted" : "text-danger-foreground", | 785 | selected |
| 786 | ? materialYouStyleLayoutActive | ||
| 787 | ? "text-thread-selected-foreground-muted" | ||
| 788 | : "text-user-bubble-foreground-muted" | ||
| 789 | : "text-danger-foreground", | ||
| 773 | )} | 790 | )} |
| 774 | numberOfLines={1} | 791 | numberOfLines={1} |
| 775 | > | 792 | > |
| ... | @@ -786,7 +803,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -786,7 +803,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 786 | <Text | 803 | <Text |
| 787 | className={cn( | 804 | className={cn( |
| 788 | "shrink text-xs", | 805 | "shrink text-xs", |
| 789 | selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted", | 806 | selected |
| 807 | ? materialYouStyleLayoutActive | ||
| 808 | ? "text-thread-selected-foreground-muted" | ||
| 809 | : "text-user-bubble-foreground-muted" | ||
| 810 | : "text-foreground-muted", | ||
| 790 | )} | 811 | )} |
| 791 | numberOfLines={1} | 812 | numberOfLines={1} |
| 792 | > | 813 | > |
| ... | @@ -794,7 +815,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -794,7 +815,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 794 | <Text | 815 | <Text |
| 795 | className={cn( | 816 | className={cn( |
| 796 | "text-xs", | 817 | "text-xs", |
| 797 | selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted", | 818 | selected |
| 819 | ? materialYouStyleLayoutActive | ||
| 820 | ? "text-thread-selected-foreground-muted" | ||
| 821 | : "text-user-bubble-foreground-muted" | ||
| 822 | : "text-foreground-muted", | ||
| 798 | )} | 823 | )} |
| 799 | style={{ fontFamily: MONO_FONT }} | 824 | style={{ fontFamily: MONO_FONT }} |
| 800 | > | 825 | > |
| ... | @@ -806,7 +831,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -806,7 +831,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 806 | <Text | 831 | <Text |
| 807 | className={cn( | 832 | className={cn( |
| 808 | "text-xs", | 833 | "text-xs", |
| 809 | selected ? "text-user-bubble-foreground-muted" : "text-foreground-tertiary", | 834 | selected |
| 835 | ? materialYouStyleLayoutActive | ||
| 836 | ? "text-thread-selected-foreground-muted" | ||
| 837 | : "text-user-bubble-foreground-muted" | ||
| 838 | : "text-foreground-tertiary", | ||
| 810 | )} | 839 | )} |
| 811 | > | 840 | > |
| 812 | {props.environmentLabel} | 841 | {props.environmentLabel} |
| ... | @@ -818,7 +847,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -818,7 +847,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 818 | kind={props.environmentMachine} | 847 | kind={props.environmentMachine} |
| 819 | size={11} | 848 | size={11} |
| 820 | tintColorClassName={ | 849 | tintColorClassName={ |
| 821 | selected ? "accent-user-bubble-foreground-muted" : "accent-foreground-tertiary" | 850 | selected |
| 851 | ? materialYouStyleLayoutActive | ||
| 852 | ? "accent-thread-selected-foreground-muted" | ||
| 853 | : "accent-user-bubble-foreground-muted" | ||
| 854 | : "accent-foreground-tertiary" | ||
| 822 | } | 855 | } |
| 823 | /> | 856 | /> |
| 824 | ) : null} | 857 | ) : null} |
| ... | @@ -829,7 +862,14 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -829,7 +862,14 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 829 | {pr ? ( | 862 | {pr ? ( |
| 830 | <Text | 863 | <Text |
| 831 | accessibilityLabel={pr.accessibilityLabel} | 864 | accessibilityLabel={pr.accessibilityLabel} |
| 832 | className={cn("text-xs", selected ? "text-user-bubble-foreground" : pr.textClassName)} | 865 | className={cn( |
| 866 | "text-xs", | ||
| 867 | selected | ||
| 868 | ? materialYouStyleLayoutActive | ||
| 869 | ? "text-thread-selected-foreground" | ||
| 870 | : "text-user-bubble-foreground" | ||
| 871 | : pr.textClassName, | ||
| 872 | )} | ||
| 833 | style={{ fontFamily: MONO_FONT }} | 873 | style={{ fontFamily: MONO_FONT }} |
| 834 | > | 874 | > |
| 835 | #{pr.label} | 875 | #{pr.label} |
| ... | @@ -863,16 +903,17 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -863,16 +903,17 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 863 | onSelectThread(thread); | 903 | onSelectThread(thread); |
| 864 | }} | 904 | }} |
| 865 | style={ | 905 | style={ |
| 866 | sidebarPane | 906 | sidebarPane || materialYouStyleLayoutActive |
| 867 | ? ({ pressed }) => ({ | 907 | ? ({ pressed }) => ({ |
| 868 | backgroundColor: selected | 908 | backgroundColor: selected |
| 869 | ? selectedBackgroundColor | 909 | ? selectedBackgroundColor |
| 870 | : pressed | 910 | : pressed |
| 871 | ? pressedBackgroundColor | 911 | ? pressedBackgroundColor |
| 872 | : drawerColor, | 912 | : sidebarPane |
| 913 | ? drawerColor | ||
| 914 | : screenColor, | ||
| 873 | borderRadius: SIDEBAR_V2_ROW_RADIUS, | 915 | borderRadius: SIDEBAR_V2_ROW_RADIUS, |
| 874 | paddingHorizontal: 12, | 916 | ...(sidebarPane ? { paddingHorizontal: 12, paddingVertical: 10 } : null), |
| 875 | paddingVertical: 10, | ||
| 876 | }) | 917 | }) |
| 877 | : ({ pressed }) => ({ opacity: pressed ? 0.7 : 1 }) | 918 | : ({ pressed }) => ({ opacity: pressed ? 0.7 : 1 }) |
| 878 | } | 919 | } |
| ... | @@ -884,7 +925,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -884,7 +925,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 884 | labels and text hierarchy carry state, an inset hairline | 925 | labels and text hierarchy carry state, an inset hairline |
| 885 | separates rows. The opaque screen background stays so swipe | 926 | separates rows. The opaque screen background stays so swipe |
| 886 | actions reveal behind the row. */ | 927 | actions reveal behind the row. */ |
| 887 | <View className="bg-screen"> | 928 | <View className={materialYouStyleLayoutActive ? undefined : "bg-screen"}> |
| 888 | <View className="px-5 py-2.5">{cardContent}</View> | 929 | <View className="px-5 py-2.5">{cardContent}</View> |
| 889 | {props.showTrailingDivider !== false ? ( | 930 | {props.showTrailingDivider !== false ? ( |
| 890 | <View className="ml-5 h-px bg-border-subtle" /> | 931 | <View className="ml-5 h-px bg-border-subtle" /> |
| ... | @@ -900,19 +941,21 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -900,19 +941,21 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 900 | } | 941 | } |
| 901 | accessibilityRole="button" | 942 | accessibilityRole="button" |
| 902 | accessibilityState={{ selected }} | 943 | accessibilityState={{ selected }} |
| 903 | className={sidebarPane ? undefined : "bg-screen"} | 944 | className={sidebarPane || materialYouStyleLayoutActive ? undefined : "bg-screen"} |
| 904 | onPress={() => { | 945 | onPress={() => { |
| 905 | close(); | 946 | close(); |
| 906 | onSelectThread(thread); | 947 | onSelectThread(thread); |
| 907 | }} | 948 | }} |
| 908 | style={ | 949 | style={ |
| 909 | sidebarPane | 950 | sidebarPane || materialYouStyleLayoutActive |
| 910 | ? ({ pressed }) => ({ | 951 | ? ({ pressed }) => ({ |
| 911 | backgroundColor: selected | 952 | backgroundColor: selected |
| 912 | ? selectedBackgroundColor | 953 | ? selectedBackgroundColor |
| 913 | : pressed | 954 | : pressed |
| 914 | ? pressedBackgroundColor | 955 | ? pressedBackgroundColor |
| 915 | : drawerColor, | 956 | : sidebarPane |
| 957 | ? drawerColor | ||
| 958 | : screenColor, | ||
| 916 | borderRadius: SIDEBAR_V2_ROW_RADIUS, | 959 | borderRadius: SIDEBAR_V2_ROW_RADIUS, |
| 917 | }) | 960 | }) |
| 918 | : ({ pressed }) => ({ opacity: pressed ? 0.7 : 1 }) | 961 | : ({ pressed }) => ({ opacity: pressed ? 0.7 : 1 }) |
| ... | @@ -940,7 +983,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -940,7 +983,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 940 | <Text | 983 | <Text |
| 941 | className={cn( | 984 | className={cn( |
| 942 | "text-base", | 985 | "text-base", |
| 943 | selected ? "text-user-bubble-foreground" : "text-foreground-muted", | 986 | selected |
| 987 | ? materialYouStyleLayoutActive | ||
| 988 | ? "text-thread-selected-foreground" | ||
| 989 | : "text-user-bubble-foreground" | ||
| 990 | : "text-foreground-muted", | ||
| 944 | )} | 991 | )} |
| 945 | numberOfLines={1} | 992 | numberOfLines={1} |
| 946 | > | 993 | > |
| ... | @@ -959,7 +1006,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -959,7 +1006,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 959 | className={cn( | 1006 | className={cn( |
| 960 | "text-sm tabular-nums", | 1007 | "text-sm tabular-nums", |
| 961 | selected | 1008 | selected |
| 962 | ? "text-user-bubble-foreground-muted" | 1009 | ? materialYouStyleLayoutActive |
| 1010 | ? "text-thread-selected-foreground-muted" | ||
| 1011 | : "text-user-bubble-foreground-muted" | ||
| 963 | : snoozedRow | 1012 | : snoozedRow |
| 964 | ? "text-foreground-secondary" | 1013 | ? "text-foreground-secondary" |
| 965 | : "text-foreground-tertiary", | 1014 | : "text-foreground-tertiary", |
apps/mobile/src/features/threads/thread-search-match.tsx+13-3| ... | @@ -2,6 +2,7 @@ import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state | ... | @@ -2,6 +2,7 @@ import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state |
| 2 | 2 | ||
| 3 | import { AppText as Text } from "../../components/AppText"; | 3 | import { AppText as Text } from "../../components/AppText"; |
| 4 | import { cn } from "../../lib/cn"; | 4 | import { cn } from "../../lib/cn"; |
| 5 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 5 | 6 | ||
| 6 | function foldAsciiCase(value: string): string { | 7 | function foldAsciiCase(value: string): string { |
| 7 | return value.replace(/[A-Z]/g, (character) => character.toLowerCase()); | 8 | return value.replace(/[A-Z]/g, (character) => character.toLowerCase()); |
| ... | @@ -50,12 +51,17 @@ export function ThreadSearchMatchExcerpt(props: { | ... | @@ -50,12 +51,17 @@ export function ThreadSearchMatchExcerpt(props: { |
| 50 | readonly compact?: boolean; | 51 | readonly compact?: boolean; |
| 51 | }) { | 52 | }) { |
| 52 | const isUser = props.match.source === "user"; | 53 | const isUser = props.match.source === "user"; |
| 54 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | ||
| 53 | const parts = splitHighlightParts(props.match.snippet, props.query); | 55 | const parts = splitHighlightParts(props.match.snippet, props.query); |
| 54 | return ( | 56 | return ( |
| 55 | <Text | 57 | <Text |
| 56 | className={cn( | 58 | className={cn( |
| 57 | props.compact ? "text-sm" : "text-xs", | 59 | props.compact ? "text-sm" : "text-xs", |
| 58 | props.selected ? "text-user-bubble-foreground-muted" : "text-foreground-muted", | 60 | props.selected |
| 61 | ? materialYouStyleLayoutActive | ||
| 62 | ? "text-thread-selected-foreground-muted" | ||
| 63 | : "text-user-bubble-foreground-muted" | ||
| 64 | : "text-foreground-muted", | ||
| 59 | )} | 65 | )} |
| 60 | numberOfLines={1} | 66 | numberOfLines={1} |
| 61 | > | 67 | > |
| ... | @@ -63,7 +69,9 @@ export function ThreadSearchMatchExcerpt(props: { | ... | @@ -63,7 +69,9 @@ export function ThreadSearchMatchExcerpt(props: { |
| 63 | className={cn( | 69 | className={cn( |
| 64 | props.compact ? "text-sm font-t3-medium" : "text-xs font-t3-medium", | 70 | props.compact ? "text-sm font-t3-medium" : "text-xs font-t3-medium", |
| 65 | props.selected | 71 | props.selected |
| 66 | ? "text-user-bubble-foreground" | 72 | ? materialYouStyleLayoutActive |
| 73 | ? "text-thread-selected-foreground" | ||
| 74 | : "text-user-bubble-foreground" | ||
| 67 | : isUser | 75 | : isUser |
| 68 | ? "text-foreground-secondary" | 76 | ? "text-foreground-secondary" |
| 69 | : "text-adaptive-emerald-600-400", | 77 | : "text-adaptive-emerald-600-400", |
| ... | @@ -77,7 +85,9 @@ export function ThreadSearchMatchExcerpt(props: { | ... | @@ -77,7 +85,9 @@ export function ThreadSearchMatchExcerpt(props: { |
| 77 | props.compact ? "text-sm" : "text-xs", | 85 | props.compact ? "text-sm" : "text-xs", |
| 78 | part.highlighted && "font-t3-bold", | 86 | part.highlighted && "font-t3-bold", |
| 79 | props.selected | 87 | props.selected |
| 80 | ? "text-user-bubble-foreground" | 88 | ? materialYouStyleLayoutActive |
| 89 | ? "text-thread-selected-foreground" | ||
| 90 | : "text-user-bubble-foreground" | ||
| 81 | : part.highlighted | 91 | : part.highlighted |
| 82 | ? "text-foreground" | 92 | ? "text-foreground" |
| 83 | : "text-foreground-muted", | 93 | : "text-foreground-muted", |
apps/mobile/src/lib/materialYouTheme.test.ts+2| ... | @@ -39,6 +39,8 @@ describe("Material You system colors", () => { | ... | @@ -39,6 +39,8 @@ describe("Material You system colors", () => { |
| 39 | 39 | ||
| 40 | expect(base).toEqual(snapshot); | 40 | expect(base).toEqual(snapshot); |
| 41 | expect(variables["--color-screen"]).toBe(palette.surface); | 41 | expect(variables["--color-screen"]).toBe(palette.surface); |
| 42 | expect(variables["--color-thread-canvas"]).toBe(palette.surfaceContainerLow); | ||
| 43 | expect(variables["--color-thread-selected"]).toBe(palette.surfaceContainer); | ||
| 42 | expect(variables["--color-header"]).toBe(palette.surfaceContainerHigh); | 44 | expect(variables["--color-header"]).toBe(palette.surfaceContainerHigh); |
| 43 | expect(variables["--color-primary"]).toBe(palette.primary); | 45 | expect(variables["--color-primary"]).toBe(palette.primary); |
| 44 | expect(variables["--color-placeholder"]).toBe("#1C1B1F9E"); | 46 | expect(variables["--color-placeholder"]).toBe("#1C1B1F9E"); |
apps/mobile/src/lib/materialYouTheme.ts+7| ... | @@ -38,6 +38,13 @@ export function materialYouPaletteToMobileThemeVariables( | ... | @@ -38,6 +38,13 @@ export function materialYouPaletteToMobileThemeVariables( |
| 38 | "--color-card": palette.surfaceContainer, | 38 | "--color-card": palette.surfaceContainer, |
| 39 | "--color-card-alt": palette.surfaceContainerHigh, | 39 | "--color-card-alt": palette.surfaceContainerHigh, |
| 40 | "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8), | 40 | "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8), |
| 41 | "--color-thread-canvas": palette.surfaceContainerLow, | ||
| 42 | "--color-thread-selected": palette.surfaceContainer, | ||
| 43 | "--color-thread-selected-foreground": palette.onSurface, | ||
| 44 | "--color-thread-selected-foreground-muted": palette.onSurfaceVariant, | ||
| 45 | "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88), | ||
| 46 | "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94), | ||
| 47 | "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46), | ||
| 41 | "--color-foreground": palette.onSurface, | 48 | "--color-foreground": palette.onSurface, |
| 42 | "--color-foreground-secondary": palette.onSurfaceVariant, | 49 | "--color-foreground-secondary": palette.onSurfaceVariant, |
| 43 | "--color-foreground-muted": palette.onSurfaceVariant, | 50 | "--color-foreground-muted": palette.onSurfaceVariant, |
apps/mobile/src/lib/mobileTheme.test.ts+1-1| ... | @@ -153,7 +153,7 @@ describe("mobile themes", () => { | ... | @@ -153,7 +153,7 @@ describe("mobile themes", () => { |
| 153 | 153 | ||
| 154 | it("maps semantic palette roles onto every mobile color variable", () => { | 154 | it("maps semantic palette roles onto every mobile color variable", () => { |
| 155 | const variables = createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light"); | 155 | const variables = createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light"); |
| 156 | expect(Object.keys(variables)).toHaveLength(68); | 156 | expect(Object.keys(variables)).toHaveLength(75); |
| 157 | expect(variables["--color-sheet-solid"]).toBe( | 157 | expect(variables["--color-sheet-solid"]).toBe( |
| 158 | themeColorToNativeColor(BUILT_IN_THEMES[0].colors.chrome), | 158 | themeColorToNativeColor(BUILT_IN_THEMES[0].colors.chrome), |
| 159 | ); | 159 | ); |
apps/mobile/src/lib/mobileTheme.ts+10| ... | @@ -218,6 +218,16 @@ export function createMobileThemeVariables( | ... | @@ -218,6 +218,16 @@ export function createMobileThemeVariables( |
| 218 | "--color-card": c.surfaceRaised, | 218 | "--color-card": c.surfaceRaised, |
| 219 | "--color-card-alt": c.surface, | 219 | "--color-card-alt": c.surface, |
| 220 | "--color-card-translucent": withAlpha(c.surfaceRaised, 0.8), | 220 | "--color-card-translucent": withAlpha(c.surfaceRaised, 0.8), |
| 221 | "--color-thread-canvas": c.surface, | ||
| 222 | "--color-thread-selected": c.surfaceRaised, | ||
| 223 | "--color-thread-selected-foreground": c.text, | ||
| 224 | "--color-thread-selected-foreground-muted": c.textMuted, | ||
| 225 | "--color-composer-panel": themeColorWithAlpha(c.surface, appearance === "dark" ? 0.92 : 0.88), | ||
| 226 | "--color-composer-surface": themeColorWithAlpha( | ||
| 227 | c.surfaceRaised, | ||
| 228 | appearance === "dark" ? 0.9 : 0.94, | ||
| 229 | ), | ||
| 230 | "--color-composer-border": themeColorWithAlpha(c.border, appearance === "dark" ? 0.46 : 0.54), | ||
| 221 | "--color-foreground": c.text, | 231 | "--color-foreground": c.text, |
| 222 | "--color-foreground-secondary": c.textMuted, | 232 | "--color-foreground-secondary": c.textMuted, |
| 223 | "--color-foreground-muted": c.mutedForeground, | 233 | "--color-foreground-muted": c.mutedForeground, |
apps/mobile/src/lib/storage.test.ts+17| ... | @@ -204,6 +204,23 @@ describe("mobile connection storage", () => { | ... | @@ -204,6 +204,23 @@ describe("mobile connection storage", () => { |
| 204 | await expect(loadPreferences()).resolves.toEqual({ ...themes, lightThemeId: "t3-chat" }); | 204 | await expect(loadPreferences()).resolves.toEqual({ ...themes, lightThemeId: "t3-chat" }); |
| 205 | }); | 205 | }); |
| 206 | 206 | ||
| 207 | it("persists the Material You layout independently of the selected theme", async () => { | ||
| 208 | await savePreferencesPatch({ | ||
| 209 | lightThemeId: "material-you", | ||
| 210 | materialYouStyleLayoutEnabled: true, | ||
| 211 | }); | ||
| 212 | await savePreferencesPatch({ lightThemeId: "t3-chat" }); | ||
| 213 | await expect(loadPreferences()).resolves.toEqual({ | ||
| 214 | lightThemeId: "t3-chat", | ||
| 215 | materialYouStyleLayoutEnabled: true, | ||
| 216 | }); | ||
| 217 | await savePreferencesPatch({ materialYouStyleLayoutEnabled: false }); | ||
| 218 | await expect(loadPreferences()).resolves.toEqual({ | ||
| 219 | lightThemeId: "t3-chat", | ||
| 220 | materialYouStyleLayoutEnabled: false, | ||
| 221 | }); | ||
| 222 | }); | ||
| 223 | |||
| 207 | it("drops the removed theme transition preference", async () => { | 224 | it("drops the removed theme transition preference", async () => { |
| 208 | mocks.setPreferencesJson(JSON.stringify({ themeTransition: "circle-bottom-left" }), 10); | 225 | mocks.setPreferencesJson(JSON.stringify({ themeTransition: "circle-bottom-left" }), 10); |
| 209 | 226 |
apps/mobile/src/persistence/mobile-preferences.ts+5| ... | @@ -21,6 +21,7 @@ export interface Preferences { | ... | @@ -21,6 +21,7 @@ export interface Preferences { |
| 21 | readonly lightThemeId?: MobileThemeId; | 21 | readonly lightThemeId?: MobileThemeId; |
| 22 | readonly darkThemeId?: MobileThemeId; | 22 | readonly darkThemeId?: MobileThemeId; |
| 23 | readonly themeMode?: MobileThemeMode; | 23 | readonly themeMode?: MobileThemeMode; |
| 24 | readonly materialYouStyleLayoutEnabled?: boolean; | ||
| 24 | readonly baseFontSize?: number; | 25 | readonly baseFontSize?: number; |
| 25 | readonly terminalFontSize?: number | null; | 26 | readonly terminalFontSize?: number | null; |
| 26 | readonly markdownFontSize?: number; | 27 | readonly markdownFontSize?: number; |
| ... | @@ -90,6 +91,7 @@ function sanitizePreferences(parsed: Preferences): Preferences { | ... | @@ -90,6 +91,7 @@ function sanitizePreferences(parsed: Preferences): Preferences { |
| 90 | lightThemeId?: MobileThemeId; | 91 | lightThemeId?: MobileThemeId; |
| 91 | darkThemeId?: MobileThemeId; | 92 | darkThemeId?: MobileThemeId; |
| 92 | themeMode?: MobileThemeMode; | 93 | themeMode?: MobileThemeMode; |
| 94 | materialYouStyleLayoutEnabled?: boolean; | ||
| 93 | baseFontSize?: number; | 95 | baseFontSize?: number; |
| 94 | terminalFontSize?: number | null; | 96 | terminalFontSize?: number | null; |
| 95 | markdownFontSize?: number; | 97 | markdownFontSize?: number; |
| ... | @@ -133,6 +135,9 @@ function sanitizePreferences(parsed: Preferences): Preferences { | ... | @@ -133,6 +135,9 @@ function sanitizePreferences(parsed: Preferences): Preferences { |
| 133 | ) { | 135 | ) { |
| 134 | preferences.themeMode = parsed.themeMode; | 136 | preferences.themeMode = parsed.themeMode; |
| 135 | } | 137 | } |
| 138 | if (typeof parsed.materialYouStyleLayoutEnabled === "boolean") { | ||
| 139 | preferences.materialYouStyleLayoutEnabled = parsed.materialYouStyleLayoutEnabled; | ||
| 140 | } | ||
| 136 | if (typeof parsed.baseFontSize === "number") preferences.baseFontSize = parsed.baseFontSize; | 141 | if (typeof parsed.baseFontSize === "number") preferences.baseFontSize = parsed.baseFontSize; |
| 137 | if (typeof parsed.terminalFontSize === "number" || parsed.terminalFontSize === null) { | 142 | if (typeof parsed.terminalFontSize === "number" || parsed.terminalFontSize === null) { |
| 138 | preferences.terminalFontSize = parsed.terminalFontSize; | 143 | preferences.terminalFontSize = parsed.terminalFontSize; |
docs/user/appearance.md+2| ... | @@ -10,6 +10,8 @@ themes or defaults. | ... | @@ -10,6 +10,8 @@ themes or defaults. |
| 10 | On Android 12 or newer, choose the **Material You** theme in Appearance to use colors from | 10 | On Android 12 or newer, choose the **Material You** theme in Appearance to use colors from |
| 11 | your wallpaper. Selecting another theme replaces those colors. Like other themes, Material You | 11 | your wallpaper. Selecting another theme replaces those colors. Like other themes, Material You |
| 12 | can be selected separately for light and dark appearances. | 12 | can be selected separately for light and dark appearances. |
| 13 | Android also offers **Material You Layout**, which changes shapes and spacing independently | ||
| 14 | of the selected theme. | ||
| 13 | 15 | ||
| 14 | ## Motion | 16 | ## Motion |
| 15 | 17 |