Skip to content

新增主題色切換功能#17

Merged
Retsomm merged 2 commits into
mainfrom
dev
May 21, 2026
Merged

新增主題色切換功能#17
Retsomm merged 2 commits into
mainfrom
dev

Conversation

@Retsomm
Copy link
Copy Markdown
Owner

@Retsomm Retsomm commented May 21, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Added system-wide light/dark theme with selectable option in Profile.
    • App UI now consistently adapts to the chosen theme across screens, components, and global styles.
    • Theme preference persists between sessions and applies early during app load to avoid flashing.
    • Several screens show themed placeholders while state or hydration completes for smoother loading.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR adds a persistent light/dark theme (tokens, global CSS, and a Zustand store) and applies theme-driven colors across screens and UI components, plus a root AppThemeBoundary and a profile theme selector.

Changes

Dark/Light Theme System

Layer / File(s) Summary
Theme system foundation and tokens
constants/theme.ts, store/use-theme-store.ts, global.css
Defines AppTheme, appThemeOptions, and appThemeColors; adds a persisted useThemeStore with hydration tracking; extends Tailwind tokens and adds .theme-dark overrides.
Root app boundary and theme application
app/_layout.tsx, app/index.tsx
Adds AppThemeBoundary to apply the active theme (including SystemUI background) and wraps the app Stack; changes initial loading placeholders to themed Views.
Navigation tab screens
app/(tabs)/chat.tsx, app/(tabs)/home.tsx, app/(tabs)/learn.tsx, app/(tabs)/profile.tsx
Converts main tab screens to read theme from useThemeStore and appThemeColors, replacing hardcoded colors with theme tokens and adding a theme selector in Profile.
Onboarding and language selection screens
app/onboarding.tsx, app/LanguageSelection.tsx, app/lesson/[lessonId].tsx
Theme-enables onboarding (placeholder and greeting color), language selection (search, rows, empty state), and lesson fallback container background; updates searchInput style layout.
Authentication screen component
components/auth-screen.tsx
Refactors AuthScreen and VerificationModal to use theme colors for icons, inputs, dividers, social buttons, and OTP styling; extracts social icon helper.
Audio teacher session component
components/audio-teacher-session.tsx
Applies theme tokens across AudioTeacherSession and subcomponents (headers, controls, feedback, chat bubbles, tab bar shadow/colors).
Utility UI components
components/bottom-tab-bar.tsx, components/chat-input-bar.tsx, components/chat-message-bubble.tsx, components/lesson-card.tsx, components/tab-placeholder-screen.tsx
Updates these components to derive styling from useThemeStore and appThemeColors instead of hardcoded values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I nibble tokens, light and dark,
I hop through classes, bright and stark,
A theme for dawn, a theme for night,
Colors aligned, both soft and bright,
The app now wears a cozy, themed cloak.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title "新增主題色切換功能" (Add theme color switching feature) accurately describes the main change—implementing theme switching functionality across the app with a new theme store, theme options UI, and theme-aware styling throughout.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/auth-screen.tsx (1)

1-650: ⚠️ Potential issue | 🟠 Major

