├── hooks ├── useColorScheme.ts ├── useColorScheme.web.ts └── useThemeColor.ts ├── assets ├── images │ ├── dp.png │ ├── icon.png │ ├── favicon.png │ ├── gift_box.webp │ ├── react-logo.png │ ├── yellow_star.webp │ ├── adaptive-icon.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ ├── twitter-cover.png │ ├── splash-icon-dark.png │ ├── splash-icon-light.png │ └── partial-react-logo.png ├── fonts │ ├── Inter-Bold.ttf │ ├── Inter-Medium.ttf │ ├── Inter-Regular.ttf │ └── Inter-SemiBold.ttf └── icon │ └── slack-chevron-left.png ├── app ├── wa-status │ ├── index.android.tsx │ ├── notes.tsx │ ├── index.tsx │ ├── page.tsx │ ├── _layout.tsx │ └── index.ios.tsx ├── untitled │ ├── _layout.android.tsx │ ├── _layout.tsx │ ├── index.tsx │ ├── [id].tsx │ └── _layout.ios.tsx ├── slack-liquid-glass │ ├── index.android.tsx │ ├── index.tsx │ ├── _layout.tsx │ ├── chat.tsx │ └── index.ios.tsx ├── 3d-rotate │ ├── _layout.tsx │ ├── index.tsx │ └── default.tsx ├── stacked-input │ └── _layout.tsx ├── 3d-card-scrolling │ ├── _layout.tsx │ └── index.tsx ├── safari-bar │ ├── _layout.tsx │ └── index.tsx ├── open-gift │ ├── _layout.tsx │ └── index.tsx ├── re-captcha │ ├── _layout.tsx │ └── index.tsx ├── in-app-notif │ ├── _layout.tsx │ └── index.tsx ├── slider-demo │ ├── _layout.tsx │ └── index.tsx ├── twitter-profile │ └── _layout.tsx ├── threads-pull-refresh │ ├── _layout.tsx │ └── index.tsx ├── keyboard-ctrl │ ├── index.tsx │ └── _layout.tsx ├── gesture-menu │ ├── _layout.tsx │ └── index.tsx ├── gesture-nav │ ├── _layout.tsx │ └── index.tsx ├── grok-sidebar │ └── _layout.tsx ├── ios-camera │ ├── _layout.tsx │ └── index.tsx ├── shared-option │ ├── _layout.tsx │ └── expanded.tsx ├── index.tsx └── _layout.tsx ├── constants ├── config.json ├── Theme.ts ├── Colors.ts └── index.ts ├── components ├── untitled │ ├── screen.tsx │ ├── header.tsx │ ├── screen.ios.tsx │ ├── button.tsx │ ├── header.ios.tsx │ ├── card.tsx │ └── bottom-bar.tsx ├── stacked-input │ ├── index.tsx │ ├── switch.tsx │ ├── provider.tsx │ ├── trigger.tsx │ └── input.tsx ├── re-captcha │ ├── config.ts │ ├── underlay-text.tsx │ ├── shimmer.tsx │ ├── card.tsx │ └── tray.tsx ├── ui │ ├── Logo.tsx │ ├── dots.tsx │ ├── animated-text.tsx │ ├── TextLink.tsx │ ├── Button.tsx │ ├── glassy-view.tsx │ └── check-box.tsx ├── notify │ ├── type.ts │ ├── index.tsx │ └── card.tsx ├── slack │ ├── header.tsx │ ├── config.ts │ ├── banner.tsx │ ├── message-box.tsx │ ├── message-bar.tsx │ ├── header.ios.tsx │ └── island.tsx ├── Container.tsx ├── ResponsiveText.tsx ├── safari-bar │ ├── article.tsx │ ├── header.tsx │ ├── config.ts │ └── bar.tsx ├── ProgressiveFade.tsx ├── ThemedView.tsx ├── PresableBounce.tsx ├── HeadComponent.tsx ├── ThemedText.tsx ├── icons │ └── index.tsx ├── drawer │ ├── DrawerItemList.tsx │ └── DrawerItem.tsx ├── 3dRotate.tsx ├── DrawerContent.tsx ├── RefreshLogo.tsx ├── TextArea.tsx └── Slider.tsx ├── tsconfig.json ├── eas.json ├── .gitignore ├── functions └── index.ts ├── README.md ├── app.json └── package.json /hooks/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | export { useColorScheme } from 'react-native'; 2 | -------------------------------------------------------------------------------- /assets/images/dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/dp.png -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /app/wa-status/index.android.tsx: -------------------------------------------------------------------------------- 1 | export * from "./index.ios"; 2 | export { default } from "./index.ios"; 3 | -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /app/untitled/_layout.android.tsx: -------------------------------------------------------------------------------- 1 | export * from "./_layout.ios"; 2 | export { default } from "./_layout.ios"; 3 | -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /assets/images/gift_box.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/gift_box.webp -------------------------------------------------------------------------------- /app/slack-liquid-glass/index.android.tsx: -------------------------------------------------------------------------------- 1 | export * from "./index.ios"; 2 | export { default } from "./index.ios"; 3 | -------------------------------------------------------------------------------- /assets/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/react-logo.png -------------------------------------------------------------------------------- /assets/images/yellow_star.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/yellow_star.webp -------------------------------------------------------------------------------- /assets/fonts/Inter-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/fonts/Inter-SemiBold.ttf -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /assets/images/twitter-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/twitter-cover.png -------------------------------------------------------------------------------- /assets/icon/slack-chevron-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/icon/slack-chevron-left.png -------------------------------------------------------------------------------- /assets/images/splash-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/splash-icon-dark.png -------------------------------------------------------------------------------- /assets/images/splash-icon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/splash-icon-light.png -------------------------------------------------------------------------------- /assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Solarin-Johnson/my-app/HEAD/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /app/3d-rotate/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/stacked-input/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/3d-card-scrolling/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/wa-status/notes.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View } from "react-native"; 3 | 4 | export default function Page() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/safari-bar/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slot } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/open-gift/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/re-captcha/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/in-app-notif/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/slider-demo/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/twitter-profile/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/threads-pull-refresh/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "expo-router"; 2 | 3 | export default function Layout() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /constants/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Solarin", 3 | "contact": { 4 | "twitter": "https://x.com/S0LARIN/", 5 | "github": "https://github.com/Solarin-Johnson" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/keyboard-ctrl/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BottomBar from "../../components/BottomBar"; 3 | 4 | export default function KeyboardCtrl() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /components/untitled/screen.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text } from "react-native"; 2 | import React from "react"; 3 | 4 | export default function UntitledScreen(props: any) { 5 | return null; 6 | } 7 | -------------------------------------------------------------------------------- /app/gesture-menu/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slot } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/gesture-nav/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slot } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/grok-sidebar/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slot } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/ios-camera/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slot } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/keyboard-ctrl/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slot } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /app/slack-liquid-glass/index.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text } from "react-native"; 2 | import React from "react"; 3 | 4 | export default function Index() { 5 | return ( 6 | 7 | Index 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /components/stacked-input/index.tsx: -------------------------------------------------------------------------------- 1 | import Input from "./input"; 2 | import { Provider } from "./provider"; 3 | import Switch from "./switch"; 4 | import Trigger from "./trigger"; 5 | 6 | export const Stacked = { 7 | Provider, 8 | Input, 9 | Trigger, 10 | Switch, 11 | }; 12 | -------------------------------------------------------------------------------- /app/wa-status/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return null; 3 | } 4 | 5 | // Dummy components to ignore errors 6 | export const ClusterContainer = (props: any) => null; 7 | export const ClusterItem = (props: any) => null; 8 | export const PageContainer = (props: any) => null; 9 | -------------------------------------------------------------------------------- /components/re-captcha/config.ts: -------------------------------------------------------------------------------- 1 | import { SharedValue } from "react-native-reanimated"; 2 | 3 | export const SPRING_CONFIG = { 4 | damping: 10, 5 | stiffness: 90, 6 | mass: 0.5, 7 | }; 8 | 9 | export type ReCaptchaProps = { 10 | shrinkProgress: SharedValue; 11 | isVerified: SharedValue; 12 | }; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /components/ui/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native"; 2 | import React from "react"; 3 | import { LogoIcon } from "../icons"; 4 | import { useThemeColor } from "@/hooks/useThemeColor"; 5 | 6 | export default function Logo() { 7 | const text = useThemeColor("text"); 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /app/wa-status/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ClusterContainer, ClusterItem, PageContainer } from "./index"; 3 | 4 | export default function Page() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 16.8.0", 4 | "appVersionSource": "remote" 5 | }, 6 | "build": { 7 | "development": { 8 | "developmentClient": true, 9 | "distribution": "internal" 10 | }, 11 | "preview": { 12 | "distribution": "internal" 13 | }, 14 | "production": { 15 | "autoIncrement": true 16 | } 17 | }, 18 | "submit": { 19 | "production": {} 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/notify/type.ts: -------------------------------------------------------------------------------- 1 | export type MessageOptions = { 2 | description?: string; 3 | expandedChild?: React.ReactNode; 4 | action?: { 5 | label: string; 6 | onClick: () => void; 7 | }; 8 | }; 9 | 10 | export type MessageType = { 11 | id?: number; 12 | text: string; 13 | options?: MessageOptions; 14 | }; 15 | 16 | export interface NotifyContextType { 17 | notify: (msg: string, options?: MessageOptions) => void; 18 | messages: MessageType[]; 19 | } 20 | -------------------------------------------------------------------------------- /components/slack/header.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text } from "react-native"; 2 | import React from "react"; 3 | 4 | export default function HeaderTitle({}: { 5 | children: React.ReactNode; 6 | tintColor?: string; 7 | style?: any; 8 | }) { 9 | return ( 10 | 11 | Header 12 | 13 | ); 14 | } 15 | 16 | export const HeaderRight = () => { 17 | return ( 18 | 19 | Right 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /components/untitled/header.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleProp, ViewStyle } from "react-native"; 2 | import React from "react"; 3 | export interface UntitledHeaderProps { 4 | title?: string; 5 | children?: React.ReactNode; 6 | style?: StyleProp; 7 | contentStyle?: StyleProp; 8 | } 9 | 10 | export default function UntitledHeader({ 11 | title, 12 | style, 13 | contentStyle, 14 | children, 15 | }: UntitledHeaderProps) { 16 | return ( 17 | 18 | eader 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/shared-option/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Stack } from "expo-router"; 3 | 4 | export default function Layout() { 5 | return ( 6 | 12 | 13 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/ios-camera/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { SafeAreaView } from "react-native-safe-area-context"; 3 | import IOSCameraControl from "@/components/iOSCameraControl"; 4 | import { Platform } from "react-native"; 5 | 6 | const isWeb = Platform.OS === "web"; 7 | 8 | export default function Index() { 9 | return ( 10 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /hooks/useColorScheme.web.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useColorScheme as useRNColorScheme } from 'react-native'; 3 | 4 | /** 5 | * To support static rendering, this value needs to be re-calculated on the client side for web 6 | */ 7 | export function useColorScheme() { 8 | const [hasHydrated, setHasHydrated] = useState(false); 9 | 10 | useEffect(() => { 11 | setHasHydrated(true); 12 | }, []); 13 | 14 | const colorScheme = useRNColorScheme(); 15 | 16 | if (hasHydrated) { 17 | return colorScheme; 18 | } 19 | 20 | return 'light'; 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | ios/ 13 | android/ 14 | 15 | # Native 16 | *.orig.* 17 | *.jks 18 | *.p8 19 | *.p12 20 | *.key 21 | *.mobileprovision 22 | 23 | # Metro 24 | .metro-health-check* 25 | 26 | # debug 27 | npm-debug.* 28 | yarn-debug.* 29 | yarn-error.* 30 | 31 | # macOS 32 | .DS_Store 33 | *.pem 34 | 35 | # local env files 36 | .env*.local 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | 41 | app-example 42 | -------------------------------------------------------------------------------- /hooks/useThemeColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Learn more about light and dark modes: 3 | * https://docs.expo.dev/guides/color-schemes/ 4 | */ 5 | 6 | import { Colors } from "@/constants/Colors"; 7 | import { useColorScheme } from "@/hooks/useColorScheme"; 8 | 9 | export function useThemeColor( 10 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark, 11 | props?: { light?: string; dark?: string } 12 | ) { 13 | const theme = useColorScheme() ?? "light"; 14 | const colorFromProps = props && props[theme]; 15 | 16 | if (colorFromProps) { 17 | return colorFromProps; 18 | } else { 19 | return Colors[theme][colorName]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/3d-rotate/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSharedValue } from "react-native-reanimated"; 3 | import Index from "./default"; 4 | import Slider from "@/components/Slider"; 5 | import { View } from "react-native"; 6 | 7 | export default function IosIndex() { 8 | const progress = useSharedValue(0); 9 | 10 | return ( 11 | 12 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /app/slider-demo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { SafeAreaView } from "react-native-safe-area-context"; 3 | import { Platform, StyleSheet } from "react-native"; 4 | import PlaybackControl from "@/components/PlaybackControl"; 5 | 6 | const isWeb = Platform.OS === "web"; 7 | 8 | export default function SliderDemo() { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | const styles = StyleSheet.create({ 17 | container: { 18 | flex: 1, 19 | justifyContent: isWeb ? "center" : "flex-end", 20 | alignItems: "center", 21 | paddingBottom: isWeb ? 0 : 72, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /functions/index.ts: -------------------------------------------------------------------------------- 1 | import * as Haptics from "expo-haptics"; 2 | 3 | export class Feedback { 4 | static light() { 5 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); 6 | } 7 | 8 | static medium() { 9 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); 10 | } 11 | 12 | static heavy() { 13 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); 14 | } 15 | 16 | static soft() { 17 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Soft); 18 | } 19 | 20 | static success() { 21 | Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); 22 | } 23 | 24 | static selection() { 25 | Haptics.selectionAsync(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /constants/Theme.ts: -------------------------------------------------------------------------------- 1 | import { fonts } from "./../node_modules/@react-navigation/native/src/theming/fonts"; 2 | import { Theme } from "@react-navigation/native"; 3 | import { Colors } from "./Colors"; 4 | 5 | export const DarkTheme: Theme = { 6 | dark: true, 7 | colors: { 8 | primary: "rgb(10, 132, 255)", 9 | border: "rgb(39, 39, 41)", 10 | notification: "rgb(255, 69, 58)", 11 | ...Colors.dark, 12 | }, 13 | fonts, 14 | }; 15 | 16 | export const LightTheme: Theme = { 17 | dark: false, 18 | colors: { 19 | primary: "rgb(0, 122, 255)", 20 | border: "rgb(216, 216, 216)", 21 | notification: "rgb(255, 59, 48)", 22 | ...Colors.light, 23 | }, 24 | fonts, 25 | }; 26 | -------------------------------------------------------------------------------- /app/untitled/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { useThemeColor } from "@/hooks/useThemeColor"; 2 | import { Stack } from "expo-router"; 3 | 4 | export default function Layout() { 5 | const bg = useThemeColor("untitledBg"); 6 | return ( 7 | 12 | 16 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/ui/dots.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text } from "react-native"; 2 | import React from "react"; 3 | import Svg, { Circle, Defs, Pattern, Rect } from "react-native-svg"; 4 | 5 | type DotsProps = { 6 | spacing?: number; 7 | dotSize?: number; 8 | color?: string; 9 | }; 10 | 11 | export default function Dots({ 12 | spacing = 20, 13 | dotSize = 2, 14 | color = "#999", 15 | }: DotsProps) { 16 | return ( 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/untitled/screen.ios.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import UntitledHeader, { UntitledHeaderProps } from "./header"; 3 | import Transition, { 4 | useScreenAnimation, 5 | } from "react-native-screen-transitions"; 6 | import UntitledBottomBar, { UntitledBottomBarProps } from "./bottom-bar"; 7 | 8 | const ScrollView = Transition.ScrollView; 9 | 10 | export default function UntitledScreen({ 11 | children, 12 | headerProps, 13 | barProps, 14 | }: { 15 | children?: React.ReactNode; 16 | headerProps?: UntitledHeaderProps; 17 | barProps?: UntitledBottomBarProps; 18 | }) { 19 | const props = useScreenAnimation(); 20 | 21 | return ( 22 | <> 23 | 24 | {children} 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /app/slack-liquid-glass/_layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Stack } from "expo-router"; 3 | import HeaderTitle, { HeaderRight } from "@/components/slack/header"; 4 | 5 | export default function Layout() { 6 | return ( 7 | 12 | 20 | , 24 | headerRight: () => , 25 | headerBackTitle: "Go Back", 26 | headerTransparent: true, 27 | }} 28 | /> 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /app/re-captcha/index.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet } from "react-native"; 2 | import React from "react"; 3 | import CaptchaCard from "@/components/re-captcha/card"; 4 | import { ThemedView } from "@/components/ThemedView"; 5 | import { useSharedValue } from "react-native-reanimated"; 6 | import Tray from "@/components/re-captcha/tray"; 7 | 8 | export default function ReCaptcha() { 9 | const shrinkProgress = useSharedValue(0); 10 | const isVerified = useSharedValue(false); 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | container: { 22 | flex: 1, 23 | alignItems: "center", 24 | justifyContent: "center", 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /components/ui/animated-text.tsx: -------------------------------------------------------------------------------- 1 | import { TextInput } from "react-native"; 2 | import Animated, { 3 | SharedValue, 4 | useAnimatedProps, 5 | } from "react-native-reanimated"; 6 | 7 | import type { TextInputProps } from "react-native"; 8 | 9 | interface TextProps extends Omit { 10 | text: SharedValue | SharedValue; 11 | } 12 | 13 | const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); 14 | 15 | export const AnimatedText = (props: TextProps) => { 16 | const { text, ...rest } = props; 17 | 18 | const animatedProps = useAnimatedProps(() => { 19 | return { 20 | text: String(text.value), 21 | } as any; 22 | }); 23 | 24 | return ( 25 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /components/slack/config.ts: -------------------------------------------------------------------------------- 1 | import { withSpring } from "react-native-reanimated"; 2 | 3 | const PADDING = 8; 4 | const FULL_HEIGHT = 350; 5 | const CLOSED_HEIGHT = 45; 6 | const ANIMATION_DELAY = 280; 7 | const OPENED_PAD = 4; 8 | 9 | const DATA = { 10 | name: "Solarin Johnson", 11 | tabs: 3, 12 | bannerText: 13 | "This is your space. Draft messages, list your to-dos, or keep links and files handy. You can also talk to yourself here, but please bear in mind you'll have to supply both sides of the conversation.", 14 | }; 15 | 16 | const SPRING_CONFIG = { 17 | stiffness: 300, 18 | damping: 30, 19 | mass: 1, 20 | overshootClamping: true, 21 | }; 22 | 23 | const applySpring = (value: number) => { 24 | "worklet"; 25 | return withSpring(value, SPRING_CONFIG); 26 | }; 27 | 28 | export { 29 | PADDING, 30 | FULL_HEIGHT, 31 | CLOSED_HEIGHT, 32 | ANIMATION_DELAY, 33 | DATA, 34 | SPRING_CONFIG, 35 | OPENED_PAD, 36 | applySpring, 37 | }; 38 | -------------------------------------------------------------------------------- /components/Container.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollView, View } from "react-native"; 2 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 3 | import { StyleSheet } from "react-native"; 4 | 5 | const Container = ({ 6 | children, 7 | noScroll, 8 | }: { 9 | children: React.ReactNode; 10 | noScroll?: boolean; 11 | }) => { 12 | const { top, bottom } = useSafeAreaInsets(); 13 | const Wrapper = noScroll ? View : ScrollView; 14 | const style = { paddingTop: top, paddingBottom: bottom }; 15 | 16 | const WrapperProps = noScroll 17 | ? {} 18 | : { 19 | contentContainerStyle: style, 20 | }; 21 | return ( 22 | 27 | {children} 28 | 29 | ); 30 | }; 31 | 32 | const styles = StyleSheet.create({ 33 | container: { 34 | flex: 1, 35 | }, 36 | }); 37 | 38 | export default Container; 39 | -------------------------------------------------------------------------------- /components/stacked-input/switch.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | View, 3 | Text, 4 | Switch as RNSwitch, 5 | SwitchProps, 6 | StyleSheet, 7 | } from "react-native"; 8 | import React, { useState } from "react"; 9 | import { ThemedText } from "../ThemedText"; 10 | 11 | interface CustomSwitchProps extends SwitchProps { 12 | title?: string; 13 | textProps?: React.ComponentProps; 14 | } 15 | 16 | export default function Switch({ 17 | title, 18 | textProps, 19 | ...switchProps 20 | }: CustomSwitchProps) { 21 | 22 | return ( 23 | 24 | {title} 25 | 26 | 29 | 30 | 31 | ); 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flexDirection: "row", 37 | alignItems: "center", 38 | justifyContent: "space-between", 39 | flex: 1, 40 | paddingHorizontal: 12, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /components/ResponsiveText.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from "react"; 2 | import { Text, View, TextProps, Platform } from "react-native"; 3 | import { ThemedText, ThemedTextProps } from "./ThemedText"; 4 | 5 | type ResponsiveTextProps = { 6 | text: string; 7 | baseSize?: number; 8 | } & TextProps & 9 | ThemedTextProps; 10 | 11 | const ResponsiveText: React.FC = ({ 12 | text, 13 | baseSize = 16, 14 | style, 15 | ...props 16 | }) => { 17 | const [width, setWidth] = useState(0); 18 | const fontSize = Math.max( 19 | 12, 20 | Math.min(32, baseSize * (width / 300) - text.length * 0.1) 21 | ); 22 | 23 | return ( 24 | setWidth(e.nativeEvent.layout.width)} 26 | style={{ width: "100%" }} 27 | > 28 | 32 | {text} 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default ResponsiveText; 39 | -------------------------------------------------------------------------------- /components/ui/TextLink.tsx: -------------------------------------------------------------------------------- 1 | import { TouchableOpacity, TouchableOpacityProps } from "react-native"; 2 | import { ThemedText, ThemedTextProps, ThemedTextWrapper } from "../ThemedText"; 3 | import * as WebBrowser from "expo-web-browser"; 4 | import { isValidElement } from "react"; 5 | 6 | interface TextLinkProps extends TouchableOpacityProps { 7 | children: React.ReactNode; 8 | link: string; 9 | textProps?: Omit; 10 | } 11 | 12 | const TextLink = ({ 13 | children, 14 | link, 15 | textProps, 16 | ...touchableProps 17 | }: TextLinkProps) => { 18 | const onPress = () => { 19 | WebBrowser.openBrowserAsync(link); 20 | }; 21 | 22 | const content = 23 | typeof children === "string" ? ( 24 | {children} 25 | ) : isValidElement(children) ? ( 26 | 27 | {children} 28 | 29 | ) : null; 30 | 31 | return ( 32 | 33 | {content} 34 | 35 | ); 36 | }; 37 | 38 | export default TextLink; 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Playground 🎮 2 | 3 | My personal [Expo](https://expo.dev) playground for React Native animations and UI experiments. 4 | 5 | ## Prerequisites 6 | 7 | This project requires a development client - you cannot use Expo Go. 8 | 9 | ## Get Started 10 | 11 | ### 1. Install dependencies 12 | 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | ### 2. Build the development client 18 | 19 | **For iOS Simulator:** 20 | 21 | ```bash 22 | npx expo run:ios 23 | ``` 24 | 25 | **For Android Emulator:** 26 | 27 | ```bash 28 | npx expo run:android 29 | ``` 30 | 31 | ### 3. Start the development server 32 | 33 | ```bash 34 | npx expo start --dev-client 35 | ``` 36 | 37 | ## What's Inside 🧪 38 | 39 | - **Keyboard Controller** - Keyboard handling 40 | - **Gesture Navigation** - Custom gesture experiments 41 | - **Twitter Profile** - Animated profile interactions 42 | - **Threads Pull Refresh** - Pull-to-refresh animations 43 | - **Grok Sidebar** - Animated sidebar with blur effects 44 | 45 | ## Notes 46 | 47 | - ⚠️ Expo Go is NOT supported 48 | - 📱 Development client must be installed first 49 | - 🔄 Rebuild only when native dependencies change 50 | -------------------------------------------------------------------------------- /components/safari-bar/article.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, StyleSheet } from "react-native"; 3 | import { ThemedText } from "../ThemedText"; 4 | 5 | type ArticleProps = { 6 | title: string; 7 | content: string[]; 8 | index: number; 9 | }; 10 | 11 | const Article: React.FC = ({ title, content }) => { 12 | return ( 13 | 14 | 15 | 16 | {title} 17 | 18 | 19 | 20 | {content.map((paragraph, i) => ( 21 | 22 | {paragraph} 23 | 24 | ))} 25 | 26 | 27 | ); 28 | }; 29 | 30 | const styles = StyleSheet.create({ 31 | article: { 32 | marginVertical: 10, 33 | paddingHorizontal: 16, 34 | padding: 10, 35 | borderRadius: 5, 36 | }, 37 | section: { 38 | marginTop: 10, 39 | gap: 20, 40 | }, 41 | content: { 42 | opacity: 0.8, 43 | }, 44 | }); 45 | 46 | export default Article; 47 | -------------------------------------------------------------------------------- /components/stacked-input/provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, use } from "react"; 2 | import { StyleProp, TextInputProps, ViewStyle } from "react-native"; 3 | import { SharedValue } from "react-native-reanimated"; 4 | 5 | type SharedVal = { 6 | currentIndex: SharedValue; 7 | itemStyles?: StyleProp; 8 | itemProps?: TextInputProps; 9 | maxIndex?: number; 10 | minIndex?: number; 11 | }; 12 | 13 | const StackedInputContext = createContext(undefined); 14 | 15 | export function Provider({ 16 | children, 17 | currentIndex, 18 | itemStyles, 19 | itemProps, 20 | maxIndex, 21 | minIndex, 22 | }: { 23 | children: React.ReactNode; 24 | } & SharedVal) { 25 | return ( 26 | 29 | {children} 30 | 31 | ); 32 | } 33 | 34 | export function useStackedInput(): SharedVal { 35 | const context = use(StackedInputContext); 36 | if (!context) { 37 | throw new Error( 38 | "useStackedInput must be used within a StackedInputProvider" 39 | ); 40 | } 41 | return context; 42 | } 43 | -------------------------------------------------------------------------------- /app/untitled/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import UntitledScreen from "@/components/untitled/screen"; 3 | import Ionicons from "@expo/vector-icons/Ionicons"; 4 | import UntitledButton, { 5 | UntitledButtonWrapper, 6 | } from "@/components/untitled/button"; 7 | import { 8 | UntitledCardMini, 9 | UntitledCardMiniWrapper, 10 | } from "@/components/untitled/card"; 11 | 12 | export default function Index() { 13 | return ( 14 | }}> 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | 24 | const Header = () => { 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /app/slack-liquid-glass/chat.tsx: -------------------------------------------------------------------------------- 1 | import SlackBanner from "@/components/slack/banner"; 2 | import { CLOSED_HEIGHT } from "@/components/slack/config"; 3 | import MessageBar from "@/components/slack/message-bar"; 4 | import MessageBox from "@/components/slack/message-box"; 5 | import { ThemedView } from "@/components/ThemedView"; 6 | import { StyleSheet, ScrollView, View } from "react-native"; 7 | import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; 8 | 9 | const MSG_BOX_HEIGHT = 60; 10 | 11 | export default function Index() { 12 | return ( 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | 29 | const styles = StyleSheet.create({ 30 | container: { 31 | flex: 1, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /components/slack/banner.tsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet } from "react-native"; 2 | import React from "react"; 3 | import { Image } from "expo-image"; 4 | import { ThemedText } from "../ThemedText"; 5 | import { DATA } from "./config"; 6 | 7 | export default function SlackBanner() { 8 | return ( 9 | 10 | 14 | 15 | {DATA.name} 16 | 17 | 18 | {DATA.bannerText} 19 | 20 | 21 | ); 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | container: { 26 | padding: 16, 27 | // backgroundColor: "lightgray", 28 | }, 29 | image: { 30 | width: 54, 31 | aspectRatio: 1, 32 | borderRadius: 12, 33 | borderCurve: "continuous", 34 | }, 35 | title: { 36 | fontSize: 21, 37 | marginVertical: 12, 38 | fontWeight: "700", 39 | letterSpacing: -0.2, 40 | }, 41 | sub: { 42 | fontSize: 17, 43 | marginVertical: 8, 44 | lineHeight: 23, 45 | opacity: 0.8, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /components/untitled/button.tsx: -------------------------------------------------------------------------------- 1 | import { View, Pressable, StyleSheet } from "react-native"; 2 | import React, { ReactElement } from "react"; 3 | import { ThemedTextWrapper } from "../ThemedText"; 4 | import { useThemeColor } from "@/hooks/useThemeColor"; 5 | import PressableBounce from "../PresableBounce"; 6 | 7 | export default function UntitledButton({ 8 | children, 9 | onPress, 10 | }: { 11 | children?: ReactElement; 12 | onPress?: () => void; 13 | }) { 14 | const text = useThemeColor("text"); 15 | return ( 16 | 20 | {children && {children}} 21 | 22 | ); 23 | } 24 | 25 | export const UntitledButtonWrapper = ({ 26 | children, 27 | }: { 28 | children?: React.ReactNode; 29 | }) => { 30 | return {children}; 31 | }; 32 | 33 | const styles = StyleSheet.create({ 34 | button: { 35 | borderRadius: 16, 36 | width: 44, 37 | height: 44, 38 | alignItems: "center", 39 | justifyContent: "center", 40 | borderCurve: "continuous", 41 | }, 42 | wrapper: { 43 | flexDirection: "row", 44 | gap: 10, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /app/slack-liquid-glass/index.ios.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedTextWrapper } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { GlassView } from "expo-glass-effect"; 4 | import { Link } from "expo-router"; 5 | import { StyleSheet } from "react-native"; 6 | import { SafeAreaView } from "react-native-safe-area-context"; 7 | 8 | export default function Index() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | Open Chat 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | 24 | const styles = StyleSheet.create({ 25 | container: { 26 | flex: 1, 27 | justifyContent: "center", 28 | alignItems: "center", 29 | }, 30 | text: { 31 | padding: 16, 32 | textAlign: "center", 33 | width: 280, 34 | fontSize: 16, 35 | }, 36 | glass: { 37 | borderRadius: 40, 38 | alignItems: "center", 39 | justifyContent: "center", 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /components/ui/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Pressable, StyleSheet, ViewStyle, TextStyle } from "react-native"; 3 | import { ThemedText, ThemedTextProps } from "../ThemedText"; 4 | 5 | interface ButtonProps { 6 | title: string; 7 | onPress?: () => void; 8 | style?: ViewStyle; 9 | textStyle?: TextStyle; 10 | bgcolor?: string; 11 | color?: string; 12 | textProps?: ThemedTextProps; 13 | } 14 | 15 | const Button: React.FC = ({ 16 | title, 17 | onPress, 18 | style, 19 | textStyle, 20 | bgcolor = "#007bff", 21 | color = "#fff", 22 | textProps, 23 | }) => { 24 | return ( 25 | 29 | 34 | {title} 35 | 36 | 37 | ); 38 | }; 39 | 40 | const styles = StyleSheet.create({ 41 | button: { 42 | padding: 10, 43 | alignItems: "center", 44 | justifyContent: "center", 45 | backgroundColor: "#007bff", 46 | borderRadius: 50, 47 | borderCurve: "continuous", 48 | }, 49 | text: { 50 | color: "#fff", 51 | fontSize: 16, 52 | }, 53 | }); 54 | 55 | export default Button; 56 | -------------------------------------------------------------------------------- /components/stacked-input/trigger.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | ReactElement, 3 | cloneElement, 4 | ComponentProps, 5 | isValidElement, 6 | } from "react"; 7 | import { Pressable, PressableProps } from "react-native"; 8 | import { useStackedInput } from "./provider"; 9 | 10 | interface TriggerProps extends Omit { 11 | type: "next" | "previous"; 12 | asChild?: boolean; 13 | children: ReactElement>; 14 | } 15 | 16 | export default function Trigger({ 17 | type, 18 | asChild, 19 | children, 20 | ...props 21 | }: TriggerProps) { 22 | const { currentIndex, minIndex = 0, maxIndex = 1 } = useStackedInput(); 23 | 24 | const handlePress = () => { 25 | if (type === "next") { 26 | if (currentIndex.value < maxIndex) { 27 | currentIndex.value += 1; 28 | } 29 | } else { 30 | if (currentIndex.value > minIndex) { 31 | currentIndex.value -= 1; 32 | } 33 | } 34 | }; 35 | 36 | if (asChild && isValidElement(children)) { 37 | return cloneElement(children, { 38 | onPress: () => { 39 | (children.props as { onPress?: () => void }).onPress?.(); 40 | handlePress(); 41 | }, 42 | }); 43 | } 44 | 45 | return ( 46 | 47 | {children} 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /app/shared-option/expanded.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Pressable } from "react-native"; 2 | import React from "react"; 3 | import Animated from "react-native-reanimated"; 4 | import { router } from "expo-router"; 5 | import { SHARED_DATA } from "@/constants"; 6 | import { Image } from "expo-image"; 7 | 8 | const AnimatedImage = Animated.createAnimatedComponent(Image); 9 | 10 | export default function Index() { 11 | const colors = ["red", "blue", "green", "yellow"]; 12 | const IMAGES = SHARED_DATA.apps.map((app) => app.logo); 13 | 14 | return ( 15 | { 18 | router.back(); 19 | }} 20 | > 21 | {IMAGES.map((image, index) => ( 22 | 30 | ))} 31 | 32 | ); 33 | } 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | flex: 1, 38 | flexDirection: "column", 39 | justifyContent: "center", 40 | gap: 24, 41 | paddingHorizontal: 24, 42 | }, 43 | box: { 44 | width: 80, 45 | height: 80, 46 | borderRadius: 40, 47 | overflow: "hidden", 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /app/untitled/[id].tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { router } from "expo-router"; 3 | import UntitledScreen from "@/components/untitled/screen"; 4 | import Ionicons from "@expo/vector-icons/Ionicons"; 5 | import UntitledButton, { 6 | UntitledButtonWrapper, 7 | } from "@/components/untitled/button"; 8 | import { UntitledCardLarge } from "@/components/untitled/card"; 9 | 10 | export default function Index() { 11 | return ( 12 | }} 14 | barProps={{ type: "fill" }} 15 | > 16 | 17 | 18 | ); 19 | } 20 | 21 | const Header = () => { 22 | const goBack = () => { 23 | router.back(); 24 | }; 25 | return ( 26 | <> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /components/ProgressiveFade.tsx: -------------------------------------------------------------------------------- 1 | import { View, StyleSheet, ViewStyle } from "react-native"; 2 | import { useThemeColor } from "@/hooks/useThemeColor"; 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 4 | import Animated from "react-native-reanimated"; 5 | 6 | type ProgressiveFadeProps = { 7 | direction?: "top" | "bottom"; 8 | height?: number; 9 | style?: ViewStyle | ViewStyle[]; 10 | }; 11 | 12 | export default function ProgressiveFade({ 13 | direction = "top", 14 | height = 64, 15 | style: customStyle, 16 | }: ProgressiveFadeProps) { 17 | const color = useThemeColor("background"); 18 | const insets = useSafeAreaInsets(); 19 | 20 | return ( 21 | 31 | 41 | 42 | ); 43 | } 44 | 45 | const styles = StyleSheet.create({ 46 | container: { 47 | width: "100%", 48 | position: "absolute", 49 | left: 0, 50 | right: 0, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /components/ThemedView.tsx: -------------------------------------------------------------------------------- 1 | import { View, type ViewProps } from "react-native"; 2 | 3 | import { useThemeColor } from "@/hooks/useThemeColor"; 4 | import { Colors } from "@/constants/Colors"; 5 | import { cloneElement } from "react"; 6 | 7 | export type ThemedViewProps = ViewProps & { 8 | lightColor?: string; 9 | darkColor?: string; 10 | invert?: boolean; 11 | colorName?: keyof typeof Colors.light & keyof typeof Colors.dark; 12 | }; 13 | 14 | export function ThemedView({ 15 | style, 16 | lightColor, 17 | darkColor, 18 | invert, 19 | colorName = "background", 20 | ...otherProps 21 | }: ThemedViewProps) { 22 | const backgroundColor = useThemeColor(invert ? "text" : colorName, { 23 | light: lightColor, 24 | dark: darkColor, 25 | }); 26 | 27 | return ; 28 | } 29 | 30 | export function ThemedViewWrapper({ 31 | children, 32 | lightColor, 33 | darkColor, 34 | invert = false, 35 | colorName = "background", 36 | style, 37 | ...rest 38 | }: ThemedViewProps & { children: React.ReactElement }) { 39 | const backgroundColor = useThemeColor(invert ? "text" : colorName, { 40 | light: lightColor, 41 | dark: darkColor, 42 | }); 43 | 44 | const combinedStyle = [{ backgroundColor }, style]; 45 | 46 | return cloneElement(children, { 47 | style: [(children.props as any).style ?? {}, ...combinedStyle], 48 | ...rest, 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /components/safari-bar/header.tsx: -------------------------------------------------------------------------------- 1 | import { View, TouchableOpacity, StyleSheet } from "react-native"; 2 | import { Ionicons } from "@expo/vector-icons"; 3 | import { ThemedText, ThemedTextWrapper } from "../ThemedText"; 4 | import { useThemeColor } from "@/hooks/useThemeColor"; 5 | import ResponsiveText from "../ResponsiveText"; 6 | 7 | interface HeaderProps { 8 | title: string; 9 | content: string; 10 | onBackPress: () => void; 11 | } 12 | 13 | const Header = ({ title, content, onBackPress }: HeaderProps) => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | {content} 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flexDirection: "column", 31 | alignItems: "center", 32 | paddingHorizontal: 16, 33 | paddingVertical: 12, 34 | gap: 4, 35 | }, 36 | nav: { 37 | flexDirection: "row", 38 | width: "100%", 39 | paddingVertical: 16, 40 | alignItems: "center", 41 | justifyContent: "space-between", 42 | }, 43 | head: { 44 | flexDirection: "column", 45 | width: "100%", 46 | gap: 6, 47 | }, 48 | content: { 49 | paddingVertical: 5, 50 | }, 51 | title: { 52 | fontSize: 28, 53 | }, 54 | }); 55 | 56 | export default Header; 57 | -------------------------------------------------------------------------------- /app/threads-pull-refresh/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import React from "react"; 3 | import RefreshLogo from "@/components/RefreshLogo"; 4 | import Animated from "react-native-reanimated"; 5 | import { 6 | useSharedValue, 7 | useAnimatedScrollHandler, 8 | } from "react-native-reanimated"; 9 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 10 | 11 | const path = 12 | "M19.25 8.50488C17.6729 2.63804 12.25 3.00452 12.25 3.00452C12.25 3.00452 4.75 2.50512 4.75 12C4.75 21.4949 12.25 20.9955 12.25 20.9955C12.25 20.9955 16.7077 21.2924 18.75 17.0782C19.4167 15.2204 19.25 11.5049 12.75 11.5049C12.75 11.5049 9.75 11.5049 9.75 14.0049C9.75 14.9812 10.75 16.0049 12.25 16.0049C13.75 16.0049 15.4212 14.9777 15.75 13.0049C16.75 7.00488 11.25 6.50488 9.75 9.00488"; 13 | 14 | export default function ThreadPullRefresh() { 15 | const scrollY = useSharedValue(0); 16 | const { top } = useSafeAreaInsets(); 17 | 18 | const scrollHandler = useAnimatedScrollHandler({ 19 | onScroll: (event) => { 20 | scrollY.value = event.contentOffset.y; 21 | }, 22 | }); 23 | 24 | return ( 25 | 30 | 31 | 32 | ); 33 | } 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | flex: 1, 38 | padding: 16, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "[my-app]", 4 | "slug": "my-app", 5 | "owner": "solarin", 6 | "version": "1.0.0", 7 | "orientation": "portrait", 8 | "icon": "./assets/images/icon.png", 9 | "scheme": "myapp", 10 | "userInterfaceStyle": "automatic", 11 | "newArchEnabled": true, 12 | "ios": { 13 | "supportsTablet": true, 14 | "bundleIdentifier": "com.myapp.solarin" 15 | }, 16 | "android": { 17 | "adaptiveIcon": { 18 | "foregroundImage": "./assets/images/adaptive-icon.png", 19 | "backgroundColor": "#181818" 20 | }, 21 | "edgeToEdgeEnabled": true, 22 | "package": "com.myapp.solarin" 23 | }, 24 | "web": { 25 | "bundler": "metro", 26 | "output": "static", 27 | "favicon": "./assets/images/dp.png" 28 | }, 29 | "plugins": [ 30 | "expo-router", 31 | [ 32 | "expo-splash-screen", 33 | { 34 | "image": "./assets/images/splash-icon-light.png", 35 | "imageWidth": 250, 36 | "backgroundColor": "#F5F5F7", 37 | "dark": { 38 | "image": "./assets/images/splash-icon-dark.png", 39 | "backgroundColor": "#181818" 40 | } 41 | } 42 | ], 43 | "expo-web-browser" 44 | ], 45 | "experiments": { 46 | "typedRoutes": true 47 | }, 48 | "extra": { 49 | "router": { 50 | "origin": false 51 | }, 52 | "eas": { 53 | "projectId": "9efc0bb6-df7f-44a9-9544-9e52e73553bb" 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /components/PresableBounce.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { GestureResponderEvent, Pressable, PressableProps } from "react-native"; 3 | import Animated, { 4 | Easing, 5 | useAnimatedStyle, 6 | useSharedValue, 7 | withSpring, 8 | withTiming, 9 | } from "react-native-reanimated"; 10 | 11 | const AnimatedPressable = Animated.createAnimatedComponent(Pressable); 12 | const easing = Easing.inOut(Easing.ease); 13 | 14 | interface PressableBounceProps extends PressableProps { 15 | children: React.ReactNode; 16 | bounceScale?: number; 17 | duration?: number; 18 | } 19 | 20 | const PressableBounce: React.FC = ({ 21 | children, 22 | bounceScale = 0.95, 23 | duration = 150, 24 | ...props 25 | }) => { 26 | const scale = useSharedValue(1); 27 | 28 | const animatedStyle = useAnimatedStyle(() => { 29 | return { 30 | transform: [{ scale: scale.value }], 31 | }; 32 | }); 33 | 34 | const handlePressIn = (event: GestureResponderEvent) => { 35 | scale.value = withTiming(bounceScale, { 36 | duration, 37 | easing, 38 | }); 39 | props.onPressIn && props.onPressIn(event); 40 | }; 41 | 42 | const handlePressOut = (event: GestureResponderEvent) => { 43 | scale.value = withSpring(1); 44 | props.onPressOut && props.onPressOut(event); 45 | }; 46 | 47 | return ( 48 | 54 | {children} 55 | 56 | ); 57 | }; 58 | 59 | export default PressableBounce; 60 | -------------------------------------------------------------------------------- /components/HeadComponent.tsx: -------------------------------------------------------------------------------- 1 | import Head from "expo-router/head"; 2 | import { Platform } from "react-native"; 3 | 4 | const isWeb = Platform.OS === "web"; 5 | 6 | export default function HeadComponent() { 7 | if (!isWeb) return null; 8 | 9 | return ( 10 | 11 | My App 12 | 16 | 17 | 18 | 19 | {/* Open Graph / Facebook */} 20 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | {/* Twitter */} 34 | 38 | 39 | 40 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /components/ui/glassy-view.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, View } from "react-native"; 2 | import { GlassContainer, GlassView } from "expo-glass-effect"; 3 | import { Image } from "expo-image"; 4 | import { ThemedText } from "../ThemedText"; 5 | 6 | export default function GlassViewComponent() { 7 | return ( 8 | 9 | 15 | 16 | 22 | 27 | 32 | 33 | 34 | ); 35 | } 36 | 37 | const styles = StyleSheet.create({ 38 | container: { 39 | flex: 1, 40 | }, 41 | backgroundImage: { 42 | ...StyleSheet.absoluteFillObject, 43 | width: "100%", 44 | height: "100%", 45 | }, 46 | containerStyle: { 47 | position: "absolute", 48 | top: 200, 49 | left: 50, 50 | width: 250, 51 | height: 100, 52 | flexDirection: "row", 53 | alignItems: "center", 54 | gap: 10, 55 | }, 56 | glass1: { 57 | width: 60, 58 | height: 60, 59 | borderRadius: 30, 60 | }, 61 | glass2: { 62 | width: 50, 63 | height: 50, 64 | borderRadius: 25, 65 | }, 66 | glass3: { 67 | width: 40, 68 | height: 40, 69 | borderRadius: 20, 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /constants/Colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode. 3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. 4 | */ 5 | 6 | const tintColorLight = "#0a7ea4"; 7 | const tintColorDark = "#fff"; 8 | 9 | const globalColors = { 10 | systemBlue: "#2D6AF6", 11 | appleRed: "#FF3B30", 12 | }; 13 | 14 | export const Colors = { 15 | light: { 16 | waBg: "#F4F4F4", 17 | text: "#181818", 18 | background: "#F5F5F7", 19 | barColor: "#FFFFFF", 20 | waCard: "#FFFFFF", 21 | card: "#F5F5F7", 22 | tint: tintColorLight, 23 | untitledBg: "#FFFFFF", 24 | untitledGradient1: "#D55FDC", 25 | untitledGradient2: "#F5C1DB", 26 | untitledBarBg: "#222222", 27 | slackBg: "#FFFFFF", 28 | slackText: "#1C1C1C", 29 | safariBg: "#F2F2F6", 30 | captchaBg: "#FFFFFF", 31 | captchaCardBg: "#F7F7F7", 32 | captchaCheckboxBg: "#272727", 33 | captchaCheckboxInitialBg: "#FFFFFF", 34 | popUpCardBg: "#FFFFFF", 35 | cardBg3D: "#E2E2E8", 36 | ...globalColors, 37 | }, 38 | dark: { 39 | waBg: "#000000", 40 | text: "#F5F5F7", 41 | background: "#121212", 42 | card: "#121212", 43 | barColor: "#1A1A1A", 44 | waCard: "#171718", 45 | tint: tintColorDark, 46 | untitledBg: "#191918", 47 | untitledGradient1: "#D55FDC", 48 | untitledGradient2: "#F5C1DB", 49 | untitledBarBg: "#323232", 50 | slackBg: "#1B1D21", 51 | slackText: "#D2D2D4", 52 | safariBg: "#1C1C1E", 53 | captchaBg: "#0E0E0E", 54 | captchaCardBg: "#181818", 55 | captchaCheckboxBg: "#343434", 56 | captchaCheckboxInitialBg: "#656565", 57 | popUpCardBg: "#1A1A1A", 58 | cardBg3D: "#252528", 59 | ...globalColors, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /app/gesture-menu/index.tsx: -------------------------------------------------------------------------------- 1 | import GestureMenu, { GestureMenuItem } from "@/components/GestureMenu"; 2 | import { Feedback } from "@/functions"; 3 | import { Lock, Search } from "lucide-react-native"; 4 | import { View, Text, Alert } from "react-native"; 5 | import { SafeAreaView } from "react-native-safe-area-context"; 6 | 7 | export default function GestureMenuScreen() { 8 | const onPress = (label: string) => { 9 | // Alert.alert(`Pressed ${label}`); 10 | }; 11 | return ( 12 | 15 | {(() => { 16 | const items = [ 17 | { label: "Option 1", icon: }, 18 | { label: "Option 2" }, 19 | { label: "Option 3" }, 20 | // { label: "Option 4" }, 21 | // { label: "Option 5" }, 22 | ]; 23 | 24 | return ( 25 | , 43 | }} 44 | roundedIndicator={false} 45 | > 46 | {items.map(({ label, icon }) => ( 47 | onPress(label)} 51 | icon={icon} 52 | /> 53 | ))} 54 | 55 | ); 56 | })()} 57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /app/wa-status/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { Stack, useRouter } from "expo-router"; 3 | import { BlurView } from "expo-blur"; 4 | import { Pressable, StyleSheet } from "react-native"; 5 | import { useThemeColor } from "@/hooks/useThemeColor"; 6 | 7 | export default function Layout() { 8 | const bg = useThemeColor("waBg"); 9 | 10 | return ( 11 | , 16 | headerBackground: () => ( 17 | 18 | ), 19 | headerTransparent: true, 20 | contentStyle: { backgroundColor: bg }, 21 | }} 22 | > 23 | 29 | 35 | , 41 | headerBackground: () => ( 42 | 43 | ), 44 | headerTransparent: true, 45 | contentStyle: { backgroundColor: bg }, 46 | }} 47 | /> 48 | 49 | ); 50 | } 51 | const HeaderRight = ({ 52 | text = "Edit", 53 | width = 50, 54 | }: { 55 | text?: string; 56 | width?: number; 57 | }) => { 58 | const router = useRouter(); 59 | 60 | return ( 61 | 70 | {text} 71 | 72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /constants/index.ts: -------------------------------------------------------------------------------- 1 | export const SHARED_DATA = { 2 | text: "I've found the top 5 music apps that match your preferences best.", 3 | apps: [ 4 | { 5 | name: "Spotify", 6 | logo: "https://storage.googleapis.com/pr-newsroom-wp/1/2023/05/Spotify_Primary_Logo_RGB_Green.png", 7 | description: 8 | "Listen to millions of songs and podcasts with tailored recommendations and custom playlists for a personalized music experience.", 9 | platforms: ["iOS", "Android"], 10 | }, 11 | { 12 | name: "Apple Music", 13 | logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Apple_Music_icon.svg/120px-Apple_Music_icon.svg.png", 14 | description: 15 | "Access over 100 million songs with spatial audio, lossless quality, and exclusive releases from your favorite artists worldwide.", 16 | platforms: ["iOS", "Android", "Web"], 17 | }, 18 | { 19 | name: "YouTube Music", 20 | logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Youtube_Music_icon.svg/120px-Youtube_Music_icon.svg.png", 21 | description: 22 | "Discover music videos, live performances, official tracks, and remixes from your favorite artists all in one streaming platform.", 23 | platforms: ["iOS", "Android", "Web"], 24 | }, 25 | { 26 | name: "Tidal", 27 | logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTtk-KVZgKJ30k8DDSqdxmMGDy6z11515g-oA&s", 28 | description: 29 | "Experience high-fidelity sound quality with lossless audio, exclusive content, and direct artist support through this premium streaming service.", 30 | platforms: ["iOS", "Android"], 31 | }, 32 | { 33 | name: "SoundCloud", 34 | logo: "https://icon-icons.com/download-file?file=https%3A%2F%2Fimages.icon-icons.com%2F2972%2FPNG%2F512%2Fsoundcloud_logo_icon_186893.png&id=186893&pack_or_individual=pack", 35 | description: 36 | "Explore indie tracks, emerging artists, remixes, and underground music from around the world in this creator-focused platform.", 37 | platforms: ["iOS", "Android", "Web"], 38 | }, 39 | ], 40 | }; 41 | -------------------------------------------------------------------------------- /components/untitled/header.ios.tsx: -------------------------------------------------------------------------------- 1 | import { View, StyleSheet, StyleProp, ViewStyle } from "react-native"; 2 | import React from "react"; 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; 4 | import { ThemedText } from "../ThemedText"; 5 | import Animated, { 6 | interpolate, 7 | useAnimatedStyle, 8 | } from "react-native-reanimated"; 9 | import { useScreenAnimation } from "react-native-screen-transitions"; 10 | 11 | export interface UntitledHeaderProps { 12 | title?: string; 13 | children?: React.ReactNode; 14 | style?: StyleProp; 15 | contentStyle?: StyleProp; 16 | } 17 | 18 | export default function UntitledHeader({ 19 | title, 20 | style, 21 | contentStyle, 22 | children, 23 | }: UntitledHeaderProps) { 24 | const { top } = useSafeAreaInsets(); 25 | const props = useScreenAnimation(); 26 | 27 | const headerAnimatedStyle = useAnimatedStyle(() => { 28 | const { 29 | progress, 30 | layouts: { 31 | screen: { width }, 32 | }, 33 | } = props.value; 34 | 35 | return { 36 | opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]), 37 | transform: [ 38 | { 39 | translateX: interpolate(progress, [0, 1, 2], [-width, 0, width]), 40 | }, 41 | ], 42 | }; 43 | }); 44 | 45 | return ( 46 | 56 | 57 | {title && ( 58 | 59 | {title} 60 | 61 | )} 62 | {children} 63 | 64 | 65 | ); 66 | } 67 | 68 | const styles = StyleSheet.create({ 69 | container: { 70 | padding: 16, 71 | paddingHorizontal: 20, 72 | }, 73 | header: { 74 | flexDirection: "row", 75 | alignItems: "center", 76 | justifyContent: "space-between", 77 | }, 78 | title: { 79 | fontSize: 23, 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /components/ThemedText.tsx: -------------------------------------------------------------------------------- 1 | import { Text, type TextProps, StyleSheet } from "react-native"; 2 | import { useThemeColor } from "@/hooks/useThemeColor"; 3 | import { cloneElement, ReactElement } from "react"; 4 | import { Colors } from "@/constants/Colors"; 5 | 6 | export type ThemedTextProps = TextProps & { 7 | type?: 8 | | "default" 9 | | "title" 10 | | "defaultSemiBold" 11 | | "subtitle" 12 | | "link" 13 | | "bold" 14 | | "regular"; 15 | colorName?: keyof typeof Colors.light & keyof typeof Colors.dark; 16 | }; 17 | 18 | export function ThemedText({ 19 | style, 20 | type = "default", 21 | colorName = "text", 22 | ...rest 23 | }: ThemedTextProps) { 24 | const color = useThemeColor(colorName); 25 | const variantKey = type as keyof typeof styles; 26 | 27 | return ( 28 | 32 | ); 33 | } 34 | 35 | export function ThemedTextWrapper({ 36 | children, 37 | type = "default", 38 | colorName = "text", 39 | style, 40 | ignoreStyle = true, 41 | ...rest 42 | }: ThemedTextProps & { children: ReactElement; ignoreStyle?: boolean }) { 43 | const color = useThemeColor(colorName); 44 | const variantKey = type as keyof typeof styles; 45 | 46 | const combinedStyle = [ 47 | { color, fontFamily: "InterMedium" }, 48 | !ignoreStyle && styles[variantKey], 49 | style, 50 | ]; 51 | 52 | return cloneElement(children, { 53 | style: [(children.props as any).style ?? {}, ...combinedStyle], 54 | color: color, 55 | ...rest, 56 | }); 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | default: { 61 | fontSize: 16, 62 | }, 63 | defaultSemiBold: { 64 | fontSize: 17, 65 | fontFamily: "InterSemiBold", 66 | }, 67 | title: { 68 | fontFamily: "InterSemiBold", 69 | fontSize: 20, 70 | }, 71 | subtitle: { 72 | fontSize: 15, 73 | }, 74 | link: { 75 | lineHeight: 30, 76 | fontSize: 16, 77 | color: "#0a7ea4", 78 | }, 79 | bold: { 80 | fontSize: 16, 81 | fontFamily: "InterBold", 82 | }, 83 | regular: { 84 | fontSize: 16, 85 | fontFamily: "InterRegular", 86 | }, 87 | }); 88 | -------------------------------------------------------------------------------- /app/untitled/_layout.ios.tsx: -------------------------------------------------------------------------------- 1 | import { useThemeColor } from "@/hooks/useThemeColor"; 2 | import type { 3 | ParamListBase, 4 | StackNavigationState, 5 | } from "@react-navigation/native"; 6 | import { withLayoutContext } from "expo-router"; 7 | import { interpolate } from "react-native-reanimated"; 8 | import Transition, { 9 | createNativeStackNavigator, 10 | type NativeStackNavigationEventMap, 11 | type NativeStackNavigationOptions, 12 | } from "react-native-screen-transitions"; 13 | 14 | const { Navigator } = createNativeStackNavigator(); 15 | 16 | export const Stack = withLayoutContext< 17 | NativeStackNavigationOptions, 18 | typeof Navigator, 19 | StackNavigationState, 20 | NativeStackNavigationEventMap 21 | >(Navigator); 22 | 23 | const SPRING_CONFIG = { 24 | damping: 50, 25 | stiffness: 400, 26 | mass: 1, 27 | overshootClamping: true, 28 | restDisplacementThreshold: 0.0001, 29 | restSpeedThreshold: 0.0001, 30 | }; 31 | 32 | // const TIMING_CONFIG = { duration: 100, easing: Easing.inOut(Easing.ease) }; 33 | // const SPRING_CONFIG = Transition.specs.DefaultSpec; 34 | 35 | export default function Layout() { 36 | const bg = useThemeColor("untitledBg"); 37 | return ( 38 | 43 | 47 | { 59 | "worklet"; 60 | 61 | const x = interpolate(progress, [0, 1, 2], [width, 0, -width]); 62 | return { 63 | contentStyle: { 64 | transform: [{ translateX: x }], 65 | }, 66 | }; 67 | }, 68 | transitionSpec: { 69 | close: SPRING_CONFIG, 70 | open: SPRING_CONFIG, 71 | }, 72 | }} 73 | /> 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /components/notify/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | useContext, 4 | ReactNode, 5 | useState, 6 | useEffect, 7 | } from "react"; 8 | import { View, StyleSheet } from "react-native"; 9 | import Banner from "./banner"; 10 | import { MessageType, NotifyContextType } from "./type"; 11 | import { useSharedValue } from "react-native-reanimated"; 12 | import { useKeyboardHandler } from "react-native-keyboard-controller"; 13 | 14 | const NotifyContext = createContext(null); 15 | 16 | let messageId = 0; 17 | const MAX_MESSAGES = 2; 18 | 19 | export const NotifyProvider = ({ children }: { children: ReactNode }) => { 20 | const [messages, setMessages] = useState([]); 21 | const messageCount = useSharedValue(0); 22 | const progress = useSharedValue(0); 23 | const height = useSharedValue(0); 24 | 25 | const notify: NotifyContextType["notify"] = (msg, options) => { 26 | messageCount.value = messageCount.value + 1; 27 | setMessages((prev) => { 28 | const newMessage = { id: messageId++, text: msg, options }; 29 | let newMessages = [...prev, newMessage]; 30 | 31 | if (newMessages.length > MAX_MESSAGES) { 32 | newMessages = newMessages.slice(newMessages.length - MAX_MESSAGES); 33 | } 34 | 35 | return newMessages; 36 | }); 37 | }; 38 | 39 | useKeyboardHandler( 40 | { 41 | onMove: (e) => { 42 | "worklet"; 43 | progress.value = e.progress; 44 | height.value = e.height; 45 | }, 46 | }, 47 | 48 | [] 49 | ); 50 | 51 | return ( 52 | 53 | {children} 54 | 55 | {messages.map((message, index) => ( 56 | 63 | ))} 64 | 65 | 66 | ); 67 | }; 68 | 69 | export const useNotify = () => { 70 | const ctx = useContext(NotifyContext); 71 | if (!ctx) throw new Error("useNotify must be used inside NotifyProvider"); 72 | return ctx; 73 | }; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "main": "expo-router/entry", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web", 10 | "lint": "expo lint" 11 | }, 12 | "dependencies": { 13 | "@expo/metro-runtime": "~6.1.2", 14 | "@expo/ui": "~0.2.0-beta.7", 15 | "@expo/vector-icons": "^15.0.2", 16 | "@react-native-masked-view/masked-view": "^0.3.2", 17 | "@react-navigation/bottom-tabs": "^7.2.0", 18 | "@react-navigation/drawer": "^7.3.9", 19 | "@react-navigation/native": "^7.0.14", 20 | "expo": "54.0.21", 21 | "expo-blur": "~15.0.7", 22 | "expo-constants": "~18.0.8", 23 | "expo-dev-client": "~6.0.16", 24 | "expo-drawpad": "^0.2.0", 25 | "expo-font": "~14.0.8", 26 | "expo-glass-effect": "~0.1.5", 27 | "expo-haptics": "~15.0.7", 28 | "expo-image": "~3.0.10", 29 | "expo-linear-gradient": "~15.0.7", 30 | "expo-linking": "~8.0.8", 31 | "expo-router": "~6.0.14", 32 | "expo-splash-screen": "~31.0.10", 33 | "expo-status-bar": "~3.0.8", 34 | "expo-symbols": "~1.0.7", 35 | "expo-system-ui": "~6.0.8", 36 | "expo-web-browser": "~15.0.8", 37 | "lucide-react-native": "^0.518.0", 38 | "react": "19.1.0", 39 | "react-dom": "19.1.0", 40 | "react-native": "^0.81.5", 41 | "react-native-gesture-handler": "~2.28.0", 42 | "react-native-keyboard-controller": "1.18.5", 43 | "react-native-reanimated": "^4.2.0", 44 | "react-native-safe-area-context": "~5.6.0", 45 | "react-native-screen-transitions": "^2.3.3", 46 | "react-native-screens": "~4.16.0", 47 | "react-native-svg": "15.12.1", 48 | "react-native-web": "^0.21.0", 49 | "react-native-webview": "13.15.0", 50 | "react-native-worklets": "^0.7.1", 51 | "svg-path-properties": "^1.3.0", 52 | "worklets": "^0.0.1-alpha" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.25.2", 56 | "@types/react": "~19.1.10", 57 | "eslint": "^9.25.0", 58 | "eslint-config-expo": "~10.0.0", 59 | "typescript": "~5.9.2" 60 | }, 61 | "private": true, 62 | "reanimated": { 63 | "staticFeatureFlags": { 64 | "ENABLE_SHARED_ELEMENT_TRANSITIONS": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /components/slack/message-box.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, StyleSheet } from "react-native"; 3 | import { DATA } from "./config"; 4 | import { ThemedText } from "../ThemedText"; 5 | import { Image } from "expo-image"; 6 | import { Link } from "expo-router"; 7 | 8 | const MessageBox = () => { 9 | const saveImage = () => { 10 | console.log("Save image"); 11 | }; 12 | 13 | const handleSharePress = () => { 14 | console.log("Share image"); 15 | }; 16 | 17 | const handleBlockPress = () => { 18 | console.log("Block user"); 19 | }; 20 | 21 | return ( 22 | 23 | 24 | 28 | 29 | 30 | 31 | 36 | {DATA.name} 37 | 38 | 39 | 40 | 44 | 45 | 46 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | flexDirection: "row", 62 | padding: 12, 63 | paddingTop: 24, 64 | gap: 10, 65 | }, 66 | leftContainer: {}, 67 | rightContainer: { 68 | flex: 1, 69 | gap: 2, 70 | }, 71 | profileImage: { 72 | width: 36, 73 | height: 36, 74 | borderRadius: 8, 75 | borderCurve: "continuous", 76 | }, 77 | senderName: { 78 | fontSize: 17, 79 | marginBottom: 5, 80 | }, 81 | messageImage: { 82 | width: "100%", 83 | height: 450, 84 | borderRadius: 8, 85 | borderCurve: "continuous", 86 | }, 87 | }); 88 | 89 | export default MessageBox; 90 | -------------------------------------------------------------------------------- /components/icons/index.tsx: -------------------------------------------------------------------------------- 1 | import Svg, { Path } from "react-native-svg"; 2 | 3 | export interface IconProps { 4 | size?: number; 5 | color?: string; 6 | weight?: number; 7 | } 8 | 9 | export function LogoIcon(props: IconProps) { 10 | const { size = 218, color = "none" } = props; 11 | const width = (size / 218) * 249; 12 | return ( 13 | 14 | 18 | 19 | ); 20 | } 21 | 22 | export const ReloadIcon = (props: IconProps) => { 23 | const { size = 24, color = "none", weight = 2 } = props; 24 | return ( 25 | 26 | 33 | 34 | ); 35 | }; 36 | 37 | export const GrokIcon = (props: IconProps) => { 38 | const { size = 48, color = "none", weight = 2 } = props; 39 | return ( 40 | 41 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StyleSheet, View } from "react-native"; 3 | import Logo from "@/components/ui/Logo"; 4 | import Button from "@/components/ui/Button"; 5 | import { useThemeColor } from "@/hooks/useThemeColor"; 6 | import { SafeAreaView } from "react-native-safe-area-context"; 7 | import { useNavigation } from "expo-router"; 8 | import { DrawerNavigationProp } from "@react-navigation/drawer"; 9 | import TextArea from "@/components/TextArea"; 10 | import { ThemedTextWrapper } from "@/components/ThemedText"; 11 | import { KeyboardAvoidingView } from "react-native-keyboard-controller"; 12 | import Slider from "@/components/Slider"; 13 | 14 | export default function Index() { 15 | const text = useThemeColor("text"); 16 | const bg = useThemeColor("background"); 17 | const navigation = useNavigation>(); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | {/* 26 |