text: /**
   * @ngdoc provider
   * @name $ionicConfigProvider
   * @module ionic
   * @description
   * Ionic automatically takes platform configurations into account to adjust things like what
   * transition style to use and whether tab icons should show on the top or bottom. For example,
   * iOS will move forward by transitioning the entering view from right to center and the leaving
   * view from center to left. However, Android will transition with the entering view going from
   * bottom to center, covering the previous view, which remains stationary. It should be noted
   * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
   * developing on a desktop browser, it's going to take on iOS default configs.
   *
   * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
   * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
   * phase and within the app itself.
   *
   * By default, all base config variables are set to `'platform'`, which means it'll take on the
   * default config of the platform on which it's running. Config variables can be set at this
   * level so all platforms follow the same setting, rather than its platform config.
   * The following code would set the same config variable for all platforms:
   *
   * ```js
   * $ionicConfigProvider.views.maxCache(10);
   * ```
   *
   * Additionally, each platform can have its own config within the `$ionicConfigProvider.platform`
   * property. The config below would only apply to Android devices.
   *
   * ```js
   * $ionicConfigProvider.platform.android.views.maxCache(5);
   * ```
   *
   * @usage
   * ```js
   * var myApp = angular.module('reallyCoolApp', ['ionic']);
   *
   * myApp.config(function($ionicConfigProvider) {
   *   $ionicConfigProvider.views.maxCache(5);
   *
   *   // note that you can also chain configs
   *   $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
   * });
   * ```
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#views.transition
   * @description Animation style when transitioning between views. Default `platform`.
   *
   * @param {string} transition Which style of view transitioning to use.
   *
   * * `platform`: Dynamically choose the correct transition style depending on the platform
   * the app is running from. If the platform is not `ios` or `android` then it will default
   * to `ios`.
   * * `ios`: iOS style transition.
   * * `android`: Android style transition.
   * * `none`: Do not perform animated transitions.
   *
   * @returns {string} value
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#views.maxCache
   * @description  Maximum number of view elements to cache in the DOM. When the max number is
   * exceeded, the view with the longest time period since it was accessed is removed. Views that
   * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
   * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
   * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
   * each view transition, and the next time the same view is shown, it will have to re-compile,
   * attach to the DOM, and link the element again. This disables caching, in effect.
   * @param {number} maxNumber Maximum number of views to retain. Default `10`.
   * @returns {number} How many views Ionic will hold onto until the a view is removed.
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#views.forwardCache
   * @description  By default, when navigating, views that were recently visited are cached, and
   * the same instance data and DOM elements are referenced when navigating back. However, when
   * navigating back in the history, the "forward" views are removed from the cache. If you
   * navigate forward to the same view again, it'll create a new DOM element and controller
   * instance. Basically, any forward views are reset each time. Set this config to `true` to have
   * forward views cached and not reset on each load.
   * @param {boolean} value
   * @returns {boolean}
   */
  
   /**
    * @ngdoc method
    * @name $ionicConfigProvider#views.swipeBackEnabled
    * @description  By default on iOS devices, swipe to go back functionality is enabled by default.
    * This method can be used to disable it globally, or on a per-view basis.
    * Note: This functionality is only supported on iOS.
    * @param {boolean} value
    * @returns {boolean}
    */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#scrolling.jsScrolling
   * @description  Whether to use JS or Native scrolling. Defaults to native scrolling. Setting this to
   * `true` has the same effect as setting each `ion-content` to have `overflow-scroll='false'`.
   * @param {boolean} value Defaults to `false` as of Ionic 1.2
   * @returns {boolean}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#backButton.icon
   * @description Back button icon.
   * @param {string} value
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#backButton.text
   * @description Back button text.
   * @param {string} value Defaults to `Back`.
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#backButton.previousTitleText
   * @description If the previous title text should become the back button text. This
   * is the default for iOS.
   * @param {boolean} value
   * @returns {boolean}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#form.checkbox
   * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`.
   * @param {string} value
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#form.toggle
   * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`.
   * @param {string} value
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#spinner.icon
   * @description Default spinner icon to use.
   * @param {string} value Can be: `android`, `ios`, `ios-small`, `bubbles`, `circles`, `crescent`,
   * `dots`, `lines`, `ripple`, or `spiral`.
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#tabs.style
   * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
   * @param {string} value Available values include `striped` and `standard`.
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#tabs.position
   * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
   * @param {string} value Available values include `top` and `bottom`.
   * @returns {string}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#templates.maxPrefetch
   * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
   * $stateProvider.state. If set to `0`, the user will have to wait
   * for a template to be fetched the first time when navigating to a new page. Default `30`.
   * @param {integer} value Max number of template to prefetch from the templateUrls defined in
   * `$stateProvider.state()`.
   * @returns {integer}
   */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#navBar.alignTitle
   * @description Which side of the navBar to align the title. Default `center`.
   *
   * @param {string} value side of the navBar to align the title.
   *
   * * `platform`: Dynamically choose the correct title style depending on the platform
   * the app is running from. If the platform is `ios`, it will default to `center`.
   * If the platform is `android`, it will default to `left`. If the platform is not
   * `ios` or `android`, it will default to `center`.
   *
   * * `left`: Left align the title in the navBar
   * * `center`: Center align the title in the navBar
   * * `right`: Right align the title in the navBar.
   *
   * @returns {string} value
   */
  
  /**
    * @ngdoc method
    * @name $ionicConfigProvider#navBar.positionPrimaryButtons
    * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
    *
    * @param {string} value side of the navBar to align the primary navBar buttons.
    *
    * * `platform`: Dynamically choose the correct title style depending on the platform
    * the app is running from. If the platform is `ios`, it will default to `left`.
    * If the platform is `android`, it will default to `right`. If the platform is not
    * `ios` or `android`, it will default to `left`.
    *
    * * `left`: Left align the primary navBar buttons in the navBar
    * * `right`: Right align the primary navBar buttons in the navBar.
    *
    * @returns {string} value
    */
  
  /**
   * @ngdoc method
   * @name $ionicConfigProvider#navBar.positionSecondaryButtons
   * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
   *
   * @param {string} value side of the navBar to align the secondary navBar buttons.
   *
   * * `platform`: Dynamically choose the correct title style depending on the platform
   * the app is running from. If the platform is `ios`, it will default to `right`.
   * If the platform is `android`, it will default to `right`. If the platform is not
   * `ios` or `android`, it will default to `right`.
   *
   * * `left`: Left align the secondary navBar buttons in the navBar
   * * `right`: Right align the secondary navBar buttons in the navBar.
   *
   * @returns {string} value
   */
  
  IonicModule
  .provider('$ionicConfig', function() {
  
    var provider = this;
    provider.platform = {};
    var PLATFORM = 'platform';
  
    var configProperties = {
      views: {
        maxCache: PLATFORM,
        forwardCache: PLATFORM,
        transition: PLATFORM,
        swipeBackEnabled: PLATFORM,
        swipeBackHitWidth: PLATFORM
      },
      navBar: {
        alignTitle: PLATFORM,
        positionPrimaryButtons: PLATFORM,
        positionSecondaryButtons: PLATFORM,
        transition: PLATFORM
      },
      backButton: {
        icon: PLATFORM,
        text: PLATFORM,
        previousTitleText: PLATFORM
      },
      form: {
        checkbox: PLATFORM,
        toggle: PLATFORM
      },
      scrolling: {
        jsScrolling: PLATFORM
      },
      spinner: {
        icon: PLATFORM
      },
      tabs: {
        style: PLATFORM,
        position: PLATFORM
      },
      templates: {
        maxPrefetch: PLATFORM
      },
      platform: {}
    };
    createConfig(configProperties, provider, '');
  
  
  
    // Default
    // -------------------------
    setPlatformConfig('default', {
  
      views: {
        maxCache: 10,
        forwardCache: false,
        transition: 'ios',
        swipeBackEnabled: true,
        swipeBackHitWidth: 45
      },
  
      navBar: {
        alignTitle: 'center',
        positionPrimaryButtons: 'left',
        positionSecondaryButtons: 'right',
        transition: 'view'
      },
  
      backButton: {
        icon: 'ion-ios-arrow-back',
        text: 'Back',
        previousTitleText: true
      },
  
      form: {
        checkbox: 'circle',
        toggle: 'large'
      },
  
      scrolling: {
        jsScrolling: true
      },
  
      spinner: {
        icon: 'ios'
      },
  
      tabs: {
        style: 'standard',
        position: 'bottom'
      },
  
      templates: {
        maxPrefetch: 30
      }
  
    });
  
  
  
    // iOS (it is the default already)
    // -------------------------
    setPlatformConfig('ios', {});
  
  
  
    // Android
    // -------------------------
    setPlatformConfig('android', {
  
      views: {
        transition: 'android',
        swipeBackEnabled: false
      },
  
      navBar: {
        alignTitle: 'left',
        positionPrimaryButtons: 'right',
        positionSecondaryButtons: 'right'
      },
  
      backButton: {
        icon: 'ion-android-arrow-back',
        text: false,
        previousTitleText: false
      },
  
      form: {
        checkbox: 'square',
        toggle: 'small'
      },
  
      spinner: {
        icon: 'android'
      },
  
      tabs: {
        style: 'striped',
        position: 'top'
      },
  
      scrolling: {
        jsScrolling: false
      }
    });
  
    // Windows Phone
    // -------------------------
    setPlatformConfig('windowsphone', {
      //scrolling: {
      //  jsScrolling: false
      //}
      spinner: {
        icon: 'android'
      }
    });
  
  
    provider.transitions = {
      views: {},
      navBar: {}
    };
  
  
    // iOS Transitions
    // -----------------------
    provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
  
      function setStyles(ele, opacity, x, boxShadowOpacity) {
        var css = {};
        css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
        css.opacity = opacity;
        if (boxShadowOpacity > -1) {
          css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';
        }
        css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
        ionic.DomUtil.cachedStyles(ele, css);
      }
  
      var d = {
        run: function(step) {
          if (direction == 'forward') {
            setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker
            setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);
  
          } else if (direction == 'back') {
            setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);
            setStyles(leavingEle, 1, step * 100, 1 - step);
  
          } else {
            // swap, enter, exit
            setStyles(enteringEle, 1, 0, -1);
            setStyles(leavingEle, 0, 0, -1);
          }
        },
        shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
      };
  
      return d;
    };
  
    provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
  
      function setStyles(ctrl, opacity, titleX, backTextX) {
        var css = {};
        css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';
        css.opacity = opacity === 1 ? '' : opacity;
  
        ctrl.setCss('buttons-left', css);
        ctrl.setCss('buttons-right', css);
        ctrl.setCss('back-button', css);
  
        css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
        ctrl.setCss('back-text', css);
  
        css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
        ctrl.setCss('title', css);
      }
  
      function enter(ctrlA, ctrlB, step) {
        if (!ctrlA || !ctrlB) return;
        var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
        var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
        setStyles(ctrlA, step, titleX, backTextX);
      }
  
      function leave(ctrlA, ctrlB, step) {
        if (!ctrlA || !ctrlB) return;
        var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
        setStyles(ctrlA, 1 - step, titleX, 0);
      }
  
      var d = {
        run: function(step) {
          var enteringHeaderCtrl = enteringHeaderBar.controller();
          var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
          if (d.direction == 'back') {
            leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
            enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
          } else {
            enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
            leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
          }
        },
        direction: direction,
        shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
      };
  
      return d;
    };
  
  
    // Android Transitions
    // -----------------------
  
    provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
      shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
  
      function setStyles(ele, x, opacity) {
        var css = {};
        css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
        css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
        css.opacity = opacity;
        ionic.DomUtil.cachedStyles(ele, css);
      }
  
      var d = {
        run: function(step) {
          if (direction == 'forward') {
            setStyles(enteringEle, (1 - step) * 99, 1); // starting at 98% prevents a flicker
            setStyles(leavingEle, step * -100, 1);
  
          } else if (direction == 'back') {
            setStyles(enteringEle, (1 - step) * -100, 1);
            setStyles(leavingEle, step * 100, 1);
  
          } else {
            // swap, enter, exit
            setStyles(enteringEle, 0, 1);
            setStyles(leavingEle, 0, 0);
          }
        },
        shouldAnimate: shouldAnimate
      };
  
      return d;
    };
  
    provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
  
      function setStyles(ctrl, opacity) {
        if (!ctrl) return;
        var css = {};
        css.opacity = opacity === 1 ? '' : opacity;
  
        ctrl.setCss('buttons-left', css);
        ctrl.setCss('buttons-right', css);
        ctrl.setCss('back-button', css);
        ctrl.setCss('back-text', css);
        ctrl.setCss('title', css);
      }
  
      return {
        run: function(step) {
          setStyles(enteringHeaderBar.controller(), step);
          setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
        },
        shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
      };
    };
  
  
    // No Transition
    // -----------------------
  
    provider.transitions.views.none = function(enteringEle, leavingEle) {
      return {
        run: function(step) {
          provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
        },
        shouldAnimate: false
      };
    };
  
    provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
      return {
        run: function(step) {
          provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
          provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
        },
        shouldAnimate: false
      };
    };
  
  
    // private: used to set platform configs
    function setPlatformConfig(platformName, platformConfigs) {
      configProperties.platform[platformName] = platformConfigs;
      provider.platform[platformName] = {};
  
      addConfig(configProperties, configProperties.platform[platformName]);
  
      createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
    }
  
  
    // private: used to recursively add new platform configs
    function addConfig(configObj, platformObj) {
      for (var n in configObj) {
        if (n != PLATFORM && configObj.hasOwnProperty(n)) {
          if (angular.isObject(configObj[n])) {
            if (!isDefined(platformObj[n])) {
              platformObj[n] = {};
            }
            addConfig(configObj[n], platformObj[n]);
  
          } else if (!isDefined(platformObj[n])) {
            platformObj[n] = null;
          }
        }
      }
    }
  
  
    // private: create methods for each config to get/set
    function createConfig(configObj, providerObj, platformPath) {
      forEach(configObj, function(value, namespace) {
  
        if (angular.isObject(configObj[namespace])) {
          // recursively drill down the config object so we can create a method for each one
          providerObj[namespace] = {};
          createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
  
        } else {
          // create a method for the provider/config methods that will be exposed
          providerObj[namespace] = function(newValue) {
            if (arguments.length) {
              configObj[namespace] = newValue;
              return providerObj;
            }
            if (configObj[namespace] == PLATFORM) {
              // if the config is set to 'platform', then get this config's platform value
              var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
              if (platformConfig || platformConfig === false) {
                return platformConfig;
              }
              // didnt find a specific platform config, now try the default
              return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
            }
            return configObj[namespace];
          };
        }
  
      });
    }
  
    function stringObj(obj, str) {
      str = str.split(".");
      for (var i = 0; i < str.length; i++) {
        if (obj && isDefined(obj[str[i]])) {
          obj = obj[str[i]];
        } else {
          return null;
        }
      }
      return obj;
    }
  
    provider.setPlatformConfig = setPlatformConfig;
  
  
    // private: Service definition for internal Ionic use
    /**
     * @ngdoc service
     * @name $ionicConfig
     * @module ionic
     * @private
     */
    provider.$get = function() {
      return provider;
    };
  })
  // Fix for URLs in Cordova apps on Windows Phone
  // http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/
  // running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx
  .config(['$compileProvider', function($compileProvider) {
    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|sms|tel|geo|ftp|mailto|file|ghttps?|ms-appx-web|ms-appx|x-wmapp0):/);
    $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|content|blob|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);
  }]);
  