Block merge: npm run lint / npm run typecheck do not pass

  • npm run lint fails (44 errors), including components/auth-screen.tsx unresolved imports like @clerk/expo, expo-linking, expo-router, expo-web-browser, react-native-safe-area-context, and posthog-react-native.
  • npm run typecheck fails for components/auth-screen.tsx too (missing module type declarations + implicit any/appThemeColors indexing issues), plus many similar failures across other files.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/auth-screen.tsx` around lines 1 - 650, The build is failing due to
missing type declarations for several third‑party imports and unsafe
indexing/implicit-any usage in this file; add proper types (install `@types`
packages where available or add ambient .d.ts module declarations for
`@clerk/expo`, expo-linking, expo-router, expo-web-browser,
react-native-safe-area-context, posthog-react-native) and then tighten the local
types: ensure the theme variable returned by useThemeStore is a typed union and
index appThemeColors with that union (or cast as keyof typeof appThemeColors)
wherever used (e.g., in AuthScreen and VerificationModal), and import/annotate
Clerk/SSO/SignIn/SignUp return types for signIn, signUp, startSSOFlow and
usePostHog so functions like handleEmailAuth, handleVerifyCode and
handleSocialAuth no longer rely on implicit any types.
🧹 Nitpick comments (2)
store/use-theme-store.ts (1)

44-61: ⚡ Quick win

Simplify redundant hydration tracking.

The hasHydrated flag is being set in three separate places:

  1. onRehydrateStorage callback (lines 44-46)
  2. persist.onFinishHydration callback (lines 55-57)
  3. Immediate synchronous check (lines 59-61)

This creates unnecessary duplication. The persist.onFinishHydration callback should be sufficient to track when hydration completes.

Simplified hydration tracking
   persist(
     (set) => ({
       hasHydrated: false,
       setHasHydrated: (hasHydrated) => set({ hasHydrated }),
       setTheme: (theme) => set({ theme }),
       theme: "light",
       toggleTheme: () =>
         set((state) => ({ theme: state.theme === "light" ? "dark" : "light" })),
     }),
     {
       name: "lingua-theme-storage",
-      onRehydrateStorage: () => (state) => {
-        state?.setHasHydrated(true);
-      },
       partialize: (state) => ({
         theme: state.theme,
       }),
       storage: createJSONStorage(getThemeStorage),
     },
   ),
 );
 
 useThemeStore.persist.onFinishHydration((state) => {
   state.setHasHydrated(true);
 });
-
-if (useThemeStore.persist.hasHydrated()) {
-  useThemeStore.setState({ hasHydrated: true });
-}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@store/use-theme-store.ts` around lines 44 - 61, The hydration flag is being
set in three places; keep only the persist.onFinishHydration handler and remove
the redundant updates: delete the onRehydrateStorage callback that calls
state?.setHasHydrated(true) and remove the immediate synchronous hasHydrated
check that calls useThemeStore.setState({ hasHydrated: true }); retain
useThemeStore.persist.onFinishHydration((state) => { state.setHasHydrated(true);
}) along with the existing partialize and storage setup (e.g.
createJSONStorage/getThemeStorage).
app/index.tsx (1)

12-12: 💤 Low value

Consider applying theme for loading placeholder consistency.

The loading View uses bg-lingua-background but doesn't apply the theme-dark class conditionally like AppThemeBoundary does. While this loading state is brief and defaults to light theme, for visual consistency you could apply the theme class here as well.

