├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── ionic-settings.css ├── ionic-settings.js ├── ionic-settings.min.css └── ionic-settings.min.js ├── gulpfile.js ├── karma-dist-concatenated.conf.js ├── karma-dist-minified.conf.js ├── karma-src.conf.js ├── lib └── ionic │ └── ionic.bundle.js ├── nbproject ├── project.properties └── project.xml ├── package.json ├── src └── ionic-settings │ ├── ionic-settings.scss │ └── ionicSettings.module.js └── test └── unit └── ionic-settings └── ionicSettingsSpec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower" 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._* 2 | .~lock.* 3 | .buildpath 4 | .DS_Store 5 | .idea 6 | .project 7 | .settings 8 | 9 | # Ignore node stuff 10 | node_modules/ 11 | npm-debug.log 12 | libpeerconnection.log 13 | 14 | # OS-specific 15 | .DS_Store 16 | 17 | # Bower components 18 | bower 19 | /nbproject/private/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": false, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false, 59 | "$": false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: ["0.10"] 3 | before_script: 4 | - npm install -g bower 5 | - bower install 6 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angularjs-library": { 3 | "props": { 4 | "author": { 5 | "name": "Ivan Weber", 6 | "email": "ivan.weber@gmx.de" 7 | }, 8 | "libraryName": { 9 | "original": "ionic-settings", 10 | "camelized": "ionicSettings", 11 | "dasherized": "ionic-settings", 12 | "slugified": "ionic-settings", 13 | "parts": [ 14 | "ionic", 15 | "settings" 16 | ] 17 | }, 18 | "includeModuleDirectives": false, 19 | "includeModuleFilters": false, 20 | "includeModuleServices": false, 21 | "includeAngularModuleResource": false, 22 | "includeAngularModuleCookies": false, 23 | "includeAngularModuleSanitize": false, 24 | "librarySrcDirectory": "src/ionic-settings", 25 | "libraryUnitTestDirectory": "test/unit/ionic-settings", 26 | "libraryUnitE2eDirectory": "test/e2e/ionic-settings" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.0.2 (2016-05-15) 2 | 3 | #### Bug fixes 4 | 5 | * **selection** fixed issue with multiple selection components 6 | 7 | ### 1.0.1 (2016-04-29) 8 | 9 | #### Changes 10 | 11 | * **initializing:** simplified the initializing process 12 | * **pin:** improved pin view appearance and usability 13 | * **setting types:** added range and input setting types 14 | 15 | ### 1.0.0 (2016-04-25) 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ivan Weber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ionic-settings 2 | 3 | * [Introduction](#1-introduction) 4 | * [Usage](#2-usage) 5 | * [Configuration provider](#3-configuration-provider) 6 | * [Services](#4-services) 7 | * [Directives](#5-directives) 8 | 9 | ##1. Introduction 10 | This plugin provides settings and lock screen for your ionic app. The keys and values are stored as app preferences on condition that you have installed 11 | [the app preferences plugin](https://github.com/apla/me.apla.cordova.app-preferences), otherwise they are stored in `localStorage`. 12 | You can test the plugin via the [ionic view app](http://view.ionic.io/) with the ID **141b234c**. 13 | 14 | ##1.1 Features 15 | * 5 setting types: `selection`, `toggle`, `input`, `range` and `pin` 16 | * 2 read-only types: `button` and `text` 17 | * grouping of settings 18 | * including settings into a view or modal view 19 | * customizing of settings appearance (color, icons and button positions) 20 | * storage as app preferences using the above mentioned plugin, otherwise in `localStorage` 21 | * touch id support if the [touch id plugin](https://github.com/leecrossley/cordova-plugin-touchid) is installed 22 | 23 | ##1.2 Screenshots 24 | 25 | 26 | ##1.3 Demo 27 | ##### Note: To test the lock screen feature respectively settings persistence please reload the demo after value changing. 28 | #### [Ionic Playground](http://play.ionic.io/app/28ea71301b60) 29 | #### ![animation](https://dl.dropbox.com/s/mc15ng39ts5l0jk/demo1.gif) 30 | 31 | ##1.4 License 32 | 33 | [MIT](https://github.com/ivandroid/ionic-settings/blob/master/LICENSE) 34 | 35 | ##1.5 Versions 36 | 37 | [CHANGELOG](https://github.com/ivandroid/ionic-settings/blob/master/CHANGELOG.md) 38 | 39 | ###1.6 Author 40 | * E-Mail: ivan.weber@gmx.de 41 | * Twitter: https://twitter.com/hybrid_app 42 | * Github: https://github.com/ivandroid 43 | * Ionic Market: https://market.ionic.io/user/6540 44 | * Your support: If you find this project useful and want support its development you can press the button below or star the project. Thanks in advance! 45 | 46 | [![](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ivan%2eweber%40gmx%2ede&lc=DE&item_name=GithubRepositories&no_note=0¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest) 47 | 48 | ##2. Usage 49 | 1. Get the files from here or install from bower: 50 | 51 | ``` 52 | bower install ionic-settings 53 | ``` 54 | 55 | 2. Include the javascript and css files or the minified versions into your `index.html` file. 56 | 57 | ```html 58 | 59 | 60 | ``` 61 | 62 | 3. Add the module `ionicSettings` to your application dependencies: 63 | 64 | ```javascript 65 | angular.module('starter', ['ionic', 'ionicSettings']) 66 | ``` 67 | 68 | 4. Settings are defined according to the following pattern: 69 | 70 | ```javascript 71 | var settings = { 72 | label1: 'Group 1', // OPTIONAL GROUP LABEL 73 | mySelection: { // KEY 74 | type: 'selection', // TYPE 75 | values: ['value 1', 'value 2', 'value 3', 'value 4', 'value 5'], // IN THIS CASE: SELECTION ARRAY 76 | label: 'Selection', // LABEL 77 | value: 'value 1', // VALUE 78 | icon: 'ion-checkmark-round' // OPTIONAL ICON 79 | }, 80 | myToggle: { 81 | type: 'toggle', 82 | label: 'Toggle', 83 | value: true, 84 | icon: 'ion-toggle' 85 | }, 86 | myInput: { 87 | type: 'input', 88 | label: 'Input', 89 | inputType: 'text', 90 | value: 'Hello World!', 91 | icon: 'ion-edit' 92 | }, 93 | myRange: { 94 | type: 'range', 95 | label: 'Range', 96 | iconLeft: 'ion-minus-circled', 97 | iconRight: 'ion-plus-circled', 98 | min: 0, 99 | max: 100, 100 | value: 50 101 | }, 102 | label2: 'Group 2', 103 | myButton: { 104 | type: 'button', 105 | label: 'Button', 106 | icon: 'ion-disc', 107 | onClick: function() { 108 | alert('Hello world!'); 109 | } 110 | }, 111 | myText: { 112 | type: 'text', 113 | label: 'Text', 114 | icon: 'ion-document-text', 115 | value: '

Hello World!

