Skip to content
Merged
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
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Project conventions

## Themed styles — use the `useThemedStyles` hook

Components that build a `StyleSheet` from the theme MUST obtain it via
[`useThemedStyles`](hooks/useThemedStyles.ts). Do NOT hand-roll the
`useTheme()` + `useMemo(() => createStyles(theme), [theme])` boilerplate.

```tsx
// ✅ styles only
const { styles } = useThemedStyles(createStyles);

// ✅ when the component also reads theme directly (icon colors, insets, etc.)
const { styles, theme } = useThemedStyles(createStyles);

// ✅ parametric factory — pass extra args after the factory
const { styles } = useThemedStyles(createStyles, disabled);

// ❌ do not do this
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
```

- Keep the `createStyles = (theme: Theme, ...args) => StyleSheet.create({ ... })`
factory at the bottom of the file, unchanged — the hook memoizes it.
- The hook returns `{ styles, theme }`; destructure only what you use.
- `useTheme()` from [context/ThemeContext](context/ThemeContext.tsx) is still fine
for components that need `theme` but build no `StyleSheet`.
13 changes: 3 additions & 10 deletions app/(drawer)/benchmark.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import React, {
useEffect,
useState,
useCallback,
useRef,
useMemo,
} from 'react';
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import { useSQLiteContext } from 'expo-sqlite';
import { useLocalSearchParams } from 'expo-router';
import { BottomSheetModal } from '@gorhom/bottom-sheet';
import { useModelStore } from '../../store/modelStore';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { Theme } from '../../styles/colors';
import {
BenchmarkResult,
Expand All @@ -30,8 +24,7 @@ import useDefaultHeader from '../../hooks/useDefaultHeader';
const BenchmarkScreen = () => {
useDefaultHeader();
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);
const db = useSQLiteContext();
const { getModelById } = useModelStore();
const { modelId } = useLocalSearchParams<{ modelId?: string }>();
Expand Down
13 changes: 3 additions & 10 deletions app/(drawer)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import React, {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
} from 'react';
import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
import { startPhantomChat } from '../../utils/startPhantomChat';
import { router, useFocusEffect, useNavigation } from 'expo-router';
import { configureReanimatedLogger } from 'react-native-reanimated';
Expand All @@ -18,7 +12,7 @@ import { LinearGradient } from 'expo-linear-gradient';
import PrimaryButton from '../../components/PrimaryButton';
import TextButton from '../../components/TextButton';
import { Theme } from '../../styles/colors';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { importChatRoom } from '../../database/exportImportRepository';
import { useChatStore } from '../../store/chatStore';
import ModelSelectSheet from '../../components/bottomSheets/ModelSelectSheet';
Expand Down Expand Up @@ -49,8 +43,7 @@ export default function App() {
headerRight: () => <NewChatHeaderButton noOp />,
});
}, [navigation]);
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles, theme } = useThemedStyles(createStyles);
const { addChat } = useChatStore();

