diff --git a/App.tsx b/App.tsx
index 0329d0c..9c87540 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,20 +1,17 @@
-import { StatusBar } from 'expo-status-bar';
-import { StyleSheet, Text, View } from 'react-native';
+import { NavigationContainer } from "@react-navigation/native";
+import React from "react";
+import { enableScreens } from "react-native-screens";
+import AuthGate from "./src/navigation/AuthGate";
+import { AuthProvider } from "./src/store/auth";
+
+enableScreens();
export default function App() {
return (
-
- Open up App.tsx to start working on your app!
-
-
+
+
+
+
+
);
}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#fff',
- alignItems: 'center',
- justifyContent: 'center',
- },
-});
diff --git a/package-lock.json b/package-lock.json
index 05e4bac..d09a78d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,10 +8,14 @@
"name": "audiobook-app",
"version": "1.0.0",
"dependencies": {
+ "@react-navigation/native": "^7.1.26",
+ "@react-navigation/native-stack": "^7.9.0",
"expo": "~54.0.30",
"expo-status-bar": "~3.0.9",
"react": "19.1.0",
- "react-native": "0.81.5"
+ "react-native": "0.81.5",
+ "react-native-safe-area-context": "~5.6.0",
+ "react-native-screens": "~4.16.0"
},
"devDependencies": {
"@types/react": "~19.1.0",
@@ -2972,6 +2976,123 @@
"integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==",
"license": "MIT"
},
+ "node_modules/@react-navigation/core": {
+ "version": "7.13.7",
+ "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.13.7.tgz",
+ "integrity": "sha512-k2ABo3250vq1ovOh/iVwXS6Hwr5PVRGXoPh/ewVFOOuEKTvOx9i//OBzt8EF+HokBxS2HBRlR2b+aCOmscRqBw==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/routers": "^7.5.3",
+ "escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
+ "nanoid": "^3.3.11",
+ "query-string": "^7.1.3",
+ "react-is": "^19.1.0",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "react": ">= 18.2.0"
+ }
+ },
+ "node_modules/@react-navigation/core/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@react-navigation/core/node_modules/react-is": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
+ "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
+ "license": "MIT"
+ },
+ "node_modules/@react-navigation/elements": {
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.3.tgz",
+ "integrity": "sha512-3+eyvWiVPIEf6tN9UdduhOEHcTuNe3R5WovgiVkfH9+jApHMTZDc2loePTpY/i2HDJhObhhChpJzO6BVjrpdYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color": "^4.2.3",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@react-native-masked-view/masked-view": ">= 0.2.0",
+ "@react-navigation/native": "^7.1.26",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-masked-view/masked-view": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-navigation/native": {
+ "version": "7.1.26",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.26.tgz",
+ "integrity": "sha512-RhKmeD0E2ejzKS6z8elAfdfwShpcdkYY8zJzvHYLq+wv183BBcElTeyMLcIX6wIn7QutXeI92Yi21t7aUWfqNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/core": "^7.13.7",
+ "escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
+ "nanoid": "^3.3.11",
+ "use-latest-callback": "^0.2.4"
+ },
+ "peerDependencies": {
+ "react": ">= 18.2.0",
+ "react-native": "*"
+ }
+ },
+ "node_modules/@react-navigation/native-stack": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.9.0.tgz",
+ "integrity": "sha512-C/mNPhI0Pnerl7C2cB+6fAkdgSmfKECMERrbyfjx3P6JmEuTC54o+GV1c62FUmlRaRUassVHbtw4EeaY2uLh0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/elements": "^2.9.3",
+ "color": "^4.2.3",
+ "sf-symbols-typescript": "^2.1.0",
+ "warn-once": "^0.1.1"
+ },
+ "peerDependencies": {
+ "@react-navigation/native": "^7.1.26",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0",
+ "react-native-screens": ">= 4.0.0"
+ }
+ },
+ "node_modules/@react-navigation/native/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@react-navigation/routers": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
+ "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11"
+ }
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -3916,6 +4037,19 @@
"node": ">=0.8"
}
},
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3931,6 +4065,34 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/color/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -4096,6 +4258,15 @@
}
}
},
+ "node_modules/decode-uri-component": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -4301,6 +4472,7 @@
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.30.tgz",
"integrity": "sha512-6q+aFfKL0SpT8prfdpR3V8HcN51ov0mCGuwQTzyuk6eeO9rg7a7LWbgPv9rEVXGZEuyULstL8LGNwHqusand7Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "54.0.20",
@@ -4885,6 +5057,12 @@
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"license": "Apache-2.0"
},
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -4912,6 +5090,15 @@
"node": ">=8"
}
},
+ "node_modules/filter-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
+ "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -5284,6 +5471,12 @@
"loose-envify": "^1.0.0"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+ "license": "MIT"
+ },
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -7222,6 +7415,24 @@
"qrcode-terminal": "bin/qrcode-terminal.js"
}
},
+ "node_modules/query-string": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
+ "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
+ "license": "MIT",
+ "dependencies": {
+ "decode-uri-component": "^0.2.2",
+ "filter-obj": "^1.1.0",
+ "split-on-first": "^1.0.0",
+ "strict-uri-encode": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -7274,6 +7485,18 @@
"ws": "^7"
}
},
+ "node_modules/react-freeze": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
+ "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0"
+ }
+ },
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -7347,6 +7570,33 @@
"react-native": "*"
}
},
+ "node_modules/react-native-safe-area-context": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
+ "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-screens": {
+ "version": "4.16.0",
+ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
+ "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "react-freeze": "^1.0.0",
+ "react-native-is-edge-to-edge": "^1.2.1",
+ "warn-once": "^0.1.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.81.5",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
@@ -7820,6 +8070,15 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/sf-symbols-typescript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz",
+ "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7870,6 +8129,15 @@
"plist": "^3.0.5"
}
},
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -7931,6 +8199,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/split-on-first": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -7994,6 +8271,15 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/strict-uri-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -8499,6 +8785,24 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/use-latest-callback": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
+ "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -8550,6 +8854,12 @@
"makeerror": "1.0.12"
}
},
+ "node_modules/warn-once": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
+ "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
+ "license": "MIT"
+ },
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
diff --git a/package.json b/package.json
index d4735e2..e81e041 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,14 @@
"web": "expo start --web"
},
"dependencies": {
+ "@react-navigation/native": "^7.1.26",
+ "@react-navigation/native-stack": "^7.9.0",
"expo": "~54.0.30",
"expo-status-bar": "~3.0.9",
"react": "19.1.0",
- "react-native": "0.81.5"
+ "react-native": "0.81.5",
+ "react-native-safe-area-context": "~5.6.0",
+ "react-native-screens": "~4.16.0"
},
"devDependencies": {
"@types/react": "~19.1.0",
diff --git a/src/components/ui/AppText.tsx b/src/components/ui/AppText.tsx
new file mode 100644
index 0000000..4eacf0a
--- /dev/null
+++ b/src/components/ui/AppText.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { Text, type TextProps, type TextStyle } from "react-native";
+import { Theme } from "../../theme/theme";
+
+type Variant = "logo" | "title" | "body" | "muted" | "error";
+
+type Props = TextProps & {
+ variant?: Variant;
+ style?: TextStyle | TextStyle[];
+};
+
+export default function AppText({ variant = "body", style, ...props }: Props) {
+ const base: TextStyle = {
+ color: Theme.Colors.text,
+ fontSize: Theme.FontSize.md,
+ };
+
+ const variants: Record = {
+ logo: { fontSize: Theme.FontSize.xxl, fontWeight: Theme.FontWeight.bold },
+ title: { fontSize: Theme.FontSize.xl, fontWeight: Theme.FontWeight.bold },
+ body: { fontSize: Theme.FontSize.md },
+ muted: { color: Theme.Colors.mutedText, fontSize: Theme.FontSize.sm },
+ error: { color: Theme.Colors.danger, fontSize: Theme.FontSize.sm },
+ };
+
+ return ;
+}
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx
new file mode 100644
index 0000000..634afcb
--- /dev/null
+++ b/src/components/ui/Button.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+import { Pressable, type PressableProps, StyleSheet, View } from "react-native";
+import { Theme } from "../../theme/theme";
+import AppText from "./AppText";
+
+type Props = PressableProps & {
+ title: string;
+ variant?: "primary" | "secondary";
+};
+
+export default function Button({
+ title,
+ variant = "primary",
+ style,
+ ...props
+}: Props) {
+ const isPrimary = variant === "primary";
+
+ return (
+ [
+ styles.base,
+ isPrimary ? styles.primary : styles.secondary,
+ pressed &&
+ (isPrimary ? styles.primaryPressed : styles.secondaryPressed),
+ typeof style === "function" ? style({ pressed }) : style,
+ ]}
+ >
+
+
+ {title}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ base: {
+ height: 48,
+ paddingVertical: 0,
+ paddingHorizontal: Theme.Spacing.lg,
+ borderRadius: Theme.Radius.md,
+ alignItems: "center", // 가로 가운데
+ justifyContent: "center", // 세로 가운데
+ },
+ text: {
+ textAlign: "center",
+ fontWeight: Theme.FontWeight.bold,
+ lineHeight: 20,
+ },
+ primary: {
+ backgroundColor: Theme.Colors.primary,
+ },
+ primaryPressed: {
+ backgroundColor: Theme.Colors.primaryPressed,
+ },
+ secondary: {
+ backgroundColor: Theme.Colors.surface,
+ borderWidth: 1,
+ borderColor: Theme.Colors.border,
+ },
+ secondaryPressed: {
+ opacity: 0.9,
+ },
+});
diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx
new file mode 100644
index 0000000..ce3f3dd
--- /dev/null
+++ b/src/components/ui/Input.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+import { StyleSheet, TextInput, type TextInputProps, View } from "react-native";
+import { Theme } from "../../theme/theme";
+
+type Props = TextInputProps;
+
+export default function Input(props: Props) {
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ wrap: {
+ borderWidth: 1,
+ borderColor: Theme.Colors.border,
+ borderRadius: Theme.Radius.md,
+ backgroundColor: Theme.Colors.inputBg,
+ },
+ input: {
+ paddingHorizontal: Theme.Spacing.md,
+ paddingVertical: Theme.Spacing.md,
+ color: Theme.Colors.text,
+ fontSize: Theme.FontSize.md,
+ },
+});
diff --git a/src/navigation/AppStack.tsx b/src/navigation/AppStack.tsx
new file mode 100644
index 0000000..d17ff21
--- /dev/null
+++ b/src/navigation/AppStack.tsx
@@ -0,0 +1,17 @@
+import { createNativeStackNavigator } from "@react-navigation/native-stack";
+import React from "react";
+import HomeScreen from "../screens/HomeScreen";
+
+export type AppStackParamList = {
+ Home: undefined;
+};
+
+const Stack = createNativeStackNavigator();
+
+export default function AppStack() {
+ return (
+
+
+
+ );
+}
diff --git a/src/navigation/AuthGate.tsx b/src/navigation/AuthGate.tsx
new file mode 100644
index 0000000..bdc50f1
--- /dev/null
+++ b/src/navigation/AuthGate.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import AppStack from "./AppStack";
+import AuthStack from "./AuthStack";
+
+export default function AuthGate() {
+ // const { isAuthed } = useAuth();
+ const isAuthed = true;
+ // return ;
+ return isAuthed ? : ;
+}
diff --git a/src/navigation/AuthStack.tsx b/src/navigation/AuthStack.tsx
new file mode 100644
index 0000000..49c671d
--- /dev/null
+++ b/src/navigation/AuthStack.tsx
@@ -0,0 +1,17 @@
+import { createNativeStackNavigator } from "@react-navigation/native-stack";
+import React from "react";
+import LoginScreen from "../screens/LoginScreen";
+
+export type AuthStackParamList = {
+ Login: undefined;
+};
+
+const Stack = createNativeStackNavigator();
+
+export default function AuthStack() {
+ return (
+
+
+
+ );
+}
diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx
new file mode 100644
index 0000000..fb615a5
--- /dev/null
+++ b/src/screens/HomeScreen.tsx
@@ -0,0 +1,18 @@
+import { StyleSheet, Text, View } from "react-native";
+
+export default function HomeScreen() {
+ return (
+
+ feawfewa
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "red",
+ alignContent: "center",
+ justifyContent: "center",
+ },
+});
diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx
new file mode 100644
index 0000000..e4c98df
--- /dev/null
+++ b/src/screens/LoginScreen.tsx
@@ -0,0 +1,74 @@
+import React from "react";
+import { Pressable, StyleSheet, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import AppText from "../components/ui/AppText";
+import Button from "../components/ui/Button";
+import Input from "../components/ui/Input";
+import { Theme } from "../theme/theme";
+
+export default function LoginScreen() {
+ return (
+
+
+ Your personal growth podcast
+ Sign in to continue
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ safe: {
+ flex: 1,
+ backgroundColor: Theme.Colors.bg,
+ },
+ container: {
+ flex: 1,
+ padding: Theme.Spacing.xl,
+ justifyContent: "center",
+ gap: Theme.Spacing.sm,
+ },
+ signupLine: {
+ textAlign: "center",
+ },
+ signupLink: {
+ color: Theme.Colors.primary,
+ fontWeight: Theme.FontWeight.bold,
+ },
+});
diff --git a/src/store/auth.tsx b/src/store/auth.tsx
new file mode 100644
index 0000000..4ff191f
--- /dev/null
+++ b/src/store/auth.tsx
@@ -0,0 +1,31 @@
+import React, { createContext, useContext, useMemo, useState } from "react";
+
+export type AuthContextValue = {
+ isAuthed: boolean;
+ login: () => void;
+ logout: () => void;
+};
+
+const AuthContext = createContext(null);
+
+export function AuthProvider({ children }: { children: React.ReactNode }) {
+ const [isAuthed, setIsAuthed] = useState(false);
+ console.log("isAuthed: " + isAuthed);
+
+ const value = useMemo(
+ () => ({
+ isAuthed,
+ login: () => setIsAuthed(true),
+ logout: () => setIsAuthed(false),
+ }),
+ [isAuthed]
+ );
+
+ return {children};
+}
+
+export function useAuth(): AuthContextValue {
+ const ctx = useContext(AuthContext);
+ if (!ctx) throw new Error("useAuth must be used within AuthProvider");
+ return ctx;
+}
diff --git a/src/theme/colors.ts b/src/theme/colors.ts
new file mode 100644
index 0000000..e55bc4e
--- /dev/null
+++ b/src/theme/colors.ts
@@ -0,0 +1,22 @@
+export const Colors = {
+ // base
+ bg: "#0B1220",
+ surface: "#111827",
+ card: "#0F172A",
+
+ // text
+ text: "#FFFFFF",
+ mutedText: "#9CA3AF",
+ placeholder: "#6B7280",
+
+ // brand
+ primary: "#4F46E5",
+ primaryPressed: "#4338CA",
+
+ // status
+ danger: "#EF4444",
+
+ // lines
+ border: "#22304A",
+ inputBg: "#0B1220",
+} as const;
diff --git a/src/theme/radius.ts b/src/theme/radius.ts
new file mode 100644
index 0000000..3ab36ef
--- /dev/null
+++ b/src/theme/radius.ts
@@ -0,0 +1,6 @@
+export const Radius = {
+ sm: 10,
+ md: 12,
+ lg: 16,
+ xl: 20,
+} as const;
diff --git a/src/theme/spacing.ts b/src/theme/spacing.ts
new file mode 100644
index 0000000..65ad9cc
--- /dev/null
+++ b/src/theme/spacing.ts
@@ -0,0 +1,8 @@
+export const Spacing = {
+ xs: 4,
+ sm: 8,
+ md: 12,
+ lg: 16,
+ xl: 20,
+ "2xl": 24,
+} as const;
diff --git a/src/theme/theme.ts b/src/theme/theme.ts
new file mode 100644
index 0000000..87cb4b7
--- /dev/null
+++ b/src/theme/theme.ts
@@ -0,0 +1,12 @@
+import { Colors } from "./colors";
+import { Radius } from "./radius";
+import { Spacing } from "./spacing";
+import { FontSize, FontWeight } from "./typography";
+
+export const Theme = {
+ Colors,
+ Spacing,
+ Radius,
+ FontSize,
+ FontWeight,
+} as const;
diff --git a/src/theme/typography.ts b/src/theme/typography.ts
new file mode 100644
index 0000000..51ed85f
--- /dev/null
+++ b/src/theme/typography.ts
@@ -0,0 +1,14 @@
+export const FontSize = {
+ sm: 13,
+ md: 15,
+ lg: 18,
+ xl: 22,
+ xxl: 26,
+} as const;
+
+export const FontWeight = {
+ regular: "400",
+ medium: "500",
+ semibold: "600",
+ bold: "700",
+} as const;