diff --git a/apps/mobile/app/(app)/(tabs)/settings.tsx b/apps/mobile/app/(app)/(tabs)/settings.tsx
index b301a110..35af9c25 100644
--- a/apps/mobile/app/(app)/(tabs)/settings.tsx
+++ b/apps/mobile/app/(app)/(tabs)/settings.tsx
@@ -1,15 +1,36 @@
+import * as Application from 'expo-application';
+
+import { Logo } from '@/components/common/logo'
import { MenuItem } from '@/components/common/menu-item'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
+import { Switch } from '@/components/ui/switch'
import { Text } from '@/components/ui/text'
import { useLocale } from '@/locales/provider'
import { useAuth, useUser } from '@clerk/clerk-expo'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Link } from 'expo-router'
-import { ChevronRightIcon, EarthIcon, LogOutIcon, PencilIcon, SwatchBookIcon } from 'lucide-react-native'
-import { Alert, ScrollView, View } from 'react-native'
+import {
+ BellIcon,
+ ChevronRightIcon,
+ CurrencyIcon,
+ EarthIcon,
+ GithubIcon,
+ InboxIcon,
+ LockKeyholeIcon,
+ LogOutIcon,
+ MessageSquareQuoteIcon,
+ PencilIcon,
+ ScanFaceIcon,
+ ShapesIcon,
+ Share2Icon,
+ StarIcon,
+ SwatchBookIcon,
+ WalletCardsIcon,
+} from 'lucide-react-native'
+import { Alert, Linking, ScrollView, View } from 'react-native'
export default function SettingsScreen() {
const { signOut } = useAuth()
@@ -19,10 +40,11 @@ export default function SettingsScreen() {
return (
-
-
+
+
+
-
+
QK
-
+
Free
@@ -47,56 +66,180 @@ export default function SettingsScreen() {
+
+
+ {t(i18n)`General`}
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
{t(i18n)`App settings`}
-
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
}
+ label={t(i18n)`Login using FaceID`}
+ icon={ScanFaceIcon}
+ rightSection={
+
+ }
/>
-
-
+
}
/>
-
+
-
+
{t(i18n)`Others`}
-
+
+
+ }
+ />
+
+ }
+ disabled
+ />
+ }
+ disabled
+ />
+ }
+ onPress={() => Linking.openURL('https://github.com/sixpm-ai/6pm')}
+ />
+
+
+
+
+
+
+ {t(i18n)`ver.`}{Application.nativeApplicationVersion}
+
+
+
+
+ {t(i18n)`Terms of use`}
+
+
+
+
+ {t(i18n)`Privacy policy`}
+
+
+
)
diff --git a/apps/mobile/components/common/logo.tsx b/apps/mobile/components/common/logo.tsx
new file mode 100644
index 00000000..b4f910b7
--- /dev/null
+++ b/apps/mobile/components/common/logo.tsx
@@ -0,0 +1,25 @@
+import Svg, { type SvgProps, Path } from 'react-native-svg'
+
+export const Logo = (props: SvgProps) => (
+
+)
diff --git a/apps/mobile/components/common/menu-item.tsx b/apps/mobile/components/common/menu-item.tsx
index 145d76ca..bb318cec 100644
--- a/apps/mobile/components/common/menu-item.tsx
+++ b/apps/mobile/components/common/menu-item.tsx
@@ -10,18 +10,21 @@ type MenuItemProps = {
rightSection?: React.ReactNode
onPress?: () => void
className?: string
+ disabled?: boolean
}
-export const MenuItem = forwardRef(function (
- { label, icon: Icon, rightSection, onPress, className }: MenuItemProps,
+export const MenuItem = forwardRef(function(
+ { label, icon: Icon, rightSection, onPress, className, disabled }: MenuItemProps,
ref: React.ForwardedRef>,
) {
return (
diff --git a/apps/mobile/components/primitives/switch/index.ts b/apps/mobile/components/primitives/switch/index.ts
new file mode 100644
index 00000000..4dd22567
--- /dev/null
+++ b/apps/mobile/components/primitives/switch/index.ts
@@ -0,0 +1 @@
+export * from './switch';
diff --git a/apps/mobile/components/primitives/switch/switch.tsx b/apps/mobile/components/primitives/switch/switch.tsx
new file mode 100644
index 00000000..808f7971
--- /dev/null
+++ b/apps/mobile/components/primitives/switch/switch.tsx
@@ -0,0 +1,65 @@
+import * as Slot from '@/components/primitives/slot';
+import type {
+ PressableRef,
+ SlottablePressableProps,
+ SlottableViewProps,
+ ViewRef,
+} from '@/components/primitives/types';
+import * as React from 'react';
+import { type GestureResponderEvent, Pressable, View } from 'react-native';
+import type { SwitchRootProps } from './types';
+
+const Root = React.forwardRef<
+ PressableRef,
+ SlottablePressableProps & SwitchRootProps
+>(
+ (
+ {
+ asChild,
+ checked,
+ onCheckedChange,
+ disabled,
+ onPress: onPressProp,
+ 'aria-valuetext': ariaValueText,
+ ...props
+ },
+ ref
+ ) => {
+ function onPress(ev: GestureResponderEvent) {
+ if (disabled) { return };
+ onCheckedChange(!checked);
+ onPressProp?.(ev);
+ }
+
+ const Component = asChild ? Slot.Pressable : Pressable;
+ return (
+
+ );
+ }
+);
+
+Root.displayName = 'RootNativeSwitch';
+
+const Thumb = React.forwardRef(
+ ({ asChild, ...props }, ref) => {
+ const Component = asChild ? Slot.View : View;
+ return ;
+ }
+);
+
+Thumb.displayName = 'ThumbNativeSwitch';
+
+export { Root, Thumb };
diff --git a/apps/mobile/components/primitives/switch/switch.web.tsx b/apps/mobile/components/primitives/switch/switch.web.tsx
new file mode 100644
index 00000000..09c9c4fa
--- /dev/null
+++ b/apps/mobile/components/primitives/switch/switch.web.tsx
@@ -0,0 +1,67 @@
+import * as Switch from '@radix-ui/react-switch';
+import * as React from 'react';
+import { Pressable, View, type GestureResponderEvent } from 'react-native';
+import * as Slot from '@/components/primitives/slot';
+import type {
+ PressableRef,
+ SlottablePressableProps,
+ SlottableViewProps,
+ ViewRef,
+} from '@/components/primitives/types';
+import type { SwitchRootProps } from './types';
+
+const Root = React.forwardRef(
+ (
+ {
+ asChild,
+ checked,
+ onCheckedChange,
+ disabled,
+ onPress: onPressProp,
+ onKeyDown: onKeyDownProp,
+ ...props
+ },
+ ref
+ ) => {
+ function onPress(ev: GestureResponderEvent) {
+ onCheckedChange(!checked);
+ onPressProp?.(ev);
+ }
+
+ function onKeyDown(ev: React.KeyboardEvent) {
+ onKeyDownProp?.(ev);
+ if (ev.key === ' ') {
+ onCheckedChange(!checked);
+ }
+ }
+
+ const Component = asChild ? Slot.Pressable : Pressable;
+ return (
+
+
+
+ );
+ }
+);
+
+Root.displayName = 'RootWebSwitch';
+
+const Thumb = React.forwardRef(({ asChild, ...props }, ref) => {
+ const Component = asChild ? Slot.View : View;
+ return (
+
+
+
+ );
+});
+
+Thumb.displayName = 'ThumbWebSwitch';
+
+export { Root, Thumb };
diff --git a/apps/mobile/components/primitives/switch/types.ts b/apps/mobile/components/primitives/switch/types.ts
new file mode 100644
index 00000000..986c2049
--- /dev/null
+++ b/apps/mobile/components/primitives/switch/types.ts
@@ -0,0 +1,11 @@
+interface SwitchRootProps {
+ checked: boolean;
+ onCheckedChange: (checked: boolean) => void;
+ disabled?: boolean;
+ /**
+ * Platform: WEB ONLY
+ */
+ onKeyDown?: (ev: React.KeyboardEvent) => void;
+}
+
+export type { SwitchRootProps };
diff --git a/apps/mobile/components/ui/switch.tsx b/apps/mobile/components/ui/switch.tsx
new file mode 100644
index 00000000..ef6c7f35
--- /dev/null
+++ b/apps/mobile/components/ui/switch.tsx
@@ -0,0 +1,97 @@
+import * as SwitchPrimitives from '@/components/primitives/switch';
+import * as React from 'react';
+import { Platform } from 'react-native';
+import Animated, {
+ interpolateColor,
+ useAnimatedStyle,
+ useDerivedValue,
+ withTiming,
+} from 'react-native-reanimated';
+
+import { useColorScheme } from '@/hooks/useColorScheme';
+import { cn } from '@/lib/utils';
+
+const SwitchWeb = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+
+SwitchWeb.displayName = 'SwitchWeb';
+
+const RGB_COLORS = {
+ light: {
+ primary: 'rgb(24, 24, 27)',
+ input: 'rgb(228, 228, 231)',
+ },
+ dark: {
+ primary: 'rgb(250, 250, 250)',
+ input: 'rgb(39, 39, 42)',
+ },
+} as const;
+
+const SwitchNative = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { colorScheme } = useColorScheme();
+ const translateX = useDerivedValue(() => (props.checked ? 18 : 0));
+ const animatedRootStyle = useAnimatedStyle(() => {
+ return {
+ backgroundColor: interpolateColor(
+ translateX.value,
+ [0, 18],
+ [RGB_COLORS[colorScheme].input, RGB_COLORS[colorScheme].primary]
+ ),
+ };
+ });
+ const animatedThumbStyle = useAnimatedStyle(() => ({
+ transform: [{ translateX: withTiming(translateX.value, { duration: 200 }) }],
+ }));
+ return (
+
+
+
+
+
+
+
+ );
+});
+SwitchNative.displayName = 'SwitchNative';
+
+const Switch = Platform.select({
+ web: SwitchWeb,
+ default: SwitchNative,
+});
+
+export { Switch };
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index b7c833bb..6454b7ed 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -26,6 +26,7 @@
"@lingui/macro": "^4.11.1",
"@lingui/react": "^4.11.1",
"@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/native": "^6.0.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2bb7eb4d..b40d076c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -117,6 +117,9 @@ importers:
'@radix-ui/react-label':
specifier: ^2.0.2
version: 2.0.2(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-switch':
+ specifier: ^1.0.3
+ version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-tabs':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1857,6 +1860,19 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-switch@1.0.3':
+ resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-tabs@1.0.4':
resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==}
peerDependencies:
@@ -1897,6 +1913,24 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-previous@1.0.1':
+ resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.0.1':
+ resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@react-native-async-storage/async-storage@1.23.1':
resolution: {integrity: sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==}
peerDependencies:
@@ -8523,6 +8557,22 @@ snapshots:
optionalDependencies:
'@types/react': 18.2.79
+ '@radix-ui/react-switch@1.0.3(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.24.7
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.79)(react@18.3.1)
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.79)(react@18.3.1)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.79)(react@18.3.1)
+ '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.79)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.79)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.2.79
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-tabs@1.0.4(@types/react-dom@18.3.0)(@types/react@18.2.79)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.24.7
@@ -8562,6 +8612,21 @@ snapshots:
optionalDependencies:
'@types/react': 18.2.79
+ '@radix-ui/react-use-previous@1.0.1(@types/react@18.2.79)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.24.7
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.2.79
+
+ '@radix-ui/react-use-size@1.0.1(@types/react@18.2.79)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.24.7
+ '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.79)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.2.79
+
'@react-native-async-storage/async-storage@1.23.1(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.2.79)(react@18.3.1))':
dependencies:
merge-options: 3.0.4