├── nativewind-env.d.ts
├── utilities
├── types.ts
└── themeOptions.ts
├── assets
├── icon.png
├── splash.png
├── favicon.png
├── splash-dark.png
└── adaptive-icon.png
├── tsconfig.json
├── babel.config.js
├── app
├── settings.tsx
├── shop
│ └── _layout.tsx
├── (tabs)
│ ├── index.tsx
│ ├── _layout.tsx
│ ├── profile.tsx
│ └── chat.tsx
└── _layout.tsx
├── metro.config.js
├── global.css
├── tailwind.config.js
├── components
└── CustomText.tsx
├── eas.json
├── .gitignore
├── package.json
├── app.json
└── README.md
/nativewind-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
--------------------------------------------------------------------------------
/utilities/types.ts:
--------------------------------------------------------------------------------
1 | type ThemeOptions = "light" | "dark" | "system"
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkbarBakhshi/RN-Journey/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkbarBakhshi/RN-Journey/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkbarBakhshi/RN-Journey/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkbarBakhshi/RN-Journey/HEAD/assets/splash-dark.png
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AkbarBakhshi/RN-Journey/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: [
5 | ["babel-preset-expo", { jsxImportSource: "nativewind" }],
6 | "nativewind/babel",
7 | ],
8 | };
9 | };
--------------------------------------------------------------------------------
/app/settings.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from 'react-native';
2 |
3 | export default function Settings() {
4 | return (
5 |
6 | Drawer [Settings]
7 |
8 | );
9 | }
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | const { getDefaultConfig } = require("expo/metro-config");
2 | const { withNativeWind } = require('nativewind/metro');
3 |
4 | const config = getDefaultConfig(__dirname)
5 |
6 | module.exports = withNativeWind(config, { input: './global.css' })
--------------------------------------------------------------------------------
/app/shop/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 |
3 | export default function Shop() {
4 | return (
5 |
6 | Drawer [Shop]
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/utilities/themeOptions.ts:
--------------------------------------------------------------------------------
1 | export const MyLightTheme = {
2 | dark: false,
3 | colors: {
4 | primary: "#5f0202",
5 | background: "#ffffff",
6 | card: "rgb(255, 255, 255)",
7 | text: "#5f0202",
8 | border: "#00ff00",
9 | notification: "rgb(55, 222, 58)",
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background: #ffffff;
7 | --foreground: #000000;
8 | --primary: #5f0202;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --background: #000000;
14 | --foreground: #ffffff;
15 | --primary: #0db4f7;
16 | }
17 | }
--------------------------------------------------------------------------------
/app/(tabs)/index.tsx:
--------------------------------------------------------------------------------
1 | import { View } from 'react-native';
2 | import CustomText from '../../components/CustomText';
3 |
4 | export default function Dashboard() {
5 | return (
6 |
7 | Update Tab [Dashboard]
8 |
9 | );
10 | }
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | // NOTE: Update this to include the paths to all of your component files.
4 | content: ["./app/**/*.{js,jsx,ts,tsx}"],
5 | presets: [require("nativewind/preset")],
6 | theme: {
7 | extend: {
8 | colors: {
9 | background: 'var(--background)',
10 | foreground: 'var(--foreground)',
11 | primary:'var(--primary)'
12 | },
13 | },
14 | },
15 | plugins: [],
16 | }
--------------------------------------------------------------------------------
/components/CustomText.tsx:
--------------------------------------------------------------------------------
1 | import { Text, TextProps, StyleProp, TextStyle } from "react-native";
2 | import { ReactNode } from "react";
3 |
4 | interface CustomTextProps extends TextProps {
5 | children: ReactNode;
6 | style?: StyleProp;
7 | }
8 |
9 | const CustomText = ({ children, style, ...props }: CustomTextProps) => {
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | };
16 |
17 | export default CustomText;
18 |
--------------------------------------------------------------------------------
/eas.json:
--------------------------------------------------------------------------------
1 | {
2 | "cli": {
3 | "version": ">= 7.8.2"
4 | },
5 | "build": {
6 | "development": {
7 | "developmentClient": true,
8 | "distribution": "internal"
9 | },
10 | "development-simulator": {
11 | "developmentClient": true,
12 | "distribution": "internal",
13 | "ios": {
14 | "simulator": true
15 | }
16 | },
17 | "preview": {
18 | "distribution": "internal"
19 | },
20 | "production": {}
21 | },
22 | "submit": {
23 | "production": {}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.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 |
11 | # Native
12 | *.orig.*
13 | *.jks
14 | *.p8
15 | *.p12
16 | *.key
17 | *.mobileprovision
18 |
19 | # Metro
20 | .metro-health-check*
21 |
22 | # debug
23 | npm-debug.*
24 | yarn-debug.*
25 | yarn-error.*
26 |
27 | # macOS
28 | .DS_Store
29 | *.pem
30 |
31 | # local env files
32 | .env*.local
33 | .env
34 |
35 | # typescript
36 | *.tsbuildinfo
37 |
38 | google-services.json
39 | GoogleService-Info.plist
40 |
41 | ios
42 | android
--------------------------------------------------------------------------------
/app/(tabs)/_layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FontAwesome from '@expo/vector-icons/FontAwesome';
3 | import { Tabs } from 'expo-router';
4 |
5 | export default function TabLayout() {
6 | return (
7 |
8 |
13 | }}
14 | />
15 |
20 | }}
21 | />
22 | ,
27 | }}
28 | />
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactnativejourney",
3 | "version": "1.0.0",
4 | "main": "expo-router/entry",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "@expo-google-fonts/exo": "^0.2.3",
13 | "@react-native-async-storage/async-storage": "1.23.1",
14 | "@react-navigation/drawer": "^6.6.15",
15 | "@rneui/base": "^4.0.0-rc.7",
16 | "@rneui/themed": "^4.0.0-rc.8",
17 | "expo": "^51.0.8",
18 | "expo-constants": "~16.0.1",
19 | "expo-dev-client": "~4.0.14",
20 | "expo-font": "~12.0.5",
21 | "expo-linking": "~6.3.1",
22 | "expo-router": "~3.5.14",
23 | "expo-status-bar": "~1.12.1",
24 | "expo-system-ui": "~3.0.4",
25 | "nativewind": "^4.0.1",
26 | "react": "18.2.0",
27 | "react-native": "0.74.1",
28 | "react-native-gesture-handler": "~2.16.1",
29 | "react-native-keyboard-aware-scroll-view": "^0.9.5",
30 | "react-native-reanimated": "~3.10.1",
31 | "react-native-safe-area-context": "4.10.1",
32 | "react-native-screens": "3.31.1",
33 | "tailwindcss": "^3.4.3"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.20.0",
37 | "@types/react": "~18.2.45",
38 | "typescript": "~5.3.3"
39 | },
40 | "private": true
41 | }
42 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "RNJourney",
4 | "slug": "RNJourney",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "automatic",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff",
13 | "dark": {
14 | "image": "./assets/splash.png",
15 | "resizeMode": "contain",
16 | "backgroundColor": "#000000"
17 | }
18 | },
19 | "assetBundlePatterns": [
20 | "**/*"
21 | ],
22 | "ios": {
23 | "supportsTablet": true,
24 | "bundleIdentifier": "com.rnjourney.RNJourney",
25 | "splash": {
26 | "image": "./assets/splash.png",
27 | "resizeMode": "contain",
28 | "backgroundColor": "#ffffff",
29 | "dark": {
30 | "image": "./assets/splash.png",
31 | "resizeMode": "contain",
32 | "backgroundColor": "#000000"
33 | }
34 | }
35 | },
36 | "android": {
37 | "adaptiveIcon": {
38 | "foregroundImage": "./assets/adaptive-icon.png",
39 | "backgroundColor": "#ffffff"
40 | },
41 | "package": "com.rnjourney.RNJourney",
42 | "splash": {
43 | "image": "./assets/splash.png",
44 | "resizeMode": "contain",
45 | "backgroundColor": "#ffffff",
46 | "dark": {
47 | "image": "./assets/splash-dark.png",
48 | "resizeMode": "contain",
49 | "backgroundColor": "#000000"
50 | }
51 | }
52 | },
53 | "web": {
54 | "bundler": "metro",
55 | "favicon": "./assets/favicon.png"
56 | },
57 | "plugins": [
58 | "expo-router",
59 | "expo-font"
60 | ],
61 | "scheme": "rnjourney",
62 | "extra": {
63 | "router": {
64 | "origin": false
65 | },
66 | "eas": {
67 | "projectId": "f3898dce-f56c-413e-8b84-dde1a0323b21"
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/(tabs)/profile.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 | import { CheckBox } from "@rneui/themed";
3 | import { useEffect, useState } from "react";
4 |
5 | import { useColorScheme } from "nativewind";
6 | import { MyLightTheme } from "../../utilities/themeOptions";
7 |
8 | import { useTheme } from '@react-navigation/native';
9 |
10 | import AsyncStorage from "@react-native-async-storage/async-storage";
11 |
12 | export default function Profile() {
13 | const { setColorScheme, colorScheme } = useColorScheme();
14 | const [selectedIndex, setSelectedIndex] = useState("light");
15 | const toggleColorScheme = async (themeValue: ThemeOptions) => {
16 | setSelectedIndex(themeValue);
17 | setColorScheme(themeValue);
18 | await AsyncStorage.setItem("theme", themeValue);
19 | };
20 |
21 | useEffect(() => {
22 | const getTheme = async () => {
23 | try {
24 | const themeValue = (await AsyncStorage.getItem(
25 | "theme"
26 | )) as ThemeOptions;
27 | if (themeValue) {
28 | setSelectedIndex(themeValue);
29 | }
30 | } catch (e) {
31 | console.log(e);
32 | }
33 | };
34 | getTheme();
35 | }, []);
36 |
37 | const { colors } = useTheme();
38 | return (
39 |
40 | toggleColorScheme("light")}
43 | checkedIcon="dot-circle-o"
44 | uncheckedIcon="circle-o"
45 | title="Light"
46 | className="bg-background"
47 | checkedColor={MyLightTheme.colors.primary}
48 | />
49 | toggleColorScheme("dark")}
52 | checkedIcon="dot-circle-o"
53 | uncheckedIcon="circle-o"
54 | title="Dark"
55 | checkedColor={colors.notification}
56 | />
57 | toggleColorScheme("system")}
60 | checkedIcon="dot-circle-o"
61 | uncheckedIcon="circle-o"
62 | title="System"
63 | checkedColor={colorScheme =="dark" ? colors.notification : MyLightTheme.colors.primary}
64 | />
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { GestureHandlerRootView } from "react-native-gesture-handler";
2 | import { Drawer } from "expo-router/drawer";
3 | import { DarkTheme, ThemeProvider } from "@react-navigation/native";
4 | import { useColorScheme } from "nativewind";
5 | import {
6 | useFonts,
7 | Exo_400Regular,
8 | Exo_700Bold,
9 | Exo_900Black,
10 | } from "@expo-google-fonts/exo";
11 |
12 | import { SplashScreen } from "expo-router";
13 |
14 | import "../global.css";
15 | import { MyLightTheme } from "../utilities/themeOptions";
16 | import { useEffect } from "react";
17 | import AsyncStorage from "@react-native-async-storage/async-storage";
18 |
19 | SplashScreen.preventAutoHideAsync();
20 |
21 | export default function Layout() {
22 | const { colorScheme, setColorScheme } = useColorScheme();
23 | useEffect(() => {
24 | const loadTheme = async () => {
25 | // await AsyncStorage.removeItem('theme');
26 | const stored = (await AsyncStorage.getItem("theme")) as ThemeOptions;
27 | if (stored) {
28 | setColorScheme(stored);
29 | } else {
30 | // Default to light if nothing or unexpected value is stored
31 | setColorScheme("light");
32 | }
33 | };
34 |
35 | loadTheme();
36 | }, [colorScheme]);
37 |
38 | const [fontsLoaded, fontError] = useFonts({
39 | Exo_400Regular,
40 | Exo_700Bold,
41 | Exo_900Black,
42 | });
43 |
44 | useEffect(() => {
45 | if (fontsLoaded || fontError) {
46 | // Hide the splash screen after the fonts have loaded (or an error was returned) and the UI is ready.
47 | setTimeout(() => {
48 | SplashScreen.hideAsync();
49 | }, 2000);
50 | }
51 | }, [fontsLoaded, fontError]);
52 |
53 | // Prevent rendering until the font has loaded or an error was returned
54 | if (!fontsLoaded && !fontError) {
55 | return null;
56 | }
57 |
58 | return (
59 |
60 |
61 |
62 |
69 |
76 |
83 |
84 |
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Upgrade to Expo51**:
2 | 1. ```npm install expo@latest``` - Upgrade expo to latest version (51 in our case)
3 | 2. ```npx expo install --fix``` - Resolve any dependency version compatibility issues
4 |
5 | **Here is a breakdown of the other branches:**
6 |
7 | 1. **Starter**: Tailwind CSS and Typescript setup. Corresponding video on YouTube: [React Native Expo with Typescript and Tailwind CSS (2024)](https://www.youtube.com/watch?v=9SdmwQPblBI)
8 |
9 | 2. **Features/ExpoRouter**: Nested routing with Expo Router V3 (Drawer and bottom tabs). Corresponding video on YouTube: [Expo Router V3 - Nested Bottom Tabs Inside Drawer Navigation](https://youtu.be/wOdz4XyMU7c)
10 |
11 | 3. **Features/Themes**: Themes with Expo Router v3 and NativeWind v4. Corresponding video on YouTube: [The best and easiest way to add a custom theme to Expo app with Expo Router (V3) and NativeWind (V4)](https://youtu.be/O6SYukr2zd0)
12 |
13 | 4. **Features/DevClient**: Expo Development Builds with Expo Dev Client. Corresponding video on YouTube: [Expo Development Build](https://youtu.be/C04MMx3ucO0)
14 |
15 | 5. **Features/Splash_Icon_Fonts**: Expo Splash screen (light and dark theme), App icon and Expo Google Fonts. Corresponding video on YouTube: [Expo Fonts, App Icon and Splash Screen (dark/light mode)](https://youtu.be/jEKdBqTclME)
16 |
17 | 6. **Features/ChatScreen**: Handling Keyboard display and hiding in React Native for TextInput so that the keyboard does not cover the text input area in iOS. Corresponding video on YouTube: [React Native Text Input iOS and Android - Handling Keyboard](https://youtu.be/DuspDGuR58Q)
18 |
19 | 7. **Features/Expo51_DevClient**: Upgrading Expo SDK 50 to SDK 51 w/development builds. Corresponding video on YouTube: [Ultimate Guide to Upgrade Expo SDK 50 to 51 - Managed workflow w/ Expo Go AND Development Builds](https://youtu.be/2gM8hg9W-J0)
20 |
21 | 8. **Features/IntervalTimer**: Build an interval timer (TABATA) in React Native. Use Expo Router, modal, Expo Haptics (vibrating device), Expo av (to make a beep sound), and more. Corresponding video on YouTube: [Build an Interval Timer in React Native (Expo Router, Navigation, Modal with Vibration and Audio)](https://youtu.be/ZU51gVaHHKs)
22 |
23 | 9. **Features/rnFirebase**: Step-by-step instructions to configure Firebase for your Expo app (Expo Routerv3). Corresponding video on YouTube: [Ultimate Firebase Setup for Expo in 2024: Step-by-Step Guide with Expo Router V3 & Full Auth Flow)](https://youtu.be/U5K-SfIOmZs)
24 |
25 | [Reset Password & Email Verification with Firebase in Expo App | Step-by-Step Guide](https://youtu.be/kCerC6XUbVc)
26 |
27 | 10. **Features/Sanity_CMS**: Stup Sanity CMS for your Expo React Native project. Corresponding video on YouTube: [How to Integrate Sanity CMS with React Native/Expo - Step-by-Step Guide](https://youtu.be/2sochWB3gjo)
28 |
29 | Make sure to update project name and database inside **sanity/sanity.config.ts** as well as **sanity.ts** files with your account details.
30 |
31 | 11. **Features/Dynamic_App_Icons**: Expo Prebuild demo using React Native Change Icon library [Expo Prebuild Showcase: Dynamically Change Application Icons in React Native](https://youtu.be/T7ybGL5HUhI)
--------------------------------------------------------------------------------
/app/(tabs)/chat.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Pressable,
3 | TextInput,
4 | View,
5 | Animated,
6 | Keyboard,
7 | Easing,
8 | Platform,
9 | } from "react-native";
10 | import CustomText from "../../components/CustomText";
11 | import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
12 | import { useEffect, useRef, useState } from "react";
13 | import * as Linking from "expo-linking";
14 |
15 | import { Feather } from "@expo/vector-icons";
16 | import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
17 |
18 | export default function Chat() {
19 | const [chatText, setChatText] = useState("");
20 | const translateYRef = useRef(new Animated.Value(0)).current;
21 | const tabBarHeight = useBottomTabBarHeight();
22 | const [textInputHeight, setTextInputHeight] = useState(60);
23 |
24 | const handleSubmit = () => {
25 | console.log(chatText);
26 | };
27 |
28 | useEffect(() => {
29 | const keyboardWillShowListener = Keyboard.addListener(
30 | "keyboardWillShow",
31 | (event) => {
32 | const { height: newKeyboardHeight } = event.endCoordinates;
33 | Animated.timing(translateYRef, {
34 | toValue: tabBarHeight - newKeyboardHeight, // negative value of translateY means move up
35 | duration: event.duration,
36 | easing: Easing.bezier(0.33, 0.66, 0.66, 1),
37 | useNativeDriver: false,
38 | }).start();
39 | }
40 | );
41 |
42 | const keyboardWillHideListener = Keyboard.addListener(
43 | "keyboardWillHide",
44 | (event) => {
45 | Animated.timing(translateYRef, {
46 | toValue: 0,
47 | duration: event.duration,
48 | easing: Easing.bezier(0.33, 0.66, 0.66, 1),
49 | useNativeDriver: false,
50 | }).start();
51 | }
52 | );
53 |
54 | return () => {
55 | keyboardWillShowListener.remove();
56 | keyboardWillHideListener.remove();
57 | };
58 | }, []);
59 |
60 | const handleContentSizeChange = (event: {
61 | nativeEvent: { contentSize: { height: number } };
62 | }) => {
63 | setTextInputHeight(Math.max(event.nativeEvent.contentSize.height, 40));
64 | };
65 |
66 | return (
67 |
68 |
72 |
73 | {
75 | Linking.openURL("https://youtu.be/nt4UQ_mzDeI");
76 | }}
77 | >
78 |
79 | AI Chatbot with Resposne Streaming
80 |
81 |
82 |
83 |
84 |
94 | setChatText(text)}
100 | placeholder="Ask a question here"
101 | onContentSizeChange={handleContentSizeChange}
102 | />
103 |
107 |
108 |
109 |
110 |
111 | );
112 | }
113 |
--------------------------------------------------------------------------------