├── .expo-shared └── assets.json ├── .gitignore ├── .node-version ├── App.js ├── README.md ├── android └── app │ └── src │ └── main │ └── jni │ └── CMakeLists.txt ├── app.json ├── assets ├── airhorn.mp3 ├── icon.png ├── signsample2.png └── splash.png ├── babel.config.js ├── components ├── FinalSign.js ├── FriendsButton.js ├── FriendsList.js ├── HomeScreen.js ├── Input.js ├── PhotoSign.js ├── SelectPhoto.js └── TotemScreen.js ├── package-lock.json ├── package.json └── styles.js /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler'; 2 | import React from 'react'; 3 | import { NavigationContainer } from '@react-navigation/native'; 4 | import { createStackNavigator } from '@react-navigation/stack'; 5 | 6 | import { HomeScreen } from './components/HomeScreen'; 7 | import { FriendsList } from './components/FriendsList'; 8 | import { Input } from './components/Input'; 9 | import { FinalSign } from './components/FinalSign'; 10 | // import { TotemScreen } from './components/TotemScreen'; 11 | // import { SelectPhoto } from './components/SelectPhoto'; 12 | // import { PhotoSign } from './components/PhotoSign'; 13 | 14 | export default App = () => { 15 | const Stack = createStackNavigator(); 16 | 17 | return ( 18 | 19 | 31 | 35 | 39 | 43 | {/* 47 | 51 | */} 55 | 59 | 60 | 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOTEM 2 | 3 | An app for festivals, raves and all your nights out! 4 | 5 | * Make a quick totem so your friends can find you in the crowd: type up a sign that flashes, take a picture, or use photo you already have. 6 | * It's too loud to hear your friends over the music! Let your group know you're off to get water or some air, or to see the next set. 7 | * Write up a quick, friendly message to all the people whose toes you just trampled :) 8 | * SOS mode: send out an SOS text to phone contacts, with your GPS location on Google Maps. 9 | * Air Horn: turn up the volume and blast an air horn noise to get attention! 10 | 11 | TOTEM 13 | 14 | SignSample2 16 | -------------------------------------------------------------------------------- /android/app/src/main/jni/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | # Define the library name here. 4 | project(rndiffapp_appmodules) 5 | 6 | # This file includes all the necessary to let you build your application with the New Architecture. 7 | include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake) 8 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "TOTEM", 4 | "slug": "totem", 5 | "privacy": "public", 6 | "platforms": [ 7 | "ios", 8 | "android", 9 | "web" 10 | ], 11 | "version": "1.0.0", 12 | "orientation": "portrait", 13 | "icon": "./assets/icon.png", 14 | "splash": { 15 | "image": "./assets/splash.png", 16 | "resizeMode": "contain", 17 | "backgroundColor": "#000000" 18 | }, 19 | "updates": { 20 | "fallbackToCacheTimeout": 0 21 | }, 22 | "assetBundlePatterns": [ 23 | "**/*" 24 | ], 25 | "ios": { 26 | "supportsTablet": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /assets/airhorn.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephbc/TOTEM/57033e836840cc5a451b33a2f9b93e2b036fc71b/assets/airhorn.mp3 -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephbc/TOTEM/57033e836840cc5a451b33a2f9b93e2b036fc71b/assets/icon.png -------------------------------------------------------------------------------- /assets/signsample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephbc/TOTEM/57033e836840cc5a451b33a2f9b93e2b036fc71b/assets/signsample2.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephbc/TOTEM/57033e836840cc5a451b33a2f9b93e2b036fc71b/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | 'react-native-reanimated/plugin', 7 | ], 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /components/FinalSign.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { View, Text, Animated } from 'react-native'; 3 | import { useKeepAwake } from 'expo-keep-awake'; 4 | import createStyles, { dimensions } from '../styles'; 5 | 6 | export const FinalSign = (props) => { 7 | useKeepAwake(); 8 | const signText = props.route.params.value; 9 | 10 | const [fadeAnim] = useState(new Animated.Value(1)); 11 | let tapped = false; 12 | 13 | const flashSign = () => { 14 | tapped = !tapped; 15 | if(tapped){ 16 | Animated.loop( 17 | Animated.sequence([ 18 | Animated.timing(fadeAnim, { 19 | toValue: 0, 20 | duration: 300, 21 | useNativeDriver: true 22 | }), 23 | Animated.timing(fadeAnim, { 24 | toValue: 1, 25 | duration: 300, 26 | useNativeDriver: true 27 | }) 28 | ]) 29 | ).start(); 30 | } else { 31 | Animated.timing(fadeAnim, { 32 | toValue: 1, 33 | duration: 100, 34 | useNativeDriver: true 35 | }).start(); 36 | } 37 | }; 38 | 39 | return ( 40 | 41 | 48 | = 10 ? 2 : 1} 50 | textBreakStrategy={'balanced'} 51 | adjustsFontSizeToFit={true} 52 | style={styles.sign} 53 | onPress={() => flashSign()}> 54 | {signText} 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | const styles = createStyles({ 62 | sign: { 63 | fontSize: 500, 64 | color: 'white', 65 | fontWeight: 'bold', 66 | textAlign: 'center' 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /components/FriendsButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Text, Pressable } from 'react-native' 3 | import createStyles, { colors, padding, dimensions } from '../styles'; 4 | 5 | export const FriendsButton = (props) => { 6 | const [active, setActive] = useState(true); 7 | 8 | return ( 9 | { 12 | setActive(!active) 13 | props.onPress() 14 | }} 15 | > 16 | {props.contact.name} 17 | 18 | ) 19 | }; 20 | 21 | const styles = createStyles({ 22 | contactButton: { 23 | backgroundColor: colors.secondary, 24 | padding: padding.sm, 25 | borderRadius: 25, 26 | margin: 5, 27 | alignItems: 'center', 28 | width: dimensions.fullWidth * 0.5, 29 | }, 30 | contactButtonPressed: { 31 | backgroundColor: colors.primary, 32 | padding: padding.sm, 33 | borderRadius: 15, 34 | margin: 5, 35 | alignItems: 'center', 36 | width: dimensions.fullWidth * 0.5, 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /components/FriendsList.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { View, Text, ScrollView, Pressable, TextInput } from 'react-native'; 3 | import * as Contacts from 'expo-contacts'; 4 | import * as SMS from 'expo-sms'; 5 | import * as Location from 'expo-location'; 6 | import createStyles, { colors, padding, fonts, dimensions } from '../styles'; 7 | import { FriendsButton } from './FriendsButton'; 8 | 9 | export const FriendsList = () => { 10 | const [allContacts, setAllContacts] = useState([]); 11 | const [visibleContacts, setVisibleContacts] = useState([]); 12 | const [SOScontacts, setSOSContacts] = useState([]); 13 | const [location, setLocation] = useState(null); 14 | const [searchTerm] = useState(null); 15 | 16 | const getAllContacts = async () => { 17 | const { status } = await Contacts.requestPermissionsAsync(); 18 | if (status === 'granted') { 19 | const { data } = await Contacts.getContactsAsync({ 20 | fields: [ 21 | Contacts.Fields.Name, 22 | Contacts.Fields.PhoneNumbers 23 | ], 24 | }); 25 | const hasPhoneNum = data.filter(contact => contact.phoneNumbers); 26 | const formattedContacts = hasPhoneNum.map(contact => { 27 | let contObj = {}; 28 | contObj.name = contact.name; 29 | contObj.number = contact.phoneNumbers[0].number; 30 | return contObj; 31 | }); 32 | setAllContacts(formattedContacts); 33 | setVisibleContacts(formattedContacts); 34 | } else { 35 | alert("Permission to access Contacts is required."); 36 | return; 37 | } 38 | }; 39 | 40 | const handleSearch = (text) => { 41 | if (text) { 42 | const searchFiltered = allContacts.filter(contact => contact.name.toLowerCase().includes(text.toLowerCase())); 43 | setVisibleContacts(searchFiltered); 44 | } else { 45 | setVisibleContacts(allContacts); 46 | } 47 | }; 48 | 49 | const getLocationAsync = async () => { 50 | let { status } = await Location.requestForegroundPermissionsAsync(); 51 | if (status === 'granted') { 52 | let location = await Location.getCurrentPositionAsync({}); 53 | setLocation(location); 54 | } else { 55 | alert("Permission to access Location is required."); 56 | return; 57 | } 58 | }; 59 | 60 | useEffect(() => { 61 | getAllContacts(); 62 | getLocationAsync(); 63 | }, []); 64 | 65 | const timeStamp = new Date(); 66 | 67 | const sendSOS = async () => { 68 | if (location) { 69 | await SMS.sendSMSAsync( 70 | SOScontacts, 71 | `SOS! PLEASE COME FIND ME! 72 | Timestamp: ${timeStamp.toLocaleTimeString()} 73 | My Location: https://www.google.com/maps/search/?api=1&query=${location.coords.latitude},${location.coords.longitude}` 74 | ); 75 | } else { 76 | alert("Current GPS location not found!"); 77 | return; 78 | } 79 | }; 80 | 81 | if (!allContacts.length) { 82 | return ( 83 | 84 | 85 | Sorry, no contacts found! 86 | 87 | 88 | ) 89 | } else { 90 | return ( 91 | 92 | 98 | 99 | {visibleContacts.map(contact => { 100 | return ( 101 | { 105 | if(SOScontacts.includes(contact.number)){ 106 | setSOSContacts(SOScontacts.filter(number => contact.number !== number)) 107 | } else { 108 | setSOSContacts([...SOScontacts, contact.number]) 109 | } 110 | }} 111 | /> 112 | ) 113 | })} 114 | 115 | SOScontacts.length ? styles.sendButton : styles.disabledSendButton} 118 | onPress={() => sendSOS()}> 119 | SEND SOS! 120 | 121 | 122 | ) 123 | } 124 | }; 125 | 126 | const styles = createStyles({ 127 | friendsSearchInput: { 128 | backgroundColor: 'white', 129 | color: 'black', 130 | width: dimensions.fullWidth * 0.75, 131 | fontWeight: 'bold', 132 | textAlign: 'center', 133 | padding: padding.sm, 134 | marginBottom: 15, 135 | }, 136 | sendButton: { 137 | backgroundColor: colors.primary, 138 | width: dimensions.fullWidth * 0.33, 139 | height: dimensions.fullHeight * 0.08, 140 | justifyContent: 'center', 141 | alignItems: 'center', 142 | padding: padding.sm, 143 | borderRadius: 25, 144 | margin: padding.md, 145 | }, 146 | disabledSendButton: { 147 | backgroundColor: colors.secondary, 148 | width: dimensions.fullWidth * 0.33, 149 | height: dimensions.fullHeight * 0.08, 150 | justifyContent: 'center', 151 | alignItems: 'center', 152 | padding: padding.sm, 153 | borderRadius: 25, 154 | margin: padding.md, 155 | }, 156 | sendButtonText: { 157 | fontSize: fonts.sm, 158 | color: 'white', 159 | fontWeight: 'bold', 160 | textAlign: 'center', 161 | }, 162 | }); 163 | -------------------------------------------------------------------------------- /components/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { View, Text, Pressable } from 'react-native'; 3 | import { Audio } from 'expo-av'; 4 | import createStyles, { colors } from '../styles'; 5 | 6 | export const HomeScreen = ({ navigation }) => { 7 | const [sound, setSound] = useState(); 8 | 9 | const playSound = async () => { 10 | const { sound } = await Audio.Sound.createAsync( 11 | require('../assets/airhorn.mp3') 12 | ); 13 | setSound(sound); 14 | await sound.playAsync(); 15 | }; 16 | 17 | useEffect(() => { 18 | return sound ? () => sound.unloadAsync() : undefined; 19 | }, [sound]); 20 | 21 | const buttonStyling = (pressed) => ([ 22 | styles.button, 23 | { backgroundColor: pressed ? colors.secondary : colors.primary } 24 | ]); 25 | 26 | return ( 27 | 28 | buttonStyling(pressed)} 30 | onPress={() => navigation.navigate('Input')}> 31 | MAKE A SIGN 32 | 33 | buttonStyling(pressed)} 35 | onPress={() => navigation.navigate('Friends')}> 36 | FIND FRIENDS 37 | 38 | buttonStyling(pressed)} 40 | onPress={() => playSound()}> 41 | PLAY AIR HORN 42 | 43 | 44 | ); 45 | }; 46 | 47 | const styles = createStyles(); 48 | -------------------------------------------------------------------------------- /components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TextInput, View, Text, Pressable, Keyboard } from 'react-native'; 3 | import createStyles, { colors, fonts } from '../styles'; 4 | 5 | export const Input = ({ navigation }) => { 6 | const [value, onChangeText] = React.useState(''); 7 | 8 | const buttonStyling = (pressed) => ([ 9 | styles.button, 10 | { backgroundColor: pressed ? colors.secondary : colors.primary } 11 | ]); 12 | 13 | return ( 14 | 15 | onChangeText(text)} 19 | value={value} 20 | autoCapitalize="characters" 21 | multiline={true} 22 | maxLength={25} 23 | /> 24 | 25 | {25 - value.length} characters remaining 26 | 27 | buttonStyling(pressed)} 29 | onPress={() => { 30 | Keyboard.dismiss(); 31 | if (value) { 32 | navigation.navigate('FinalSign', { value: value }); 33 | } 34 | }}> 35 | TOTEM TIME! 36 | 37 | 38 | ); 39 | }; 40 | 41 | const styles = createStyles({ 42 | textInput: { 43 | color: 'white', 44 | flex: 1, 45 | fontSize: fonts.lg, 46 | fontWeight: 'bold', 47 | textAlign: 'center', 48 | }, 49 | inputLimit: { 50 | color: 'grey', 51 | fontSize: fonts.sm, 52 | margin: 10, 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /components/PhotoSign.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { Animated } from 'react-native'; 3 | // import { useKeepAwake } from 'expo-keep-awake'; 4 | 5 | // import { 6 | // PinchGestureHandler, 7 | // RotationGestureHandler, 8 | // State, 9 | // } from 'react-native-gesture-handler'; 10 | 11 | // import styles from '../styles'; 12 | 13 | // export const PhotoSign = (props) => { 14 | // useKeepAwake(); 15 | 16 | // /* Pinching */ 17 | // let pinchRef = React.createRef(); 18 | // let baseScale = new Animated.Value(1); 19 | // let pinchScale = new Animated.Value(1); 20 | // let scale = Animated.multiply(baseScale, pinchScale); 21 | // let lastScale = 1; 22 | // let onPinchGestureEvent = Animated.event( 23 | // [{ nativeEvent: { scale: pinchScale } }], 24 | // { useNativeDriver: true } 25 | // ); 26 | 27 | // const onPinchHandlerStateChange = event => { 28 | // if (event.nativeEvent.oldState === State.ACTIVE) { 29 | // lastScale *= event.nativeEvent.scale; 30 | // baseScale.setValue(lastScale); 31 | // pinchScale.setValue(1); 32 | // } 33 | // }; 34 | 35 | // /* Rotation */ 36 | // let rotationRef = React.createRef(); 37 | // let rotate = new Animated.Value(0); 38 | // let rotateStr = rotate.interpolate({ 39 | // inputRange: [-100, 100], 40 | // outputRange: ['-100rad', '100rad'], 41 | // }); 42 | // let lastRotate = 0; 43 | // let onRotateGestureEvent = Animated.event( 44 | // [{ nativeEvent: { rotation: rotate } }], 45 | // { useNativeDriver: true } 46 | // ); 47 | 48 | // const onRotateHandlerStateChange = event => { 49 | // if (event.nativeEvent.oldState === State.ACTIVE) { 50 | // lastRotate += event.nativeEvent.rotation; 51 | // rotate.setOffset(lastRotate); 52 | // rotate.setValue(0); 53 | // } 54 | // }; 55 | 56 | // return ( 57 | // 62 | // 63 | // 68 | // 69 | // 82 | // 83 | // 84 | // 85 | // 86 | // ) 87 | // } 88 | -------------------------------------------------------------------------------- /components/SelectPhoto.js: -------------------------------------------------------------------------------- 1 | // import React, { useEffect } from 'react'; 2 | // import { Text, View, Pressable } from 'react-native'; 3 | // import * as ImagePicker from 'expo-image-picker'; 4 | // import * as MediaLibrary from 'expo-media-library'; 5 | // import { Camera } from 'expo-camera'; 6 | // import styles from '../styles.js'; 7 | 8 | // export const SelectPhoto = ({ navigation }) => { 9 | // let [selectedImage, setSelectedImage] = React.useState(null); 10 | 11 | // const openImagePickerAsync = async () => { 12 | // const { status } = await Camera.requestCameraPermissionsAsync(); 13 | // if (status !== 'granted') { 14 | // alert("Permission to access camera roll is required!"); 15 | // return; 16 | // } 17 | 18 | // let pickerResult = await ImagePicker.launchImageLibraryAsync(); 19 | // if (pickerResult.cancelled === true) { 20 | // return; 21 | // } 22 | // setSelectedImage({ localUri: pickerResult.uri }); 23 | // } 24 | 25 | // const openCameraAsync = async () => { 26 | // const { status } = await Camera.requestCameraPermissionsAsync(); 27 | // if (status !== 'granted') { 28 | // alert("Permission to access camera is required!"); 29 | // return; 30 | // } 31 | 32 | // let result = await ImagePicker.launchCameraAsync({ 33 | // allowEditing: false, 34 | // exif: true 35 | // }); 36 | // if(result.cancelled === true) { 37 | // return; 38 | // } 39 | 40 | // const asset = await MediaLibrary.createAssetAsync(result.uri); 41 | // MediaLibrary.createAlbumAsync('Totem', asset); 42 | 43 | // setSelectedImage({ localUri: result.uri }); 44 | // } 45 | 46 | // useEffect(() => { 47 | // if (selectedImage !== null) { 48 | // navigation.navigate('PhotoSign', { pic: selectedImage }); 49 | // } 50 | // }) 51 | 52 | // return ( 53 | // 54 | 55 | // pressed ? styles.pressedButton : styles.button}> 58 | // CHOOSE FROM GALLERY 59 | // 60 | 61 | // pressed ? styles.pressedButton : styles.button}> 64 | // TAKE A PHOTO 65 | // 66 | 67 | // 68 | // ); 69 | // } 70 | -------------------------------------------------------------------------------- /components/TotemScreen.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { View, Text, Pressable } from 'react-native'; 3 | // import styles from '../styles'; 4 | 5 | // export const TotemScreen = ({ navigation }) => { 6 | // return ( 7 | // 8 | 9 | // pressed ? styles.pressedButton : styles.button} 11 | // onPress={() => navigation.navigate('Input')}> 12 | // TEXT SIGN 13 | // 14 | 15 | // pressed ? styles.pressedButton : styles.button} 17 | // onPress={() => navigation.navigate('PHOTO')}> 18 | // PICTURE SIGN 19 | // 20 | 21 | // 22 | // ); 23 | // }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@react-native-community/masked-view": "^0.1.11", 12 | "@react-navigation/native": "^5.9.8", 13 | "@react-navigation/stack": "^5.14.9", 14 | "expo": "^50.0.17", 15 | "expo-av": "~13.10.6", 16 | "expo-camera": "~14.1.3", 17 | "expo-contacts": "~12.8.2", 18 | "expo-image-picker": "~14.7.1", 19 | "expo-keep-awake": "~12.8.2", 20 | "expo-location": "~16.5.5", 21 | "expo-media-library": "~15.9.2", 22 | "expo-sms": "~11.7.1", 23 | "expo-updates": "~0.24.12", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "react-native": "0.73.6", 27 | "react-native-gesture-handler": "~2.14.0", 28 | "react-native-reanimated": "~3.6.2", 29 | "react-native-safe-area-context": "4.8.2", 30 | "react-native-screens": "~3.29.0", 31 | "react-native-web": "~0.19.6", 32 | "react-native-webview": "13.6.4", 33 | "watchman": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.19.3", 37 | "babel-preset-expo": "^10.0.0" 38 | }, 39 | "private": true 40 | } 41 | -------------------------------------------------------------------------------- /styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Dimensions } from 'react-native'; 2 | 3 | export const dimensions = { 4 | fullHeight: Dimensions.get('window').height, 5 | fullWidth: Dimensions.get('window').width 6 | }; 7 | 8 | export const colors = { 9 | primary: '#9792e3', // active 10 | secondary: '#b5b1eb', // inactive 11 | }; 12 | 13 | export const padding = { 14 | sm: 10, 15 | md: 25, 16 | }; 17 | 18 | export const fonts = { 19 | sm: 18, 20 | md: 25, 21 | lg: 70, 22 | } 23 | 24 | const baseStyles = { 25 | view: { 26 | flex: 1, 27 | flexDirection: 'column', 28 | alignItems: 'center', 29 | justifyContent: 'center', 30 | alignContent: 'center', 31 | backgroundColor: 'black', 32 | padding: padding.sm, 33 | }, 34 | button: { 35 | backgroundColor: colors.primary, 36 | width: dimensions.fullWidth * 0.6, 37 | height: dimensions.fullHeight * 0.1, 38 | justifyContent: 'center', 39 | alignItems: 'center', 40 | padding: padding.md, 41 | borderRadius: 50, 42 | margin: 25, 43 | }, 44 | buttonText: { 45 | fontSize: fonts.sm, 46 | color: 'white', 47 | fontWeight: 'bold', 48 | textAlign: 'center', 49 | }, 50 | Text: { 51 | color: 'white', 52 | fontSize: fonts.sm, 53 | fontWeight: 'bold', 54 | margin: padding.sm, 55 | }, 56 | // pinchableImage: { 57 | // height: fullHeight, 58 | // resizeMode: "contain", 59 | // }, 60 | // wrapper: { 61 | // flex: 1, 62 | // justifyContent: 'flex-end', 63 | // backgroundColor: 'black', 64 | // } 65 | // }, 66 | }; 67 | 68 | export default function createStyles(overrides = {}) { 69 | return StyleSheet.create({...baseStyles, ...overrides}) 70 | }; 71 | --------------------------------------------------------------------------------