├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── app.example.json ├── babel.config.js ├── config ├── firestore.indexes.json ├── firestore.rules ├── options.example.json └── storage.rules ├── firebase.example.json ├── functions ├── .gitignore ├── expo.js ├── index.js ├── package-lock.json └── package.json ├── package-lock.json ├── package.json └── src ├── App.js ├── components ├── AddItemScreen.js ├── CameraView.js ├── Comments.js ├── ConvertAnonymousUser.js ├── EmojiOverlay.js ├── ItemScreen.js ├── Login.js ├── MapScreen.js └── Settings.js ├── containers ├── AddItemScreen.js ├── AuthContainer.js ├── ConvertAnonymousUser.js ├── DataContainer.js ├── ItemScreen.js ├── MapScreen.js ├── Navigator.js ├── NotificationContainer.js └── Settings.js └── lib ├── firebase.js ├── geo.js ├── options.js ├── theme.js ├── urlToBlob.js ├── usernameGenerator.js └── wrapInTry.js /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | /app.json 9 | /firebase.json 10 | /config/options.json 11 | assets/**/* 12 | firebase-debug.log 13 | .firebaserc 14 | node_modules 15 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import App from './src/App' 2 | export default App 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-community-maps 2 | Boilerplate app to geographically map user content, built on top of the awesome [Expo toolchain](https://docs.expo.io/versions/latest/) 3 | 4 |  5 | 6 | Create your own app (in minutes!) to map interesting things around your neighborhood. Maybe it's your fav graffiti, or a really nice sofa that needs a new home. Other users are notified in real time and can comment on each post. 7 | 8 | ## Get started 9 | 1. Fork and clone this repo 10 | 2. Install package depenencies: run `npm i && cd functions && npm i && cd ..` 11 | 3. Use the `*.example.*` files to create you own versions of `app.json`, `firebase.json`, and `config/options.json` 12 | 4. Setup the backend: 13 | 1. Visit the [firebase console](https://console.firebase.google.com) and create a new project, 14 | 2. Select Authentication and enable Email/Password and Anonymous signin methods 15 | 3. Select Database and create a Firestore database 16 | 4. Select Storage and click Get Started 17 | 5. Install the [firebase cli](https://firebase.google.com/docs/cli/) 18 | 6. Run `firebase setup:web` and copy the printed config variables into `firebase.json` (don't overwrite existing rule paths) 19 | 7. Run `firebase deploy` 20 | 5. Run `expo start` 21 | 22 | ## Options 23 | `app.json` - this is your standard [expo config file](https://docs.expo.io/versions/latest/workflow/configuration/). You'll need to customize this before publishing. 24 | 25 | `config/options.json` - Everything related to the look-and-feel of the app, and which features are enabled, comes from this file. 26 | 27 | |name|default|description| 28 | |----|-------|-----------| 29 | |`defaultEmoji`|🍔|the default emoji for new items| 30 | |`noEmojiPicker`|`false`|disallow users to pick their own emoji for new items| 31 | |`newMsgPlaceholder`|['Say something...']|Array of placeholder strings to be chosen from at random| 32 | 33 | 34 | 35 | #### Projects using react-native-community-maps 36 | [Sofab](https://itunes.apple.com/us/app/sofab/id1455005485) 37 | -------------------------------------------------------------------------------- /app.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "YourAppName", 4 | "slug": "your-app-slug", 5 | "privacy": "public", 6 | "sdkVersion": "31.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android" 10 | ], 11 | "version": "1.0.0", 12 | "orientation": "portrait", 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "bundleIdentifier": "your.bundle.id", 21 | "buildNumber": "1", 22 | "infoPlist": { 23 | "NSCameraUsageDescription": "Upload pictures of what you find", 24 | "NSLocationWhenInUseUsageDescription": "Tell users where you took the picture" 25 | } 26 | }, 27 | "android": { 28 | "package": "your.package.id" 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | env: { 6 | production: { 7 | plugins: ['react-native-paper/babel'], 8 | }, 9 | }, 10 | }; 11 | }; -------------------------------------------------------------------------------- /config/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /config/firestore.rules: -------------------------------------------------------------------------------- 1 | // Allow read/write access on all documents to any user signed in to the application 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | match /{document=**} { 5 | allow read, write: if request.auth.uid != null; 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /config/options.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "itemEmoji": "🍔", 3 | "placeholders": [ "Say something..." ], 4 | "noEmojiPicker": false, 5 | "shareUrl": "https://your-project-subdomain-goes-here.cloudfunctions.net/getItem/{id}" 6 | } -------------------------------------------------------------------------------- /config/storage.rules: -------------------------------------------------------------------------------- 1 | // Only authenticated users can read or write to the bucket 2 | service firebase.storage { 3 | match /b/{bucket}/o { 4 | match /{allPaths=**} { 5 | allow read, write: if request.auth != null; 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /firebase.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "xxxxxxxxxxx", 3 | "authDomain": "xxxxxxxxxxx", 4 | "databaseURL": "xxxxxxxxxxx", 5 | "projectId": "xxxxxxxxxxx", 6 | "storageBucket": "xxxxxxxxxxx", 7 | "messagingSenderId": "xxxxxxxxxxx", 8 | "firestore": { 9 | "rules": "config/firestore.rules", 10 | "indexes": "config/firestore.indexes.json" 11 | }, 12 | "storage": { 13 | "rules": "config/storage.rules" 14 | } 15 | } -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /functions/expo.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | const PUSH_URL = 'https://exp.host/--/api/v2/push/send' 4 | 5 | module.exports.sendPushNotification = payload => { 6 | return axios({ 7 | url: PUSH_URL, 8 | method: 'POST', 9 | data: payload, 10 | responseType: 'json' 11 | }) 12 | } -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions') 2 | const { GeoFirestore } = require('geofirestore') 3 | 4 | const { sendPushNotification } = require('./expo') 5 | 6 | const express = require('express'); 7 | 8 | const admin = require('firebase-admin'); 9 | admin.initializeApp(); 10 | 11 | const firestore = admin.firestore() 12 | const geofirestore = new GeoFirestore(firestore); 13 | const notificationPreferences = geofirestore.collection('notificationPreferences') 14 | 15 | 16 | exports.onCreateItem = functions.firestore 17 | .document('items/{itemId}') 18 | .onCreate((snap, context) => { 19 | const data = snap.data() 20 | 21 | // get nearby users 22 | return notificationPreferences 23 | // this doesnt work for some reason. the link that it returns to create the requried index does nothing. 24 | // .where('shouldNotify', '==', true) 25 | .near({ center: data.coordinates, radius: 10000 }) 26 | .get() 27 | .then(result => { 28 | 29 | const notificationQueue = [] 30 | 31 | // filter users that should be notified 32 | result.docs.forEach(doc => { 33 | const { 34 | shouldNotify, 35 | pushToken, 36 | radius, 37 | } = doc.data() 38 | 39 | if ( 40 | shouldNotify && // do they want to be notified? 41 | pushToken && // do we have a push token for them? 42 | doc.distance*1000 < radius // is this item within the their notification radius? 43 | ) notificationQueue.push(pushToken) // if so, add it to the queue. 44 | }) 45 | 46 | const promises = notificationQueue.map(token => 47 | sendPushNotification({ 48 | to: token, 49 | title: "New item available", 50 | data, 51 | }) 52 | ) 53 | 54 | return Promise.all(promises) 55 | .then(() => true) 56 | }) 57 | }) 58 | 59 | 60 | const app = express(); 61 | app.get('/:id', (req, res) => { 62 | return firestore 63 | .collection('items') 64 | .doc(req.params.id) 65 | .get() 66 | .then(doc => { 67 | if (!doc.exists) 68 | return res.status(404).send('You just got 404\'d!!') 69 | const data = doc.data() 70 | res.status(200).send(` 71 | 72 |
73 |