├── .babelrc ├── .editorconfig ├── .gitignore ├── .watchmanconfig ├── App.js ├── actions ├── bull.js ├── currencies.js ├── currency.js ├── graphs.js ├── language.js ├── navigation.js ├── news.js ├── portfolio.js ├── search.js └── theme.js ├── api ├── currencies.js ├── graphs.js ├── images.js ├── news.js └── push-notifications.js ├── app.json ├── assets ├── fonts │ └── SpaceMono-Regular.ttf └── icons │ ├── application.png │ ├── notification-icon.png │ └── watermark.png ├── components ├── adverts │ └── button.js ├── bull │ ├── 404.js │ ├── header.js │ ├── overview.js │ └── refresh.js ├── converter │ └── header.js ├── currencies │ ├── header.js │ ├── item.js │ └── load-all.js ├── errors │ └── ajax.js ├── graphs │ ├── axis-y.js │ ├── bar.js │ ├── header.js │ └── tree.js ├── navigations │ └── tabbar-bottom.js ├── news │ ├── header.js │ └── refresh.js ├── portfolio │ ├── header.js │ ├── item.js │ └── modal-add.js ├── search │ ├── icon.js │ └── input.js └── utilities │ ├── back.js │ ├── button.js │ ├── code.js │ ├── header-action.js │ ├── headings.js │ ├── integer.js │ ├── loader.js │ ├── notification.js │ └── sections.js ├── configuration ├── adverts.js ├── application.js ├── database.js ├── environment.js └── store.js ├── constants ├── bull.js ├── currencies.js ├── currency.js ├── graphs.js ├── language.js ├── navigation.js ├── news.js ├── portfolio.js ├── search.js └── theme.js ├── middleware ├── analytics.js ├── currency.js ├── language.js ├── portfolio.js └── theme.js ├── mock ├── currencies.js └── graphs.js ├── navigations └── router.js ├── package-lock.json ├── package.json ├── properties ├── currencies.js ├── device.js ├── exchanges.js ├── languages.js ├── languages │ ├── chinese.js │ ├── english.js │ ├── french.js │ ├── german.js │ ├── malay.js │ ├── spanish.js │ ├── turkish.js │ └── vietnamese.js ├── themes.js └── themes │ ├── amazon.js │ ├── default.js │ ├── facebook.js │ ├── google.js │ ├── heineken.js │ ├── killbill.js │ ├── lagoon.js │ ├── lego.js │ ├── marley.js │ ├── matrix.js │ ├── mcdonalds.js │ ├── midnight.js │ ├── twitter.js │ └── windows.js ├── readme.md ├── reducers ├── bull.js ├── currencies.js ├── currency.js ├── graphs.js ├── index.js ├── language.js ├── navigation.js ├── news.js ├── portfolio.js ├── search.js └── theme.js ├── schematics ├── currency.js ├── graphs.js └── news.js ├── screens ├── bull.js ├── converter.js ├── currencies.js ├── currency.js ├── detail.js ├── donate.js ├── exchanges.js ├── language.js ├── languages.js ├── main.js ├── news.js ├── portfolio.js ├── settings.js ├── theme.js └── themes.js ├── styles ├── adverts.js ├── bull.js ├── button.js ├── code.js ├── converter.js ├── currencies.js ├── detail.js ├── errors.js ├── graphs.js ├── header.js ├── headings.js ├── help.js ├── integer.js ├── layout.js ├── list-control.js ├── list.js ├── loader.js ├── main.js ├── modal.js ├── news.js ├── notification.js ├── portfolio.js ├── push-notifications.js ├── scene.js ├── search.js ├── section.js ├── seperators.js ├── stripe.js └── tabbar.js └── utilities ├── __tests__ └── cache.js ├── analytics.js ├── array.js ├── cache.js ├── colors.js ├── numbers.js ├── routes.js └── string.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : [ 3 | "babel-preset-expo" 4 | ] , 5 | "env" : { 6 | "development" : { 7 | "plugins" : [ 8 | "transform-react-jsx-source" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # EditorConfig is awesome: http://EditorConfig.org 3 | root = true 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/**/* 3 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { AppLoading , 5 | Constants , 6 | FacebookAds } from 'expo'; 7 | import Sentry from 'sentry-expo'; 8 | import { Ionicons } from '@expo/vector-icons'; 9 | import Main from './screens/main'; 10 | import currency from './actions/currency'; 11 | import language from './actions/language'; 12 | import theme from './actions/theme'; 13 | import portfolio from './actions/portfolio'; 14 | import application from './configuration/application'; 15 | import database from './configuration/database'; 16 | import configuration from './configuration/store'; 17 | import analytics from './utilities/analytics'; 18 | import cache from './utilities/cache'; 19 | 20 | Sentry.config ( application.sentry ).install (); 21 | 22 | const store = configuration (); 23 | 24 | export default class Application extends React.Component { 25 | 26 | state = { 27 | cache : false 28 | }; 29 | 30 | async setCache () { 31 | 32 | try { 33 | 34 | await cache.assets ({ 35 | images : [] , 36 | fonts : [ 37 | Ionicons.font , 38 | { 39 | 'space-mono' : require ( './assets/fonts/SpaceMono-Regular.ttf' ) 40 | } 41 | ] 42 | }); 43 | } 44 | 45 | catch ( error ) { 46 | console.log ( error.message ); 47 | } 48 | 49 | finally { 50 | 51 | this.setState ({ 52 | cache : true 53 | }); 54 | } 55 | } 56 | 57 | componentWillMount () { 58 | 59 | this.setCache (); 60 | 61 | // Setup the local databases 62 | database.portfolio.setup (); 63 | database.settings.setup (); 64 | 65 | // Setup the analytics 66 | analytics.setup (); 67 | 68 | // Get any data from the local databases 69 | store.dispatch ( theme.get ()); 70 | store.dispatch ( language.get ()); 71 | store.dispatch ( portfolio.get ()); 72 | 73 | // Getting a users preferred currency is the catalyst to kicking off the correct calls to the currencies. 74 | // Once we know this we know what currency conversion to use 75 | store.dispatch ( currency.get ()); 76 | 77 | // Send test ads to the device if in development mode 78 | if ( __DEV__ ) { 79 | 80 | FacebookAds.AdSettings.addTestDevice ( FacebookAds.AdSettings.currentDeviceHash ); 81 | } 82 | } 83 | 84 | render () { 85 | 86 | if ( this.state.cache ) { 87 | 88 | return ( 89 | 90 | 91 |
92 | 93 | ); 94 | } 95 | 96 | else { 97 | 98 | return ; 99 | } 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /actions/bull.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/bull'; 3 | 4 | export default { 5 | 6 | error ( data ) { 7 | 8 | return { 9 | error : data , 10 | type : constants.error 11 | }; 12 | } , 13 | 14 | get () { 15 | 16 | return { 17 | loading : true , 18 | type : constants.get 19 | }; 20 | } , 21 | 22 | set ( currencies ) { 23 | 24 | return { 25 | currencies : currencies , 26 | loading : false , 27 | type : constants.set 28 | }; 29 | } 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /actions/currencies.js: -------------------------------------------------------------------------------- 1 | 2 | import api from '../api/currencies'; 3 | import constants from '../constants/currencies'; 4 | import bull from '../actions/bull'; 5 | import schematic from '../schematics/currency'; 6 | import environment from '../configuration/environment'; 7 | 8 | const currencies = { 9 | 10 | error ( data ) { 11 | 12 | return { 13 | error : data , 14 | type : constants.error 15 | }; 16 | } , 17 | 18 | get () { 19 | 20 | return { 21 | type : constants.get 22 | }; 23 | } , 24 | 25 | order ( order ) { 26 | 27 | return { 28 | order : order , 29 | type : constants.order 30 | }; 31 | } , 32 | 33 | set ( data ) { 34 | 35 | return { 36 | items : data , 37 | type : constants.set 38 | }; 39 | } 40 | 41 | } , 42 | 43 | callbacks = { 44 | 45 | // Dispatch an error 46 | error ( data , dispatch ) { 47 | 48 | dispatch ( bull.error ( data )); 49 | dispatch ( currencies.error ( data )); 50 | } , 51 | 52 | // Dispatch that we are getting data 53 | get ( dispatch ) { 54 | 55 | dispatch ( currencies.get ()); 56 | dispatch ( bull.get ()); 57 | } , 58 | 59 | // Rewrite the API response to our data schema 60 | normalise ( data , dispatch , currency ) { 61 | 62 | const normalised = schematic.get ( data , currency ); 63 | 64 | dispatch ( currencies.set ( normalised )); 65 | dispatch ( bull.set ( normalised )); 66 | } , 67 | 68 | // Return correct response depending on environment 69 | response ( response ) { 70 | 71 | return environment.data.mock ? response : response.json (); 72 | } 73 | }; 74 | 75 | export default { 76 | 77 | order : currencies.order , 78 | 79 | get ( currency ) { 80 | 81 | return ( dispatch ) => { 82 | 83 | callbacks.get ( dispatch ); 84 | 85 | // Get the currencies 86 | return api.get ( currency ) 87 | .then ( callbacks.response ) 88 | .then (( data ) => callbacks.normalise ( data , dispatch , currency )) 89 | .catch (( data ) => callbacks.error ( data , dispatch )); 90 | } 91 | } , 92 | 93 | stream ( currency ) { 94 | 95 | return ( dispatch ) => { 96 | 97 | callbacks.get ( dispatch ); 98 | 99 | // Get the currencies 100 | return api.stream ( currency ) 101 | .then ( callbacks.response ) 102 | .then (( data ) => callbacks.normalise ( data , dispatch , currency )) 103 | .catch (( data ) => callbacks.error ( data , dispatch )); 104 | } 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /actions/currency.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/currency'; 3 | 4 | export default { 5 | 6 | get () { 7 | 8 | return { 9 | type : constants.get 10 | }; 11 | } , 12 | 13 | save ( id ) { 14 | 15 | return { 16 | id : id , 17 | type : constants.save 18 | }; 19 | } , 20 | 21 | set ( id ) { 22 | 23 | return { 24 | id : id , 25 | type : constants.set 26 | }; 27 | } 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /actions/graphs.js: -------------------------------------------------------------------------------- 1 | 2 | import api from '../api/graphs'; 3 | import constants from '../constants/graphs'; 4 | import environment from '../configuration/environment'; 5 | import schematic from '../schematics/graphs'; 6 | 7 | const graphs = { 8 | 9 | error ( data ) { 10 | 11 | return { 12 | error : data , 13 | type : constants.error 14 | }; 15 | } , 16 | 17 | get () { 18 | 19 | return { 20 | type : constants.get 21 | }; 22 | } , 23 | 24 | set ( data ) { 25 | 26 | return { 27 | market : data.market , 28 | prices : data.prices , 29 | type : constants.set 30 | }; 31 | } 32 | 33 | }; 34 | 35 | export default { 36 | 37 | get ( id ) { 38 | 39 | return ( dispatch ) => { 40 | 41 | dispatch ( graphs.get ()); 42 | 43 | // Get the currencies 44 | return api.get ( id ) 45 | 46 | // Transform the reponse 47 | .then (( response ) => { 48 | 49 | return environment.data.mock ? response : response.json (); 50 | 51 | }) 52 | 53 | // Dispatch the data 54 | .then (( data ) => { 55 | 56 | const normalised = schematic.get ( data ); 57 | 58 | dispatch ( graphs.set ( normalised )); 59 | 60 | }) 61 | 62 | // Or dispatch an error 63 | .catch ( function ( data ) { 64 | 65 | dispatch ( graphs.error ( data )); 66 | 67 | }); 68 | } 69 | } , 70 | 71 | reset () { 72 | 73 | return { 74 | type : constants.reset 75 | }; 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /actions/language.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/language'; 3 | 4 | export default { 5 | 6 | get () { 7 | 8 | return { 9 | type : constants.get 10 | }; 11 | } , 12 | 13 | save ( id ) { 14 | 15 | return { 16 | id : id , 17 | type : constants.save 18 | }; 19 | } , 20 | 21 | set ( id ) { 22 | 23 | return { 24 | id : id , 25 | type : constants.set 26 | }; 27 | } 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /actions/navigation.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/navigation'; 3 | 4 | export default { 5 | 6 | navigate ( previous , current ) { 7 | 8 | return { 9 | previous : previous , 10 | current : current , 11 | type : constants.navigate 12 | }; 13 | } 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /actions/news.js: -------------------------------------------------------------------------------- 1 | 2 | import api from '../api/news'; 3 | import constants from '../constants/news'; 4 | import schematic from '../schematics/news'; 5 | 6 | const news = { 7 | 8 | error ( data ) { 9 | 10 | return { 11 | error : data , 12 | type : constants.error 13 | }; 14 | } , 15 | 16 | get () { 17 | 18 | return { 19 | type : constants.get 20 | }; 21 | } , 22 | 23 | set ( data ) { 24 | 25 | return { 26 | items : data , 27 | type : constants.set 28 | }; 29 | } 30 | }; 31 | 32 | export default { 33 | 34 | get () { 35 | 36 | return ( dispatch ) => { 37 | 38 | dispatch ( news.get ()); 39 | 40 | // Get the currencies 41 | return api.get () 42 | 43 | // Transform the reponse 44 | .then (( response ) => response.text ()) 45 | 46 | // Dispatch the data 47 | .then (( data ) => { 48 | 49 | const normalised = schematic.get ( data ); 50 | 51 | dispatch ( news.set ( normalised )); 52 | 53 | }) 54 | 55 | // Or dispatch an error 56 | .catch (( data ) => dispatch ( news.error ( data ))); 57 | } 58 | } 59 | }; -------------------------------------------------------------------------------- /actions/portfolio.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/portfolio'; 3 | 4 | export default { 5 | 6 | delete ( id ) { 7 | 8 | return { 9 | id : id , 10 | type : constants.delete 11 | }; 12 | } , 13 | 14 | reset ( id ) { 15 | 16 | return { 17 | id : id , 18 | type : constants.reset 19 | }; 20 | } , 21 | 22 | get () { 23 | 24 | return { 25 | type : constants.get 26 | }; 27 | } , 28 | 29 | save ( id , amount , name ) { 30 | 31 | return { 32 | amount : amount , 33 | id : id , 34 | name : name , 35 | type : constants.save 36 | }; 37 | } , 38 | 39 | set ( id , amount , name ) { 40 | 41 | return { 42 | amount : amount , 43 | id : id , 44 | name : name , 45 | type : constants.set 46 | }; 47 | } , 48 | 49 | setup ( items ) { 50 | 51 | return { 52 | items : items , 53 | type : constants.setup 54 | }; 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /actions/search.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/search'; 3 | 4 | export default { 5 | 6 | on ( boolean ) { 7 | 8 | return { 9 | value : boolean , 10 | type : constants.on 11 | }; 12 | } , 13 | 14 | set ( value ) { 15 | 16 | return { 17 | value : value , 18 | type : constants.set 19 | }; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /actions/theme.js: -------------------------------------------------------------------------------- 1 | 2 | import constants from '../constants/theme'; 3 | 4 | export default { 5 | 6 | get () { 7 | 8 | return { 9 | type : constants.get 10 | }; 11 | } , 12 | 13 | save ( id ) { 14 | 15 | return { 16 | id : id , 17 | type : constants.save 18 | }; 19 | } , 20 | 21 | set ( id ) { 22 | 23 | return { 24 | id : id , 25 | type : constants.set 26 | }; 27 | } 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /api/currencies.js: -------------------------------------------------------------------------------- 1 | 2 | import environment from '../configuration/environment'; 3 | import currencies from '../mock/currencies'; 4 | 5 | const limit = 100 , 6 | api = { 7 | domain : 'https://api.coinmarketcap.com' , 8 | path :'/v1/ticker/' , 9 | params : { 10 | limit : 'limit=' , 11 | currency : 'convert=' 12 | } , 13 | headers : { 14 | Accept : 'application/json' , 15 | headers : { 16 | 'Content-Type' : 'application/json' 17 | } 18 | } 19 | }; 20 | 21 | export default { 22 | 23 | get : async function ( currency ) { 24 | 25 | const url = api.domain + api.path + '?' + api.params.limit + limit + '&' + api.params.currency + currency; 26 | 27 | return environment.data.mock ? currencies : fetch ( url , { 28 | ...api.headers , 29 | method : 'GET' 30 | }); 31 | } , 32 | 33 | limit : limit , 34 | 35 | stream : async function ( currency ) { 36 | 37 | const url = api.domain + api.path + '?' + api.params.limit + '0&' + api.params.currency + currency; 38 | 39 | return environment.data.mock ? currencies : fetch ( url , { 40 | ...api.headers , 41 | method : 'GET' 42 | }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /api/graphs.js: -------------------------------------------------------------------------------- 1 | 2 | import environment from '../configuration/environment'; 3 | import graphs from '../mock/graphs'; 4 | 5 | const api = { 6 | domain : 'https://graphs2.coinmarketcap.com' , 7 | path :'/currencies/' , 8 | headers : { 9 | Accept : 'application/json' , 10 | headers : { 11 | 'Content-Type' : 'application/json' 12 | } 13 | } 14 | }; 15 | 16 | export default { 17 | 18 | get : async function ( id ) { 19 | 20 | return ( environment.data.mock ? graphs : fetch ( api.domain + api.path + id , { 21 | ...api.headers , 22 | method : 'GET' 23 | })); 24 | } 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /api/images.js: -------------------------------------------------------------------------------- 1 | 2 | const api = { 3 | 4 | domain : 'https://files.coinmarketcap.com' , 5 | 6 | icons : { 7 | 8 | small : '/static/img/coins/16x16/' , 9 | medium : '/static/img/coins/32x32/' , 10 | large : '/static/img/coins/64x64/' 11 | 12 | } 13 | }; 14 | 15 | export default { 16 | 17 | currencies : { 18 | 19 | small ( id ) { 20 | 21 | return api.domain + api.icons.small + id + '.png'; 22 | } , 23 | 24 | medium ( id ) { 25 | 26 | return api.domain + api.icons.medium + id + '.png'; 27 | } , 28 | 29 | large ( id ) { 30 | 31 | return api.domain + api.icons.large + id + '.png'; 32 | } 33 | } 34 | }; -------------------------------------------------------------------------------- /api/news.js: -------------------------------------------------------------------------------- 1 | 2 | const api = { 3 | domain : 'https://feeds.feedburner.com' , 4 | path :'/CoinDesk' , 5 | params : { 6 | format : '?format=xml' 7 | } , 8 | headers : { 9 | Accept : 'text/xml' , 10 | headers : { 11 | 'Content-Type' : 'text/xml' 12 | } 13 | } 14 | }; 15 | 16 | export default { 17 | 18 | get : async function () { 19 | 20 | const url = api.domain + api.path + api.params.format; 21 | 22 | return fetch ( url , { 23 | ...api.headers , 24 | method : 'GET' 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /api/push-notifications.js: -------------------------------------------------------------------------------- 1 | 2 | import { Permissions , 3 | Notifications } from 'expo'; 4 | 5 | // Example server, implemented in Rails: https://git.io/vKHKv 6 | const api = { 7 | doman : 'https://exponent-push-server.herokuapp.com' , 8 | path :'/tokens' , 9 | headers : { 10 | Accept : 'application/json' , 11 | headers : { 12 | 'Content-Type' : 'application/json' 13 | } 14 | } 15 | } 16 | 17 | export default { 18 | 19 | setup : async function () { 20 | 21 | // Android remote notification permissions are granted during the app 22 | // install, so this will only ask on iOS 23 | let { status } = await Permissions.askAsync ( Permissions.REMOTE_NOTIFICATIONS ) , 24 | token; 25 | 26 | // Stop here if the user did not grant permissions 27 | if ( status !== 'granted ') { 28 | return; 29 | } 30 | 31 | // Get the token that uniquely identifies this device 32 | token = await Notifications.getExponentPushTokenAsync (); 33 | 34 | // POST the token to our backend so we can use it to send pushes from there 35 | return fetch ( api.domain + api.path , { 36 | ...headers , 37 | body : JSON.stringify ({ 38 | token : { 39 | value : token 40 | } 41 | }) , 42 | method : 'POST' 43 | }); 44 | } 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo" : { 3 | "name" : "Cryptobullography", 4 | "description" : "The latest Bull on crytpo coins and blockchain tokens.", 5 | "slug" : "bullet", 6 | "privacy" : "public", 7 | "sdkVersion" : "20.0.0", 8 | "version" : "6.1.0", 9 | "orientation" : "portrait", 10 | "primaryColor" : "#000", 11 | "icon" : "./assets/icons/application.png", 12 | "notification" : { 13 | "icon" : "./assets/icons/notification-icon.png", 14 | "color" : "#dcdcdc" 15 | }, 16 | "loading" : { 17 | "backgroundColor" : "#333", 18 | "backgroundImage" : "", 19 | "icon" : "./assets/icons/watermark.png", 20 | "hideExponentText" : true 21 | }, 22 | "packagerOpts" : { 23 | "assetExts" : ["ttf"] 24 | }, 25 | "android": { 26 | "package": "com.webstew.bullet" , 27 | "versionCode": 5 28 | } , 29 | "ios" : { 30 | "bundleIdentifier": "com.webstew.bullet" , 31 | "supportsTablet" : true 32 | } , 33 | "hooks": { 34 | "postPublish": [ 35 | { 36 | "file": "sentry-expo/upload-sourcemaps", 37 | "config": { 38 | "organization": "stuart-pretorius", 39 | "project": "cryptobullography", 40 | "authToken": "ed48dbbf8e374dcdaa813a6850dbf06e5f56835566114b728c4215ecbe7aad03" 41 | } 42 | } 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebStew/bullet/f51c42e8e6420ab093e92c73f09742abc6b90a74/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/icons/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebStew/bullet/f51c42e8e6420ab093e92c73f09742abc6b90a74/assets/icons/application.png -------------------------------------------------------------------------------- /assets/icons/notification-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebStew/bullet/f51c42e8e6420ab093e92c73f09742abc6b90a74/assets/icons/notification-icon.png -------------------------------------------------------------------------------- /assets/icons/watermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebStew/bullet/f51c42e8e6420ab093e92c73f09742abc6b90a74/assets/icons/watermark.png -------------------------------------------------------------------------------- /components/adverts/button.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Image , 4 | Platform , 5 | Text , 6 | View } from 'react-native'; 7 | import { FacebookAds } from 'expo'; 8 | import style from '../../styles/adverts'; 9 | import layout from '../../styles/layout'; 10 | 11 | const manager = ( id ) => new FacebookAds.NativeAdsManager ( id , 1 ) , 12 | Component = FacebookAds.withNativeAd ( class Ad extends React.Component { 13 | 14 | render () { 15 | 16 | const advert = this.props.nativeAd , 17 | language = this.props.language , 18 | theme = this.props.theme , 19 | appearance = style ( theme ).button ; 20 | 21 | return ( 22 | 23 | 24 | { language.actions.ad } 25 | 26 | 27 | 33 | 34 | 35 | 39 | { advert.title } 40 | 41 | 42 | 43 | 46 | { advert.description } 47 | 48 | 49 | 50 | 54 | { advert.callToActionText } 55 | 56 | 57 | 58 | 59 | 60 | ); 61 | } 62 | }); 63 | 64 | export default class ButtonAd extends React.Component { 65 | 66 | render () { 67 | 68 | return ( 69 | 74 | ); 75 | } 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /components/bull/404.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Text , 4 | View } from 'react-native'; 5 | import { Ionicons } from '@expo/vector-icons'; 6 | import layout from '../../styles/layout'; 7 | import style from '../../styles/errors'; 8 | import bull from '../../styles/bull'; 9 | 10 | export default class NotFound extends React.Component { 11 | 12 | render () { 13 | 14 | const theme = this.props.theme , 15 | language = this.props.language , 16 | appearance = style ( theme ) , 17 | arrange = layout ( theme ) ; 18 | 19 | // Only render if the bull rating is zero 20 | if ( 21 | this.props.bull.loading || 22 | this.props.bull.error || 23 | this.props.bull.rating !== 0 24 | ) { 25 | return null; 26 | } 27 | 28 | return ( 29 | 34 | 35 | 40 | 41 | { language.screens.bull [ '404' ]} 42 | 43 | 44 | 45 | ); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /components/bull/header.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Text } from 'react-native'; 5 | import style from '../../styles/header'; 6 | 7 | 8 | export default connect ( 9 | 10 | state => ({ 11 | currencies : state.currencies , 12 | language : state.language , 13 | theme : state.theme 14 | }) 15 | 16 | ) ( class Header extends React.Component { 17 | 18 | render () { 19 | 20 | const language = this.props.language , 21 | theme = this.props.theme , 22 | title = this.props.currencies.loading ? language.actions.calculating : language.screens.bull.title , 23 | appearance = style ( theme ) ; 24 | 25 | return ( 26 | 27 | { title } 28 | 29 | ); 30 | } 31 | }); -------------------------------------------------------------------------------- /components/bull/refresh.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { TouchableOpacity } from 'react-native'; 5 | import { Ionicons } from '@expo/vector-icons'; 6 | import actions from '../../actions/currencies'; 7 | import style from '../../styles/header'; 8 | import api from '../../api/currencies'; 9 | import analytics from '../../utilities/analytics'; 10 | 11 | export default connect ( 12 | 13 | state => ({ 14 | bull : state.bull , 15 | currency : state.currency , 16 | language : state.language , 17 | theme : state.theme 18 | }) 19 | 20 | ) ( class Refresh extends React.Component { 21 | 22 | constructor ( props ) { 23 | super ( props ); 24 | 25 | this.refresh = this.refresh.bind ( this ); 26 | } 27 | 28 | refresh () { 29 | 30 | const action = this.props.bull.competitors > api.limit ? 'stream' : 'get'; 31 | 32 | analytics.event ( 33 | 'bull' , 34 | 'refresh' , 35 | action , 36 | 'user' 37 | ); 38 | this.props.dispatch ( actions [ action ] ( this.props.currency.id )); 39 | } 40 | 41 | render () { 42 | 43 | const theme = this.props.theme , 44 | appearance = style ( theme ) ; 45 | 46 | if ( this.props.bull.loading ) { 47 | 48 | return null; 49 | } 50 | 51 | return ( 52 | 56 | 61 | 62 | ); 63 | } 64 | }); -------------------------------------------------------------------------------- /components/converter/header.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Text } from 'react-native'; 5 | import style from '../../styles/header'; 6 | 7 | 8 | export default connect ( 9 | 10 | state => ({ 11 | currencies : state.currencies , 12 | language : state.language , 13 | theme : state.theme 14 | }) 15 | 16 | ) ( class Header extends React.Component { 17 | 18 | render () { 19 | 20 | const language = this.props.language , 21 | theme = this.props.theme , 22 | title = this.props.currencies.loading ? language.actions.loading : language.screens.converter.title , 23 | appearance = style ( theme ) ; 24 | 25 | return ( 26 | 27 | { title } 28 | 29 | ); 30 | } 31 | }); -------------------------------------------------------------------------------- /components/currencies/header.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Text } from 'react-native'; 5 | import style from '../../styles/header'; 6 | 7 | export default connect ( 8 | 9 | state => ({ 10 | currencies : state.currencies , 11 | language : state.language , 12 | theme : state.theme 13 | }) 14 | 15 | ) ( class Header extends React.Component { 16 | 17 | render () { 18 | 19 | const language = this.props.language , 20 | title = this.props.currencies.loading ? language.actions.loading : language.screens.currencies.title.replace ( '{{length}}' , this.props.currencies.items.length ) , 21 | theme = this.props.theme , 22 | appearance = style ( theme ) ; 23 | 24 | return ( 25 | 29 | { title } 30 | 31 | ); 32 | } 33 | }); -------------------------------------------------------------------------------- /components/currencies/item.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Image , 4 | Text , 5 | TouchableOpacity , 6 | View } from 'react-native'; 7 | import Integer from '../utilities/integer'; 8 | import list from '../../styles/list'; 9 | import style from '../../styles/currencies'; 10 | import images from '../../api/images'; 11 | import numbers from '../../utilities/numbers'; 12 | 13 | export default class Item extends React.PureComponent { 14 | 15 | constructor ( props ) { 16 | super ( props ); 17 | 18 | this.detail = this.detail.bind ( this ); 19 | } 20 | 21 | detail () { 22 | 23 | this.props.navigation.navigate ( 24 | 'detail' , 25 | { 26 | item : this.props.item 27 | } 28 | ); 29 | } 30 | 31 | render () { 32 | 33 | const currency = this.props.currency , 34 | item = this.props.item , 35 | language = this.props.language , 36 | theme = this.props.theme , 37 | items = list ( theme ) , 38 | appearance = style ( theme ) ; 39 | 40 | return ( 41 | 42 | 43 | 50 | 54 | 60 | 61 | 69 | { item.name } 70 | 71 | 72 | 80 | { item.rating ? numbers.format ( item.rating ) : language.errors [ 500 ]} 81 | 82 | 94 | 102 | { item.prices.fiat ? currency.symbol + numbers.format ( item.prices.fiat.toFixed ( 2 )) : language.errors [ 500 ]} 103 | 104 | 105 | 106 | ); 107 | } 108 | }; -------------------------------------------------------------------------------- /components/currencies/load-all.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Text , 5 | TouchableOpacity } from 'react-native'; 6 | import actions from '../../actions/currencies'; 7 | import style from '../../styles/header'; 8 | import api from '../../api/currencies'; 9 | import analytics from '../../utilities/analytics'; 10 | 11 | export default connect ( 12 | 13 | state => ({ 14 | currency : state.currency , 15 | currencies : state.currencies , 16 | language : state.language , 17 | theme : state.theme 18 | }) 19 | 20 | ) ( class All extends React.Component { 21 | 22 | constructor ( props ) { 23 | super ( props ); 24 | 25 | this.refresh = this.refresh.bind ( this ); 26 | } 27 | 28 | refresh () { 29 | 30 | const action = this.props.currencies.items.length > api.limit ? 'get' : 'stream'; 31 | 32 | analytics.event ( 33 | 'currencies' , 34 | 'load' , 35 | action , 36 | 'user' 37 | ); 38 | this.props.dispatch ( actions [ action ] ( this.props.currency.id )); 39 | } 40 | 41 | render () { 42 | 43 | const theme = this.props.theme , 44 | language = this.props.language , 45 | appearance = style ( theme ) ; 46 | let action; 47 | 48 | if ( this.props.currencies.loading ) { 49 | return null; 50 | } 51 | 52 | action = language.actions.load + ' '; 53 | action += this.props.currencies.items.length > api.limit ? api.limit : language.actions.all; 54 | 55 | return ( 56 | 60 | 61 | { action } 62 | 63 | 64 | ); 65 | } 66 | }); -------------------------------------------------------------------------------- /components/errors/ajax.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Text , 4 | TouchableOpacity , 5 | View } from 'react-native'; 6 | import { Ionicons } from '@expo/vector-icons'; 7 | import style from '../../styles/errors'; 8 | import layout from '../../styles/layout'; 9 | 10 | export default class Error extends React.Component { 11 | 12 | render () { 13 | 14 | const language = this.props.language , 15 | text = this.props.text || language.errors.default , 16 | theme = this.props.theme , 17 | arrange = layout ( theme ) , 18 | appearance = style ( theme ) ; 19 | 20 | // If there is no error return 21 | if ( ! this.props.error ) { 22 | return null; 23 | } 24 | 25 | return ( 26 | 30 | 34 | 39 | 40 | { text } 41 | 42 | 43 | 44 | ); 45 | } 46 | }; -------------------------------------------------------------------------------- /components/graphs/axis-y.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Text , 4 | View } from 'react-native'; 5 | import style from '../../styles/graphs'; 6 | 7 | export default class AxisY extends React.Component { 8 | 9 | render () { 10 | 11 | const theme = this.props.theme , 12 | appearance = style ( theme ) ; 13 | 14 | let cells = this.props.data.map (( item , index ) => { 15 | 16 | return ( 17 | 21 | 22 | { item } 23 | 24 | 25 | ); 26 | }); 27 | 28 | return ( 29 | 30 | { cells } 31 | 32 | ); 33 | } 34 | } -------------------------------------------------------------------------------- /components/graphs/bar.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Animated , 4 | View } from 'react-native'; 5 | import colour from '../../utilities/colors'; 6 | 7 | export default class Bar extends React.PureComponent { 8 | 9 | constructor ( props ) { 10 | super ( props ); 11 | 12 | this.state = { 13 | height : new Animated.Value ( 0 ) 14 | }; 15 | } 16 | 17 | componentDidMount () { 18 | 19 | const value = this.props.value; 20 | 21 | Animated.timing ( 22 | this.state.height , 23 | { 24 | toValue : value , 25 | duration : 1000 26 | } 27 | ).start (); 28 | } 29 | 30 | render () { 31 | 32 | const style = this.props.style , 33 | color = this.props.color , 34 | padding = this.props.padding , 35 | height = this.state.height ; 36 | 37 | return ( 38 | 45 | 55 | 56 | ); 57 | } 58 | } -------------------------------------------------------------------------------- /components/graphs/header.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Text , 4 | View } from 'react-native'; 5 | 6 | export default class Header extends React.PureComponent { 7 | 8 | render () { 9 | 10 | const style = this.props.style; 11 | 12 | return ( 13 | 14 | 15 | { this.props.value } 16 | 17 | 18 | ); 19 | } 20 | } -------------------------------------------------------------------------------- /components/news/header.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Text } from 'react-native'; 5 | import style from '../../styles/header'; 6 | 7 | export default connect ( 8 | 9 | state => ({ 10 | news : state.news , 11 | language : state.language , 12 | theme : state.theme 13 | }) 14 | 15 | ) ( class Header extends React.Component { 16 | 17 | render () { 18 | 19 | const language = this.props.language , 20 | title = this.props.news.loading ? language.actions.loading : language.screens.news.title , 21 | theme = this.props.theme , 22 | appearance = style ( theme ) ; 23 | 24 | return ( 25 | 29 | { title } 30 | 31 | ); 32 | } 33 | }); -------------------------------------------------------------------------------- /components/news/refresh.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { TouchableOpacity } from 'react-native'; 5 | import { Ionicons } from '@expo/vector-icons'; 6 | import actions from '../../actions/news'; 7 | import style from '../../styles/header'; 8 | import analytics from '../../utilities/analytics'; 9 | 10 | export default connect ( 11 | 12 | state => ({ 13 | language : state.language , 14 | news : state.news , 15 | theme : state.theme 16 | }) 17 | 18 | ) ( class Refresh extends React.Component { 19 | 20 | constructor ( props ) { 21 | super ( props ); 22 | 23 | this.refresh = this.refresh.bind ( this ); 24 | } 25 | 26 | refresh () { 27 | 28 | analytics.event ( 29 | 'news' , 30 | 'refresh' , 31 | 'get' , 32 | 'user' 33 | ); 34 | this.props.dispatch ( actions.get ()); 35 | } 36 | 37 | render () { 38 | 39 | const theme = this.props.theme , 40 | appearance = style ( theme ) ; 41 | 42 | if ( this.props.news.loading ) { 43 | 44 | return null; 45 | } 46 | 47 | return ( 48 | 52 | 57 | 58 | ); 59 | } 60 | }); -------------------------------------------------------------------------------- /components/portfolio/header.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Text } from 'react-native'; 5 | import style from '../../styles/header'; 6 | 7 | 8 | export default connect ( 9 | 10 | state => ({ 11 | currencies : state.currencies , 12 | language : state.language , 13 | theme : state.theme 14 | }) 15 | 16 | ) ( class Header extends React.Component { 17 | 18 | render () { 19 | 20 | const language = this.props.language , 21 | theme = this.props.theme , 22 | title = this.props.currencies.loading ? language.actions.loading : language.screens.portfolio.title , 23 | appearance = style ( theme ) ; 24 | 25 | return ( 26 | 27 | { title } 28 | 29 | ); 30 | } 31 | }); -------------------------------------------------------------------------------- /components/portfolio/item.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Image , 4 | Text , 5 | TouchableOpacity , 6 | View } from 'react-native'; 7 | import Integer from '../utilities/integer'; 8 | import list from '../../styles/list'; 9 | import style from '../../styles/portfolio'; 10 | import images from '../../api/images'; 11 | import numbers from '../../utilities/numbers'; 12 | 13 | export default class Item extends React.PureComponent { 14 | 15 | constructor ( props ) { 16 | 17 | super ( props ); 18 | 19 | this.detail = this.detail.bind ( this ); 20 | } 21 | 22 | detail () { 23 | 24 | this.props.navigation.navigate ( 25 | 'detail' , 26 | { 27 | item : this.props.data 28 | } 29 | ); 30 | } 31 | 32 | render () { 33 | 34 | const currency = this.props.currency , 35 | item = this.props.item , 36 | language = this.props.language , 37 | theme = this.props.theme , 38 | items = list ( theme ) , 39 | appearance = style ( theme ) ; 40 | 41 | return ( 42 | 43 | 50 | 54 | 60 | 68 | { item.name } 69 | 70 | 71 | 79 | { numbers.format ( item.amount )} 80 | 81 | 88 | { item.price ? currency.symbol + numbers.format ( item.price.toFixed ( 2 )) : language.errors [ 500 ]} 89 | 90 | 98 | { item.total ? currency.symbol + numbers.format ( item.total.toFixed ( 2 )) : '0' } 99 | 100 | 101 | 102 | ); 103 | } 104 | }; -------------------------------------------------------------------------------- /components/portfolio/modal-add.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Modal , 4 | Text , 5 | TextInput , 6 | View } from 'react-native'; 7 | import actions from '../../actions/portfolio'; 8 | import Button from '../utilities/button'; 9 | import Heading from '../utilities/headings'; 10 | import analytics from '../../utilities/analytics'; 11 | import style from '../../styles/modal'; 12 | 13 | export default class Dialogue extends React.Component { 14 | 15 | constructor ( props ) { 16 | super ( props ); 17 | 18 | this.amount = this.amount.bind ( this ); 19 | this.blur = this.blur.bind ( this ); 20 | this.focus = this.focus.bind ( this ); 21 | this.reset = this.reset.bind ( this ); 22 | this.set = this.set.bind ( this ); 23 | this.state = { 24 | amount : '0' 25 | }; 26 | } 27 | 28 | amount ( value ) { 29 | 30 | this.setState ({ 31 | amount : value 32 | }); 33 | } 34 | 35 | blur () { 36 | 37 | if ( ! this.state.amount ) { 38 | 39 | this.setState ({ 40 | amount : '0' 41 | }); 42 | } 43 | } 44 | 45 | focus () { 46 | 47 | if ( this.state.amount === '0' ) { 48 | 49 | this.setState ({ 50 | amount : '' 51 | }); 52 | } 53 | } 54 | 55 | remove ( portfolioed ) { 56 | 57 | const theme = this.props.theme , 58 | language = this.props.language ; 59 | 60 | if ( ! portfolioed ) { 61 | 62 | return null; 63 | } 64 | 65 | return ( 66 | 67 |