' 116 | }, 117 | myPin: { 118 | type: 'pin', 119 | label: 'PIN & Touch ID', 120 | value: '', 121 | icon: 'ion-locked', 122 | onValid: function() { // OPTIONAL ACTION ON VALID PIN 123 | alert('Success!'); 124 | }, 125 | onInvalid: function() { // OPTIONAL ACTION ON INVALID PIN 126 | alert('Fail!'); 127 | } 128 | } 129 | }; 130 | ``` 131 | 132 | 5. To initialize the settings invoke the `init()` method of the `$ionicSettings` service (returns promise) passing your settings model object. If you'd like to protect your app with a pin / touch id, make sure to initialize your settings before the main state of your app is loaded like shown below. 133 | 134 | ```javascript 135 | // INITIALIZATION IN CONFIG PHASE (USING PIN) 136 | angular.module('starter', ['ionic', 'ionicSettings']) 137 | .config(function($stateProvider, $urlRouterProvider) { 138 | $stateProvider 139 | .state('main', { 140 | url: '/main', 141 | abstract: true, 142 | templateUrl: 'templates/main.html', 143 | resolve: { 144 | settings: function($ionicSettings, $ionicPopup) { 145 | var settings = { 146 | toggle1: { 147 | type: 'toggle', 148 | label: 'Toggle 1', 149 | value: true 150 | }, 151 | toggle2: { 152 | type: 'toggle', 153 | label: 'Toggle 2', 154 | value: false 155 | }, 156 | pin: { 157 | type: 'pin', 158 | label: 'PIN', 159 | value: '', 160 | onValid: function() { 161 | $ionicPopup.alert({ 162 | title: 'Success', 163 | template: 'Welcome!' 164 | }); 165 | }, 166 | onInvalid: function($event, wrongPinValue) { 167 | $ionicPopup.alert({ 168 | title: 'Fail', 169 | template: 'Wrong pin: ' + wrongPinValue + '! Try again.' 170 | }); 171 | } 172 | } 173 | }; 174 | return $ionicSettings.init(settings); 175 | } 176 | } 177 | }) 178 | }); 179 | // INITIALIZATION IN CONTROLLER (WITHOUT PIN) 180 | angular.module('starter.controllers', []) 181 | .controller('YourCtrl', function($scope, $ionicSettings) { 182 | $ionicSettings.init({ 183 | awesomeSelection: { 184 | type: 'selection', 185 | values: ['one', 'two', 'three'], 186 | label: 'Awesome Selection', 187 | value: 'two' 188 | }, 189 | coolToggle: { 190 | type: 'toggle', 191 | label: 'Cool toggle', 192 | value: true 193 | } 194 | }); 195 | }); 196 | ``` 197 | 198 | 6. To include your settings into a view simply use the `ion-settings` directive within the `ion-content` element. To show the settings as modal add the directive `ion-settings-button` to the navigation bar. Pressing this button opens the modal. 199 | 200 | ```html 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | ``` 214 | 215 | ##3. Configuration provider 216 | 217 | Via the `$ionicSettingsConfigProvider` the following options can be set in the configuration phase: 218 | 219 | option|description|type|accepted values|default value 220 | ---|---|---|---|--- 221 | color|color of setting elements|string|ionic color names|*positive* 222 | icon|settings icon|string|ion-icons|*ion-android-settings* for Android and *ion-ios-gear* for iOS 223 | iconClose|close button icon|string|ion-icons|*ion-android-close* for Android and *ion-ios-close-empty* for iOS 224 | iconClosePosition|close button icon position|string|*right, left*|*right* 225 | modalAnimation|modal animation|string|ionic modal animation identifiers|custom animation for Android (ionic-settings.scss) and *slide-in-up* for iOS 226 | title|settings title|string|text|*Settings* 227 | touchID|touch id support|boolean|*true, false*|*true* 228 | 229 | #### Example 230 | ##### Code 231 | 232 | ```javascript 233 | angular.module('starter', ['ionic', 'ionicSettings']) 234 | .config(function($ionicSettingsConfigProvider) { 235 | $ionicSettingsConfigProvider.setColor('assertive'); 236 | $ionicSettingsConfigProvider.setIcon('ion-wrench'); 237 | $ionicSettingsConfigProvider.setIconClose('ion-close-circled'); 238 | $ionicSettingsConfigProvider.setIconClosePosition('left'); 239 | $ionicSettingsConfigProvider.setModalAnimation('slide-in-up'); 240 | $ionicSettingsConfigProvider.setTitle('My awesome settings'); 241 | $ionicSettingsConfigProvider.setTouchID(false); 242 | }); 243 | ``` 244 | 245 | ##### Result 246 | 247 | 248 | ##4. Services 249 | ### Service `$ionicSettings` 250 | 251 | Using this service you have access to the following events and methods: 252 | 253 | event|description|return-value 254 | ---|---|--- 255 | `changed`|Setting changed event|object containing key and value of a changed setting 256 | 257 | method|description|return-value 258 | ---|---|--- 259 | `get(key)`|Getting a value by key|value of a given key 260 | `getData()`|Getting all settings keys and values|object containing all key value pairs 261 | `init(modelObject)`|Initializing of settings passing your settings model object|initialized settings model object as promise 262 | `set(key, value)`|Setting a value by key|none 263 | 264 | #### Example 265 | 266 | ```javascript 267 | angular.module('starter.controllers', []) 268 | .controller('YourCtrl', function($rootScope, $ionicSettings) { 269 | $rootScope.$on($ionicSettings.changed, function($event, changedSetting) { 270 | alert(changedSetting.key + ' -> ' + changedSetting.value); 271 | }); 272 | }); 273 | ``` 274 | 275 | ```javascript 276 | angular.module('starter.controllers', []) 277 | .controller('YourCtrl', function($scope, $ionicSettings) { 278 | $scope.set = function(key, value) { 279 | $ionicSettings.set(key, value); 280 | }; 281 | $scope.get = function(key) { 282 | alert($ionicSettings.get(key)); 283 | }; 284 | }); 285 | ``` 286 | 287 | ##5. Directives 288 | 289 | ### Directive `ion-settings` 290 | 291 | Use this directive to include your settings into a view. 292 | 293 | #### Example 294 | 295 | ```html 296 | 297 | 298 | 299 | 300 | 301 | ``` 302 | 303 | ### Directive `ion-settings-button` 304 | 305 | Use this directive to include the settings button to the navigation bar and use settings as modal. 306 | 307 | #### Example 308 | 309 | ```html 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | ``` 318 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-settings", 3 | "homepage": "https://github.com/ivandroid/ionic-settings", 4 | "version": "1.0.2", 5 | "authors": [ 6 | { 7 | "name": "Ivan Weber", 8 | "email": "ivan.weber@gmx.de" 9 | } 10 | ], 11 | "license": "MIT", 12 | "main": [ 13 | "dist/ionic-settings.min.js", 14 | "dist/ionic-settings.min.css" 15 | ], 16 | "ignore": [ 17 | "src", 18 | "test", 19 | "gulpfile.js", 20 | "karma-*.conf.js", 21 | "**/.*" 22 | ], 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "angular-mocks": ">=1.2.0", 26 | "angular-scenario": ">=1.2.0", 27 | "angular": ">=1.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dist/ionic-settings.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2016 Ivan Weber 3 | * 4 | * ionic-settings, v1.0.2 5 | * 6 | * Licensed under the MIT license. Please see LICENSE for more information. 7 | * 8 | */ 9 | .ionic-settings-numbers > div { 10 | margin-top: 20px !important; } 11 | 12 | .ionic-settings-numbers-button { 13 | border-radius: 50% !important; 14 | height: 70px !important; 15 | width: 72px !important; 16 | font-size: 20px !important; 17 | margin: 10px !important; } 18 | 19 | .ionic-settings-numbers-dot { 20 | font-size: 27px; 21 | margin: 10px; } 22 | 23 | .ionic-settings-numbers-row { 24 | display: flex; 25 | flex-direction: row; 26 | justify-content: center; 27 | width: 100%; 28 | height: 60px; 29 | margin-bottom: 20px; } 30 | 31 | .ionic-settings-numbers-value { 32 | font-size: 38px; 33 | margin: 10px; } 34 | 35 | .ionic-settings-icon-smaller:before { 36 | font-size: 27px; } 37 | 38 | .ionic-settings-icon-larger:before { 39 | font-size: 30px !important; 40 | margin-bottom: 27px !important; } 41 | 42 | .ionic-settings-left { 43 | float: left; } 44 | 45 | .ionic-settings-right { 46 | float: right; } 47 | 48 | .ionic-settings-android { 49 | font-size: 14px; 50 | color: #696969; 51 | margin-top: 25px; } 52 | 53 | .ionic-settings-ios { 54 | color: gray; } 55 | 56 | .animate-ng-show.ng-hide-add { 57 | animation: 0.5s zoomOut ease; 58 | -webkit-animation: 0.5s zoomOut ease; } 59 | 60 | .animate-ng-show.ng-hide-remove { 61 | animation: 0.5s zoomIn ease; 62 | -webkit-animation: 0.5s zoomIn ease; } 63 | 64 | .ionic-settings-animate-modal-android { 65 | -webkit-transform: scale(0); 66 | transform: scale(0); 67 | opacity: 0; } 68 | 69 | .ionic-settings-animate-modal-android.ng-enter, .fade-in > .ng-enter { 70 | -webkit-transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 250ms; 71 | transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 250ms; } 72 | 73 | .ionic-settings-animate-modal-android.ng-enter-active, 74 | .ionic-settings-animate-modal-android > .ng-enter-active { 75 | -webkit-transform: scale(1); 76 | transform: scale(1); 77 | opacity: 1; } 78 | 79 | .ionic-settings-animate-modal-android.ng-leave, .fade-in > .ng-leave { 80 | -webkit-transition: all ease-in-out 250ms; 81 | transition: all ease-in-out 250ms; } 82 | 83 | @-webkit-keyframes zoomIn { 84 | from { 85 | opacity: 0; 86 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 87 | transform: scale3d(0.3, 0.3, 0.3); } 88 | 50% { 89 | opacity: 1; } } 90 | 91 | @keyframes zoomIn { 92 | from { 93 | opacity: 0; 94 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 95 | transform: scale3d(0.3, 0.3, 0.3); } 96 | 50% { 97 | opacity: 1; } } 98 | 99 | .zoomIn { 100 | -webkit-animation-name: zoomIn; 101 | animation-name: zoomIn; } 102 | 103 | @-webkit-keyframes zoomOut { 104 | from { 105 | opacity: 1; } 106 | 50% { 107 | opacity: 0; 108 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 109 | transform: scale3d(0.3, 0.3, 0.3); } 110 | to { 111 | opacity: 0; } } 112 | 113 | @keyframes zoomOut { 114 | from { 115 | opacity: 1; } 116 | 50% { 117 | opacity: 0; 118 | -webkit-transform: scale3d(0.3, 0.3, 0.3); 119 | transform: scale3d(0.3, 0.3, 0.3); } 120 | to { 121 | opacity: 0; } } 122 | 123 | .zoomOut { 124 | -webkit-animation-name: zoomOut; 125 | animation-name: zoomOut; } 126 | -------------------------------------------------------------------------------- /dist/ionic-settings.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2016 Ivan Weber 3 | * 4 | * ionic-settings, v1.0.2 5 | * 6 | * Licensed under the MIT license. Please see LICENSE for more information. 7 | * 8 | */ 9 | 10 | /* global ionic, touchid */ 11 | 12 | (function(angular) { 13 | 14 | angular.module("ionicSettings.constants", []) 15 | .constant("IONIC_SETTINGS_BUTTON", "button") 16 | .constant("IONIC_SETTINGS_PIN", "pin") 17 | .constant("IONIC_SETTINGS_PIN_EMPTY", "\u25CB") 18 | .constant("IONIC_SETTINGS_PIN_FILLED", "\u25CF") 19 | .constant("IONIC_SETTINGS_SELECTION", "selection") 20 | .constant("IONIC_SETTINGS_TEXT", "text"); 21 | 22 | angular.module("ionicSettings.directives", []) 23 | .directive("ionSettings", [ 24 | "$ionicSettings", 25 | "$ionicSettingsConfig", 26 | "IONIC_SETTINGS_PIN", function( 27 | $ionicSettings, 28 | $ionicSettingsConfig, 29 | IONIC_SETTINGS_PIN) { 30 | return { 31 | restrict: "E", 32 | scope: true, 33 | template: '
' + 34 | '
{{item}}
' + 35 | '
' + 36 | '' + 40 | '
{{item.label}}
' + 41 | '
{{item.value}}
' + 42 | '
{{item.value}}
' + 43 | '' + 44 | '' + 45 | '
' + 46 | '' + 50 | '{{item.label}}' + 51 | '' + 52 | '' + 53 | '' + 59 | '{{item.label}}' + 60 | '' + 61 | '' + 62 | '
' + 63 | '' + 64 | '' + 68 | '
' + 69 | '' + 73 | '' + 81 | '
' + 82 | '
', 83 | link: function($scope) { 84 | var active = {}; 85 | var modals = {}; 86 | 87 | $scope.data = $ionicSettings.getData(); 88 | $scope.isAndroid = $ionicSettingsConfig.isAndroid; 89 | $scope.iconClose = $ionicSettingsConfig.iconClose; 90 | $scope.iconClosePosition = $ionicSettingsConfig.iconClosePosition; 91 | $scope.iconDone = $ionicSettingsConfig.iconDone; 92 | $scope.iconSelected = $ionicSettingsConfig.isAndroid ? "ion-android-done" : "ion-ios-checkmark-empty"; 93 | $scope.pin = { 94 | active: false 95 | }; 96 | $scope.showing = false; 97 | $scope.color = $ionicSettingsConfig.color; 98 | $scope.elementColor = $scope.color === "stable" || $scope.color === "default" || $scope.color === "light" ? "dark" : $scope.color; 99 | $scope.barColor = "bar-" + $scope.color; 100 | 101 | angular.forEach($scope.data, function(item, key) { 102 | modals[key] = $ionicSettings.createModal($scope, key); 103 | 104 | $scope.$watch("data." + key + ".value", function(newValue, oldValue) { 105 | if (angular.isDefined(newValue) && newValue !== oldValue) { 106 | $ionicSettings.store(key, newValue).then(function() { 107 | $scope.$root.$broadcast($ionicSettings.changed, { 108 | key: key, 109 | value: newValue 110 | }); 111 | }, function() { 112 | $scope.data[key].value = oldValue; 113 | }); 114 | } 115 | }); 116 | }); 117 | 118 | $scope.addValue = function(value) { 119 | if ($scope.value.length === 4) { 120 | $scope.value = ""; 121 | } 122 | $scope.value += value; 123 | }; 124 | 125 | $scope.clear = function() { 126 | $scope.value = ""; 127 | }; 128 | 129 | $scope.dismiss = function() { 130 | if (active.item.type === IONIC_SETTINGS_PIN && active.item.value.length !== 4) { 131 | $scope.clear(); 132 | $scope.pin.active = false; 133 | } 134 | if (active.modal.isShown()) { 135 | active.modal.hide(); 136 | } 137 | }; 138 | 139 | $scope.hidePin = function() { 140 | $scope.showing = false; 141 | }; 142 | 143 | $scope.doAction = function(key) { 144 | active.modal = modals[key]; 145 | active.item = $scope.data[key]; 146 | if (active.modal) { 147 | if (active.item.type === IONIC_SETTINGS_PIN) { 148 | if (active.item.value.length === 4) { 149 | active.item.value = ""; 150 | } else { 151 | $scope.value = active.item.value; 152 | active.modal.show(); 153 | } 154 | } else { 155 | active.modal.show(); 156 | } 157 | } else { 158 | if (angular.isFunction(active.item.onClick)) { 159 | active.item.onClick(); 160 | } 161 | } 162 | }; 163 | 164 | $scope.savePin = function() { 165 | active.item.value = $scope.value; 166 | active.modal.hide(); 167 | }; 168 | 169 | $scope.select = function(value) { 170 | active.item.value = value; 171 | $scope.dismiss(); 172 | }; 173 | 174 | $scope.showPin = function() { 175 | $scope.showing = true; 176 | }; 177 | 178 | $scope.$on("modal.hidden", $scope.dismiss); 179 | 180 | $scope.$on("$destroy", function() { 181 | for (var key in modals) { 182 | if (modals[key]) { 183 | modals[key].remove(); 184 | } 185 | } 186 | }); 187 | } 188 | }; 189 | }]) 190 | .directive("ionSettingsButton", [ 191 | "$ionicSettingsConfig", 192 | "$ionicSettings", function( 193 | $ionicSettingsConfig, 194 | $ionicSettings) { 195 | return { 196 | template: '', 197 | $scope: true, 198 | link: function($scope) { 199 | $scope.barColor = "bar-" + $ionicSettingsConfig.color; 200 | $scope.icon = $ionicSettingsConfig.icon; 201 | $scope.iconClose = $ionicSettingsConfig.iconClose; 202 | $scope.iconClosePosition = $ionicSettingsConfig.iconClosePosition; 203 | $scope.settingsModal = $ionicSettings.createModal($scope); 204 | $scope.title = $ionicSettingsConfig.title; 205 | 206 | $scope.dismiss = function() { 207 | $scope.settingsModal.hide(); 208 | }; 209 | } 210 | }; 211 | }]) 212 | .directive("ngTouchstart", function () { 213 | return { 214 | controller: ["$scope", "$element", function ($scope, $element) { 215 | $element.bind("touchstart", onTouchStart); 216 | function onTouchStart(event) { 217 | var method = $element.attr("ng-touchstart"); 218 | $scope.$event = event; 219 | $scope.$apply(method); 220 | } 221 | 222 | }] 223 | }; 224 | }) 225 | .directive("ngTouchend", function () { 226 | return { 227 | controller: ["$scope", "$element", function ($scope, $element) { 228 | $element.bind("touchend", onTouchEnd); 229 | function onTouchEnd(event) { 230 | var method = $element.attr("ng-touchend"); 231 | $scope.$event = event; 232 | $scope.$apply(method); 233 | } 234 | 235 | }] 236 | }; 237 | }); 238 | 239 | angular.module("ionicSettings.providers", []).provider("$ionicSettingsConfig", function() { 240 | var android = ionic.Platform.isAndroid(); 241 | var ios = ionic.Platform.isIOS(); 242 | var color = "positive"; 243 | var icon = android ? "ion-android-settings" : "ion-ios-gear"; 244 | var iconClose = android ? "ion-android-close" : "ion-ios-close-empty"; 245 | var iconClosePosition = "right"; 246 | var iconDone = android ? "ion-android-done" : "ion-ios-checkmark-empty"; 247 | var modalAnimation = ios ? "slide-in-up" : "ionic-settings-animate-modal-android"; 248 | var title = "Settings"; 249 | var touchID = true; 250 | return { 251 | setAndroid: function(_android) { 252 | android = _android; 253 | }, 254 | setIcon: function(_icon) { 255 | icon = _icon; 256 | }, 257 | setIconClose: function(_iconClose) { 258 | iconClose = _iconClose; 259 | }, 260 | setIconClosePosition: function(_iconClosePosition) { 261 | iconClosePosition = _iconClosePosition; 262 | }, 263 | setModalAnimation: function(_modalAnimation) { 264 | modalAnimation = _modalAnimation; 265 | }, 266 | setColor: function(_color) { 267 | color = _color; 268 | }, 269 | setTitle: function(_title) { 270 | title = _title; 271 | }, 272 | setTouchID: function(_touchID) { 273 | touchID = _touchID; 274 | }, 275 | $get: function() { 276 | return { 277 | color: color, 278 | icon: icon, 279 | iconClose: iconClose, 280 | iconClosePosition: iconClosePosition, 281 | iconDone: iconDone, 282 | isAndroid: android, 283 | modalAnimation: modalAnimation, 284 | title: title, 285 | touchID: touchID 286 | }; 287 | } 288 | }; 289 | }); 290 | 291 | angular.module("ionicSettings.services", []) 292 | .factory("$ionicSettings", [ 293 | "$ionicSettingsConfig", 294 | "$ionicModal", 295 | "$log", 296 | "$q", 297 | "$rootScope", 298 | "$window", 299 | "IONIC_SETTINGS_PIN", 300 | "IONIC_SETTINGS_SELECTION", 301 | "IONIC_SETTINGS_TEXT", function( 302 | $ionicSettingsConfig, 303 | $ionicModal, 304 | $log, 305 | $q, 306 | $rootScope, 307 | $window, 308 | IONIC_SETTINGS_PIN, 309 | IONIC_SETTINGS_SELECTION, 310 | IONIC_SETTINGS_TEXT) { 311 | var self = this; 312 | var data = {}; 313 | var pin; 314 | var $cordovaSplashscreenOn = true; 315 | 316 | self.changed = "$ionicSettings.changed"; 317 | self.onInvalidPin = "$ionicSettings.onInvalidPin"; 318 | self.onValidPin = "$ionicSettings.onValidPin"; 319 | 320 | self.createModal = function($scope, key) { 321 | var html; 322 | if (key) { 323 | var item = data[key]; 324 | switch(item.type) { 325 | case IONIC_SETTINGS_SELECTION: 326 | html = 327 | '' + 328 | '' + 329 | '' + 330 | '

