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