diff --git a/code/01-creating-custom-buttons/App.js b/code/01-creating-custom-buttons/App.js
new file mode 100644
index 00000000..8cf74602
--- /dev/null
+++ b/code/01-creating-custom-buttons/App.js
@@ -0,0 +1,9 @@
+import { StyleSheet } from 'react-native';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return ;
+}
+
+const styles = StyleSheet.create({});
diff --git a/code/01-creating-custom-buttons/app.json b/code/01-creating-custom-buttons/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/01-creating-custom-buttons/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/01-creating-custom-buttons/assets/adaptive-icon.png b/code/01-creating-custom-buttons/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/01-creating-custom-buttons/assets/adaptive-icon.png differ
diff --git a/code/01-creating-custom-buttons/assets/favicon.png b/code/01-creating-custom-buttons/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/01-creating-custom-buttons/assets/favicon.png differ
diff --git a/code/01-creating-custom-buttons/assets/icon.png b/code/01-creating-custom-buttons/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/01-creating-custom-buttons/assets/icon.png differ
diff --git a/code/01-creating-custom-buttons/assets/splash.png b/code/01-creating-custom-buttons/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/01-creating-custom-buttons/assets/splash.png differ
diff --git a/code/01-creating-custom-buttons/babel.config.js b/code/01-creating-custom-buttons/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/01-creating-custom-buttons/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/01-creating-custom-buttons/components/PrimaryButton.js b/code/01-creating-custom-buttons/components/PrimaryButton.js
new file mode 100644
index 00000000..383da71c
--- /dev/null
+++ b/code/01-creating-custom-buttons/components/PrimaryButton.js
@@ -0,0 +1,11 @@
+import { View, Text } from 'react-native';
+
+function PrimaryButton({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default PrimaryButton;
diff --git a/code/01-creating-custom-buttons/package.json b/code/01-creating-custom-buttons/package.json
new file mode 100644
index 00000000..a56b658b
--- /dev/null
+++ b/code/01-creating-custom-buttons/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/01-creating-custom-buttons/screens/GameOverScreen.js b/code/01-creating-custom-buttons/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/01-creating-custom-buttons/screens/GameScreen.js b/code/01-creating-custom-buttons/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/01-creating-custom-buttons/screens/StartGameScreen.js b/code/01-creating-custom-buttons/screens/StartGameScreen.js
new file mode 100644
index 00000000..05de7428
--- /dev/null
+++ b/code/01-creating-custom-buttons/screens/StartGameScreen.js
@@ -0,0 +1,15 @@
+import { TextInput, View } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+ Reset
+ Confirm
+
+ );
+}
+
+export default StartGameScreen;
diff --git a/code/02-styling-number-input/App.js b/code/02-styling-number-input/App.js
new file mode 100644
index 00000000..8cf74602
--- /dev/null
+++ b/code/02-styling-number-input/App.js
@@ -0,0 +1,9 @@
+import { StyleSheet } from 'react-native';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return ;
+}
+
+const styles = StyleSheet.create({});
diff --git a/code/02-styling-number-input/app.json b/code/02-styling-number-input/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/02-styling-number-input/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/02-styling-number-input/assets/adaptive-icon.png b/code/02-styling-number-input/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/02-styling-number-input/assets/adaptive-icon.png differ
diff --git a/code/02-styling-number-input/assets/favicon.png b/code/02-styling-number-input/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/02-styling-number-input/assets/favicon.png differ
diff --git a/code/02-styling-number-input/assets/icon.png b/code/02-styling-number-input/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/02-styling-number-input/assets/icon.png differ
diff --git a/code/02-styling-number-input/assets/splash.png b/code/02-styling-number-input/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/02-styling-number-input/assets/splash.png differ
diff --git a/code/02-styling-number-input/babel.config.js b/code/02-styling-number-input/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/02-styling-number-input/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/02-styling-number-input/components/PrimaryButton.js b/code/02-styling-number-input/components/PrimaryButton.js
new file mode 100644
index 00000000..383da71c
--- /dev/null
+++ b/code/02-styling-number-input/components/PrimaryButton.js
@@ -0,0 +1,11 @@
+import { View, Text } from 'react-native';
+
+function PrimaryButton({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default PrimaryButton;
diff --git a/code/02-styling-number-input/package.json b/code/02-styling-number-input/package.json
new file mode 100644
index 00000000..a56b658b
--- /dev/null
+++ b/code/02-styling-number-input/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/02-styling-number-input/screens/GameOverScreen.js b/code/02-styling-number-input/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/02-styling-number-input/screens/GameScreen.js b/code/02-styling-number-input/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/02-styling-number-input/screens/StartGameScreen.js b/code/02-styling-number-input/screens/StartGameScreen.js
new file mode 100644
index 00000000..1625c5d5
--- /dev/null
+++ b/code/02-styling-number-input/screens/StartGameScreen.js
@@ -0,0 +1,41 @@
+import { TextInput, View, StyleSheet } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+ Reset
+ Confirm
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#72063c',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center'
+ }
+});
\ No newline at end of file
diff --git a/code/03-configuring-text-input/App.js b/code/03-configuring-text-input/App.js
new file mode 100644
index 00000000..8cf74602
--- /dev/null
+++ b/code/03-configuring-text-input/App.js
@@ -0,0 +1,9 @@
+import { StyleSheet } from 'react-native';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return ;
+}
+
+const styles = StyleSheet.create({});
diff --git a/code/03-configuring-text-input/app.json b/code/03-configuring-text-input/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/03-configuring-text-input/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/03-configuring-text-input/assets/adaptive-icon.png b/code/03-configuring-text-input/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/03-configuring-text-input/assets/adaptive-icon.png differ
diff --git a/code/03-configuring-text-input/assets/favicon.png b/code/03-configuring-text-input/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/03-configuring-text-input/assets/favicon.png differ
diff --git a/code/03-configuring-text-input/assets/icon.png b/code/03-configuring-text-input/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/03-configuring-text-input/assets/icon.png differ
diff --git a/code/03-configuring-text-input/assets/splash.png b/code/03-configuring-text-input/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/03-configuring-text-input/assets/splash.png differ
diff --git a/code/03-configuring-text-input/babel.config.js b/code/03-configuring-text-input/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/03-configuring-text-input/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/03-configuring-text-input/components/PrimaryButton.js b/code/03-configuring-text-input/components/PrimaryButton.js
new file mode 100644
index 00000000..383da71c
--- /dev/null
+++ b/code/03-configuring-text-input/components/PrimaryButton.js
@@ -0,0 +1,11 @@
+import { View, Text } from 'react-native';
+
+function PrimaryButton({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default PrimaryButton;
diff --git a/code/03-configuring-text-input/package.json b/code/03-configuring-text-input/package.json
new file mode 100644
index 00000000..a56b658b
--- /dev/null
+++ b/code/03-configuring-text-input/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/03-configuring-text-input/screens/GameOverScreen.js b/code/03-configuring-text-input/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/03-configuring-text-input/screens/GameScreen.js b/code/03-configuring-text-input/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/03-configuring-text-input/screens/StartGameScreen.js b/code/03-configuring-text-input/screens/StartGameScreen.js
new file mode 100644
index 00000000..580c889a
--- /dev/null
+++ b/code/03-configuring-text-input/screens/StartGameScreen.js
@@ -0,0 +1,47 @@
+import { TextInput, View, StyleSheet } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+ Reset
+ Confirm
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#72063c',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+});
diff --git a/code/04-improving-buttons/App.js b/code/04-improving-buttons/App.js
new file mode 100644
index 00000000..8cf74602
--- /dev/null
+++ b/code/04-improving-buttons/App.js
@@ -0,0 +1,9 @@
+import { StyleSheet } from 'react-native';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return ;
+}
+
+const styles = StyleSheet.create({});
diff --git a/code/04-improving-buttons/app.json b/code/04-improving-buttons/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/04-improving-buttons/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/04-improving-buttons/assets/adaptive-icon.png b/code/04-improving-buttons/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/04-improving-buttons/assets/adaptive-icon.png differ
diff --git a/code/04-improving-buttons/assets/favicon.png b/code/04-improving-buttons/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/04-improving-buttons/assets/favicon.png differ
diff --git a/code/04-improving-buttons/assets/icon.png b/code/04-improving-buttons/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/04-improving-buttons/assets/icon.png differ
diff --git a/code/04-improving-buttons/assets/splash.png b/code/04-improving-buttons/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/04-improving-buttons/assets/splash.png differ
diff --git a/code/04-improving-buttons/babel.config.js b/code/04-improving-buttons/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/04-improving-buttons/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/04-improving-buttons/components/PrimaryButton.js b/code/04-improving-buttons/components/PrimaryButton.js
new file mode 100644
index 00000000..8109ee02
--- /dev/null
+++ b/code/04-improving-buttons/components/PrimaryButton.js
@@ -0,0 +1,46 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children }) {
+ function pressHandler() {
+ console.log('Pressed!');
+ }
+
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={pressHandler}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/04-improving-buttons/package.json b/code/04-improving-buttons/package.json
new file mode 100644
index 00000000..a56b658b
--- /dev/null
+++ b/code/04-improving-buttons/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/04-improving-buttons/screens/GameOverScreen.js b/code/04-improving-buttons/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/04-improving-buttons/screens/GameScreen.js b/code/04-improving-buttons/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/04-improving-buttons/screens/StartGameScreen.js b/code/04-improving-buttons/screens/StartGameScreen.js
new file mode 100644
index 00000000..6533266b
--- /dev/null
+++ b/code/04-improving-buttons/screens/StartGameScreen.js
@@ -0,0 +1,61 @@
+import { TextInput, View, StyleSheet } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#4e0329',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1
+ }
+});
diff --git a/code/05-coloring-components-overall-app/App.js b/code/05-coloring-components-overall-app/App.js
new file mode 100644
index 00000000..2dc817db
--- /dev/null
+++ b/code/05-coloring-components-overall-app/App.js
@@ -0,0 +1,18 @@
+import { StyleSheet, View } from 'react-native';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ backgroundColor: '#ddb52f'
+ }
+});
diff --git a/code/05-coloring-components-overall-app/app.json b/code/05-coloring-components-overall-app/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/05-coloring-components-overall-app/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/05-coloring-components-overall-app/assets/adaptive-icon.png b/code/05-coloring-components-overall-app/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/05-coloring-components-overall-app/assets/adaptive-icon.png differ
diff --git a/code/05-coloring-components-overall-app/assets/favicon.png b/code/05-coloring-components-overall-app/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/05-coloring-components-overall-app/assets/favicon.png differ
diff --git a/code/05-coloring-components-overall-app/assets/icon.png b/code/05-coloring-components-overall-app/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/05-coloring-components-overall-app/assets/icon.png differ
diff --git a/code/05-coloring-components-overall-app/assets/splash.png b/code/05-coloring-components-overall-app/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/05-coloring-components-overall-app/assets/splash.png differ
diff --git a/code/05-coloring-components-overall-app/babel.config.js b/code/05-coloring-components-overall-app/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/05-coloring-components-overall-app/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/05-coloring-components-overall-app/components/PrimaryButton.js b/code/05-coloring-components-overall-app/components/PrimaryButton.js
new file mode 100644
index 00000000..8109ee02
--- /dev/null
+++ b/code/05-coloring-components-overall-app/components/PrimaryButton.js
@@ -0,0 +1,46 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children }) {
+ function pressHandler() {
+ console.log('Pressed!');
+ }
+
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={pressHandler}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/05-coloring-components-overall-app/package.json b/code/05-coloring-components-overall-app/package.json
new file mode 100644
index 00000000..a56b658b
--- /dev/null
+++ b/code/05-coloring-components-overall-app/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/05-coloring-components-overall-app/screens/GameOverScreen.js b/code/05-coloring-components-overall-app/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/05-coloring-components-overall-app/screens/GameScreen.js b/code/05-coloring-components-overall-app/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/05-coloring-components-overall-app/screens/StartGameScreen.js b/code/05-coloring-components-overall-app/screens/StartGameScreen.js
new file mode 100644
index 00000000..6533266b
--- /dev/null
+++ b/code/05-coloring-components-overall-app/screens/StartGameScreen.js
@@ -0,0 +1,61 @@
+import { TextInput, View, StyleSheet } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#4e0329',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1
+ }
+});
diff --git a/code/06-adding-linear-gradient/App.js b/code/06-adding-linear-gradient/App.js
new file mode 100644
index 00000000..25273c8e
--- /dev/null
+++ b/code/06-adding-linear-gradient/App.js
@@ -0,0 +1,18 @@
+import { StyleSheet } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+});
diff --git a/code/06-adding-linear-gradient/app.json b/code/06-adding-linear-gradient/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/06-adding-linear-gradient/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/06-adding-linear-gradient/assets/adaptive-icon.png b/code/06-adding-linear-gradient/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/06-adding-linear-gradient/assets/adaptive-icon.png differ
diff --git a/code/06-adding-linear-gradient/assets/favicon.png b/code/06-adding-linear-gradient/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/06-adding-linear-gradient/assets/favicon.png differ
diff --git a/code/06-adding-linear-gradient/assets/icon.png b/code/06-adding-linear-gradient/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/06-adding-linear-gradient/assets/icon.png differ
diff --git a/code/06-adding-linear-gradient/assets/splash.png b/code/06-adding-linear-gradient/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/06-adding-linear-gradient/assets/splash.png differ
diff --git a/code/06-adding-linear-gradient/babel.config.js b/code/06-adding-linear-gradient/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/06-adding-linear-gradient/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/06-adding-linear-gradient/components/PrimaryButton.js b/code/06-adding-linear-gradient/components/PrimaryButton.js
new file mode 100644
index 00000000..8109ee02
--- /dev/null
+++ b/code/06-adding-linear-gradient/components/PrimaryButton.js
@@ -0,0 +1,46 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children }) {
+ function pressHandler() {
+ console.log('Pressed!');
+ }
+
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={pressHandler}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/06-adding-linear-gradient/package.json b/code/06-adding-linear-gradient/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/06-adding-linear-gradient/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/06-adding-linear-gradient/screens/GameOverScreen.js b/code/06-adding-linear-gradient/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/06-adding-linear-gradient/screens/GameScreen.js b/code/06-adding-linear-gradient/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/06-adding-linear-gradient/screens/StartGameScreen.js b/code/06-adding-linear-gradient/screens/StartGameScreen.js
new file mode 100644
index 00000000..1ac719ed
--- /dev/null
+++ b/code/06-adding-linear-gradient/screens/StartGameScreen.js
@@ -0,0 +1,61 @@
+import { TextInput, View, StyleSheet } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#3b021f',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1
+ }
+});
diff --git a/code/07-adding-background-image/App.js b/code/07-adding-background-image/App.js
new file mode 100644
index 00000000..144ab2e8
--- /dev/null
+++ b/code/07-adding-background-image/App.js
@@ -0,0 +1,28 @@
+import { StyleSheet, ImageBackground } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return (
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15
+ }
+});
diff --git a/code/07-adding-background-image/app.json b/code/07-adding-background-image/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/07-adding-background-image/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/07-adding-background-image/assets/adaptive-icon.png b/code/07-adding-background-image/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/07-adding-background-image/assets/adaptive-icon.png differ
diff --git a/code/07-adding-background-image/assets/favicon.png b/code/07-adding-background-image/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/07-adding-background-image/assets/favicon.png differ
diff --git a/code/07-adding-background-image/assets/icon.png b/code/07-adding-background-image/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/07-adding-background-image/assets/icon.png differ
diff --git a/code/07-adding-background-image/assets/images/background.png b/code/07-adding-background-image/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/07-adding-background-image/assets/images/background.png differ
diff --git a/code/07-adding-background-image/assets/splash.png b/code/07-adding-background-image/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/07-adding-background-image/assets/splash.png differ
diff --git a/code/07-adding-background-image/babel.config.js b/code/07-adding-background-image/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/07-adding-background-image/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/07-adding-background-image/components/PrimaryButton.js b/code/07-adding-background-image/components/PrimaryButton.js
new file mode 100644
index 00000000..8109ee02
--- /dev/null
+++ b/code/07-adding-background-image/components/PrimaryButton.js
@@ -0,0 +1,46 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children }) {
+ function pressHandler() {
+ console.log('Pressed!');
+ }
+
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={pressHandler}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/07-adding-background-image/package.json b/code/07-adding-background-image/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/07-adding-background-image/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/07-adding-background-image/screens/GameOverScreen.js b/code/07-adding-background-image/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/07-adding-background-image/screens/GameScreen.js b/code/07-adding-background-image/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/07-adding-background-image/screens/StartGameScreen.js b/code/07-adding-background-image/screens/StartGameScreen.js
new file mode 100644
index 00000000..1ac719ed
--- /dev/null
+++ b/code/07-adding-background-image/screens/StartGameScreen.js
@@ -0,0 +1,61 @@
+import { TextInput, View, StyleSheet } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#3b021f',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1
+ }
+});
diff --git a/code/08-getting-started-with-game-logic/App.js b/code/08-getting-started-with-game-logic/App.js
new file mode 100644
index 00000000..144ab2e8
--- /dev/null
+++ b/code/08-getting-started-with-game-logic/App.js
@@ -0,0 +1,28 @@
+import { StyleSheet, ImageBackground } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+
+export default function App() {
+ return (
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15
+ }
+});
diff --git a/code/08-getting-started-with-game-logic/app.json b/code/08-getting-started-with-game-logic/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/08-getting-started-with-game-logic/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/08-getting-started-with-game-logic/assets/adaptive-icon.png b/code/08-getting-started-with-game-logic/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/08-getting-started-with-game-logic/assets/adaptive-icon.png differ
diff --git a/code/08-getting-started-with-game-logic/assets/favicon.png b/code/08-getting-started-with-game-logic/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/08-getting-started-with-game-logic/assets/favicon.png differ
diff --git a/code/08-getting-started-with-game-logic/assets/icon.png b/code/08-getting-started-with-game-logic/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/08-getting-started-with-game-logic/assets/icon.png differ
diff --git a/code/08-getting-started-with-game-logic/assets/images/background.png b/code/08-getting-started-with-game-logic/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/08-getting-started-with-game-logic/assets/images/background.png differ
diff --git a/code/08-getting-started-with-game-logic/assets/splash.png b/code/08-getting-started-with-game-logic/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/08-getting-started-with-game-logic/assets/splash.png differ
diff --git a/code/08-getting-started-with-game-logic/babel.config.js b/code/08-getting-started-with-game-logic/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/08-getting-started-with-game-logic/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/08-getting-started-with-game-logic/components/PrimaryButton.js b/code/08-getting-started-with-game-logic/components/PrimaryButton.js
new file mode 100644
index 00000000..e3500d27
--- /dev/null
+++ b/code/08-getting-started-with-game-logic/components/PrimaryButton.js
@@ -0,0 +1,42 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/08-getting-started-with-game-logic/package.json b/code/08-getting-started-with-game-logic/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/08-getting-started-with-game-logic/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/08-getting-started-with-game-logic/screens/GameOverScreen.js b/code/08-getting-started-with-game-logic/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/08-getting-started-with-game-logic/screens/GameScreen.js b/code/08-getting-started-with-game-logic/screens/GameScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/08-getting-started-with-game-logic/screens/StartGameScreen.js b/code/08-getting-started-with-game-logic/screens/StartGameScreen.js
new file mode 100644
index 00000000..fd2469dc
--- /dev/null
+++ b/code/08-getting-started-with-game-logic/screens/StartGameScreen.js
@@ -0,0 +1,89 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen() {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ console.log('Valid number!');
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#3b021f',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/09-switching-screens/App.js b/code/09-switching-screens/App.js
new file mode 100644
index 00000000..22908f55
--- /dev/null
+++ b/code/09-switching-screens/App.js
@@ -0,0 +1,42 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/09-switching-screens/app.json b/code/09-switching-screens/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/09-switching-screens/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/09-switching-screens/assets/adaptive-icon.png b/code/09-switching-screens/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/09-switching-screens/assets/adaptive-icon.png differ
diff --git a/code/09-switching-screens/assets/favicon.png b/code/09-switching-screens/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/09-switching-screens/assets/favicon.png differ
diff --git a/code/09-switching-screens/assets/icon.png b/code/09-switching-screens/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/09-switching-screens/assets/icon.png differ
diff --git a/code/09-switching-screens/assets/images/background.png b/code/09-switching-screens/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/09-switching-screens/assets/images/background.png differ
diff --git a/code/09-switching-screens/assets/splash.png b/code/09-switching-screens/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/09-switching-screens/assets/splash.png differ
diff --git a/code/09-switching-screens/babel.config.js b/code/09-switching-screens/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/09-switching-screens/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/09-switching-screens/components/PrimaryButton.js b/code/09-switching-screens/components/PrimaryButton.js
new file mode 100644
index 00000000..e3500d27
--- /dev/null
+++ b/code/09-switching-screens/components/PrimaryButton.js
@@ -0,0 +1,42 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/09-switching-screens/package.json b/code/09-switching-screens/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/09-switching-screens/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/09-switching-screens/screens/GameOverScreen.js b/code/09-switching-screens/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/09-switching-screens/screens/GameScreen.js b/code/09-switching-screens/screens/GameScreen.js
new file mode 100644
index 00000000..945a47bf
--- /dev/null
+++ b/code/09-switching-screens/screens/GameScreen.js
@@ -0,0 +1,7 @@
+import { Text } from 'react-native';
+
+function GameScreen() {
+ return Game Screen!
+}
+
+export default GameScreen;
\ No newline at end of file
diff --git a/code/09-switching-screens/screens/StartGameScreen.js b/code/09-switching-screens/screens/StartGameScreen.js
new file mode 100644
index 00000000..ba0fc380
--- /dev/null
+++ b/code/09-switching-screens/screens/StartGameScreen.js
@@ -0,0 +1,89 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen({onPickNumber}) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#3b021f',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/App.js b/code/10-respecting-device-screen-restrictions-safeareaview/App.js
new file mode 100644
index 00000000..064b928a
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/App.js
@@ -0,0 +1,42 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/app.json b/code/10-respecting-device-screen-restrictions-safeareaview/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/assets/adaptive-icon.png b/code/10-respecting-device-screen-restrictions-safeareaview/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/10-respecting-device-screen-restrictions-safeareaview/assets/adaptive-icon.png differ
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/assets/favicon.png b/code/10-respecting-device-screen-restrictions-safeareaview/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/10-respecting-device-screen-restrictions-safeareaview/assets/favicon.png differ
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/assets/icon.png b/code/10-respecting-device-screen-restrictions-safeareaview/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/10-respecting-device-screen-restrictions-safeareaview/assets/icon.png differ
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/assets/images/background.png b/code/10-respecting-device-screen-restrictions-safeareaview/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/10-respecting-device-screen-restrictions-safeareaview/assets/images/background.png differ
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/assets/splash.png b/code/10-respecting-device-screen-restrictions-safeareaview/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/10-respecting-device-screen-restrictions-safeareaview/assets/splash.png differ
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/babel.config.js b/code/10-respecting-device-screen-restrictions-safeareaview/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/components/PrimaryButton.js b/code/10-respecting-device-screen-restrictions-safeareaview/components/PrimaryButton.js
new file mode 100644
index 00000000..e3500d27
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/components/PrimaryButton.js
@@ -0,0 +1,42 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: '#640233' }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: '#72063c',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/package.json b/code/10-respecting-device-screen-restrictions-safeareaview/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/screens/GameOverScreen.js b/code/10-respecting-device-screen-restrictions-safeareaview/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/screens/GameScreen.js b/code/10-respecting-device-screen-restrictions-safeareaview/screens/GameScreen.js
new file mode 100644
index 00000000..c62ef937
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/screens/GameScreen.js
@@ -0,0 +1,24 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+function GameScreen() {
+ return (
+
+ Opponent's Guess
+ {/* GUESS */}
+
+ Higher or lower?
+ {/* + - */}
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24
+ }
+});
\ No newline at end of file
diff --git a/code/10-respecting-device-screen-restrictions-safeareaview/screens/StartGameScreen.js b/code/10-respecting-device-screen-restrictions-safeareaview/screens/StartGameScreen.js
new file mode 100644
index 00000000..ba0fc380
--- /dev/null
+++ b/code/10-respecting-device-screen-restrictions-safeareaview/screens/StartGameScreen.js
@@ -0,0 +1,89 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+
+function StartGameScreen({onPickNumber}) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: '#3b021f',
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: '#ddb52f',
+ borderBottomWidth: 2,
+ color: '#ddb52f',
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/11-managing-colors-globally/App.js b/code/11-managing-colors-globally/App.js
new file mode 100644
index 00000000..46730dfa
--- /dev/null
+++ b/code/11-managing-colors-globally/App.js
@@ -0,0 +1,46 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/11-managing-colors-globally/app.json b/code/11-managing-colors-globally/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/11-managing-colors-globally/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/11-managing-colors-globally/assets/adaptive-icon.png b/code/11-managing-colors-globally/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/11-managing-colors-globally/assets/adaptive-icon.png differ
diff --git a/code/11-managing-colors-globally/assets/favicon.png b/code/11-managing-colors-globally/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/11-managing-colors-globally/assets/favicon.png differ
diff --git a/code/11-managing-colors-globally/assets/icon.png b/code/11-managing-colors-globally/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/11-managing-colors-globally/assets/icon.png differ
diff --git a/code/11-managing-colors-globally/assets/images/background.png b/code/11-managing-colors-globally/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/11-managing-colors-globally/assets/images/background.png differ
diff --git a/code/11-managing-colors-globally/assets/splash.png b/code/11-managing-colors-globally/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/11-managing-colors-globally/assets/splash.png differ
diff --git a/code/11-managing-colors-globally/babel.config.js b/code/11-managing-colors-globally/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/11-managing-colors-globally/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/11-managing-colors-globally/components/PrimaryButton.js b/code/11-managing-colors-globally/components/PrimaryButton.js
new file mode 100644
index 00000000..1bca52b8
--- /dev/null
+++ b/code/11-managing-colors-globally/components/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/11-managing-colors-globally/components/Title.js b/code/11-managing-colors-globally/components/Title.js
new file mode 100644
index 00000000..e247a429
--- /dev/null
+++ b/code/11-managing-colors-globally/components/Title.js
@@ -0,0 +1,21 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../constants/colors';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: Colors.accent500,
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: Colors.accent500,
+ padding: 12,
+ },
+});
diff --git a/code/11-managing-colors-globally/constants/colors.js b/code/11-managing-colors-globally/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/11-managing-colors-globally/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/11-managing-colors-globally/package.json b/code/11-managing-colors-globally/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/11-managing-colors-globally/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/11-managing-colors-globally/screens/GameOverScreen.js b/code/11-managing-colors-globally/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/11-managing-colors-globally/screens/GameScreen.js b/code/11-managing-colors-globally/screens/GameScreen.js
new file mode 100644
index 00000000..1edf892a
--- /dev/null
+++ b/code/11-managing-colors-globally/screens/GameScreen.js
@@ -0,0 +1,26 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Title from '../components/Title';
+
+function GameScreen() {
+ return (
+
+ Opponent's Guess
+ {/* GUESS */}
+
+ Higher or lower?
+ {/* + - */}
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24
+ }
+});
\ No newline at end of file
diff --git a/code/11-managing-colors-globally/screens/StartGameScreen.js b/code/11-managing-colors-globally/screens/StartGameScreen.js
new file mode 100644
index 00000000..f5354008
--- /dev/null
+++ b/code/11-managing-colors-globally/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/PrimaryButton';
+import Colors from '../constants/colors';
+
+function StartGameScreen({onPickNumber}) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/12-creating-using-displaying-random-number/App.js b/code/12-creating-using-displaying-random-number/App.js
new file mode 100644
index 00000000..46730dfa
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/App.js
@@ -0,0 +1,46 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/12-creating-using-displaying-random-number/app.json b/code/12-creating-using-displaying-random-number/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/12-creating-using-displaying-random-number/assets/adaptive-icon.png b/code/12-creating-using-displaying-random-number/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/12-creating-using-displaying-random-number/assets/adaptive-icon.png differ
diff --git a/code/12-creating-using-displaying-random-number/assets/favicon.png b/code/12-creating-using-displaying-random-number/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/12-creating-using-displaying-random-number/assets/favicon.png differ
diff --git a/code/12-creating-using-displaying-random-number/assets/icon.png b/code/12-creating-using-displaying-random-number/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/12-creating-using-displaying-random-number/assets/icon.png differ
diff --git a/code/12-creating-using-displaying-random-number/assets/images/background.png b/code/12-creating-using-displaying-random-number/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/12-creating-using-displaying-random-number/assets/images/background.png differ
diff --git a/code/12-creating-using-displaying-random-number/assets/splash.png b/code/12-creating-using-displaying-random-number/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/12-creating-using-displaying-random-number/assets/splash.png differ
diff --git a/code/12-creating-using-displaying-random-number/babel.config.js b/code/12-creating-using-displaying-random-number/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/12-creating-using-displaying-random-number/components/game/NumberContainer.js b/code/12-creating-using-displaying-random-number/components/game/NumberContainer.js
new file mode 100644
index 00000000..a5714670
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/components/game/NumberContainer.js
@@ -0,0 +1,30 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ fontWeight: 'bold',
+ },
+});
diff --git a/code/12-creating-using-displaying-random-number/components/ui/PrimaryButton.js b/code/12-creating-using-displaying-random-number/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/12-creating-using-displaying-random-number/components/ui/Title.js b/code/12-creating-using-displaying-random-number/components/ui/Title.js
new file mode 100644
index 00000000..265056a3
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/components/ui/Title.js
@@ -0,0 +1,21 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/12-creating-using-displaying-random-number/constants/colors.js b/code/12-creating-using-displaying-random-number/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/12-creating-using-displaying-random-number/package.json b/code/12-creating-using-displaying-random-number/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/12-creating-using-displaying-random-number/screens/GameOverScreen.js b/code/12-creating-using-displaying-random-number/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/12-creating-using-displaying-random-number/screens/GameScreen.js b/code/12-creating-using-displaying-random-number/screens/GameScreen.js
new file mode 100644
index 00000000..fa273ead
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/screens/GameScreen.js
@@ -0,0 +1,41 @@
+import { useState } from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+function GameScreen({ userNumber }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+ Higher or lower?
+ {/* + - */}
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+});
diff --git a/code/12-creating-using-displaying-random-number/screens/StartGameScreen.js b/code/12-creating-using-displaying-random-number/screens/StartGameScreen.js
new file mode 100644
index 00000000..dc6f1956
--- /dev/null
+++ b/code/12-creating-using-displaying-random-number/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function StartGameScreen({onPickNumber}) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/13-adding-game-control-buttons/App.js b/code/13-adding-game-control-buttons/App.js
new file mode 100644
index 00000000..d4cc1885
--- /dev/null
+++ b/code/13-adding-game-control-buttons/App.js
@@ -0,0 +1,46 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/13-adding-game-control-buttons/app.json b/code/13-adding-game-control-buttons/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/13-adding-game-control-buttons/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/13-adding-game-control-buttons/assets/adaptive-icon.png b/code/13-adding-game-control-buttons/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/13-adding-game-control-buttons/assets/adaptive-icon.png differ
diff --git a/code/13-adding-game-control-buttons/assets/favicon.png b/code/13-adding-game-control-buttons/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/13-adding-game-control-buttons/assets/favicon.png differ
diff --git a/code/13-adding-game-control-buttons/assets/icon.png b/code/13-adding-game-control-buttons/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/13-adding-game-control-buttons/assets/icon.png differ
diff --git a/code/13-adding-game-control-buttons/assets/images/background.png b/code/13-adding-game-control-buttons/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/13-adding-game-control-buttons/assets/images/background.png differ
diff --git a/code/13-adding-game-control-buttons/assets/splash.png b/code/13-adding-game-control-buttons/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/13-adding-game-control-buttons/assets/splash.png differ
diff --git a/code/13-adding-game-control-buttons/babel.config.js b/code/13-adding-game-control-buttons/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/13-adding-game-control-buttons/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/13-adding-game-control-buttons/components/game/NumberContainer.js b/code/13-adding-game-control-buttons/components/game/NumberContainer.js
new file mode 100644
index 00000000..a5714670
--- /dev/null
+++ b/code/13-adding-game-control-buttons/components/game/NumberContainer.js
@@ -0,0 +1,30 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ fontWeight: 'bold',
+ },
+});
diff --git a/code/13-adding-game-control-buttons/components/ui/PrimaryButton.js b/code/13-adding-game-control-buttons/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/13-adding-game-control-buttons/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/13-adding-game-control-buttons/components/ui/Title.js b/code/13-adding-game-control-buttons/components/ui/Title.js
new file mode 100644
index 00000000..265056a3
--- /dev/null
+++ b/code/13-adding-game-control-buttons/components/ui/Title.js
@@ -0,0 +1,21 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/13-adding-game-control-buttons/constants/colors.js b/code/13-adding-game-control-buttons/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/13-adding-game-control-buttons/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/13-adding-game-control-buttons/package.json b/code/13-adding-game-control-buttons/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/13-adding-game-control-buttons/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/13-adding-game-control-buttons/screens/GameOverScreen.js b/code/13-adding-game-control-buttons/screens/GameOverScreen.js
new file mode 100644
index 00000000..e69de29b
diff --git a/code/13-adding-game-control-buttons/screens/GameScreen.js b/code/13-adding-game-control-buttons/screens/GameScreen.js
new file mode 100644
index 00000000..bcfdf616
--- /dev/null
+++ b/code/13-adding-game-control-buttons/screens/GameScreen.js
@@ -0,0 +1,82 @@
+import { useState } from 'react';
+import { View, Text, StyleSheet, Alert } from 'react-native';
+
+import NumberContainer from '../components/game/NumberContainer';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber }) {
+ const initialGuess = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ userNumber
+ );
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+ Higher or lower?
+
+
+ -
+
+
+ +
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+});
diff --git a/code/13-adding-game-control-buttons/screens/StartGameScreen.js b/code/13-adding-game-control-buttons/screens/StartGameScreen.js
new file mode 100644
index 00000000..dc6f1956
--- /dev/null
+++ b/code/13-adding-game-control-buttons/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function StartGameScreen({onPickNumber}) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/14-checking-for-game-over/App.js b/code/14-checking-for-game-over/App.js
new file mode 100644
index 00000000..2e5b1333
--- /dev/null
+++ b/code/14-checking-for-game-over/App.js
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/14-checking-for-game-over/app.json b/code/14-checking-for-game-over/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/14-checking-for-game-over/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/14-checking-for-game-over/assets/adaptive-icon.png b/code/14-checking-for-game-over/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/14-checking-for-game-over/assets/adaptive-icon.png differ
diff --git a/code/14-checking-for-game-over/assets/favicon.png b/code/14-checking-for-game-over/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/14-checking-for-game-over/assets/favicon.png differ
diff --git a/code/14-checking-for-game-over/assets/icon.png b/code/14-checking-for-game-over/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/14-checking-for-game-over/assets/icon.png differ
diff --git a/code/14-checking-for-game-over/assets/images/background.png b/code/14-checking-for-game-over/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/14-checking-for-game-over/assets/images/background.png differ
diff --git a/code/14-checking-for-game-over/assets/splash.png b/code/14-checking-for-game-over/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/14-checking-for-game-over/assets/splash.png differ
diff --git a/code/14-checking-for-game-over/babel.config.js b/code/14-checking-for-game-over/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/14-checking-for-game-over/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/14-checking-for-game-over/components/game/NumberContainer.js b/code/14-checking-for-game-over/components/game/NumberContainer.js
new file mode 100644
index 00000000..a5714670
--- /dev/null
+++ b/code/14-checking-for-game-over/components/game/NumberContainer.js
@@ -0,0 +1,30 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ fontWeight: 'bold',
+ },
+});
diff --git a/code/14-checking-for-game-over/components/ui/PrimaryButton.js b/code/14-checking-for-game-over/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/14-checking-for-game-over/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/14-checking-for-game-over/components/ui/Title.js b/code/14-checking-for-game-over/components/ui/Title.js
new file mode 100644
index 00000000..265056a3
--- /dev/null
+++ b/code/14-checking-for-game-over/components/ui/Title.js
@@ -0,0 +1,21 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/14-checking-for-game-over/constants/colors.js b/code/14-checking-for-game-over/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/14-checking-for-game-over/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/14-checking-for-game-over/package.json b/code/14-checking-for-game-over/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/14-checking-for-game-over/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/14-checking-for-game-over/screens/GameOverScreen.js b/code/14-checking-for-game-over/screens/GameOverScreen.js
new file mode 100644
index 00000000..54ab09f1
--- /dev/null
+++ b/code/14-checking-for-game-over/screens/GameOverScreen.js
@@ -0,0 +1,7 @@
+import { Text } from 'react-native';
+
+function GameOverScreen() {
+ return Game is over!
+}
+
+export default GameOverScreen;
\ No newline at end of file
diff --git a/code/14-checking-for-game-over/screens/GameScreen.js b/code/14-checking-for-game-over/screens/GameScreen.js
new file mode 100644
index 00000000..d15abe3b
--- /dev/null
+++ b/code/14-checking-for-game-over/screens/GameScreen.js
@@ -0,0 +1,88 @@
+import { useState, useEffect } from 'react';
+import { View, Text, StyleSheet, Alert } from 'react-native';
+
+import NumberContainer from '../components/game/NumberContainer';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(
+ 1,
+ 100,
+ userNumber
+ );
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+ Higher or lower?
+
+
+ -
+
+
+ +
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+});
diff --git a/code/14-checking-for-game-over/screens/StartGameScreen.js b/code/14-checking-for-game-over/screens/StartGameScreen.js
new file mode 100644
index 00000000..dc6f1956
--- /dev/null
+++ b/code/14-checking-for-game-over/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function StartGameScreen({onPickNumber}) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ inputContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 100,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/15-cascading-styles/App.js b/code/15-cascading-styles/App.js
new file mode 100644
index 00000000..2e5b1333
--- /dev/null
+++ b/code/15-cascading-styles/App.js
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/15-cascading-styles/app.json b/code/15-cascading-styles/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/15-cascading-styles/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/15-cascading-styles/assets/adaptive-icon.png b/code/15-cascading-styles/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/15-cascading-styles/assets/adaptive-icon.png differ
diff --git a/code/15-cascading-styles/assets/favicon.png b/code/15-cascading-styles/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/15-cascading-styles/assets/favicon.png differ
diff --git a/code/15-cascading-styles/assets/icon.png b/code/15-cascading-styles/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/15-cascading-styles/assets/icon.png differ
diff --git a/code/15-cascading-styles/assets/images/background.png b/code/15-cascading-styles/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/15-cascading-styles/assets/images/background.png differ
diff --git a/code/15-cascading-styles/assets/splash.png b/code/15-cascading-styles/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/15-cascading-styles/assets/splash.png differ
diff --git a/code/15-cascading-styles/babel.config.js b/code/15-cascading-styles/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/15-cascading-styles/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/15-cascading-styles/components/game/NumberContainer.js b/code/15-cascading-styles/components/game/NumberContainer.js
new file mode 100644
index 00000000..a5714670
--- /dev/null
+++ b/code/15-cascading-styles/components/game/NumberContainer.js
@@ -0,0 +1,30 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ fontWeight: 'bold',
+ },
+});
diff --git a/code/15-cascading-styles/components/ui/Card.js b/code/15-cascading-styles/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/15-cascading-styles/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/15-cascading-styles/components/ui/InstructionText.js b/code/15-cascading-styles/components/ui/InstructionText.js
new file mode 100644
index 00000000..2947cca6
--- /dev/null
+++ b/code/15-cascading-styles/components/ui/InstructionText.js
@@ -0,0 +1,16 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/15-cascading-styles/components/ui/PrimaryButton.js b/code/15-cascading-styles/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/15-cascading-styles/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/15-cascading-styles/components/ui/Title.js b/code/15-cascading-styles/components/ui/Title.js
new file mode 100644
index 00000000..265056a3
--- /dev/null
+++ b/code/15-cascading-styles/components/ui/Title.js
@@ -0,0 +1,21 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/15-cascading-styles/constants/colors.js b/code/15-cascading-styles/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/15-cascading-styles/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/15-cascading-styles/package.json b/code/15-cascading-styles/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/15-cascading-styles/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/15-cascading-styles/screens/GameOverScreen.js b/code/15-cascading-styles/screens/GameOverScreen.js
new file mode 100644
index 00000000..54ab09f1
--- /dev/null
+++ b/code/15-cascading-styles/screens/GameOverScreen.js
@@ -0,0 +1,7 @@
+import { Text } from 'react-native';
+
+function GameOverScreen() {
+ return Game is over!
+}
+
+export default GameOverScreen;
\ No newline at end of file
diff --git a/code/15-cascading-styles/screens/GameScreen.js b/code/15-cascading-styles/screens/GameScreen.js
new file mode 100644
index 00000000..5591cada
--- /dev/null
+++ b/code/15-cascading-styles/screens/GameScreen.js
@@ -0,0 +1,101 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert } from 'react-native';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+ -
+
+
+
+
+ +
+
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/15-cascading-styles/screens/StartGameScreen.js b/code/15-cascading-styles/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/15-cascading-styles/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/16-working-with-icons/App.js b/code/16-working-with-icons/App.js
new file mode 100644
index 00000000..2e5b1333
--- /dev/null
+++ b/code/16-working-with-icons/App.js
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/16-working-with-icons/app.json b/code/16-working-with-icons/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/16-working-with-icons/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/16-working-with-icons/assets/adaptive-icon.png b/code/16-working-with-icons/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/16-working-with-icons/assets/adaptive-icon.png differ
diff --git a/code/16-working-with-icons/assets/favicon.png b/code/16-working-with-icons/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/16-working-with-icons/assets/favicon.png differ
diff --git a/code/16-working-with-icons/assets/icon.png b/code/16-working-with-icons/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/16-working-with-icons/assets/icon.png differ
diff --git a/code/16-working-with-icons/assets/images/background.png b/code/16-working-with-icons/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/16-working-with-icons/assets/images/background.png differ
diff --git a/code/16-working-with-icons/assets/splash.png b/code/16-working-with-icons/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/16-working-with-icons/assets/splash.png differ
diff --git a/code/16-working-with-icons/babel.config.js b/code/16-working-with-icons/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/16-working-with-icons/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/16-working-with-icons/components/game/NumberContainer.js b/code/16-working-with-icons/components/game/NumberContainer.js
new file mode 100644
index 00000000..a5714670
--- /dev/null
+++ b/code/16-working-with-icons/components/game/NumberContainer.js
@@ -0,0 +1,30 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ fontWeight: 'bold',
+ },
+});
diff --git a/code/16-working-with-icons/components/ui/Card.js b/code/16-working-with-icons/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/16-working-with-icons/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/16-working-with-icons/components/ui/InstructionText.js b/code/16-working-with-icons/components/ui/InstructionText.js
new file mode 100644
index 00000000..2947cca6
--- /dev/null
+++ b/code/16-working-with-icons/components/ui/InstructionText.js
@@ -0,0 +1,16 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/16-working-with-icons/components/ui/PrimaryButton.js b/code/16-working-with-icons/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/16-working-with-icons/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/16-working-with-icons/components/ui/Title.js b/code/16-working-with-icons/components/ui/Title.js
new file mode 100644
index 00000000..265056a3
--- /dev/null
+++ b/code/16-working-with-icons/components/ui/Title.js
@@ -0,0 +1,21 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/16-working-with-icons/constants/colors.js b/code/16-working-with-icons/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/16-working-with-icons/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/16-working-with-icons/package.json b/code/16-working-with-icons/package.json
new file mode 100644
index 00000000..5bbe216a
--- /dev/null
+++ b/code/16-working-with-icons/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/16-working-with-icons/screens/GameOverScreen.js b/code/16-working-with-icons/screens/GameOverScreen.js
new file mode 100644
index 00000000..54ab09f1
--- /dev/null
+++ b/code/16-working-with-icons/screens/GameOverScreen.js
@@ -0,0 +1,7 @@
+import { Text } from 'react-native';
+
+function GameOverScreen() {
+ return Game is over!
+}
+
+export default GameOverScreen;
\ No newline at end of file
diff --git a/code/16-working-with-icons/screens/GameScreen.js b/code/16-working-with-icons/screens/GameScreen.js
new file mode 100644
index 00000000..20c655fc
--- /dev/null
+++ b/code/16-working-with-icons/screens/GameScreen.js
@@ -0,0 +1,102 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/16-working-with-icons/screens/StartGameScreen.js b/code/16-working-with-icons/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/16-working-with-icons/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/17-adding-using-custom-font/App.js b/code/17-adding-using-custom-font/App.js
new file mode 100644
index 00000000..d573ad9a
--- /dev/null
+++ b/code/17-adding-using-custom-font/App.js
@@ -0,0 +1,70 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useFonts } from 'expo-font';
+import AppLoading from 'expo-app-loading';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+
+ const [fontsLoaded] = useFonts({
+ 'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'),
+ 'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'),
+ });
+
+ if (!fontsLoaded) {
+ return ;
+ }
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/17-adding-using-custom-font/app.json b/code/17-adding-using-custom-font/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/17-adding-using-custom-font/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/17-adding-using-custom-font/assets/adaptive-icon.png b/code/17-adding-using-custom-font/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/adaptive-icon.png differ
diff --git a/code/17-adding-using-custom-font/assets/favicon.png b/code/17-adding-using-custom-font/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/favicon.png differ
diff --git a/code/17-adding-using-custom-font/assets/fonts/OpenSans-Bold.ttf b/code/17-adding-using-custom-font/assets/fonts/OpenSans-Bold.ttf
new file mode 100755
index 00000000..96fabd86
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/fonts/OpenSans-Bold.ttf differ
diff --git a/code/17-adding-using-custom-font/assets/fonts/OpenSans-Regular.ttf b/code/17-adding-using-custom-font/assets/fonts/OpenSans-Regular.ttf
new file mode 100755
index 00000000..2d4da3a6
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/fonts/OpenSans-Regular.ttf differ
diff --git a/code/17-adding-using-custom-font/assets/icon.png b/code/17-adding-using-custom-font/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/icon.png differ
diff --git a/code/17-adding-using-custom-font/assets/images/background.png b/code/17-adding-using-custom-font/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/images/background.png differ
diff --git a/code/17-adding-using-custom-font/assets/splash.png b/code/17-adding-using-custom-font/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/17-adding-using-custom-font/assets/splash.png differ
diff --git a/code/17-adding-using-custom-font/babel.config.js b/code/17-adding-using-custom-font/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/17-adding-using-custom-font/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/17-adding-using-custom-font/components/game/NumberContainer.js b/code/17-adding-using-custom-font/components/game/NumberContainer.js
new file mode 100644
index 00000000..5168630e
--- /dev/null
+++ b/code/17-adding-using-custom-font/components/game/NumberContainer.js
@@ -0,0 +1,31 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ // fontWeight: 'bold',
+ fontFamily: 'open-sans-bold'
+ },
+});
diff --git a/code/17-adding-using-custom-font/components/ui/Card.js b/code/17-adding-using-custom-font/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/17-adding-using-custom-font/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/17-adding-using-custom-font/components/ui/InstructionText.js b/code/17-adding-using-custom-font/components/ui/InstructionText.js
new file mode 100644
index 00000000..8b69fe31
--- /dev/null
+++ b/code/17-adding-using-custom-font/components/ui/InstructionText.js
@@ -0,0 +1,17 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ fontFamily: 'open-sans',
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/17-adding-using-custom-font/components/ui/PrimaryButton.js b/code/17-adding-using-custom-font/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/17-adding-using-custom-font/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/17-adding-using-custom-font/components/ui/Title.js b/code/17-adding-using-custom-font/components/ui/Title.js
new file mode 100644
index 00000000..581d8fcf
--- /dev/null
+++ b/code/17-adding-using-custom-font/components/ui/Title.js
@@ -0,0 +1,20 @@
+import { Text, StyleSheet } from 'react-native';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontFamily: 'open-sans-bold',
+ fontSize: 24,
+ // fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/17-adding-using-custom-font/constants/colors.js b/code/17-adding-using-custom-font/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/17-adding-using-custom-font/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/17-adding-using-custom-font/package.json b/code/17-adding-using-custom-font/package.json
new file mode 100644
index 00000000..46a61e32
--- /dev/null
+++ b/code/17-adding-using-custom-font/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3",
+ "expo-font": "~10.0.4",
+ "expo-app-loading": "~1.3.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/17-adding-using-custom-font/screens/GameOverScreen.js b/code/17-adding-using-custom-font/screens/GameOverScreen.js
new file mode 100644
index 00000000..54ab09f1
--- /dev/null
+++ b/code/17-adding-using-custom-font/screens/GameOverScreen.js
@@ -0,0 +1,7 @@
+import { Text } from 'react-native';
+
+function GameOverScreen() {
+ return Game is over!
+}
+
+export default GameOverScreen;
\ No newline at end of file
diff --git a/code/17-adding-using-custom-font/screens/GameScreen.js b/code/17-adding-using-custom-font/screens/GameScreen.js
new file mode 100644
index 00000000..20c655fc
--- /dev/null
+++ b/code/17-adding-using-custom-font/screens/GameScreen.js
@@ -0,0 +1,102 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/17-adding-using-custom-font/screens/StartGameScreen.js b/code/17-adding-using-custom-font/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/17-adding-using-custom-font/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/18-using-styling-nested-text/App.js b/code/18-using-styling-nested-text/App.js
new file mode 100644
index 00000000..d573ad9a
--- /dev/null
+++ b/code/18-using-styling-nested-text/App.js
@@ -0,0 +1,70 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useFonts } from 'expo-font';
+import AppLoading from 'expo-app-loading';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+
+ const [fontsLoaded] = useFonts({
+ 'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'),
+ 'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'),
+ });
+
+ if (!fontsLoaded) {
+ return ;
+ }
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = ;
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/18-using-styling-nested-text/app.json b/code/18-using-styling-nested-text/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/18-using-styling-nested-text/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/18-using-styling-nested-text/assets/adaptive-icon.png b/code/18-using-styling-nested-text/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/adaptive-icon.png differ
diff --git a/code/18-using-styling-nested-text/assets/favicon.png b/code/18-using-styling-nested-text/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/favicon.png differ
diff --git a/code/18-using-styling-nested-text/assets/fonts/OpenSans-Bold.ttf b/code/18-using-styling-nested-text/assets/fonts/OpenSans-Bold.ttf
new file mode 100755
index 00000000..96fabd86
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/fonts/OpenSans-Bold.ttf differ
diff --git a/code/18-using-styling-nested-text/assets/fonts/OpenSans-Regular.ttf b/code/18-using-styling-nested-text/assets/fonts/OpenSans-Regular.ttf
new file mode 100755
index 00000000..2d4da3a6
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/fonts/OpenSans-Regular.ttf differ
diff --git a/code/18-using-styling-nested-text/assets/icon.png b/code/18-using-styling-nested-text/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/icon.png differ
diff --git a/code/18-using-styling-nested-text/assets/images/background.png b/code/18-using-styling-nested-text/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/images/background.png differ
diff --git a/code/18-using-styling-nested-text/assets/images/success.png b/code/18-using-styling-nested-text/assets/images/success.png
new file mode 100755
index 00000000..aae773a0
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/images/success.png differ
diff --git a/code/18-using-styling-nested-text/assets/splash.png b/code/18-using-styling-nested-text/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/18-using-styling-nested-text/assets/splash.png differ
diff --git a/code/18-using-styling-nested-text/babel.config.js b/code/18-using-styling-nested-text/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/18-using-styling-nested-text/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/18-using-styling-nested-text/components/game/NumberContainer.js b/code/18-using-styling-nested-text/components/game/NumberContainer.js
new file mode 100644
index 00000000..5168630e
--- /dev/null
+++ b/code/18-using-styling-nested-text/components/game/NumberContainer.js
@@ -0,0 +1,31 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ // fontWeight: 'bold',
+ fontFamily: 'open-sans-bold'
+ },
+});
diff --git a/code/18-using-styling-nested-text/components/ui/Card.js b/code/18-using-styling-nested-text/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/18-using-styling-nested-text/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/18-using-styling-nested-text/components/ui/InstructionText.js b/code/18-using-styling-nested-text/components/ui/InstructionText.js
new file mode 100644
index 00000000..8b69fe31
--- /dev/null
+++ b/code/18-using-styling-nested-text/components/ui/InstructionText.js
@@ -0,0 +1,17 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ fontFamily: 'open-sans',
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/18-using-styling-nested-text/components/ui/PrimaryButton.js b/code/18-using-styling-nested-text/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/18-using-styling-nested-text/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/18-using-styling-nested-text/components/ui/Title.js b/code/18-using-styling-nested-text/components/ui/Title.js
new file mode 100644
index 00000000..581d8fcf
--- /dev/null
+++ b/code/18-using-styling-nested-text/components/ui/Title.js
@@ -0,0 +1,20 @@
+import { Text, StyleSheet } from 'react-native';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontFamily: 'open-sans-bold',
+ fontSize: 24,
+ // fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/18-using-styling-nested-text/constants/colors.js b/code/18-using-styling-nested-text/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/18-using-styling-nested-text/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/18-using-styling-nested-text/package.json b/code/18-using-styling-nested-text/package.json
new file mode 100644
index 00000000..46a61e32
--- /dev/null
+++ b/code/18-using-styling-nested-text/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3",
+ "expo-font": "~10.0.4",
+ "expo-app-loading": "~1.3.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/18-using-styling-nested-text/screens/GameOverScreen.js b/code/18-using-styling-nested-text/screens/GameOverScreen.js
new file mode 100644
index 00000000..8791722c
--- /dev/null
+++ b/code/18-using-styling-nested-text/screens/GameOverScreen.js
@@ -0,0 +1,58 @@
+import { View, Image, Text, StyleSheet } from 'react-native';
+
+import Title from '../components/ui/Title';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function GameOverScreen() {
+ return (
+
+ GAME OVER!
+
+
+
+
+ Your phone needed X rounds to
+ guess the number Y.
+
+ Start New Game
+
+ );
+}
+
+export default GameOverScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ padding: 24,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ imageContainer: {
+ width: 300,
+ height: 300,
+ borderRadius: 150,
+ borderWidth: 3,
+ borderColor: Colors.primary800,
+ overflow: 'hidden',
+ margin: 36,
+ },
+ image: {
+ width: '100%',
+ height: '100%',
+ },
+ summaryText: {
+ fontFamily: 'open-sans',
+ fontSize: 24,
+ textAlign: 'center',
+ marginBottom: 24
+ },
+ highlight: {
+ fontFamily: 'open-sans-bold',
+ color: Colors.primary500,
+ },
+});
diff --git a/code/18-using-styling-nested-text/screens/GameScreen.js b/code/18-using-styling-nested-text/screens/GameScreen.js
new file mode 100644
index 00000000..20c655fc
--- /dev/null
+++ b/code/18-using-styling-nested-text/screens/GameScreen.js
@@ -0,0 +1,102 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/18-using-styling-nested-text/screens/StartGameScreen.js b/code/18-using-styling-nested-text/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/18-using-styling-nested-text/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/App.js b/code/19-adding-logic-to-restart-games/App.js
new file mode 100644
index 00000000..32d227ee
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/App.js
@@ -0,0 +1,82 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useFonts } from 'expo-font';
+import AppLoading from 'expo-app-loading';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+ const [guessRounds, setGuessRounds] = useState(0);
+
+ const [fontsLoaded] = useFonts({
+ 'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'),
+ 'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'),
+ });
+
+ if (!fontsLoaded) {
+ return ;
+ }
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ function startNewGameHandler() {
+ setUserNumber(null);
+ setGuessRounds(0);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = (
+
+ );
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/app.json b/code/19-adding-logic-to-restart-games/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/19-adding-logic-to-restart-games/assets/adaptive-icon.png b/code/19-adding-logic-to-restart-games/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/adaptive-icon.png differ
diff --git a/code/19-adding-logic-to-restart-games/assets/favicon.png b/code/19-adding-logic-to-restart-games/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/favicon.png differ
diff --git a/code/19-adding-logic-to-restart-games/assets/fonts/OpenSans-Bold.ttf b/code/19-adding-logic-to-restart-games/assets/fonts/OpenSans-Bold.ttf
new file mode 100755
index 00000000..96fabd86
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/fonts/OpenSans-Bold.ttf differ
diff --git a/code/19-adding-logic-to-restart-games/assets/fonts/OpenSans-Regular.ttf b/code/19-adding-logic-to-restart-games/assets/fonts/OpenSans-Regular.ttf
new file mode 100755
index 00000000..2d4da3a6
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/fonts/OpenSans-Regular.ttf differ
diff --git a/code/19-adding-logic-to-restart-games/assets/icon.png b/code/19-adding-logic-to-restart-games/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/icon.png differ
diff --git a/code/19-adding-logic-to-restart-games/assets/images/background.png b/code/19-adding-logic-to-restart-games/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/images/background.png differ
diff --git a/code/19-adding-logic-to-restart-games/assets/images/success.png b/code/19-adding-logic-to-restart-games/assets/images/success.png
new file mode 100755
index 00000000..aae773a0
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/images/success.png differ
diff --git a/code/19-adding-logic-to-restart-games/assets/splash.png b/code/19-adding-logic-to-restart-games/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/19-adding-logic-to-restart-games/assets/splash.png differ
diff --git a/code/19-adding-logic-to-restart-games/babel.config.js b/code/19-adding-logic-to-restart-games/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/19-adding-logic-to-restart-games/components/game/NumberContainer.js b/code/19-adding-logic-to-restart-games/components/game/NumberContainer.js
new file mode 100644
index 00000000..5168630e
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/components/game/NumberContainer.js
@@ -0,0 +1,31 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ // fontWeight: 'bold',
+ fontFamily: 'open-sans-bold'
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/components/ui/Card.js b/code/19-adding-logic-to-restart-games/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/components/ui/InstructionText.js b/code/19-adding-logic-to-restart-games/components/ui/InstructionText.js
new file mode 100644
index 00000000..8b69fe31
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/components/ui/InstructionText.js
@@ -0,0 +1,17 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ fontFamily: 'open-sans',
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/components/ui/PrimaryButton.js b/code/19-adding-logic-to-restart-games/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/components/ui/Title.js b/code/19-adding-logic-to-restart-games/components/ui/Title.js
new file mode 100644
index 00000000..581d8fcf
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/components/ui/Title.js
@@ -0,0 +1,20 @@
+import { Text, StyleSheet } from 'react-native';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontFamily: 'open-sans-bold',
+ fontSize: 24,
+ // fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/constants/colors.js b/code/19-adding-logic-to-restart-games/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/19-adding-logic-to-restart-games/package.json b/code/19-adding-logic-to-restart-games/package.json
new file mode 100644
index 00000000..46a61e32
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3",
+ "expo-font": "~10.0.4",
+ "expo-app-loading": "~1.3.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/19-adding-logic-to-restart-games/screens/GameOverScreen.js b/code/19-adding-logic-to-restart-games/screens/GameOverScreen.js
new file mode 100644
index 00000000..d7e2bb98
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/screens/GameOverScreen.js
@@ -0,0 +1,59 @@
+import { View, Image, Text, StyleSheet } from 'react-native';
+
+import Title from '../components/ui/Title';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function GameOverScreen({ roundsNumber, userNumber, onStartNewGame }) {
+ return (
+
+ GAME OVER!
+
+
+
+
+ Your phone needed {roundsNumber}{' '}
+ rounds to guess the number{' '}
+ {userNumber}.
+
+ Start New Game
+
+ );
+}
+
+export default GameOverScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ padding: 24,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ imageContainer: {
+ width: 300,
+ height: 300,
+ borderRadius: 150,
+ borderWidth: 3,
+ borderColor: Colors.primary800,
+ overflow: 'hidden',
+ margin: 36,
+ },
+ image: {
+ width: '100%',
+ height: '100%',
+ },
+ summaryText: {
+ fontFamily: 'open-sans',
+ fontSize: 24,
+ textAlign: 'center',
+ marginBottom: 24,
+ },
+ highlight: {
+ fontFamily: 'open-sans-bold',
+ color: Colors.primary500,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/screens/GameScreen.js b/code/19-adding-logic-to-restart-games/screens/GameScreen.js
new file mode 100644
index 00000000..0a01b740
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/screens/GameScreen.js
@@ -0,0 +1,107 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ useEffect(() => {
+ minBoundary = 1;
+ maxBoundary = 100;
+ }, []);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ }
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* LOG ROUNDS */}
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/19-adding-logic-to-restart-games/screens/StartGameScreen.js b/code/19-adding-logic-to-restart-games/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/19-adding-logic-to-restart-games/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/20-styling-game-round-logs/App.js b/code/20-styling-game-round-logs/App.js
new file mode 100644
index 00000000..32d227ee
--- /dev/null
+++ b/code/20-styling-game-round-logs/App.js
@@ -0,0 +1,82 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useFonts } from 'expo-font';
+import AppLoading from 'expo-app-loading';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+ const [guessRounds, setGuessRounds] = useState(0);
+
+ const [fontsLoaded] = useFonts({
+ 'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'),
+ 'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'),
+ });
+
+ if (!fontsLoaded) {
+ return ;
+ }
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler() {
+ setGameIsOver(true);
+ }
+
+ function startNewGameHandler() {
+ setUserNumber(null);
+ setGuessRounds(0);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = (
+
+ );
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/20-styling-game-round-logs/app.json b/code/20-styling-game-round-logs/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/20-styling-game-round-logs/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/20-styling-game-round-logs/assets/adaptive-icon.png b/code/20-styling-game-round-logs/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/adaptive-icon.png differ
diff --git a/code/20-styling-game-round-logs/assets/favicon.png b/code/20-styling-game-round-logs/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/favicon.png differ
diff --git a/code/20-styling-game-round-logs/assets/fonts/OpenSans-Bold.ttf b/code/20-styling-game-round-logs/assets/fonts/OpenSans-Bold.ttf
new file mode 100755
index 00000000..96fabd86
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/fonts/OpenSans-Bold.ttf differ
diff --git a/code/20-styling-game-round-logs/assets/fonts/OpenSans-Regular.ttf b/code/20-styling-game-round-logs/assets/fonts/OpenSans-Regular.ttf
new file mode 100755
index 00000000..2d4da3a6
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/fonts/OpenSans-Regular.ttf differ
diff --git a/code/20-styling-game-round-logs/assets/icon.png b/code/20-styling-game-round-logs/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/icon.png differ
diff --git a/code/20-styling-game-round-logs/assets/images/background.png b/code/20-styling-game-round-logs/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/images/background.png differ
diff --git a/code/20-styling-game-round-logs/assets/images/success.png b/code/20-styling-game-round-logs/assets/images/success.png
new file mode 100755
index 00000000..aae773a0
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/images/success.png differ
diff --git a/code/20-styling-game-round-logs/assets/splash.png b/code/20-styling-game-round-logs/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/20-styling-game-round-logs/assets/splash.png differ
diff --git a/code/20-styling-game-round-logs/babel.config.js b/code/20-styling-game-round-logs/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/20-styling-game-round-logs/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/20-styling-game-round-logs/components/game/GuessLogItem.js b/code/20-styling-game-round-logs/components/game/GuessLogItem.js
new file mode 100644
index 00000000..943e656c
--- /dev/null
+++ b/code/20-styling-game-round-logs/components/game/GuessLogItem.js
@@ -0,0 +1,36 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function GuessLogItem({ roundNumber, guess }) {
+ return (
+
+ #{roundNumber}
+ Opponent's Guess: {guess}
+
+ );
+}
+
+export default GuessLogItem;
+
+const styles = StyleSheet.create({
+ listItem: {
+ borderColor: Colors.primary800,
+ borderWidth: 1,
+ borderRadius: 40,
+ padding: 12,
+ marginVertical: 8,
+ backgroundColor: Colors.accent500,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '100%',
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 0 },
+ shadowOpacity: 0.25,
+ shadowRadius: 3,
+ },
+ itemText: {
+ fontFamily: 'open-sans'
+ }
+});
diff --git a/code/20-styling-game-round-logs/components/game/NumberContainer.js b/code/20-styling-game-round-logs/components/game/NumberContainer.js
new file mode 100644
index 00000000..5168630e
--- /dev/null
+++ b/code/20-styling-game-round-logs/components/game/NumberContainer.js
@@ -0,0 +1,31 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ // fontWeight: 'bold',
+ fontFamily: 'open-sans-bold'
+ },
+});
diff --git a/code/20-styling-game-round-logs/components/ui/Card.js b/code/20-styling-game-round-logs/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/20-styling-game-round-logs/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/20-styling-game-round-logs/components/ui/InstructionText.js b/code/20-styling-game-round-logs/components/ui/InstructionText.js
new file mode 100644
index 00000000..8b69fe31
--- /dev/null
+++ b/code/20-styling-game-round-logs/components/ui/InstructionText.js
@@ -0,0 +1,17 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ fontFamily: 'open-sans',
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/20-styling-game-round-logs/components/ui/PrimaryButton.js b/code/20-styling-game-round-logs/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/20-styling-game-round-logs/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/20-styling-game-round-logs/components/ui/Title.js b/code/20-styling-game-round-logs/components/ui/Title.js
new file mode 100644
index 00000000..581d8fcf
--- /dev/null
+++ b/code/20-styling-game-round-logs/components/ui/Title.js
@@ -0,0 +1,20 @@
+import { Text, StyleSheet } from 'react-native';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontFamily: 'open-sans-bold',
+ fontSize: 24,
+ // fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/20-styling-game-round-logs/constants/colors.js b/code/20-styling-game-round-logs/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/20-styling-game-round-logs/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/20-styling-game-round-logs/package.json b/code/20-styling-game-round-logs/package.json
new file mode 100644
index 00000000..46a61e32
--- /dev/null
+++ b/code/20-styling-game-round-logs/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3",
+ "expo-font": "~10.0.4",
+ "expo-app-loading": "~1.3.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/20-styling-game-round-logs/screens/GameOverScreen.js b/code/20-styling-game-round-logs/screens/GameOverScreen.js
new file mode 100644
index 00000000..d7e2bb98
--- /dev/null
+++ b/code/20-styling-game-round-logs/screens/GameOverScreen.js
@@ -0,0 +1,59 @@
+import { View, Image, Text, StyleSheet } from 'react-native';
+
+import Title from '../components/ui/Title';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function GameOverScreen({ roundsNumber, userNumber, onStartNewGame }) {
+ return (
+
+ GAME OVER!
+
+
+
+
+ Your phone needed {roundsNumber}{' '}
+ rounds to guess the number{' '}
+ {userNumber}.
+
+ Start New Game
+
+ );
+}
+
+export default GameOverScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ padding: 24,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ imageContainer: {
+ width: 300,
+ height: 300,
+ borderRadius: 150,
+ borderWidth: 3,
+ borderColor: Colors.primary800,
+ overflow: 'hidden',
+ margin: 36,
+ },
+ image: {
+ width: '100%',
+ height: '100%',
+ },
+ summaryText: {
+ fontFamily: 'open-sans',
+ fontSize: 24,
+ textAlign: 'center',
+ marginBottom: 24,
+ },
+ highlight: {
+ fontFamily: 'open-sans-bold',
+ color: Colors.primary500,
+ },
+});
diff --git a/code/20-styling-game-round-logs/screens/GameScreen.js b/code/20-styling-game-round-logs/screens/GameScreen.js
new file mode 100644
index 00000000..fee87ff0
--- /dev/null
+++ b/code/20-styling-game-round-logs/screens/GameScreen.js
@@ -0,0 +1,128 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert, Text, FlatList } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import GuessLogItem from '../components/game/GuessLogItem';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+ const [guessRounds, setGuessRounds] = useState([initialGuess]);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver();
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ useEffect(() => {
+ minBoundary = 1;
+ maxBoundary = 100;
+ }, []);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ setGuessRounds((prevGuessRounds) => [newRndNumber, ...prevGuessRounds]);
+ }
+
+ const guessRoundsListLength = guessRounds.length;
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* {guessRounds.map(guessRound => {guessRound})} */}
+ (
+
+ )}
+ keyExtractor={(item) => item}
+ />
+
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+ listContainer: {
+ flex: 1,
+ padding: 16,
+ },
+});
diff --git a/code/20-styling-game-round-logs/screens/StartGameScreen.js b/code/20-styling-game-round-logs/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/20-styling-game-round-logs/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/code/21-finished/App.js b/code/21-finished/App.js
new file mode 100644
index 00000000..445519f1
--- /dev/null
+++ b/code/21-finished/App.js
@@ -0,0 +1,83 @@
+import { useState } from 'react';
+import { StyleSheet, ImageBackground, SafeAreaView } from 'react-native';
+import { LinearGradient } from 'expo-linear-gradient';
+import { useFonts } from 'expo-font';
+import AppLoading from 'expo-app-loading';
+
+import StartGameScreen from './screens/StartGameScreen';
+import GameScreen from './screens/GameScreen';
+import GameOverScreen from './screens/GameOverScreen';
+import Colors from './constants/colors';
+
+export default function App() {
+ const [userNumber, setUserNumber] = useState();
+ const [gameIsOver, setGameIsOver] = useState(true);
+ const [guessRounds, setGuessRounds] = useState(0);
+
+ const [fontsLoaded] = useFonts({
+ 'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'),
+ 'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'),
+ });
+
+ if (!fontsLoaded) {
+ return ;
+ }
+
+ function pickedNumberHandler(pickedNumber) {
+ setUserNumber(pickedNumber);
+ setGameIsOver(false);
+ }
+
+ function gameOverHandler(numberOfRounds) {
+ setGameIsOver(true);
+ setGuessRounds(numberOfRounds);
+ }
+
+ function startNewGameHandler() {
+ setUserNumber(null);
+ setGuessRounds(0);
+ }
+
+ let screen = ;
+
+ if (userNumber) {
+ screen = (
+
+ );
+ }
+
+ if (gameIsOver && userNumber) {
+ screen = (
+
+ );
+ }
+
+ return (
+
+
+ {screen}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ rootScreen: {
+ flex: 1,
+ },
+ backgroundImage: {
+ opacity: 0.15,
+ },
+});
diff --git a/code/21-finished/app.json b/code/21-finished/app.json
new file mode 100644
index 00000000..9a1223e7
--- /dev/null
+++ b/code/21-finished/app.json
@@ -0,0 +1,32 @@
+{
+ "expo": {
+ "name": "RNCourse",
+ "slug": "RNCourse",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#FFFFFF"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/code/21-finished/assets/adaptive-icon.png b/code/21-finished/assets/adaptive-icon.png
new file mode 100644
index 00000000..03d6f6b6
Binary files /dev/null and b/code/21-finished/assets/adaptive-icon.png differ
diff --git a/code/21-finished/assets/favicon.png b/code/21-finished/assets/favicon.png
new file mode 100644
index 00000000..e75f697b
Binary files /dev/null and b/code/21-finished/assets/favicon.png differ
diff --git a/code/21-finished/assets/fonts/OpenSans-Bold.ttf b/code/21-finished/assets/fonts/OpenSans-Bold.ttf
new file mode 100755
index 00000000..96fabd86
Binary files /dev/null and b/code/21-finished/assets/fonts/OpenSans-Bold.ttf differ
diff --git a/code/21-finished/assets/fonts/OpenSans-Regular.ttf b/code/21-finished/assets/fonts/OpenSans-Regular.ttf
new file mode 100755
index 00000000..2d4da3a6
Binary files /dev/null and b/code/21-finished/assets/fonts/OpenSans-Regular.ttf differ
diff --git a/code/21-finished/assets/icon.png b/code/21-finished/assets/icon.png
new file mode 100644
index 00000000..a0b1526f
Binary files /dev/null and b/code/21-finished/assets/icon.png differ
diff --git a/code/21-finished/assets/images/background.png b/code/21-finished/assets/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/code/21-finished/assets/images/background.png differ
diff --git a/code/21-finished/assets/images/success.png b/code/21-finished/assets/images/success.png
new file mode 100755
index 00000000..aae773a0
Binary files /dev/null and b/code/21-finished/assets/images/success.png differ
diff --git a/code/21-finished/assets/splash.png b/code/21-finished/assets/splash.png
new file mode 100644
index 00000000..0e89705a
Binary files /dev/null and b/code/21-finished/assets/splash.png differ
diff --git a/code/21-finished/babel.config.js b/code/21-finished/babel.config.js
new file mode 100644
index 00000000..2900afe9
--- /dev/null
+++ b/code/21-finished/babel.config.js
@@ -0,0 +1,6 @@
+module.exports = function(api) {
+ api.cache(true);
+ return {
+ presets: ['babel-preset-expo'],
+ };
+};
diff --git a/code/21-finished/components/game/GuessLogItem.js b/code/21-finished/components/game/GuessLogItem.js
new file mode 100644
index 00000000..943e656c
--- /dev/null
+++ b/code/21-finished/components/game/GuessLogItem.js
@@ -0,0 +1,36 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function GuessLogItem({ roundNumber, guess }) {
+ return (
+
+ #{roundNumber}
+ Opponent's Guess: {guess}
+
+ );
+}
+
+export default GuessLogItem;
+
+const styles = StyleSheet.create({
+ listItem: {
+ borderColor: Colors.primary800,
+ borderWidth: 1,
+ borderRadius: 40,
+ padding: 12,
+ marginVertical: 8,
+ backgroundColor: Colors.accent500,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ width: '100%',
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 0 },
+ shadowOpacity: 0.25,
+ shadowRadius: 3,
+ },
+ itemText: {
+ fontFamily: 'open-sans'
+ }
+});
diff --git a/code/21-finished/components/game/NumberContainer.js b/code/21-finished/components/game/NumberContainer.js
new file mode 100644
index 00000000..5168630e
--- /dev/null
+++ b/code/21-finished/components/game/NumberContainer.js
@@ -0,0 +1,31 @@
+import { View, Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function NumberContainer({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default NumberContainer;
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 4,
+ borderColor: Colors.accent500,
+ padding: 24,
+ margin: 24,
+ borderRadius: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ numberText: {
+ color: Colors.accent500,
+ fontSize: 36,
+ // fontWeight: 'bold',
+ fontFamily: 'open-sans-bold'
+ },
+});
diff --git a/code/21-finished/components/ui/Card.js b/code/21-finished/components/ui/Card.js
new file mode 100644
index 00000000..a0ea35b1
--- /dev/null
+++ b/code/21-finished/components/ui/Card.js
@@ -0,0 +1,26 @@
+import { View, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function Card({ children }) {
+ return {children};
+}
+
+export default Card;
+
+const styles = StyleSheet.create({
+ card: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 36,
+ marginHorizontal: 24,
+ padding: 16,
+ backgroundColor: Colors.primary800,
+ borderRadius: 8,
+ elevation: 4,
+ shadowColor: 'black',
+ shadowOffset: { width: 0, height: 2 },
+ shadowRadius: 6,
+ shadowOpacity: 0.25,
+ },
+});
diff --git a/code/21-finished/components/ui/InstructionText.js b/code/21-finished/components/ui/InstructionText.js
new file mode 100644
index 00000000..8b69fe31
--- /dev/null
+++ b/code/21-finished/components/ui/InstructionText.js
@@ -0,0 +1,17 @@
+import { Text, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function InstructionText({ children, style }) {
+ return {children};
+}
+
+export default InstructionText;
+
+const styles = StyleSheet.create({
+ instructionText: {
+ fontFamily: 'open-sans',
+ color: Colors.accent500,
+ fontSize: 24,
+ },
+});
diff --git a/code/21-finished/components/ui/PrimaryButton.js b/code/21-finished/components/ui/PrimaryButton.js
new file mode 100644
index 00000000..bd963545
--- /dev/null
+++ b/code/21-finished/components/ui/PrimaryButton.js
@@ -0,0 +1,44 @@
+import { View, Text, Pressable, StyleSheet } from 'react-native';
+
+import Colors from '../../constants/colors';
+
+function PrimaryButton({ children, onPress }) {
+ return (
+
+
+ pressed
+ ? [styles.buttonInnerContainer, styles.pressed]
+ : styles.buttonInnerContainer
+ }
+ onPress={onPress}
+ android_ripple={{ color: Colors.primary600 }}
+ >
+ {children}
+
+
+ );
+}
+
+export default PrimaryButton;
+
+const styles = StyleSheet.create({
+ buttonOuterContainer: {
+ borderRadius: 28,
+ margin: 4,
+ overflow: 'hidden',
+ },
+ buttonInnerContainer: {
+ backgroundColor: Colors.primary500,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ elevation: 2,
+ },
+ buttonText: {
+ color: 'white',
+ textAlign: 'center',
+ },
+ pressed: {
+ opacity: 0.75,
+ },
+});
diff --git a/code/21-finished/components/ui/Title.js b/code/21-finished/components/ui/Title.js
new file mode 100644
index 00000000..581d8fcf
--- /dev/null
+++ b/code/21-finished/components/ui/Title.js
@@ -0,0 +1,20 @@
+import { Text, StyleSheet } from 'react-native';
+
+function Title({ children }) {
+ return {children};
+}
+
+export default Title;
+
+const styles = StyleSheet.create({
+ title: {
+ fontFamily: 'open-sans-bold',
+ fontSize: 24,
+ // fontWeight: 'bold',
+ color: 'white',
+ textAlign: 'center',
+ borderWidth: 2,
+ borderColor: 'white',
+ padding: 12,
+ },
+});
diff --git a/code/21-finished/constants/colors.js b/code/21-finished/constants/colors.js
new file mode 100644
index 00000000..d181ccea
--- /dev/null
+++ b/code/21-finished/constants/colors.js
@@ -0,0 +1,9 @@
+const Colors = {
+ primary500: '#72063c',
+ primary600: '#640233',
+ primary700: '#4e0329',
+ primary800: '#3b021f',
+ accent500: '#ddb52f'
+};
+
+export default Colors;
\ No newline at end of file
diff --git a/code/21-finished/package.json b/code/21-finished/package.json
new file mode 100644
index 00000000..46a61e32
--- /dev/null
+++ b/code/21-finished/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "rncourse",
+ "version": "1.0.0",
+ "main": "node_modules/expo/AppEntry.js",
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "web": "expo start --web",
+ "eject": "expo eject"
+ },
+ "dependencies": {
+ "expo": "~44.0.0",
+ "expo-status-bar": "~1.2.0",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-native": "0.64.3",
+ "react-native-web": "0.17.1",
+ "expo-linear-gradient": "~11.0.3",
+ "expo-font": "~10.0.4",
+ "expo-app-loading": "~1.3.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.9"
+ },
+ "private": true
+}
diff --git a/code/21-finished/screens/GameOverScreen.js b/code/21-finished/screens/GameOverScreen.js
new file mode 100644
index 00000000..d7e2bb98
--- /dev/null
+++ b/code/21-finished/screens/GameOverScreen.js
@@ -0,0 +1,59 @@
+import { View, Image, Text, StyleSheet } from 'react-native';
+
+import Title from '../components/ui/Title';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Colors from '../constants/colors';
+
+function GameOverScreen({ roundsNumber, userNumber, onStartNewGame }) {
+ return (
+
+ GAME OVER!
+
+
+
+
+ Your phone needed {roundsNumber}{' '}
+ rounds to guess the number{' '}
+ {userNumber}.
+
+ Start New Game
+
+ );
+}
+
+export default GameOverScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ padding: 24,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ imageContainer: {
+ width: 300,
+ height: 300,
+ borderRadius: 150,
+ borderWidth: 3,
+ borderColor: Colors.primary800,
+ overflow: 'hidden',
+ margin: 36,
+ },
+ image: {
+ width: '100%',
+ height: '100%',
+ },
+ summaryText: {
+ fontFamily: 'open-sans',
+ fontSize: 24,
+ textAlign: 'center',
+ marginBottom: 24,
+ },
+ highlight: {
+ fontFamily: 'open-sans-bold',
+ color: Colors.primary500,
+ },
+});
diff --git a/code/21-finished/screens/GameScreen.js b/code/21-finished/screens/GameScreen.js
new file mode 100644
index 00000000..03bd41ff
--- /dev/null
+++ b/code/21-finished/screens/GameScreen.js
@@ -0,0 +1,128 @@
+import { useState, useEffect } from 'react';
+import { View, StyleSheet, Alert, Text, FlatList } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+
+import NumberContainer from '../components/game/NumberContainer';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import GuessLogItem from '../components/game/GuessLogItem';
+
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
+
+let minBoundary = 1;
+let maxBoundary = 100;
+
+function GameScreen({ userNumber, onGameOver }) {
+ const initialGuess = generateRandomBetween(1, 100, userNumber);
+ const [currentGuess, setCurrentGuess] = useState(initialGuess);
+ const [guessRounds, setGuessRounds] = useState([initialGuess]);
+
+ useEffect(() => {
+ if (currentGuess === userNumber) {
+ onGameOver(guessRounds.length);
+ }
+ }, [currentGuess, userNumber, onGameOver]);
+
+ useEffect(() => {
+ minBoundary = 1;
+ maxBoundary = 100;
+ }, []);
+
+ function nextGuessHandler(direction) {
+ // direction => 'lower', 'greater'
+ if (
+ (direction === 'lower' && currentGuess < userNumber) ||
+ (direction === 'greater' && currentGuess > userNumber)
+ ) {
+ Alert.alert("Don't lie!", 'You know that this is wrong...', [
+ { text: 'Sorry!', style: 'cancel' },
+ ]);
+ return;
+ }
+
+ if (direction === 'lower') {
+ maxBoundary = currentGuess;
+ } else {
+ minBoundary = currentGuess + 1;
+ }
+
+ const newRndNumber = generateRandomBetween(
+ minBoundary,
+ maxBoundary,
+ currentGuess
+ );
+ setCurrentGuess(newRndNumber);
+ setGuessRounds((prevGuessRounds) => [newRndNumber, ...prevGuessRounds]);
+ }
+
+ const guessRoundsListLength = guessRounds.length;
+
+ return (
+
+ Opponent's Guess
+ {currentGuess}
+
+
+ Higher or lower?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* {guessRounds.map(guessRound => {guessRound})} */}
+ (
+
+ )}
+ keyExtractor={(item) => item}
+ />
+
+
+ );
+}
+
+export default GameScreen;
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ padding: 24,
+ },
+ instructionText: {
+ marginBottom: 12,
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+ listContainer: {
+ flex: 1,
+ padding: 16,
+ },
+});
diff --git a/code/21-finished/screens/StartGameScreen.js b/code/21-finished/screens/StartGameScreen.js
new file mode 100644
index 00000000..48b5a574
--- /dev/null
+++ b/code/21-finished/screens/StartGameScreen.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { TextInput, View, StyleSheet, Alert } from 'react-native';
+
+import PrimaryButton from '../components/ui/PrimaryButton';
+import Title from '../components/ui/Title';
+import Colors from '../constants/colors';
+import Card from '../components/ui/Card';
+import InstructionText from '../components/ui/InstructionText';
+
+function StartGameScreen({ onPickNumber }) {
+ const [enteredNumber, setEnteredNumber] = useState('');
+
+ function numberInputHandler(enteredText) {
+ setEnteredNumber(enteredText);
+ }
+
+ function resetInputHandler() {
+ setEnteredNumber('');
+ }
+
+ function confirmInputHandler() {
+ const chosenNumber = parseInt(enteredNumber);
+
+ if (isNaN(chosenNumber) || chosenNumber <= 0 || chosenNumber > 99) {
+ Alert.alert(
+ 'Invalid number!',
+ 'Number has to be a number between 1 and 99.',
+ [{ text: 'Okay', style: 'destructive', onPress: resetInputHandler }]
+ );
+ return;
+ }
+
+ onPickNumber(chosenNumber);
+ }
+
+ return (
+
+ Guess My Number
+
+
+ Enter a Number
+
+
+
+
+ Reset
+
+
+ Confirm
+
+
+
+
+ );
+}
+
+export default StartGameScreen;
+
+const styles = StyleSheet.create({
+ rootContainer: {
+ flex: 1,
+ marginTop: 100,
+ alignItems: 'center',
+ },
+ numberInput: {
+ height: 50,
+ width: 50,
+ fontSize: 32,
+ borderBottomColor: Colors.accent500,
+ borderBottomWidth: 2,
+ color: Colors.accent500,
+ marginVertical: 8,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+ buttonsContainer: {
+ flexDirection: 'row',
+ },
+ buttonContainer: {
+ flex: 1,
+ },
+});
diff --git a/extra-files/fonts/OpenSans-Bold.ttf b/extra-files/fonts/OpenSans-Bold.ttf
new file mode 100755
index 00000000..96fabd86
Binary files /dev/null and b/extra-files/fonts/OpenSans-Bold.ttf differ
diff --git a/extra-files/fonts/OpenSans-Regular.ttf b/extra-files/fonts/OpenSans-Regular.ttf
new file mode 100755
index 00000000..2d4da3a6
Binary files /dev/null and b/extra-files/fonts/OpenSans-Regular.ttf differ
diff --git a/extra-files/images/background.png b/extra-files/images/background.png
new file mode 100644
index 00000000..300c5477
Binary files /dev/null and b/extra-files/images/background.png differ
diff --git a/extra-files/images/success.png b/extra-files/images/success.png
new file mode 100755
index 00000000..aae773a0
Binary files /dev/null and b/extra-files/images/success.png differ
diff --git a/extra-files/logic/random.js b/extra-files/logic/random.js
new file mode 100644
index 00000000..a017ddfa
--- /dev/null
+++ b/extra-files/logic/random.js
@@ -0,0 +1,9 @@
+function generateRandomBetween(min, max, exclude) {
+ const rndNum = Math.floor(Math.random() * (max - min)) + min;
+
+ if (rndNum === exclude) {
+ return generateRandomBetween(min, max, exclude);
+ } else {
+ return rndNum;
+ }
+}
diff --git a/extra-files/starting-project.zip b/extra-files/starting-project.zip
new file mode 100644
index 00000000..78404b74
Binary files /dev/null and b/extra-files/starting-project.zip differ