' + item.label + '

' + 331 | '' + 332 | '
' + 333 | '' + 334 | '' + 335 | '{{value}}' + 336 | '' + 337 | '' + 338 | '' + 339 | '
'; 340 | break; 341 | case IONIC_SETTINGS_PIN: 342 | html = 343 | '' + 344 | '' + 345 | '
' + 346 | '' + 347 | '' + 348 | '
' + 349 | '

' + item.label + '

' + 350 | '
' + 351 | '' + 352 | '' + 353 | '
' + 354 | '
' + 355 | '' + 356 | '
' + 357 | '
' + 358 | '' + 359 | '{{value[$index]}}' + 360 | '
' + 361 | '
' + 362 | '' + 367 | '
' + 368 | '
' + 369 | '' + 370 | '' + 371 | '' + 379 | '
' + 380 | '
' + 381 | '
' + 382 | '
'; 383 | break; 384 | case IONIC_SETTINGS_TEXT: 385 | html = 386 | '' + 387 | '' + 388 | '' + 389 | '

' + item.label + '

' + 390 | '' + 391 | '
' + 392 | '' + item.value + '' + 393 | '
'; 394 | angular.extend($scope, data[key].scopeObject); 395 | break; 396 | } 397 | } else { 398 | html = '' + 399 | '' + 400 | '' + 401 | '

{{title}}

