├── .bowerrc ├── .gitignore ├── README.md ├── bower.json ├── config.xml ├── gulpfile.js ├── hooks ├── .gitignore ├── README.md └── after_platform_add │ └── 010_install_plugins.js ├── package.json ├── readme ├── app_store.png ├── character.png ├── character_green.png ├── character_half_green.png ├── github_promo.png ├── google_play.png ├── icon.png ├── items.png ├── items_green.png ├── quests.png └── quests_green.png ├── scss └── ionic.app.scss └── www ├── app ├── app.js ├── battle │ ├── battle-controller.js │ ├── battle.html │ └── battle.js ├── components │ ├── auth │ │ ├── auth-controller.js │ │ ├── auth-fitbit-service.js │ │ ├── auth-jawbone-service.js │ │ ├── auth-login-controller.js │ │ └── auth.js │ ├── resource │ │ ├── resource-service.js │ │ └── resource.js │ └── utility │ │ └── utility.js ├── feedback │ ├── feedback-controller.js │ ├── feedback.html │ └── feedback.js ├── friends │ ├── friends-add-controller.js │ ├── friends-add.html │ ├── friends-controller.js │ ├── friends.html │ └── friends.js ├── help │ ├── help-controller.js │ ├── help.html │ └── help.js ├── inventory │ ├── inventory-controller.js │ ├── inventory-detail-controller.js │ ├── inventory-detail.html │ ├── inventory.html │ └── inventory.js ├── leaderboard │ ├── leaderboard-controller.js │ ├── leaderboard.html │ └── leaderboard.js ├── main │ ├── main-controller.js │ ├── main.html │ └── main.js ├── menu │ └── menu.html ├── quest │ ├── quest-controller.js │ ├── quest-detail-controller.js │ ├── quest-detail.html │ ├── quest-service.js │ ├── quest.html │ └── quest.js ├── select │ ├── select-class-controller.js │ ├── select-class.html │ ├── select-username-controller.js │ ├── select-username.html │ └── select.js └── shop │ ├── shop-controller.js │ ├── shop-detail-controller.js │ ├── shop-detail.html │ ├── shop.html │ └── shop.js ├── assets ├── css │ ├── icostyle.css │ └── style.css ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff └── img │ ├── arrow.png │ ├── dagger.png │ ├── fitbit.png │ ├── fitbitbutton.png │ ├── ionic.png │ ├── jawbone_icon.jpg │ ├── jawbonebutton.png │ └── questIcon.png └── index.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | platforms/ 3 | plugins/ 4 | www/lib 5 | *.DS_store 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Get it on Google Play 5 | 6 | 7 | Get it on the App Store 9 | 10 | 11 | FitRPG is a mobile app created by [Amira Anuar](https://github.com/aellawind), [Matt Gutierrez](https://github.com/fatchicken007), and [Conor Fennell](https://github.com/conorfennell) at [Hack Reactor](http://www.hackreactor.com/). FitRPG transforms a Fitbit user's data into a character that can fight friends, battle bosses, and go on quests using the steps, distance, and sleep tracked by the Fitbit. The game logic seeks to motivate users to stay fit and challenge themselves to go that extra mile in order to win a battle or complete a quest. 12 | 13 |

Featured On

14 | * [Lifehacker](http://lifehacker.com/fitrpg-turns-your-fitbit-into-a-game-you-play-with-frie-1602140820) 15 | 16 |

Tech Stack

17 | * [Ionic Framework](http://ionicframework.com/) 18 | * [AngularJS](https://angularjs.org/) 19 | * [Node.js](http://nodejs.org/) 20 | * [Express.js](http://expressjs.com/) 21 | * [MongoDB](http://www.mongodb.org/) 22 | 23 |

Code Base

24 | * [Client side](https://github.com/fitrpg/fitrpg-ionic) 25 | * [Server side](https://github.com/fitrpg/fitrpg-server) 26 | 27 | Challenges: 28 | * User flow during fitbit OAuth 29 | * Originally we wanted to do the OAuth login client side on the app. But due to fitbit using OAuth 1.0 and not allowing CORS or JSONP, it had to be done server side. This was a challenge since the server redirects your app during the OAuth process and takes you out of the app context. We had to find a way to keep this redirect within the app and inform the app if the authentication was successful. Read our blog post [here](http://amiraanuar.com/mobile-authentication-in-ionic-with-oauth-through-external-apis-fitbit-pt-2-client/) on how we implemented the client-side portion of authentication via Ionic. 30 | * Game logic design 31 | * Balancing how sleep, steps and other activities relate to the characters attributes and making sure one is not more effective than other attributes. 32 | * User interface design 33 | * A game can have a lot of different options and views, reducing and compressing these views and making them innutaive is a challenge. 34 | * Security 35 | * Implementing json web tokens 36 | * OAuth 37 | 38 |

Upcoming Features

39 | * Push Notfications 40 | * Versus Missions 41 | * Jawbone Support 42 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HelloIonic", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.0.0-beta.9" 6 | }, 7 | "dependencies": { 8 | "angular-bootstrap": "~0.11.0", 9 | "bootstrap": "~3.1.1", 10 | "angular-local-storage": "~0.0.5", 11 | "angular-resource": "~1.2.16", 12 | "angular-timer": "~1.1.4", 13 | "ngCordova": "~0.1.2-alpha" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FitRPG 4 | 5 | Get more out of your healthy lifestyle 6 | 7 | 8 | FitRPG Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var bower = require('bower'); 4 | var concat = require('gulp-concat'); 5 | var sass = require('gulp-sass'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var sh = require('shelljs'); 9 | 10 | var paths = { 11 | sass: ['./scss/**/*.scss'] 12 | }; 13 | 14 | gulp.task('default', ['sass']); 15 | 16 | gulp.task('sass', function(done) { 17 | gulp.src('./scss/ionic.app.scss') 18 | .pipe(sass()) 19 | .pipe(gulp.dest('./www/css/')) 20 | .pipe(minifyCss({ 21 | keepSpecialComments: 0 22 | })) 23 | .pipe(rename({ extname: '.min.css' })) 24 | .pipe(gulp.dest('./www/css/')) 25 | .on('end', done); 26 | }); 27 | 28 | gulp.task('watch', function() { 29 | gulp.watch(paths.sass, ['sass']); 30 | }); 31 | 32 | gulp.task('install', ['git-check'], function() { 33 | return bower.commands.install() 34 | .on('log', function(data) { 35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 36 | }); 37 | }); 38 | 39 | gulp.task('git-check', function(done) { 40 | if (sh.which('git')) { 41 | console.log( 42 | ' ' + gutil.colors.red('Git is not installed.'), 43 | '\n Git, the version control system, is required to download Ionic.', 44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 46 | ); 47 | process.exit(1); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /hooks/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/hooks/.gitignore -------------------------------------------------------------------------------- /hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /hooks/after_platform_add/010_install_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | //this hook installs all your plugins 4 | 5 | // add your plugins to this list--either the identifier, the filesystem location or the URL 6 | var pluginlist = [ 7 | // 'org.apache.cordova.console', 8 | 'org.apache.cordova.device', 9 | // 'org.apache.cordova.file', 10 | 'org.apache.cordova.inappbrowser', 11 | // 'org.apache.cordova.media' 12 | 'org.apache.cordova.statusbar', 13 | 'https://github.com/driftyco/ionic-plugins-keyboard.git', 14 | 'https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git', 15 | // 'https://github.com/phonegap-build/PushPlugin.git', 16 | ]; 17 | 18 | // no need to configure below 19 | 20 | var fs = require('fs'); 21 | var path = require('path'); 22 | var sys = require('sys') 23 | var exec = require('child_process').exec; 24 | 25 | function puts(error, stdout, stderr) { 26 | sys.puts(stdout) 27 | } 28 | 29 | pluginlist.forEach(function(plug) { 30 | exec('cordova plugin add ' + plug, puts); 31 | }); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-project", 3 | "version": "1.0.0", 4 | "description": "An Ionic project", 5 | "dependencies": { 6 | "gulp": "^3.5.6", 7 | "gulp-concat": "^2.2.0", 8 | "gulp-minify-css": "^0.3.0", 9 | "gulp-rename": "^1.2.0", 10 | "gulp-sass": "^0.7.1", 11 | "method-override": "^1.0.1", 12 | "jawbone-up": "^0.1.1", 13 | "q": "*", 14 | "bower": "^1.3.3", 15 | "gulp-util": "^2.2.14", 16 | "shelljs": "^0.3.0", 17 | "tiny-lr": "*" 18 | }, 19 | "devDependencies": { 20 | "gulp-util": "^2.2.14", 21 | "shelljs": "^0.3.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /readme/app_store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/app_store.png -------------------------------------------------------------------------------- /readme/character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/character.png -------------------------------------------------------------------------------- /readme/character_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/character_green.png -------------------------------------------------------------------------------- /readme/character_half_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/character_half_green.png -------------------------------------------------------------------------------- /readme/github_promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/github_promo.png -------------------------------------------------------------------------------- /readme/google_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/google_play.png -------------------------------------------------------------------------------- /readme/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/icon.png -------------------------------------------------------------------------------- /readme/items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/items.png -------------------------------------------------------------------------------- /readme/items_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/items_green.png -------------------------------------------------------------------------------- /readme/quests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/quests.png -------------------------------------------------------------------------------- /readme/quests_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/readme/quests_green.png -------------------------------------------------------------------------------- /scss/ionic.app.scss: -------------------------------------------------------------------------------- 1 | /* 2 | To customize the look and feel of Ionic, you can override the variables 3 | in ionic's _variables.scss file. 4 | 5 | For example, you might change some of the default colors: 6 | 7 | $light: #fff !default; 8 | $stable: #f8f8f8 !default; 9 | $positive: #4a87ee !default; 10 | $calm: #43cee6 !default; 11 | $balanced: #66cc33 !default; 12 | $energized: #f0b840 !default; 13 | $assertive: #ef4e3a !default; 14 | $royal: #8a6de9 !default; 15 | $dark: #444 !default; 16 | */ 17 | 18 | // The path for our ionicons font files, relative to the built CSS in www/css 19 | $ionicons-font-path: "../lib/ionic/fonts" !default; 20 | 21 | // Include all of Ionic 22 | @import "www/lib/ionic/scss/ionic"; 23 | 24 | -------------------------------------------------------------------------------- /www/app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile', [ 2 | 'ionic', 3 | 'timer', 4 | 'ui.bootstrap', 5 | 'ngCordova', 6 | 'mobile.battle', 7 | 'mobile.authentication', 8 | 'mobile.resource', 9 | 'mobile.feedback', 10 | 'mobile.friends', 11 | 'mobile.help', 12 | 'mobile.inventory', 13 | 'mobile.leaderboard', 14 | 'mobile.main', 15 | 'mobile.quest', 16 | 'mobile.select', 17 | 'mobile.shop', 18 | ]) 19 | 20 | .run(function($rootScope,$ionicPlatform,$state,$ionicNavBarDelegate,$cordovaPush,$window) { 21 | $ionicPlatform.ready(function() { 22 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 23 | // for form inputs) 24 | if(window.cordova && window.cordova.plugins.Keyboard) { 25 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 26 | } 27 | 28 | ionic.Platform.fullScreen(); 29 | 30 | if(window.cordova) { 31 | var device = { 32 | isApple: ionic.Platform.isIOS(), 33 | isGoogle: ionic.Platform.isAndroid(), 34 | }; 35 | 36 | var androidConfig = { 37 | 'senderID':'replace_with_sender_id', 38 | 'ecb':'onNotification' 39 | }; 40 | 41 | var iosConfig = { 42 | 'badge':'true', 43 | 'sound':'true', 44 | 'alert':'true', 45 | 'ecb':'onNotificationAPN' 46 | }; 47 | 48 | var config; 49 | 50 | if (device.isApple) { 51 | config = iosConfig; 52 | } else if (device.isGoogle) { 53 | config = androidConfig; 54 | } 55 | 56 | // $cordovaPush.register(config).then(function(result) { 57 | // console.log(result); 58 | // }, function(err) { 59 | // console.log(err); 60 | // }); 61 | } 62 | 63 | }); 64 | 65 | $ionicPlatform.registerBackButtonAction(function () { 66 | if ($state.current.name === 'app.character') { 67 | navigator.app.exitApp(); 68 | } else if($state.current.name === 'app.feedback' || $state.current.name === 'app.inventory' || $state.current.name === 'app.shop' || $state.current.name === 'app.friends' || $state.current.name === 'app.battle' || $state.current.name === 'app.quest' || $state.current.name === 'app.leaderboard' || $state.current.name === 'app.help') { 69 | $state.go('app.character'); 70 | } else { 71 | $ionicNavBarDelegate.back(); 72 | } 73 | }, 100); 74 | }) 75 | 76 | .config(function($stateProvider, $urlRouterProvider) { 77 | 78 | $stateProvider 79 | 80 | .state('select', { 81 | url: '/select', 82 | templateUrl: 'app/select/select-class.html', 83 | controller: 'SelectClassCtrl' 84 | }) 85 | 86 | .state('username', { 87 | url: '/username', 88 | templateUrl: 'app/select/select-username.html', 89 | controller: 'UsernameCtrl' 90 | }) 91 | 92 | .state('app', { 93 | url: "/app", 94 | abstract: true, 95 | templateUrl: "app/menu/menu.html" 96 | }) 97 | 98 | .state('app.feedback', { 99 | url: '/feedback', 100 | views: { 101 | 'menuContent': { 102 | templateUrl: 'app/feedback/feedback.html', 103 | controller: 'FeedbackCtrl' 104 | } 105 | } 106 | }) 107 | 108 | .state('app.character', { 109 | url: '/character', 110 | views: { 111 | 'menuContent': { 112 | templateUrl: 'app/main/main.html', 113 | controller: 'CharacterCtrl' 114 | } 115 | } 116 | }) 117 | 118 | .state('app.inventory', { 119 | url: '/inventory', 120 | views: { 121 | 'menuContent': { 122 | templateUrl: 'app/inventory/inventory.html', 123 | controller: 'InventoryCtrl' 124 | } 125 | } 126 | }) 127 | .state('app.inventory-detail', { 128 | url: '/inventory/:inventoryId', 129 | views: { 130 | 'menuContent': { 131 | templateUrl: 'app/inventory/inventory-detail.html', 132 | controller: 'InventoryDetailCtrl' 133 | } 134 | } 135 | }) 136 | 137 | .state('app.shop', { 138 | url: '/shop', 139 | views: { 140 | 'menuContent': { 141 | templateUrl: 'app/shop/shop.html', 142 | controller: 'ShopCtrl' 143 | } 144 | } 145 | }) 146 | 147 | .state('app.shop-detail', { 148 | url: '/shop/:shopId', 149 | views: { 150 | 'menuContent': { 151 | templateUrl: 'app/shop/shop-detail.html', 152 | controller: 'ShopDetailCtrl' 153 | } 154 | } 155 | }) 156 | 157 | .state('app.friends', { 158 | url: '/friends', 159 | views: { 160 | 'menuContent': { 161 | templateUrl: 'app/friends/friends.html', 162 | controller: 'FriendsCtrl' 163 | } 164 | } 165 | }) 166 | 167 | .state('app.addfriends', { 168 | url: '/friends/add', 169 | views: { 170 | 'menuContent': { 171 | templateUrl: 'app/friends/friends-add.html', 172 | controller: 'AddFriendsCtrl' 173 | } 174 | } 175 | }) 176 | 177 | .state('app.battle', { 178 | url: '/battle', 179 | views: { 180 | 'menuContent': { 181 | templateUrl: 'app/battle/battle.html', 182 | controller: 'BattleCtrl' 183 | } 184 | } 185 | }) 186 | 187 | .state('app.quest', { 188 | url: '/quests', 189 | views: { 190 | 'menuContent': { 191 | templateUrl: 'app/quest/quest.html', 192 | controller: 'QuestCtrl' 193 | } 194 | } 195 | }) 196 | 197 | .state('app.quest-detail', { 198 | url: '/quest/:questId', 199 | views: { 200 | 'menuContent': { 201 | templateUrl: 'app/quest/quest-detail.html', 202 | controller: 'QuestDetailCtrl' 203 | } 204 | } 205 | }) 206 | 207 | .state('app.leaderboard', { 208 | url: '/leaderboard', 209 | views: { 210 | 'menuContent': { 211 | templateUrl: 'app/leaderboard/leaderboard.html', 212 | controller: 'LeaderboardCtrl' 213 | } 214 | } 215 | }) 216 | 217 | .state('app.help', { 218 | url: '/help', 219 | views: { 220 | 'menuContent': { 221 | templateUrl: 'app/help/help.html', 222 | controller: 'HelpCtrl' 223 | } 224 | } 225 | }) 226 | }) 227 | 228 | //PUSH NOTIFICATION FUNCTIONS 229 | function onNotificationAPN (event) { 230 | if ( event.alert ) { 231 | navigator.notification.alert(event.alert); 232 | } 233 | 234 | if ( event.sound ) { 235 | var snd = new Media(event.sound); 236 | snd.play(); 237 | } 238 | 239 | if ( event.badge ) { 240 | $cordovaPush.setBadgeNumber(2).then(function(result) { 241 | // Success! 242 | }, function(err) { 243 | // An error occured. Show a message to the user 244 | }); 245 | } 246 | }; 247 | 248 | function onNotification(e) { 249 | console.log(e.event); 250 | 251 | switch( e.event ) { 252 | case 'registered': 253 | if ( e.regid.length > 0 ) 254 | { 255 | // Your GCM push server needs to know the regID before it can push to this device 256 | // here is where you might want to send it the regID for later use. 257 | console.log("regID = " + e.regid); 258 | } 259 | break; 260 | 261 | case 'message': 262 | // if this flag is set, this notification happened while we were in the foreground. 263 | // you might want to play a sound to get the user's attention, throw up a dialog, etc. 264 | if ( e.foreground ) 265 | { 266 | var soundfile = e.soundname || e.payload.sound; 267 | var my_media = new Media("/android_asset/www/"+ soundfile); 268 | my_media.play(); 269 | } 270 | else 271 | { // otherwise we were launched because the user touched a notification in the notification tray. 272 | if ( e.coldstart ) { 273 | } 274 | else { 275 | } 276 | } 277 | console.log(e.payload.message, e.payload.msgcnt, e.payload.timeStamp) 278 | break; 279 | 280 | case 'error': 281 | break; 282 | 283 | default: 284 | break; 285 | } 286 | }; 287 | -------------------------------------------------------------------------------- /www/app/battle/battle-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.battle.controllers') 2 | 3 | .controller('BattleCtrl', function( 4 | $scope, 5 | $q, 6 | $window, 7 | $cordovaSocialSharing, 8 | $ionicPopup, 9 | $ionicLoading, 10 | $ionicListDelegate, 11 | $ionicScrollDelegate, 12 | $ionicNavBarDelegate, 13 | User, 14 | Battle, 15 | RandomUser, 16 | SoloMissions 17 | ) 18 | { 19 | 20 | var battles; 21 | 22 | var sendTweet = function (message) { 23 | $cordovaSocialSharing.shareViaTwitter(message).then(function(result) { 24 | // Success! 25 | }, function(err) { 26 | // An error occured. Show a message to the user 27 | }); 28 | }; 29 | 30 | // Show/hide buttons on specified screen 31 | var tabSettings = function(tab) { 32 | if (tab === 'boss') { 33 | $scope.friendsTab = false; 34 | $scope.showHistory = false; 35 | $scope.showRandom = false; 36 | } else if (tab === 'friend') { 37 | $scope.friendsTab = true; 38 | $scope.showHistory = true; 39 | $scope.showRandom = true; 40 | } else if (tab === 'history') { 41 | $scope.friendsTab = true; 42 | $scope.showHistory = false; 43 | $scope.showRandom = false; 44 | } 45 | }; 46 | 47 | tabSettings('friend'); 48 | 49 | $scope.hasBattles = false; 50 | $scope.isPending = true; 51 | 52 | // Set active class on specified tab 53 | var activeTab = function(name) { 54 | $scope.tabClass = { 55 | boss: '', 56 | friend: '', 57 | }; 58 | 59 | $scope.tabClass[name] = 'button-tab-active'; 60 | }; 61 | 62 | activeTab('friend'); 63 | 64 | var loading = setTimeout(function(){ 65 | $ionicLoading.show({ 66 | template: '

Loading...

' 67 | }); 68 | }, 500); 69 | 70 | var stopLoading = function() { 71 | clearTimeout(loading); 72 | $ionicLoading.hide(); 73 | }; 74 | 75 | var checkLevel = function(user) { 76 | var newLevel = util.calcLevel(user.fitbit.experience + user.attributes.experience); 77 | user.attributes.skillPts = util.calcSkillPoints(user.attributes.skillPts,newLevel,user.attributes.level); 78 | user.attributes.level = newLevel; 79 | }; 80 | 81 | // TEXT PROMPTS 82 | var healthWarning = function() { 83 | var title = 'Unfit for Battle'; 84 | var body = 'You don\'t look so good. You need to recover some of your health before you can battle again.'; 85 | 86 | util.showAlert($ionicPopup, title, body, 'OK', function() { 87 | $ionicListDelegate.closeOptionButtons(); 88 | }); 89 | }; 90 | 91 | var listOfBattles = function() { 92 | // make a copy of the $scope.user.missionsVersus 93 | for (var i=0; i<$scope.user.missionsVersus.length; i++) { 94 | battles[i] = {}; 95 | for (var key in $scope.user.missionsVersus[i]) { 96 | battles[i][key] = $scope.user.missionsVersus[i][key]; 97 | } 98 | } 99 | 100 | if ($scope.user.friends.length === 0) { 101 | stopLoading(); 102 | }; 103 | 104 | var getFriendData = function(id) { 105 | User.get({id: id}, function(user){ 106 | if (user._id) { 107 | $scope.friends.push(user); 108 | var friend = $scope.friends[$scope.friends.length-1]; 109 | for (var j=0; j 0) { 222 | $scope.startBattle(enemy._id); 223 | } else { 224 | title = 'No Matches Found'; 225 | body = 'We couldn\'t find a suitable match for you. You must be too strong.'; 226 | util.showAlert($ionicPopup, title, body, 'OK', function() {}); 227 | } 228 | }); 229 | }; 230 | 231 | title = 'Random Battle'; 232 | body = 'Battle a randomly matched player. Are you up for the challenge?' 233 | util.showPrompt($ionicPopup, title, body, 'Start', 'Cancel', findRandomBattle); 234 | }; 235 | 236 | $scope.startBattle = function(id) { 237 | var title, body; 238 | if ($scope.user.attributes.HP === 0) { 239 | healthWarning(); 240 | } else { 241 | var battlePending = false; 242 | var battleRequest = false; 243 | 244 | for (var i=0; i<$scope.user.missionsVersus.length; i++) { 245 | var mission = $scope.user.missionsVersus[i]; 246 | if (mission.enemy === id && mission.status.toLowerCase() === 'pending') { 247 | battlePending = true; 248 | } else if (mission.enemy === id && mission.status.toLowerCase() === 'request') { 249 | battleRequest = true; 250 | } 251 | } 252 | if (battleRequest) { 253 | console.log('get ready for battle!'); 254 | // get the correct battle 255 | var battle; 256 | var indexOfBattle; 257 | for(var i = 0; i < $scope.user.missionsVersus.length; i++){ 258 | if ($scope.user.missionsVersus[i].enemy === id) { 259 | indexOfBattle = i; 260 | battle = $scope.user.missionsVersus[i]; 261 | } 262 | } 263 | 264 | // get user attributes from database 265 | User.get({id : id}, function(enemy){ 266 | var enemyBattle; 267 | for(var i = 0; i < enemy.missionsVersus.length; i++){ 268 | if (enemy.missionsVersus[i].enemy === $scope.user._id) { 269 | enemyBattle = enemy.missionsVersus[i]; 270 | } 271 | } 272 | 273 | // use game logic to determine winner of battle 274 | // post battle results to database for both players 275 | 276 | var winner = util.battle($scope.user,enemy); 277 | 278 | var updateExp = function(player1,player2,status) { 279 | var player1Xp = player1.attributes.experience + player1.fitbit.experience; 280 | var player2Xp = player2.attributes.experience + player2.fitbit.experience; 281 | var player1AttrXp = player1.attributes.experience; 282 | var player2AttrXp = player2.attributes.experience; 283 | var diff = Math.abs(player2Xp-player1Xp); 284 | if (status === 'win') { 285 | if (player2Xp >= player1Xp) { 286 | return player1AttrXp + diff*0.2; 287 | } else { 288 | return player1AttrXp + diff*(1/Math.log(diff)*0.5); 289 | } 290 | } else if (status === 'loss') { 291 | if (player2Xp >= player1Xp) { 292 | return player1AttrXp - diff*(1/Math.log(diff)*0.5); 293 | } else { 294 | return player1AttrXp - diff*0.2; 295 | } 296 | } 297 | }; 298 | 299 | var saveBattleResult = function(winnerId, loserId) { 300 | var battle = { 301 | winner : winnerId, 302 | loser : loserId, 303 | createdAt : new Date() 304 | } 305 | 306 | Battle.save(battle); 307 | } 308 | 309 | var adjustAttr = function(playerWin,playerLose) { 310 | playerLose.attributes.HP = 0; 311 | playerWin.attributes.HP = winner.hp; 312 | playerWin.attributes.gold += Math.floor(playerLose.attributes.gold * 0.1); 313 | playerLose.attributes.gold = Math.floor(playerLose.attributes.gold *= 0.9); 314 | playerWin.attributes.experience = Math.floor(updateExp(playerWin,playerLose,'win')); 315 | playerLose.attributes.experience = Math.floor(updateExp(playerLose,playerWin,'loss')); 316 | saveBattleResult(playerWin._id,playerLose._id); 317 | }; 318 | 319 | var handleNegXp = function(player,level){ 320 | var playerXp = player.fitbit.experience + player.attributes.experience; 321 | var levelXp = util.levelExp(level); 322 | if (playerXp < 0) { 323 | player.attributes.experience = -player.fitbit.experience; 324 | } else if (playerXp < levelXp) { 325 | player.attributes.experience += (levelXp - playerXp); 326 | } 327 | }; 328 | 329 | if (winner.result === 'player 1') { 330 | adjustAttr($scope.user,enemy); 331 | handleNegXp(enemy, enemy.attributes.level); 332 | enemyBattle.status = 'loss'; 333 | battle.status = 'win'; 334 | checkLevel($scope.user); 335 | } else if (winner.result === 'player 2') { 336 | adjustAttr(enemy,$scope.user); 337 | handleNegXp($scope.user, $scope.user.attributes.level); 338 | enemyBattle.status = 'win'; 339 | battle.status = 'loss'; 340 | } 341 | 342 | util.showAlert($ionicPopup,'Challenge Accepted','Your duel to the death with '+ enemy.profile.displayName+ ' is in progress. Who will come out on top?', 'Results', function() { 343 | battleResults(battle.status, enemy.username); 344 | }); 345 | 346 | $scope.user.missionsVersus.splice(indexOfBattle,1); 347 | 348 | for (var i=0; i<$scope.friends.length; i++) { 349 | var friend = $scope.friends[i]; 350 | if (friend._id === id) { 351 | delete friend.battleData; 352 | } 353 | } 354 | 355 | User.update($scope.user); 356 | User.update(enemy); 357 | }); 358 | } else if (battlePending) { 359 | title = 'Battle Pending'; 360 | body = 'You are already have a request to do battle with this friend.'; 361 | 362 | util.showAlert($ionicPopup, title, body, 'OK', function() { 363 | $ionicListDelegate.closeOptionButtons(); 364 | }); 365 | } else { 366 | var checkMissionExists = function(player,enemyId) { 367 | var missions = player.missionsVersus; 368 | for (var i=0; i 0) { 532 | title = 'Mission Started'; 533 | body = 'You are waging war against the forces of evil...', 534 | 535 | util.showAlert($ionicPopup, title, body, 'Continue', startBossBattle); 536 | } else { 537 | healthWarning(); 538 | } 539 | }; 540 | }) -------------------------------------------------------------------------------- /www/app/battle/battle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | Friends 16 | Bosses 17 |
18 |
19 | 20 | 21 | 24 | 25 | 26 |
27 |
28 |
29 | 33 |
34 | 35 | 36 | 37 | 38 |

{{friend.username}}

39 |

Level: {{friend.attributes.level}} Status: {{friend.battleData.status}}

40 | Fight 41 | Cancel 42 |
43 | No Pending Battles or Requests 44 |
45 | 46 |
47 | 48 | 49 |
50 |
51 |
52 |

{{wins}}

53 |

Wins

54 |
55 |
56 |

{{losses}}

57 |

Losses

58 |
59 |
60 |
61 | Wins 62 |
63 | 64 | 65 | 66 |

{{battle.username}}

67 |

Level {{battle.attributes.level}} {{battle.character}}

68 |
69 |
70 | 71 |
72 | Losses 73 |
74 | 75 | 76 | 77 |

{{battle.username}}

78 |

Level {{battle.attributes.level}} {{battle.character}}

79 |
80 |
81 |
82 |
83 | 84 | 85 |
86 | 93 | 94 | 95 |

{{mission.title}}

96 |

Level {{mission.level}}, 97 | Difficulty: 98 | 99 | 100 | 101 | 102 | 103 |

104 | Fight 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 | -------------------------------------------------------------------------------- /www/app/battle/battle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.battle', [ 4 | 5 | 'mobile.battle.controllers' 6 | ]); 7 | 8 | angular.module('mobile.battle.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/components/auth/auth-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.authentication.controllers') 2 | 3 | .controller('AuthenticationController', function ($scope, $state, $ionicLoading, User, localStorageService) { 4 | // Check our local storage for the proper credentials to ensure we are logged in, this means users can't get past app unless they select a username 5 | if (localStorageService.get('username')) { 6 | if (localStorageService.get('fitbit-token') && isTokenInDate(localStorageService)) { 7 | $state.transitionTo('app.character'); 8 | $scope.Authenticated = true; 9 | } 10 | } else if (localStorageService.get('fitbit-token') && isTokenInDate(localStorageService)) { 11 | 12 | $ionicLoading.show({ 13 | template: '

loading...

', 14 | animation: 'fade-in', 15 | showBackdrop: false, 16 | maxWidth: 200, 17 | showDelay: 100 18 | }); 19 | 20 | User.get({id : localStorageService.get('userId') }, function(user) { 21 | if (user.username === undefined) { 22 | $ionicLoading.hide(); 23 | $state.transitionTo('username'); 24 | $scope.Authenticated = true; 25 | } else { 26 | $ionicLoading.hide(); 27 | $state.transitionTo('app.character'); 28 | $scope.Authenticated = true; 29 | } 30 | }); 31 | } else { 32 | $scope.needsAuthentication = true; 33 | } 34 | 35 | $scope.logout = function () { 36 | localStorageService.clearAll(); 37 | location.href=location.pathname; 38 | }; 39 | 40 | }); 41 | 42 | var isTokenInDate = function(localStorageService){ 43 | var tokenDate = new Date(JSON.parse(localStorageService.get('token-date'))); 44 | if (tokenDate) { 45 | var today = new Date(); 46 | var timeDiff = Math.abs(today.getTime() - tokenDate.getTime()); 47 | var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)); 48 | if(diffDays > 10) { 49 | return false; 50 | } 51 | } else { 52 | return false; 53 | } 54 | return true; 55 | }; 56 | -------------------------------------------------------------------------------- /www/app/components/auth/auth-fitbit-service.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.authentication.services') 2 | 3 | .factory('FitbitLoginService', function ($window, $state, $ionicLoading, $location, localStorageService, $location) { 4 | var url = 'http://fitrpg.azurewebsites.net/fitbit/auth'; 5 | var usernameUrl = 'http://fitrpg.azurewebsites.net/fitbit/getUsername'; 6 | var loginWindow, token, hasToken, userId, hasUserId; 7 | 8 | return { 9 | login: function () { 10 | loginWindow = $window.open(url, '_blank', 'location=no,toolbar=no,hidden=yes'); 11 | $ionicLoading.show({ 12 | template: '

Contacting Fitbit...

', 13 | animation: 'fade-in', 14 | showBackdrop: false, 15 | maxWidth: 200, 16 | showDelay: 200 17 | }); 18 | 19 | loginWindow.addEventListener("loadstop", function(e) { 20 | $ionicLoading.hide(); 21 | loginWindow.show(); 22 | }); 23 | 24 | loginWindow.addEventListener('loadstart', function (event) { 25 | hasToken = event.url.indexOf('?oauth_token='); 26 | hasUserId = event.url.indexOf('&userId='); 27 | if (hasToken > -1 && hasUserId > -1) { 28 | token = event.url.match('oauth_token=(.*)&userId')[1]; 29 | userId = event.url.match('&userId=(.*)')[1]; 30 | localStorageService.set('fitbit-token', token); 31 | localStorageService.set('token-date', JSON.stringify(new Date())); 32 | localStorageService.set('userId', userId); 33 | loginWindow.close(); 34 | location.href=location.pathname; 35 | } 36 | }); 37 | }, 38 | }; 39 | }); 40 | -------------------------------------------------------------------------------- /www/app/components/auth/auth-jawbone-service.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.authentication.services') 2 | 3 | .factory('JawboneLoginService', function ($window, $state, localStorageService) { 4 | var url = 'https://fitrpg.azurewebsites.net/jawbone/auth'; 5 | var loginWindow, token, hasToken, userId, hasUserId; 6 | 7 | return { 8 | login: function () { 9 | loginWindow = $window.open(url, '_blank', 'location=no,toolbar=no,hidden=yes'); 10 | loginWindow.addEventListener('loadstart', function (event) { 11 | hasToken = event.url.indexOf('?token='); 12 | hasUserId = event.url.indexOf('&userid='); 13 | if (hasToken > -1) { 14 | token = event.url.substring(hasToken + 7); 15 | userId = event.url.substring(hasUserId + 8) 16 | localStorageService.set('jawbone-token', token); 17 | localStorageService.set('userId', userId); 18 | location.href=location.pathname; 19 | loginWindow.close(); 20 | } 21 | }); 22 | }, 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /www/app/components/auth/auth-login-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.authentication.controllers') 2 | 3 | .controller('LoginController', function ($scope, $state, FitbitLoginService, JawboneLoginService) { 4 | 5 | $scope.fitbitlogin = FitbitLoginService.login; 6 | $scope.jawbonelogin = JawboneLoginService.login; 7 | 8 | }); 9 | -------------------------------------------------------------------------------- /www/app/components/auth/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.authentication', [ 4 | 5 | 'mobile.authentication.controllers', 6 | 'mobile.authentication.services' 7 | ]); 8 | 9 | angular.module('mobile.authentication.controllers', ['LocalStorageModule', 'ionic']); 10 | angular.module('mobile.authentication.services', ['LocalStorageModule', 'ionic']); 11 | -------------------------------------------------------------------------------- /www/app/components/resource/resource-service.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.resource.services') 2 | 3 | .constant('SERVER_URL', 'http://fitrpg.azurewebsites.net') 4 | 5 | .factory('authHttpInterceptor', ['localStorageService', function(localStorageService) { 6 | return { 7 | 'request': function(config) { 8 | config.headers = config.headers || {}; 9 | // do some API key setting 10 | // Add json web token 11 | if(localStorageService.get('fitbit-token')){ 12 | config.headers.Authorization = 'Bearer ' + localStorageService.get('fitbit-token'); 13 | } 14 | return config; 15 | } 16 | }; 17 | }]) 18 | 19 | .config(['$httpProvider', function($httpProvider) { 20 | $httpProvider.interceptors.push('authHttpInterceptor'); 21 | }]) 22 | 23 | .factory('CheckUsername', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 24 | return $resource(SERVER_URL + '/api/users/check/:username', {username: '@username'}); 25 | }]) 26 | 27 | .factory('Refresh', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 28 | return $resource(SERVER_URL + '/fitbit/refresh/:id', {id: '@id'}); 29 | }]) 30 | 31 | .factory('Settings', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 32 | return $resource(SERVER_URL + '/settings/:id', {id: '@id'}); 33 | }]) 34 | 35 | .factory('User', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 36 | return $resource(SERVER_URL + '/api/users/:id', {id : '@id'}, { 37 | update: { method: 'PUT' } 38 | }); 39 | }]) 40 | 41 | // get fitbit data based off of a date range 42 | .factory('DatesData', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 43 | return $resource(SERVER_URL + '/fitbit/daterange/:id/:type/:activity/:startDate/:endDate', 44 | {id: '@id', type: '@type', activity: '@activity', startDate: '@startDate', endDate: '@endDate'} ); 45 | }]) 46 | 47 | // get fitbit data based off of a time range 48 | .factory('NewTimesData', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 49 | return $resource(SERVER_URL + '/fitbit/new/timerange/:id/:activity/:startDate/:startTime/:endTime', 50 | {id: '@id', activity: '@activity', startDate: '@startDate', startTime: '@startTime', endTime: '@endTime'} ); 51 | }]) 52 | 53 | // OLD RESOURCE WE NEVER USE BECAUSE WE CHANGE IT--LEAVING IT HERE TO REMEMBER FOR BACKWARDS COMPATIABILITY 54 | .factory('TimesData', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 55 | return $resource(SERVER_URL + '/fitbit/timerange/:id/:activity/:startDate/:endDate/:startTime/:endTime', 56 | {id: '@id', activity: '@activity', startDate: '@startDate', endDate: '@endDate', startTime: '@startTime', endTime: '@endTime'} ); 57 | }]) 58 | 59 | 60 | .factory('Shop', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 61 | return $resource(SERVER_URL + '/api/items/:id', {id : '@id'}); 62 | }]) 63 | 64 | .factory('Battle', ['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 65 | return $resource(SERVER_URL + '/api/battles/:id', {id : '@id'}); 66 | }]) 67 | 68 | .factory('SoloMissions',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 69 | return $resource(SERVER_URL + '/api/solos/:id', {id : '@id'}); 70 | }]) 71 | 72 | .factory('Quests',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 73 | return $resource(SERVER_URL + '/api/quests/:id', {id : '@id'}); 74 | }]) 75 | 76 | .factory('VersusMissions',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 77 | return $resource(SERVER_URL + '/api/groups/:id', {id : '@id'}); 78 | }]) 79 | 80 | .factory('RandomUser',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 81 | return $resource(SERVER_URL + '/api/users/random/:id/:level', 82 | {id : '@id', level: '@level'}); 83 | }]) 84 | 85 | .factory('UserSearch',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 86 | return $resource(SERVER_URL + '/api/users/search/:username', {username : '@username'}); 87 | }]) 88 | 89 | .factory('Leaderboard',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 90 | return $resource(SERVER_URL + '/api/users/leaderboard'); 91 | }]) 92 | 93 | .factory('Feedback',['$resource', 'SERVER_URL', function($resource, SERVER_URL) { 94 | return $resource(SERVER_URL + '/feedback/:id', {id : '@id'}); 95 | }]); 96 | -------------------------------------------------------------------------------- /www/app/components/resource/resource.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.resource', [ 4 | 5 | 'mobile.resource.services' 6 | ]); 7 | 8 | angular.module('mobile.resource.services', ['ngResource']); -------------------------------------------------------------------------------- /www/app/components/utility/utility.js: -------------------------------------------------------------------------------- 1 | var util = { 2 | vitalityToHp: function(vitality, charClass){ 3 | var hp; 4 | // change character classes 5 | if (charClass === 'strength' || charClass === 'endurance') { 6 | hp = vitality * 10; 7 | } else if (charClass === 'dexterity') { 8 | hp = vitality * 12; 9 | } else if (charClass === 'vitality') { 10 | hp = vitality * 15; 11 | } else { 12 | hp = vitality * 10; 13 | } 14 | 15 | return hp; 16 | }, 17 | 18 | attack: function(first,second,count) { 19 | first.attackBonus = first.attackBonus || 1; 20 | if (Math.floor(count%(100/(first.endurance/20))) === 0) { 21 | if (Math.random() < 1/(1+second.dexterity/25)) { 22 | var strength = first.strength; 23 | if (Math.random() < 0.05) { 24 | strength *= 2; 25 | } 26 | // console.log(strength); 27 | return Math.floor(second.HP - strength*first.attackBonus); 28 | } 29 | } 30 | return second.HP; 31 | }, 32 | 33 | battleTurns: function(player1Attr, player2Attr) { 34 | var firstAttack = Math.random(); 35 | var count = 0; 36 | var characterBonus = function(player) { 37 | if (player.characterClass === 'strength') { 38 | player.strength *= 1.1; 39 | } else if (player.characterClass === 'dexterity') { 40 | player.dexterity *= 1.4; 41 | } else if (player.characterClass === 'endurance') { 42 | player.endurance *= 1.1; 43 | } else if (player.characterClass === 'vitality') { 44 | player.dexterity *= 1.2; 45 | } 46 | }; 47 | 48 | characterBonus(player1Attr); 49 | characterBonus(player2Attr); 50 | 51 | while (player1Attr.HP > 0 && player2Attr.HP > 0) { 52 | count++; 53 | if (firstAttack >= 0.5) { 54 | player2Attr.HP = this.attack(player1Attr,player2Attr,count); 55 | if (player2Attr.HP > 0) { 56 | player1Attr.HP = this.attack(player2Attr,player1Attr,count); 57 | } else { 58 | break; 59 | } 60 | } else { 61 | player1Attr.HP = this.attack(player2Attr,player1Attr,count); 62 | if (player1Attr.HP > 0) { 63 | player2Attr.HP = this.attack(player1Attr,player2Attr,count); 64 | } else { 65 | break; 66 | } 67 | } 68 | } 69 | }, 70 | 71 | playerAttr: function(player) { 72 | return { 73 | strength: player.attributes.strength + player.fitbit.strength, 74 | endurance: player.attributes.endurance + player.fitbit.endurance, 75 | dexterity: player.attributes.dexterity + player.fitbit.dexterity, 76 | HP: player.attributes.HP, 77 | attackBonus: player.fitbit.attackBonus, 78 | }; 79 | }, 80 | 81 | battle: function(player1, player2){ 82 | var player1Attr = this.playerAttr(player1); 83 | var player2Attr = this.playerAttr(player2); 84 | 85 | 86 | this.battleTurns(player1Attr,player2Attr); 87 | console.log(player1Attr.HP, player2Attr.HP); 88 | 89 | if (player1Attr.HP > player2Attr.HP) { 90 | return {result:'player 1', hp: player1Attr.HP}; 91 | } else { 92 | return {result:'player 2', hp: player2Attr.HP}; 93 | } 94 | 95 | }, 96 | 97 | bossBattle: function(player,boss) { 98 | var player1 = this.playerAttr(player); 99 | var count = 0; 100 | if (boss.difficulty !== null) { 101 | boss.HP = boss.vitality*5*boss.difficulty; 102 | } else { 103 | boss.HP = boss.vitality*10; 104 | } 105 | 106 | this.battleTurns(player1,boss); 107 | console.log(player1.HP,boss.HP); 108 | 109 | if (player1.HP > boss.HP) { 110 | return {result:'player', hp: player1.HP}; 111 | } else { 112 | return {result:'boss', hp: 0}; 113 | } 114 | }, 115 | 116 | capitalize: function(string) { 117 | return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); 118 | }, 119 | 120 | showAlert: function(controller, title, body, button, callback) { 121 | var alertPopup = controller.alert({ 122 | title: title, 123 | template: body, 124 | okText: button 125 | }); 126 | alertPopup.then(function(res) { 127 | callback(); 128 | }); 129 | }, 130 | 131 | showPrompt: function(controller,title,body,okText,cancelText,callbackTrue,callbackFalse) { 132 | var confirmPopup = controller.confirm({ 133 | title: title, 134 | template: body, 135 | okText: okText, 136 | cancelText : cancelText 137 | }); 138 | confirmPopup.then(function(res) { 139 | if(res) { 140 | callbackTrue(); 141 | } else if (callbackFalse) { 142 | callbackFalse(); 143 | } 144 | }); 145 | }, 146 | 147 | showPopup: function(controller,title,body,btn1,btn2,cancelBtn,callbackTrue,callbackFalse) { 148 | var myPopup = controller.show({ 149 | title: title, 150 | template: body, 151 | buttons: [ 152 | {text: cancelBtn}, 153 | {text: btn1, 154 | onTap: function(e) { 155 | return 'btn1'; 156 | } 157 | }, 158 | {text: btn2, 159 | onTap: function(e) { 160 | return 'btn2'; 161 | } 162 | } 163 | ] 164 | }); 165 | myPopup.then(function(res) { 166 | if(res === 'btn1') { 167 | callbackFalse(); 168 | } else if (res === 'btn2') { 169 | callbackTrue(); 170 | } 171 | }); 172 | }, 173 | 174 | currentLevelExp: function(lvl,exp) { 175 | return exp - (100*Math.pow(lvl-1,3) + 360*Math.pow(lvl-1,2) + 3500*(lvl-1)); 176 | }, 177 | 178 | nextLevelExp: function(lvl) { 179 | return (100*Math.pow(lvl,3) + 360*Math.pow(lvl,2) + 3500*lvl) - (100*Math.pow(lvl-1,3) + 360*Math.pow(lvl-1,2) + 3500*(lvl-1)); 180 | }, 181 | 182 | levelExp: function(lvl) { 183 | return 100*Math.pow(lvl-1,3) + 360*Math.pow(lvl-1,2) + 3500*(lvl-1); 184 | }, 185 | 186 | calcLevel: function(experience, currLvl) { 187 | var level = currLvl || 1; 188 | var total = experience; 189 | var expToLevel = function(lvl) { 190 | return 100*Math.pow(lvl,3) + 360*Math.pow(lvl,2) + 3500*lvl; 191 | }; 192 | while (expToLevel(level) <= total) { 193 | level++; 194 | } 195 | return level; 196 | }, 197 | 198 | calcSkillPoints: function(currSkillPts, lvl, currLvl) { 199 | return currSkillPts + (lvl-currLvl)*5; 200 | }, 201 | 202 | }; 203 | 204 | // Necessary to format to the way Fitbit wants our dates 205 | Date.prototype.yyyymmdd = function() { 206 | var yyyy = this.getFullYear().toString(); 207 | var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based 208 | var dd = this.getDate().toString(); 209 | return yyyy + '-' + (mm[1]?mm:'0'+mm[0]) + '-' + (dd[1]?dd:'0'+dd[0]); 210 | }; 211 | 212 | // Useful to add days and hours to the start time/day 213 | Date.prototype.addDays = function(days,hours) { 214 | var date = new Date(this.valueOf()); 215 | date.setDate(date.getDate() + days); 216 | if(hours) { 217 | date.setHours(this.getHours()+hours); 218 | } 219 | return date; 220 | }; -------------------------------------------------------------------------------- /www/app/feedback/feedback-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.feedback.controllers') 2 | 3 | .controller('FeedbackCtrl', function($scope, $state, $ionicPopup, User, Feedback, localStorageService) { 4 | $scope.feedback = { 5 | email: '', 6 | message: '', 7 | }; 8 | 9 | var navTo = function(location) { 10 | $state.go('app.' + location); 11 | }; 12 | 13 | 14 | $scope.submit = function(feedback) { 15 | if (!localStorageService.get('rate')) { 16 | localStorageService.set('rate',true); 17 | $scope.user.attributes.gold += 500; 18 | User.update($scope.user); 19 | } 20 | 21 | Feedback.save({email: feedback.email, message: feedback.message, createdAt: new Date()}); 22 | 23 | var title = 'Feedback Received'; 24 | var body = 'We are constantly implementing new features and improving this app. Your feedback will help us greatly!'; 25 | 26 | util.showAlert($ionicPopup,title,body,'OK',function(){ 27 | navTo('character'); 28 | }); 29 | 30 | }; 31 | }) 32 | -------------------------------------------------------------------------------- /www/app/feedback/feedback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 8 | 9 | 12 | 13 | 14 |
15 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /www/app/feedback/feedback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.feedback', [ 4 | 5 | 'mobile.feedback.controllers' 6 | ]); 7 | 8 | angular.module('mobile.feedback.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/friends/friends-add-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.friends.controllers') 2 | 3 | .controller('AddFriendsCtrl', function($scope, User, $ionicPopup, $ionicLoading, UserSearch) { 4 | // friends is accessed from $rootScope.user.friends in the template 5 | $scope.friends = []; 6 | 7 | // User.query(function(users){ 8 | // for (var i=0; iSearching...

' 35 | }); 36 | }, 500); 37 | 38 | var stopLoading = function() { 39 | clearTimeout(loading); 40 | $ionicLoading.hide(); 41 | }; 42 | 43 | UserSearch.query({username: username}, function(users) { 44 | if (users.length === 0) { 45 | $scope.notFound = true; 46 | } 47 | 48 | for (var i=0; i 2 | 3 | 4 | 5 | 13 | 14 |
15 | 18 | 21 |
22 | 23 |
24 | No matches found! 25 |
26 | 27 | 28 | 29 | 30 |

{{friend.username}}

31 |

Level {{friend.attributes.level}} {{friend.character}}

32 | Add 33 |
34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /www/app/friends/friends-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.friends.controllers') 2 | 3 | .controller('FriendsCtrl', function($scope, $state, User, $ionicLoading, $ionicListDelegate, $ionicPopup, $q) { 4 | // friends is accessed from $rootScope.user 5 | $scope.friends = []; 6 | $scope.friendRequests = []; 7 | 8 | $scope.showList = { 9 | friend: true, 10 | pending: true, 11 | request: true, 12 | }; 13 | 14 | $scope.toggleList = function(list) { 15 | $scope.showList[list] = !$scope.showList[list]; 16 | }; 17 | 18 | var loading = setTimeout(function(){ 19 | $ionicLoading.show({ 20 | template: '

Loading...

' 21 | }); 22 | }, 500); 23 | 24 | var stopLoading = function() { 25 | clearTimeout(loading); 26 | $ionicLoading.hide(); 27 | }; 28 | 29 | if ($scope.user.friends.length === 0) { 30 | stopLoading(); 31 | } 32 | 33 | for (var i=0; i<$scope.user.friends.length; i++) { 34 | var friend = $scope.user.friends[i]; 35 | User.get({id: friend}, function(user){ 36 | if (user._id) { 37 | $scope.friends.push(user); 38 | $scope.hasFriends = true; 39 | } 40 | stopLoading(); 41 | }); 42 | } 43 | 44 | if ($scope.user.friendRequests) { 45 | for (var i=0; i<$scope.user.friendRequests.length; i++) { 46 | var friendRequest = $scope.user.friendRequests[i]; 47 | var getFriend = function(request) { 48 | User.get({id: friendRequest.id}, function(friend) { 49 | friend.requestStatus = request.status; 50 | $scope.friendRequests.push(friend); 51 | }); 52 | }; 53 | getFriend(friendRequest); 54 | } 55 | } 56 | 57 | var removeFriendRequest = function(user, friendId) { 58 | var index; 59 | for (var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 21 |
22 | Friends 23 |
24 | 25 | 26 | 27 |

{{friend.username}}

28 |

Level {{friend.attributes.level}} {{friend.character}}

29 | 30 | Battle 31 |
32 | Add Friends 33 | 34 |
35 | Requests 36 |
37 | 38 | 39 | 40 |

{{friend.username}}

41 |

Level {{friend.attributes.level}} {{friend.character}}

42 | Accept 43 | Reject 44 |
45 | 46 |
47 | Pending 48 |
49 | 50 | 51 | 52 |

{{friend.username}}

53 |

Level {{friend.attributes.level}} {{friend.character}}

54 |
55 | 56 | 57 | 58 |
59 |
60 | 61 | -------------------------------------------------------------------------------- /www/app/friends/friends.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.friends', [ 4 | 5 | 'mobile.friends.controllers' 6 | ]); 7 | 8 | angular.module('mobile.friends.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/help/help-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.help.controllers') 2 | 3 | .controller('HelpCtrl', function($scope, $stateParams, $location, $ionicScrollDelegate) { 4 | 5 | $scope.scrollTo = function(id){ 6 | $location.hash(id); 7 | $ionicScrollDelegate.anchorScroll(true) 8 | }; 9 | 10 | $scope.scrollTop = function() { 11 | $ionicScrollDelegate.scrollTop(true); 12 | }; 13 | 14 | 15 | $scope.questions = [ 16 | {question: 'What is the purpose of FitRPG?', goTo: 'purpose' }, 17 | {question: 'How does FitRPG turn my fitness data into my character attributes?', goTo: 'convert' }, 18 | {question: 'How can I improve my character\'s attributes?', goTo: 'attributes' }, 19 | {question: 'How can I level up my character?', goTo: 'levelUp' }, 20 | {question: 'What is a battle and how do I battle someone?', goTo: 'battle' }, 21 | {question: 'What is a quest and how does it work?', goTo: 'quest' }, 22 | {question: 'How often can I do a specific quest?', goTo: 'questFrequency' }, 23 | {question: 'What are skill points and what can I do with them?', goTo: 'skillPts' }, 24 | {question: 'How do attributes help me in battle?', goTo: 'attributeDef' }, 25 | {question: 'What are weapons, equipment, and potions? How do I buy them?', goTo: 'purchasing' }, 26 | {question: 'What can I do with the weapons I buy?', goTo: 'weapons' }, 27 | {question: 'What is weapon size?', goTo: 'weaponsize' }, 28 | {question: 'How do I equip my character with weapons I\'ve bought?', goTo: 'equip' }, 29 | {question: 'How do I add friends that don\'t show up in search?', goTo: 'addFriends' }, 30 | {question: 'How does FitRPG retrieve my Fitbit data, and how do I make sure it is synced?', goTo: 'fitbitdata' }, 31 | {question: 'How much of my personal fitness data can my friends actually see?', goTo: 'privacy' }, 32 | {question: 'How can I contact you with further questions or feedback?', goTo: 'feedback' } 33 | ]; 34 | 35 | $scope.answers = [ 36 | { question: 'What is the purpose of FitRPG?', id: 'purpose', 37 | answer: 'FitRPG encourages a healthier lifestyle by coupling it with an RPG that turns your fitness data into skills, experience points, and HP that you can use to engage in battles against your friends. You can also go on quests to earn more gold and experience points.' 38 | }, 39 | { question: 'How does FitRPG turn my fitness data into my character attributes?', id: 'convert', 40 | answer: 'We only start using the data you have from the day you signed up with FitRPG. We don\'t want people to come in with too much of an advantage. We use fun and fancy algorithms to turn your data into points. Steps are converted to experience; sleep is converted to vitality and HP recovery; distance is converted to endurance; and the workouts you log manually are converted to strength or dexterity. Your level is based off of your attributes and the points you win in the game.' 41 | }, 42 | { question: 'How can I improve my character\'s attributes?', id: 'attributes', 43 | answer: 'More steps! Also more sleep and logging your workouts will improve your character\'s other attributes. Going on quests or battling friends/bosses will also win you skill points you can convert to attribute points.' 44 | }, 45 | { question: 'How can I level up my character?', id: 'levelUp', 46 | answer: 'You level up when you gain experience through steps, battles, or quests.' 47 | }, 48 | { question: 'What is a battle and how do I battle someone?', id: 'battle', 49 | answer: 'A battle is a one-to-one fight where your attributes are stacked up against your friend or the boss\'s attributes. Our game decides who is the most FIT and who deserves to win.' 50 | }, 51 | { question: 'What is a quest and how does it work?', id: 'quest', 52 | answer: 'Quests are ways to motivate you to accomplish specific fitness goals. There are four categories of quests: Steps, distance, sleep, and strength training. They all have their own time limits that range from a few hours to a week. You choose what you want to do and you can continuously check your progress. Once your time has expired, if you\'ve completed the quest, you gain gold and experience points. If not, you lose some.' 53 | }, 54 | { question: 'How often can I do a specific quest?', id: 'questFrequency', 55 | answer: 'We only let you do a quest once a week, and we only let you do one of each type of quest at a time.' 56 | }, 57 | { question: 'What are skill points and what can I do with them?', id: 'skillPts', 58 | answer: 'You gain skill points through leveling up. Each point you get is one you can add to your endurance, dexterity, strength, or vitality. When you have skill points, you will see a small plus button next to your attributes on your dashboard, and you can tap it to increase that attribute.' 59 | }, 60 | { question: 'How do attributes help me in battle?', id: 'attributeDef', 61 | answer: 'Strength increases your attack damage. Dexterity increases your evasion rate. Endurance increases your attack rate. Vitality increases your max HP.' 62 | }, 63 | { question: 'What are weapons, equipment, and potions? How do I buy them?', id: 'purchasing' , 64 | answer: 'You can equip your character with weapons and equipment to give yourself a better chance to beat bosses or friends. To buy them you have to have gold that you\'ve won from quests or battles. Go to the store from the side menu. You can buy multiples of the same item' 65 | }, 66 | { question: 'What can I do with the weapons I buy?', id: 'weapons', 67 | answer: 'Weapons will increase some of your attributes. Depending on the weapon, your strength/dexterity/endurance/vitality may increase by a certain number, to give you an advantage when battling a stronger friend or boss.' 68 | }, 69 | { question: 'What is weapon size?', id: 'weaponsize', 70 | answer: 'The weapon size is the amount of slots a weapon will take up when equipped. A two-handed weapon will take two slots while a one-handed weapon will take only one slot and therefore two one-handed weapons can be equipped.' 71 | }, 72 | { question: 'How do I equip my character with weapons I\'ve bought?', id: 'equip' , 73 | answer: 'You can equip your character with weapons and equipment to give yourself a better chance to beat bosses or friends. To buy them you have to have gold that you\'ve won from quests or battles. Go to the store from the side menu.' 74 | }, 75 | { question: 'How do I add friends that don\'t show up in search?', id: 'addFriends', 76 | answer: 'You can either add friends through Fitbit and if they have our app, they will show up in your friends page. Otherwise, as long as they also have FitRPG installed, you can find them through going to friends and tapping the icon in the top right to search.' 77 | }, 78 | { question: 'How does FitRPG retrieve my Fitbit data, and how do I make sure it is synced?', id: 'fitbitdata', 79 | answer: 'Every time you log in, we retrieve new data from Fitbit. You also have to refresh to see updated data when you sync with Fitbit. To refresh, just drag down the page and the refresh icon will appear at the top, while the app retrieves your updated data.' 80 | }, 81 | { question: 'How much of my personal fitness data can my friends actually see?', id: 'privacy', 82 | answer: 'Your friends cannot see any of your actual fitness data or even your attributes right now. They can only see what level you are, your username, and your picture (and how you rank in the leaderboard ;)!)' 83 | }, 84 | { question: 'How can I contact you with further questions or feedback?', id: 'feedback', 85 | answer: 'Email us at FitRPG@gmail.com for anything we may have missed!' 86 | } 87 | ]; 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /www/app/help/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 13 |
14 | 15 |
16 |
17 |
18 | Frequently Asked Questions 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 |
30 | Answers 31 |
32 |
33 |

{{ answer.question }}

34 | 35 | 36 |

{{ answer.answer }}

37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /www/app/help/help.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.help', [ 4 | 5 | 'mobile.help.controllers' 6 | ]); 7 | 8 | angular.module('mobile.help.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/inventory/inventory-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.inventory.controllers') 2 | 3 | .controller('InventoryCtrl', function($scope, Shop, $ionicLoading) { 4 | // inventory is accessed from $rootScope.user.inventory in the template 5 | var inventory = $scope.user.inventory; 6 | 7 | var makeCopy = function(object) { 8 | objectCopy = {}; 9 | for (var key in object) { 10 | objectCopy[key] = object[key]; 11 | } 12 | return objectCopy; 13 | }; 14 | 15 | $scope.inventory = []; 16 | var loading = setTimeout(function(){ 17 | $ionicLoading.show({ 18 | template: '

Loading...

' 19 | }); 20 | }, 500); 21 | 22 | Shop.query( function (storeItems) { 23 | for (var i=0; i 0) { 31 | return 'text-green'; 32 | } else { 33 | 34 | return 'text-red'; 35 | } 36 | }; 37 | 38 | $scope.sellItem = function() { 39 | var title, body, callback; 40 | if (item.equipped === false) { 41 | $scope.user.attributes.gold = $scope.user.attributes.gold + $scope.inventoryItem.sellPrice; 42 | if ($scope.inventoryItem.type.toLowerCase() !== 'potion') { 43 | // remove from inventory 44 | $scope.user.inventory.splice(index, 1); 45 | } else { 46 | if (item.quantity > 1) { 47 | item.quantity -= 1; 48 | } else if (item.quantity === 1) { 49 | $scope.user.inventory.splice(index, 1); 50 | } 51 | } 52 | // save user 53 | User.update($scope.user); 54 | title = 'Item Sold'; 55 | body = 'You received ' + $scope.inventoryItem.sellPrice + ' gold for your item.'; 56 | callback = function() { 57 | $state.go('app.character'); 58 | } 59 | } else { 60 | title = 'Item Equipped'; 61 | body = 'You must unequip your item before you can sell it.'; 62 | callback = function(){}; 63 | } 64 | 65 | util.showAlert($ionicPopup, title, body, 'OK', callback); 66 | }; 67 | 68 | var setEquippedItem = function(slot, inventoryItem, name) { 69 | $scope.user.equipped[slot].name = name; 70 | $scope.user.equipped[slot].inventoryId = parseFloat(inventoryItem.id); 71 | return true; 72 | }; 73 | 74 | var addItemAttributes = function() { 75 | $scope.user.attributes.strength += $scope.inventoryItem.strength; 76 | $scope.user.attributes.vitality += $scope.inventoryItem.vitality; 77 | $scope.user.attributes.dexterity += $scope.inventoryItem.dexterity; 78 | $scope.user.attributes.endurance += $scope.inventoryItem.endurance; 79 | }; 80 | 81 | $scope.equipItem = function() { 82 | var itemSet = false; 83 | if (item.equipped === false) { 84 | if ($scope.inventoryItem.type.toLowerCase() === 'weapon') { 85 | if ($scope.inventoryItem.size === 1) { 86 | if ($scope.user.equipped.weapon1.name === '') { 87 | itemSet = setEquippedItem('weapon1',item,$scope.inventoryItem.name) 88 | } else if ($scope.user.equipped.weapon2.name === '') { 89 | itemSet = setEquippedItem('weapon2',item,$scope.inventoryItem.name) 90 | } 91 | } else if ($scope.inventoryItem.size === 2) { 92 | if ($scope.user.equipped.weapon1.name === '' && $scope.user.equipped.weapon2.name === '') { 93 | itemSet = setEquippedItem('weapon1',item,$scope.inventoryItem.name) 94 | itemSet = setEquippedItem('weapon2',item,$scope.inventoryItem.name) 95 | } 96 | } 97 | } else if ($scope.inventoryItem.type.toLowerCase() === 'armor') { 98 | if ($scope.user.equipped.armor.name === '') { 99 | itemSet = setEquippedItem('armor',item,$scope.inventoryItem.name) 100 | } 101 | } else if ($scope.inventoryItem.type.toLowerCase() === 'accessory') { 102 | if ($scope.user.equipped.accessory1.name === '') { 103 | itemSet = setEquippedItem('accessory1',item,$scope.inventoryItem.name) 104 | } else if ($scope.user.equipped.accessory2.name === '') { 105 | itemSet = setEquippedItem('accessory2',item,$scope.inventoryItem.name) 106 | } 107 | } 108 | if (itemSet) { 109 | item.equipped = true; 110 | addItemAttributes(); 111 | User.update($scope.user); 112 | util.showAlert($ionicPopup, 'Item Equipped','You are ready to wage war against the forces of evil.', 'OK', function() { 113 | $state.go('app.character'); 114 | }) 115 | } else { 116 | util.showAlert($ionicPopup, 'Remove An Item','You are holding too many items. You need to take off an item before you can put this on.', 'OK', function() { 117 | $state.go('app.character'); 118 | }) 119 | } 120 | } else { 121 | util.showAlert($ionicPopup, 'Item Already Equipped','You are already using this item. Select a different item to equip.', 'OK', function() { 122 | $state.go('app.character'); 123 | }) 124 | } 125 | }; 126 | 127 | $scope.useItem = function() { 128 | var totalVitality = $scope.user.attributes.vitality + $scope.user.fitbit.vitality; 129 | var maxHp = util.vitalityToHp(totalVitality,$scope.user.characterClass); 130 | if (item.quantity > 0) { 131 | $scope.user.attributes.HP += $scope.inventoryItem.hp; 132 | if ($scope.user.attributes.HP > maxHp) { 133 | $scope.user.attributes.HP = maxHp; 134 | } 135 | // subtract quantity from inventory -> remove if quantity = 0 136 | item.quantity -= 1; 137 | } 138 | 139 | if (item.quantity === 0) { 140 | $scope.user.inventory.splice(index, 1); 141 | } 142 | 143 | User.update($scope.user); 144 | util.showAlert($ionicPopup, 'HP Recovered','Your HP is recovering!', 'OK', function() { 145 | $state.go('app.character'); 146 | }) 147 | }; 148 | 149 | $scope.checkType = function() { 150 | if ($scope.inventoryItem) { 151 | if ($scope.inventoryItem.type.toLowerCase() === 'potion') { 152 | return true; 153 | } else { 154 | return false; 155 | } 156 | } 157 | }; 158 | }); 159 | -------------------------------------------------------------------------------- /www/app/inventory/inventory-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 |
7 | 8 |

{{inventoryItem.name}}

9 |

Type: {{inventoryItem.type}}

10 |
11 | 12 |
13 |

14 | Strength

15 | Vitality

16 | Endurance

17 | Dexterity

18 | Size

19 | Sell Price 20 |

21 |

22 | 23 | {{ user.attributes.strength }} 24 | {{ user.attributes.strength + inventoryItem.strength }} 25 |

26 | {{ user.attributes.vitality }} 27 | {{ user.attributes.vitality + inventoryItem.vitality }} 28 |

29 | {{ user.attributes.endurance }} 30 | {{ user.attributes.endurance + inventoryItem.endurance }} 31 |

32 | {{ user.attributes.dexterity }} 33 | {{ user.attributes.dexterity + inventoryItem.dexterity }} 34 |

35 | {{ inventoryItem.sizeText }} 36 |

37 | {{ inventoryItem.sellPrice }} Gold 38 |

39 |

40 | 41 |
42 | 43 |
44 |

{{ inventoryItem.description }}


45 |

Quantity: {{ inventoryItem.quantity }}


46 |

Sell Price

47 |

{{ inventoryItem.sellPrice }} Gold

48 |
49 | 50 | 64 |
65 | 66 |
67 | 68 |
69 |
70 | -------------------------------------------------------------------------------- /www/app/inventory/inventory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | Equipment 9 | Items 10 |
11 |
12 | 13 | 14 | 15 |
16 |
17 | Weapons 18 |
19 | 20 |
21 | 22 |

{{item.name}}

23 |

{{item.equipped}}

24 | 25 |
26 | No Weapons Available 27 |
28 | 29 |
30 | Armor 31 |
32 | 33 | 41 | 42 |
43 | Accessories 44 |
45 | 46 |
47 | 48 | 49 |

{{item.name}}

50 |

{{item.equipped}}

51 | 52 |
53 | No Accessories Available 54 |
55 | 56 |
57 | 58 |
59 |
60 | Potions 61 |
62 | 63 |
64 | 65 | 66 |

{{item.name}}

67 | 68 |
69 | No Potions Available 70 |
71 |
72 | 73 |
74 |
75 | -------------------------------------------------------------------------------- /www/app/inventory/inventory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.inventory', [ 4 | 5 | 'mobile.inventory.controllers' 6 | ]); 7 | 8 | angular.module('mobile.inventory.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/leaderboard/leaderboard-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.leaderboard.controllers') 2 | 3 | .controller('LeaderboardCtrl', function($scope, $ionicLoading, User, Leaderboard) { 4 | var loading = setTimeout(function(){ 5 | $ionicLoading.show({ 6 | template: '

Loading...

' 7 | }); 8 | }, 500); 9 | 10 | $scope.allTab = 'button-tab-active'; 11 | $scope.all = function() { 12 | $scope.allTab = 'button-tab-active'; 13 | $scope.friendsTab = ''; 14 | $scope.leaderboard = []; 15 | Leaderboard.query(function(users) { 16 | $scope.leaderboard = users; 17 | clearTimeout(loading); 18 | $ionicLoading.hide(); 19 | }) 20 | }; 21 | 22 | $scope.friends = function() { 23 | $scope.allTab = ''; 24 | $scope.friendsTab = 'button-tab-active'; 25 | $scope.leaderboard = []; 26 | $scope.leaderboard.push($scope.user); 27 | for(var i = 0; i < $scope.user.friends.length; i++){ 28 | User.get({id : $scope.user.friends[i]}, function(user) { 29 | if (user['_id']) { 30 | $scope.leaderboard.push(user); 31 | } 32 | }) 33 | } 34 | }; 35 | 36 | $scope.all(); 37 | }); 38 | -------------------------------------------------------------------------------- /www/app/leaderboard/leaderboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | All 9 | Friends 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 |

{{leader.username}}

18 |

Level {{leader.attributes.level}} {{leader.character}}

19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /www/app/leaderboard/leaderboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.leaderboard', [ 4 | 5 | 'mobile.leaderboard.controllers' 6 | ]); 7 | 8 | angular.module('mobile.leaderboard.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/main/main-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.main.controllers') 2 | 3 | .controller('CharacterCtrl', function( 4 | $rootScope, 5 | $scope, 6 | $state, 7 | $ionicLoading, 8 | $ionicNavBarDelegate, 9 | $ionicPopup, 10 | $ionicPlatform, 11 | User, 12 | Shop, 13 | Quests, 14 | TimesData, 15 | DatesData, 16 | Refresh, 17 | Settings, 18 | localStorageService, 19 | $window ) 20 | { 21 | // initialize $rootScope.user to eliminate console errors before authentication 22 | var loading = setTimeout(function(){ 23 | $ionicLoading.show({ 24 | template: '

Loading...

' 25 | }); 26 | }, 500); 27 | 28 | $scope.calculatedData = {}; 29 | $scope.alertCount = 0; 30 | $scope.showAlert = false; 31 | 32 | var device = { 33 | isApple: ionic.Platform.isIOS(), 34 | isGoogle: ionic.Platform.isAndroid(), 35 | }; 36 | 37 | var addAlert = function(status, name) { 38 | name = name || ''; 39 | var type, msg; 40 | if (status === 'loss') { 41 | type = 'danger'; 42 | msg = 'Looks like you need to work out more. You lost to ' + name + '.'; 43 | } else if (status === 'win') { 44 | type = 'success'; 45 | msg = 'You beat ' + name + '. You gained experience and gold.' 46 | } else if (status === 'request') { 47 | type = ''; 48 | msg = 'Someone wants to battle you.'; 49 | } 50 | $scope.alertCount++; 51 | $scope.alerts.push({type: type, msg: msg}); 52 | }; 53 | 54 | var addLevelUpAlert = function() { 55 | var type, msg; 56 | type = 'success'; 57 | msg = 'You leveled up! You\'ve gained skill points to increase your attributes.'; 58 | $scope.alertCount++; 59 | $scope.levelUpAlerts.push({type: type, msg: msg}); 60 | }; 61 | 62 | var addFriendRequestAlert = function() { 63 | var type, msg; 64 | type = 'success'; 65 | msg = 'You have a new friend request. Click here to view the requests.'; 66 | $scope.alertCount++; 67 | $scope.friendRequestAlerts.push({type: type, msg: msg}); 68 | }; 69 | 70 | var addQuestAlert = function(quest) { 71 | var type, msg; 72 | if (quest.status === 'success') { 73 | type = 'success'; 74 | msg = 'You completed your quest to ' + quest.shortDesc.toLowerCase() + ' You won ' + quest.gold + " pieces!"; 75 | } else if (quest.status === 'fail') { 76 | type = 'danger'; 77 | msg = 'Sorry, you didn\'t finish your quest to ' + quest.shortDesc.toLowerCase() + ' You lost gold. Try again in a few days.' 78 | } 79 | $scope.alertCount++; 80 | $scope.questAlerts.push({type: type, msg: msg}); 81 | }; 82 | 83 | $scope.displayAlerts = function() { 84 | $scope.alertCount = 0; 85 | $scope.showAlert = true; 86 | } 87 | 88 | $scope.closeAlert = function(index) { 89 | $scope.alerts.splice(index, 1); 90 | }; 91 | 92 | $scope.closeLevelUpAlert = function(index) { 93 | $scope.levelUpAlerts.splice(index, 1); 94 | }; 95 | 96 | $scope.closeFriendRequestAlert = function(index) { 97 | $scope.friendRequestAlerts.splice(index, 1); 98 | }; 99 | 100 | $scope.closeQuestAlert = function(index) { 101 | $scope.questAlerts.splice(index, 1); 102 | }; 103 | 104 | var calculateData = function(user) { 105 | $scope.calculatedData.currentXp = Math.floor(util.currentLevelExp(user.attributes.level, user.fitbit.experience + user.attributes.experience)); 106 | $scope.calculatedData.requiredXp = util.nextLevelExp(user.attributes.level); 107 | $scope.calculatedData.strength = user.attributes.strength + user.fitbit.strength; 108 | $scope.calculatedData.vitality = user.attributes.vitality + user.fitbit.vitality; 109 | $scope.calculatedData.dexterity = user.attributes.dexterity + user.fitbit.dexterity; 110 | $scope.calculatedData.endurance = user.attributes.endurance + user.fitbit.endurance; 111 | $scope.calculatedData.maxHp = util.vitalityToHp($scope.calculatedData.vitality,$scope.user.characterClass); //change to $scope.user.characterClass 112 | user.attributes.HP += user.fitbit.HPRecov; 113 | user.fitbit.HPRecov = 0; 114 | if (user.attributes.HP > $scope.calculatedData.maxHp) { 115 | user.attributes.HP = $scope.calculatedData.maxHp; 116 | } 117 | if (user.attributes.gold < 0) { 118 | user.attributes.gold = 0; 119 | } 120 | }; 121 | 122 | var alertBattleStatus = function() { 123 | $scope.alerts = []; 124 | var listOfIndices = []; 125 | var alertWin = false; 126 | var alertLoss = false; 127 | var alertRequest = false; 128 | for (var i=0; i<$rootScope.user.missionsVersus.length; i++) { 129 | var alertMission = function(index){ 130 | var mission = $rootScope.user.missionsVersus[index]; 131 | if (mission.type === 'battle') { 132 | if (mission.status === 'win' || mission.status === 'loss') { 133 | User.get({id: mission.enemy}, function(enemy){ 134 | addAlert(mission.status,enemy.profile.displayName); 135 | }); 136 | listOfIndices.push(index); 137 | } else if (mission.status === 'request' && !alertRequest) { 138 | alertRequest = true; 139 | addAlert(mission.status); 140 | } 141 | } 142 | }; 143 | alertMission(i); 144 | } 145 | 146 | var removeMission = function(index,count) { 147 | if (count < listOfIndices.length) { 148 | $rootScope.user.missionsVersus.splice(index-count,1); 149 | removeMission(listOfIndices[count+1],count+1); 150 | } 151 | }; 152 | 153 | if (listOfIndices.length > 0) { 154 | removeMission(listOfIndices[0],0); 155 | } 156 | 157 | }; 158 | 159 | var alertLevelUpStatus = function() { 160 | $scope.levelUpAlerts = []; 161 | var userLevel = localStorageService.get('level'); 162 | var currentLevel = $rootScope.user.attributes.level; 163 | if (!userLevel) { 164 | userLevel = localStorageService.set('level',1); 165 | } 166 | if (userLevel < currentLevel) { 167 | localStorageService.set('level', currentLevel); 168 | addLevelUpAlert(); 169 | } 170 | }; 171 | 172 | var alertFriendRequestStatus = function() { 173 | $scope.friendRequestAlerts = []; 174 | var alertRequest = false; 175 | if ($rootScope.user.friendRequests) { 176 | for (var i=0; i<$rootScope.user.friendRequests.length; i++) { 177 | var request = $rootScope.user.friendRequests[i]; 178 | if (request.status === 'request' && !alertRequest) { 179 | alertRequest = true; 180 | addFriendRequestAlert(); 181 | } 182 | } 183 | } 184 | }; 185 | 186 | var alertQuestStatus = function() { 187 | $scope.questAlerts = []; 188 | var today = parseInt(Date.parse(new Date())); 189 | for (var j =0; j< $rootScope.user.quests.length; j++) { 190 | (function(i) { 191 | var quest = $rootScope.user.quests[i]; 192 | if (quest.status === 'active') { 193 | var completeDate = parseInt(Date.parse(quest.completionTime)); 194 | if (today >= completeDate) { 195 | if (quest.numDays < 1) { 196 | TimesData.get(quest.getObj, function(result) { 197 | var total = result.total; 198 | if (total >= quest.winGoal) { 199 | $rootScope.user.quests[i].status = 'success'; 200 | $rootScope.user.attributes.gold += quest.gold; 201 | } else { 202 | $rootScope.user.quests[i].status = 'fail'; 203 | $rootScope.user.attributes.gold = $rootScope.user.attributes.gold - Math.floor(quest.gold/3); 204 | } 205 | User.update($rootScope.user); 206 | addQuestAlert(quest); 207 | }); 208 | } else if (quest.numDays > 0 ) { 209 | DatesData.get(quest.getObj, function(result) { 210 | var total = result.total; 211 | if (total >= quest.winGoal) { 212 | $rootScope.user.quests[i].status = 'success'; 213 | $rootScope.user.attributes.gold += quest.gold; 214 | } else { 215 | $rootScope.user.quests[i].status = 'fail'; 216 | $rootScope.user.attributes.gold = $rootScope.user.attributes.gold - Math.floor(quest.gold/3); 217 | } 218 | User.update($rootScope.user); 219 | addQuestAlert(quest); 220 | }); 221 | } 222 | } 223 | } 224 | }(j)); 225 | } 226 | }; 227 | 228 | var setWeapons = function() { 229 | var defaultWeapon = function(location) { 230 | $rootScope.user.equipped[location] = {}; 231 | $rootScope.user.equipped[location].name = ''; 232 | $rootScope.user.equipped[location].inventoryId = null; 233 | }; 234 | 235 | if (!$rootScope.user.equipped) { 236 | $rootScope.user.equipped = {}; 237 | defaultWeapon('weapon1'); 238 | defaultWeapon('weapon2'); 239 | defaultWeapon('armor'); 240 | defaultWeapon('accessory1'); 241 | defaultWeapon('accessory2'); 242 | } 243 | } 244 | 245 | var localUserId = localStorageService.get('userId'); //'2Q2TVT'; // 246 | 247 | var checkNewData = function() { 248 | User.get({id : localUserId}, function (user) { 249 | $rootScope.user = user; 250 | setWeapons(); 251 | getSettings(); 252 | if (user.needsUpdate === true) { 253 | Refresh.get({id:localUserId}, function() { 254 | User.get({id:localUserId}, function(user) { 255 | console.log('needed a new update'); 256 | $rootScope.user = user; 257 | calculateData($rootScope.user); 258 | alertBattleStatus(); 259 | alertLevelUpStatus(); 260 | alertFriendRequestStatus(); 261 | alertQuestStatus(); 262 | $rootScope.user.needsUpdate = false; 263 | User.update($rootScope.user); 264 | clearTimeout(loading); 265 | $ionicLoading.hide(); 266 | }); 267 | }); 268 | } else { 269 | console.log('did not need a new update'); 270 | alertBattleStatus(); 271 | calculateData($rootScope.user); 272 | User.update($rootScope.user); 273 | clearTimeout(loading); 274 | $ionicLoading.hide(); 275 | } 276 | }); 277 | } 278 | 279 | var refresh = function() { 280 | console.log('refreshing'); 281 | Refresh.get({id: localUserId}, function() { // this will tell fitbit to get new data 282 | User.get({id : localUserId}, function (user) { // this will retrieve that new data 283 | $rootScope.user = user; 284 | calculateData($rootScope.user); 285 | $scope.alertCount = 0; 286 | $scope.showAlert = false; 287 | alertBattleStatus(); 288 | alertLevelUpStatus(); 289 | alertFriendRequestStatus(); 290 | User.update($rootScope.user); 291 | $scope.$broadcast('scroll.refreshComplete'); 292 | }); 293 | }); 294 | getSettings(); 295 | }; 296 | 297 | $scope.refresh = refresh; 298 | 299 | checkNewData(); 300 | document.addEventListener("resume", checkNewData, false); //whenever we resume the app, retrieve new data if there is any 301 | 302 | $scope.hasSkillPoints = function() { 303 | if ($rootScope.user && $rootScope.user.attributes.skillPts > 0) { 304 | return true; 305 | } else { 306 | return false; 307 | } 308 | }; 309 | 310 | $scope.applyAttributes = function(attr) { 311 | if ($rootScope.user.attributes.skillPts > 0) { 312 | $rootScope.user.attributes[attr]++; 313 | $rootScope.user.attributes.skillPts--; 314 | if (attr === 'vitality') { 315 | // change char class from warrior to user class 316 | // $rootScope.user.attributes.hp = util.vitalityToHp($rootScope.user.attributes.vitality,'warrior'); 317 | $scope.calculatedData.maxHp = util.vitalityToHp($rootScope.user.attributes.vitality,'warrior'); 318 | } 319 | calculateData($rootScope.user); 320 | // update database 321 | User.update($rootScope.user); 322 | } 323 | }; 324 | 325 | $scope.isEquipped = function(slot) { 326 | var user = $rootScope.user; 327 | if (user && user.equipped[slot].inventoryId !== null) { 328 | return true; 329 | } else { 330 | return false; 331 | } 332 | }; 333 | 334 | $scope.unequip = function(slot){ 335 | var inventory = $rootScope.user.inventory; 336 | var inventoryId = $rootScope.user.equipped[slot].inventoryId; 337 | 338 | var empty = function(location) { 339 | $rootScope.user.equipped[location].name = ''; 340 | $rootScope.user.equipped[location].inventoryId = null; 341 | }; 342 | 343 | if (slot === 'weapon1' || slot === 'weapon2') { 344 | if ($rootScope.user.equipped['weapon1'].inventoryId === $rootScope.user.equipped['weapon2'].inventoryId) { 345 | empty('weapon1'); 346 | empty('weapon2'); 347 | } 348 | } 349 | 350 | empty(slot); 351 | 352 | var storeId; 353 | for (var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | {{alert.msg}} 26 | {{alert.msg}} 27 | {{alert.msg}} 28 | {{alert.msg}} 29 | 30 |
31 |
32 |
33 | 34 |

{{user.username}}

35 |

Level: {{user.attributes.level}} {{user.character}}

36 |
37 | 38 |
39 |
40 | {{user.attributes.skillPts}} Skill Points 41 |
42 |
43 | {{user.attributes.gold}} Gold 44 |
45 |
46 | 47 |
48 |
49 |

50 | XP 51 | {{calculatedData.currentXp}} / {{calculatedData.requiredXp}} 52 |

53 | 54 |
55 |
56 | 57 |
58 |
59 |

60 | HP 61 | {{user.attributes.HP}} / {{calculatedData.maxHp}} 62 |

63 | 64 |
65 |
66 | 67 |
68 |
69 | 70 |

{{calculatedData.strength}}

71 |

Strength

72 |
73 |
74 | 75 |

{{calculatedData.vitality}}

76 |

Vitality

77 |
78 |
79 | 80 |

{{calculatedData.endurance}}

81 |

Endurance

82 |
83 |
84 | 85 |

{{calculatedData.dexterity}}

86 |

Dexterity

87 |
88 |
89 | 109 |
110 | 111 |
112 |
113 | What I'm Wielding 114 | 115 | 116 | 117 |
118 | 119 |
120 |

Weapons

121 |
122 |

{{user.equipped.weapon1.name}}

123 |
124 |
125 |

{{user.equipped.weapon2.name}}

126 |
127 |
128 | 129 |
130 |

Armor

131 |
132 |

{{user.equipped.armor.name}}

133 |
134 |
135 | 136 |
137 |

Accessories

138 |
139 |

{{user.equipped.accessory1.name}}

140 |
141 |
142 |

{{user.equipped.accessory2.name}}

143 |
144 |
145 | 146 |
147 | 148 |
149 | 150 |
151 | 152 | -------------------------------------------------------------------------------- /www/app/main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.main', [ 4 | 5 | 'mobile.main.controllers' 6 | ]); 7 | 8 | angular.module('mobile.main.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/menu/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Menu

13 |
14 | 15 | 16 | 17 | 18 | Character 19 | 20 | 21 | 22 | Battles 23 | 24 | 25 | 26 | Quests 27 | 28 | 29 | 30 | Inventory 31 | 32 | 33 | 34 | Shop 35 | 36 | 37 | 38 | Friends 39 | 40 | 41 | 42 | Leaderboard 43 | 44 | 45 | 46 | Help 47 | 48 | 49 | 50 | Logout 51 | 52 | 53 | 54 |
55 |
56 | -------------------------------------------------------------------------------- /www/app/quest/quest-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.quest.controllers') 2 | 3 | // This controller handles the list of the quests that show up as all,active,and completed 4 | .controller('QuestCtrl', function($scope, $ionicLoading, $ionicScrollDelegate, localStorageService, SoloMissions, Quests, User, TimesData, DatesData) { 5 | 6 | // Creates the loading screen that only shows up after 500 ms if items have not yet loaded 7 | var loading = setTimeout(function(){ 8 | $ionicLoading.show({ 9 | template: '

Loading...

' 10 | }); 11 | }, 500); 12 | 13 | $scope.allTab = 'button-tab-active'; 14 | $scope.all = function() { 15 | $scope.isAll = true; 16 | $scope.isActive = false; 17 | $scope.isComplete = false; 18 | $scope.quests = []; 19 | $scope.availableQuests = []; 20 | $scope.allTab = 'button-tab-active'; 21 | $scope.activeTab = ''; 22 | $scope.completedTab = ''; 23 | $ionicScrollDelegate.scrollTop(); 24 | 25 | // Create an array of ids of quests the user is currently doing 26 | var currentQuests = []; 27 | for (var i = 0; i < $scope.user.quests.length; i++ ) { 28 | currentQuests.push($scope.user.quests[i].questId); 29 | } 30 | // Compare all quests against this array and only display them if they're not on it 31 | Quests.query(function(quests){ 32 | $scope.quests = quests; 33 | for (var i =0; i<$scope.quests.length;i++) { 34 | if (currentQuests.indexOf($scope.quests[i]._id) < 0) { 35 | $scope.availableQuests.push($scope.quests[i]); 36 | } 37 | } 38 | clearTimeout(loading); 39 | $ionicLoading.hide(); 40 | }); 41 | }; 42 | 43 | // Show all of the quests that are active 44 | $scope.active = function() { 45 | $scope.isAll = false; 46 | $scope.isActive = true; 47 | $scope.isComplete = false; 48 | $scope.allTab = ''; 49 | $scope.activeTab = 'button-tab-active'; 50 | $scope.completedTab = ''; 51 | $ionicScrollDelegate.scrollTop(); 52 | 53 | var today = new Date(); 54 | $scope.quests = []; 55 | for (var i =0; i< $scope.user.quests.length; i++) { 56 | if($scope.user.quests[i].status === 'active') { 57 | (function(i) { 58 | Quests.get({id : $scope.user.quests[i].questId}, function(q) { 59 | $scope.quests.push(q); 60 | }); 61 | }(i)); 62 | } 63 | } 64 | }; 65 | 66 | // Show all of the quests that have already been completed in the last week 67 | $scope.completed = function() { 68 | $scope.isAll = false; 69 | $scope.isActive = false; 70 | $scope.isComplete = true; 71 | $scope.successfulQuests = []; 72 | $scope.failedQuests = []; 73 | $scope.allTab = ''; 74 | $scope.activeTab = ''; 75 | $scope.completedTab = 'button-tab-active'; 76 | $ionicScrollDelegate.scrollTop(); 77 | var today = new Date(); 78 | var refreshQuests = []; 79 | 80 | // Iterate over all the quests that the user has stored; maybe eventually just save them 81 | // locally to the user object 82 | for (var j =0; j< $scope.user.quests.length; j++) { 83 | (function(i) { 84 | var quest = $scope.user.quests[i]; 85 | var completeDate = new Date(quest.completionTime); //convert date to matched format 86 | // Iterate over all the quests and get the ones that have status of completed 87 | if(quest.status === 'success' || quest.status === 'fail') { 88 | // if 7 days have passed since this was completed, they can do it again so we remove it from their user array 89 | if(completeDate.addDays(7) > today) { 90 | refreshQuests.push(quest); 91 | Quests.get({id : quest.questId}, function(q) { 92 | q.completionTime = quest.completionTime; //add completion time so we can sort them 93 | if (quest.status === 'success') { 94 | $scope.successfulQuests.push(q); 95 | } else { 96 | $scope.failedQuests.push(q); 97 | } 98 | }); 99 | } 100 | } else { 101 | refreshQuests.push(quest); 102 | } 103 | }(j)); 104 | } 105 | 106 | $scope.user.quests = refreshQuests; 107 | User.update($scope.user); 108 | }; 109 | 110 | $scope.all(); 111 | 112 | $scope.showList = { 113 | steps: true, 114 | distance: true, 115 | strength: true, 116 | sleep: true, 117 | succeed: true, 118 | fail: true 119 | } 120 | 121 | $scope.toggleList = function (list) { 122 | $scope.showList[list] = !$scope.showList[list]; 123 | }; 124 | 125 | }); 126 | -------------------------------------------------------------------------------- /www/app/quest/quest-detail-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.quest.controllers') 2 | 3 | .constant('daysWeek', { 4 | 0: 'Sunday', 5 | 1: 'Monday', 6 | 2: 'Tuesday', 7 | 3: 'Wedneday', 8 | 4: 'Thursday', 9 | 5: 'Friday', 10 | 6: 'Saturday' 11 | }) 12 | 13 | // This particular controller handles the individual pages of each quest 14 | .controller('QuestDetailCtrl', function($scope, $state, $stateParams, Quests, $ionicPopup, User, TimesData, DatesData, daysWeek, finishQuest, NewTimesData, $cordovaSocialSharing) { 15 | var questId = $stateParams.questId 16 | $scope.quest = Quests.get({id: questId}); 17 | $scope.availableQuest = true; 18 | $scope.activeQuest = false; 19 | $scope.completedQuest = false; 20 | $scope.userQuest; 21 | 22 | // Show 1-5 stars on the screen depending on the difficulty 23 | $scope.difficulty = function(num) { 24 | return num <= $scope.quest.difficulty; 25 | }; 26 | 27 | var sendTweet = function (message) { 28 | $cordovaSocialSharing.shareViaTwitter(message).then(function(result) { 29 | // Success! 30 | }, function(err) { 31 | // An error occured. Show a message to the user 32 | }); 33 | }; 34 | 35 | // Check if the user currently has this quest active or completed 36 | var pickOutQuest = function() { 37 | for (var i = 0; i < $scope.user.quests.length; i++) { 38 | $scope.winGoal = $scope.user.quests[i].winGoal; 39 | if ($scope.user.quests[i].questId === questId) { 40 | $scope.availableQuest = false; 41 | $scope.userQuest = $scope.user.quests[i]; 42 | evalQuest(); 43 | return; //stop looping 44 | } 45 | } 46 | }; 47 | 48 | // Called in pickOutQuest, and when we refresh to show updated data 49 | // We also return true or false for whether or not the quest has been completed 50 | var evalQuest = function(cb) { 51 | if ($scope.userQuest.status === 'active') { 52 | $scope.activeQuest = true; 53 | $scope.$broadcast('timer-set-countdown'); 54 | $scope.parsedDate = Date.parse($scope.userQuest.completionTime); 55 | if ($scope.userQuest.numDays < 1) { 56 | if ($scope.userQuest.getObj.startDate !== $scope.userQuest.getObj.endDate) { 57 | var newGetObjectBeforeMidnight = {}; 58 | newGetObjectBeforeMidnight.startTime = $scope.userQuest.getObj.startTime; 59 | newGetObjectBeforeMidnight.endTime = '23:59'; 60 | newGetObjectBeforeMidnight.startDate = $scope.userQuest.getObj.startDate; 61 | newGetObjectBeforeMidnight.id = $scope.userQuest.getObj.id; 62 | newGetObjectBeforeMidnight.activity = $scope.userQuest.getObj.activity; 63 | NewTimesData.get(newGetObjectBeforeMidnight, function(result) { 64 | var preMidnightTotal = parseInt(result.total) || 0; 65 | var newGetObjectAfterMidnight = {}; 66 | newGetObjectAfterMidnight.startTime = '00:00'; 67 | newGetObjectAfterMidnight.endTime = $scope.userQuest.getObj.endTime; 68 | newGetObjectAfterMidnight.startDate = $scope.userQuest.getObj.endDate; 69 | newGetObjectAfterMidnight.id = $scope.userQuest.getObj.id; 70 | newGetObjectAfterMidnight.activity = $scope.userQuest.getObj.activity; 71 | NewTimesData.get(newGetObjectAfterMidnight, function(result2) { 72 | var postMidnightTotal = parseInt(result2.total) || 0; 73 | $scope.progress = preMidnightTotal + postMidnightTotal; 74 | var completed = $scope.progress >= $scope.winGoal; 75 | console.log('completed',completed); 76 | if (cb) { cb(completed) }; 77 | }); 78 | }); 79 | } else { 80 | TimesData.get($scope.userQuest.getObj, function(result) { 81 | $scope.progress = result.total || 0; //current progress 82 | var completed = $scope.progress >= $scope.winGoal; 83 | if (cb) { cb(completed) }; 84 | }); 85 | } 86 | } else if ($scope.userQuest.numDays > 0 ) { // multiday quests 87 | DatesData.get($scope.userQuest.getObj, function(result) { 88 | $scope.progress = result.total || 0; // current progress 89 | var completed = $scope.progress >= $scope.winGoal; 90 | if (cb) { cb(completed) }; 91 | }); 92 | } 93 | } else if ($scope.userQuest.status === 'success' || $scope.userQuest.status === 'fail') { 94 | $scope.completedQuest = true; 95 | if (cb) {cb()}; 96 | } 97 | }; 98 | 99 | // function that helps us format the times for dates to make calls to fitbit, so '5' is '05' 100 | var timify = function(time) { 101 | if (time.length === 1) { 102 | time = '0' + time; 103 | } 104 | return time; 105 | } 106 | 107 | $scope.startQuest = function() { 108 | 109 | var numDays = $scope.quest.numDays; 110 | var numHours = $scope.quest.numHours; 111 | var winGoal = $scope.quest.winGoals; 112 | var gold = $scope.quest.gold; 113 | var desc = $scope.quest.shortDescription; 114 | var start = new Date(); // start date 115 | var addD = numDays-1 >= 0 ? numDays-1 : 0; // the number of days we're adding to get to 'end' 116 | var startTime = timify(start.getHours()) + ':' + timify(start.getMinutes()); 117 | var title = 'Embark On A New Quest!'; 118 | var endString,end,body; 119 | 120 | // for multi-day quests, there's a type, MAY LATER ON HAVE TO ACCOUNT FOR DIFFERENT SLEEP QUESTS 121 | if (numDays >=1) { 122 | end = start.addDays(addD); // end date 123 | end.setHours(23); 124 | end.setMinutes(59); // timer always ends at midnight 125 | endString = daysWeek[end.getDay()] + ' at 11:59PM'; 126 | body = 'This is the mission you\'ve chosen:
' + $scope.quest.shortDescription + 127 | '
You will have from today until ' + endString + 128 | ' to complete this quest. Do you accept?'; 129 | } else { // for one-day quests, we want to keep the times, otherwise we don't care 130 | end = start.addDays(addD,numHours); // end date 131 | endString =end.toLocaleTimeString(); 132 | body = 'This is the mission you\'ve chosen:
' + $scope.quest.shortDescription + 133 | '
You will have until ' + endString + 134 | ' to complete this quest. Do you accept?'; 135 | } 136 | 137 | var startTheQuest = function() { 138 | 139 | // Generate the quest object to be saved to the user's quests array 140 | var questObj = { 141 | questId: questId, 142 | numDays : numDays, 143 | numHours : numHours, 144 | completionTime: end, 145 | status: 'active', 146 | winGoal: winGoal, 147 | gold : gold, 148 | shortDesc : desc, 149 | endString : endString, 150 | getObj: { // the query object to be used when calling $resource 151 | id : $scope.user._id, 152 | activity : $scope.quest.activity, 153 | startDate : start.yyyymmdd(), 154 | endDate : end.yyyymmdd(), 155 | } 156 | }; 157 | 158 | if (numDays >= 1 ) { 159 | questObj.getObj.type = $scope.quest.type; 160 | } else { 161 | questObj.getObj.startTime = timify(start.getHours()) + ':' +timify(start.getMinutes()); 162 | questObj.getObj.endTime = timify(end.getHours()) + ':' +timify(end.getMinutes()); 163 | } 164 | 165 | $scope.availableQuest = false; 166 | $scope.user.quests.push(questObj); 167 | User.update($scope.user); 168 | $state.go('app.quest'); 169 | 170 | }; 171 | 172 | util.showPrompt($ionicPopup, title, body, 'I accept', 'Too scared', startTheQuest); 173 | 174 | }; 175 | 176 | $scope.refresh = function() { 177 | evalQuest($scope.quest, function() { 178 | $scope.$broadcast('scroll.refreshComplete'); 179 | }); 180 | }; 181 | 182 | $scope.manualCompleteQuest = function() { 183 | var title, body, endQuest, message; 184 | evalQuest(function(completed) { 185 | console.log('completed',completed); 186 | if(completed) { 187 | title = 'Success!'; 188 | body = 'Congratulations! You completed this quest. You won ' + $scope.userQuest.gold + ' gold pieces.'; 189 | message = 'I completed my quest: ' + $scope.userQuest.shortDesc + ' I\'m super fit! @fitrpg'; 190 | endQuest = finishQuest.winQuest; 191 | } else { 192 | title = 'Fail!'; 193 | body = 'Sorry, you did not complete your quest on time. You lost gold. Try again later.'; 194 | message = 'I didn\'t complete my quest: ' + $scope.userQuest.shortDesc + ' I need to train more. @fitrpg'; 195 | endQuest = finishQuest.loseQuest; 196 | } 197 | User.update(endQuest($scope.user,$scope.userQuest)); 198 | util.showPrompt($ionicPopup, title, body, 'Share', 'Continue', 199 | function() { 200 | sendTweet(message); 201 | }, 202 | function() { 203 | $state.go('app.quest'); 204 | } 205 | ); 206 | }); 207 | }; 208 | 209 | // Every time we load the page, we check to see if the quest is completed or not 210 | pickOutQuest(); 211 | }); -------------------------------------------------------------------------------- /www/app/quest/quest-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 |
9 |
10 |
11 | 12 |

{{quest.title}}

13 |

Difficulty: 14 | 15 | 16 | 17 | 18 | 19 |

20 |
21 | 22 |
23 |

{{quest.shortDescription}}

24 |

{{quest.longDescription}}


25 |

Time Limit: {{quest.numDays}} day(s), {{quest.numHours}} hour(s)

26 |

Reward: {{quest.gold}} gold pieces


27 |
28 | 29 |
30 |
31 |

Your Progress{{progress}} / {{winGoal}}

32 | 33 |
34 |
35 | 36 |
37 | 38 | Start Quest 39 |

You recently completed this quest!

40 |
41 | 42 |
43 |
44 | 45 |
46 | You have {{days}} day(s), {{hours}} hour(s), {{minutes}} minute(s), and {{seconds}} second(s) remaining.

47 | 48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /www/app/quest/quest-service.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.quest.services') 2 | 3 | .factory('finishQuest', function() { 4 | return { 5 | winQuest: function(user,userQuest) { 6 | userQuest.status = 'success'; 7 | user.userQuest = userQuest; 8 | user.attributes.gold += userQuest.gold; 9 | return user; 10 | }, 11 | loseQuest: function(user,userQuest) { 12 | userQuest.status = 'fail'; 13 | user.userQuest = userQuest; 14 | user.attributes.gold = user.attributes.gold - Math.floor(userQuest.gold/2); 15 | return user; 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /www/app/quest/quest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | All 9 | Active 10 | Completed 11 |
12 |
13 | 14 | 15 | 16 | 17 | {{alert.msg}} 18 | 19 |
20 | 21 | 22 |
23 |
24 | Step Up 25 |
26 | 27 | 28 |

{{quest.title}}

29 |

{{quest.shortDescription}} Time: {{quest.numDays}} days, {{quest.numHours}} hrs

30 | 31 |
32 | 33 | 34 |
35 | Go the Distance 36 |
37 | 38 | 39 |

{{quest.title}}

40 |

{{quest.shortDescription}} Time: {{quest.numDays}} days, {{quest.numHours}} hrs

41 | 42 |
43 | 44 | 45 |
46 | Crush It 47 |
48 | 49 | Quests to improve your strength and working out will be coming soon! 50 | 51 | 57 | 58 | 59 |
60 | Sleep It Off 61 |
62 | 63 | Quests to improve your sleep will be coming soon! 64 | 65 | 71 | 72 | 73 |
74 | 75 | 76 | 77 | 88 | 89 | 90 | 111 | 112 |
113 | 114 |
115 |
116 | -------------------------------------------------------------------------------- /www/app/quest/quest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.quest', [ 4 | 5 | 'mobile.quest.controllers', 6 | 'mobile.quest.services' 7 | ]); 8 | 9 | angular.module('mobile.quest.controllers', ['LocalStorageModule','ionic']); 10 | angular.module('mobile.quest.services', ['LocalStorageModule','ionic']); 11 | -------------------------------------------------------------------------------- /www/app/select/select-class-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.select.controllers') 2 | 3 | .controller('SelectClassCtrl',function($scope, $state, User) { 4 | 5 | $scope.disable = true; 6 | $scope.selected = function(item) { 7 | $scope.disable = false; 8 | $scope.char = item; 9 | }; 10 | 11 | $scope.characterClasses = [{'text': 'Shadow Elf','value': 'endurance','description':'Endurance bonus for runners'}, 12 | {'text': 'Dwarf King', 'value': 'strength','description':'Strength bonus for gym goers'}, 13 | {'text': 'Rune Knight', 'value': 'dexterity','description':'Dexterity bonus for sports players'}, 14 | {'text': 'Dream Weaver', 'value': 'vitality','description':'Vitality bonus for those who sleep'}]; 15 | 16 | $scope.data = { 17 | clientSide: 'ng' 18 | }; 19 | 20 | $scope.submit = function() { 21 | $scope.user.character = $scope.char.text; 22 | $scope.user.characterClass = $scope.char.value; 23 | User.update($scope.user); 24 | $state.transitionTo('app.character'); 25 | } 26 | 27 | $scope.disable = function() { 28 | if ($scope.username === '' || $scope.username === undefined ) { 29 | return false; 30 | } 31 | return true; 32 | } 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /www/app/select/select-class.html: -------------------------------------------------------------------------------- 1 |
2 |

FitRPG

3 |
4 |
5 |

Choose A Character Class

6 |
7 | 8 | 13 |

{{ item.text }}

14 |

{{ item.description }}

15 |
16 | 17 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /www/app/select/select-username-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.select.controllers') 2 | 3 | .controller('UsernameCtrl',function($rootScope, $scope, $state, User, localStorageService, $cacheFactory, CheckUsername) { 4 | 5 | var localUserId = localStorageService.get('userId'); 6 | 7 | $scope.submit = function(username) { 8 | 9 | // attempt to clear cache - may not work necessarily 10 | // var $httpDefaultCache = $cacheFactory.get('$http'); 11 | // $httpDefaultCache.removeAll(); 12 | // end attempt 13 | 14 | User.get({id : localUserId}, function (user) { 15 | $rootScope.user = user; 16 | CheckUsername.get({username:username}, function (user) { //this will return an object or null 17 | if (user.username === username) { 18 | $window.alert('Sorry! That username already exists.') 19 | } else { 20 | $rootScope.user.username = username; 21 | localStorageService.set('username', username); 22 | } 23 | }); 24 | }); 25 | 26 | $state.transitionTo('select'); // in the future don't let them transition in case their is a duplicate UN 27 | } 28 | 29 | if(localStorageService.get('username')){ 30 | $state.transitionTo('app.character'); 31 | } 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /www/app/select/select-username.html: -------------------------------------------------------------------------------- 1 |
2 |

FitRPG

3 |
4 |
5 |

Choose Character Name

6 |
7 | 8 | 9 |
10 | 13 |
14 | 15 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /www/app/select/select.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.select', [ 4 | 5 | 'mobile.select.controllers' 6 | ]); 7 | 8 | angular.module('mobile.select.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/app/shop/shop-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.shop.controllers') 2 | 3 | .controller('ShopCtrl', function($rootScope, $state, $scope, Shop, $ionicLoading) { 4 | var loading = setTimeout(function(){ 5 | $ionicLoading.show({ 6 | template: '

Loading...

' 7 | }); 8 | }, 500); 9 | 10 | $scope.getData = function() { 11 | $scope.shop = []; 12 | Shop.query( function (items) { 13 | var userLvl = $scope.user.attributes.level; 14 | for (var i=0; i= item.level) { 17 | $scope.shop.push(item); 18 | } 19 | } 20 | clearTimeout(loading); 21 | $ionicLoading.hide(); 22 | }); 23 | }; 24 | 25 | $scope.equipmentTab = 'button-tab-active'; 26 | $scope.equipment = function() { 27 | $scope.isEquipment = true; 28 | $scope.equipmentTab = 'button-tab-active'; 29 | $scope.itemsTab = ''; 30 | }; 31 | 32 | $scope.potion = function(id) { 33 | $scope.isEquipment = false; 34 | $scope.equipmentTab = ''; 35 | $scope.itemsTab = 'button-tab-active'; 36 | }; 37 | 38 | $scope.getData(); 39 | $scope.equipment(); 40 | 41 | $scope.showList = { 42 | weapons: true, 43 | armor: true, 44 | accessories: true, 45 | potions: true 46 | }; 47 | 48 | $scope.toggleList = function(list) { 49 | $scope.showList[list] = !$scope.showList[list]; 50 | }; 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /www/app/shop/shop-detail-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('mobile.shop.controllers') 2 | 3 | .controller('ShopDetailCtrl', function($scope, $stateParams, Shop, User, $ionicPopup, $q) { 4 | $scope.isWeapon = false; 5 | $scope.shopItem = Shop.get({id : $stateParams.shopId}, function(item){ 6 | $scope.shopItem.type = util.capitalize($scope.shopItem.type); 7 | if ($scope.shopItem.size === 1) { 8 | $scope.shopItem.sizeText = 'One-Handed'; 9 | } else if ($scope.shopItem.size === 2) { 10 | $scope.shopItem.sizeText = 'Two-Handed'; 11 | } 12 | if ($scope.shopItem.type === 'Weapon') { 13 | $scope.isWeapon = true; 14 | } 15 | }); 16 | 17 | $scope.addClass = function(attr) { 18 | if (attr >= 0) { 19 | return 'text-green'; 20 | } else { 21 | return 'text-red'; 22 | } 23 | }; 24 | 25 | $scope.buyItem = function() { 26 | if ($scope.user.attributes.gold >= $scope.shopItem.cost) { 27 | $scope.user.attributes.gold = $scope.user.attributes.gold - $scope.shopItem.cost; 28 | // add to inventory 29 | var found = false; 30 | var inventoryId = 0; 31 | if ($scope.user.inventory.length > 0) { 32 | inventoryId = $scope.user.inventory[$scope.user.inventory.length-1].id+1; 33 | } 34 | 35 | if ($scope.shopItem.type.toLowerCase() === 'potion') { 36 | var inventory = $scope.user.inventory; 37 | for (var i=0; i 2 | 3 |
4 |
5 | 6 |
7 | 8 |

{{shopItem.name}}

9 |

Type: {{shopItem.type}}

10 |
11 | 12 |
13 |

14 | Strength

15 | Vitality

16 | Endurance

17 | Dexterity

18 | Size

19 | Cost 20 |

21 |

22 | 23 | {{ user.attributes.strength }} 24 | {{ user.attributes.strength + shopItem.strength }} 25 |

26 | {{ user.attributes.vitality }} 27 | {{ user.attributes.vitality + shopItem.vitality }} 28 |

29 | {{ user.attributes.endurance }} 30 | {{ user.attributes.endurance + shopItem.endurance }} 31 |

32 | {{ user.attributes.dexterity }} 33 | {{ user.attributes.dexterity + shopItem.dexterity }} 34 |

35 | {{ shopItem.sizeText }} 36 |

37 | {{ shopItem.cost }} Gold 38 |

39 | 40 |
41 | 42 | 43 |
44 |

{{ shopItem.description }}

45 |
46 |

Cost

47 |

{{ shopItem.cost }} Gold

48 |
49 | 50 | 51 | 57 |
58 |
59 |
60 | 61 | -------------------------------------------------------------------------------- /www/app/shop/shop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | Equipment 9 | Items 10 |
11 |
12 | 13 | 14 | 15 | 16 | 77 | 78 |
79 |
80 | Potions 81 |
82 | 83 | 90 |
91 | 92 |
93 |
94 | -------------------------------------------------------------------------------- /www/app/shop/shop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mobile.shop', [ 4 | 5 | 'mobile.shop.controllers' 6 | ]); 7 | 8 | angular.module('mobile.shop.controllers', ['LocalStorageModule','ionic']); 9 | -------------------------------------------------------------------------------- /www/assets/css/icostyle.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../fonts/icomoon.eot?-h8ghme'); 4 | src:url('../fonts/icomoon.eot?#iefix-h8ghme') format('embedded-opentype'), 5 | url('../fonts/icomoon.woff?-h8ghme') format('woff'), 6 | url('../fonts/icomoon.ttf?-h8ghme') format('truetype'), 7 | url('../fonts/icomoon.svg?-h8ghme#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | font-family: 'icomoon'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .icon-battle:before { 27 | content: "\e60c"; 28 | } 29 | .icon-fight:before { 30 | content: "\e60b"; 31 | } 32 | .icon-plastron:before { 33 | content: "\e602"; 34 | } 35 | .icon-plain-dagger:before { 36 | content: "\e608"; 37 | } 38 | .icon-locked-fortress:before { 39 | content: "\e609"; 40 | } 41 | .icon-wyvern:before { 42 | content: "\e600"; 43 | } 44 | .icon-podium:before { 45 | content: "\e601"; 46 | } 47 | .icon-checked-shield:before { 48 | content: "\e603"; 49 | } 50 | .icon-anvil:before { 51 | content: "\e604"; 52 | } 53 | .icon-coin:before { 54 | content: "\e605"; 55 | } 56 | .icon-support:before { 57 | content: "\e60a"; 58 | } 59 | .icon-lab:before { 60 | content: "\e606"; 61 | } 62 | .icon-shield:before { 63 | content: "\e607"; 64 | } 65 | -------------------------------------------------------------------------------- /www/assets/css/style.css: -------------------------------------------------------------------------------- 1 | /*#f3796a*/ 2 | /*#adaeae*/ 3 | 4 | /*Quest page*/ 5 | .questTimer { 6 | text-align: center; 7 | font-style: italic; 8 | } 9 | 10 | .battleStatus { 11 | float:right; 12 | } 13 | 14 | /* FAQ & HELP PAGE */ 15 | .item.faq { 16 | font-size:17px; 17 | white-space: normal; 18 | font-family: 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; 19 | padding:11px 11px 11px 11px; 20 | } 21 | 22 | .list.marg { 23 | margin:3px 10px -5px 10px; 24 | } 25 | 26 | .scrollUp { 27 | position:absolute; 28 | right:-36px; 29 | top:15px; 30 | } 31 | 32 | /* Make sure text wraps around */ 33 | .item.list-heading.faqanswer h1 { 34 | white-space:normal; 35 | padding-right:13px; 36 | } 37 | 38 | .item.list-heading.faqanswer p { 39 | white-space:normal; 40 | } 41 | 42 | .item.item-padding.noquest { 43 | white-space:normal; 44 | } 45 | 46 | /* MAIN CHARACTER SCREEN */ 47 | .bar-custom { 48 | background-color: #fff; 49 | border-bottom: 2px solid #f3796a; 50 | } 51 | 52 | .icon-gray { 53 | color: #777; 54 | } 55 | 56 | .bar .title { 57 | color: #f3796a; 58 | text-align: left; 59 | left: 52px !important; 60 | } 61 | 62 | .item-main { 63 | background-color: #f3796a; 64 | padding-left: 85px; 65 | } 66 | 67 | .item-main h3 { 68 | font-weight: bold; 69 | color: #fff; 70 | } 71 | 72 | .item-main p { 73 | color: #fff; 74 | font-size: 12px; 75 | } 76 | 77 | .item-main > img:first-child { 78 | top: 0; 79 | left: 0; 80 | max-width: 70px; 81 | max-height: 100%; 82 | border-radius: 0; 83 | } 84 | 85 | .tab-main-left { 86 | text-align: left; 87 | padding-left: 20px; 88 | } 89 | 90 | .tab-main-right { 91 | text-align: right; 92 | padding-right: 20px; 93 | } 94 | 95 | .title-red { 96 | font-size: 20px; 97 | color: #f3796a; 98 | } 99 | 100 | .small-text { 101 | font-size: 10px; 102 | } 103 | 104 | .medium-text { 105 | font-size: 11px; 106 | } 107 | 108 | .main-attributes { 109 | text-align: center; 110 | } 111 | 112 | .main-attributes i { 113 | position: relative; 114 | float: right; 115 | top: 0px; 116 | right: 0px; 117 | } 118 | 119 | .main-attributes h1 { 120 | font-size: 20px; 121 | color: #777; 122 | margin-top: 5px; 123 | margin-bottom: 0; 124 | } 125 | 126 | .main-attributes p { 127 | font-size: 11px; 128 | } 129 | 130 | .progress-bar { 131 | background-color: #f3796a; 132 | } 133 | 134 | .card-title { 135 | background-color: #f3796a; 136 | font-weight: bold; 137 | color: #fff; 138 | } 139 | 140 | .list-heading { 141 | color: #777; 142 | border-top: none; 143 | border-bottom: none; 144 | } 145 | 146 | .list-heading h1 { 147 | font-size: 16px; 148 | font-weight: normal; 149 | color: #777; 150 | padding-left: 10px; 151 | padding-bottom: 5px; 152 | border-bottom: thin solid #eee; 153 | } 154 | 155 | .list-heading p { 156 | font-size: 14px; 157 | padding-left: 15px; 158 | padding-bottom: 10px; 159 | } 160 | 161 | .button-custom { 162 | background-color: #f3796a; 163 | text-align: center; 164 | color: #fff; 165 | border: 0; 166 | font-size: 20px; 167 | font-style: normal; 168 | padding: 7px 30px 7px 30px; 169 | } 170 | 171 | .no-bottom-margin { 172 | margin-bottom: 0px; 173 | } 174 | 175 | /* END CHARACTER SCREEN */ 176 | 177 | /* BEGIN LIST SCREEN */ 178 | 179 | .button-tab { 180 | padding: 0; 181 | margin: 0; 182 | border: none; 183 | } 184 | 185 | .button-bar > .button-tab-active { 186 | border-bottom: 2px solid #f3796a; 187 | } 188 | 189 | .bar-light .button { 190 | font-size: 14px; 191 | color: #f3796a; 192 | } 193 | 194 | .item-divider { 195 | padding: 4px 25px; 196 | min-height: 18px; 197 | font-size: 14px; 198 | font-weight: 700; 199 | color: #f3796a; 200 | } 201 | 202 | .has-subheader { 203 | top: 84px; 204 | } 205 | 206 | .custom-subheader { 207 | padding-bottom: 0; 208 | height: 39px; 209 | } 210 | 211 | .item p { 212 | margin: 0; 213 | } 214 | 215 | .item-padding { 216 | padding: 10px 25px; 217 | } 218 | 219 | /* END LIST SCREEN */ 220 | 221 | /* BEGIN BATTLE STATS */ 222 | .battle-stats { 223 | text-align: center; 224 | } 225 | 226 | .battle-stats h1 { 227 | font-size: 20px; 228 | color: #777; 229 | margin-top: 5px; 230 | margin-bottom: 0; 231 | } 232 | 233 | .battle-stats p { 234 | font-size: 11px; 235 | } 236 | /* END BATTLE STATS */ 237 | 238 | /* MISC */ 239 | 240 | .alert-dismissable .close { 241 | width: 33px; 242 | height: 41px; 243 | top: -12px; 244 | right: -31px; 245 | } 246 | 247 | /* END MISC */ 248 | 249 | ion-item.refresh.item { 250 | border-style:none; 251 | width: 15px; 252 | right:14px; 253 | position:absolute; 254 | background-color: #EEEEEE; 255 | margin-bottom:20px; 256 | } 257 | 258 | i.ion-refresh.refresh { 259 | position: absolute; 260 | right:13px; 261 | font-size:19px; 262 | color:gray; 263 | top: 0px; 264 | } 265 | 266 | .username { 267 | height: 46px; 268 | padding: 10px 16px; 269 | font-size: 18px; 270 | line-height: 1.33; 271 | border-radius: 6px; 272 | border-style: double; 273 | border-color: black; 274 | } 275 | 276 | .padding-horizontal { 277 | padding: 30px; 278 | } 279 | 280 | .row { 281 | margin-left: 0; 282 | margin-right: 0; 283 | } 284 | 285 | .dashboard { 286 | background-color: #EEEEEE; 287 | } 288 | 289 | .text-red { 290 | color: red; 291 | font-weight: bold; 292 | } 293 | 294 | .text-green { 295 | color: green; 296 | font-weight: bold; 297 | } 298 | 299 | .donut { 300 | margin: 0 auto; 301 | width: 200px; 302 | } 303 | 304 | .popup { 305 | border-radius: 12px; 306 | } 307 | 308 | .popup-head { 309 | background-color: #ef4e3a; 310 | border-top-left-radius: 12px; 311 | border-top-right-radius: 12px; 312 | } 313 | 314 | .popup-title { 315 | color: #fff; 316 | } 317 | 318 | .popup-body { 319 | text-align: center; 320 | min-height: 100px; 321 | } 322 | 323 | .button { 324 | border-width: 0; 325 | } 326 | 327 | .popup-buttons.row { 328 | padding: 10px 40px; 329 | } 330 | 331 | .popup-buttons .button { 332 | border-radius: 45px; 333 | } 334 | 335 | .button.button-positive { 336 | background-color: #777; 337 | } 338 | 339 | .button-swipe { 340 | padding-top: 15px; 341 | } 342 | 343 | .item-input { 344 | margin: 0; 345 | } 346 | 347 | .card .item:last-child, .list-inset .item:last-child { 348 | margin: 0; 349 | } 350 | 351 | #login { 352 | background-color: #f3796a 353 | } 354 | 355 | .login-text { 356 | position: absolute; 357 | width: 250px; 358 | text-align: center; 359 | left: 50%; 360 | top: 20%; 361 | margin-left: -125px; 362 | } 363 | 364 | .login-text h1{ 365 | color: #fff; 366 | font-size: 50px; 367 | } 368 | 369 | .login-text p{ 370 | color: #fff; 371 | font-size: 21px; 372 | line-height: 1.2em; 373 | } 374 | 375 | .loginbutton { 376 | position: absolute; 377 | left: 50%; 378 | margin-left: -150px; 379 | max-width: 300px; 380 | bottom: 15%; 381 | } 382 | 383 | .scroll-content { 384 | padding-bottom: 20px; 385 | } 386 | 387 | .icon-btn { 388 | padding: 0 10px; 389 | } -------------------------------------------------------------------------------- /www/assets/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/fonts/icomoon.eot -------------------------------------------------------------------------------- /www/assets/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /www/assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /www/assets/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/fonts/icomoon.woff -------------------------------------------------------------------------------- /www/assets/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/arrow.png -------------------------------------------------------------------------------- /www/assets/img/dagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/dagger.png -------------------------------------------------------------------------------- /www/assets/img/fitbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/fitbit.png -------------------------------------------------------------------------------- /www/assets/img/fitbitbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/fitbitbutton.png -------------------------------------------------------------------------------- /www/assets/img/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/ionic.png -------------------------------------------------------------------------------- /www/assets/img/jawbone_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/jawbone_icon.jpg -------------------------------------------------------------------------------- /www/assets/img/jawbonebutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/jawbonebutton.png -------------------------------------------------------------------------------- /www/assets/img/questIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitrpg/fitrpg-ionic/151cb16febac5632c8ed61f8aeba9b131af9aa25/www/assets/img/questIcon.png -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 | 86 |
87 | 88 |
89 | 90 |
91 |
92 | 93 | 94 | 98 | 99 | Login with Fitbit 100 | 101 | 102 |
103 |
104 | 105 |
106 | 107 | 108 | --------------------------------------------------------------------------------