├── assets ├── icon.png ├── splash.png ├── favicon.png ├── adaptive-icon.png └── images │ ├── emoji1.png │ ├── emoji2.png │ ├── emoji3.png │ ├── emoji4.png │ ├── emoji5.png │ ├── emoji6.png │ └── background-image.png ├── sticker-smash-assets.zip ├── babel.config.js ├── components ├── ImageViewer.js ├── IconButton.js ├── CircleButton.js ├── EmojiList.js ├── EmojiPicker.js ├── Button.js └── EmojiSticker.js ├── .gitignore ├── app.json ├── package.json ├── README.md └── App.js /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/images/emoji1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/emoji1.png -------------------------------------------------------------------------------- /assets/images/emoji2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/emoji2.png -------------------------------------------------------------------------------- /assets/images/emoji3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/emoji3.png -------------------------------------------------------------------------------- /assets/images/emoji4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/emoji4.png -------------------------------------------------------------------------------- /assets/images/emoji5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/emoji5.png -------------------------------------------------------------------------------- /assets/images/emoji6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/emoji6.png -------------------------------------------------------------------------------- /sticker-smash-assets.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/sticker-smash-assets.zip -------------------------------------------------------------------------------- /assets/images/background-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matedev01/StickerSmash/HEAD/assets/images/background-image.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | "@babel/plugin-proposal-export-namespace-from", 7 | "react-native-reanimated/plugin", 8 | ], 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /components/ImageViewer.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Image } from "react-native-web"; 2 | 3 | export default function ImageViewer({ placeholderImageSource, selectedImage }) { 4 | const imageSource = selectedImage ? { uri: selectedImage } : placeholderImageSource; 5 | return ( 6 | 7 | ); 8 | } 9 | 10 | const styles = StyleSheet.create({ 11 | image: { 12 | width: 320, 13 | height: 440, 14 | borderRadius: 18, 15 | }, 16 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /components/IconButton.js: -------------------------------------------------------------------------------- 1 | import { Pressable, StyleSheet, Text } from 'react-native'; 2 | import MaterialIcons from '@expo/vector-icons/MaterialIcons'; 3 | 4 | export default function IconButton({ icon, label, onPress }) { 5 | return ( 6 | 7 | 8 | {label} 9 | 10 | ); 11 | } 12 | 13 | const styles = StyleSheet.create({ 14 | iconButton: { 15 | justifyContent: 'center', 16 | alignItems: 'center', 17 | }, 18 | iconButtonLabel: { 19 | color: '#fff', 20 | marginTop: 12, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "StickerSmash", 4 | "slug": "StickerSmash", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /components/CircleButton.js: -------------------------------------------------------------------------------- 1 | import { View, Pressable, StyleSheet } from 'react-native'; 2 | import MaterialIcons from '@expo/vector-icons/MaterialIcons'; 3 | 4 | export default function CircleButton({ onPress }) { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | const styles = StyleSheet.create({ 15 | circleButtonContainer: { 16 | width: 84, 17 | height: 84, 18 | marginHorizontal: 60, 19 | borderWidth: 4, 20 | borderColor: '#ffd33d', 21 | borderRadius: 42, 22 | padding: 3, 23 | }, 24 | circleButton: { 25 | flex: 1, 26 | justifyContent: 'center', 27 | alignItems: 'center', 28 | borderRadius: 42, 29 | backgroundColor: '#fff', 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stickersmash", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@expo/vector-icons": "^13.0.0", 13 | "@expo/webpack-config": "^19.0.0", 14 | "expo": "~49.0.15", 15 | "expo-image-picker": "~14.3.2", 16 | "expo-status-bar": "~1.6.0", 17 | "react": "18.2.0", 18 | "react-dom": "18.2.0", 19 | "react-native": "0.72.6", 20 | "react-native-gesture-handler": "~2.12.0", 21 | "react-native-reanimated": "~3.3.0", 22 | "react-native-web": "~0.19.6", 23 | "react-native-view-shot": "3.7.0", 24 | "expo-media-library": "~15.4.1" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.20.0", 28 | "@babel/plugin-proposal-export-namespace-from": "^7.18.9" 29 | }, 30 | "private": true 31 | } 32 | -------------------------------------------------------------------------------- /components/EmojiList.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { StyleSheet, FlatList, Image, Platform, Pressable } from 'react-native'; 3 | 4 | export default function EmojiList({ onSelect, onCloseModal }) { 5 | const [emoji] = useState([ 6 | require('../assets/images/emoji1.png'), 7 | require('../assets/images/emoji2.png'), 8 | require('../assets/images/emoji3.png'), 9 | require('../assets/images/emoji4.png'), 10 | require('../assets/images/emoji5.png'), 11 | require('../assets/images/emoji6.png'), 12 | ]); 13 | 14 | return ( 15 | { 21 | return ( 22 | { 24 | onSelect(item); 25 | onCloseModal(); 26 | }}> 27 | 28 | 29 | ); 30 | }} 31 | /> 32 | ); 33 | } 34 | 35 | const styles = StyleSheet.create({ 36 | listContainer: { 37 | borderTopRightRadius: 10, 38 | borderTopLeftRadius: 10, 39 | paddingHorizontal: 20, 40 | flexDirection: 'row', 41 | alignItems: 'center', 42 | justifyContent: 'space-between', 43 | }, 44 | image: { 45 | width: 100, 46 | height: 100, 47 | marginRight: 20, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Native is a JavaScript framework for developing mobile applications. It is built on top of the React library, which is primarily used for building web user interfaces. React Native enables developers to build native mobile apps for iOS and Android platforms using a single codebase, written in JavaScript. 2 | 3 | The main advantage of using React Native is that it allows for faster development of cross-platform mobile applications. With React Native, developers can reuse their web development skills to build mobile applications. Because the codebase is written in JavaScript, developers can write code once and deploy it on multiple platforms. 4 | 5 | React Native uses a declarative approach to define the user interface of mobile applications. Instead of using platform-specific APIs, React Native components are written in JavaScript and render natively on the respective platforms. React Native also provides access to native components and APIs, allowing developers to build apps with device-specific features, such as camera, GPS, and push notifications. 6 | 7 | React Native also provides hot-reloading, which means that changes in the code are reflected immediately in the mobile app during the development process. Other features of React Native include a modular architecture, fast development cycle, and a vast ecosystem of third-party libraries and tools. 8 | 9 | React Native is widely used by developers and companies to build mobile applications. Some popular apps that were built with React Native include Facebook, Instagram, Airbnb, and Skype. 10 | 11 | ##This is React-Native Tutorial 12 | -------------------------------------------------------------------------------- /components/EmojiPicker.js: -------------------------------------------------------------------------------- 1 | import { Modal, View, Text, Pressable, StyleSheet } from 'react-native'; 2 | import MaterialIcons from '@expo/vector-icons/MaterialIcons'; 3 | 4 | export default function EmojiPicker({ isVisible, children, onClose }) { 5 | return ( 6 | 7 | 8 | 9 | Choose a sticker 10 | 11 | 12 | 13 | 14 | {children} 15 | 16 | 17 | ); 18 | } 19 | 20 | const styles = StyleSheet.create({ 21 | modalContent: { 22 | height: '25%', 23 | width: '100%', 24 | backgroundColor: '#25292e', 25 | borderTopRightRadius: 18, 26 | borderTopLeftRadius: 18, 27 | position: 'absolute', 28 | bottom: 0, 29 | }, 30 | titleContainer: { 31 | height: '16%', 32 | backgroundColor: '#464C55', 33 | borderTopRightRadius: 10, 34 | borderTopLeftRadius: 10, 35 | paddingHorizontal: 20, 36 | flexDirection: 'row', 37 | alignItems: 'center', 38 | justifyContent: 'space-between', 39 | }, 40 | title: { 41 | color: '#fff', 42 | fontSize: 16, 43 | }, 44 | pickerContainer: { 45 | flexDirection: 'row', 46 | justifyContent: 'center', 47 | alignItems: 'center', 48 | paddingHorizontal: 50, 49 | paddingVertical: 20, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /components/Button.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, View, Pressable, Text } from 'react-native'; 2 | import FontAwesome from "@expo/vector-icons/FontAwesome"; 3 | 4 | export default function Button({ label, theme, onPress }) { 5 | if (theme === "primary") { 6 | return ( 7 | 10 | 14 | 20 | {label} 21 | 22 | 23 | ); 24 | } 25 | return ( 26 | 27 | 28 | {label} 29 | 30 | 31 | ); 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | buttonContainer: { 36 | width: 320, 37 | height: 68, 38 | marginHorizontal: 20, 39 | alignItems: 'center', 40 | justifyContent: 'center', 41 | padding: 3, 42 | }, 43 | button: { 44 | borderRadius: 10, 45 | width: '100%', 46 | height: '100%', 47 | alignItems: 'center', 48 | justifyContent: 'center', 49 | flexDirection: 'row', 50 | }, 51 | buttonIcon: { 52 | paddingRight: 8, 53 | }, 54 | buttonLabel: { 55 | color: '#fff', 56 | fontSize: 16, 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /components/EmojiSticker.js: -------------------------------------------------------------------------------- 1 | import { Gesture, GestureDetector } from 'react-native-gesture-handler'; 2 | import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; 3 | 4 | export default function EmojiSticker({ imageSize, stickerSource }) { 5 | const scaleImage = useSharedValue(imageSize); 6 | const translateX = useSharedValue(0); 7 | const translateY = useSharedValue(0); 8 | 9 | const doubleTap = Gesture.Tap() 10 | .numberOfTaps(2) 11 | .onStart(() => { 12 | if (scaleImage.value !== imageSize * 2) { 13 | scaleImage.value = scaleImage.value * 2; 14 | } 15 | }); 16 | 17 | const imageStyle = useAnimatedStyle(() => { 18 | return { 19 | width: withSpring(scaleImage.value), 20 | height: withSpring(scaleImage.value), 21 | }; 22 | }); 23 | 24 | const containerStyle = useAnimatedStyle(() => { 25 | return { 26 | transform: [ 27 | { 28 | translateX: translateX.value, 29 | }, 30 | { 31 | translateY: translateY.value, 32 | }, 33 | ], 34 | }; 35 | }); 36 | 37 | const drag = Gesture.Pan() 38 | .onChange((event) => { 39 | translateX.value += event.changeX; 40 | translateY.value += event.changeY; 41 | }); 42 | return ( 43 | 44 | 45 | 46 | 51 | 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { useState, useRef } from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | import { captureRef } from 'react-native-view-shot'; 4 | import { GestureHandlerRootView } from "react-native-gesture-handler"; 5 | import { StatusBar } from 'expo-status-bar'; 6 | import * as ImagePicker from 'expo-image-picker'; 7 | import * as MediaLibrary from 'expo-media-library'; 8 | 9 | 10 | import Button from './components/Button'; 11 | import CircleButton from './components/CircleButton'; 12 | import IconButton from './components/IconButton'; 13 | import ImageViewer from './components/ImageViewer'; 14 | import EmojiPicker from "./components/EmojiPicker"; 15 | import EmojiList from './components/EmojiList'; 16 | import EmojiSticker from './components/EmojiSticker'; 17 | 18 | 19 | const PlaceholderImage = require('./assets/images/background-image.png'); 20 | 21 | export default function App() { 22 | const imageRef = useRef(); 23 | 24 | const [selectedImage, setSelectedImage] = useState(null); 25 | const [showAppOptions, setShowAppOptions] = useState(false); 26 | const [isModalVisible, setIsModalVisible] = useState(false); 27 | const [pickedEmoji, setPickedEmoji] = useState(null); 28 | const [status, requestPermission] = MediaLibrary.usePermissions(); 29 | 30 | if (status === null) { 31 | requestPermission(); 32 | } 33 | 34 | const pickImageAsync = async () => { 35 | let result = await ImagePicker.launchImageLibraryAsync({ 36 | allowsEditing: true, 37 | quality: 1, 38 | }); 39 | 40 | if (!result.canceled) { 41 | setSelectedImage(result.assets[0].uri); 42 | setShowAppOptions(true); 43 | } else { 44 | alert('You did not select any image.'); 45 | } 46 | }; 47 | 48 | const onReset = () => { 49 | setShowAppOptions(false); 50 | }; 51 | 52 | const onAddSticker = () => { 53 | setIsModalVisible(true); 54 | }; 55 | 56 | const onModalClose = () => { 57 | setIsModalVisible(false); 58 | }; 59 | 60 | const onSaveImageAsync = async () => { 61 | try { 62 | const localUri = await captureRef(imageRef, { 63 | height: 440, 64 | quality: 1, 65 | }); 66 | 67 | await MediaLibrary.saveToLibraryAsync(localUri); 68 | if (localUri) { 69 | alert("Saved!"); 70 | } 71 | } catch (e) { 72 | console.log(e); 73 | } 74 | }; 75 | 76 | 77 | return ( 78 | 79 | 80 | 81 | 85 | {pickedEmoji !== null ? : null} 86 | 87 | 88 | {showAppOptions ? ( 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ) : ( 97 | 98 |