' + 402 | '' + 403 | '
' + 404 | '' + 405 | '' + 406 | '' + 407 | '
'; 408 | } 409 | if (html) { 410 | return $ionicModal.fromTemplate(html, { 411 | scope: $scope, 412 | animation: $ionicSettingsConfig.modalAnimation, 413 | backdropClickToClose: false 414 | }); 415 | } 416 | }; 417 | 418 | self.fetch = function(key) { 419 | var q = $q.defer(); 420 | if ($window.plugins) { 421 | $window.plugins.appPreferences.fetch(key).then(function(value) { 422 | if (value === null) { 423 | q.reject(value); 424 | } else { 425 | q.resolve({key: key, value: value}); 426 | } 427 | }, function(error) { 428 | q.reject(new Error(error)); 429 | }); 430 | } else { 431 | var value = localStorage[key]; 432 | if (value === undefined) { 433 | q.reject(value); 434 | } else { 435 | if (value === "true" || value === "false") { 436 | value = value === "true"; 437 | } 438 | q.resolve({key: key, value: value}); 439 | } 440 | } 441 | return q.promise; 442 | }; 443 | 444 | self.fetchAll = function() { 445 | var fetching = []; 446 | for (var key in data) { 447 | var item = data[key]; 448 | if (angular.isObject(item)) { 449 | fetching.push(self.fetch(key)); 450 | } 451 | } 452 | return $q.all(fetching).then(function(result) { 453 | for (var i in result) { 454 | var resultItem = result[i]; 455 | var key = resultItem.key; 456 | var value = resultItem.value; 457 | 458 | var item = data[key]; 459 | item.value = value; 460 | if (item.type === IONIC_SETTINGS_PIN) { 461 | pin = { 462 | key: key, 463 | value: value 464 | }; 465 | } 466 | } 467 | return data; 468 | }); 469 | }; 470 | 471 | self.get = function(key) { 472 | if (data[key]) { 473 | return data[key].value; 474 | } else { 475 | $log.error("Setting key " + key + " is not defined."); 476 | return; 477 | } 478 | }; 479 | 480 | self.getData = function() { 481 | return data; 482 | }; 483 | 484 | self.init = function(_data) { 485 | data = _data; 486 | 487 | var q = $q.defer(); 488 | 489 | self.fetchAll().then(function(result) { 490 | if (pin && pin.value.length === 4) { 491 | var $scope = $rootScope.$new(); 492 | var pinModal = self.createModal($scope, pin.key); 493 | 494 | $scope.color = $ionicSettingsConfig.color; 495 | $scope.barColor = "bar-" + $scope.color; 496 | $scope.start = true; 497 | $scope.value = ""; 498 | $scope.valid = false; 499 | 500 | var pinItem = data[pin.key]; 501 | if (pinItem.onValid) { 502 | $scope.$on(self.onValidPin, pinItem.onValid); 503 | } 504 | if (pinItem.onInvalid) { 505 | $scope.$on(self.onInvalidPin, pinItem.onInvalid); 506 | } 507 | 508 | $scope.addValue = function(value) { 509 | $scope.value += value; 510 | if ($scope.value.length === 4) { 511 | $scope.valid = $scope.value === pin.value; 512 | if ($scope.valid) { 513 | pinModal.remove().then(function() { 514 | q.resolve(result); 515 | $rootScope.$broadcast(self.onValidPin); 516 | if ($cordovaSplashscreenOn) { 517 | try { 518 | navigator.splashscreen.show(); 519 | } catch(e) {} 520 | } 521 | }); 522 | } else { 523 | $rootScope.$broadcast(self.onInvalidPin, $scope.value); 524 | $scope.clear(); 525 | } 526 | } 527 | if ($scope.value.length > 4) { 528 | $scope.clear(); 529 | $scope.value += value; 530 | } 531 | }; 532 | 533 | $scope.clear = function() { 534 | $scope.value = ""; 535 | }; 536 | 537 | pinModal.show().then(function() { 538 | try { 539 | navigator.splashscreen.hide(); 540 | } catch(e) { 541 | $cordovaSplashscreenOn = false; 542 | } 543 | if ($ionicSettingsConfig.touchID) { 544 | try { 545 | touchid.checkSupport(function() { 546 | touchid.authenticate(function() { 547 | q.resolve(result); 548 | }, function() { 549 | }, "Touch ID"); 550 | }); 551 | } catch(e) {} 552 | } 553 | }); 554 | } else { 555 | q.resolve(result); 556 | } 557 | }, function() { 558 | self.storeAll(_data).then(function(result) { 559 | q.resolve(result); 560 | }); 561 | }); 562 | 563 | return q.promise; 564 | }; 565 | 566 | self.set = function(key, value) { 567 | data[key].value = value; 568 | return self.store(key, value); 569 | }; 570 | 571 | self.store = function(key, value) { 572 | var q = $q.defer(); 573 | if ($window.plugins) { 574 | $window.plugins.appPreferences.store(key, value).then(function(value) { 575 | q.resolve(value); 576 | }, function(error) { 577 | q.reject(new Error(error)); 578 | }); 579 | } else { 580 | localStorage[key] = value; 581 | q.resolve(localStorage[key]); 582 | } 583 | return q.promise; 584 | }; 585 | 586 | self.setSelectionValues = function(key, values) { 587 | angular.copy(values, data[key].values); 588 | return self.set(key, ""); 589 | }; 590 | 591 | self.storeAll = function() { 592 | var storing = []; 593 | for (var key in data) { 594 | var item = data[key]; 595 | if (angular.isObject(item)) { 596 | storing.push(self.store(key, item.value)); 597 | } 598 | } 599 | return $q.all(storing); 600 | }; 601 | 602 | return self; 603 | }]); 604 | 605 | // App 606 | angular.module("ionicSettings", [ 607 | "ionicSettings.constants", 608 | "ionicSettings.directives", 609 | "ionicSettings.providers", 610 | "ionicSettings.services" 611 | ]); 612 | })(angular); 613 | -------------------------------------------------------------------------------- /dist/ionic-settings.min.css: -------------------------------------------------------------------------------- 1 | .ionic-settings-numbers>div{margin-top:20px!important}.ionic-settings-numbers-button{border-radius:50%!important;height:70px!important;width:72px!important;font-size:20px!important;margin:10px!important}.ionic-settings-numbers-dot{font-size:27px;margin:10px}.ionic-settings-numbers-row{display:flex;flex-direction:row;justify-content:center;width:100%;height:60px;margin-bottom:20px}.ionic-settings-numbers-value{font-size:38px;margin:10px}.ionic-settings-icon-smaller:before{font-size:27px}.ionic-settings-icon-larger:before{font-size:30px!important;margin-bottom:27px!important}.ionic-settings-left{float:left}.ionic-settings-right{float:right}.ionic-settings-android{font-size:14px;color:#696969;margin-top:25px}.ionic-settings-ios{color:gray}.animate-ng-show.ng-hide-add{animation:.5s zoomOut ease;-webkit-animation:.5s zoomOut ease}.animate-ng-show.ng-hide-remove{animation:.5s zoomIn ease;-webkit-animation:.5s zoomIn ease}.ionic-settings-animate-modal-android{-webkit-transform:scale(0);transform:scale(0);opacity:0}.fade-in>.ng-enter,.ionic-settings-animate-modal-android.ng-enter{-webkit-transition:all cubic-bezier(.1,.7,.1,1) 250ms;transition:all cubic-bezier(.1,.7,.1,1) 250ms}.ionic-settings-animate-modal-android.ng-enter-active,.ionic-settings-animate-modal-android>.ng-enter-active{-webkit-transform:scale(1);transform:scale(1);opacity:1}.fade-in>.ng-leave,.ionic-settings-animate-modal-android.ng-leave{-webkit-transition:all ease-in-out 250ms;transition:all ease-in-out 250ms}@-webkit-keyframes zoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes zoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut} -------------------------------------------------------------------------------- /dist/ionic-settings.min.js: -------------------------------------------------------------------------------- 1 | !function(n){n.module("ionicSettings.constants",[]).constant("IONIC_SETTINGS_BUTTON","button").constant("IONIC_SETTINGS_PIN","pin").constant("IONIC_SETTINGS_PIN_EMPTY","○").constant("IONIC_SETTINGS_PIN_FILLED","●").constant("IONIC_SETTINGS_SELECTION","selection").constant("IONIC_SETTINGS_TEXT","text"),n.module("ionicSettings.directives",[]).directive("ionSettings",["$ionicSettings","$ionicSettingsConfig","IONIC_SETTINGS_PIN",function(i,t,e){return{restrict:"E",scope:!0,template:'
{{item}}
{{item.label}}
{{item.value}}
{{item.value}}
{{item.label}}{{item.label}}
',link:function(o){var s={},l={};o.data=i.getData(),o.isAndroid=t.isAndroid,o.iconClose=t.iconClose,o.iconClosePosition=t.iconClosePosition,o.iconDone=t.iconDone,o.iconSelected=t.isAndroid?"ion-android-done":"ion-ios-checkmark-empty",o.pin={active:!1},o.showing=!1,o.color=t.color,o.elementColor="stable"===o.color||"default"===o.color||"light"===o.color?"dark":o.color,o.barColor="bar-"+o.color,n.forEach(o.data,function(t,e){l[e]=i.createModal(o,e),o.$watch("data."+e+".value",function(t,s){n.isDefined(t)&&t!==s&&i.store(e,t).then(function(){o.$root.$broadcast(i.changed,{key:e,value:t})},function(){o.data[e].value=s})})}),o.addValue=function(n){4===o.value.length&&(o.value=""),o.value+=n},o.clear=function(){o.value=""},o.dismiss=function(){s.item.type===e&&4!==s.item.value.length&&(o.clear(),o.pin.active=!1),s.modal.isShown()&&s.modal.hide()},o.hidePin=function(){o.showing=!1},o.doAction=function(i){s.modal=l[i],s.item=o.data[i],s.modal?s.item.type===e?4===s.item.value.length?s.item.value="":(o.value=s.item.value,s.modal.show()):s.modal.show():n.isFunction(s.item.onClick)&&s.item.onClick()},o.savePin=function(){s.item.value=o.value,s.modal.hide()},o.select=function(n){s.item.value=n,o.dismiss()},o.showPin=function(){o.showing=!0},o.$on("modal.hidden",o.dismiss),o.$on("$destroy",function(){for(var n in l)l[n]&&l[n].remove()})}}}]).directive("ionSettingsButton",["$ionicSettingsConfig","$ionicSettings",function(n,i){return{template:'',$scope:!0,link:function(t){t.barColor="bar-"+n.color,t.icon=n.icon,t.iconClose=n.iconClose,t.iconClosePosition=n.iconClosePosition,t.settingsModal=i.createModal(t),t.title=n.title,t.dismiss=function(){t.settingsModal.hide()}}}}]).directive("ngTouchstart",function(){return{controller:["$scope","$element",function(n,i){function t(t){var e=i.attr("ng-touchstart");n.$event=t,n.$apply(e)}i.bind("touchstart",t)}]}}).directive("ngTouchend",function(){return{controller:["$scope","$element",function(n,i){function t(t){var e=i.attr("ng-touchend");n.$event=t,n.$apply(e)}i.bind("touchend",t)}]}}),n.module("ionicSettings.providers",[]).provider("$ionicSettingsConfig",function(){var n=ionic.Platform.isAndroid(),i=ionic.Platform.isIOS(),t="positive",e=n?"ion-android-settings":"ion-ios-gear",o=n?"ion-android-close":"ion-ios-close-empty",s="right",l=n?"ion-android-done":"ion-ios-checkmark-empty",c=i?"slide-in-up":"ionic-settings-animate-modal-android",a="Settings",r=!0;return{setAndroid:function(i){n=i},setIcon:function(n){e=n},setIconClose:function(n){o=n},setIconClosePosition:function(n){s=n},setModalAnimation:function(n){c=n},setColor:function(n){t=n},setTitle:function(n){a=n},setTouchID:function(n){r=n},$get:function(){return{color:t,icon:e,iconClose:o,iconClosePosition:s,iconDone:l,isAndroid:n,modalAnimation:c,title:a,touchID:r}}}}),n.module("ionicSettings.services",[]).factory("$ionicSettings",["$ionicSettingsConfig","$ionicModal","$log","$q","$rootScope","$window","IONIC_SETTINGS_PIN","IONIC_SETTINGS_SELECTION","IONIC_SETTINGS_TEXT",function(i,t,e,o,s,l,c,a,r){var u,d=this,g={},m=!0;return d.changed="$ionicSettings.changed",d.onInvalidPin="$ionicSettings.onInvalidPin",d.onValidPin="$ionicSettings.onValidPin",d.createModal=function(e,o){var s;if(o){var l=g[o];switch(l.type){case a:s='

'+l.label+'

{{value}}
';break;case c:s='

'+l.label+'

{{value[$index]}}
';break;case r:s='

'+l.label+'

'+l.value+"
",n.extend(e,g[o].scopeObject)}}else s='

{{title}}

';return s?t.fromTemplate(s,{scope:e,animation:i.modalAnimation,backdropClickToClose:!1}):void 0},d.fetch=function(n){var i=o.defer();if(l.plugins)l.plugins.appPreferences.fetch(n).then(function(t){null===t?i.reject(t):i.resolve({key:n,value:t})},function(n){i.reject(new Error(n))});else{var t=localStorage[n];void 0===t?i.reject(t):(("true"===t||"false"===t)&&(t="true"===t),i.resolve({key:n,value:t}))}return i.promise},d.fetchAll=function(){var i=[];for(var t in g){var e=g[t];n.isObject(e)&&i.push(d.fetch(t))}return o.all(i).then(function(n){for(var i in n){var t=n[i],e=t.key,o=t.value,s=g[e];s.value=o,s.type===c&&(u={key:e,value:o})}return g})},d.get=function(n){return g[n]?g[n].value:(e.error("Setting key "+n+" is not defined."),void 0)},d.getData=function(){return g},d.init=function(n){g=n;var t=o.defer();return d.fetchAll().then(function(n){if(u&&4===u.value.length){var e=s.$new(),o=d.createModal(e,u.key);e.color=i.color,e.barColor="bar-"+e.color,e.start=!0,e.value="",e.valid=!1;var l=g[u.key];l.onValid&&e.$on(d.onValidPin,l.onValid),l.onInvalid&&e.$on(d.onInvalidPin,l.onInvalid),e.addValue=function(i){e.value+=i,4===e.value.length&&(e.valid=e.value===u.value,e.valid?o.remove().then(function(){if(t.resolve(n),s.$broadcast(d.onValidPin),m)try{navigator.splashscreen.show()}catch(i){}}):(s.$broadcast(d.onInvalidPin,e.value),e.clear())),e.value.length>4&&(e.clear(),e.value+=i)},e.clear=function(){e.value=""},o.show().then(function(){try{navigator.splashscreen.hide()}catch(e){m=!1}if(i.touchID)try{touchid.checkSupport(function(){touchid.authenticate(function(){t.resolve(n)},function(){},"Touch ID")})}catch(e){}})}else t.resolve(n)},function(){d.storeAll(n).then(function(n){t.resolve(n)})}),t.promise},d.set=function(n,i){return g[n].value=i,d.store(n,i)},d.store=function(n,i){var t=o.defer();return l.plugins?l.plugins.appPreferences.store(n,i).then(function(n){t.resolve(n)},function(n){t.reject(new Error(n))}):(localStorage[n]=i,t.resolve(localStorage[n])),t.promise},d.setSelectionValues=function(i,t){return n.copy(t,g[i].values),d.set(i,"")},d.storeAll=function(){var i=[];for(var t in g){var e=g[t];n.isObject(e)&&i.push(d.store(t,e.value))}return o.all(i)},d}]),n.module("ionicSettings",["ionicSettings.constants","ionicSettings.directives","ionicSettings.providers","ionicSettings.services"])}(angular); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var karma = require('karma').server; 3 | var concat = require('gulp-concat'); 4 | var sass = require('gulp-sass'); 5 | var minifyCss = require('gulp-minify-css'); 6 | var uglify = require('gulp-uglify'); 7 | var rename = require('gulp-rename'); 8 | var path = require('path'); 9 | var plumber = require('gulp-plumber'); 10 | var runSequence = require('run-sequence'); 11 | var jshint = require('gulp-jshint'); 12 | 13 | /** 14 | * File patterns 15 | **/ 16 | 17 | // Root directory 18 | var rootDirectory = path.resolve('./'); 19 | 20 | // Source directory for build process 21 | var sourceDirectory = path.join(rootDirectory, './src'); 22 | 23 | // tests 24 | var testDirectory = path.join(rootDirectory, './test/unit'); 25 | 26 | var sourceFiles = [ 27 | // Make sure module files are handled first 28 | path.join(sourceDirectory, '/**/*.module.js'), 29 | // Then add all JavaScript files 30 | path.join(sourceDirectory, '/**/*.js') 31 | ]; 32 | 33 | var lintFiles = [ 34 | 'gulpfile.js', 35 | // Karma configuration 36 | 'karma-*.conf.js' 37 | ].concat(sourceFiles); 38 | 39 | gulp.task('build', function() { 40 | gulp.src(sourceFiles) 41 | .pipe(plumber()) 42 | .pipe(concat('ionic-settings.js')) 43 | .pipe(gulp.dest('./dist/')) 44 | .pipe(uglify()) 45 | .pipe(rename('ionic-settings.min.js')) 46 | .pipe(gulp.dest('./dist')); 47 | }); 48 | 49 | gulp.task('sass', function(done) { 50 | gulp.src('./src/ionic-settings/ionic-settings.scss') 51 | .pipe(sass()) 52 | .on('error', sass.logError) 53 | .pipe(gulp.dest('./dist/')) 54 | .pipe(minifyCss({ 55 | keepSpecialComments: 0 56 | })) 57 | .pipe(rename({extname: '.min.css'})) 58 | .pipe(gulp.dest('./dist/')) 59 | .on('end', done); 60 | }); 61 | 62 | /** 63 | * Watch task 64 | */ 65 | gulp.task('watch', function() { 66 | 67 | // Watch JavaScript files 68 | gulp.watch(sourceFiles, ['process-all']); 69 | 70 | // watch test files and re-run unit tests when changed 71 | gulp.watch(path.join(testDirectory, '/**/*.js'), ['test-src']); 72 | }); 73 | 74 | /** 75 | * Validate source JavaScript 76 | */ 77 | gulp.task('jshint', function() { 78 | return gulp.src(lintFiles) 79 | .pipe(plumber()) 80 | .pipe(jshint()) 81 | .pipe(jshint.reporter('jshint-stylish')) 82 | .pipe(jshint.reporter('fail')); 83 | }); 84 | 85 | /** 86 | * Run test once and exit 87 | */ 88 | gulp.task('test-src', function(done) { 89 | karma.start({ 90 | configFile: __dirname + '/karma-src.conf.js', 91 | singleRun: true 92 | }, done); 93 | }); 94 | 95 | /** 96 | * Run test once and exit 97 | */ 98 | gulp.task('test-dist-concatenated', function(done) { 99 | karma.start({ 100 | configFile: __dirname + '/karma-dist-concatenated.conf.js', 101 | singleRun: true 102 | }, done); 103 | }); 104 | 105 | /** 106 | * Run test once and exit 107 | */ 108 | gulp.task('test-dist-minified', function(done) { 109 | karma.start({ 110 | configFile: __dirname + '/karma-dist-minified.conf.js', 111 | singleRun: true 112 | }, done); 113 | }); 114 | 115 | gulp.task('default', function(done) { 116 | runSequence('build', 'sass', 'test-src', done); 117 | }); 118 | -------------------------------------------------------------------------------- /karma-dist-concatenated.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'], 14 | 15 | plugins: [ 16 | 'karma-mocha', 17 | 'karma-chai', 18 | 'karma-sinon-chai', 19 | 'karma-chrome-launcher', 20 | 'karma-phantomjs-launcher', 21 | 'karma-jquery', 22 | 'karma-chai-jquery' 23 | ], 24 | 25 | // list of files / patterns to load in the browser 26 | files: [ 27 | 'bower/angular/angular.js', 28 | 'bower/angular-mocks/angular-mocks.js', 29 | 'dist/ionic-settings.js', 30 | 'test/unit/**/*.js' 31 | ], 32 | 33 | 34 | // list of files to exclude 35 | exclude: [ 36 | ], 37 | 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | }, 43 | 44 | 45 | // test results reporter to use 46 | // possible values: 'dots', 'progress' 47 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 48 | reporters: ['progress'], 49 | 50 | 51 | // web server port 52 | port: 9876, 53 | 54 | 55 | // enable / disable colors in the output (reporters and logs) 56 | colors: true, 57 | 58 | 59 | // level of logging 60 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 61 | logLevel: config.LOG_INFO, 62 | 63 | 64 | // enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: true, 66 | 67 | 68 | // start these browsers 69 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 70 | browsers: ['PhantomJS'], 71 | 72 | 73 | // Continuous Integration mode 74 | // if true, Karma captures browsers, runs the tests and exits 75 | singleRun: false 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /karma-dist-minified.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Aug 21 2014 10:24:39 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'], 14 | 15 | plugins: [ 16 | 'karma-mocha', 17 | 'karma-chai', 18 | 'karma-sinon-chai', 19 | 'karma-chrome-launcher', 20 | 'karma-phantomjs-launcher', 21 | 'karma-jquery', 22 | 'karma-chai-jquery' 23 | ], 24 | 25 | // list of files / patterns to load in the browser 26 | files: [ 27 | 'bower/angular/angular.js', 28 | 'bower/angular-mocks/angular-mocks.js', 29 | 'dist/ionic-settings.min.js', 30 | 'test/unit/**/*.js' 31 | ], 32 | 33 | 34 | // list of files to exclude 35 | exclude: [ 36 | ], 37 | 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | }, 43 | 44 | 45 | // test results reporter to use 46 | // possible values: 'dots', 'progress' 47 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 48 | reporters: ['progress'], 49 | 50 | 51 | // web server port 52 | port: 9876, 53 | 54 | 55 | // enable / disable colors in the output (reporters and logs) 56 | colors: true, 57 | 58 | 59 | // level of logging 60 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 61 | logLevel: config.LOG_INFO, 62 | 63 | 64 | // enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: true, 66 | 67 | 68 | // start these browsers 69 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 70 | browsers: ['PhantomJS'], 71 | 72 | 73 | // Continuous Integration mode 74 | // if true, Karma captures browsers, runs the tests and exits 75 | singleRun: false 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /karma-src.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sun Jan 10 2016 18:55:04 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | // frameworks to use 9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['jasmine'], 11 | // list of files / patterns to load in the browser 12 | files: [ 13 | 'lib/ionic/ionic.bundle.js', 14 | 'bower/angular/angular.js', 15 | 'bower/angular-mocks/angular-mocks.js', 16 | 'src/**/*.module.js', 17 | 'src/**/*.js', 18 | 'test/unit/**/*.js' 19 | ], 20 | // list of files to exclude 21 | exclude: [ 22 | ], 23 | // preprocess matching files before serving them to the browser 24 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 25 | preprocessors: { 26 | }, 27 | // test results reporter to use 28 | // possible values: 'dots', 'progress' 29 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 30 | reporters: ['progress'], 31 | // web server port 32 | port: 9876, 33 | // enable / disable colors in the output (reporters and logs) 34 | colors: true, 35 | // level of logging 36 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 37 | logLevel: config.LOG_INFO, 38 | // enable / disable watching file and executing tests whenever any file changes 39 | autoWatch: true, 40 | // start these browsers 41 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 42 | browsers: ['PhantomJS'], 43 | // Continuous Integration mode 44 | // if true, Karma captures browsers, runs the tests and exits 45 | singleRun: false 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /nbproject/project.properties: -------------------------------------------------------------------------------- 1 | auxiliary.org-netbeans-modules-css-prep.less_2e_compiler_2e_options= 2 | auxiliary.org-netbeans-modules-css-prep.less_2e_enabled=false 3 | auxiliary.org-netbeans-modules-css-prep.less_2e_mappings=/less:/css 4 | auxiliary.org-netbeans-modules-css-prep.sass_2e_compiler_2e_options= 5 | auxiliary.org-netbeans-modules-css-prep.sass_2e_configured=true 6 | auxiliary.org-netbeans-modules-css-prep.sass_2e_enabled=false 7 | auxiliary.org-netbeans-modules-css-prep.sass_2e_mappings=/dist:/dist 8 | auxiliary.org-netbeans-modules-javascript-gulp.action_2e_build=default 9 | auxiliary.org-netbeans-modules-javascript-nodejs.enabled=true 10 | auxiliary.org-netbeans-modules-javascript-nodejs.run_2e_enabled=true 11 | auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=js/libs 12 | file.reference.ionic-settings-src=src 13 | file.reference.ionic-settings-test=testd 14 | file.reference.ionic-settings-test-1=test 15 | files.encoding=UTF-8 16 | run.as=node.js 17 | source.folder=${file.reference.ionic-settings-src} 18 | test.folder=${file.reference.ionic-settings-test-1} 19 | -------------------------------------------------------------------------------- /nbproject/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.netbeans.modules.web.clientproject 4 | 5 | 6 | ionic-settings 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-settings", 3 | "version": "1.0.2", 4 | "author": { 5 | "name": "Ivan Weber", 6 | "email": "ivan.weber@gmx.de" 7 | }, 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "chai": "^1.9.1", 11 | "chai-jquery": "^1.2.3", 12 | "gulp": "^3.8.7", 13 | "gulp-concat": "^2.3.4", 14 | "gulp-jshint": "^1.8.4", 15 | "gulp-minify-css": "^1.2.3", 16 | "gulp-plumber": "^0.6.6", 17 | "gulp-rename": "^1.2.0", 18 | "gulp-sass": "^2.1.1", 19 | "gulp-uglify": "^0.3.1", 20 | "jshint-stylish": "^0.4.0", 21 | "karma": "^0.12.22", 22 | "karma-chai": "^0.1.0", 23 | "karma-chai-jquery": "^1.0.0", 24 | "karma-chrome-launcher": "^0.1.4", 25 | "karma-jasmine": "^0.1.5", 26 | "karma-jquery": "^0.1.0", 27 | "karma-mocha": "^0.1.8", 28 | "karma-phantomjs-launcher": "^0.1.4", 29 | "karma-sinon-chai": "^0.2.0", 30 | "karma-spec-reporter": "^0.0.18", 31 | "mocha": "^1.21.4", 32 | "run-sequence": "^1.0.2", 33 | "sinon": "^1.10.3", 34 | "sinon-chai": "^2.5.0" 35 | }, 36 | "engines": { 37 | "node": ">=0.8.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ionic-settings/ionic-settings.scss: -------------------------------------------------------------------------------- 1 | 2 | $inputColor: #696969; 3 | $settingsTextSize: 14px; 4 | $positive: #387ef5; 5 | $assertive: #ef473a; 6 | 7 | .ionic-settings-numbers > div { 8 | margin-top: 20px !important; 9 | } 10 | 11 | .ionic-settings-numbers-button { 12 | border-radius: 50% !important; 13 | height: 70px !important; 14 | width: 72px !important; 15 | font-size: 20px !important; 16 | margin: 10px !important; 17 | } 18 | 19 | .ionic-settings-numbers-dot { 20 | font-size: 27px; 21 | margin: 10px; 22 | } 23 | 24 | .ionic-settings-numbers-row { 25 | display: flex; 26 | flex-direction: row; 27 | justify-content: center; 28 | width: 100%; 29 | height: 60px; 30 | margin-bottom: 20px; 31 | } 32 | 33 | .ionic-settings-numbers-value { 34 | font-size: 38px; 35 | margin: 10px; 36 | } 37 | 38 | .ionic-settings-icon-smaller:before { 39 | font-size: 27px; 40 | } 41 | 42 | .ionic-settings-icon-larger:before { 43 | font-size: 30px !important; 44 | margin-bottom: 27px !important; 45 | } 46 | 47 | .ionic-settings-left { 48 | float: left; 49 | } 50 | 51 | .ionic-settings-right { 52 | float: right; 53 | } 54 | 55 | .ionic-settings-android { 56 | font-size: $settingsTextSize; 57 | color: $inputColor; 58 | margin-top: 25px; 59 | } 60 | 61 | .ionic-settings-ios { 62 | color: gray; 63 | } 64 | 65 | .animate-ng-show.ng-hide-add { 66 | animation: 0.5s zoomOut ease; 67 | -webkit-animation: 0.5s zoomOut ease; 68 | } 69 | 70 | .animate-ng-show.ng-hide-remove { 71 | animation: 0.5s zoomIn ease; 72 | -webkit-animation: 0.5s zoomIn ease; 73 | } 74 | 75 | .ionic-settings-animate-modal-android { 76 | -webkit-transform: scale(0.0); 77 | transform: scale(0.0); 78 | opacity: 0; 79 | } 80 | 81 | .ionic-settings-animate-modal-android.ng-enter, .fade-in > .ng-enter { 82 | -webkit-transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 250ms; 83 | transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 250ms; 84 | } 85 | 86 | .ionic-settings-animate-modal-android.ng-enter-active, 87 | .ionic-settings-animate-modal-android > .ng-enter-active { 88 | -webkit-transform: scale(1.0); 89 | transform: scale(1.0); 90 | opacity: 1; 91 | } 92 | 93 | .ionic-settings-animate-modal-android.ng-leave, .fade-in > .ng-leave { 94 | -webkit-transition: all ease-in-out 250ms; 95 | transition: all ease-in-out 250ms; 96 | } 97 | 98 | @-webkit-keyframes zoomIn { 99 | from { 100 | opacity: 0; 101 | -webkit-transform: scale3d(.3, .3, .3); 102 | transform: scale3d(.3, .3, .3); 103 | } 104 | 105 | 50% { 106 | opacity: 1; 107 | } 108 | } 109 | 110 | @keyframes zoomIn { 111 | from { 112 | opacity: 0; 113 | -webkit-transform: scale3d(.3, .3, .3); 114 | transform: scale3d(.3, .3, .3); 115 | } 116 | 117 | 50% { 118 | opacity: 1; 119 | } 120 | } 121 | 122 | .zoomIn { 123 | -webkit-animation-name: zoomIn; 124 | animation-name: zoomIn; 125 | } 126 | 127 | @-webkit-keyframes zoomOut { 128 | from { 129 | opacity: 1; 130 | } 131 | 132 | 50% { 133 | opacity: 0; 134 | -webkit-transform: scale3d(.3, .3, .3); 135 | transform: scale3d(.3, .3, .3); 136 | } 137 | 138 | to { 139 | opacity: 0; 140 | } 141 | } 142 | 143 | @keyframes zoomOut { 144 | from { 145 | opacity: 1; 146 | } 147 | 148 | 50% { 149 | opacity: 0; 150 | -webkit-transform: scale3d(.3, .3, .3); 151 | transform: scale3d(.3, .3, .3); 152 | } 153 | 154 | to { 155 | opacity: 0; 156 | } 157 | } 158 | 159 | .zoomOut { 160 | -webkit-animation-name: zoomOut; 161 | animation-name: zoomOut; 162 | } 163 | -------------------------------------------------------------------------------- /src/ionic-settings/ionicSettings.module.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | 3 | angular.module("ionicSettings.constants", []) 4 | .constant("IONIC_SETTINGS_BUTTON", "button") 5 | .constant("IONIC_SETTINGS_PIN", "pin") 6 | .constant("IONIC_SETTINGS_PIN_EMPTY", "\u25CB") 7 | .constant("IONIC_SETTINGS_PIN_FILLED", "\u25CF") 8 | .constant("IONIC_SETTINGS_SELECTION", "selection") 9 | .constant("IONIC_SETTINGS_TEXT", "text"); 10 | 11 | angular.module("ionicSettings.directives", []) 12 | .directive("ionSettings", [ 13 | "$ionicSettings", 14 | "$ionicSettingsConfig", 15 | "IONIC_SETTINGS_PIN", function( 16 | $ionicSettings, 17 | $ionicSettingsConfig, 18 | IONIC_SETTINGS_PIN) { 19 | return { 20 | restrict: "E", 21 | scope: true, 22 | template: '
' + 23 | '
{{item}}
' + 24 | '
' + 25 | '' + 29 | '
{{item.label}}
' + 30 | '
{{item.value}}
' + 31 | '
{{item.value}}
' + 32 | '' + 33 | '' + 34 | '
' + 35 | '' + 39 | '{{item.label}}' + 40 | '' + 41 | '' + 42 | '' + 48 | '{{item.label}}' + 49 | '' + 50 | '' + 51 | '
' + 52 | '' + 53 | '' + 57 | '
' + 58 | '' + 62 | '' + 70 | '
' + 71 | '
', 72 | link: function($scope) { 73 | var active = {}; 74 | var modals = {}; 75 | 76 | $scope.data = $ionicSettings.getData(); 77 | $scope.isAndroid = $ionicSettingsConfig.isAndroid; 78 | $scope.iconClose = $ionicSettingsConfig.iconClose; 79 | $scope.iconClosePosition = $ionicSettingsConfig.iconClosePosition; 80 | $scope.iconDone = $ionicSettingsConfig.iconDone; 81 | $scope.iconSelected = $ionicSettingsConfig.isAndroid ? "ion-android-done" : "ion-ios-checkmark-empty"; 82 | $scope.pin = { 83 | active: false 84 | }; 85 | $scope.showing = false; 86 | $scope.color = $ionicSettingsConfig.color; 87 | $scope.elementColor = $scope.color === "stable" || $scope.color === "default" || $scope.color === "light" ? "dark" : $scope.color; 88 | $scope.barColor = "bar-" + $scope.color; 89 | 90 | angular.forEach($scope.data, function(item, key) { 91 | modals[key] = $ionicSettings.createModal($scope, key); 92 | 93 | $scope.$watch("data." + key + ".value", function(newValue, oldValue) { 94 | if (angular.isDefined(newValue) && newValue !== oldValue) { 95 | $ionicSettings.store(key, newValue).then(function() { 96 | $scope.$root.$broadcast($ionicSettings.changed, { 97 | key: key, 98 | value: newValue 99 | }); 100 | }, function() { 101 | $scope.data[key].value = oldValue; 102 | }); 103 | } 104 | }); 105 | }); 106 | 107 | $scope.addValue = function(value) { 108 | if ($scope.value.length === 4) { 109 | $scope.value = ""; 110 | } 111 | $scope.value += value; 112 | }; 113 | 114 | $scope.clear = function() { 115 | $scope.value = ""; 116 | }; 117 | 118 | $scope.dismiss = function() { 119 | if (active.item.type === IONIC_SETTINGS_PIN && active.item.value.length !== 4) { 120 | $scope.clear(); 121 | $scope.pin.active = false; 122 | } 123 | if (active.modal.isShown()) { 124 | active.modal.hide(); 125 | } 126 | }; 127 | 128 | $scope.hidePin = function() { 129 | $scope.showing = false; 130 | }; 131 | 132 | $scope.doAction = function(key) { 133 | active.modal = modals[key]; 134 | active.item = $scope.data[key]; 135 | if (active.modal) { 136 | if (active.item.type === IONIC_SETTINGS_PIN) { 137 | if (active.item.value.length === 4) { 138 | active.item.value = ""; 139 | } else { 140 | $scope.value = active.item.value; 141 | active.modal.show(); 142 | } 143 | } else { 144 | active.modal.show(); 145 | } 146 | } else { 147 | if (angular.isFunction(active.item.onClick)) { 148 | active.item.onClick(); 149 | } 150 | } 151 | }; 152 | 153 | $scope.savePin = function() { 154 | active.item.value = $scope.value; 155 | active.modal.hide(); 156 | }; 157 | 158 | $scope.select = function(value) { 159 | active.item.value = value; 160 | $scope.dismiss(); 161 | }; 162 | 163 | $scope.showPin = function() { 164 | $scope.showing = true; 165 | }; 166 | 167 | $scope.$on("modal.hidden", $scope.dismiss); 168 | 169 | $scope.$on("$destroy", function() { 170 | for (var key in modals) { 171 | if (modals[key]) { 172 | modals[key].remove(); 173 | } 174 | } 175 | }); 176 | } 177 | }; 178 | }]) 179 | .directive("ionSettingsButton", [ 180 | "$ionicSettingsConfig", 181 | "$ionicSettings", function( 182 | $ionicSettingsConfig, 183 | $ionicSettings) { 184 | return { 185 | template: '', 186 | $scope: true, 187 | link: function($scope) { 188 | $scope.barColor = "bar-" + $ionicSettingsConfig.color; 189 | $scope.icon = $ionicSettingsConfig.icon; 190 | $scope.iconClose = $ionicSettingsConfig.iconClose; 191 | $scope.iconClosePosition = $ionicSettingsConfig.iconClosePosition; 192 | $scope.settingsModal = $ionicSettings.createModal($scope); 193 | $scope.title = $ionicSettingsConfig.title; 194 | 195 | $scope.dismiss = function() { 196 | $scope.settingsModal.hide(); 197 | }; 198 | } 199 | }; 200 | }]) 201 | .directive("ngTouchstart", function () { 202 | return { 203 | controller: ["$scope", "$element", function ($scope, $element) { 204 | $element.bind("touchstart", onTouchStart); 205 | function onTouchStart(event) { 206 | var method = $element.attr("ng-touchstart"); 207 | $scope.$event = event; 208 | $scope.$apply(method); 209 | } 210 | 211 | }] 212 | }; 213 | }) 214 | .directive("ngTouchend", function () { 215 | return { 216 | controller: ["$scope", "$element", function ($scope, $element) { 217 | $element.bind("touchend", onTouchEnd); 218 | function onTouchEnd(event) { 219 | var method = $element.attr("ng-touchend"); 220 | $scope.$event = event; 221 | $scope.$apply(method); 222 | } 223 | 224 | }] 225 | }; 226 | }); 227 | 228 | angular.module("ionicSettings.providers", []).provider("$ionicSettingsConfig", function() { 229 | var android = ionic.Platform.isAndroid(); 230 | var ios = ionic.Platform.isIOS(); 231 | var color = "positive"; 232 | var icon = android ? "ion-android-settings" : "ion-ios-gear"; 233 | var iconClose = android ? "ion-android-close" : "ion-ios-close-empty"; 234 | var iconClosePosition = "right"; 235 | var iconDone = android ? "ion-android-done" : "ion-ios-checkmark-empty"; 236 | var modalAnimation = ios ? "slide-in-up" : "ionic-settings-animate-modal-android"; 237 | var title = "Settings"; 238 | var touchID = true; 239 | return { 240 | setAndroid: function(_android) { 241 | android = _android; 242 | }, 243 | setIcon: function(_icon) { 244 | icon = _icon; 245 | }, 246 | setIconClose: function(_iconClose) { 247 | iconClose = _iconClose; 248 | }, 249 | setIconClosePosition: function(_iconClosePosition) { 250 | iconClosePosition = _iconClosePosition; 251 | }, 252 | setModalAnimation: function(_modalAnimation) { 253 | modalAnimation = _modalAnimation; 254 | }, 255 | setColor: function(_color) { 256 | color = _color; 257 | }, 258 | setTitle: function(_title) { 259 | title = _title; 260 | }, 261 | setTouchID: function(_touchID) { 262 | touchID = _touchID; 263 | }, 264 | $get: function() { 265 | return { 266 | color: color, 267 | icon: icon, 268 | iconClose: iconClose, 269 | iconClosePosition: iconClosePosition, 270 | iconDone: iconDone, 271 | isAndroid: android, 272 | modalAnimation: modalAnimation, 273 | title: title, 274 | touchID: touchID 275 | }; 276 | } 277 | }; 278 | }); 279 | 280 | angular.module("ionicSettings.services", []) 281 | .factory("$ionicSettings", [ 282 | "$ionicSettingsConfig", 283 | "$ionicModal", 284 | "$log", 285 | "$q", 286 | "$rootScope", 287 | "$window", 288 | "IONIC_SETTINGS_PIN", 289 | "IONIC_SETTINGS_SELECTION", 290 | "IONIC_SETTINGS_TEXT", function( 291 | $ionicSettingsConfig, 292 | $ionicModal, 293 | $log, 294 | $q, 295 | $rootScope, 296 | $window, 297 | IONIC_SETTINGS_PIN, 298 | IONIC_SETTINGS_SELECTION, 299 | IONIC_SETTINGS_TEXT) { 300 | var self = this; 301 | var data = {}; 302 | var pin; 303 | var $cordovaSplashscreenOn = true; 304 | 305 | self.changed = "$ionicSettings.changed"; 306 | self.onInvalidPin = "$ionicSettings.onInvalidPin"; 307 | self.onValidPin = "$ionicSettings.onValidPin"; 308 | 309 | self.createModal = function($scope, key) { 310 | var html; 311 | if (key) { 312 | var item = data[key]; 313 | switch(item.type) { 314 | case IONIC_SETTINGS_SELECTION: 315 | html = 316 | '' + 317 | '' + 318 | '' + 319 | '