Optional theme consistency
+import { useThemeStore } from "`@/store/use-theme-store`";
+
 export default function Index() {
   const { isLoaded, isSignedIn } = useAuth();
   const hasHydratedLanguage = useLanguageStore((state) => state.hasHydrated);
   const selectedLanguageId = useLanguageStore((state) => state.selectedLanguageId);
+  const theme = useThemeStore((state) => state.theme);
+  const hasHydratedTheme = useThemeStore((state) => state.hasHydrated);
+  const activeTheme = hasHydratedTheme ? theme : "light";
 
   if (!isLoaded || !hasHydratedLanguage) {
-    return <View className="flex-1 bg-lingua-background" />;
+    return (
+      <View
+        className={`flex-1 bg-lingua-background ${activeTheme === "dark" ? "theme-dark" : ""}`}
+      />
+    );
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/index.tsx` at line 12, Loading placeholder lacks the conditional theme
class used elsewhere; update the View returned in app/index.tsx (the loading
placeholder JSX) to include the same conditional theme class logic used by
AppThemeBoundary so it applies "theme-dark" when appropriate, by mirroring the
theme-detection/className composition that AppThemeBoundary uses for consistent
light/dark styling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@constants/theme.ts`:
- Around line 14-15: The dark-theme entry uses the Chinese label "黑色" which may
be incorrect for dark mode; update the label in the theme definition where the
object contains icon: "moon" to a more appropriate term like "深色" or "暗色" (e.g.,
change label: "黑色" to label: "深色") to match typical Chinese UI terminology for
dark mode.

---

Outside diff comments:
In `@components/auth-screen.tsx`:
- Around line 1-650: The build is failing due to missing type declarations for
several third‑party imports and unsafe indexing/implicit-any usage in this file;
add proper types (install `@types` packages where available or add ambient .d.ts
module declarations for `@clerk/expo`, expo-linking, expo-router,
expo-web-browser, react-native-safe-area-context, posthog-react-native) and then
tighten the local types: ensure the theme variable returned by useThemeStore is
a typed union and index appThemeColors with that union (or cast as keyof typeof
appThemeColors) wherever used (e.g., in AuthScreen and VerificationModal), and
import/annotate Clerk/SSO/SignIn/SignUp return types for signIn, signUp,
startSSOFlow and usePostHog so functions like handleEmailAuth, handleVerifyCode
and handleSocialAuth no longer rely on implicit any types.

---

Nitpick comments:
In `@app/index.tsx`:
- Line 12: Loading placeholder lacks the conditional theme class used elsewhere;
update the View returned in app/index.tsx (the loading placeholder JSX) to
include the same conditional theme class logic used by AppThemeBoundary so it
applies "theme-dark" when appropriate, by mirroring the
theme-detection/className composition that AppThemeBoundary uses for consistent
light/dark styling.

In `@store/use-theme-store.ts`:
- Around line 44-61: The hydration flag is being set in three places; keep only
the persist.onFinishHydration handler and remove the redundant updates: delete
the onRehydrateStorage callback that calls state?.setHasHydrated(true) and
remove the immediate synchronous hasHydrated check that calls
useThemeStore.setState({ hasHydrated: true }); retain
useThemeStore.persist.onFinishHydration((state) => { state.setHasHydrated(true);
}) along with the existing partialize and storage setup (e.g.
createJSONStorage/getThemeStorage).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d386a8e-2b72-49ec-ac2f-5f01c27c91c6

📥 Commits

Reviewing files that changed from the base of the PR and between 3845018 and ff7a3bf.

📒 Files selected for processing (19)
  • app/(tabs)/chat.tsx
  • app/(tabs)/home.tsx
  • app/(tabs)/learn.tsx
  • app/(tabs)/profile.tsx
  • app/LanguageSelection.tsx
  • app/_layout.tsx
  • app/index.tsx
  • app/lesson/[lessonId].tsx
  • app/onboarding.tsx
  • components/audio-teacher-session.tsx
  • components/auth-screen.tsx
  • components/bottom-tab-bar.tsx
  • components/chat-input-bar.tsx
  • components/chat-message-bubble.tsx
  • components/lesson-card.tsx
  • components/tab-placeholder-screen.tsx
  • constants/theme.ts
  • global.css
  • store/use-theme-store.ts

Comment thread constants/theme.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/index.tsx (1)

1-33: ⚠️ Potential issue | 🔴 Critical

Lint/typecheck are currently failing for this branch.
npm run lint exits with code 2 due to import/no-unresolved (e.g., @clerk/expo, expo-router, react-native-safe-area-context, posthog-react-native). npm run typecheck fails with TS2307 missing modules (same imports) and many implicit any/typing errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/index.tsx` around lines 1 - 33, The lint/typecheck failures come from
unresolved imports (e.g., `@clerk/expo`, expo-router,
react-native-safe-area-context, posthog-react-native) used by symbols like
useAuth, Redirect, View, useLanguageStore and useThemeStore; fix by either
installing the appropriate packages and their type defs (npm install `@clerk/expo`
expo-router react-native-safe-area-context posthog-react-native and add any
`@types/`* packages if available) or, if those libs have no TS types, create a
project-level ambient declaration file (e.g., globals.d.ts) that declares
missing modules with lines like declare module "expo-router"; declare module
"`@clerk/expo`"; declare module "posthog-react-native"; declare module
"react-native-safe-area-context"; ensure this .d.ts is included by tsconfig
(include or typeRoots) so the compiler and ESLint resolve the imports and the
symbols useAuth/Redirect/View resolve without TS2307/import/no-unresolved
errors.
🧹 Nitpick comments (1)
app/index.tsx (1)

18-18: ⚡ Quick win

Avoid dynamic NativeWind class interpolation for theme state.

Line 18 builds className dynamically; this conflicts with the project rule for dynamic styles. Prefer inline/StyleSheet for the theme-dependent part.

Suggested change
+import { appThemeColors } from "`@/constants/theme`";
 import { View } from "react-native";
@@
-      <View
-        className={`flex-1 bg-lingua-background ${activeTheme === "dark" ? "theme-dark" : ""}`}
-      />
+      <View
+        style={{ flex: 1, backgroundColor: appThemeColors[activeTheme].background }}
+      />

As per coding guidelines, "Use StyleSheet or inline styles instead of NativeWind classes for ... dynamic styles".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/index.tsx` at line 18, The current JSX uses a template string in the
className (className={`flex-1 bg-lingua-background ${activeTheme === "dark" ?
"theme-dark" : ""}`}) which mixes dynamic theme state into NativeWind classes;
replace that with a static className for the layout (e.g., "flex-1
bg-lingua-background") and move the theme-dependent styling into a StyleSheet or
inline style applied via the style prop, using the existing activeTheme variable
to select the appropriate style object (e.g., styles.darkContainer vs
styles.lightContainer); update the element where className is set and add the
new style lookup, and define the corresponding StyleSheet entries instead of
toggling NativeWind classes dynamically.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/index.tsx`:
- Around line 1-33: The lint/typecheck failures come from unresolved imports
(e.g., `@clerk/expo`, expo-router, react-native-safe-area-context,
posthog-react-native) used by symbols like useAuth, Redirect, View,
useLanguageStore and useThemeStore; fix by either installing the appropriate
packages and their type defs (npm install `@clerk/expo` expo-router
react-native-safe-area-context posthog-react-native and add any `@types/`*
packages if available) or, if those libs have no TS types, create a
project-level ambient declaration file (e.g., globals.d.ts) that declares
missing modules with lines like declare module "expo-router"; declare module
"`@clerk/expo`"; declare module "posthog-react-native"; declare module
"react-native-safe-area-context"; ensure this .d.ts is included by tsconfig
(include or typeRoots) so the compiler and ESLint resolve the imports and the
symbols useAuth/Redirect/View resolve without TS2307/import/no-unresolved
errors.

---

Nitpick comments:
In `@app/index.tsx`:
- Line 18: The current JSX uses a template string in the className
(className={`flex-1 bg-lingua-background ${activeTheme === "dark" ? "theme-dark"
: ""}`}) which mixes dynamic theme state into NativeWind classes; replace that
with a static className for the layout (e.g., "flex-1 bg-lingua-background") and
move the theme-dependent styling into a StyleSheet or inline style applied via
the style prop, using the existing activeTheme variable to select the
appropriate style object (e.g., styles.darkContainer vs styles.lightContainer);
update the element where className is set and add the new style lookup, and
define the corresponding StyleSheet entries instead of toggling NativeWind
classes dynamically.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 87e4110d-beea-449a-8bdb-b3b60074bbe4

📥 Commits

Reviewing files that changed from the base of the PR and between ff7a3bf and c10f5d7.

📒 Files selected for processing (3)
  • app/index.tsx
  • constants/theme.ts
  • store/use-theme-store.ts
💤 Files with no reviewable changes (1)
  • store/use-theme-store.ts

@Retsomm Retsomm merged commit ee9adf5 into main May 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant