├── App.js ├── README.md ├── __tests__ └── App-test.js ├── _universe ├── app.json ├── entry.js ├── rn-cli.config.js └── transformer.js ├── api ├── GooglePoly.js └── registerForPushNotificationsAsync.js ├── app.json ├── assets ├── fonts │ └── SpaceMono-Regular.ttf ├── images │ ├── icon.png │ ├── robot-dev.png │ ├── robot-prod.png │ └── splash.png └── objects │ └── TurkeyObject.json ├── components ├── .DS_Store ├── AppComponents │ ├── GooglePolyAsset.js │ ├── SearchableGooglePolyAssetList.js │ └── index.js └── __tests__ │ └── StyledText-test.js ├── constants └── Colors.js ├── navigation ├── MainTabNavigator.js └── RootNavigation.js ├── package.json ├── screens └── HomeScreen.js └── util ├── MTLLoader.js └── OBJLoader.js /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native'; 3 | import { AppLoading, Asset, Font } from 'expo'; 4 | import { Ionicons } from '@expo/vector-icons'; 5 | import RootNavigation from './navigation/RootNavigation'; 6 | 7 | export default class App extends React.Component { 8 | state = { 9 | isLoadingComplete: false, 10 | }; 11 | 12 | render() { 13 | if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) { 14 | return ( 15 | 20 | ); 21 | } else { 22 | return ( 23 | 24 | {Platform.OS === 'ios' && } 25 | {Platform.OS === 'android' && } 26 | 27 | 28 | ); 29 | } 30 | } 31 | 32 | _loadResourcesAsync = async () => { 33 | return Promise.all([ 34 | Asset.loadAsync([ 35 | require('./assets/images/robot-dev.png'), 36 | require('./assets/images/robot-prod.png'), 37 | ]), 38 | Font.loadAsync({ 39 | // This is the font that we are using for our tab bar 40 | ...Ionicons.font, 41 | // We include SpaceMono because we use it in HomeScreen.js. Feel free 42 | // to remove this if you are not using it in your app 43 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'), 44 | }), 45 | ]); 46 | }; 47 | 48 | _handleLoadingError = error => { 49 | // In this case, you might want to report the error to your error 50 | // reporting service, for example Sentry 51 | console.warn(error); 52 | }; 53 | 54 | _handleFinishLoading = () => { 55 | this.setState({ isLoadingComplete: true }); 56 | }; 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | flex: 1, 62 | backgroundColor: '#fff', 63 | }, 64 | statusBarUnderlay: { 65 | height: 24, 66 | backgroundColor: 'rgba(0,0,0,0.2)', 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PolySnap 2 | Augmented Reality application using Expo and Google Poly 3 | -------------------------------------------------------------------------------- /__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import App from '../App'; 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders the loading screen', async () => { 7 | const tree = renderer.create().toJSON(); 8 | expect(tree).toMatchSnapshot(); 9 | }); 10 | 11 | it('renders the root without loading screen', async () => { 12 | const tree = renderer.create().toJSON(); 13 | expect(tree).toMatchSnapshot(); 14 | }); 15 | -------------------------------------------------------------------------------- /_universe/app.json: -------------------------------------------------------------------------------- 1 | { 2 | expo: { 3 | name: "My New Project", 4 | description: "No description", 5 | slug: "my-new-project", 6 | privacy: "unlisted", 7 | sdkVersion: "UNVERSIONED", 8 | version: "1.0.0", 9 | orientation: "portrait", 10 | primaryColor: "#cccccc", 11 | icon: "./assets/images/icon.png", 12 | splash: { 13 | image: "./assets/images/splash.png", 14 | resizeMode: "contain", 15 | backgroundColor: "#ffffff" 16 | }, 17 | ios: { 18 | supportsTablet: true 19 | }, 20 | packagerOpts: { 21 | config: "./_universe/rn-cli.config.js" 22 | }, 23 | entryPoint: "_universe/entry.js" 24 | } 25 | } -------------------------------------------------------------------------------- /_universe/entry.js: -------------------------------------------------------------------------------- 1 | import Expo from 'expo'; 2 | import App from '../App.js'; 3 | 4 | Expo.registerRootComponent(App); 5 | -------------------------------------------------------------------------------- /_universe/rn-cli.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @noflow 3 | */ 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | let metroBundlerRequirePath; 9 | const rootPath = path.resolve('.'); 10 | const nodeModulePath = path.join(rootPath, 'node_modules'); 11 | const reactNativeModulePath = path.join(nodeModulePath, 'react-native'); 12 | const stats = fs.lstatSync(reactNativeModulePath); 13 | if (stats.isSymbolicLink()) { 14 | metroBundlerRequirePath = 'react-native/node_modules/metro-bundler'; 15 | } else { 16 | metroBundlerRequirePath = 'metro-bundler'; 17 | } 18 | 19 | const blacklist = require(metroBundlerRequirePath + '/src/blacklist'); 20 | const babelRegisterOnly = require(metroBundlerRequirePath + '/src/babelRegisterOnly'); 21 | 22 | const registeredTransformModulePaths = new Set(); 23 | 24 | module.exports = { 25 | getBlacklistRE() { 26 | return blacklist([/__internal__\/(.*)/]); 27 | }, 28 | getProvidesModuleNodeModules() { 29 | return []; 30 | }, 31 | getTransformModulePath() { 32 | const modulePath = path.join(__dirname, 'transformer.js'); 33 | if (!registeredTransformModulePaths.has(modulePath)) { 34 | babelRegisterOnly([modulePath]); 35 | registeredTransformModulePaths.add(modulePath); 36 | } 37 | return modulePath; 38 | }, 39 | extraNodeModules: getNodeModulesForDirectory(path.resolve('.')), 40 | }; 41 | 42 | function getNodeModulesForDirectory(rootPath) { 43 | const nodeModulePath = path.join(rootPath, 'node_modules'); 44 | const folders = fs.readdirSync(nodeModulePath); 45 | return folders.reduce((modules, folderName) => { 46 | const folderPath = path.join(nodeModulePath, folderName); 47 | if (folderName.startsWith('@')) { 48 | const scopedModuleFolders = fs.readdirSync(folderPath); 49 | const scopedModules = scopedModuleFolders.reduce((scopedModules, scopedFolderName) => { 50 | scopedModules[`${folderName}/${scopedFolderName}`] = maybeResolveSymlink( 51 | path.join(folderPath, scopedFolderName) 52 | ); 53 | return scopedModules; 54 | }, {}); 55 | return Object.assign({}, modules, scopedModules); 56 | } 57 | modules[folderName] = maybeResolveSymlink(folderPath); 58 | return modules; 59 | }, {}); 60 | } 61 | 62 | function maybeResolveSymlink(maybeSymlinkPath) { 63 | if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) { 64 | const resolved = path.resolve( 65 | path.dirname(maybeSymlinkPath), 66 | fs.readlinkSync(maybeSymlinkPath) 67 | ); 68 | return resolved; 69 | } 70 | return maybeSymlinkPath; 71 | } 72 | -------------------------------------------------------------------------------- /_universe/transformer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * Note: This is a fork of the fb-specific transform.js 10 | * 11 | */ 12 | 'use strict'; 13 | 14 | /** 15 | * [Expo] This transformer was based on React Native's transformer with the 16 | * following changes: 17 | * - Makes the packager use react-native-lab's copy of react-native 18 | * - Rewrites the paths of this module's dependencies so we load the 19 | * dependencies from react-native-lab's copy of react-native, to simulate 20 | * if we hadn't forked the transformer at all 21 | */ 22 | 23 | const fs = require('fs'); 24 | const path = require('path'); 25 | 26 | let moduleBasePath = ''; 27 | let isLinked = false; 28 | const rootPath = path.resolve('.'); 29 | const nodeModulePath = path.join(rootPath, 'node_modules'); 30 | const reactNativeModulePath = path.join(nodeModulePath, 'react-native'); 31 | const stats = fs.lstatSync(reactNativeModulePath); 32 | if (stats.isSymbolicLink()) { 33 | isLinked = true; 34 | moduleBasePath = path.join(reactNativeModulePath, 'node_modules') + path.sep; 35 | } 36 | 37 | const babel = require(moduleBasePath + 'babel-core'); 38 | const crypto = require('crypto'); 39 | const externalHelpersPlugin = require(moduleBasePath + 'babel-plugin-external-helpers'); 40 | const generate = require(moduleBasePath + 'babel-generator').default; 41 | const inlineRequiresPlugin = require(moduleBasePath + 'babel-preset-fbjs/plugins/inline-requires'); 42 | const makeHMRConfig = require(moduleBasePath + 'babel-preset-react-native/configs/hmr'); 43 | const resolvePlugins = require(moduleBasePath + 'babel-preset-react-native/lib/resolvePlugins'); 44 | 45 | const { compactMapping } = require(moduleBasePath + 'metro-bundler/src/Bundler/source-map'); 46 | 47 | const cacheKeyParts = [ 48 | fs.readFileSync(__filename), 49 | require(moduleBasePath + 'babel-plugin-external-helpers/package.json').version, 50 | require(moduleBasePath + 'babel-preset-fbjs/package.json').version, 51 | require(moduleBasePath + 'babel-preset-react-native/package.json').version, 52 | ]; 53 | 54 | const EXPO_REACT_NATIVE_PATH = isLinked 55 | ? path.join(process.env.EXPO_UNIVERSE_DIR, 'react-native-lab', 'react-native') 56 | : reactNativeModulePath; 57 | if (!fs.existsSync(EXPO_REACT_NATIVE_PATH)) { 58 | throw new Error( 59 | `Expo copy of React Native could not be found. Are you sure it exists at: ${EXPO_REACT_NATIVE_PATH}?` 60 | ); 61 | } 62 | let EXPO_REACT_PATH = isLinked 63 | ? path.join(EXPO_REACT_NATIVE_PATH, 'node_modules/react') 64 | : path.join(nodeModulePath, 'react'); 65 | if (!fs.existsSync(EXPO_REACT_PATH)) { 66 | throw new Error( 67 | `React Native's "react" peer could not be found. Are you sure it exists at: ${EXPO_REACT_PATH}?` 68 | ); 69 | } 70 | 71 | /** 72 | * Return a memoized function that checks for the existence of a 73 | * project level .babelrc file, and if it doesn't exist, reads the 74 | * default RN babelrc file and uses that. 75 | */ 76 | const getBabelRC = (function() { 77 | let babelRC = null; 78 | 79 | return function _getBabelRC(projectRoot) { 80 | if (babelRC !== null) { 81 | return babelRC; 82 | } 83 | 84 | babelRC = { plugins: [] }; 85 | 86 | // Let's look for the .babelrc in the project root. 87 | // In the future let's look into adding a command line option to specify 88 | // this location. 89 | let projectBabelRCPath; 90 | if (projectRoot) { 91 | projectBabelRCPath = path.resolve(projectRoot, '.babelrc'); 92 | } 93 | 94 | // If a .babelrc file doesn't exist in the project, 95 | // use the Babel config provided with react-native. 96 | if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) { 97 | babelRC = { 98 | presets: [require('babel-preset-react-native')], 99 | plugins: [], 100 | }; 101 | 102 | // Require the babel-preset's listed in the default babel config 103 | // $FlowFixMe: dynamic require can't be avoided 104 | babelRC.presets = babelRC.presets.map(preset => require('babel-preset-' + preset)); 105 | babelRC.plugins = resolvePlugins(babelRC.plugins); 106 | } else { 107 | // if we find a .babelrc file we tell babel to use it 108 | babelRC.extends = projectBabelRCPath; 109 | } 110 | 111 | return babelRC; 112 | }; 113 | })(); 114 | 115 | /** 116 | * Given a filename and options, build a Babel 117 | * config object with the appropriate plugins. 118 | */ 119 | function buildBabelConfig(filename, options) { 120 | const babelRC = getBabelRC(options.projectRoot); 121 | 122 | const extraConfig = { 123 | // [Expo] We add the module resolver plugin (as a preset) to make sure 124 | // we're looking up peer deps (like react-native and react) in the 125 | // right place 126 | presets: [...(babelRC.presets || []), buildModuleResolverPreset()], 127 | // [Expo] When running in universe, we don't want to disable 128 | // babelRC lookup for dependencies 129 | babelrc: false, 130 | code: false, 131 | filename, 132 | }; 133 | 134 | let config = Object.assign({}, babelRC, extraConfig); 135 | 136 | // Add extra plugins 137 | const extraPlugins = [externalHelpersPlugin]; 138 | 139 | var inlineRequires = options.inlineRequires; 140 | var blacklist = typeof inlineRequires === 'object' ? inlineRequires.blacklist : null; 141 | if (inlineRequires && !(blacklist && filename in blacklist)) { 142 | extraPlugins.push(inlineRequiresPlugin); 143 | } 144 | 145 | config.plugins = extraPlugins.concat(config.plugins); 146 | 147 | if (options.dev && options.hot) { 148 | const hmrConfig = makeHMRConfig(options, filename); 149 | config = Object.assign({}, config, hmrConfig); 150 | } 151 | 152 | return Object.assign({}, babelRC, config); 153 | } 154 | 155 | function transform({ filename, options, src }) { 156 | options = options || { platform: '', projectRoot: '', inlineRequires: false }; 157 | 158 | const OLD_BABEL_ENV = process.env.BABEL_ENV; 159 | process.env.BABEL_ENV = options.dev ? 'development' : 'production'; 160 | 161 | try { 162 | const babelConfig = buildBabelConfig(filename, options); 163 | const { ast, ignored } = babel.transform(src, babelConfig); 164 | 165 | if (ignored) { 166 | return { 167 | ast: null, 168 | code: src, 169 | filename, 170 | map: null, 171 | }; 172 | } else { 173 | const result = generate( 174 | ast, 175 | { 176 | comments: false, 177 | compact: false, 178 | filename, 179 | retainLines: !!options.retainLines, 180 | sourceFileName: filename, 181 | sourceMaps: true, 182 | }, 183 | src 184 | ); 185 | 186 | return { 187 | ast, 188 | code: result.code, 189 | filename, 190 | map: options.generateSourceMaps ? result.map : result.rawMappings.map(compactMapping), 191 | }; 192 | } 193 | } catch (e) { 194 | console.error(e); 195 | throw e; 196 | } finally { 197 | process.env.BABEL_ENV = OLD_BABEL_ENV; 198 | } 199 | } 200 | 201 | /** 202 | * [Expo] Returns an Expo-internal Babel preset for aliasing react-native and 203 | * react imports 204 | */ 205 | function buildModuleResolverPreset() { 206 | return { 207 | plugins: [ 208 | [ 209 | require('babel-plugin-module-resolver').default, 210 | { 211 | alias: { 212 | react: EXPO_REACT_PATH, 213 | 'react-native': EXPO_REACT_NATIVE_PATH, 214 | }, 215 | }, 216 | ], 217 | ], 218 | }; 219 | } 220 | 221 | function getCacheKey() { 222 | var key = crypto.createHash('md5'); 223 | cacheKeyParts.forEach(part => key.update(part)); 224 | return key.digest('hex'); 225 | } 226 | 227 | module.exports = { 228 | transform, 229 | getCacheKey, 230 | }; 231 | -------------------------------------------------------------------------------- /api/GooglePoly.js: -------------------------------------------------------------------------------- 1 | 2 | import ExpoTHREE from 'expo-three'; 3 | import AssetUtils from 'expo-asset-utils'; 4 | import * as THREE from 'three'; 5 | require("./../util/OBJLoader"); 6 | require("./../util/MTLLoader"); 7 | 8 | export default class GooglePoly { 9 | 10 | constructor(apiKey) { 11 | this.apiKey = apiKey; 12 | this.currentResults = []; 13 | this.nextPageToken = ""; 14 | this.keywords = ""; 15 | } 16 | 17 | // Returns a query URL based on the given data... 18 | static getQueryURL(apiKey, keywords, nextPageToken) { 19 | var baseURL = "https://poly.googleapis.com/v1/assets?"; 20 | 21 | var url = baseURL + "key=" + apiKey; 22 | url += "&pageSize=10"; 23 | url += "&maxComplexity=MEDIUM"; 24 | url += "&format=OBJ"; 25 | if (keywords) { url += "&keywords=" + encodeURIComponent(keywords); } 26 | if (nextPageToken) { url += "&pageToken=" + nextPageToken; } 27 | return url; 28 | } 29 | 30 | // Sets current search parameters and resets member variables... 31 | setSearchParams = (keywords) => { 32 | this.currentResults = []; 33 | this.nextPageToken = ""; 34 | this.keywords = keywords; 35 | } 36 | 37 | // Returns the results of the current query... 38 | getSearchResults() { 39 | var url = GooglePoly.getQueryURL(this.apiKey, this.keywords, this.nextPageToken); 40 | 41 | return fetch(url) 42 | .then(function(response) { return response.json(); }) 43 | .then(function(data) { 44 | this.currentResults = this.currentResults.concat(data.assets); 45 | this.nextPageToken = data.nextPageToken; 46 | 47 | return Promise.resolve(data.assets); 48 | }.bind(this)); 49 | } 50 | 51 | // Returns a Three.js object 52 | static getThreeModel(objectData, success, failure) { 53 | if (!success) { success = function() { }; } 54 | if (!failure) { failure = function() { }; } 55 | if (!objectData) { failure("objectData is null"); return; } 56 | 57 | // Search for a format... 58 | var format = objectData.formats.find(format => { return format.formatType == "OBJ"; }); 59 | if (format === undefined) { failure("No format found"); return; } 60 | 61 | // Search for a resource... 62 | var obj = format.root; 63 | var mtl = format.resources.find(resource => { return resource.url.endsWith("mtl"); }); 64 | var tex = format.resources.find(resource => { return resource.url.endsWith("png"); }); 65 | var path = obj.url.slice(0, obj.url.indexOf(obj.relativePath)); 66 | 67 | // Load the MTL... 68 | var loader = new THREE.MTLLoader(); 69 | loader.setCrossOrigin(true); 70 | loader.setTexturePath(path); 71 | loader.load(mtl.url, function(materials) { 72 | 73 | // Load the OBJ... 74 | loader = new THREE.OBJLoader(); 75 | loader.setMaterials(materials); 76 | loader.load(obj.url, async function(object) { 77 | 78 | // If there is a texture, apply it... 79 | if (tex !== undefined) { 80 | var texUri = await AssetUtils.uriAsync(tex.url); 81 | var texture = new THREE.MeshBasicMaterial({ map: await ExpoTHREE.loadAsync(texUri) }); 82 | object.traverse((child) => { 83 | if (child instanceof THREE.Mesh) { 84 | child.material = texture; 85 | } 86 | }); 87 | } 88 | 89 | // Return the object... 90 | success(object); 91 | }); 92 | }); 93 | } 94 | } -------------------------------------------------------------------------------- /api/registerForPushNotificationsAsync.js: -------------------------------------------------------------------------------- 1 | import { Constants, Permissions, Notifications } from 'expo'; 2 | 3 | // Example server, implemented in Rails: https://git.io/vKHKv 4 | const PUSH_ENDPOINT = 'https://expo-push-server.herokuapp.com/tokens'; 5 | 6 | export default (async function registerForPushNotificationsAsync() { 7 | // Remote notifications do not work in simulators, only on device 8 | if (!Constants.isDevice) { 9 | return; 10 | } 11 | 12 | // Android remote notification permissions are granted during the app 13 | // install, so this will only ask on iOS 14 | let { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS); 15 | 16 | // Stop here if the user did not grant permissions 17 | if (status !== 'granted') { 18 | return; 19 | } 20 | 21 | // Get the token that uniquely identifies this device 22 | let token = await Notifications.getExpoPushTokenAsync(); 23 | 24 | // POST the token to our backend so we can use it to send pushes from there 25 | return fetch(PUSH_ENDPOINT, { 26 | method: 'POST', 27 | headers: { 28 | Accept: 'application/json', 29 | 'Content-Type': 'application/json', 30 | }, 31 | body: JSON.stringify({ 32 | token: { 33 | value: token, 34 | }, 35 | }), 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "poly-snap", 4 | "description": "A very interesting project.", 5 | "slug": "poly-snap", 6 | "privacy": "public", 7 | "sdkVersion": "25.0.0", 8 | "platforms": ["ios", "android"], 9 | "version": "1.0.0", 10 | "orientation": "portrait", 11 | "icon": "./assets/images/icon.png", 12 | "splash": { 13 | "image": "./assets/images/splash.png", 14 | "resizeMode": "contain", 15 | "backgroundColor": "#ffffff" 16 | }, 17 | "ios": { 18 | "supportsTablet": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/robot-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/robot-dev.png -------------------------------------------------------------------------------- /assets/images/robot-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/robot-prod.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/splash.png -------------------------------------------------------------------------------- /assets/objects/TurkeyObject.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"assets/6_2gGwLWWHN", 3 | "displayName":"Happy Turkey Day!", 4 | "authorName":"Zan Isgett", 5 | "description":"#thanksgiving #turkey #pilgrim #holiday", 6 | "createTime":"2017-11-06T21:08:08.975118Z", 7 | "updateTime":"2018-03-20T09:26:47.062381Z", 8 | "formats":[{"root":{"relativePath":"model.obj","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/2fjlcvDtM61/model.obj","contentType":"text/plain"},"resources":[{"relativePath":"materials.mtl","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/2fjlcvDtM61/materials.mtl","contentType":"text/plain"}],"formatComplexity":{"triangleCount":"2145"},"formatType":"OBJ"},{"root":{"relativePath":"model-triangulated.obj","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/a9hzk3RXgZ1/model-triangulated.obj","contentType":"text/plain"},"resources":[{"relativePath":"materials.mtl","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/a9hzk3RXgZ1/materials.mtl","contentType":"text/plain"}],"formatComplexity":{"triangleCount":"3724"},"formatType":"OBJ"},{"root":{"relativePath":"model.fbx","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/5yj8Kskb-QE/model.fbx","contentType":"application/octet-stream"},"formatComplexity":{},"formatType":"FBX"},{"root":{"relativePath":"model.gltf","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/5tDSnSpY5Nw/model.gltf","contentType":"model/gltf+json"},"resources":[{"relativePath":"model.bin","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/5tDSnSpY5Nw/model.bin","contentType":"application/octet-stream"}],"formatComplexity":{"triangleCount":"3724"},"formatType":"GLTF"}], 9 | "thumbnail": { 10 | "relativePath":"aWLD8imIWmk.png", 11 | "url":"https://lh3.googleusercontent.com/WK4clNLrfVxwv2pljofKuuDKlpYqZkF6uHLZrjqmTFBMEFGm-lDYUzwBbltCMbyWmA", 12 | "contentType":"image/png" 13 | }, 14 | "license":"CREATIVE_COMMONS_BY","visibility":"PUBLIC","isCurated":true,"presentationParams":{"orientingRotation":{"w":1},"colorSpace":"LINEAR"} 15 | } -------------------------------------------------------------------------------- /components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/components/.DS_Store -------------------------------------------------------------------------------- /components/AppComponents/GooglePolyAsset.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'; 4 | 5 | export default class GooglePolyAsset extends React.Component { 6 | 7 | static defaultProps = { 8 | asset: { }, 9 | onPress: function(asset) { }, 10 | } 11 | 12 | render() { 13 | return ( 14 | this.props.onPress(this.props.asset)}> 15 | 16 | {this.props.asset.displayName} 17 | {this.props.asset.authorName} 18 | 19 | ); 20 | } 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | container: { padding: 10, }, 25 | thumbnail: { width: 150, height: 150, borderRadius: 10, }, 26 | displayName: { fontWeight: "bold", textAlign: "center", width: 150, }, 27 | authorName: { textAlign: "center", width: 150, }, 28 | }) -------------------------------------------------------------------------------- /components/AppComponents/SearchableGooglePolyAssetList.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { StyleSheet, View, Text, TextInput, ScrollView, Button } from 'react-native'; 4 | import { MaterialCommunityIcons as Icon } from 'react-native-vector-icons'; 5 | import GooglePolyAsset from './GooglePolyAsset'; 6 | 7 | export default class SearchableGooglePolyAssetList extends React.Component { 8 | 9 | static defaultProps = { 10 | googlePoly: { }, 11 | onCancelPress: function() { }, 12 | onAssetPress: function(asset) { }, 13 | } 14 | 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | searchQuery: "", 19 | currentResults: this.props.googlePoly.currentResults, 20 | } 21 | } 22 | 23 | onSearchPress = () => { 24 | var keywords = this.state.searchQuery; 25 | this.props.googlePoly.setSearchParams(keywords); 26 | 27 | this.props.googlePoly.getSearchResults().then(function(assets) { 28 | this.setState({currentResults: this.props.googlePoly.currentResults }); 29 | }.bind(this)); 30 | } 31 | 32 | onLoadMorePress = () => { 33 | this.props.googlePoly.getSearchResults().then(function(assets) { 34 | this.setState({currentResults: this.props.googlePoly.currentResults }); 35 | }.bind(this)); 36 | } 37 | 38 | onSearchChangeText = (text) => { 39 | this.setState({searchQuery: text}); 40 | } 41 | 42 | renderSearchInput() { 43 | return ( 44 | 45 | 46 | 47 | 54 | 55 |