' + item.label + '

' + 320 | '' + 321 | '
' + 322 | '' + 323 | '' + 324 | '{{value}}' + 325 | '' + 326 | '' + 327 | '' + 328 | '
'; 329 | break; 330 | case IONIC_SETTINGS_PIN: 331 | html = 332 | '' + 333 | '' + 334 | '
' + 335 | '' + 336 | '' + 337 | '
' + 338 | '

' + item.label + '

' + 339 | '
' + 340 | '' + 341 | '' + 342 | '
' + 343 | '
' + 344 | '' + 345 | '
' + 346 | '
' + 347 | '' + 348 | '{{value[$index]}}' + 349 | '
' + 350 | '
' + 351 | '' + 356 | '
' + 357 | '
' + 358 | '' + 359 | '' + 360 | '' + 368 | '
' + 369 | '
' + 370 | '
' + 371 | '
'; 372 | break; 373 | case IONIC_SETTINGS_TEXT: 374 | html = 375 | '' + 376 | '' + 377 | '' + 378 | '

' + item.label + '

' + 379 | '' + 380 | '
' + 381 | '' + item.value + '' + 382 | '
'; 383 | angular.extend($scope, data[key].scopeObject); 384 | break; 385 | } 386 | } else { 387 | html = '' + 388 | '' + 389 | '' + 390 | '

