├── .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 |
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 |
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 |
9 |
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 | [](https://david-dm.org/loicknuchel/ionic-starter)
4 | [](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 |
--------------------------------------------------------------------------------