├── .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 |
13 |
14 |
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 |
--------------------------------------------------------------------------------