{{title}}

' + 391 | '' + 392 | '
' + 393 | '' + 394 | '' + 395 | '' + 396 | '
'; 397 | } 398 | if (html) { 399 | return $ionicModal.fromTemplate(html, { 400 | scope: $scope, 401 | animation: $ionicSettingsConfig.modalAnimation, 402 | backdropClickToClose: false 403 | }); 404 | } 405 | }; 406 | 407 | self.fetch = function(key) { 408 | var q = $q.defer(); 409 | if ($window.plugins) { 410 | $window.plugins.appPreferences.fetch(key).then(function(value) { 411 | if (value === null) { 412 | q.reject(value); 413 | } else { 414 | q.resolve({key: key, value: value}); 415 | } 416 | }, function(error) { 417 | q.reject(new Error(error)); 418 | }); 419 | } else { 420 | var value = localStorage[key]; 421 | if (value === undefined) { 422 | q.reject(value); 423 | } else { 424 | if (value === "true" || value === "false") { 425 | value = value === "true"; 426 | } 427 | q.resolve({key: key, value: value}); 428 | } 429 | } 430 | return q.promise; 431 | }; 432 | 433 | self.fetchAll = function() { 434 | var fetching = []; 435 | for (var key in data) { 436 | var item = data[key]; 437 | if (angular.isObject(item)) { 438 | fetching.push(self.fetch(key)); 439 | } 440 | } 441 | return $q.all(fetching).then(function(result) { 442 | for (var i in result) { 443 | var resultItem = result[i]; 444 | var key = resultItem.key; 445 | var value = resultItem.value; 446 | 447 | var item = data[key]; 448 | item.value = value; 449 | if (item.type === IONIC_SETTINGS_PIN) { 450 | pin = { 451 | key: key, 452 | value: value 453 | }; 454 | } 455 | } 456 | return data; 457 | }); 458 | }; 459 | 460 | self.get = function(key) { 461 | if (data[key]) { 462 | return data[key].value; 463 | } else { 464 | $log.error("Setting key " + key + " is not defined."); 465 | return; 466 | } 467 | }; 468 | 469 | self.getData = function() { 470 | return data; 471 | }; 472 | 473 | self.init = function(_data) { 474 | data = _data; 475 | 476 | var q = $q.defer(); 477 | 478 | self.fetchAll().then(function(result) { 479 | if (pin && pin.value.length === 4) { 480 | var $scope = $rootScope.$new(); 481 | var pinModal = self.createModal($scope, pin.key); 482 | 483 | $scope.color = $ionicSettingsConfig.color; 484 | $scope.barColor = "bar-" + $scope.color; 485 | $scope.start = true; 486 | $scope.value = ""; 487 | $scope.valid = false; 488 | 489 | var pinItem = data[pin.key]; 490 | if (pinItem.onValid) { 491 | $scope.$on(self.onValidPin, pinItem.onValid); 492 | } 493 | if (pinItem.onInvalid) { 494 | $scope.$on(self.onInvalidPin, pinItem.onInvalid); 495 | } 496 | 497 | $scope.addValue = function(value) { 498 | $scope.value += value; 499 | if ($scope.value.length === 4) { 500 | $scope.valid = $scope.value === pin.value; 501 | if ($scope.valid) { 502 | pinModal.remove().then(function() { 503 | q.resolve(result); 504 | $rootScope.$broadcast(self.onValidPin); 505 | if ($cordovaSplashscreenOn) { 506 | try { 507 | navigator.splashscreen.show(); 508 | } catch(e) {} 509 | } 510 | }); 511 | } else { 512 | $rootScope.$broadcast(self.onInvalidPin, $scope.value); 513 | $scope.clear(); 514 | } 515 | } 516 | if ($scope.value.length > 4) { 517 | $scope.clear(); 518 | $scope.value += value; 519 | } 520 | }; 521 | 522 | $scope.clear = function() { 523 | $scope.value = ""; 524 | }; 525 | 526 | pinModal.show().then(function() { 527 | try { 528 | navigator.splashscreen.hide(); 529 | } catch(e) { 530 | $cordovaSplashscreenOn = false; 531 | } 532 | if ($ionicSettingsConfig.touchID) { 533 | try { 534 | touchid.checkSupport(function() { 535 | touchid.authenticate(function() { 536 | q.resolve(result); 537 | }, function() { 538 | }, "Touch ID"); 539 | }); 540 | } catch(e) {} 541 | } 542 | }); 543 | } else { 544 | q.resolve(result); 545 | } 546 | }, function() { 547 | self.storeAll(_data).then(function(result) { 548 | q.resolve(result); 549 | }); 550 | }); 551 | 552 | return q.promise; 553 | }; 554 | 555 | self.set = function(key, value) { 556 | data[key].value = value; 557 | return self.store(key, value); 558 | }; 559 | 560 | self.store = function(key, value) { 561 | var q = $q.defer(); 562 | if ($window.plugins) { 563 | $window.plugins.appPreferences.store(key, value).then(function(value) { 564 | q.resolve(value); 565 | }, function(error) { 566 | q.reject(new Error(error)); 567 | }); 568 | } else { 569 | localStorage[key] = value; 570 | q.resolve(localStorage[key]); 571 | } 572 | return q.promise; 573 | }; 574 | 575 | self.setSelectionValues = function(key, values) { 576 | angular.copy(values, data[key].values); 577 | return self.set(key, ""); 578 | }; 579 | 580 | self.storeAll = function() { 581 | var storing = []; 582 | for (var key in data) { 583 | var item = data[key]; 584 | if (angular.isObject(item)) { 585 | storing.push(self.store(key, item.value)); 586 | } 587 | } 588 | return $q.all(storing); 589 | }; 590 | 591 | return self; 592 | }]); 593 | 594 | // App 595 | angular.module("ionicSettings", [ 596 | "ionicSettings.constants", 597 | "ionicSettings.directives", 598 | "ionicSettings.providers", 599 | "ionicSettings.services" 600 | ]); 601 | })(angular); 602 | -------------------------------------------------------------------------------- /test/unit/ionic-settings/ionicSettingsSpec.js: -------------------------------------------------------------------------------- 1 | /* global expect */ 2 | 3 | "use strict"; 4 | 5 | 6 | describe("modules", function() { 7 | var module; 8 | var dependencies; 9 | dependencies = []; 10 | 11 | var hasModule = function(module) { 12 | return dependencies.indexOf(module) >= 0; 13 | }; 14 | 15 | beforeEach(function() { 16 | module = angular.module("ionicSettings"); 17 | dependencies = module.requires; 18 | }); 19 | 20 | it("should load constants module", function() { 21 | expect(hasModule("ionicSettings.constants")).toBeTruthy(); 22 | }); 23 | 24 | it("should load directives module", function() { 25 | expect(hasModule("ionicSettings.directives")).toBeTruthy(); 26 | }); 27 | 28 | it("should load providers module", function() { 29 | expect(hasModule("ionicSettings.providers")).toBeTruthy(); 30 | }); 31 | 32 | it("should load services module", function() { 33 | expect(hasModule("ionicSettings.services")).toBeTruthy(); 34 | }); 35 | }); --------------------------------------------------------------------------------