├── .bowerrc ├── scss ├── _styles.scss ├── _overrides.scss └── ionic.app.scss ├── resources ├── icon.png └── splash.png ├── www ├── app │ ├── constants.js │ ├── twitts │ │ ├── twitt.html │ │ ├── twitt.ctrl.js │ │ ├── twitts.ctrl.js │ │ └── twitts.html │ ├── layout │ │ ├── layout.ctrl.js │ │ ├── loading.ctrl.js │ │ └── layout.html │ ├── common │ │ ├── backend.js │ │ ├── plugins │ │ │ ├── utils.js │ │ │ ├── device.js │ │ │ ├── app-version.js │ │ │ ├── inappbrowser.js │ │ │ ├── keyboard.js │ │ │ ├── device-accounts.js │ │ │ ├── file-transfer.js │ │ │ ├── sqlite.js │ │ │ ├── email-composer.js │ │ │ ├── social-sharing.js │ │ │ ├── toast.js │ │ │ ├── geolocation.js │ │ │ ├── barcode.js │ │ │ ├── background-geolocation.js │ │ │ ├── camera.js │ │ │ ├── media.js │ │ │ ├── local-notification.js │ │ │ ├── dialogs.js │ │ │ ├── push.js │ │ │ └── file.js │ │ ├── ui │ │ │ ├── utils.js │ │ │ └── generic-filters.js │ │ ├── utils.js │ │ └── storage.js │ ├── authentication │ │ ├── login.ctrl.js │ │ └── login.html │ ├── settings │ │ ├── settings.ctrl.js │ │ └── settings.html │ ├── _Logger.js │ └── app.js ├── data │ └── twitts.json └── index.html ├── bower.json ├── ionic.project ├── .editorconfig ├── .gitignore ├── package.json ├── gulpfile.js ├── hooks ├── after_prepare │ └── 010_add_platform_class.js └── README.md ├── README.md └── config.xml /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /scss/_styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Your app styles 3 | */ 4 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loicknuchel/ionic-starter/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loicknuchel/ionic-starter/HEAD/resources/splash.png -------------------------------------------------------------------------------- /scss/_overrides.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Styles overrides for Ionic & very generic styles 3 | */ 4 | .pull-right { 5 | float: right !important; 6 | } 7 | -------------------------------------------------------------------------------- /www/app/constants.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .constant('C', { 5 | backendUrl: 'data' 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-starter", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.2.0", 6 | "lodash": "~3.10.1", 7 | "moment": "~2.10.6" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ionic.project: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-starter", 3 | "app_id": "", 4 | "gulpStartupTasks": [ 5 | "sass", 6 | "watch" 7 | ], 8 | "watchPatterns": [ 9 | "www/**/*", 10 | "!www/lib/**/*" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /www/app/twitts/twitt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 |

{{data.twitt.user}}

7 |

{{data.twitt.content}}

8 |
9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /www/app/twitts/twitt.ctrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .controller('TwittCtrl', TwittCtrl); 5 | 6 | function TwittCtrl($scope, $stateParams, Storage){ 7 | var data = {}, fn = {}; 8 | $scope.data = data; 9 | $scope.fn = fn; 10 | 11 | Storage.getTwitt($stateParams.id).then(function(twitt){ 12 | data.twitt = twitt; 13 | }); 14 | } 15 | })(); 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | # Editor files 5 | /.idea/ 6 | /.brackets.json 7 | 8 | # Cordova folders 9 | node_modules/ 10 | platforms/ 11 | plugins/ 12 | 13 | # when use sass, do not commit css files 14 | /www/css/ 15 | /www/lib/ 16 | 17 | # do not commit generated images 18 | /resources/android/ 19 | /resources/ios/ 20 | 21 | *~ 22 | -------------------------------------------------------------------------------- /www/app/layout/layout.ctrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .controller('LayoutCtrl', LayoutCtrl); 5 | 6 | function LayoutCtrl($state, $scope, $ionicHistory, Storage){ 7 | var fn = {}; 8 | $scope.fn = fn; 9 | 10 | fn.logout = function(){ 11 | Storage.clear().then(function(){ 12 | $ionicHistory.clearHistory(); 13 | $ionicHistory.clearCache(); 14 | $state.go('login'); 15 | }); 16 | }; 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /www/app/common/backend.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('Backend', Backend); 5 | 6 | function Backend($http, Storage, C){ 7 | return { 8 | getTwitts: getTwitts 9 | }; 10 | 11 | function getTwitts(){ 12 | return $http.get(C.backendUrl+'/twitts.json').then(function(res){ 13 | return Storage.setTwitts(res.data).then(function(){ 14 | return res.data; 15 | }); 16 | }); 17 | } 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /www/app/layout/loading.ctrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .controller('LoadingCtrl', LoadingCtrl); 5 | 6 | function LoadingCtrl($state, $ionicHistory, Storage){ 7 | $ionicHistory.nextViewOptions({ 8 | disableAnimate: true, 9 | disableBack: true 10 | }); 11 | Storage.getUser().then(function(user){ 12 | if(user){ 13 | $state.go('app.twitts'); 14 | } else { 15 | $state.go('login'); 16 | } 17 | }); 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /www/app/common/plugins/utils.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('PluginUtils', PluginUtils); 5 | 6 | function PluginUtils($ionicPlatform, $q, $log){ 7 | return { 8 | onReady: onReady 9 | }; 10 | 11 | function onReady(name, testFn){ 12 | return $ionicPlatform.ready().then(function(){ 13 | if(!testFn()){ 14 | $log.error('pluginNotFound:'+name); 15 | return $q.reject({message: 'pluginNotFound:'+name}); 16 | } 17 | }); 18 | } 19 | } 20 | })(); 21 | -------------------------------------------------------------------------------- /www/app/twitts/twitts.ctrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .controller('TwittsCtrl', TwittsCtrl); 5 | 6 | function TwittsCtrl($scope, Storage, Backend){ 7 | var data = {}, fn = {}; 8 | $scope.data = data; 9 | $scope.fn = fn; 10 | 11 | $scope.$on('$ionicView.enter', function(){ 12 | Storage.getTwitts().then(function(twitts){ 13 | data.twitts = twitts; 14 | Backend.getTwitts().then(function(twitts){ 15 | data.twitts = twitts; 16 | }); 17 | }); 18 | }); 19 | } 20 | })(); 21 | -------------------------------------------------------------------------------- /www/app/authentication/login.ctrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .controller('LoginCtrl', LoginCtrl); 5 | 6 | function LoginCtrl($scope, $state, Storage){ 7 | var fn = {}, data = {}; 8 | $scope.fn = fn; 9 | $scope.data = data; 10 | 11 | data.credentials = { 12 | login: '', 13 | password: '' 14 | }; 15 | 16 | fn.login = function(credentials){ 17 | if(credentials.login){ 18 | Storage.setUser({login: credentials.login}).then(function(){ 19 | $state.go('app.twitts'); 20 | }); 21 | } 22 | }; 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /www/app/settings/settings.ctrl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .controller('SettingsCtrl', SettingsCtrl); 5 | 6 | function SettingsCtrl($scope, Storage, UiUtils, resolvedSettings){ 7 | var fn = {}, data = {}; 8 | $scope.fn = fn; 9 | $scope.data = data; 10 | 11 | data.settings = resolvedSettings; 12 | 13 | $scope.$watch('data.settings', function(settings, oldSettings){ 14 | if(settings && oldSettings && !angular.equals(settings, oldSettings)){ 15 | Storage.setUserSettings(settings).then(function(){ 16 | UiUtils.showToast('Paramètres enregistrés'); 17 | }); 18 | } 19 | }, true); 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /www/app/settings/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
Paramètre 1 5 | 11 |
12 |
Paramètre 2 13 | 19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-starter", 3 | "version": "0.0.1", 4 | "description": "Start a new ionic project in seconds !", 5 | "dependencies": { 6 | "gulp": "^3.9.0", 7 | "gulp-sass": "^2.0.4", 8 | "gulp-concat": "^2.6.0", 9 | "gulp-minify-css": "^1.2.0", 10 | "gulp-rename": "^1.2.2" 11 | }, 12 | "devDependencies": { 13 | "bower": "^1.4.1", 14 | "gulp-util": "^3.0.6", 15 | "shelljs": "^0.5.1" 16 | }, 17 | "cordovaPlugins": [ 18 | "cordova-plugin-device", 19 | "cordova-plugin-console", 20 | "cordova-plugin-whitelist", 21 | "cordova-plugin-splashscreen", 22 | "cordova-plugin-statusbar", 23 | "ionic-plugin-keyboard", 24 | "cordova-plugin-x-toast", 25 | "cordova-sqlite-storage", 26 | "cordova-plugin-inappbrowser" 27 | ], 28 | "cordovaPlatforms": [] 29 | } 30 | -------------------------------------------------------------------------------- /www/app/authentication/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 9 | 13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /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: #387ef5 !default; 10 | $calm: #11c1f3 !default; 11 | $balanced: #33cd5f !default; 12 | $energized: #ffc900 !default; 13 | $assertive: #ef473a !default; 14 | $royal: #886aea !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 | @import '_overrides'; 24 | @import "_styles"; 25 | -------------------------------------------------------------------------------- /www/app/twitts/twitts.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 | 8 |
9 | 10 |
Aucun twitt :(
11 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /www/app/layout/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Menu

16 |
17 | 18 | 23 | 24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /www/app/common/plugins/device.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('DevicePlugin', DevicePlugin); 5 | 6 | // for Device plugin : org.apache.cordova.device (https://github.com/apache/cordova-plugin-device) 7 | function DevicePlugin($window, PluginUtils){ 8 | var pluginName = 'Device'; 9 | var pluginTest = function(){ return $window.device; }; 10 | return { 11 | getDevice: getDevice 12 | }; 13 | 14 | function getDevice(){ 15 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 16 | return $window.device; 17 | }); 18 | } 19 | } 20 | 21 | 22 | /************************** 23 | * * 24 | * Browser Mock * 25 | * * 26 | **************************/ 27 | ionic.Platform.ready(function(){ 28 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 29 | if(!window.device){ 30 | var browser = {available: true, cordova: "", manufacturer: "", model: "", platform: "browser", uuid: "0123456789", version: "0" }; 31 | var android = {available: true, cordova: "3.6.4", manufacturer: "LGE", model: "Nexus 4", platform: "Android", uuid: "891b8e516ae6bd65", version: "5.0.1"}; 32 | window.device = android; 33 | } 34 | } 35 | }); 36 | })(); 37 | -------------------------------------------------------------------------------- /www/data/twitts.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": "1", "user": "Venkman", "avatar": "http://ionicframework.com/img/docs/venkman.jpg", "content": "Back off, man. I'm a scientist." }, 3 | {"id": "2", "user": "Egon", "avatar": "http://ionicframework.com/img/docs/spengler.jpg", "content": "We're gonna go full stream." }, 4 | {"id": "3", "user": "Ray", "avatar": "http://ionicframework.com/img/docs/stantz.jpg", "content": "Ugly little spud, isn't he?" }, 5 | {"id": "4", "user": "Winston", "avatar": "http://ionicframework.com/img/docs/winston.jpg", "content": "That's a big Twinkie." }, 6 | {"id": "5", "user": "Tully", "avatar": "http://ionicframework.com/img/docs/tully.jpg", "content": "Okay, who brought the dog?" }, 7 | {"id": "6", "user": "Dana", "avatar": "http://ionicframework.com/img/docs/barrett.jpg", "content": "I am The Gatekeeper!" }, 8 | {"id": "7", "user": "Slimer", "avatar": "http://ionicframework.com/img/docs/slimer.jpg", "content": "Boo!" }, 9 | {"id": "8", "user": "Loïc", "avatar": "https://pbs.twimg.com/profile_images/3133057797/81ea4e63c7078eec0a7c7d6ae57a3ce1.jpeg", "content": "Really nice, isn't it ?" } 10 | ] -------------------------------------------------------------------------------- /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 | .on('error', sass.logError) 20 | .pipe(gulp.dest('./www/css/')) 21 | .pipe(minifyCss({ 22 | keepSpecialComments: 0 23 | })) 24 | .pipe(rename({ extname: '.min.css' })) 25 | .pipe(gulp.dest('./www/css/')) 26 | .on('end', done); 27 | }); 28 | 29 | gulp.task('watch', function() { 30 | gulp.watch(paths.sass, ['sass']); 31 | }); 32 | 33 | gulp.task('install', ['git-check'], function() { 34 | return bower.commands.install() 35 | .on('log', function(data) { 36 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 37 | }); 38 | }); 39 | 40 | gulp.task('git-check', function(done) { 41 | if (!sh.which('git')) { 42 | console.log( 43 | ' ' + gutil.colors.red('Git is not installed.'), 44 | '\n Git, the version control system, is required to download Ionic.', 45 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 46 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 47 | ); 48 | process.exit(1); 49 | } 50 | done(); 51 | }); 52 | -------------------------------------------------------------------------------- /www/app/common/ui/utils.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('UiUtils', UiUtils); 5 | 6 | function UiUtils($q, $ionicPopup, $ionicActionSheet, ToastPlugin){ 7 | // TODO : queue $ionicPopup to not display more than one at a time 8 | return { 9 | confirm: confirm, 10 | showInfo: showError, 11 | showError: showError, 12 | showToast: showToast, 13 | showOptions: showOptions 14 | }; 15 | 16 | function confirm(message, title, buttons){ 17 | var opts = {}; 18 | if(title) { opts.title = title; } 19 | if(message){ opts.template = message; } 20 | opts.cancelText = buttons && buttons.length > 0 ? buttons[0] : 'Non'; 21 | opts.okText = buttons && buttons.length > 1 ? buttons[1] : 'Oui'; 22 | return $ionicPopup.confirm(opts).then(function(res){ 23 | if(res){ return res; } 24 | else { return $q.reject(); } 25 | }); 26 | } 27 | 28 | function showError(message, title, buttons){ 29 | var opts = {}; 30 | if(title) { opts.title = title; } 31 | if(message){ opts.template = message; } 32 | opts.okText = buttons && buttons.length > 0 ? buttons[0] : 'Ok'; 33 | return $ionicPopup.alert(opts); 34 | } 35 | 36 | function showToast(message){ 37 | ToastPlugin.show(message); 38 | } 39 | 40 | function showOptions(options, title){ 41 | var defer = $q.defer(); 42 | $ionicActionSheet.show({ 43 | titleText: title, 44 | buttons: options, 45 | buttonClicked: function(index){ 46 | defer.resolve(options[index]); 47 | return true; 48 | }, 49 | cancel: function(){ 50 | defer.reject(); 51 | } 52 | }); 53 | return defer.promise; 54 | } 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ionic Starter 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | -------------------------------------------------------------------------------- /www/app/common/plugins/app-version.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('AppVersionPlugin', AppVersionPlugin); 5 | 6 | // for AppVersion plugin : cordova-plugin-appversion (https://github.com/Rareloop/cordova-plugin-app-version) 7 | /* 8 | http://developer.android.com/intl/vi/reference/android/content/pm/PackageInfo.html 9 | https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/#//apple_ref/occ/instp/NSBundle/infoDictionary 10 | https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 11 | */ 12 | function AppVersionPlugin($window, PluginUtils){ 13 | var pluginName = 'AppVersion'; 14 | var pluginTest = function(){ return $window.AppVersion; }; 15 | return { 16 | get: get, 17 | getVersion: getVersion, 18 | getBuild: getBuild 19 | }; 20 | 21 | function get(){ 22 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 23 | return $window.AppVersion; 24 | }); 25 | } 26 | 27 | function getVersion(){ 28 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 29 | return $window.AppVersion.version; 30 | }); 31 | } 32 | 33 | function getBuild(){ 34 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 35 | return $window.AppVersion.build; 36 | }); 37 | } 38 | } 39 | 40 | 41 | /************************** 42 | * * 43 | * Browser Mock * 44 | * * 45 | **************************/ 46 | ionic.Platform.ready(function(){ 47 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 48 | if(!window.AppVersion){ 49 | window.AppVersion = { 50 | version: '1.2.3', 51 | build: '1234' 52 | }; 53 | } 54 | } 55 | }); 56 | })(); 57 | -------------------------------------------------------------------------------- /www/app/common/ui/generic-filters.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .filter('date', formatDate) 5 | .filter('time', formatTime) 6 | .filter('datetime', formatDatetime) 7 | .filter('reverse', reverseArray) 8 | .filter('with', withArray) 9 | .filter('inSlicesOf', filterInSlicesOf); 10 | 11 | function formatDate(){ 12 | return function(date, format){ 13 | var mDate = moment(date); 14 | if(date && mDate.isValid()){ 15 | return mDate.format(format ? format : 'D MMMM YYYY'); 16 | } else { 17 | return date; 18 | } 19 | } 20 | } 21 | 22 | function formatTime(){ 23 | return function(date, format){ 24 | var mDate = moment(date); 25 | if(date && mDate.isValid()){ 26 | return mDate.format(format ? format : 'HH:mm'); 27 | } else { 28 | return date; 29 | } 30 | } 31 | } 32 | 33 | function formatDatetime(){ 34 | return function(date, format){ 35 | var mDate = moment(date); 36 | if(date && mDate.isValid()){ 37 | return mDate.format(format ? format : 'DD/MM/YYYY HH:mm'); 38 | } else { 39 | return date; 40 | } 41 | } 42 | } 43 | 44 | function reverseArray(){ 45 | return function(items){ 46 | return items.slice().reverse(); 47 | }; 48 | } 49 | 50 | function withArray(){ 51 | return function(items, items2){ 52 | return items.concat(items2); 53 | }; 54 | } 55 | 56 | function filterInSlicesOf($rootScope){ 57 | return function(items, count){ 58 | if(!angular.isArray(items) && !angular.isString(items)) return items; 59 | if(!count){ count = 3; } 60 | var array = []; 61 | for(var i = 0; i < items.length; i++){ 62 | var chunkIndex = parseInt(i / count, 10); 63 | var isFirst = (i % count === 0); 64 | if(isFirst){ array[chunkIndex] = []; } 65 | array[chunkIndex].push(items[i]); 66 | } 67 | 68 | if(angular.equals($rootScope.arrayinSliceOf, array)){ 69 | return $rootScope.arrayinSliceOf; 70 | } else { 71 | $rootScope.arrayinSliceOf = array; 72 | } 73 | 74 | return array; 75 | }; 76 | } 77 | })(); 78 | -------------------------------------------------------------------------------- /www/app/common/plugins/inappbrowser.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('InAppBrowserPlugin', InAppBrowserPlugin) 5 | .directive('href', href); 6 | 7 | // for InAppBrowser plugin : cordova-plugin-inappbrowser (https://github.com/apache/cordova-plugin-inappbrowser) 8 | function InAppBrowserPlugin($window, PluginUtils){ 9 | var pluginName = 'InAppBrowser'; 10 | var pluginTest = function(){ return $window.cordova && $window.cordova.InAppBrowser; }; 11 | return { 12 | open: open 13 | }; 14 | 15 | function open(url, target, options){ 16 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 17 | $window.cordova.InAppBrowser.open(url, target, options); 18 | }); 19 | } 20 | } 21 | 22 | // open external links (starting with http:, https:, tel: or sms:) outside the app 23 | function href(InAppBrowserPlugin){ 24 | var externePrefixes = ['http:', 'https:', 'tel:', 'sms:']; 25 | return { 26 | restrict: 'A', 27 | scope: { 28 | url: '@href' 29 | }, 30 | link: function(scope, element, attrs){ 31 | if(isExterneUrl(scope.url)){ 32 | element.bind('click', function(e){ 33 | e.preventDefault(); 34 | InAppBrowserPlugin.open(encodeURI(scope.url), '_system', 'location=yes'); 35 | }); 36 | } 37 | } 38 | }; 39 | function isExterneUrl(url){ 40 | if(url){ 41 | for(var i in externePrefixes){ 42 | if(url.indexOf(externePrefixes[i]) === 0){ 43 | return true; 44 | } 45 | } 46 | } 47 | return false; 48 | } 49 | } 50 | 51 | 52 | /************************** 53 | * * 54 | * Browser Mock * 55 | * * 56 | **************************/ 57 | ionic.Platform.ready(function(){ 58 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 59 | if(!window.cordova){window.cordova = {};} 60 | if(!window.cordova.InAppBrowser){ 61 | window.cordova.InAppBrowser = { 62 | open: function(url, target, options){ 63 | window.open(url, target, options); 64 | } 65 | }; 66 | } 67 | } 68 | }); 69 | })(); 70 | -------------------------------------------------------------------------------- /www/app/common/plugins/keyboard.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('KeyboardPlugin', KeyboardPlugin); 5 | 6 | // for Keyboard plugin : https://github.com/driftyco/ionic-plugin-keyboard 7 | function KeyboardPlugin($window, PluginUtils){ 8 | var pluginName = 'Keyboard'; 9 | var pluginTest = function(){ return $window.cordova && $window.cordova.plugins && $window.cordova.plugins.Keyboard; }; 10 | var service = { 11 | hideKeyboardAccessoryBar : hideKeyboardAccessoryBar, 12 | disableScroll : disableScroll 13 | }; 14 | 15 | return service; 16 | 17 | function hideKeyboardAccessoryBar(){ 18 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 19 | $window.cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 20 | }); 21 | } 22 | 23 | function disableScroll(shouldDisable){ 24 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 25 | window.cordova.plugins.Keyboard.disableScroll(shouldDisable); 26 | }); 27 | } 28 | } 29 | 30 | 31 | /************************** 32 | * * 33 | * Browser Mock * 34 | * * 35 | **************************/ 36 | ionic.Platform.ready(function(){ 37 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 38 | if(!window.cordova){window.cordova = {};} 39 | if(!window.cordova.plugins){window.cordova.plugins = {};} 40 | if(!window.cordova.plugins.Keyboard){ 41 | window.cordova.plugins.Keyboard = (function(){ 42 | var plugin = { 43 | isVisible: false, 44 | show: function(){ 45 | plugin.isVisible = true; 46 | var event = new Event('native.keyboardshow'); 47 | event.keyboardHeight = 284; 48 | window.dispatchEvent(event); 49 | }, 50 | close: function(){ 51 | plugin.isVisible = false; 52 | window.dispatchEvent(new Event('native.keyboardhide')); 53 | }, 54 | hideKeyboardAccessoryBar: function(shouldHide){}, 55 | disableScroll: function(shouldDisable){} 56 | } 57 | return plugin; 58 | })(); 59 | } 60 | } 61 | }); 62 | })(); 63 | -------------------------------------------------------------------------------- /www/app/common/plugins/device-accounts.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('DeviceAccountsPlugin', DeviceAccountsPlugin); 5 | 6 | // for DeviceAccounts plugin : https://github.com/loicknuchel/cordova-device-accounts 7 | function DeviceAccountsPlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'DeviceAccounts'; 9 | var pluginTest = function(){ return $window.plugins && $window.plugins.DeviceAccounts; }; 10 | var service = { 11 | getAccounts: getAccounts, 12 | getEmail: getEmail 13 | }; 14 | 15 | function getAccounts(){ 16 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 17 | var defer = $q.defer(); 18 | $window.plugins.DeviceAccounts.get(function(accounts){ 19 | defer.resolve(accounts); 20 | }, function(error){ 21 | $log.error('pluginError:'+pluginName, error); 22 | defer.reject(error); 23 | }); 24 | return defer.promise; 25 | }); 26 | } 27 | 28 | function getEmail(){ 29 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 30 | var defer = $q.defer(); 31 | $window.plugins.DeviceAccounts.getEmail(function(email){ 32 | defer.resolve(email); 33 | }, function(error){ 34 | $log.error('pluginError:'+pluginName, error); 35 | defer.reject(error); 36 | }); 37 | return defer.promise; 38 | }); 39 | } 40 | 41 | return service; 42 | } 43 | 44 | 45 | /************************** 46 | * * 47 | * Browser Mock * 48 | * * 49 | **************************/ 50 | ionic.Platform.ready(function(){ 51 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 52 | if(!window.plugins){window.plugins = {};} 53 | if(!window.plugins.DeviceAccounts){ 54 | window.plugins.DeviceAccounts = { 55 | get: function(onSuccess, onFail){ onSuccess([{type:'com.google', name:'test@example.com'}]); }, 56 | getByType: function(type, onSuccess, onFail){ onSuccess([{type:'com.google', name:'test@example.com'}]); }, 57 | getEmails: function(onSuccess, onFail){ onSuccess(['test@example.com']); }, 58 | getEmail: function(onSuccess, onFail){ onSuccess('test@example.com'); } 59 | }; 60 | } 61 | } 62 | }); 63 | })(); 64 | -------------------------------------------------------------------------------- /www/app/common/utils.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('Utils', Utils); 5 | 6 | function Utils($q, $timeout){ 7 | return { 8 | createUuid: createUuid, 9 | isEmail: isEmail, 10 | isUrl: isUrl, 11 | padLeft: padLeft, 12 | encodeUTF8: encodeUTF8, 13 | decodeUTF8: decodeUTF8, 14 | parseKeyValue: parseKeyValue, 15 | realAsync: realAsync 16 | }; 17 | 18 | function createUuid(){ 19 | function S4(){ return (((1+Math.random())*0x10000)|0).toString(16).substring(1); } 20 | return (S4() + S4() + '-' + S4() + '-4' + S4().substr(0,3) + '-' + S4() + '-' + S4() + S4() + S4()).toLowerCase(); 21 | } 22 | 23 | function isEmail(str){ 24 | var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 25 | return re.test(str); 26 | } 27 | 28 | function isUrl(str){ 29 | return (/^(https?):\/\/((?:[a-z0-9.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*))?$/i).test(str); 30 | } 31 | 32 | function padLeft(num, length, char){ 33 | var res = num.toString(); 34 | while(res.length < length){ 35 | res = char+res; 36 | } 37 | return res; 38 | } 39 | 40 | function encodeUTF8(string){ 41 | try { 42 | return unescape(encodeURIComponent(string)); 43 | } catch(e){ 44 | return string; 45 | } 46 | } 47 | 48 | function decodeUTF8(string){ 49 | try { 50 | return decodeURIComponent(escape(string)); 51 | } catch(e){ 52 | return string; 53 | } 54 | } 55 | 56 | function parseKeyValue(str){ 57 | var lines = []; 58 | var parts = str.split(/\r\n|\r|\n/g); 59 | var line = ''; 60 | for(var i in parts){ 61 | if(!parts[i].startsWith(' ') && parts[i].indexOf('=') > -1 && line.length > 0){ 62 | lines.push(line); 63 | line = ''; 64 | } 65 | line += parts[i].trim(); 66 | } 67 | lines.push(line); 68 | var result = {}; 69 | for(var i in lines){ 70 | var index = lines[i].indexOf('='); 71 | if(index > -1){ 72 | result[lines[i].substr(0, index)] = lines[i].substr(index+1); 73 | } 74 | } 75 | return result; 76 | } 77 | 78 | function realAsync(fn){ 79 | var defer = $q.defer(); 80 | $timeout(function(){ 81 | defer.resolve(fn()); 82 | }, 0); 83 | return defer.promise; 84 | } 85 | } 86 | })(); 87 | -------------------------------------------------------------------------------- /www/app/common/plugins/file-transfer.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('FileTransferPlugin', FileTransferPlugin); 5 | 6 | // for FileTransferPlugin plugin : cordova-plugin-file-transfer (https://github.com/apache/cordova-plugin-file-transfer) 7 | function FileTransferPlugin($window, $q, PluginUtils){ 8 | var pluginName = 'FileTransfer'; 9 | var pluginTest = function(){ return $window.FileTransfer; }; 10 | return { 11 | options: options, 12 | upload: upload, 13 | download: download 14 | }; 15 | 16 | function options(opts){ 17 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 18 | var options = new FileUploadOptions(); 19 | opts = opts || {}; 20 | for(var i in opts){ 21 | options[i] = opts[i]; 22 | } 23 | return options; 24 | }); 25 | } 26 | 27 | function upload(filePath, serverURL, options, onProgress){ 28 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 29 | var defer = $q.defer(); 30 | var ft = new FileTransfer(); 31 | if(onProgress){ ft.onprogress = onProgress; } 32 | ft.upload(filePath, serverURL, function(data){ 33 | defer.resolve(data); 34 | }, function(error){ 35 | defer.reject(error); 36 | }, options); 37 | return defer.promise; 38 | }); 39 | } 40 | 41 | function download(sourceUri, destinationPath, trustAllHosts, options){ 42 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 43 | var defer = $q.defer(); 44 | var ft = new FileTransfer(); 45 | ft.download(sourceUri, destinationPath, function(entry){ 46 | defer.resolve(entry); 47 | }, function(error){ 48 | defer.reject(error); 49 | }, trustAllHosts, options); 50 | return defer.promise; 51 | }); 52 | } 53 | } 54 | 55 | /************************** 56 | * * 57 | * Browser Mock * 58 | * * 59 | **************************/ 60 | ionic.Platform.ready(function(){ 61 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 62 | if(!window.FileTransfer){ 63 | window.FileTransfer = function(){}; 64 | FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts){ 65 | if(successCallback){ successCallback({}); } 66 | }; 67 | FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options){ 68 | if(successCallback){ successCallback({}); } 69 | }; 70 | window.FileUploadOptions = function(){}; 71 | } 72 | } 73 | }); 74 | })(); 75 | -------------------------------------------------------------------------------- /www/app/common/plugins/sqlite.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('SQLitePlugin', SQLitePlugin); 5 | 6 | // for SQLite plugin : cordova-sqlite-storage (https://github.com/litehelpers/Cordova-sqlite-storage) 7 | function SQLitePlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'SQLite'; 9 | var pluginTest = function(){ return $window.sqlitePlugin || $window.openDatabase; }; 10 | // https://github.com/litehelpers/Cordova-sqlite-storage#opening-a-database 11 | var Location = { 12 | Documents: 0, // (default) visible to iTunes and backed up by iCloud 13 | Library: 1, // backed up by iCloud, NOT visible to iTunes 14 | LocalDatabase: 2 // NOT visible to iTunes and NOT backed up by iCloud 15 | } 16 | return { 17 | open: open, 18 | query: query 19 | }; 20 | 21 | function open(options){ 22 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 23 | var opts = angular.extend({ 24 | name: 'my.db', 25 | location: Location.Documents 26 | }, options); 27 | var defer = $q.defer(); 28 | if(window.sqlitePlugin){ 29 | $window.sqlitePlugin.openDatabase(opts, function(db){ 30 | defer.resolve(db); 31 | }, function(error){ 32 | $log.error('pluginError:'+pluginName, error); 33 | defer.reject(error); 34 | }); 35 | } else { 36 | $log.warn('Storage: SQLite plugin not installed, falling back to WebSQL. Make sure to install cordova-sqlite-storage in production!'); 37 | var db = window.openDatabase(opts.name, '1.0', 'database', 5 * 1024 * 1024); 38 | defer.resolve(db); 39 | } 40 | return defer.promise; 41 | }); 42 | } 43 | 44 | function query(db, query, args){ 45 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 46 | var defer = $q.defer(); 47 | db.transaction(function(tx){ 48 | tx.executeSql(query, args || [], function(tx, res){ 49 | var data = []; 50 | for(var i=0; i -1) return; // already added 28 | 29 | var newBodyTag = bodyTag; 30 | 31 | var classAttr = findClassAttr(bodyTag); 32 | if(classAttr) { 33 | // body tag has existing class attribute, add the classname 34 | var endingQuote = classAttr.substring(classAttr.length-1); 35 | var newClassAttr = classAttr.substring(0, classAttr.length-1); 36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; 37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr); 38 | 39 | } else { 40 | // add class attribute to the body tag 41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); 42 | } 43 | 44 | html = html.replace(bodyTag, newBodyTag); 45 | 46 | fs.writeFileSync(indexPath, html, 'utf8'); 47 | 48 | process.stdout.write('add to body class: ' + platformClass + '\n'); 49 | } catch(e) { 50 | process.stdout.write(e); 51 | } 52 | } 53 | 54 | function findBodyTag(html) { 55 | // get the body tag 56 | try{ 57 | return html.match(/])(.*?)>/gi)[0]; 58 | }catch(e){} 59 | } 60 | 61 | function findClassAttr(bodyTag) { 62 | // get the body tag's class attribute 63 | try{ 64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; 65 | }catch(e){} 66 | } 67 | 68 | if (rootdir) { 69 | 70 | // go through each of the platform directories that have been prepared 71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); 72 | 73 | for(var x=0; x, availables: '+JSON.stringify(obj)); 56 | return false; 57 | } 58 | 59 | function instrumentFn(prefix, fn, level){ 60 | level = level || Config.Level.INFO; 61 | var instrumented = function Log$$instrumented(){ 62 | var jsonArgs = []; 63 | for(var j in arguments){ 64 | jsonArgs.push(JSON.stringify(arguments[j])); 65 | } 66 | log(level, prefix+'('+jsonArgs.join(', ')+')'); 67 | var ret = fn.apply(this, arguments); 68 | /*if(ret && typeof ret.then === 'function'){ 69 | ret.then(function(data){ 70 | log(level, prefix+' END'); 71 | return data; 72 | }); 73 | } else { 74 | log(level, prefix+' END'); 75 | }*/ 76 | return ret; 77 | } 78 | instrumented.__riginalFn = fn; 79 | return instrumented; 80 | } 81 | 82 | function instrumentObj(prefix, obj, level){ 83 | for(var i in obj){ 84 | if(typeof obj[i] === 'function'){ 85 | obj[i] = instrumentFn(prefix+'.'+i, obj[i], level); 86 | } 87 | } 88 | } 89 | })(); 90 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /www/app/common/plugins/social-sharing.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('SocialSharingPlugin', SharingPlugin); 5 | 6 | // for Sharing plugin : https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin 7 | function SharingPlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'SocialSharing'; 9 | var pluginTest = function(){ return $window.plugins && $window.plugins.socialsharing; }; 10 | var service = { 11 | share: share, 12 | shareViaFacebook: shareViaFacebook, 13 | shareViaTwitter: shareViaTwitter, 14 | shareViaEmail: shareViaEmail 15 | }; 16 | 17 | // _fileOrFileArray can be null, a string or an array of strings 18 | function share(message, _subject, _fileOrFileArray, _link){ 19 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 20 | var defer = $q.defer(); 21 | $window.plugins.socialsharing.share(message, _subject || null, _fileOrFileArray || null, _link || null, function(){ 22 | defer.resolve(); 23 | }, function(error){ 24 | $log.error('pluginError:'+pluginName, error); 25 | defer.reject(error); 26 | }); 27 | return defer.promise; 28 | }); 29 | } 30 | 31 | function shareViaFacebook(message, _fileOrFileArray, _link){ 32 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 33 | var defer = $q.defer(); 34 | $window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint(message, _fileOrFileArray || null, _link || null, 'Tu peux coller le message par défaut si tu veux...', function(){ 35 | defer.resolve(); 36 | }, function(error){ 37 | $log.error('pluginError:'+pluginName, error); 38 | defer.reject(error); 39 | }); 40 | return defer.promise; 41 | }); 42 | } 43 | 44 | function shareViaTwitter(message, _file, _link){ 45 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 46 | var defer = $q.defer(); 47 | $window.plugins.socialsharing.shareViaTwitter(message, _file || null, _link || null, function(){ 48 | defer.resolve(); 49 | }, function(error){ 50 | $log.error('pluginError:'+pluginName, error); 51 | defer.reject(error); 52 | }); 53 | return defer.promise; 54 | }); 55 | } 56 | 57 | function shareViaEmail(message, _subject, _fileOrFileArray){ 58 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 59 | var defer = $q.defer(); 60 | $window.plugins.socialsharing.shareViaEmail(message, _subject || null, null /*to*/, null /*cc*/, null /*bcc*/, _fileOrFileArray || null, function(){ 61 | defer.resolve(); 62 | }, function(error){ 63 | $log.error('pluginError:'+pluginName, error); 64 | defer.reject(error); 65 | }); 66 | return defer.promise; 67 | }); 68 | } 69 | 70 | return service; 71 | } 72 | 73 | 74 | /************************** 75 | * * 76 | * Browser Mock * 77 | * * 78 | **************************/ 79 | ionic.Platform.ready(function(){ 80 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 81 | 82 | } 83 | }); 84 | })(); 85 | -------------------------------------------------------------------------------- /www/app/common/plugins/toast.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('ToastPlugin', ToastPlugin); 5 | 6 | // for Toast plugin : cordova-plugin-x-toast (https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin) 7 | function ToastPlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'Toast'; 9 | var pluginTest = function(){ return $window.plugins && $window.plugins.toast; }; 10 | return { 11 | show: show, 12 | showShortTop : function(message){ return show(message, 'short', 'top'); }, 13 | showShortCenter : function(message){ return show(message, 'short', 'center'); }, 14 | showShortBottom : function(message){ return show(message, 'short', 'bottom'); }, 15 | showLongTop : function(message){ return show(message, 'long', 'top'); }, 16 | showLongCenter : function(message){ return show(message, 'long', 'center'); }, 17 | showLongBottom : function(message){ return show(message, 'long', 'bottom'); } 18 | }; 19 | 20 | function show(message, duration, position){ 21 | if(!duration){ duration = 'short'; } // possible values : 'short', 'long' 22 | if(!position){ position = 'bottom'; } // possible values : 'top', 'center', 'bottom' 23 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 24 | var defer = $q.defer(); 25 | $window.plugins.toast.show(message, duration, position, function(){ 26 | defer.resolve(); 27 | }, function(error){ 28 | $log.error('pluginError:'+pluginName, error); 29 | defer.reject(error); 30 | }); 31 | return defer.promise; 32 | }); 33 | } 34 | } 35 | 36 | 37 | /************************** 38 | * * 39 | * Browser Mock * 40 | * * 41 | **************************/ 42 | ionic.Platform.ready(function(){ 43 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 44 | if(!window.plugins){window.plugins = {};} 45 | if(!window.plugins.toast){ 46 | window.plugins.toast = { 47 | show: function(message, duration, position, successCallback, errorCallback){ 48 | // durations : short, long 49 | // positions : top, center, bottom 50 | // default: short bottom 51 | console.log('Toast: '+message); 52 | if(successCallback){window.setTimeout(successCallback('OK'), 0);} 53 | }, 54 | showShortTop: function(message, successCallback, errorCallback){this.show(message, 'short', 'top', successCallback, errorCallback);}, 55 | showShortCenter: function(message, successCallback, errorCallback){this.show(message, 'short', 'center', successCallback, errorCallback);}, 56 | showShortBottom: function(message, successCallback, errorCallback){this.show(message, 'short', 'bottom', successCallback, errorCallback);}, 57 | showLongTop: function(message, successCallback, errorCallback){this.show(message, 'long', 'top', successCallback, errorCallback);}, 58 | showLongCenter: function(message, successCallback, errorCallback){this.show(message, 'long', 'center', successCallback, errorCallback);}, 59 | showLongBottom: function(message, successCallback, errorCallback){this.show(message, 'long', 'bottom', successCallback, errorCallback);} 60 | }; 61 | } 62 | } 63 | }); 64 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ionic Starter 2 | 3 | [![Dependency Status](https://david-dm.org/loicknuchel/ionic-starter.svg)](https://david-dm.org/loicknuchel/ionic-starter) 4 | [![devDependency Status](https://david-dm.org/loicknuchel/ionic-starter/dev-status.svg)](https://david-dm.org/loicknuchel/ionic-starter#info=devDependencies) 5 | 6 | This project aims to let you start a new app as fast as possible. 7 | 8 | Just clone it and you are ready to do ! Many boilerplate code is already written and most usefull libs are included. I'm trying to follow best practices with Angular & Ionic so it could be a good kick start :) 9 | 10 | Feel free to open an issue for any question or suggestion you could have. 11 | 12 | This application (mostly) follows the [John Papa's style guide](https://github.com/johnpapa/angular-styleguide). 13 | 14 | ## Getting started 15 | 16 | - install nodejs, npm, gulp, bower, cordova, ionic & sass (if not already done) 17 | - `git clone git@github.com:loicknuchel/ionic-starter.git` : clone this repo 18 | - `cd ionic-starter` : go to folder 19 | - `bower install` : install app dependencies 20 | - `npm install` : install build dependencies 21 | - `ionic setup sass` : use sass 22 | - `ionic serve` : start the app on your browser 23 | 24 | For the impatients, you can run all these commands in one time : `git clone git@github.com:loicknuchel/ionic-starter.git && cd ionic-starter && bower install && npm install && ionic setup sass && ionic serve` 25 | 26 | To run the app on your android device : 27 | 28 | - `ionic platform add android` : add android platform to the project 29 | - `ionic resources` : generate icon & splash-screen for project platforms 30 | - `ionic run android` : run your app ! 31 | 32 | Once again, in one command : `ionic platform add android && ionic resources && ionic run android` 33 | 34 | ## Personnalize 35 | 36 | As it's only a template project, you may want to change its name. For that, you just have to open : 37 | 38 | - `config.xml` (widget id, name, description & author) 39 | - `www/index.html` (title) 40 | - `bower.json` (name, homepage, author & description) 41 | - `package.json` (name & description) 42 | - `ionic.project` (name) 43 | 44 | ## Used versions 45 | 46 | - Node v4.2.2 (`node -v`) 47 | - Cordova 5.4.0 (`cordova -version`) 48 | - Bower 1.7.0 (`bower -v`) 49 | - Angular 1.4.3 (see bower.json) 50 | - Ionic 1.2.0 (see bower.json) 51 | 52 | ## Infos 53 | 54 | ### Browser development 55 | 56 | - Chrome cordova : https://chrome.google.com/webstore/detail/cordova-mocks/iigcccneenmnplhhfhaeahiofeeeifpn (https://github.com/pbernasconi/chrome-cordova) 57 | 58 | ### Android debug 59 | 60 | - android remote debug : https://developer.chrome.com/devtools/docs/remote-debugging 61 | - activate developer mode on android 62 | 63 | ### Specific urls 64 | 65 | Use these custom urls to open other apps using inappbrowser (org.apache.cordova.inappbrowser) 66 | 67 | - "tel:0123456789" => call this number 68 | - "sms:0123456789?body=coucou" => send sms to this number 69 | - "geo:lat,lon" => open google map to this geoloc 70 | - "mailto:toto@example.com" => send an email 71 | - "market:???" 72 | 73 | see http://stackoverflow.com/questions/26271313/tel-sms-and-mailto-no-longer-working-in-android-after-upgrading-to-cordo 74 | 75 | ### Other links 76 | 77 | - Push 78 | - https://github.com/hollyschinsky/PushNotificationSample 79 | - Unit test 80 | - https://bradb.net/unit-testing-with-the-ionic-framework/ 81 | - http://forum.ionicframework.com/t/ionic-and-karma-unittest/8799 82 | - Data 83 | - PouchDB (http://devgirl.org/2014/12/30/sync-data-using-pouchdb-in-your-ionic-framework-app/) 84 | -------------------------------------------------------------------------------- /www/app/common/plugins/geolocation.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('GeolocationPlugin', GeolocationPlugin); 5 | 6 | // for Geolocation plugin : org.apache.cordova.geolocation (https://github.com/apache/cordova-plugin-geolocation) 7 | function GeolocationPlugin($window, $q, $timeout, $log, PluginUtils){ 8 | // http://stackoverflow.com/questions/8543763/android-geo-location-tutorial 9 | // http://tol8.blogspot.fr/2014/03/how-to-get-reliable-geolocation-data-on.html 10 | // http://www.andygup.net/how-accurate-is-html5-geolocation-really-part-2-mobile-web/ 11 | /* 12 | * Solutions : 13 | * -> reboot device 14 | * -> don't use cordova plugin ! 15 | * -> use native geolocation (should code plugin...) 16 | */ 17 | var pluginName = 'Geolocation'; 18 | var pluginTest = function(){ return $window.navigator && $window.navigator.geolocation; }; 19 | var service = { 20 | getCurrentPosition: getCurrentPosition 21 | }; 22 | 23 | function getCurrentPosition(_timeout, _enableHighAccuracy, _maximumAge){ 24 | var opts = { 25 | enableHighAccuracy: _enableHighAccuracy ? _enableHighAccuracy : true, 26 | timeout: _timeout ? _timeout : 10000, 27 | maximumAge: _maximumAge ? _maximumAge : 0 28 | }; 29 | 30 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 31 | var defer = $q.defer(); 32 | var geolocTimeout = $timeout(function(){ 33 | defer.reject({message: 'Geolocation didn\'t responded within '+opts.timeout+' millis :('}); 34 | }, opts.timeout); 35 | $window.navigator.geolocation.getCurrentPosition(function(position){ 36 | $timeout.cancel(geolocTimeout); 37 | defer.resolve(position); 38 | }, function(error){ 39 | $timeout.cancel(geolocTimeout); 40 | $log.error('pluginError:'+pluginName, error); 41 | defer.reject(error); 42 | }, opts); 43 | return defer.promise; 44 | }); 45 | } 46 | 47 | function getCurrentPositionByWatch(_timeout, _enableHighAccuracy, _maximumAge){ 48 | var opts = { 49 | enableHighAccuracy: _enableHighAccuracy ? _enableHighAccuracy : true, 50 | timeout: _timeout ? _timeout : 10000, 51 | maximumAge: _maximumAge ? _maximumAge : 1000 52 | }; 53 | 54 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 55 | var defer = $q.defer(); 56 | var watchID = null; 57 | var geolocTimeout = $timeout(function(){ 58 | $window.navigator.geolocation.clearWatch(watchID); 59 | defer.reject({message: 'Geolocation didn\'t responded within '+opts.timeout+' millis :('}); 60 | }, opts.timeout); 61 | watchID = $window.navigator.geolocation.watchPosition(function(position){ 62 | $window.navigator.geolocation.clearWatch(watchID); 63 | $timeout.cancel(geolocTimeout); 64 | defer.resolve(position); 65 | }, function(error){ 66 | $timeout.cancel(geolocTimeout); 67 | $log.error('pluginError:'+pluginName, error); 68 | defer.reject(error); 69 | }, opts); 70 | return defer.promise; 71 | }); 72 | } 73 | 74 | return service; 75 | } 76 | 77 | 78 | /************************** 79 | * * 80 | * Browser Mock * 81 | * * 82 | **************************/ 83 | ionic.Platform.ready(function(){ 84 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 85 | 86 | } 87 | }); 88 | })(); 89 | -------------------------------------------------------------------------------- /www/app/common/plugins/barcode.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('BarcodePlugin', BarcodePlugin); 5 | 6 | // for Barcode plugin : phonegap-plugin-barcodescanner (https://github.com/phonegap/phonegap-plugin-barcodescanner) 7 | function BarcodePlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'Barcode'; 9 | var pluginTest = function(){ return $window.cordova && $window.cordova.plugins && $window.cordova.plugins.barcodeScanner; }; 10 | var lock = false; // to prevent starting scan twice 11 | return { 12 | scan: scan, 13 | encode: encode 14 | }; 15 | 16 | function scan(){ 17 | if(!lock){ 18 | lock = true; 19 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 20 | var defer = $q.defer(); 21 | $window.cordova.plugins.barcodeScanner.scan(function(result){ 22 | lock = false; 23 | defer.resolve(result); 24 | }, function(error){ 25 | $log.error('pluginError:'+pluginName, error); 26 | lock = false; 27 | defer.reject(error); 28 | }); 29 | return defer.promise; 30 | }); 31 | } else { 32 | $log.warn(pluginName+' is locked'); 33 | return $q.reject(); 34 | } 35 | } 36 | 37 | function encode(type, data){ 38 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 39 | var defer = $q.defer(); 40 | $window.cordova.plugins.barcodeScanner.encode(type, data, function(result){ 41 | defer.resolve(result); 42 | }, function(error){ 43 | $log.error('pluginError:'+pluginName, error); 44 | defer.reject(error); 45 | }); 46 | return defer.promise; 47 | }); 48 | } 49 | } 50 | 51 | 52 | /************************** 53 | * * 54 | * Browser Mock * 55 | * * 56 | **************************/ 57 | ionic.Platform.ready(function(){ 58 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 59 | if(!window.cordova){window.cordova = {};} 60 | if(!window.cordova.plugins){window.cordova.plugins = {};} 61 | if(!window.cordova.plugins.barcodeScanner){ 62 | window.cordova.plugins.barcodeScanner = { 63 | Encode: { 64 | EMAIL_TYPE: 'EMAIL_TYPE', 65 | PHONE_TYPE: 'PHONE_TYPE', 66 | SMS_TYPE: 'SMS_TYPE', 67 | TEXT_TYPE: 'TEXT_TYPE' 68 | }, 69 | format: { 70 | all_1D: 61918, 71 | aztec: 1, 72 | codabar: 2, 73 | code_39: 4, 74 | code_93: 8, 75 | code_128: 16, 76 | data_MATRIX: 32, 77 | ean_8: 64, 78 | ean_13: 128, 79 | itf: 256, 80 | maxicode: 512, 81 | msi: 131072, 82 | pdf_417: 1024, 83 | plessey: 262144, 84 | qr_CODE: 2048, 85 | rss_14: 4096, 86 | rss_EXPANDED: 8192, 87 | upc_A: 16384, 88 | upc_E: 32768, 89 | upc_EAN_EXTENSION: 65536 90 | }, 91 | scan: function(success, fail){ 92 | var text = window.prompt('Texte :'); 93 | if(success){ 94 | if(text){ success({text: text, format: 'QR_CODE', cancelled: false}); } 95 | else { success({text: '', format: '', cancelled: true}); } 96 | } 97 | }, 98 | encode: function(type, data, success, fail){ 99 | alert('barcodeScanner.encode() not implemented !'); 100 | success({}); 101 | } 102 | }; 103 | } 104 | } 105 | }); 106 | })(); 107 | -------------------------------------------------------------------------------- /www/app/common/plugins/background-geolocation.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('BackgroundGeolocationPlugin', BackgroundGeolocationPlugin); 5 | 6 | // for BackgroundGeolocation plugin : https://github.com/christocracy/cordova-plugin-background-geolocation 7 | function BackgroundGeolocationPlugin($window, $q, $log, GeolocationPlugin, PluginUtils){ 8 | var pluginName = 'BackgroundGeolocation'; 9 | var pluginTest = function(){ return $window.plugins && $window.plugins.backgroundGeoLocation; }; 10 | var service = { 11 | enable: enable, 12 | disable: stop, 13 | configure: configure, 14 | start: start, 15 | stop: stop 16 | }; 17 | var defaultOpts = { 18 | desiredAccuracy: 10, 19 | stationaryRadius: 20, 20 | distanceFilter: 30, 21 | notificationTitle: 'Location tracking', 22 | notificationText: 'ENABLED', 23 | activityType: 'AutomotiveNavigation', 24 | debug: true, 25 | stopOnTerminate: true 26 | }; 27 | 28 | // postLocation function should take a 'location' parameter and return a promise 29 | function configure(opts, postLocation){ 30 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 31 | var callbackFn = function(location){ 32 | if(postLocation){ 33 | postLocation(location).then(function(){ 34 | $window.plugins.backgroundGeoLocation.finish(); 35 | }, function(error){ 36 | $log.error('pluginError:'+pluginName, error); 37 | $window.plugins.backgroundGeoLocation.finish(); 38 | }); 39 | } else { 40 | $window.plugins.backgroundGeoLocation.finish(); 41 | } 42 | }; 43 | var failureFn = function(error){ 44 | $log.error('pluginError:'+pluginName, error); 45 | }; 46 | var options = angular.extend({}, defaultOpts, opts); 47 | $window.plugins.backgroundGeoLocation.configure(callbackFn, failureFn, options); 48 | return GeolocationPlugin.getCurrentPosition(); 49 | }); 50 | } 51 | 52 | function start(){ 53 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 54 | $window.plugins.backgroundGeoLocation.start(); 55 | }); 56 | } 57 | 58 | function stop(){ 59 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 60 | $window.plugins.backgroundGeoLocation.stop(); 61 | }); 62 | } 63 | 64 | function enable(opts, postLocation){ 65 | return configure(opts, postLocation).then(function(){ 66 | return start(); 67 | }); 68 | } 69 | 70 | return service; 71 | } 72 | 73 | 74 | /************************** 75 | * * 76 | * Browser Mock * 77 | * * 78 | **************************/ 79 | ionic.Platform.ready(function(){ 80 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 81 | if(!window.plugins){window.plugins = {};} 82 | if(!window.plugins.backgroundGeoLocation){ 83 | window.plugins.backgroundGeoLocation = (function(){ 84 | var config = null; 85 | var callback = null; 86 | var interval = null; 87 | return { 88 | configure: function(callbackFn, failureFn, opts){config = opts; callback = callbackFn;}, 89 | start: function(){ 90 | if(interval === null){ 91 | interval = setInterval(function(){ 92 | window.navigator.geolocation.getCurrentPosition(function(position){ 93 | callback(position); 94 | }); 95 | }, 3000); 96 | } 97 | }, 98 | stop: function(){ 99 | if(interval !== null){ 100 | clearInterval(interval); 101 | interval = null; 102 | } 103 | }, 104 | finish: function(){} 105 | }; 106 | })(); 107 | } 108 | } 109 | }); 110 | })(); 111 | -------------------------------------------------------------------------------- /www/app/app.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app', ['ionic']) 4 | .config(configBlock) 5 | .run(runBlock); 6 | 7 | function configBlock($stateProvider, $urlRouterProvider, $provide){ 8 | $stateProvider 9 | .state('loading', { 10 | url: '/loading', 11 | template: '', 12 | controller: 'LoadingCtrl' 13 | }) 14 | .state('login', { 15 | url: '/login', 16 | templateUrl: 'app/authentication/login.html', 17 | controller: 'LoginCtrl' 18 | }) 19 | .state('app', { 20 | url: '/app', 21 | abstract: true, 22 | templateUrl: 'app/layout/layout.html', 23 | controller: 'LayoutCtrl' 24 | }) 25 | .state('app.twitts', { 26 | url: '/twitts', 27 | views: { 28 | 'menuContent': { 29 | templateUrl: 'app/twitts/twitts.html', 30 | controller: 'TwittsCtrl' 31 | } 32 | } 33 | }) 34 | .state('app.twitt', { 35 | url: '/twitts/:id', 36 | views: { 37 | 'menuContent': { 38 | templateUrl: 'app/twitts/twitt.html', 39 | controller: 'TwittCtrl' 40 | } 41 | } 42 | }) 43 | .state('app.settings', { 44 | url: '/settings', 45 | views: { 46 | 'menuContent': { 47 | templateUrl: 'app/settings/settings.html', 48 | controller: 'SettingsCtrl', 49 | resolve: { 50 | resolvedSettings: function(Storage){ 51 | return Storage.getUserSettings(); 52 | } 53 | } 54 | } 55 | } 56 | }); 57 | 58 | $urlRouterProvider.otherwise('/loading'); 59 | 60 | // catch Angular errors 61 | $provide.decorator('$exceptionHandler', ['$delegate', function($delegate){ 62 | return function(exception, cause){ 63 | $delegate(exception, cause); 64 | var data = {}; 65 | if(cause) { data.cause = cause; } 66 | if(exception){ 67 | if(exception.message) { data.message = exception.message; } 68 | if(exception.name) { data.name = exception.name; } 69 | if(exception.stack) { data.stack = exception.stack; } 70 | } 71 | Logger.error('Angular error: '+data.message, {cause: data.cause, stack: data.stack}); 72 | }; 73 | }]); 74 | } 75 | 76 | // catch JavaScript errors 77 | window.onerror = function(message, url, line, col, error){ 78 | var stopPropagation = false; 79 | var data = {}; 80 | if(message) { data.message = message; } 81 | if(url) { data.fileName = url; } 82 | if(line) { data.lineNumber = line; } 83 | if(col) { data.columnNumber = col; } 84 | if(error){ 85 | if(error.name) { data.name = error.name; } 86 | if(error.stack) { data.stack = error.stack; } 87 | } 88 | if(navigator){ 89 | if(navigator.userAgent) { data['navigator.userAgent'] = navigator.userAgent; } 90 | if(navigator.platform) { data['navigator.platform'] = navigator.platform; } 91 | if(navigator.vendor) { data['navigator.vendor'] = navigator.vendor; } 92 | if(navigator.appCodeName) { data['navigator.appCodeName'] = navigator.appCodeName; } 93 | if(navigator.appName) { data['navigator.appName'] = navigator.appName; } 94 | if(navigator.appVersion) { data['navigator.appVersion'] = navigator.appVersion; } 95 | if(navigator.product) { data['navigator.product'] = navigator.product; } 96 | } 97 | Logger.error('JavaScript error: '+data.message, {cause: data.cause, stack: data.stack}); 98 | return stopPropagation; 99 | }; 100 | 101 | function runBlock($rootScope){ 102 | $rootScope.safeApply = function(fn){ 103 | var phase = this.$root ? this.$root.$$phase : this.$$phase; 104 | if(phase === '$apply' || phase === '$digest'){ 105 | if(fn && (typeof(fn) === 'function')){ 106 | fn(); 107 | } 108 | } else { 109 | this.$apply(fn); 110 | } 111 | }; 112 | } 113 | })(); 114 | -------------------------------------------------------------------------------- /www/app/common/plugins/camera.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('CameraPlugin', CameraPlugin); 5 | 6 | // for Camera plugin : cordova-plugin-camera (https://github.com/apache/cordova-plugin-camera) 7 | function CameraPlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'Camera'; 9 | var pluginTest = function(){ return $window.navigator && $window.navigator.camera; }; 10 | return { 11 | getPicture: _getPicture, 12 | takePicture: takePicture, 13 | findPicture: findPicture 14 | }; 15 | 16 | function _getPicture(_opts){ 17 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 18 | var opts = angular.extend({ 19 | quality : 50, // between 0-100 (default: 50) 20 | destinationType : $window.Camera.DestinationType.FILE_URI, // Type of result (default: FILE_URI) 21 | sourceType : $window.Camera.PictureSourceType.CAMERA, // Source of the picture (default: CAMERA) 22 | allowEdit : false, 23 | encodingType: $window.Camera.EncodingType.JPEG, // (default: JPEG) 24 | // targetWidth: 100, 25 | // targetHeight: 100, 26 | mediaType: $window.Camera.MediaType.PICTURE, // (default: PICTURE) 27 | cameraDirection: $window.Camera.Direction.BACK, // (default: BACK) 28 | correctOrientation: true, // rotate the image to correct for the orientation of the device during capture 29 | saveToPhotoAlbum: false 30 | }, _opts); 31 | var defer = $q.defer(); 32 | $window.navigator.camera.getPicture(function(picture){ 33 | defer.resolve(picture); 34 | }, function(error){ 35 | $log.error('pluginError:'+pluginName, error); 36 | defer.reject(error); 37 | }, opts); 38 | return defer.promise; 39 | }); 40 | } 41 | 42 | function takePicture(){ 43 | return _getPicture({}); 44 | } 45 | 46 | function findPicture(){ 47 | return _getPicture({sourceType: $window.Camera.PictureSourceType.PHOTOLIBRARY}); 48 | } 49 | } 50 | 51 | 52 | /************************** 53 | * * 54 | * Browser Mock * 55 | * * 56 | **************************/ 57 | ionic.Platform.ready(function(){ 58 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 59 | if(!window.navigator){window.navigator = {};} 60 | if(!window.navigator.camera){ 61 | window.navigator.camera = (function(){ 62 | window.Camera = { 63 | DestinationType: { 64 | DATA_URL: 0, // return image as base64-encoded string 65 | FILE_URI: 1, // return image file URI (default) 66 | NATIVE_URI: 2 // return image native URI 67 | }, 68 | Direction: { 69 | BACK: 0, // Use the back-facing camera (default) 70 | FRONT: 1 // Use the front-facing camera 71 | }, 72 | EncodingType: { 73 | JPEG: 0, // (default) 74 | PNG: 1 75 | }, 76 | MediaType: { 77 | PICTURE: 0, // allow selection of pictures only. Will return format specified via DestinationType (default) 78 | VIDEO: 1, // allow selection of video only, will always return FILE_URI 79 | ALLMEDIA: 2 // allow selection from all media types 80 | }, 81 | PictureSourceType: { 82 | PHOTOLIBRARY: 0, // dialog displays that allows users to select an existing image 83 | CAMERA: 1, // opens the device's default camera application that allows users to snap pictures (default) 84 | SAVEDPHOTOALBUM: 2 // dialog displays that allows users to select an existing image 85 | }, 86 | PopoverArrowDirection: { // iOS only 87 | ARROW_UP: 1, 88 | ARROW_DOWN: 2, 89 | ARROW_LEFT: 4, 90 | ARROW_RIGHT: 8, 91 | ARROW_ANY: 15 92 | } 93 | }; 94 | 95 | var ret = JSON.parse(JSON.stringify(window.Camera)); 96 | ret.getPicture = function(success, error, options){ 97 | var uri = window.prompt('Image uri :'); 98 | if(uri){ 99 | if(success){ success(uri); } 100 | } else { 101 | if(error){ error(); } 102 | } 103 | }; 104 | 105 | return ret; 106 | })(); 107 | } 108 | } 109 | }); 110 | })(); 111 | -------------------------------------------------------------------------------- /www/app/common/plugins/media.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('MediaPlugin', MediaPlugin); 5 | 6 | // for Media plugin : org.apache.cordova.media (https://github.com/apache/cordova-plugin-media) 7 | function MediaPlugin($window, $q, $ionicPlatform, $log, PluginUtils){ 8 | var pluginName = 'Media'; 9 | var pluginTest = function(){ return $window.Media; }; 10 | var service = { 11 | loadMedia: loadMedia, 12 | statusToMessage: statusToMessage, 13 | errorToMessage: errorToMessage 14 | }; 15 | 16 | function loadMedia(src, onStop, onError, onStatus){ 17 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 18 | var mediaSuccess = function(){ 19 | if(onStop){onStop();} 20 | }; 21 | var mediaError = function(error){ 22 | $log.error('pluginError:'+pluginName, { 23 | src: src, 24 | code: error.code, 25 | message: errorToMessage(error.code) 26 | }); 27 | if(onError){onError(error);} 28 | }; 29 | var mediaStatus = function(status){ 30 | if(onStatus){onStatus(status);} 31 | }; 32 | 33 | if($ionicPlatform.is('android')){src = '/android_asset/www/' + src;} 34 | return new $window.Media(src, mediaSuccess, mediaError, mediaStatus); 35 | }); 36 | } 37 | 38 | function statusToMessage(status){ 39 | if(status === 0){return 'Media.MEDIA_NONE';} 40 | else if(status === 1){return 'Media.MEDIA_STARTING';} 41 | else if(status === 2){return 'Media.MEDIA_RUNNING';} 42 | else if(status === 3){return 'Media.MEDIA_PAUSED';} 43 | else if(status === 4){return 'Media.MEDIA_STOPPED';} 44 | else {return 'Unknown status <'+status+'>';} 45 | } 46 | 47 | function errorToMessage(code){ 48 | if(code === 1){return 'MediaError.MEDIA_ERR_ABORTED';} 49 | else if(code === 2){return 'MediaError.MEDIA_ERR_NETWORK';} 50 | else if(code === 3){return 'MediaError.MEDIA_ERR_DECODE';} 51 | else if(code === 4){return 'MediaError.MEDIA_ERR_NONE_SUPPORTED';} 52 | else {return 'Unknown code <'+code+'>';} 53 | } 54 | 55 | return service; 56 | } 57 | 58 | 59 | /************************** 60 | * * 61 | * Browser Mock * 62 | * * 63 | **************************/ 64 | ionic.Platform.ready(function(){ 65 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 66 | if(!window.Media){ 67 | window.Media = function(src, mediaSuccess, mediaError, mediaStatus){ 68 | // src: A URI containing the audio content. (DOMString) 69 | // mediaSuccess: (Optional) The callback that executes after a Media object has completed the current play, record, or stop action. (Function) 70 | // mediaError: (Optional) The callback that executes if an error occurs. (Function) 71 | // mediaStatus: (Optional) The callback that executes to indicate status changes. (Function) 72 | 73 | if (typeof Audio !== 'function' && typeof Audio !== 'object'){ 74 | console.warn('HTML5 Audio is not supported in this browser'); 75 | } 76 | var sound = new Audio(); 77 | sound.src = src; 78 | sound.addEventListener('ended', mediaSuccess, false); 79 | sound.load(); 80 | 81 | return { 82 | // Returns the current position within an audio file (in seconds). 83 | getCurrentPosition: function(mediaSuccess, mediaError){ mediaSuccess(sound.currentTime); }, 84 | // Returns the duration of an audio file (in seconds) or -1. 85 | getDuration: function(){ return isNaN(sound.duration) ? -1 : sound.duration; }, 86 | // Start or resume playing an audio file. 87 | play: function(){ sound.play(); }, 88 | // Pause playback of an audio file. 89 | pause: function(){ sound.pause(); }, 90 | // Releases the underlying operating system's audio resources. Should be called on a ressource when it's no longer needed ! 91 | release: function(){}, 92 | // Moves the position within the audio file. 93 | seekTo: function(milliseconds){}, // TODO 94 | // Set the volume for audio playback (between 0.0 and 1.0). 95 | setVolume: function(volume){ sound.volume = volume; }, 96 | // Start recording an audio file. 97 | startRecord: function(){}, 98 | // Stop recording an audio file. 99 | stopRecord: function(){}, 100 | // Stop playing an audio file. 101 | stop: function(){ sound.pause(); if(mediaSuccess){mediaSuccess();} } // TODO 102 | }; 103 | }; 104 | } 105 | } 106 | }); 107 | })(); 108 | -------------------------------------------------------------------------------- /www/app/common/plugins/local-notification.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('LocalNotificationPlugin', LocalNotificationPlugin); 5 | 6 | // for LocalNotification plugin : de.appplant.cordova.plugin.local-notification (https://github.com/katzer/cordova-plugin-local-notifications/) 7 | function LocalNotificationPlugin($window, $q, PluginUtils){ 8 | var pluginName = 'LocalNotification'; 9 | var pluginTest = function(){ return $window.plugin && $window.plugin.notification && $window.plugin.notification.local; }; 10 | var service = { 11 | schedule: schedule, 12 | cancel: cancel, 13 | onClick: function(callback){ return on('click', callback); } 14 | }; 15 | 16 | function schedule(opts){ 17 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 18 | $window.plugin.notification.local.schedule(opts); 19 | }); 20 | } 21 | 22 | function cancel(id){ 23 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 24 | var defer = $q.defer(); 25 | $window.plugin.notification.local.cancel(id, function(){ 26 | defer.resolve(); 27 | }); 28 | return defer.promise; 29 | }); 30 | } 31 | 32 | function on(event, callback){ 33 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 34 | $window.plugin.notification.local.on(event, callback); 35 | }); 36 | } 37 | 38 | return service; 39 | } 40 | 41 | 42 | /************************** 43 | * * 44 | * Browser Mock * 45 | * * 46 | **************************/ 47 | ionic.Platform.ready(function(){ 48 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 49 | if(!window.plugin){window.plugin = {};} 50 | if(!window.plugin.notification){window.plugin.notification = {};} 51 | if(!window.plugin.notification.local){ 52 | window.plugin.notification.local = (function(){ 53 | var notifs = {}; 54 | // https://github.com/katzer/cordova-plugin-local-notifications/wiki/04.-Scheduling#interface 55 | var defaults = { 56 | id: '0', 57 | title: '', 58 | text: '', 59 | every: 0, 60 | at: new Date(), 61 | badge: 0, 62 | sound: 'res://platform_default', 63 | data: null, 64 | icon: 'res://icon', 65 | smallIcon: 'res://ic_popup_reminder', 66 | ongoing: false, 67 | led: 'FFFFFF' 68 | }; 69 | 70 | function withDefaults(opts){ 71 | var res = JSON.parse(JSON.stringify(defaults)); 72 | for(var i in opts){ 73 | res[i] = opts[i]; 74 | } 75 | return res; 76 | } 77 | 78 | var ret = { 79 | hasPermission: function(callback, scope){if(callback){callback(true);}}, 80 | registerPermission: function(callback, scope){if(callback){callback(true);}}, 81 | schedule: function(opts, callback, scope){ 82 | if(!Array.isArray(opts)){ opts = [opts]; } 83 | for(var i in opts){ 84 | var params = withDefaults(opts[i]); 85 | if(ret.onadd){ret.onadd(params.id, 'foreground', params.json);} 86 | notifs[params.id] = params; 87 | } 88 | if(callback){callback();} 89 | }, 90 | cancel: function(id, callback, scope){ 91 | if(ret.oncancel){ret.oncancel(id, 'foreground', notifs[id].json);} 92 | delete notifs[id]; 93 | if(callback){callback();} 94 | }, 95 | cancelAll: function(callback, scope){ 96 | for(var i in notifs){ 97 | if(ret.oncancel){ret.oncancel(notifs[i].id, 'foreground', notifs[i].json);} 98 | delete notifs[i]; 99 | } 100 | if(callback){callback();} 101 | }, 102 | on: function(event, callback){}, // TODO 103 | isScheduled: function(id, callback, scope){ 104 | if(callback){callback(!!notifs[id]);} 105 | }, 106 | getScheduledIds: function(callback, scope){ 107 | if(callback){ 108 | var ids = []; 109 | for(var i in notifs){ ids.push(notifs[i].id); } 110 | callback(ids); 111 | } 112 | }, 113 | isTriggered: function(id, callback, scope){if(callback){callback(false);}}, // TODO 114 | getTriggeredIds: function(callback, scope){if(callback){callback([]);}}, // TODO 115 | getDefaults: function(){return JSON.parse(JSON.stringify(defaults));}, 116 | setDefaults: function(opts){ defaults = withDefaults(opts); } 117 | }; 118 | 119 | return ret; 120 | })(); 121 | } 122 | } 123 | }); 124 | })(); 125 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ionic-starter 4 | Start a new ionic project in seconds ! 5 | Your Name Here 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | -------------------------------------------------------------------------------- /www/app/common/plugins/dialogs.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('DialogPlugin', DialogPlugin); 5 | 6 | // for Dialogs plugin : org.apache.cordova.dialogs (https://github.com/apache/cordova-plugin-dialogs) 7 | function DialogPlugin($window, $q, $log, PluginUtils){ 8 | var pluginName = 'Dialogs'; 9 | var pluginTest = function(){ return $window.navigator && $window.navigator.notification; }; 10 | /* 11 | * Button indexes : 12 | * - 0 : cancel with backdrop 13 | * - 1 : Ok 14 | * - 2 : Annuler 15 | * Or, your index in buttonLabels array but one based !!! (0 is still cancel) 16 | */ 17 | var service = { 18 | alert: pluginAlert, 19 | confirm: function(message, _title){ 20 | return pluginConfirm(message, _title).then(function(buttonIndex){ 21 | return _isConfirm(buttonIndex); 22 | }); 23 | }, 24 | confirmMulti: pluginConfirm, 25 | prompt: function(message, _title, _defaultText){ 26 | return pluginPrompt(message, _title, null, _defaultText).then(function(result){ 27 | result.confirm = _isConfirm(result.buttonIndex); 28 | return result; 29 | }); 30 | }, 31 | promptMulti: pluginPrompt, 32 | beep: pluginBeep 33 | }; 34 | 35 | function pluginAlert(message, _title, _buttonName){ 36 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 37 | var defer = $q.defer(); 38 | $window.navigator.notification.alert(message, function(){ defer.resolve(); }, _title, _buttonName); 39 | return defer.promise; 40 | }, function(error){ 41 | $log.error('pluginError:'+pluginName, error); 42 | $window.alert(message); 43 | }); 44 | } 45 | 46 | function pluginConfirm(message, _title, _buttonLabels){ 47 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 48 | var defer = $q.defer(); 49 | $window.navigator.notification.confirm(message, function(buttonIndex){ defer.resolve(buttonIndex); }, _title, _buttonLabels); 50 | return defer.promise; 51 | }, function(error){ 52 | $log.error('pluginError:'+pluginName, error); 53 | return _toButtonIndex($window.confirm(message)); 54 | }); 55 | } 56 | 57 | function pluginPrompt(message, _title, _buttonLabels, _defaultText){ 58 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 59 | var defer = $q.defer(); 60 | $window.navigator.notification.prompt(message, function(result){ defer.resolve(result); }, _title, _buttonLabels, _defaultText); 61 | return defer.promise; 62 | }, function(error){ 63 | $log.error('pluginError:'+pluginName, error); 64 | var text = $window.prompt(message, _defaultText); 65 | return {buttonIndex: _toButtonIndex(text), input1: text}; 66 | }); 67 | } 68 | 69 | function pluginBeep(times){ 70 | if(!times){times = 1;} 71 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 72 | $window.navigator.notification.beep(times); 73 | }, function(error){ 74 | $log.error('pluginError:'+pluginName, error); 75 | if(beepFallback){ 76 | beepFallback(times); 77 | } else { 78 | $q.reject(error); 79 | } 80 | }); 81 | } 82 | 83 | function _isConfirm(buttonIndex){ 84 | return buttonIndex === 1 ? true : false; 85 | } 86 | function _toButtonIndex(value){ 87 | return value ? 1 : 2; 88 | } 89 | 90 | var AudioCtx = window.AudioContext || window.webkitAudioContext; 91 | if(AudioCtx){ 92 | var ctx = new AudioCtx(); 93 | var html5Beep = function(callback){ 94 | var duration = 200; 95 | var type = 0; 96 | if(!callback){callback = function(){};} 97 | var osc = ctx.createOscillator(); 98 | osc.type = type; 99 | osc.connect(ctx.destination); 100 | osc.noteOn(0); 101 | $window.setTimeout(function(){ 102 | osc.noteOff(0); 103 | callback(); 104 | }, duration); 105 | }; 106 | var beepFallback = function(times){ 107 | if(times > 0){ 108 | html5Beep(function(){ 109 | $window.setTimeout(function(){beepFallback(times-1);}, 500); 110 | }); 111 | } 112 | }; 113 | } 114 | 115 | return service; 116 | } 117 | 118 | 119 | /************************** 120 | * * 121 | * Browser Mock * 122 | * * 123 | **************************/ 124 | ionic.Platform.ready(function(){ 125 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 126 | if(!window.navigator){window.navigator = {};} 127 | if(!window.navigator.notification){ 128 | window.navigator.notification = (function(){ 129 | var ctx = new(window.AudioContext || window.webkitAudioContext); 130 | function html5Beep(callback){ 131 | var duration = 200; 132 | var type = 0; 133 | if(!callback){callback = function(){};} 134 | var osc = ctx.createOscillator(); 135 | osc.type = type; 136 | osc.connect(ctx.destination); 137 | osc.noteOn(0); 138 | window.setTimeout(function(){ 139 | osc.noteOff(0); 140 | callback(); 141 | }, duration); 142 | } 143 | 144 | function beep(times){ 145 | if(times > 0){ 146 | html5Beep(function(){ 147 | window.setTimeout(function(){beep(times-1);}, 500); 148 | }); 149 | } 150 | } 151 | 152 | return { 153 | alert: function(message, alertCallback, title, buttonName){ 154 | window.alert(message); 155 | if(alertCallback){alertCallback();} 156 | }, 157 | confirm: function(message, confirmCallback, title, buttonLabels){ 158 | var c = window.confirm(message); 159 | if(confirmCallback){confirmCallback(c ? 1 : 2);} 160 | }, 161 | prompt: function(message, promptCallback, title, buttonLabels, defaultText){ 162 | var text = window.prompt(message, defaultText); 163 | if(promptCallback){promptCallback({buttonIndex: text ? 1 : 2, input1: text});} 164 | }, 165 | beep: beep 166 | }; 167 | })(); 168 | } 169 | } 170 | }); 171 | })(); 172 | -------------------------------------------------------------------------------- /www/app/common/plugins/push.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('PushPlugin', PushPlugin); 5 | 6 | // for Push plugin : https://github.com/phonegap-build/PushPlugin 7 | function PushPlugin($q, $http, $ionicPlatform, $window, $log, PluginUtils, Config){ 8 | var pluginName = 'Push'; 9 | var pluginTest = function(){ return $window.plugins && $window.plugins.pushNotification; }; 10 | var callbackCurRef = 1; 11 | var callbackList = {}; 12 | var service = { 13 | type: { 14 | ALL: 'all', 15 | MESSAGE: 'message', 16 | REGISTERED: 'registered', 17 | ERROR: 'error' 18 | }, 19 | sendPush: sendPush, 20 | register: register, 21 | onNotification: onNotification, 22 | cancel: cancel 23 | }; 24 | 25 | // This function is not part of the plugin, you should implement it here !!! 26 | function sendPush(recipients, data){ 27 | if($ionicPlatform.is('android')){ 28 | return $http.post('https://android.googleapis.com/gcm/send', { 29 | registration_ids: recipients, // array of registrationIds 30 | data: data // payload, usefull fields: title, message, timestamp, msgcnt 31 | }, { 32 | headers: { 33 | Authorization: 'key='+Config.gcm.apiServerKey 34 | } 35 | }).then(function(){ 36 | return true; 37 | }); 38 | } else { 39 | $window.alert('Your platform don\'t have push support :('); 40 | return $q.when(false); 41 | } 42 | } 43 | 44 | function register(senderID){ 45 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 46 | var defer = $q.defer(); 47 | var callbackRef = onNotification(function(notification){ 48 | defer.resolve(notification.regid); 49 | cancel(callbackRef); 50 | }, service.type.REGISTERED); 51 | $window.plugins.pushNotification.register(function(data){}, function(err){ registerDefer.reject(err); }, { 52 | senderID: senderID, 53 | ecb: 'onPushNotification' 54 | }); 55 | return defer.promise; 56 | }); 57 | } 58 | 59 | function onNotification(callback, _type){ 60 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 61 | var id = callbackCurRef++; 62 | callbackList[id] = {fn: callback, type: _type || service.type.MESSAGE}; 63 | return id; 64 | }); 65 | } 66 | 67 | function cancel(id){ 68 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 69 | delete callbackList[id]; 70 | }); 71 | } 72 | 73 | $window.onPushNotification = function(notification){ 74 | if(notification.event === service.type.MESSAGE){} // normal notification 75 | else if(notification.event === service.type.REGISTERED){} // registration acknowledgment 76 | else if(notification.event === service.type.ERROR){ $log.error('GCM error', notification); } // GCM error 77 | else { $log.error('unknown GCM event has occurred', notification); } // unknown notification 78 | 79 | for(var i in callbackList){ 80 | if(callbackList[i].type === service.type.ALL || callbackList[i].type === notification.event){ 81 | callbackList[i].fn(notification); 82 | } 83 | } 84 | }; 85 | 86 | // iOS only 87 | function setApplicationIconBadgeNumber(badgeNumber){ 88 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 89 | var defer = $q.defer(); 90 | $window.plugins.pushNotification.setApplicationIconBadgeNumber(function(a,b,c){ 91 | console.log('success a', a); 92 | console.log('success b', b); 93 | console.log('success c', c); 94 | defer.resolve(); 95 | }, function(err){ 96 | // on Android : "Invalid action : setApplicationIconBadgeNumber" 97 | defer.reject(err); 98 | }, badgeNumber); 99 | return defer.promise; 100 | }); 101 | } 102 | 103 | // iOS only 104 | function showToastNotification(options){ 105 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 106 | var defer = $q.defer(); 107 | $window.plugins.pushNotification.showToastNotification(function(a,b,c){ 108 | console.log('success a', a); 109 | console.log('success b', b); 110 | console.log('success c', c); 111 | defer.resolve(); 112 | }, function(err){ 113 | // on Android : "Invalid action : showToastNotification" 114 | defer.reject(err); 115 | }, options); 116 | return defer.promise; 117 | }); 118 | } 119 | 120 | return service; 121 | } 122 | 123 | 124 | /************************** 125 | * * 126 | * Browser Mock * 127 | * * 128 | **************************/ 129 | ionic.Platform.ready(function(){ 130 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 131 | if(!window.plugins){window.plugins = {};} 132 | if(!window.plugins.pushNotification){ 133 | window.plugins.pushNotification = (function(){ 134 | return { 135 | // https://github.com/phonegap-build/PushPlugin methods 136 | register: function(successCallback, errorCallback, options){ 137 | setTimeout(function(){ 138 | if(successCallback){ 139 | successCallback('OK'); 140 | } 141 | if(options && options.ecb){ 142 | eval(options.ecb)({ 143 | event: 'registered', 144 | regid: 'registration_id' 145 | }); 146 | } 147 | }, 0); 148 | }, 149 | setApplicationIconBadgeNumber: function(successCallback, errorCallback, badge){if(errorCallback){errorCallback('Invalid action : setApplicationIconBadgeNumber');}}, 150 | showToastNotification: function(successCallback, errorCallback, options){if(errorCallback){errorCallback('Invalid action : showToastNotification');}}, 151 | unregister: function(successCallback, errorCallback, options){}, 152 | 153 | // https://github.com/Pushwoosh/pushwoosh-phonegap-3.0-plugin methods 154 | onDeviceReady: function(opts){}, 155 | registerDevice: function(successCallback, errorCallback){ if(successCallback){successCallback('status');} } 156 | }; 157 | })(); 158 | } 159 | } 160 | }); 161 | })(); 162 | -------------------------------------------------------------------------------- /www/app/common/storage.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('Storage', Storage) 5 | .factory('_StorageUtils', _StorageUtils) // private service, should not be used outside this file !!! 6 | .factory('_SQLiteUtils', _SQLiteUtils); // private service, should not be used outside this file !!! 7 | 8 | function Storage(_StorageUtils){ 9 | var keys = { 10 | user: 'user', 11 | userSettings: 'user-settings', 12 | twitts: 'twitts' 13 | }; 14 | return { 15 | // user 16 | getUser: getUser, 17 | setUser: setUser, 18 | getUserSettings: getUserSettings, 19 | setUserSettings: setUserSettings, 20 | // twitts 21 | getTwitt: getTwitt, 22 | getTwitts: getTwitts, 23 | setTwitts: setTwitts, 24 | // global 25 | clear: clear 26 | }; 27 | 28 | function getUser(){ 29 | return _StorageUtils.get(keys.user); 30 | } 31 | 32 | function setUser(user){ 33 | return _StorageUtils.set(keys.user, user); 34 | } 35 | 36 | function getUserSettings(){ 37 | return _StorageUtils.get(keys.userSettings, { 38 | autoSendActions: true, 39 | logsActivated: true 40 | }); 41 | } 42 | 43 | function setUserSettings(settings){ 44 | return _StorageUtils.set(keys.userSettings, settings); 45 | } 46 | 47 | function getTwitt(id){ 48 | return getTwitts().then(function(twitts){ 49 | return _.find(twitts, {id: id}); 50 | }); 51 | } 52 | 53 | function getTwitts(){ 54 | return _StorageUtils.get(keys.twitts); 55 | } 56 | 57 | function setTwitts(twitts){ 58 | return _StorageUtils.set(keys.twitts, twitts); 59 | } 60 | 61 | function clear(){ 62 | return _StorageUtils.clear(); 63 | } 64 | } 65 | 66 | // Storage helper allowing to cache data 67 | function _StorageUtils($q, _SQLiteUtils, Utils){ 68 | var useStorage = true; // if false, only the cache will be used, data won't be persistent 69 | var storagePrefix = ''; // to prefix all entries 70 | var storageCache = {}; 71 | var promiseStorageCache = {}; 72 | var keysCache = null; 73 | var keysCachePromise = null; 74 | var storage = _SQLiteUtils; 75 | return { 76 | get: get, 77 | getEndsWith: getEndsWith, 78 | set: set, 79 | remove: remove, 80 | clear: clear 81 | }; 82 | 83 | function get(key, _defaultValue){ 84 | if(storageCache[key]){ 85 | return Utils.realAsync(function(){ return angular.copy(storageCache[key]); }); 86 | } else if(promiseStorageCache[key]){ 87 | return promiseStorageCache[key]; 88 | } else { 89 | if(useStorage){ 90 | promiseStorageCache[key] = storage.getItem(storagePrefix+key).then(function(value){ 91 | try { 92 | storageCache[key] = JSON.parse(value) || angular.copy(_defaultValue); 93 | } catch(e) { 94 | storageCache[key] = angular.copy(_defaultValue); 95 | } 96 | delete promiseStorageCache[key]; 97 | return angular.copy(storageCache[key]); 98 | }, function(error){ 99 | Logger.error('Unable to _StorageUtils.get('+key+') !!!', error); 100 | delete promiseStorageCache[key]; 101 | }); 102 | return promiseStorageCache[key]; 103 | } else { 104 | storageCache[key] = angular.copy(_defaultValue); 105 | return Utils.realAsync(function(){ return angular.copy(storageCache[key]); }); 106 | } 107 | } 108 | } 109 | 110 | function getEndsWith(keyEnd, _defaultValue){ 111 | return _getKeys().then(function(keys){ 112 | var matchingKeys = _.filter(keys, function(key){ 113 | return key && key.endsWith(keyEnd); 114 | }); 115 | var promises = _.map(matchingKeys, function(key){ 116 | return get(key, _defaultValue); 117 | }); 118 | return $q.all(promises); 119 | }); 120 | } 121 | 122 | function set(key, value){ 123 | if(!angular.equals(storageCache[key], value)){ 124 | storageCache[key] = angular.copy(value); 125 | _setKey(key); 126 | if(useStorage){ 127 | return storage.setItem(storagePrefix+key, JSON.stringify(storageCache[key])).then(function(value){ 128 | // return nothing ! 129 | }, function(error){ 130 | Logger.error('error in LocalForageUtils._set('+key+')', error); 131 | }); 132 | } else { 133 | return $q.when(); 134 | } 135 | } else { 136 | //Logger.debug('Don\'t save <'+key+'> because values are equals !', value); 137 | return $q.when(); 138 | } 139 | } 140 | 141 | function remove(key){ 142 | delete storageCache[key]; 143 | _removeKey(key); 144 | if(useStorage){ 145 | return storage.removeItem(storagePrefix+key); 146 | } else { 147 | return $q.when(); 148 | } 149 | } 150 | 151 | function clear(){ 152 | storageCache = {}; 153 | _clearKeys(); 154 | if(useStorage){ 155 | return storage.clear(); 156 | } else { 157 | return $q.when(); 158 | } 159 | } 160 | 161 | function _getKeys(){ 162 | if(keysCache){ 163 | return Utils.realAsync(function(){ return angular.copy(keysCache); }); 164 | } else if(keysCachePromise){ 165 | return keysCachePromise; 166 | } else { 167 | if(useStorage){ 168 | keysCachePromise = storage.keys().then(function(keys){ 169 | keysCache = keys; 170 | keysCachePromise = null; 171 | return angular.copy(keysCache); 172 | }, function(error){ 173 | Logger.error('Unable to _StorageUtils._getKeys() !!!', error); 174 | keysCachePromise = null; 175 | }); 176 | return keysCachePromise; 177 | } else { 178 | keysCache = []; 179 | for(var i in storageCache){ 180 | keysCache.push(i); 181 | } 182 | return Utils.realAsync(function(){ return angular.copy(keysCache); }); 183 | } 184 | } 185 | } 186 | 187 | function _setKey(key){ 188 | if(keysCache){ 189 | if(keysCache.indexOf(key) < 0){ 190 | keysCache.push(key); 191 | } 192 | } else if(keysCachePromise){ 193 | keysCachePromise.then(function(){ 194 | _setKey(key); 195 | }); 196 | } 197 | } 198 | 199 | function _removeKey(key){ 200 | if(keysCache){ 201 | if(keysCache.indexOf(key) >= 0){ 202 | keysCache.splice(keysCache.indexOf(key), 1); 203 | } 204 | } else if(keysCachePromise){ 205 | keysCachePromise.then(function(){ 206 | _removeKey(key); 207 | }); 208 | } 209 | } 210 | 211 | function _clearKeys(){ 212 | if(keysCache){ 213 | keysCache = null; 214 | } else if(keysCachePromise){ 215 | keysCachePromise.then(function(){ 216 | _clearKeys(); 217 | }); 218 | } 219 | } 220 | } 221 | 222 | function _SQLiteUtils(SQLitePlugin){ 223 | var tableName = 'KeyValue'; 224 | var onReady = _init(); 225 | return { 226 | getItem: getItem, 227 | setItem: setItem, 228 | removeItem: removeItem, 229 | keys: keys, 230 | clear: clear 231 | }; 232 | 233 | function getItem(key){ 234 | return _query('SELECT value FROM '+tableName+' WHERE key = ? LIMIT 1', [key]).then(function(data){ 235 | if(data.length > 0){ 236 | return data[0].value; 237 | } 238 | }); 239 | } 240 | 241 | function setItem(key, value){ 242 | return _query('INSERT OR REPLACE INTO '+tableName+'(key, value) VALUES (?, ?)', [key, value]).then(function(data){ 243 | }); 244 | } 245 | 246 | function removeItem(key){ 247 | return _query('DELETE FROM '+tableName+' WHERE key = ?', [key]).then(function(data){ 248 | }); 249 | } 250 | 251 | function keys(){ 252 | return _query('SELECT key FROM '+tableName).then(function(data){ 253 | return _.map(data, 'key'); 254 | }); 255 | } 256 | 257 | function clear(){ 258 | return _query('DELETE FROM '+tableName).then(function(data){ 259 | }); 260 | } 261 | 262 | function _query(query, args){ 263 | return onReady.then(function(db){ 264 | return SQLitePlugin.query(db, query, args); 265 | }); 266 | } 267 | 268 | function _init(){ 269 | return SQLitePlugin.open().then(function(db){ 270 | return SQLitePlugin.query(db, 'CREATE TABLE IF NOT EXISTS '+tableName+' (key text primary key, value text)').then(function(){ 271 | return db; 272 | }); 273 | }); 274 | } 275 | } 276 | })(); 277 | -------------------------------------------------------------------------------- /www/app/common/plugins/file.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | angular.module('app') 4 | .factory('FilePlugin', FilePlugin) 5 | .filter('fullPath', fullPathFilter); 6 | 7 | // for File plugin : cordova-plugin-file (https://github.com/apache/cordova-plugin-file) 8 | function FilePlugin($window, $q, $log, PluginUtils){ 9 | var pluginName = 'File'; 10 | var pluginTest = function(){ return $window.cordova && $window.cordova.file; }; 11 | // https://developer.mozilla.org/fr/docs/Web/API/FileError 12 | var ERR_CODE = { 13 | '1': 'NOT_FOUND_ERR', 14 | '2': 'SECURITY_ERR', 15 | '4': 'NOT_READABLE_ERR', 16 | '5': 'ENCODING_ERR', 17 | '6': 'NO_MODIFICATION_ALLOWED_ERR', 18 | '7': 'INVALID_STATE_ERR', 19 | '9': 'INVALID_MODIFICATION_ERR', 20 | '10': 'QUOTA_EXCEEDED_ERR', 21 | '11': 'TYPE_MISMATCH_ERR', 22 | '12': 'PATH_EXISTS_ERR' 23 | }; 24 | return { 25 | getFullPath: getFullPath, 26 | getFileEntry: getFileEntry, 27 | getContent: getContent, 28 | getContentTree: getContentTree, 29 | getFile: getFile, 30 | getFileBinary: getFileBinary, 31 | getFileBase64: getFileBase64, 32 | createFolder: createFolder, 33 | createFile: createFile, 34 | copyFile: copyFile, 35 | removeFile: removeFile, 36 | removeFiles: removeFiles, 37 | removeFolder: removeFolder, 38 | removeFolders: removeFolders, 39 | clear: clear 40 | }; 41 | 42 | function getFullPath(path, fileLocation){ 43 | if(fileLocation === undefined){ 44 | if(path.startsWith('file://') || path.startsWith('content://')){ 45 | return path; 46 | } else { 47 | return $window.cordova.file.dataDirectory + path; 48 | } 49 | } else { 50 | return fileLocation + path; 51 | } 52 | } 53 | 54 | function getFileEntry(path, fileLocation){ 55 | return PluginUtils.onReady(pluginName, pluginTest).then(function(){ 56 | var defer = $q.defer(); 57 | var fullPath = getFullPath(path, fileLocation); 58 | $window.resolveLocalFileSystemURL(fullPath, function(fileEntry){ 59 | defer.resolve(fileEntry); 60 | }, function(error){ 61 | if(!error){ error = {}; } 62 | error.path = fullPath; 63 | if(error.code){ error.message = ERR_CODE[error.code]; } 64 | $log.error('pluginError:'+pluginName, error); 65 | defer.reject(error); 66 | }); 67 | return defer.promise; 68 | }); 69 | } 70 | 71 | function getContent(path, fileLocation){ 72 | return getFileEntry(path, fileLocation).then(function(dirEntry){ 73 | if(dirEntry.isDirectory){ 74 | return _getContent(dirEntry); 75 | } else { 76 | return $q.reject({message: 'Path "'+path+'" is not a directory !'}); 77 | } 78 | }); 79 | } 80 | function _getContent(dirEntry){ 81 | var defer = $q.defer(); 82 | var dirReader = dirEntry.createReader(); 83 | dirReader.readEntries(function(entries){ 84 | defer.resolve(entries); 85 | }, function(err){ 86 | defer.reject(err); 87 | }); 88 | return defer.promise; 89 | } 90 | 91 | function getContentTree(path, fileLocation){ 92 | return getFileEntry(path, fileLocation).then(function(fileEntry){ 93 | return _getContentTree(fileEntry, {}); 94 | }); 95 | } 96 | function _getContentTree(fileEntry, tree){ 97 | if(fileEntry.isFile){ 98 | tree.entry = fileEntry; 99 | tree.children = []; 100 | return $q.when(tree); 101 | } else { 102 | return _getContent(fileEntry).then(function(childEntries){ 103 | return $q.all(childEntries.map(function(child){ 104 | return _getContentTree(child, {}); 105 | })).then(function(children){ 106 | tree.entry = fileEntry; 107 | tree.children = children; 108 | return tree; 109 | }); 110 | }); 111 | } 112 | } 113 | 114 | function getFile(path, fileLocation){ 115 | return getFileEntry(path, fileLocation).then(function(fileEntry){ 116 | var defer = $q.defer(); 117 | fileEntry.file(function(file){ 118 | defer.resolve(file); 119 | }); 120 | return defer.promise; 121 | }); 122 | } 123 | 124 | function getFileBinary(path, fileLocation){ 125 | return getFile(path, fileLocation).then(function(file){ 126 | var defer = $q.defer(); 127 | var reader = new FileReader(); 128 | reader.onloadend = function(evt){ 129 | var binary = evt.target.result; 130 | defer.resolve(binary); 131 | }; 132 | reader.readAsBinaryString(file); 133 | return defer.promise; 134 | }); 135 | } 136 | 137 | function getFileBase64(path, fileLocation){ 138 | return getFile(path, fileLocation).then(function(file){ 139 | var defer = $q.defer(); 140 | var reader = new FileReader(); 141 | reader.onloadend = function(evt){ 142 | var base64WithPrefix = evt.target.result; 143 | var base64 = base64WithPrefix.replace(/data:(image|application)\/(jpeg|png|zip);base64,/, ''); 144 | defer.resolve(base64); 145 | }; 146 | reader.readAsDataURL(file); 147 | return defer.promise; 148 | }); 149 | } 150 | 151 | function createFolder(path, fileLocation){ 152 | return getFileEntry('', fileLocation).then(function(dirEntry){ 153 | return _createFolderRec(dirEntry, path.split('/')); 154 | }); 155 | } 156 | 157 | function createFile(path, content, fileLocation){ 158 | Logger.info('createFile('+path+')'); 159 | var folders = path.split('/'); 160 | var filename = folders.pop(); 161 | return createFolder(folders.join('/'), fileLocation).then(function(dirEntry){ 162 | var defer = $q.defer(); 163 | dirEntry.getFile(filename, {create: true}, function(fileEntry){ 164 | if(content !== null && content !== undefined){ 165 | fileEntry.createWriter(function(fileWriter){ 166 | fileWriter.onwriteend = function(e){ 167 | defer.resolve(fileEntry); 168 | }; 169 | fileWriter.write(content); 170 | }, function(error){ 171 | defer.reject(error); 172 | }); 173 | } else { 174 | defer.resolve(fileEntry); 175 | } 176 | }, function(error){ 177 | defer.reject(error); 178 | }); 179 | return defer.promise; 180 | }); 181 | } 182 | 183 | function copyFile(filepath, newFilepath, fileLocation, newFileLocation){ 184 | var newPath = newFilepath.substr(0, newFilepath.lastIndexOf('/') + 1); 185 | var newFilename = newFilepath.substr(newFilepath.lastIndexOf('/') + 1); 186 | return $q.all([getFileEntry(filepath, fileLocation), createFolder(newPath, newFileLocation)]).then(function(results){ 187 | var fileEntry = results[0], newDirEntry = results[1]; 188 | var defer = $q.defer(); 189 | fileEntry.copyTo(newDirEntry, newFilename, function(result){ 190 | defer.resolve(result); 191 | }, function(error){ 192 | defer.reject(error); 193 | }); 194 | return defer.promise; 195 | }); 196 | } 197 | 198 | function removeFile(path, fileLocation){ 199 | return getFileEntry(path, fileLocation).then(function(fileEntry){ 200 | var defer = $q.defer(); 201 | if(!fileEntry.isDirectory){ 202 | fileEntry.remove(function(){ 203 | defer.resolve(); 204 | }, function(error){ 205 | defer.reject(error); 206 | }); 207 | } else { 208 | defer.resolve({message: 'Path "'+path+'" is not a file !'}); 209 | } 210 | return defer.promise; 211 | }); 212 | } 213 | 214 | function removeFiles(paths, fileLocation){ 215 | var removePromiseArr = paths.map(function(path){ 216 | return removeFile(path, fileLocation); 217 | }); 218 | return $q.all(removePromiseArr); 219 | } 220 | 221 | function removeFolder(path, fileLocation){ 222 | return getFileEntry(path, fileLocation).then(function(dirEntry){ 223 | var defer = $q.defer(); 224 | if(dirEntry.isDirectory){ 225 | dirEntry.removeRecursively(function(){ 226 | defer.resolve(); 227 | }, function(error){ 228 | defer.reject(error); 229 | }); 230 | } else { 231 | defer.resolve({message: 'Path "'+path+'" is not a directory !'}); 232 | } 233 | return defer.promise; 234 | }); 235 | } 236 | 237 | function removeFolders(paths, fileLocation){ 238 | var removePromiseArr = paths.map(function(path){ 239 | return removedFolder(path, fileLocation); 240 | }); 241 | return $q.all(removePromiseArr); 242 | } 243 | 244 | function clear(path, fileLocation){ 245 | return getContent(path, fileLocation).then(function(entries){ 246 | var promises = entries.map(function(entry){ 247 | var entryPath = entry.fullPath.substr(1); // remove the starting '/' 248 | return removeFolder(entryPath, fileLocation); 249 | }); 250 | return $q.all(promises).then(function(results){ 251 | return results; 252 | }, function(err){ 253 | return err; 254 | }); 255 | }); 256 | } 257 | 258 | /** 259 | * Private methods 260 | */ 261 | function _createFolderRec(dirEntry, folders){ 262 | if(folders.length === 0){ 263 | return $q.when(dirEntry); 264 | } else { 265 | var localFolders = folders.slice(); 266 | var defer = $q.defer(); 267 | dirEntry.getDirectory(localFolders.pop(), {create: true}, function(newDir){ 268 | defer.resolve(_createFolderRec(newDir, localFolders)); 269 | }, function(error){ 270 | defer.reject(error); 271 | }); 272 | return defer.promise; 273 | } 274 | } 275 | } 276 | 277 | function fullPathFilter(FilePlugin){ 278 | return function(path, defaultPath){ 279 | return path ? FilePlugin.getFullPath(path) : defaultPath; 280 | }; 281 | } 282 | 283 | 284 | /************************** 285 | * * 286 | * Browser Mock * 287 | * * 288 | **************************/ 289 | ionic.Platform.ready(function(){ 290 | if(!(ionic.Platform.isAndroid() || ionic.Platform.isIOS() || ionic.Platform.isIPad())){ 291 | if(!window.cordova){window.cordova = {};} 292 | if(!window.cordova.file){ 293 | window.cordova.file = { 294 | applicationDirectory: 'file:///android_asset/', 295 | applicationStorageDirectory: 'file:///data/data/com.exemple.myapp/', 296 | cacheDirectory: 'file:///data/data/com.exemple.myapp/cache/', 297 | dataDirectory: 'file:///data/data/com.exemple.myapp/files/', 298 | documentsDirectory: null, 299 | externalApplicationStorageDirectory: 'file:///storage/emulated/0/Android/data/com.exemple.myapp/', 300 | externalCacheDirectory: 'file:///storage/emulated/0/Android/data/com.exemple.myapp/cache/', 301 | externalDataDirectory: 'file:///storage/emulated/0/Android/data/com.exemple.myapp/files/', 302 | externalRootDirectory: 'file:///storage/emulated/0/', 303 | sharedDirectory: null, 304 | syncedDataDirectory: null, 305 | tempDirectory: null 306 | }; 307 | window.resolveLocalFileSystemURL = function(uri, successCallback, errorCallback){ 308 | var filename = uri.substr(uri.lastIndexOf('/') + 1); 309 | successCallback(FileEntry(filename, uri)); 310 | }; 311 | window.DirectoryEntry = function(name, fullPath, fileSystem, nativeURL){ 312 | }; 313 | window.FileEntry = function(name, fullPath, fileSystem, nativeURL){ 314 | return { 315 | isFile: name.indexOf('.') > -1, 316 | isDirectory: name.indexOf('.') < 0, 317 | createReader: function(){ 318 | return { 319 | readEntries: function(success, fail){ 320 | success([]); 321 | } 322 | }; 323 | }, 324 | createWriter: function(success, fail){ 325 | success({ 326 | write: function(content){ 327 | if(this.onwriteend){ 328 | this.onwriteend(new FileEntry(name, fullPath, fileSystem, nativeURL)); 329 | } 330 | } 331 | }); 332 | }, 333 | getDirectory: function(path, flags, success, fail){ 334 | success(new FileEntry(path, fullPath+path, fileSystem, nativeURL+path)); 335 | }, 336 | getFile: function(path, flags, success, fail){ 337 | success(new FileEntry(path, fullPath+path, fileSystem, nativeURL+path)); 338 | }, 339 | remove: function(success, fail){ 340 | success(); 341 | }, 342 | toURL: function(mimeType){ 343 | return nativeURL; 344 | } 345 | } 346 | }; 347 | } 348 | } 349 | }); 350 | })(); 351 | --------------------------------------------------------------------------------