From ed3728595d6c5ccfe138b7f3f10560c50c4183d4 Mon Sep 17 00:00:00 2001
From: Retsomm <112182ssss@gmail.com>
Date: Mon, 18 May 2026 21:31:35 +0800
Subject: [PATCH 1/2] implement state management and bottom navigation
---
.env.example | 1 +
app/(tabs)/_layout.tsx | 36 +
app/(tabs)/ai-teacher.tsx | 10 +
app/(tabs)/chat.tsx | 10 +
app/(tabs)/home.tsx | 10 +
app/(tabs)/learn.tsx | 10 +
app/(tabs)/profile.tsx | 10 +
...ge-selection.tsx => LanguageSelection.tsx} | 20 +-
app/index.tsx | 286 +-------
app/profile.tsx | 121 ----
.../{earth-cropped.png => EarthCropped.png} | Bin
components/bottom-tab-bar.tsx | 145 ++++
components/tab-placeholder-screen.tsx | 35 +
constants/images.ts | 2 +-
package-lock.json | 666 +++++++++++++++++-
package.json | 4 +-
store/UseLanguageStore.ts | 60 ++
17 files changed, 1011 insertions(+), 415 deletions(-)
create mode 100644 .env.example
create mode 100644 app/(tabs)/_layout.tsx
create mode 100644 app/(tabs)/ai-teacher.tsx
create mode 100644 app/(tabs)/chat.tsx
create mode 100644 app/(tabs)/home.tsx
create mode 100644 app/(tabs)/learn.tsx
create mode 100644 app/(tabs)/profile.tsx
rename app/{language-selection.tsx => LanguageSelection.tsx} (92%)
delete mode 100644 app/profile.tsx
rename assets/images/{earth-cropped.png => EarthCropped.png} (100%)
create mode 100644 components/bottom-tab-bar.tsx
create mode 100644 components/tab-placeholder-screen.tsx
create mode 100644 store/UseLanguageStore.ts
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..d42905f
--- /dev/null
+++ b/.env.example
@@ -0,0 +1 @@
+EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your_clerk_production_publishable_key
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
new file mode 100644
index 0000000..fa2766e
--- /dev/null
+++ b/app/(tabs)/_layout.tsx
@@ -0,0 +1,36 @@
+import { BottomTabBar } from "@/components/bottom-tab-bar";
+import { useLanguageStore } from "@/store/UseLanguageStore";
+import { useAuth } from "@clerk/expo";
+import { Redirect, Tabs } from "expo-router";
+
+export default function TabsLayout() {
+ const { isLoaded, isSignedIn } = useAuth();
+ const hasHydratedLanguage = useLanguageStore((state) => state.hasHydrated);
+ const selectedLanguageId = useLanguageStore((state) => state.selectedLanguageId);
+
+ if (!isLoaded || !hasHydratedLanguage) {
+ return null;
+ }
+
+ if (!isSignedIn) {
+ return ;
+ }
+
+ if (!selectedLanguageId) {
+ return ;
+ }
+
+ return (
+ }
+ >
+
+
+
+
+
+
+ );
+}
diff --git a/app/(tabs)/ai-teacher.tsx b/app/(tabs)/ai-teacher.tsx
new file mode 100644
index 0000000..2af7924
--- /dev/null
+++ b/app/(tabs)/ai-teacher.tsx
@@ -0,0 +1,10 @@
+import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen";
+
+export default function AiTeacherScreen() {
+ return (
+
+ );
+}
diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx
new file mode 100644
index 0000000..8d7e481
--- /dev/null
+++ b/app/(tabs)/chat.tsx
@@ -0,0 +1,10 @@
+import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen";
+
+export default function ChatScreen() {
+ return (
+
+ );
+}
diff --git a/app/(tabs)/home.tsx b/app/(tabs)/home.tsx
new file mode 100644
index 0000000..6c3c719
--- /dev/null
+++ b/app/(tabs)/home.tsx
@@ -0,0 +1,10 @@
+import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen";
+
+export default function HomeScreen() {
+ return (
+
+ );
+}
diff --git a/app/(tabs)/learn.tsx b/app/(tabs)/learn.tsx
new file mode 100644
index 0000000..972e130
--- /dev/null
+++ b/app/(tabs)/learn.tsx
@@ -0,0 +1,10 @@
+import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen";
+
+export default function LearnScreen() {
+ return (
+
+ );
+}
diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx
new file mode 100644
index 0000000..a9403c4
--- /dev/null
+++ b/app/(tabs)/profile.tsx
@@ -0,0 +1,10 @@
+import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen";
+
+export default function ProfileScreen() {
+ return (
+
+ );
+}
diff --git a/app/language-selection.tsx b/app/LanguageSelection.tsx
similarity index 92%
rename from app/language-selection.tsx
rename to app/LanguageSelection.tsx
index bfda703..fa40c9a 100644
--- a/app/language-selection.tsx
+++ b/app/LanguageSelection.tsx
@@ -1,5 +1,6 @@
import { images } from "@/constants/images";
import { defaultLanguageId, languages } from "@/data/languages";
+import { useLanguageStore } from "@/store/UseLanguageStore";
import type { SupportedLanguage } from "@/types/learning";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
@@ -19,8 +20,14 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function LanguageSelectionScreen() {
const insets = useSafeAreaInsets();
const { width } = useWindowDimensions();
+ const persistedLanguageId = useLanguageStore((state) => state.selectedLanguageId);
+ const setSelectedLanguage = useLanguageStore(
+ (state) => state.setSelectedLanguageId,
+ );
const [searchQuery, setSearchQuery] = useState("");
- const [selectedLanguageId, setSelectedLanguageId] = useState(defaultLanguageId);
+ const [selectedLanguageId, setSelectedLanguageId] = useState(
+ persistedLanguageId ?? defaultLanguageId,
+ );
const earthWidth = Math.min(width * 0.95, 520);
const earthHeight = earthWidth * (828 / 1127);
@@ -51,6 +58,15 @@ export default function LanguageSelectionScreen() {
[selectedLanguageId],
);
+ const handleConfirmLanguage = () => {
+ if (!selectedLanguage) {
+ return;
+ }
+
+ setSelectedLanguage(selectedLanguage.id);
+ router.replace("/");
+ };
+
return (
[styles.confirmButton, pressed && styles.pressed]}
>
diff --git a/app/index.tsx b/app/index.tsx
index f7bb34a..ed85f51 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -1,51 +1,13 @@
+import { useLanguageStore } from "@/store/UseLanguageStore";
import { useAuth } from "@clerk/expo";
-import { images } from "@/constants/images";
-import { colors, typography } from "@/theme/tokens";
-import { Link, Redirect } from "expo-router";
-import type React from "react";
-import {
- Image,
- ScrollView,
- StyleSheet,
- Text,
- View,
- useWindowDimensions,
-} from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-
-const primaryColors = [
- { name: "Lingua Purple", hex: colors.primary.purple },
- { name: "Lingua Deep Purple", hex: colors.primary.deepPurple },
- { name: "Lingua Blue", hex: colors.primary.blue },
- { name: "Lingua Green", hex: colors.primary.green },
-] as const;
-
-const semanticColors = [
- { name: "Success", hex: colors.semantic.success },
- { name: "Warning", hex: colors.semantic.warning },
- { name: "Streak", hex: colors.semantic.streak },
- { name: "Error", hex: colors.semantic.error },
- { name: "Info", hex: colors.semantic.info },
-] as const;
-
-const neutralColors = [
- { name: "Text / Primary", hex: colors.neutral.textPrimary },
- { name: "Text / Secondary", hex: colors.neutral.textSecondary },
- { name: "Border", hex: colors.neutral.border },
- { name: "Surface", hex: colors.neutral.surface },
- { name: "Background", hex: colors.neutral.background },
-] as const;
-
-const typeRows = Object.values(typography.scale);
+import { Redirect } from "expo-router";
export default function Index() {
const { isLoaded, isSignedIn } = useAuth();
- const insets = useSafeAreaInsets();
- const { width } = useWindowDimensions();
- const isWide = width >= 900;
- const horizontalPadding = isWide ? 28 : 16;
+ const hasHydratedLanguage = useLanguageStore((state) => state.hasHydrated);
+ const selectedLanguageId = useLanguageStore((state) => state.selectedLanguageId);
- if (!isLoaded) {
+ if (!isLoaded || !hasHydratedLanguage) {
return null;
}
@@ -53,239 +15,9 @@ export default function Index() {
return ;
}
- return (
-
-
-
-
-
- Open Profile
-
-
-
-
-
- Open Onboarding
-
-
-
-
-
- Choose Language
-
-
-
-
-
-
-
- lingua
-
-
-
-
-
-
-
-
-
-
-
-
-
- Font Family
-
- Poppins
-
-
- Poppins is a modern, geometric sans-serif typeface that provides
- excellent readability and a friendly personality.
-
-
-
- {typeRows.map((row) => (
-
- ))}
-
-
-
-
-
- );
-}
-
-type DesignCardProps = {
- children: React.ReactNode;
- fill?: boolean;
- title: string;
-};
-
-function DesignCard({ children, fill, title }: DesignCardProps) {
- return (
-
-
- {title}
-
-
- {children}
-
- );
-}
-
-type ColorItem = {
- hex: string;
- name: string;
-};
-
-type ColorGroupProps = {
- items: readonly ColorItem[];
- outlinedLast?: boolean;
- size: "large" | "medium";
- title: string;
-};
-
-function ColorGroup({ items, outlinedLast, size, title }: ColorGroupProps) {
- const swatchStyle =
- size === "large" ? styles.largeSwatch : styles.mediumSwatch;
-
- return (
-
- {title}
-
- {items.map((item, index) => {
- const isOutlined = Boolean(
- outlinedLast && index === items.length - 1,
- );
-
- return (
-
-
-
- {item.name}
-
-
- {item.hex}
-
-
- );
- })}
-
-
- );
-}
-
-type TypographyRowProps = {
- row: (typeof typeRows)[number];
-};
-
-function TypographyRow({ row }: TypographyRowProps) {
- const isHeading = row.label.startsWith("H");
- const fontClass =
- row.weight === "Bold"
- ? "font-poppins-bold"
- : row.weight === "SemiBold"
- ? "font-poppins-semibold"
- : row.weight === "Medium"
- ? "font-poppins-medium"
- : "font-poppins";
+ if (!selectedLanguageId) {
+ return ;
+ }
- return (
-
-
- {row.label}
-
- {row.usage}
- {row.size}px
- {row.weight}
-
- {isHeading
- ? (row.lineHeight / row.size).toFixed(1)
- : row.label === "Caption"
- ? "1.4"
- : "1.6"}
-
-
- );
+ return ;
}
-
-const styles = StyleSheet.create({
- screen: {
- backgroundColor: colors.neutral.background,
- flex: 1,
- },
- content: {
- backgroundColor: colors.neutral.background,
- },
- contentWide: {
- padding: 28,
- },
- contentNarrow: {
- padding: 16,
- },
- column: {
- flex: 1,
- },
- card: {
- shadowColor: colors.neutral.textPrimary,
- shadowOffset: { height: 8, width: 0 },
- shadowOpacity: 0.03,
- shadowRadius: 18,
- },
- fillCard: {
- minHeight: 892,
- },
- logo: {
- height: 112,
- width: 112,
- },
- largeSwatch: {
- borderRadius: 8,
- height: 98,
- width: 104,
- },
- mediumSwatch: {
- borderRadius: 8,
- height: 78,
- width: 78,
- },
- typeLabel: {
- width: 160,
- },
-});
diff --git a/app/profile.tsx b/app/profile.tsx
deleted file mode 100644
index 066c3ce..0000000
--- a/app/profile.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useAuth, useClerk, useUser } from "@clerk/expo";
-import { Ionicons } from "@expo/vector-icons";
-import { Redirect, router } from "expo-router";
-import { useState } from "react";
-import { Image, Pressable, Text, View } from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-
-export default function ProfileScreen() {
- const { isLoaded, isSignedIn } = useAuth();
- const { signOut } = useClerk();
- const { user } = useUser();
- const insets = useSafeAreaInsets();
- const [isSigningOut, setSigningOut] = useState(false);
- const [signOutError, setSignOutError] = useState(null);
-
- const emailAddress = user?.primaryEmailAddress?.emailAddress ?? "";
- const displayName = user?.fullName ?? user?.firstName ?? "Language Learner";
-
- const handleSignOut = async () => {
- if (isSigningOut) {
- return;
- }
-
- setSigningOut(true);
- setSignOutError(null);
-
- try {
- await signOut();
- router.replace("/onboarding");
- } catch (error) {
- console.error("Failed to sign out", error);
- setSignOutError("Unable to sign out. Please try again.");
- } finally {
- setSigningOut(false);
- }
- };
-
- if (!isLoaded) {
- return null;
- }
-
- if (!isSignedIn) {
- return ;
- }
-
- return (
-
-
-
- router.back()}
- >
-
-
-
- Profile
-
-
-
-
-
- {user?.imageUrl ? (
-
- ) : (
-
-
-
- )}
-
-
- {displayName}
-
- {emailAddress ? (
-
- {emailAddress}
-
- ) : null}
-
-
-
-
-
- Account
-
-
- Signed in with Clerk
-
-
-
-
-
-
- {isSigningOut ? "Signing Out..." : "Sign Out"}
-
-
- {signOutError ? (
-
- {signOutError}
-
- ) : null}
-
-
-
- );
-}
diff --git a/assets/images/earth-cropped.png b/assets/images/EarthCropped.png
similarity index 100%
rename from assets/images/earth-cropped.png
rename to assets/images/EarthCropped.png
diff --git a/components/bottom-tab-bar.tsx b/components/bottom-tab-bar.tsx
new file mode 100644
index 0000000..c0f356c
--- /dev/null
+++ b/components/bottom-tab-bar.tsx
@@ -0,0 +1,145 @@
+import { Ionicons } from "@expo/vector-icons";
+import type { BottomTabBarProps } from "@react-navigation/bottom-tabs";
+import type { ComponentProps } from "react";
+import { useEffect, useRef, useState } from "react";
+import {
+ Animated,
+ Pressable,
+ StyleSheet,
+ Text,
+ View,
+ type LayoutChangeEvent,
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+type TabIconName = ComponentProps["name"];
+
+const ACTIVE_SIZE = 54;
+
+const tabIcons: Record = {
+ "ai-teacher": "headset",
+ chat: "chatbubble-outline",
+ home: "home",
+ learn: "book-outline",
+ profile: "person-outline",
+};
+
+const tabLabels: Record = {
+ "ai-teacher": "AI Teacher",
+ chat: "Chat",
+ home: "Home",
+ learn: "Learn",
+ profile: "Profile",
+};
+
+export function BottomTabBar({
+ descriptors,
+ navigation,
+ state,
+}: BottomTabBarProps) {
+ const insets = useSafeAreaInsets();
+ const [barWidth, setBarWidth] = useState(0);
+ const indicatorX = useRef(new Animated.Value(0)).current;
+ const tabWidth = barWidth / state.routes.length;
+
+ useEffect(() => {
+ if (!barWidth) {
+ return;
+ }
+
+ Animated.spring(indicatorX, {
+ damping: 18,
+ mass: 0.7,
+ stiffness: 180,
+ toValue: state.index * tabWidth + (tabWidth - ACTIVE_SIZE) / 2,
+ useNativeDriver: true,
+ }).start();
+ }, [barWidth, indicatorX, state.index, tabWidth]);
+
+ const handleLayout = (event: LayoutChangeEvent) => {
+ setBarWidth(event.nativeEvent.layout.width);
+ };
+
+ return (
+
+
+ {barWidth > 0 ? (
+
+
+
+ ) : null}
+
+ {state.routes.map((route, index) => {
+ const descriptor = descriptors[route.key];
+ const isFocused = state.index === index;
+ const label = tabLabels[route.name] ?? String(descriptor.options.title);
+ const iconName = tabIcons[route.name] ?? "ellipse-outline";
+
+ const onPress = () => {
+ const event = navigation.emit({
+ canPreventDefault: true,
+ target: route.key,
+ type: "tabPress",
+ });
+
+ if (!isFocused && !event.defaultPrevented) {
+ navigation.navigate(route.name, route.params);
+ }
+ };
+
+ return (
+ pressed && styles.pressed}
+ >
+ {isFocused ? (
+
+ ) : (
+ <>
+
+
+ {label}
+
+ >
+ )}
+
+ );
+ })}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ activeCircle: {
+ left: 0,
+ zIndex: 1,
+ },
+ bar: {
+ boxShadow: "0 8px 24px rgba(13, 19, 43, 0.08)",
+ },
+ pressed: {
+ opacity: 0.72,
+ },
+});
diff --git a/components/tab-placeholder-screen.tsx b/components/tab-placeholder-screen.tsx
new file mode 100644
index 0000000..8995b66
--- /dev/null
+++ b/components/tab-placeholder-screen.tsx
@@ -0,0 +1,35 @@
+import { Text, View } from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+type TabPlaceholderScreenProps = {
+ subtitle: string;
+ title: string;
+};
+
+export function TabPlaceholderScreen({
+ subtitle,
+ title,
+}: TabPlaceholderScreenProps) {
+ const insets = useSafeAreaInsets();
+
+ return (
+
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+
+
+ );
+}
diff --git a/constants/images.ts b/constants/images.ts
index 66f3651..5cffbcb 100644
--- a/constants/images.ts
+++ b/constants/images.ts
@@ -2,7 +2,7 @@ import mascotLogo from "../assets/images/mascot-logo.png";
import mascotAuth from "../assets/images/mascot-auth.png";
import mascotWelcome from "../assets/images/mascot-welcome.png";
import earth from "../assets/images/earth.png";
-import earthCropped from "../assets/images/earth-cropped.png";
+import earthCropped from "../assets/images/EarthCropped.png";
import chineseFlag from "../assets/images/flags/chinese.png";
import englishFlag from "../assets/images/flags/english.png";
import frenchFlag from "../assets/images/flags/french.png";
diff --git a/package-lock.json b/package-lock.json
index 84c0289..62e7e35 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@clerk/expo": "^3.2.12",
"@expo/vector-icons": "^15.0.3",
+ "@react-native-async-storage/async-storage": "^2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
@@ -40,7 +41,8 @@
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1",
- "tailwindcss": "^4.3.0"
+ "tailwindcss": "^4.3.0",
+ "zustand": "^5.0.13"
},
"devDependencies": {
"@types/react": "~19.1.0",
@@ -1595,6 +1597,35 @@
"zustand": "5.0.3"
}
},
+ "node_modules/@base-org/account/node_modules/zustand": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.3.tgz",
+ "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@clerk/clerk-js": {
"version": "6.11.1",
"resolved": "https://registry.npmmirror.com/@clerk/clerk-js/-/clerk-js-6.11.1.tgz",
@@ -1652,6 +1683,39 @@
}
}
},
+ "node_modules/@clerk/clerk-js/node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmmirror.com/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@clerk/clerk-js/node_modules/react-dom": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.6.tgz",
+ "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.6"
+ }
+ },
+ "node_modules/@clerk/clerk-js/node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
"node_modules/@clerk/expo": {
"version": "3.2.12",
"resolved": "https://registry.npmmirror.com/@clerk/expo/-/expo-3.2.12.tgz",
@@ -3080,16 +3144,15 @@
}
},
"node_modules/@react-native-async-storage/async-storage": {
- "version": "1.24.0",
- "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz",
- "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
+ "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
"license": "MIT",
- "optional": true,
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
- "react-native": "^0.0.0-0 || >=0.60 <1.0"
+ "react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native/assets-registry": {
@@ -3568,6 +3631,90 @@
"@react-native-async-storage/async-storage": "^1.17.7"
}
},
+ "node_modules/@solana-mobile/wallet-standard-mobile/node_modules/@react-native-async-storage/async-storage": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz",
+ "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "merge-options": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react-native": "^0.0.0-0 || >=0.60 <1.0"
+ }
+ },
+ "node_modules/@solana/buffer-layout": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz",
+ "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "buffer": "~6.0.3"
+ },
+ "engines": {
+ "node": ">=5.10"
+ }
+ },
+ "node_modules/@solana/buffer-layout/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/@solana/codecs-core": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@solana/codecs-core/-/codecs-core-2.3.0.tgz",
+ "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@solana/errors": "2.3.0"
+ },
+ "engines": {
+ "node": ">=20.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.3.3"
+ }
+ },
+ "node_modules/@solana/codecs-numbers": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz",
+ "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@solana/codecs-core": "2.3.0",
+ "@solana/errors": "2.3.0"
+ },
+ "engines": {
+ "node": ">=20.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.3.3"
+ }
+ },
"node_modules/@solana/codecs-strings": {
"version": "6.9.0",
"resolved": "https://registry.npmmirror.com/@solana/codecs-strings/-/codecs-strings-6.9.0.tgz",
@@ -3680,6 +3827,49 @@
"node": ">=20"
}
},
+ "node_modules/@solana/errors": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@solana/errors/-/errors-2.3.0.tgz",
+ "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "chalk": "^5.4.1",
+ "commander": "^14.0.0"
+ },
+ "bin": {
+ "errors": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": ">=20.18.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.3.3"
+ }
+ },
+ "node_modules/@solana/errors/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@solana/errors/node_modules/commander": {
+ "version": "14.0.3",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz",
+ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@solana/wallet-adapter-base": {
"version": "0.9.27",
"resolved": "https://registry.npmmirror.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.27.tgz",
@@ -3740,6 +3930,19 @@
"react-native": ">0.74"
}
},
+ "node_modules/@solana/wallet-adapter-react/node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native-async-storage/async-storage": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz",
+ "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "merge-options": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react-native": "^0.0.0-0 || >=0.60 <1.0"
+ }
+ },
"node_modules/@solana/wallet-standard": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@solana/wallet-standard/-/wallet-standard-1.1.4.tgz",
@@ -3860,6 +4063,75 @@
"react": "*"
}
},
+ "node_modules/@solana/web3.js": {
+ "version": "1.98.4",
+ "resolved": "https://registry.npmmirror.com/@solana/web3.js/-/web3.js-1.98.4.tgz",
+ "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.25.0",
+ "@noble/curves": "^1.4.2",
+ "@noble/hashes": "^1.4.0",
+ "@solana/buffer-layout": "^4.0.1",
+ "@solana/codecs-numbers": "^2.1.0",
+ "agentkeepalive": "^4.5.0",
+ "bn.js": "^5.2.1",
+ "borsh": "^0.7.0",
+ "bs58": "^4.0.1",
+ "buffer": "6.0.3",
+ "fast-stable-stringify": "^1.0.0",
+ "jayson": "^4.1.1",
+ "node-fetch": "^2.7.0",
+ "rpc-websockets": "^9.0.2",
+ "superstruct": "^2.0.2"
+ }
+ },
+ "node_modules/@solana/web3.js/node_modules/base-x": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmmirror.com/base-x/-/base-x-3.0.11.tgz",
+ "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/@solana/web3.js/node_modules/bs58": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/bs58/-/bs58-4.0.1.tgz",
+ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "base-x": "^3.0.2"
+ }
+ },
+ "node_modules/@solana/web3.js/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/@stripe/stripe-js": {
"version": "5.6.0",
"resolved": "https://registry.npmmirror.com/@stripe/stripe-js/-/stripe-js-5.6.0.tgz",
@@ -4256,6 +4528,16 @@
"@babel/types": "^7.28.2"
}
},
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz",
@@ -4344,7 +4626,7 @@
"version": "19.1.17",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.1.17.tgz",
"integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@@ -4356,6 +4638,16 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"license": "MIT"
},
+ "node_modules/@types/ws": {
+ "version": "7.4.7",
+ "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-7.4.7.tgz",
+ "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/yargs": {
"version": "17.0.35",
"resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.35.tgz",
@@ -5163,6 +5455,19 @@
"node": ">= 14"
}
},
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz",
@@ -5799,6 +6104,45 @@
"node": ">=0.6"
}
},
+ "node_modules/bn.js": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.3.tgz",
+ "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/borsh": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmmirror.com/borsh/-/borsh-0.7.0.tgz",
+ "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "bn.js": "^5.2.0",
+ "bs58": "^4.0.0",
+ "text-encoding-utf-8": "^1.0.2"
+ }
+ },
+ "node_modules/borsh/node_modules/base-x": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmmirror.com/base-x/-/base-x-3.0.11.tgz",
+ "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/borsh/node_modules/bs58": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/bs58/-/bs58-4.0.1.tgz",
+ "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "base-x": "^3.0.2"
+ }
+ },
"node_modules/bplist-creator": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/bplist-creator/-/bplist-creator-0.1.0.tgz",
@@ -5933,6 +6277,21 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
+ "node_modules/bufferutil": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/bufferutil/-/bufferutil-4.1.0.tgz",
+ "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
@@ -6394,7 +6753,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/data-view-buffer": {
@@ -6568,6 +6927,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delay": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/delay/-/delay-5.0.0.tgz",
+ "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
@@ -6906,6 +7278,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es6-promise": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/es6-promisify": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/es6-promisify/-/es6-promisify-5.0.0.tgz",
+ "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "es6-promise": "^4.0.3"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
@@ -8098,6 +8487,15 @@
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"license": "Apache-2.0"
},
+ "node_modules/eyes": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmmirror.com/eyes/-/eyes-0.1.8.tgz",
+ "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==",
+ "peer": true,
+ "engines": {
+ "node": "> 0.1.90"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -8117,6 +8515,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-stable-stringify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz",
+ "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/fastest-levenshtein": {
"version": "1.0.16",
"resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
@@ -8821,6 +9226,16 @@
"node": ">= 14"
}
},
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
"node_modules/hyphenate-style-name": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
@@ -9272,7 +9687,6 @@
"resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"license": "MIT",
- "optional": true,
"engines": {
"node": ">=8"
}
@@ -9447,6 +9861,16 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
+ "node_modules/isomorphic-ws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
+ "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "ws": "*"
+ }
+ },
"node_modules/isows": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/isows/-/isows-1.0.7.tgz",
@@ -9505,6 +9929,57 @@
"node": ">= 0.4"
}
},
+ "node_modules/jayson": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/jayson/-/jayson-4.3.0.tgz",
+ "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/connect": "^3.4.33",
+ "@types/node": "^12.12.54",
+ "@types/ws": "^7.4.4",
+ "commander": "^2.20.3",
+ "delay": "^5.0.0",
+ "es6-promisify": "^5.0.0",
+ "eyes": "^0.1.8",
+ "isomorphic-ws": "^4.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "stream-json": "^1.9.1",
+ "uuid": "^8.3.2",
+ "ws": "^7.5.10"
+ },
+ "bin": {
+ "jayson": "bin/jayson.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jayson/node_modules/@types/node": {
+ "version": "12.20.55",
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz",
+ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/jayson/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/jayson/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/jest-environment-node": {
"version": "29.7.0",
"resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
@@ -9777,6 +10252,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "license": "ISC",
+ "peer": true
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
@@ -10305,7 +10787,6 @@
"resolved": "https://registry.npmmirror.com/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"license": "MIT",
- "optional": true,
"dependencies": {
"is-plain-obj": "^2.1.0"
},
@@ -10879,6 +11360,19 @@
"node": ">= 6.13.0"
}
},
+ "node_modules/node-gyp-build": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz",
@@ -12610,6 +13104,103 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/rpc-websockets": {
+ "version": "9.3.10",
+ "resolved": "https://registry.npmmirror.com/rpc-websockets/-/rpc-websockets-9.3.10.tgz",
+ "integrity": "sha512-QT5PQ6LiWhA5RCS93oWwgxU4XzQltkYm8C3aTmmKEgj0HolGRo3VbdzELw7CEV35l9T7Amha8Vnr4rCfSjVP+w==",
+ "license": "LGPL-3.0-only",
+ "peer": true,
+ "dependencies": {
+ "@swc/helpers": "^0.5.11",
+ "@types/ws": "^8.2.2",
+ "buffer": "^6.0.3",
+ "eventemitter3": "^5.0.1",
+ "ws": "^8.5.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "paypal",
+ "url": "https://paypal.me/kozjak"
+ },
+ "optionalDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^6.0.0"
+ }
+ },
+ "node_modules/rpc-websockets/node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/rpc-websockets/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/rpc-websockets/node_modules/utf-8-validate": {
+ "version": "6.0.6",
+ "resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-6.0.6.tgz",
+ "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
+ "node_modules/rpc-websockets/node_modules/ws": {
+ "version": "8.20.1",
+ "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.1.tgz",
+ "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/safe-array-concat": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.4.tgz",
@@ -13194,6 +13785,23 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/stream-chain": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmmirror.com/stream-chain/-/stream-chain-2.2.5.tgz",
+ "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==",
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/stream-json": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmmirror.com/stream-json/-/stream-json-1.9.1.tgz",
+ "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "stream-chain": "^2.2.5"
+ }
+ },
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
@@ -13393,6 +14001,16 @@
"node": ">= 6"
}
},
+ "node_modules/superstruct": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/superstruct/-/superstruct-2.0.2.tgz",
+ "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
@@ -13564,6 +14182,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/text-encoding-utf-8": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
+ "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==",
+ "peer": true
+ },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
@@ -13804,7 +14428,6 @@
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -14059,6 +14682,21 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/utf-8-validate": {
+ "version": "5.0.10",
+ "resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
+ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "node-gyp-build": "^4.3.0"
+ },
+ "engines": {
+ "node": ">=6.14.2"
+ }
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -14786,9 +15424,9 @@
}
},
"node_modules/zustand": {
- "version": "5.0.3",
- "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.3.tgz",
- "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
+ "version": "5.0.13",
+ "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.13.tgz",
+ "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
diff --git a/package.json b/package.json
index 99e80f9..c9565bd 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"dependencies": {
"@clerk/expo": "^3.2.12",
"@expo/vector-icons": "^15.0.3",
+ "@react-native-async-storage/async-storage": "^2.2.0",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
@@ -44,7 +45,8 @@
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1",
- "tailwindcss": "^4.3.0"
+ "tailwindcss": "^4.3.0",
+ "zustand": "^5.0.13"
},
"devDependencies": {
"@types/react": "~19.1.0",
diff --git a/store/UseLanguageStore.ts b/store/UseLanguageStore.ts
new file mode 100644
index 0000000..51cd7d4
--- /dev/null
+++ b/store/UseLanguageStore.ts
@@ -0,0 +1,60 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
+import { create } from "zustand";
+import {
+ createJSONStorage,
+ persist,
+ type StateStorage,
+} from "zustand/middleware";
+
+type LanguageState = {
+ clearSelectedLanguage: () => void;
+ hasHydrated: boolean;
+ selectedLanguageId: string | null;
+ setHasHydrated: (hasHydrated: boolean) => void;
+ setSelectedLanguageId: (languageId: string) => void;
+};
+
+const noopStorage: StateStorage = {
+ getItem: () => null,
+ removeItem: () => undefined,
+ setItem: () => undefined,
+};
+
+const getLanguageStorage = () => {
+ if (process.env.EXPO_OS === "web" && typeof window === "undefined") {
+ return noopStorage;
+ }
+
+ return AsyncStorage;
+};
+
+export const useLanguageStore = create()(
+ persist(
+ (set) => ({
+ clearSelectedLanguage: () => set({ selectedLanguageId: null }),
+ hasHydrated: false,
+ selectedLanguageId: null,
+ setHasHydrated: (hasHydrated) => set({ hasHydrated }),
+ setSelectedLanguageId: (languageId) =>
+ set({ selectedLanguageId: languageId }),
+ }),
+ {
+ name: "lingua-language-storage",
+ onRehydrateStorage: () => (state) => {
+ state?.setHasHydrated(true);
+ },
+ partialize: (state) => ({
+ selectedLanguageId: state.selectedLanguageId,
+ }),
+ storage: createJSONStorage(getLanguageStorage),
+ },
+ ),
+);
+
+useLanguageStore.persist.onFinishHydration((state) => {
+ state.setHasHydrated(true);
+});
+
+if (useLanguageStore.persist.hasHydrated()) {
+ useLanguageStore.setState({ hasHydrated: true });
+}
From f451e5da2d05f6ea854bd4b8605e28ff0a324840 Mon Sep 17 00:00:00 2001
From: Retsomm <112182ssss@gmail.com>
Date: Mon, 18 May 2026 21:46:33 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=92=B0=E5=A2=83?=
=?UTF-8?q?=E8=AE=8A=E6=95=B8=E7=AF=84=E6=9C=AC=EF=BC=8C=E4=BF=AE=E6=AD=A3?=
=?UTF-8?q?=20Clerk=20=E5=8F=AF=E7=99=BC=E5=B8=83=E9=87=91=E9=91=B0?=
=?UTF-8?q?=E7=82=BA=E9=A0=90=E8=A8=AD=E5=80=BC=EF=BC=9B=E9=87=8D=E6=A7=8B?=
=?UTF-8?q?=E5=BA=95=E9=83=A8=E6=A8=99=E7=B1=A4=E6=AC=84=E6=A8=A3=E5=BC=8F?=
=?UTF-8?q?=EF=BC=8C=E6=94=B9=E5=96=84=E5=9C=93=E5=BD=A2=E6=8C=87=E7=A4=BA?=
=?UTF-8?q?=E5=99=A8=E9=A1=AF=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env.example | 2 +-
components/bottom-tab-bar.tsx | 17 +++++++++++++++--
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/.env.example b/.env.example
index d42905f..bf39f24 100644
--- a/.env.example
+++ b/.env.example
@@ -1 +1 @@
-EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_your_clerk_production_publishable_key
+EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_CLERK_PUBLISHABLE_KEY
diff --git a/components/bottom-tab-bar.tsx b/components/bottom-tab-bar.tsx
index c0f356c..0383565 100644
--- a/components/bottom-tab-bar.tsx
+++ b/components/bottom-tab-bar.tsx
@@ -73,9 +73,12 @@ export function BottomTabBar({
>
{barWidth > 0 ? (