const handleSetModel = async (model: Model, replace = false) => {
Expand Down
5 changes: 2 additions & 3 deletions app/(drawer)/model-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import WarningSheet, {
WarningSheetData,
} from '../../components/bottomSheets/WarningSheet';
import ModelManagementSheet from '../../components/bottomSheets/ModelManagementSheet';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { Theme } from '../../styles/colors';
import { fontFamily, fontSizes } from '../../styles/fontStyles';
import { Model } from '../../database/modelRepository';
Expand All @@ -32,8 +32,7 @@ import { useConfirm } from '../../hooks/useConfirm';
const ModelHubScreen = () => {
useDefaultHeader();
const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles, theme } = useThemedStyles(createStyles);

const addModelSheetRef = useRef<BottomSheetModal | null>(null);
const wifiWarningSheetRef = useRef<BottomSheetModal<WarningSheetData> | null>(
Expand Down
7 changes: 3 additions & 4 deletions app/(drawer)/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import React, { useMemo } from 'react';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import { useRouter } from 'expo-router';
import useDefaultHeader from '../../hooks/useDefaultHeader';
import { SettingsRow } from '../../components/settings/SettingsRow';
import EditIcon from '../../assets/icons/edit.svg';
import InfoCircleIcon from '../../assets/icons/info-circle.svg';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { Theme } from '../../styles/colors';

const SettingsScreen = () => {
useDefaultHeader();
const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);

return (
<View style={styles.container}>
Expand Down
7 changes: 3 additions & 4 deletions app/(modals)/add-local-model.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { Text, StyleSheet, Alert, View } from 'react-native';
import { useRouter } from 'expo-router';
import { useModelStore } from '../../store/modelStore';
import ModalHeader from '../../components/ModalHeader';
import { fontSizes, fontFamily } from '../../styles/fontStyles';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import PrimaryButton from '../../components/PrimaryButton';
import { ScrollView } from 'react-native-gesture-handler';
import UploadInput from '../../components/UploadInput';
Expand All @@ -26,8 +26,7 @@ export interface LocalModelFormState {

export default function AddLocalModelScreen() {
const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);
const { addModelToDB } = useModelStore();

const [
Expand Down
7 changes: 3 additions & 4 deletions app/(modals)/add-remote-model.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useMemo, useRef, useState } from 'react';
import React, { useRef, useState } from 'react';
import { Text, StyleSheet, Alert, Platform, View } from 'react-native';
import { useRouter } from 'expo-router';
import { useModelStore } from '../../store/modelStore';
import ModalHeader from '../../components/ModalHeader';
import TextFieldInput from '../../components/TextFieldInput';
import { fontSizes, fontFamily } from '../../styles/fontStyles';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import PrimaryButton from '../../components/PrimaryButton';
import { ScrollView } from 'react-native-gesture-handler';
import Toast from 'react-native-toast-message';
Expand All @@ -20,8 +20,7 @@ export interface RemoteModelFormState {

export default function AddRemoteModelScreen() {
const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);
const scrollViewRef = useRef<ScrollView>(null);
const { addModelToDB } = useModelStore();

Expand Down
7 changes: 3 additions & 4 deletions app/(modals)/custom-system-prompt.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Platform, Text, StyleSheet, View } from 'react-native';
import { useRouter } from 'expo-router';
import { ScrollView } from 'react-native-gesture-handler';
Expand All @@ -8,16 +8,15 @@ import PrimaryButton from '../../components/PrimaryButton';
import TextAreaField from '../../components/TextAreaField';
import { CustomKeyboardAvoidingView } from '../../components/CustomKeyboardAvoidingView';
import { fontSizes, fontFamily } from '../../styles/fontStyles';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { Theme } from '../../styles/colors';
import { useSettingsStore } from '../../store/settingsStore';
import { Feedback } from '../../utils/Feedback';
import { MAX_CUSTOM_SYSTEM_PROMPT_LENGTH } from '../../constants/settings';

export default function CustomSystemPromptScreen() {
const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);

const customSystemPrompt = useSettingsStore((s) => s.customSystemPrompt);
const setCustomSystemPrompt = useSettingsStore(
Expand Down
7 changes: 3 additions & 4 deletions app/(modals)/edit-local-model/[modelId].tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { Text, StyleSheet, View, Alert } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useModelStore } from '../../../store/modelStore';
import ModalHeader from '../../../components/ModalHeader';
import { fontSizes, fontFamily } from '../../../styles/fontStyles';
import { useTheme } from '../../../context/ThemeContext';
import { useThemedStyles } from '../../../hooks/useThemedStyles';
import PrimaryButton from '../../../components/PrimaryButton';
import { ScrollView } from 'react-native-gesture-handler';
import UploadInput from '../../../components/UploadInput';
Expand Down Expand Up @@ -40,8 +40,7 @@ export default function EditLocalModelScreen() {
const modelId = parseInt(rawModelId);

const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);

const { getModelById, editModel } = useModelStore();
const model = getModelById(modelId);
Expand Down
7 changes: 3 additions & 4 deletions app/(modals)/edit-remote-model/[modelId].tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useMemo, useRef, useState } from 'react';
import React, { useRef, useState } from 'react';
import { Text, StyleSheet, Alert, Platform, View } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useModelStore } from '../../../store/modelStore';
import ModalHeader from '../../../components/ModalHeader';
import TextFieldInput from '../../../components/TextFieldInput';
import { fontSizes, fontFamily } from '../../../styles/fontStyles';
import { useTheme } from '../../../context/ThemeContext';
import { useThemedStyles } from '../../../hooks/useThemedStyles';
import PrimaryButton from '../../../components/PrimaryButton';
import { ScrollView } from 'react-native-gesture-handler';
import Toast from 'react-native-toast-message';
Expand All @@ -25,8 +25,7 @@ export default function EditRemoteModelScreen() {
const scrollViewRef = useRef<ScrollView>(null);
const router = useRouter();

const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);

const { getModelById, editModel } = useModelStore();
const model = getModelById(modelId);
Expand Down
5 changes: 2 additions & 3 deletions app/(modals)/model-family/[family].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
BottomSheetModal,
BottomSheetModalProvider,
} from '@gorhom/bottom-sheet';
import { useTheme } from '../../../context/ThemeContext';
import { useThemedStyles } from '../../../hooks/useThemedStyles';
import { Theme } from '../../../styles/colors';
import { fontFamily, fontSizes } from '../../../styles/fontStyles';
import { useModelStore } from '../../../store/modelStore';
Expand All @@ -22,8 +22,7 @@ import { FAMILY_DESCRIPTIONS } from '../../../constants/family-descriptions';

const FamilyScreen = () => {
const router = useRouter();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);
const { family } = useLocalSearchParams<{ family: string }>();
const familyName = decodeURIComponent(family ?? '');

Expand Down
13 changes: 3 additions & 10 deletions app/(modals)/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import React, {
useLayoutEffect,
useMemo,
useRef,
useState,
useEffect,
} from 'react';
import React, { useLayoutEffect, useRef, useState, useEffect } from 'react';
import {
Dimensions,
Image,
Expand All @@ -13,7 +7,7 @@ import {
View,
BackHandler,
} from 'react-native';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { Theme } from '../../styles/colors';
import OnboardingIntroPanel from '../../components/onboarding/OnboardingIntroPanel';
import Animated, {
Expand Down Expand Up @@ -85,8 +79,7 @@ function OnboardingScreen() {
router.replace('/(modals)/select-starting-model');
};

const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles, theme } = useThemedStyles(createStyles);
const bgSpillProgress = useSharedValue(0);

const [stepNumber, setStepNumber] = useState(0);
Expand Down
5 changes: 2 additions & 3 deletions app/(modals)/select-starting-model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { StyleSheet, View, Text, ScrollView } from 'react-native';
import { useRouter } from 'expo-router';
import { fontSizes, fontFamily } from '../../styles/fontStyles';
import { useTheme } from '../../context/ThemeContext';
import { useThemedStyles } from '../../hooks/useThemedStyles';
import { useModelStore } from '../../store/modelStore';
import PrimaryButton from '../../components/PrimaryButton';
import ModelCard from '../../components/model-hub/ModelCard';
Expand All @@ -19,8 +19,7 @@ import { getDeviceMemoryGB } from '../../utils/modelCompatibility';
function SelectStartingModelScreen() {
const router = useRouter();
const db = useSQLiteContext();
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);
const { initPhantomChat } = useChatStore();
const { setActiveChatId } = useLLMStore();
const { downloadedModels } = useModelStore();
Expand Down
7 changes: 3 additions & 4 deletions components/AppToast.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import {
Platform,
StyleSheet,
Expand All @@ -11,13 +11,12 @@ import Toast, { ToastConfig } from 'react-native-toast-message';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { Theme } from '../styles/colors';
import { useTheme } from '../context/ThemeContext';
import { useThemedStyles } from '../hooks/useThemedStyles';
import CloseIcon from '../assets/icons/close.svg';
import { fontFamily, fontSizes } from '../styles/fontStyles';

const AppToast: React.FC = () => {
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);
const insets = useSafeAreaInsets();

const toastConfig: ToastConfig = {
Expand Down
13 changes: 7 additions & 6 deletions components/Chip.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useTheme } from '../context/ThemeContext';
import { useThemedStyles } from '../hooks/useThemedStyles';
import { fontFamily, fontSizes } from '../styles/fontStyles';
import { Theme } from '../styles/colors';

Expand All @@ -19,10 +19,11 @@ const Chip = ({
textColor,
borderColor,
}: Props) => {
const { theme } = useTheme();
const styles = useMemo(
() => createStyles(theme, backgroundColor, textColor, borderColor),
[theme, backgroundColor, textColor, borderColor]
const { styles } = useThemedStyles(
createStyles,
backgroundColor,
textColor,
borderColor
);

return (
Expand Down
7 changes: 3 additions & 4 deletions components/Divider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React, { useMemo } from 'react';
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { useTheme } from '../context/ThemeContext';
import { useThemedStyles } from '../hooks/useThemedStyles';
import { Theme } from '../styles/colors';

export const Divider = () => {
const { theme } = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const { styles } = useThemedStyles(createStyles);

return <View style={styles.divider} />;
};
Expand Down
Loading
Loading