Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/mobile/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
--color-card: #ffffff;
--color-card-alt: #f5f5f5;
--color-card-translucent: rgba(255, 255, 255, 0.8);
--color-thread-canvas: #f5f5f5;
--color-thread-selected: #ffffff;
--color-thread-selected-foreground: #262626;
--color-thread-selected-foreground-muted: #737373;
--color-composer-panel: rgba(245, 245, 245, 0.88);
--color-composer-surface: rgba(255, 255, 255, 0.94);
--color-composer-border: rgba(0, 0, 0, 0.1);

/* Text */
--color-foreground: #262626;
Expand Down Expand Up @@ -117,6 +124,13 @@
--color-card: #171717;
--color-card-alt: #1c1c1c;
--color-card-translucent: rgba(17, 17, 17, 0.8);
--color-thread-canvas: #1c1c1c;
--color-thread-selected: #171717;
--color-thread-selected-foreground: #f5f5f5;
--color-thread-selected-foreground-muted: #8e8e93;
--color-composer-panel: rgba(28, 28, 28, 0.92);
--color-composer-surface: rgba(23, 23, 23, 0.9);
--color-composer-border: rgba(255, 255, 255, 0.08);

/* Text */
--color-foreground: #f5f5f5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Typeface
import android.os.Build
import android.text.Editable
import android.text.InputType
import android.text.Spanned
import android.text.TextWatcher
import android.text.style.ReplacementSpan
import android.util.TypedValue
import android.view.Gravity
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
Expand All @@ -27,6 +29,11 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView(
appContext
) {
private val editor = SelectionAwareEditText(context)
private val defaultHighlightColor = editor.highlightColor
private val defaultSelectionColor = context.resolveThemeColor(
android.R.attr.colorAccent,
editor.currentTextColor,
)
private val onComposerChange by EventDispatcher()
private val onComposerSelectionChange by EventDispatcher()
private val onComposerFocus by EventDispatcher()
Expand Down Expand Up @@ -152,6 +159,11 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView(
val theme = JSONObject(themeJson)
editor.setTextColor(parseColor(theme.optString("text"), Color.BLACK))
editor.setHintTextColor(parseColor(theme.optString("placeholder"), Color.GRAY))
if (theme.isNull("selection")) {
resetSelectionTheme()
} else {
applySelectionTheme(parseColor(theme.optString("selection"), editor.currentTextColor))
}
chipTheme = ComposerChipTheme(
chipBackground = parseColor(theme.optString("chipBackground"), chipTheme.chipBackground),
chipBorder = parseColor(theme.optString("chipBorder"), chipTheme.chipBorder),
Expand Down Expand Up @@ -277,6 +289,21 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView(
editor.setLineSpacing(max(0, desiredLineHeightPx - fontHeight).toFloat(), 1f)
}

private fun applySelectionTheme(color: Int) {
editor.highlightColor = color.withAlpha(0x52)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return

editor.textCursorDrawable = editor.textCursorDrawable?.mutate()?.apply { setTint(color) }
editor.textSelectHandle?.mutate()?.setTint(color)
editor.textSelectHandleLeft?.mutate()?.setTint(color)
editor.textSelectHandleRight?.mutate()?.setTint(color)
}

private fun resetSelectionTheme() {
applySelectionTheme(defaultSelectionColor)
editor.highlightColor = defaultHighlightColor
}

private fun currentSelectionPayload(): Map<String, Int> =
mapOf(
"start" to editor.selectionStart.coerceAtLeast(0),
Expand Down Expand Up @@ -335,12 +362,24 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView(

private fun parseColor(value: String, fallback: Int): Int =
try {
Color.parseColor(value)
val androidColor = if (value.length == 9 && value.startsWith("#")) {
"#${value.takeLast(2)}${value.substring(1, 7)}"
} else {
value
}
Color.parseColor(androidColor)
} catch (_: Exception) {
fallback
}
}

private fun Int.withAlpha(alpha: Int): Int = (this and 0x00FFFFFF) or (alpha shl 24)

private fun Context.resolveThemeColor(attribute: Int, fallback: Int): Int {
val value = TypedValue()
return if (theme.resolveAttribute(attribute, value, true)) value.data else fallback
}

private data class ComposerToken(
val type: String,
val source: String,
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@expo/metro-runtime": "~56.0.15",
"@expo/ui": "~56.0.18",
"@legendapp/list": "catalog:",
"@material/material-color-utilities": "0.3.0",
"@noble/curves": "catalog:",
"@noble/hashes": "catalog:",
"@pierre/diffs": "catalog:",
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/components/AndroidScreenHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function AndroidScreenHeader(props: {
readonly trailing?: ReactNode;
readonly onBack?: () => void;
readonly embedded?: boolean;
readonly hideBottomBorder?: boolean;
}) {
const insets = useSafeAreaInsets();
const foregroundColor = useThemeColor("--color-foreground");
Expand All @@ -61,6 +62,7 @@ export function AndroidScreenHeader(props: {
className="border-b border-header-border bg-header px-3 pb-2.5"
style={{
paddingTop: props.embedded ? 8 : Math.max(insets.top, 12),
borderBottomWidth: props.hideBottomBorder ? 0 : undefined,
}}
>
<View className="min-h-12 flex-row items-center gap-2">
Expand Down
61 changes: 57 additions & 4 deletions apps/mobile/src/components/ThemedSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,73 @@
import { Platform, Switch, type SwitchProps } from "react-native";
import { Platform, Pressable, Switch, View, type SwitchProps } from "react-native";

import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
import { useThemeColor } from "../lib/useThemeColor";
import { SymbolView } from "./AppSymbol";

export function ThemedSwitch(props: SwitchProps) {
const { materialYouStyleLayoutActive } = useAppearancePreferences();
const activeTrack = String(useThemeColor("--color-switch-active-track"));
const inactiveTrack = String(useThemeColor("--color-switch-inactive-track"));
const activeThumb = String(useThemeColor("--color-switch-active-thumb"));
const inactiveThumb = String(useThemeColor("--color-switch-inactive-thumb"));
const value = Boolean(props.value);

if (Platform.OS === "android" && materialYouStyleLayoutActive) {
const track = value ? activeTrack : inactiveTrack;
const thumb = value ? activeThumb : inactiveThumb;

return (
<Pressable
accessibilityHint={props.accessibilityHint}
accessibilityLabel={props.accessibilityLabel}
accessibilityRole="switch"
accessibilityState={{ checked: value, disabled: props.disabled }}
disabled={props.disabled}
hitSlop={8}
onPress={() => props.onValueChange?.(!value)}
style={{ opacity: props.disabled ? 0.38 : 1 }}
testID={props.testID}
>
<View
style={{
alignItems: value ? "flex-end" : "flex-start",
backgroundColor: track,
borderColor: value ? track : inactiveThumb,
borderRadius: 16,
borderWidth: 2,
height: 32,
justifyContent: "center",
paddingHorizontal: 2,
width: 52,
}}
>
<View
style={{
alignItems: "center",
backgroundColor: thumb,
borderRadius: 12,
height: 24,
justifyContent: "center",
width: 24,
}}
>
<SymbolView
name={value ? "checkmark" : "xmark"}
size={13}
tintColor={value ? activeTrack : inactiveTrack}
type="monochrome"
/>
</View>
</View>
</Pressable>
);
}

return (
<Switch
{...props}
ios_backgroundColor={inactiveTrack}
thumbColor={
Platform.OS === "android" ? (props.value ? activeThumb : inactiveThumb) : undefined
}
thumbColor={Platform.OS === "android" ? (value ? activeThumb : inactiveThumb) : undefined}
trackColor={{ false: inactiveTrack, true: activeTrack }}
/>
);
Expand Down
33 changes: 28 additions & 5 deletions apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,11 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
useAdaptiveWorkspaceLayout();
const [searchQuery, setSearchQuery] = useState("");
const isAndroid = Platform.OS === "android";
const { themeAppearance: highlightTheme } = useAppearancePreferences();
const { materialYouStyleLayoutActive, themeAppearance: highlightTheme } =
useAppearancePreferences();
const iconColor = String(useThemeColor("--color-icon-muted"));
const sheetSurfaceColor = String(useThemeColor("--color-sheet-solid"));
const screenColor = String(useThemeColor("--color-screen"));
const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace(
props.route.params,
);
Expand Down Expand Up @@ -361,14 +363,16 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
const usesCompactMailToolbar =
Platform.OS === "ios" && !layout.usesSplitView && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED;

return (
const content = (
<>
{/* Static header config (glass preset and title) lives in Stack.tsx. The
live sheet color stays dynamic here so the FlatList can remain the
direct scene child for native scroll-edge sampling. */}
<NativeStackScreenOptions
options={{
contentStyle: { backgroundColor: sheetSurfaceColor },
contentStyle: {
backgroundColor: materialYouStyleLayoutActive ? screenColor : sheetSurfaceColor,
},
headerShown: !isAndroid,
unstable_headerSubtitle:
Platform.OS === "ios" && projectName.length > 0 ? projectName : undefined,
Expand Down Expand Up @@ -404,6 +408,7 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
title="Files"
subtitle={projectName}
onBack={handleReturnToThread}
hideBottomBorder={materialYouStyleLayoutActive}
actions={[
{
accessibilityLabel: "Refresh files",
Expand All @@ -412,13 +417,23 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
},
]}
/>
<View className="flex-row items-center gap-2 border-b border-border px-3 py-2">
<View
className={
materialYouStyleLayoutActive
? "mx-4 my-2 min-h-12 flex-row items-center gap-2 rounded-full border border-input-border bg-input px-3.5"
: "flex-row items-center gap-2 border-b border-border px-3 py-2"
}
>
<SymbolView name="magnifyingglass" size={17} tintColor={iconColor} type="monochrome" />
<TextInput
accessibilityLabel="Search files"
autoCapitalize="none"
autoCorrect={false}
className="min-h-10 flex-1 rounded-xl py-2 text-sm"
className={
materialYouStyleLayoutActive
? "min-h-10 flex-1 py-2 text-sm text-foreground"
: "min-h-10 flex-1 rounded-xl py-2 text-sm"
}
placeholder="Search files"
value={searchQuery}
onChangeText={setSearchQuery}
Expand Down Expand Up @@ -461,6 +476,14 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
<FilesToolbarBottomFade />
</>
);

return materialYouStyleLayoutActive ? (
<View className="flex-1" style={{ backgroundColor: screenColor }}>
{content}
</View>
) : (
content
);
}

export function ThreadFileScreen(props: ThreadFileRouteScreenProps) {
Expand Down
14 changes: 12 additions & 2 deletions apps/mobile/src/features/home/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { T3Wordmark } from "../../components/T3Wordmark";
import { HOME_HORIZONTAL_INSET } from "../../lib/layoutMetrics";
import { resolveMobileStageLabel } from "../../lib/mobileBranding";
import { useThemeColor } from "../../lib/useThemeColor";
import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled";
import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items";
Expand Down Expand Up @@ -67,6 +68,7 @@ function checkedMenuState(checked: boolean) {

function AndroidHomeHeader(props: HomeHeaderProps) {
const insets = useSafeAreaInsets();
const { materialYouStyleLayoutActive } = useAppearancePreferences();
const iconColor = useThemeColor("--color-icon");
const mutedColor = useThemeColor("--color-foreground-muted");
const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant);
Expand Down Expand Up @@ -201,7 +203,11 @@ function AndroidHomeHeader(props: HomeHeaderProps) {
<>
<NativeStackScreenOptions options={{ headerShown: false }} />
<View
className="border-b border-header-border bg-header pb-3"
className={
materialYouStyleLayoutActive
? "bg-header pb-3"
: "border-b border-header-border bg-header pb-3"
}
style={{
paddingHorizontal: HOME_HORIZONTAL_INSET,
paddingTop: Math.max(insets.top, 12),
Expand Down Expand Up @@ -266,7 +272,11 @@ function AndroidHomeHeader(props: HomeHeaderProps) {
</Pressable>
</View>

<View className="min-h-12 flex-row items-center gap-2.5 rounded-2xl border border-input-border bg-input px-3.5">
<View
className={`min-h-12 flex-row items-center gap-2.5 border border-input-border bg-input px-3.5 ${
materialYouStyleLayoutActive ? "rounded-full" : "rounded-2xl"
}`}
>
<SymbolView name="magnifyingglass" size={17} tintColor={mutedColor} type="monochrome" />
<TextInput
accessibilityLabel="Search threads"
Expand Down
Loading
Loading