+
+
## Content
* Appcelerator Titanum app (app folder)
diff --git a/app/Resources/alloy/CFG.js b/app/Resources/alloy/CFG.js
new file mode 100644
index 0000000..59a89e7
--- /dev/null
+++ b/app/Resources/alloy/CFG.js
@@ -0,0 +1 @@
+module.exports={"dependencies":{}};
\ No newline at end of file
diff --git a/app/Resources/android/alloy.bootstrap.js b/app/Resources/android/alloy.bootstrap.js
new file mode 100644
index 0000000..f45b74c
--- /dev/null
+++ b/app/Resources/android/alloy.bootstrap.js
@@ -0,0 +1,5 @@
+var Alloy = require('/alloy');
+
+global.Alloy = Alloy;
+global._ = Alloy._;
+global.Backbone = Alloy.Backbone;
\ No newline at end of file
diff --git a/app/Resources/android/alloy.js b/app/Resources/android/alloy.js
new file mode 100755
index 0000000..b6e58ee
--- /dev/null
+++ b/app/Resources/android/alloy.js
@@ -0,0 +1,683 @@
+/**
+ * @class Alloy
+ * Top-level module for Alloy functions.
+ *
+ * Alloy is an application framework built on top of the Titanium SDK designed to help rapidly
+ * develop high quality applications and reduce maintenance.
+ *
+ * Alloy uses the model-view-controller architecture to separate the application into three
+ * components:
+ *
+ * * **Models** provide the data of the application. Alloy utilizes **Backbone Model and Collection**
+ * objects for this functionality.
+ *
+ * * **Views** provide the UI components to interact with the application, written using **XML markup**
+ * and **Titanium Stylesheets (TSS)**, which abstracts the UI components of the Titanium API.
+ *
+ * * **Controllers** provide the glue layer between the Model and View components as well as
+ * additional application logic using the **Alloy API** and **Titanium API**.
+ *
+ * The API documentation provided here is used with Alloy Controllers and Widget Controllers to
+ * interact with the View and Model components of the application or widget.
+ *
+ * For guides on using Alloy, see
+ * [Alloy Framework](http://docs.appcelerator.com/platform/latest/#!/guide/Alloy_Framework).
+ */
+var _ = require('/alloy/underscore')._,
+Backbone = require('/alloy/backbone'),
+CONST = require('/alloy/constants');
+
+exports.version = '1.15.2';
+exports._ = _;
+exports.Backbone = Backbone;
+
+var DEFAULT_WIDGET = 'widget';
+var MW320_CHECK = false;
+var IDENTITY_TRANSFORM = true ? Ti.UI.createMatrix2D ? Ti.UI.createMatrix2D() : Ti.UI.create2DMatrix() : undefined;
+var RESET = {
+ bottom: null,
+ left: null,
+ right: null,
+ top: null,
+ height: null,
+ width: null,
+ shadowColor: null,
+ shadowOffset: null,
+ backgroundImage: null,
+ backgroundRepeat: null,
+ center: null,
+ layout: null,
+ backgroundSelectedColor: null,
+ backgroundSelectedImage: null,
+
+ // non-null resets
+ opacity: 1.0,
+ touchEnabled: true,
+ enabled: true,
+ horizontalWrap: true,
+ zIndex: 0,
+
+ //##### DISPARITIES #####//
+
+ // Setting to "null" on android works the first time. Leaves the color
+ // on subsequent calls.
+ backgroundColor: true ? 'transparent' : null,
+
+ // creates a font slightly different (smaller) than default on iOS
+ // https://jira.appcelerator.org/browse/TIMOB-14565
+ font: null,
+
+ // Throws an exception on Android if set to null. Works on other platforms.
+ // https://jira.appcelerator.org/browse/TIMOB-14566
+ visible: true,
+
+ // Setting to "null" on android makes text transparent
+ // https://jira.appcelerator.org/browse/TIMOB-14567
+ color: true ? '#000' : null,
+
+ // Android will leave artifact of previous transform unless the identity matrix is
+ // manually reset.
+ // https://jira.appcelerator.org/browse/TIMOB-14568
+ //
+ // Mobileweb does not respect matrix properties set in the constructor, despite the
+ // documentation at docs.appcelerator.com indicating that it should.
+ // https://jira.appcelerator.org/browse/TIMOB-14570
+ transform: true ? IDENTITY_TRANSFORM : null,
+
+ // Crashes if set to null on anything but Android
+ // https://jira.appcelerator.org/browse/TIMOB-14571
+ backgroundGradient: !true ? {} : null,
+
+ // All supported platforms have varying behavior with border properties
+ // https://jira.appcelerator.org/browse/TIMOB-14573
+ borderColor: true ? null : 'transparent',
+
+ // https://jira.appcelerator.org/browse/TIMOB-14575
+ borderRadius: false ? 0 : null,
+
+ // https://jira.appcelerator.org/browse/TIMOB-14574
+ borderWidth: false ? 0 : null };
+
+
+if (false) {
+ RESET = _.extend(RESET, {
+ backgroundLeftCap: 0,
+ backgroundTopCap: 0 });
+
+} else if (true) {
+ RESET = _.extend(RESET, {
+ backgroundDisabledColor: null,
+ backgroundDisabledImage: null,
+ backgroundFocusedColor: null,
+ backgroundFocusedImage: null,
+ focusable: false,
+ keepScreenOn: false });
+
+}
+
+function ucfirst(text) {
+ if (!text) {return text;}
+ return text[0].toUpperCase() + text.substr(1);
+}
+
+function addNamespace(apiName) {
+ return (CONST.IMPLICIT_NAMESPACES[apiName] || CONST.NAMESPACE_DEFAULT) +
+ '.' + apiName;
+}
+
+exports.M = function (name, modelDesc, migrations) {
+ var config = (modelDesc || {}).config || {};
+ var adapter = config.adapter || {};
+ var extendObj = {};
+ var extendClass = {};
+ var mod;
+
+ if (adapter.type) {
+ mod = require('/alloy/sync/' + adapter.type);
+ extendObj.sync = function (method, model, opts) {
+ return mod.sync(method, model, opts);
+ };
+ } else {
+ extendObj.sync = function (method, model, opts) {
+ Ti.API.warn('Execution of ' + method + '#sync() function on a model that does not support persistence');
+ Ti.API.warn('model: ' + JSON.stringify(model.toJSON()));
+ };
+ }
+ extendObj.defaults = config.defaults;
+
+ // construct the model based on the current adapter type
+ if (migrations) {extendClass.migrations = migrations;}
+
+ // Run the pre model creation code, if any
+ if (mod && _.isFunction(mod.beforeModelCreate)) {
+ config = mod.beforeModelCreate(config, name) || config;
+ }
+
+ // Create the Model object
+ var Model = Backbone.Model.extend(extendObj, extendClass);
+ Model.prototype.config = config;
+
+ // Extend the Model with extendModel(), if defined
+ if (_.isFunction(modelDesc.extendModel)) {
+ Model = modelDesc.extendModel(Model) || Model;
+ }
+
+ // Run the post model creation code, if any
+ if (mod && _.isFunction(mod.afterModelCreate)) {
+ mod.afterModelCreate(Model, name);
+ }
+
+ return Model;
+};
+
+exports.C = function (name, modelDesc, model) {
+ var extendObj = { model: model };
+ var config = (model ? model.prototype.config : {}) || {};
+ var mod;
+
+ if (config.adapter && config.adapter.type) {
+ mod = require('/alloy/sync/' + config.adapter.type);
+ extendObj.sync = function (method, model, opts) {
+ return mod.sync(method, model, opts);
+ };
+ } else {
+ extendObj.sync = function (method, model, opts) {
+ Ti.API.warn('Execution of ' + method + '#sync() function on a collection that does not support persistence');
+ Ti.API.warn('model: ' + JSON.stringify(model.toJSON()));
+ };
+ }
+
+ var Collection = Backbone.Collection.extend(extendObj);
+ Collection.prototype.config = config;
+
+ // extend the collection object
+ if (_.isFunction(modelDesc.extendCollection)) {
+ Collection = modelDesc.extendCollection(Collection) || Collection;
+ }
+
+ // do any post collection creation code form the sync adapter
+ if (mod && _.isFunction(mod.afterCollectionCreate)) {
+ mod.afterCollectionCreate(Collection);
+ }
+
+ return Collection;
+};
+
+exports.UI = {};
+exports.UI.create = function (controller, apiName, opts) {
+ opts = opts || {};
+
+ // Make sure we have a full api name
+ var baseName, ns;
+ var parts = apiName.split('.');
+ if (parts.length === 1) {
+ baseName = apiName;
+ ns = opts.ns || CONST.IMPLICIT_NAMESPACES[baseName] || CONST.NAMESPACE_DEFAULT;
+ } else if (parts.length > 1) {
+ baseName = parts[parts.length - 1];
+ ns = parts.slice(0, parts.length - 1).join('.');
+ } else {
+ throw 'Alloy.UI.create() failed: No API name was given in the second parameter';
+ }
+ opts.apiName = ns + '.' + baseName;
+ baseName = baseName[0].toUpperCase() + baseName.substr(1);
+
+ // generate the style object
+ var style = exports.createStyle(controller, opts);
+
+ // create the titanium proxy object
+ return eval(ns)['create' + baseName](style);
+};
+
+exports.createStyle = function (controller, opts, defaults) {
+ var classes, apiName;
+
+ // If there's no opts, there's no reason to load the style module. Just
+ // return an empty object.
+ if (!opts) {return {};}
+
+ // make opts.classes an array if it isn't already
+ if (_.isArray(opts.classes)) {
+ classes = opts.classes.slice(0);
+ } else if (_.isString(opts.classes)) {
+ classes = opts.classes.split(/\s+/);
+ } else {
+ classes = [];
+ }
+
+ // give opts.apiName a namespace if it doesn't have one already
+ apiName = opts.apiName;
+ if (apiName && apiName.indexOf('.') === -1) {
+ apiName = addNamespace(apiName);
+ }
+
+ // TODO: check cached styles based on opts and controller
+
+ // Load the runtime style for the given controller
+ var styleArray;
+ if (controller && _.isObject(controller)) {
+ styleArray = require('/alloy/widgets/' + controller.widgetId +
+ '/styles/' + controller.name);
+ } else {
+ styleArray = require('/alloy/styles/' + controller);
+ }
+ var styleFinal = {};
+
+ // iterate through all styles
+ var i, len;
+ for (i = 0, len = styleArray.length; i < len; i++) {
+ var style = styleArray[i];
+
+ // give the apiName a namespace if necessary
+ var styleApi = style.key;
+ if (style.isApi && styleApi.indexOf('.') === -1) {
+ styleApi = (CONST.IMPLICIT_NAMESPACES[styleApi] ||
+ CONST.NAMESPACE_DEFAULT) + '.' + styleApi;
+ }
+
+ // does this style match the given opts?
+ if (style.isId && opts.id && style.key === opts.id ||
+ style.isClass && _.contains(classes, style.key)) {
+ // do nothing here, keep on processing
+ } else if (style.isApi) {
+ if (style.key.indexOf('.') === -1) {
+ style.key = addNamespace(style.key);
+ }
+ if (style.key !== apiName) {continue;}
+ } else {
+ // no matches, skip this style
+ continue;
+ }
+
+ // can we clear out any form factor queries?
+ if (style.queries && style.queries.formFactor &&
+ !exports[style.queries.formFactor]) {
+ continue;
+ }
+
+ // process runtime custom queries
+ if (style.queries && style.queries.if && (
+ style.queries.if.trim().toLowerCase() === 'false' ||
+ style.queries.if.indexOf('Alloy.Globals') !== -1 &&
+ exports.Globals[style.queries.if.split('.')[2]] === false)) {
+ continue;
+ }
+
+ // Merge this style into the existing style object
+ exports.deepExtend(true, styleFinal, style.style);
+ }
+
+ // TODO: cache the style based on the opts and controller
+
+ // Merge remaining extra style properties from opts, if any
+ var extraStyle = _.omit(opts, [
+ CONST.CLASS_PROPERTY,
+ CONST.APINAME_PROPERTY]);
+
+ exports.deepExtend(true, styleFinal, extraStyle);
+ styleFinal[CONST.CLASS_PROPERTY] = classes;
+ styleFinal[CONST.APINAME_PROPERTY] = apiName;
+
+ if (MW320_CHECK) {delete styleFinal[CONST.APINAME_PROPERTY];}
+
+ return defaults ? _.defaults(styleFinal, defaults) : styleFinal;
+};
+
+function processStyle(controller, proxy, classes, opts, defaults) {
+ opts = opts || {};
+ opts.classes = classes;
+ if (proxy.apiName) {opts.apiName = proxy.apiName;}
+ if (proxy.id) {opts.id = proxy.id;}
+ proxy.applyProperties(exports.createStyle(controller, opts, defaults));
+ if (true) {proxy.classes = classes;}
+}
+
+exports.addClass = function (controller, proxy, classes, opts) {
+
+ // make sure we actually have classes to add
+ if (!classes) {
+ if (opts) {
+ if (MW320_CHECK) {delete opts.apiName;}
+ proxy.applyProperties(opts);
+ }
+ return;
+ } else {
+ // create a union of the existing classes with the new one(s)
+ var pClasses = proxy[CONST.CLASS_PROPERTY] || [];
+ var beforeLen = pClasses.length;
+ classes = _.isString(classes) ? classes.split(/\s+/) : classes;
+ var newClasses = _.union(pClasses, classes || []);
+
+ // make sure we actually added classes before processing styles
+ if (beforeLen === newClasses.length) {
+ if (opts) {
+ if (MW320_CHECK) {delete opts.apiName;}
+ proxy.applyProperties(opts);
+ }
+ return;
+ } else {
+ processStyle(controller, proxy, newClasses, opts);
+ }
+ }
+};
+
+exports.removeClass = function (controller, proxy, classes, opts) {
+ classes = classes || [];
+ var pClasses = proxy[CONST.CLASS_PROPERTY] || [];
+ var beforeLen = pClasses.length;
+
+ // make sure there's classes to remove before processing
+ if (!beforeLen || !classes.length) {
+ if (opts) {
+ if (MW320_CHECK) {delete opts.apiName;}
+ proxy.applyProperties(opts);
+ }
+ return;
+ } else {
+ // remove the given class(es)
+ classes = _.isString(classes) ? classes.split(/\s+/) : classes;
+ var newClasses = _.difference(pClasses, classes);
+
+ // make sure there was actually a difference before processing
+ if (beforeLen === newClasses.length) {
+ if (opts) {
+ if (MW320_CHECK) {delete opts.apiName;}
+ proxy.applyProperties(opts);
+ }
+ return;
+ } else {
+ processStyle(controller, proxy, newClasses, opts, RESET);
+ }
+ }
+};
+
+exports.resetClass = function (controller, proxy, classes, opts) {
+ classes = classes || [];
+ classes = _.isString(classes) ? classes.split(/\s+/) : classes;
+ processStyle(controller, proxy, classes, opts, RESET);
+};
+
+/**
+ * @method createWidget
+ * Factory method for instantiating a widget controller. Creates and returns an instance of the
+ * named widget.
+ * @param {String} id Id of widget to instantiate.
+ * @param {String} [name="widget"] Name of the view within the widget to instantiate ('widget' by default)
+ * @param {Object} [args] Arguments to pass to the widget.
+ * @return {Alloy.Controller} Alloy widget controller object.
+ */
+exports.createWidget = function (id, name, args) {
+ if (typeof name !== 'undefined' && name !== null &&
+ _.isObject(name) && !_.isString(name)) {
+ args = name;
+ name = DEFAULT_WIDGET;
+ }
+ return new (require('/alloy/widgets/' + id + '/controllers/' + (name || DEFAULT_WIDGET)))(args);
+};
+
+/**
+ * @method createController
+ * Factory method for instantiating a controller. Creates and returns an instance of the
+ * named controller.
+ * @param {String} name Name of controller to instantiate.
+ * @param {Object} [args] Arguments to pass to the controller.
+ * @return {Alloy.Controller} Alloy controller object.
+ */
+exports.createController = function (name, args) {
+ return new (require('/alloy/controllers/' + name))(args);
+};
+
+/**
+ * @method createModel
+ * Factory method for instantiating a Backbone Model object. Creates and returns an instance of the
+ * named model.
+ *
+ * See [Backbone.Model](http://docs.appcelerator.com/backbone/0.9.2/#Model) in the Backbone.js documentation for
+ * information on the methods and properties provided by the Model object.
+ * @param {String} name Name of model to instantiate.
+ * @param {Object} [args] Arguments to pass to the model.
+ * @return {Backbone.Model} Backbone model object.
+ */
+exports.createModel = function (name, args) {
+ return new (require('/alloy/models/' + ucfirst(name)).Model)(args);
+};
+
+/**
+ * @method createCollection
+ * Factory method for instantiating a Backbone collection of model objects. Creates and returns a
+ * collection for holding the named type of model objects.
+ *
+ * See [Backbone.Collection](http://docs.appcelerator.com/backbone/0.9.2/#Collection) in the Backbone.js
+ * documentation for information on the methods and properties provided by the
+ * Collection object.
+ * @param {String} name Name of model to hold in this collection.
+ * @param {Object} [args] Arguments to pass to the collection.
+ * @return {Backbone.Collection} Backbone collection object.
+ */
+exports.createCollection = function (name, args) {
+ return new (require('/alloy/models/' + ucfirst(name)).Collection)(args);
+};
+
+function isTabletFallback() {
+ return Math.min(
+ Ti.Platform.displayCaps.platformHeight,
+ Ti.Platform.displayCaps.platformWidth) >=
+ 700;
+}
+
+/**
+ * @property {Boolean} isTablet
+ * `true` if the current device is a tablet.
+ *
+ */
+exports.isTablet = function () {
+ if (false) {
+ return "android" === 'ipad';
+ } else if (true) {
+ var psc = Ti.Platform.Android.physicalSizeCategory;
+ return psc === Ti.Platform.Android.PHYSICAL_SIZE_CATEGORY_LARGE ||
+ psc === Ti.Platform.Android.PHYSICAL_SIZE_CATEGORY_XLARGE;
+ } else if (false) {
+ return Math.min(
+ Ti.Platform.displayCaps.platformHeight,
+ Ti.Platform.displayCaps.platformWidth) >=
+ 400;
+ // } else if (OS_BLACKBERRY) {
+ // // Tablets not currently supported by BB TiSDK
+ // // https://jira.appcelerator.org/browse/TIMOB-13225
+ // return false;
+ } else if (false) {
+ // per http://www.extremetech.com/computing/139768-windows-8-smartphones-and-windows-phone-8-tablets
+ // tablets should be >= 1024x768 and phones could be lower, though current phones are running at
+ // the 1280x720 range and higher
+ return Math.max(
+ Ti.Platform.displayCaps.platformHeight,
+ Ti.Platform.displayCaps.platformWidth) >=
+ 1024;
+ } else {
+ return isTabletFallback();
+ }
+}();
+
+/**
+ * @property {Boolean} isHandheld
+ * `true` if the current device is a handheld device (not a tablet).
+ *
+ */
+exports.isHandheld = !exports.isTablet;
+
+/**
+ * @property {Object} Globals
+ * An object for storing globally accessible variables and functions.
+ * Alloy.Globals is accessible in any controller in your app:
+ *
+ * Alloy.Globals.someGlobalObject = { key: 'value' };
+ * Alloy.Globals.someGlobalFunction = function(){};
+ *
+ * Alloy.Globals can be accessed in other non-controller Javascript files
+ * like this:
+ *
+ * var theObject = require('alloy').Globals.someGlobalObject;
+ *
+ */
+exports.Globals = {};
+
+/**
+ * @property {Object} Models
+ * An object for storing globally accessible Alloy models. Singleton models
+ * created via markup will be stored on this object.
+ *
+ *
+ *
+ * The above markup would effectively generate the following code:
+ *
+ * Alloy.Models.myModel = Alloy.createModel('MyModel');
+ *
+ * Alloy.Models.myModel would then be accessible in any controller in your app.
+ *
+ */
+exports.Models = {};
+
+/*
+ * Creates a singleton instance of a Model based on the given model, or
+ * returns an existing instance if one has already been created.
+ * Documented in docs/apidoc/model.js for docs site.
+ */
+exports.Models.instance = function (name) {
+ return exports.Models[name] || (exports.Models[name] = exports.createModel(name));
+};
+
+/**
+ * @property {Object} Collections
+ * An object for storing globally accessible Alloy collections. Singleton collections
+ * created via markup will be stored on this object.
+ *
+ *
+ *
+ * The above markup would effectively generate the following code:
+ *
+ * Alloy.Collections.myModel = Alloy.createCollection('MyModel');
+ *
+ * Alloy.Collections.myModel would then be accessible in any controller in your app.
+ *
+ */
+exports.Collections = {};
+
+/*
+ * Creates a singleton instance of a Collection based on the given model, or
+ * returns an existing instance if one has already been created.
+ * Documented in docs/apidoc/collection.js for docs site.
+ */
+exports.Collections.instance = function (name) {
+ return exports.Collections[name] || (exports.Collections[name] = exports.createCollection(name));
+};
+
+/**
+ * @property {Object} CFG
+ * An object that stores Alloy configuration values as defined in your app's
+ * app/config.json file. Here's what a typical config.json file might look
+ * like in an Alloy app.
+ *
+ * {
+ * "global": { "key": "defaultValue", "anotherKey": 12345 },
+ * "env:development": {},
+ * "env:test": {},
+ * "env:production": {},
+ * "os:ios": { "key": "iosValue" },
+ * "os:android": { "key": "androidValue" },
+ * "dependencies": {}
+ * }
+ *
+ * If this app was compiled for iOS, the Alloy.CFG would look like this:
+ *
+ * Alloy.CFG = {
+ * "key": "iosValue",
+ * "anotherKey": 12345
+ * }
+ *
+ * Alloy.CFG is accessible in any controller in your app, and can be accessed
+ * in other non-controller Javascript files like this:
+ *
+ * var theKey = require('alloy').CFG.key;
+ *
+ */
+exports.CFG = require('/alloy/CFG');
+
+if (true) {
+ exports.Android = {};
+ exports.Android.menuItemCreateArgs = ['itemId', 'groupId', 'title', 'order', 'actionView', 'checkable', 'checked', 'enabled', 'icon', 'showAsAction', 'titleCondensed', 'visible'];
+}
+
+/*
+ * Adapted version of node.extend https://www.npmjs.org/package/node.extend
+ *
+ * Original copyright:
+ *
+ * node.extend
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * @fileoverview
+ * Port of jQuery.extend that actually works on node.js
+ */
+exports.deepExtend = function () {
+ var target = arguments[0] || {};
+ var i = 1;
+ var length = arguments.length;
+ var deep = false;
+ var options, name, src, copy, copy_is_array, clone;
+
+ // Handle a deep copy situation
+ if (typeof target === 'boolean') {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if (typeof target !== 'object' && !_.isFunction(target)) {
+ target = {};
+ }
+
+ for (; i < length; i++) {
+ // Only deal with non-null/undefined values
+ options = arguments[i];
+ if (options != null) {
+ if (typeof options === 'string') {
+ options = options.split('');
+ }
+ // Extend the base object
+ for (name in options) {
+ src = target[name];
+ copy = options[name];
+
+ // Prevent never-ending loop
+ if (target === copy) {
+ continue;
+ }
+
+ if (deep && copy && !_.isFunction(copy) && _.isObject(copy) && ((copy_is_array = _.isArray(copy)) || !_.has(copy, 'apiName'))) {
+ // Recurse if we're merging plain objects or arrays
+ if (copy_is_array) {
+ copy_is_array = false;
+ clone = src && _.isArray(src) ? src : [];
+ } else if (_.isDate(copy)) {
+ clone = new Date(copy.valueOf());
+ } else {
+ clone = src && _.isObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[name] = exports.deepExtend(deep, clone, copy);
+ } else {
+ target[name] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
\ No newline at end of file
diff --git a/app/Resources/android/alloy/CFG.js b/app/Resources/android/alloy/CFG.js
new file mode 100755
index 0000000..59a89e7
--- /dev/null
+++ b/app/Resources/android/alloy/CFG.js
@@ -0,0 +1 @@
+module.exports={"dependencies":{}};
\ No newline at end of file
diff --git a/app/Resources/android/alloy/backbone.js b/app/Resources/android/alloy/backbone.js
new file mode 100644
index 0000000..d99cc02
--- /dev/null
+++ b/app/Resources/android/alloy/backbone.js
@@ -0,0 +1,1431 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+// Initial Setup
+// -------------
+
+// Save a reference to the global object (`window` in the browser, `global`
+// on the server).
+var root = this;
+
+// Save the previous value of the `Backbone` variable, so that it can be
+// restored later on, if `noConflict` is used.
+var previousBackbone = root.Backbone;
+
+// Create a local reference to slice/splice.
+var slice = Array.prototype.slice;
+var splice = Array.prototype.splice;
+
+// The top-level namespace. All public Backbone classes and modules will
+// be attached to this. Exported for both CommonJS and the browser.
+var Backbone;
+if (typeof exports !== 'undefined') {
+ Backbone = exports;
+} else {
+ Backbone = root.Backbone = {};
+}
+
+// Current version of the library. Keep in sync with `package.json`.
+Backbone.VERSION = '0.9.2';
+
+// Require Underscore, if we're on the server, and it's not already present.
+var _ = root._;
+if (!_ && (typeof require !== 'undefined')) _ = require('/alloy/underscore');
+
+// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+var $ = root.jQuery || root.Zepto || root.ender;
+
+// Set the JavaScript library that will be used for DOM manipulation and
+// Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+// Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+// alternate JavaScript library (or a mock library for testing your views
+// outside of a browser).
+Backbone.setDomLibrary = function(lib) {
+ $ = lib;
+};
+
+// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+// to its previous owner. Returns a reference to this Backbone object.
+Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+};
+
+// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+// set a `X-Http-Method-Override` header.
+Backbone.emulateHTTP = false;
+
+// Turn on `emulateJSON` to support legacy servers that can't deal with direct
+// `application/json` requests ... will encode the body as
+// `application/x-www-form-urlencoded` instead and will send the model in a
+// form param named `model`.
+Backbone.emulateJSON = false;
+
+// Backbone.Events
+// -----------------
+
+// Regular expression used to split event strings
+var eventSplitter = /\s+/;
+
+// A module that can be mixed in to *any object* in order to provide it with
+// custom events. You may bind with `on` or remove with `off` callback functions
+// to an event; trigger`-ing an event fires all callbacks in succession.
+//
+// var object = {};
+// _.extend(object, Backbone.Events);
+// object.on('expand', function(){ alert('expanded'); });
+// object.trigger('expand');
+//
+var Events = Backbone.Events = {
+
+ // Bind one or more space separated events, `events`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on: function(events, callback, context) {
+
+ var calls, event, node, tail, list;
+ if (!callback) return this;
+ events = events.split(eventSplitter);
+ calls = this._callbacks || (this._callbacks = {});
+
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ while (event = events.shift()) {
+ list = calls[event];
+ node = list ? list.tail : {};
+ node.next = tail = {};
+ node.context = context;
+ node.callback = callback;
+ calls[event] = {tail: tail, next: list ? list.next : node};
+ }
+
+ return this;
+ },
+
+ // Remove one or many callbacks. If `context` is null, removes all callbacks
+ // with that function. If `callback` is null, removes all callbacks for the
+ // event. If `events` is null, removes all bound callbacks for all events.
+ off: function(events, callback, context) {
+ var event, calls, node, tail, cb, ctx;
+
+ // No events, or removing *all* events.
+ if (!(calls = this._callbacks)) return;
+ if (!(events || callback || context)) {
+ delete this._callbacks;
+ return this;
+ }
+
+ // Loop through the listed events and contexts, splicing them out of the
+ // linked list of callbacks if appropriate.
+ events = events ? events.split(eventSplitter) : _.keys(calls);
+ while (event = events.shift()) {
+ node = calls[event];
+ delete calls[event];
+ if (!node || !(callback || context)) continue;
+ // Create a new list, omitting the indicated callbacks.
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ cb = node.callback;
+ ctx = node.context;
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
+ this.on(event, cb, ctx);
+ }
+ }
+ }
+
+ return this;
+ },
+
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
+ // passed the same arguments as `trigger` is, apart from the event name
+ // (unless you're listening on `"all"`, which will cause your callback to
+ // receive the true name of the event as the first argument).
+ trigger: function(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks)) return this;
+ all = calls.all;
+ events = events.split(eventSplitter);
+ rest = slice.call(arguments, 1);
+
+ // For each event, walk through the linked list of callbacks twice,
+ // first to trigger the event, then to trigger any `"all"` callbacks.
+ while (event = events.shift()) {
+ if (node = calls[event]) {
+ tail = node.tail;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, rest);
+ }
+ }
+ if (node = all) {
+ tail = node.tail;
+ args = [event].concat(rest);
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ }
+
+ return this;
+ }
+
+};
+
+// Aliases for backwards compatibility.
+Events.bind = Events.on;
+Events.unbind = Events.off;
+
+// Backbone.Model
+// --------------
+
+// Create a new model, with defined attributes. A client id (`cid`)
+// is automatically generated and assigned for you.
+var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (options && options.parse) attributes = this.parse(attributes);
+ if (defaults = getValue(this, 'defaults')) {
+ attributes = _.extend({}, defaults, attributes);
+ }
+ if (options && options.collection) this.collection = options.collection;
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this.set(attributes, {silent: true});
+ // Reset change tracking.
+ this.changed = {};
+ this._silent = {};
+ this._pending = {};
+ this._previousAttributes = _.clone(this.attributes);
+ this.initialize.apply(this, arguments);
+};
+
+// Attach all inheritable methods to the Model prototype.
+_.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // A hash of attributes that have silently changed since the last time
+ // `change` was called. Will become pending attributes on the next call.
+ _silent: null,
+
+ // A hash of attributes that have changed since the last `'change'` event
+ // began.
+ _pending: null,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute: 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.clone(this.attributes);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.get(attr);
+ return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has: function(attr) {
+ return this.get(attr) != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless
+ // you choose to silence it.
+ set: function(key, value, options) {
+ var attrs, attr, val;
+
+ // Handle both
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs instanceof Model) attrs = attrs.attributes;
+ if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var changes = options.changes = {};
+ var now = this.attributes;
+ var escaped = this._escapedAttributes;
+ var prev = this._previousAttributes || {};
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // If the new and current value differ, record the change.
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+ delete escaped[attr];
+ (options.silent ? this._silent : changes)[attr] = true;
+ }
+
+ // Update or delete the current value.
+ options.unset ? delete now[attr] : now[attr] = val;
+
+ // If the new and previous value differ, record the change. If not,
+ // then remove changes for this attribute.
+ if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+ this.changed[attr] = val;
+ if (!options.silent) this._pending[attr] = true;
+ } else {
+ delete this.changed[attr];
+ delete this._pending[attr];
+ }
+ }
+
+ // Fire the `"change"` events.
+ if (!options.silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ (options || (options = {})).unset = true;
+ return this.set(attr, null, options);
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ (options || (options = {})).unset = true;
+ return this.set(_.clone(this.attributes), options);
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp, xhr), options)) return false;
+ if (success) success(model, resp);
+ };
+ options.error = Backbone.wrapError(options.error, model, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Set a hash of model attributes, and sync the model to the server.
+ // If the server returns an attributes hash that differs, the model's
+ // state will be `set` again.
+ save: function(key, value, options) {
+ var attrs, current;
+
+ // Handle both `("key", value)` and `({key: value})` -style calls.
+ if (_.isObject(key) || key == null) {
+ attrs = key;
+ options = value;
+ } else {
+ attrs = {};
+ attrs[key] = value;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (!this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ var serverAttrs = model.parse(resp, xhr);
+ if (options.wait) {
+ delete options.wait;
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
+ }
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ // Finish configuring and sending the Ajax request.
+ options.error = Backbone.wrapError(options.error, model, options);
+ var method = this.isNew() ? 'create' : 'update';
+ var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+ if (options.wait) this.set(current, silentOptions);
+ return xhr;
+ },
+
+ // Destroy this model on the server if it was already persisted.
+ // Optimistically removes the model from its collection, if it has one.
+ // If `wait: true` is passed, waits for the server to respond before removal.
+ destroy: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+
+ var triggerDestroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ if (this.isNew()) {
+ triggerDestroy();
+ return false;
+ }
+
+ options.success = function(resp) {
+ if (options.wait) triggerDestroy();
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
+ };
+
+ options.error = Backbone.wrapError(options.error, model, options);
+ var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+ if (!options.wait) triggerDestroy();
+ return xhr;
+ },
+
+ // Default URL for the model's representation on the server -- if you're
+ // using Backbone's restful methods, override this to change the endpoint
+ // that will be called.
+ url: function() {
+ var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
+ },
+
+ // **parse** converts a response into the hash of attributes to be `set` on
+ // the model. The default implementation is just to pass the response along.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Create a new model with identical attributes to this one.
+ clone: function() {
+ return new this.constructor(this.attributes);
+ },
+
+ // A model is new if it has never been saved to the server, and lacks an id.
+ isNew: function() {
+ return this.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ options || (options = {});
+ var changing = this._changing;
+ this._changing = true;
+
+ // Silent changes become pending changes.
+ for (var attr in this._silent) this._pending[attr] = true;
+
+ // Silent changes are triggered.
+ var changes = _.extend({}, options.changes, this._silent);
+ this._silent = {};
+ for (var attr in changes) {
+ this.trigger('change:' + attr, this, this.get(attr), options);
+ }
+ if (changing) return this;
+
+ // Continue firing `"change"` events while there are pending changes.
+ while (!_.isEmpty(this._pending)) {
+ this._pending = {};
+ this.trigger('change', this, options);
+ // Pending and silent changes still remain.
+ for (var attr in this.changed) {
+ if (this._pending[attr] || this._silent[attr]) continue;
+ delete this.changed[attr];
+ }
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (!arguments.length) return !_.isEmpty(this.changed);
+ return _.has(this.changed, attr);
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+ var val, changed = false, old = this._previousAttributes;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (!arguments.length || !this._previousAttributes) return null;
+ return this._previousAttributes[attr];
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ // Check if the model is currently in a valid state. It's only possible to
+ // get into an *invalid* state if you're using silent changes.
+ isValid: function() {
+ return !this.validate(this.attributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (options.silent || !this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) {
+ options.error(this, error, options);
+ } else {
+ this.trigger('error', this, error, options);
+ }
+ return false;
+ }
+
+});
+
+// Backbone.Collection
+// -------------------
+
+// Provides a standard collection class for our sets of models, ordered
+// or unordered. If a `comparator` is specified, the Collection will maintain
+// its models in sort order, as they're added and removed.
+var Collection = Backbone.Collection = function(models, options) {
+ options || (options = {});
+ if (options.model) this.model = options.model;
+ if (options.comparator) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true, parse: options.parse});
+};
+
+// Define the Collection's inheritable methods.
+_.extend(Collection.prototype, Events, {
+
+ // The default model for a collection is just a **Backbone.Model**.
+ // This should be overridden in most cases.
+ model: Model,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // The JSON representation of a Collection is an array of the
+ // models' attributes.
+ toJSON: function(options) {
+ return this.map(function(model){ return model.toJSON(options); });
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Begin by turning bare objects into model references, and preventing
+ // invalid models or duplicate models from being added.
+ for (i = 0, length = models.length; i < length; i++) {
+ if (!(model = models[i] = this._prepareModel(models[i], options))) {
+ throw new Error("Can't add an invalid model to a collection");
+ }
+ cid = model.cid;
+ id = model.id;
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+ dups.push(i);
+ continue;
+ }
+ cids[cid] = ids[id] = model;
+ }
+
+ // Remove duplicates.
+ i = dups.length;
+ while (i--) {
+ models.splice(dups[i], 1);
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ for (i = 0, length = models.length; i < length; i++) {
+ (model = models[i]).on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // Insert models into the collection, re-sorting if needed, and triggering
+ // `add` events unless silenced.
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) this.sort({silent: true});
+ if (options.silent) return this;
+ for (i = 0, length = this.models.length; i < length; i++) {
+ if (!cids[(model = this.models[i]).cid]) continue;
+ options.index = i;
+ model.trigger('add', model, this, options);
+ }
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+ this._removeReference(model);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, options);
+ return model;
+ },
+
+ // Remove a model from the end of the collection.
+ pop: function(options) {
+ var model = this.at(this.length - 1);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Add a model to the beginning of the collection.
+ unshift: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Get a model from the set by id.
+ get: function(id) {
+ if (id == null) return void 0;
+ return this._byId[id.id != null ? id.id : id];
+ },
+
+ // Get a model from the set by client id.
+ getByCid: function(cid) {
+ return cid && this._byCid[cid.cid || cid];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ return this.models[index];
+ },
+
+ // Return models with matching attributes. Useful for simple cases of `filter`.
+ where: function(attrs) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return true;
+ });
+ },
+
+ // Force the collection to re-sort itself. You don't need to call this under
+ // normal circumstances, as the set will maintain sort order as each item
+ // is added.
+ sort: function(options) {
+ options || (options = {});
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.map(this.models, function(model){ return model.get(attr); });
+ },
+
+ // When you have more items than you want to add or remove individually,
+ // you can reset the entire set with a new list of models, without firing
+ // any `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ models || (models = []);
+ options || (options = {});
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ this._reset();
+ this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === undefined) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+ if (success) success(collection, resp);
+ };
+ options.error = Backbone.wrapError(options.error, collection, options);
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // Create a new instance of a model in this collection. Add the model to the
+ // collection immediately, unless `wait: true` is passed, in which case we
+ // wait for the server to agree.
+ create: function(model, options) {
+ var coll = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) coll.add(model, options);
+ var success = options.success;
+ options.success = function(nextModel, resp, xhr) {
+ if (options.wait) coll.add(nextModel, options);
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', model, resp, options);
+ }
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ // **parse** converts a response into a list of models to be added to the
+ // collection. The default implementation is just to pass it through.
+ parse: function(resp, xhr) {
+ return resp;
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function () {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function(options) {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(model, options) {
+ options || (options = {});
+ if (!(model instanceof Model)) {
+ var attrs = model;
+ options.collection = this;
+ model = new this.model(attrs, options);
+ if (!model._validate(model.attributes, options)) model = false;
+ } else if (!model.collection) {
+ model.collection = this;
+ }
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ if (this == model.collection) {
+ delete model.collection;
+ }
+ model.off('all', this._onModelEvent, this);
+ },
+
+ // Internal method called every time a model in the set fires an event.
+ // Sets need to update their indexes when models change ids. All other
+ // events simply proxy through. "add" and "remove" events that originate
+ // in other collections are ignored.
+ _onModelEvent: function(event, model, collection, options) {
+ if ((event == 'add' || event == 'remove') && collection != this) return;
+ if (event == 'destroy') {
+ this.remove(model, options);
+ }
+ if (model && event === 'change:' + model.idAttribute) {
+ delete this._byId[model.previous(model.idAttribute)];
+ this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+});
+
+// Underscore methods that we want to implement on the Collection.
+var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+// Mix in each Underscore method as a proxy to `Collection#models`.
+_.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+ };
+});
+
+// Backbone.Router
+// -------------------
+
+// Routers map faux-URLs to actions, and fire events when routes are
+// matched. Creating a new one sets its `routes` hash, if not set statically.
+var Router = Backbone.Router = function(options) {
+ options || (options = {});
+ if (options.routes) this.routes = options.routes;
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+};
+
+// Cached regular expressions for matching named param parts and splatted
+// parts of route strings.
+var namedParam = /:\w+/g;
+var splatParam = /\*\w+/g;
+var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+// Set up all inheritable **Backbone.Router** properties and methods.
+_.extend(Router.prototype, Events, {
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Manually bind a single named route to a callback. For example:
+ //
+ // this.route('search/:query/p:num', 'search', function(query, num) {
+ // ...
+ // });
+ //
+ route: function(route, name, callback) {
+ Backbone.history || (Backbone.history = new History);
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ },
+
+ // Bind all defined routes to `Backbone.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes: function() {
+ if (!this.routes) return;
+ var routes = [];
+ for (var route in this.routes) {
+ routes.unshift([route, this.routes[route]]);
+ }
+ for (var i = 0, l = routes.length; i < l; i++) {
+ this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+});
+
+// Backbone.History
+// ----------------
+
+// Handles cross-browser history management, based on URL fragments. If the
+// browser does not support `onhashchange`, falls back to polling.
+var History = Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+};
+
+// Cached regex for cleaning leading hashes and slashes .
+var routeStripper = /^[#\/]/;
+
+// Cached regex for detecting MSIE.
+var isExplorer = /msie [\w.]+/;
+
+// Has the history handling already been started?
+History.started = false;
+
+// Set up all inheritable **Backbone.History** properties and methods.
+_.extend(History.prototype, Events, {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(windowOverride) {
+ var loc = windowOverride ? windowOverride.location : window.location;
+ var match = loc.href.match(/#(.*)$/);
+ return match ? match[1] : '';
+ },
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment: function(fragment, forcePushState) {
+ if (fragment == null) {
+ if (this._hasPushState || forcePushState) {
+ fragment = window.location.pathname;
+ var search = window.location.search;
+ if (search) fragment += search;
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+ return fragment.replace(routeStripper, '');
+ },
+
+ // Start the hash change handling, returning `true` if the current URL matches
+ // an existing route, and `false` otherwise.
+ start: function(options) {
+ if (History.started) throw new Error("Backbone.history has already been started");
+ History.started = true;
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ if (oldIE) {
+ this.iframe = $('').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ $(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ $(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = window.location;
+ var atRoot = loc.pathname == this.options.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ window.location.replace(this.options.root + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+ }
+
+ if (!this.options.silent) {
+ return this.loadUrl();
+ }
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+ if (current == this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ var frag = (fragment || '').replace(routeStripper, '');
+ if (this.fragment == frag) return;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+ this.fragment = frag;
+ window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this.fragment = frag;
+ this._updateHash(window.location, frag, options.replace);
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+ // When replace is true, we don't want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, frag, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ window.location.assign(this.options.root + fragment);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+ } else {
+ location.hash = fragment;
+ }
+ }
+});
+
+// Backbone.View
+// -------------
+
+// Creating a Backbone.View creates its initial element outside of the DOM,
+// if an existing element is not provided...
+var View = Backbone.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ this.delegateEvents();
+};
+
+// Cached regex to split keys for `delegate`.
+var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+// List of view options to be merged as properties.
+var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
+
+// Set up all inheritable **Backbone.View** properties and methods.
+_.extend(View.prototype, Events, {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName: 'div',
+
+ // jQuery delegate for element lookup, scoped to DOM elements within the
+ // current view. This should be prefered to global lookups where possible.
+ $: function(selector) {
+ return this.$el.find(selector);
+ },
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // **render** is the core function that your view should override, in order
+ // to populate its element (`this.el`), with the appropriate HTML. The
+ // convention is for **render** to always return `this`.
+ render: function() {
+ return this;
+ },
+
+ // Remove this view from the DOM. Note that the view isn't present in the
+ // DOM by default, so calling this method may be a no-op.
+ remove: function() {
+ this.$el.remove();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) $(el).attr(attributes);
+ if (content) $(el).html(content);
+ return el;
+ },
+
+ // Change the view's element (`this.el` property), including event
+ // re-delegation.
+ setElement: function(element, delegate) {
+ if (this.$el) this.undelegateEvents();
+ this.$el = (element instanceof $) ? element : $(element);
+ this.el = this.$el[0];
+ if (delegate !== false) this.delegateEvents();
+ return this;
+ },
+
+ // Set callbacks, where `this.events` is a hash of
+ //
+ // *{"event selector": "callback"}*
+ //
+ // {
+ // 'mousedown .title': 'edit',
+ // 'click .button': 'save'
+ // 'click .open': function(e) { ... }
+ // }
+ //
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
+ // Uses event delegation for efficiency.
+ // Omitting the selector binds the event to `this.el`.
+ // This only works for delegate-able events: not `focus`, `blur`, and
+ // not `change`, `submit`, and `reset` in Internet Explorer.
+ delegateEvents: function(events) {
+ if (!(events || (events = getValue(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
+ // You usually don't need to use this, but may wish to if you have multiple
+ // Backbone views attached to the same DOM element.
+ undelegateEvents: function() {
+ this.$el.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, this.options, options);
+ for (var i = 0, l = viewOptions.length; i < l; i++) {
+ var attr = viewOptions[i];
+ if (options[attr]) this[attr] = options[attr];
+ }
+ this.options = options;
+ },
+
+ // Ensure that the View has a DOM element to render into.
+ // If `this.el` is a string, pass it through `$()`, take the first
+ // matching element, and re-assign it to `el`. Otherwise, create
+ // an element from the `id`, `className` and `tagName` properties.
+ _ensureElement: function() {
+ if (!this.el) {
+ var attrs = getValue(this, 'attributes') || {};
+ if (this.id) attrs.id = this.id;
+ if (this.className) attrs['class'] = this.className;
+ this.setElement(this.make(this.tagName, attrs), false);
+ } else {
+ this.setElement(this.el, false);
+ }
+ }
+
+});
+
+// The self-propagating extend function that Backbone classes use.
+var extend = function (protoProps, classProps) {
+ var child = inherits(this, protoProps, classProps);
+ child.extend = this.extend;
+ return child;
+};
+
+// Set up inheritance for the model, collection, and view.
+Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+// Backbone.sync
+// -------------
+
+// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+};
+
+// Override this function to change the manner in which Backbone persists
+// models to the server. You will be passed the type of request, and the
+// model in question. By default, makes a RESTful Ajax request
+// to the model's `url()`. Some possible customizations could be:
+//
+// * Use `setTimeout` to batch rapid-fire updates into a single request.
+// * Send up the models as XML instead of JSON.
+// * Persist models via WebSockets instead of Ajax.
+//
+// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+// as `POST`, with a `_method` parameter containing the true HTTP method,
+// as well as all requests with the body as `application/x-www-form-urlencoded`
+// instead of `application/json` with the model in a param named `model`.
+// Useful when interfacing with server-side languages like **PHP** that make
+// it difficult to read the body of `PUT` requests.
+Backbone.sync = function(method, model, options) {
+ var type = methodMap[method];
+
+ // Default options, unless specified.
+ options || (options = {});
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = getValue(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (!options.data && model && (method == 'create' || method == 'update')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(model.toJSON());
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (Backbone.emulateJSON) {
+ params.contentType = 'application/x-www-form-urlencoded';
+ params.data = params.data ? {model: params.data} : {};
+ }
+
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+ // And an `X-HTTP-Method-Override` header.
+ if (Backbone.emulateHTTP) {
+ if (type === 'PUT' || type === 'DELETE') {
+ if (Backbone.emulateJSON) params.data._method = type;
+ params.type = 'POST';
+ params.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ };
+ }
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !Backbone.emulateJSON) {
+ params.processData = false;
+ }
+
+ // Make the request, allowing the user to override any Ajax options.
+ return $.ajax(_.extend(params, options));
+};
+
+// Wrap an optional error callback with a fallback error event.
+Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(originalModel, resp, options);
+ } else {
+ originalModel.trigger('error', originalModel, resp, options);
+ }
+ };
+};
+
+// Helpers
+// -------
+
+// Shared empty constructor function to aid in prototype-chain creation.
+var ctor = function(){};
+
+// Helper function to correctly set up the prototype chain, for subclasses.
+// Similar to `goog.inherits`, but uses a hash of prototype properties and
+// class properties to be extended.
+var inherits = function(parent, protoProps, staticProps) {
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Inherit class (static) properties from parent.
+ _.extend(child, parent);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Add static properties to the constructor function, if supplied.
+ if (staticProps) _.extend(child, staticProps);
+
+ // Correctly set child's `prototype.constructor`.
+ child.prototype.constructor = child;
+
+ // Set a convenience property in case the parent's prototype is needed later.
+ child.__super__ = parent.prototype;
+
+ return child;
+};
+
+// Helper function to get a value from a Backbone object as a property
+// or as a function.
+var getValue = function(object, prop) {
+ if (!(object && object[prop])) return null;
+ return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+};
+
+// Throw an error when a URL is needed, and none is supplied.
+var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+};
+
+}).call(this);
diff --git a/app/Resources/android/alloy/constants.js b/app/Resources/android/alloy/constants.js
new file mode 100755
index 0000000..ef29764
--- /dev/null
+++ b/app/Resources/android/alloy/constants.js
@@ -0,0 +1,272 @@
+var isTitanium = typeof Titanium !== 'undefined';
+var _, generatePlatformArray;
+
+if (isTitanium) {
+ _ = require('/alloy/underscore')._;
+} else {
+ var platforms = require('../../platforms/index');
+ _ = require('../lib/alloy/underscore')._;
+
+ // iterate through supported platforms to create specific constants
+ generatePlatformArray = function(key) {
+ var ret = [];
+ _.each(_.keys(platforms), function(p) {
+ ret.push(platforms[p][key]);
+ });
+ return ret;
+ };
+
+ // generate compile time constants based on supported platforms
+ exports.PLATFORMS = generatePlatformArray('platform');
+ exports.PLATFORM_FOLDERS_ALLOY = generatePlatformArray('alloyFolder');
+ exports.PLATFORM_FOLDERS = generatePlatformArray('titaniumFolder');
+}
+
+// General default values
+exports.ALLOY_DIR = 'app';
+exports.ALLOY_RUNTIME_DIR = 'alloy';
+exports.RESOURCES_DIR = 'Resources';
+exports.NAME_DEFAULT = 'index';
+exports.NAME_WIDGET_DEFAULT = 'widget';
+exports.NPM_WIDGET_PREFIX = 'alloy-widget-';
+exports.NPM_WIDGET_KEYWORDS = ['appcelerator', 'titanium', 'alloy', 'widget'];
+exports.GLOBAL_STYLE = 'app.tss';
+exports.ROOT_NODE = 'Alloy';
+exports.NAMESPACE_DEFAULT = 'Ti.UI';
+exports.REQUIRE_TYPE_DEFAULT = 'view';
+exports.PLUGIN_NAME = 'ti.alloy';
+exports.EXPR_PREFIX = '#';
+exports.PLUGIN_FILE = 'plugin.py';
+exports.HOOK_FILE = 'alloy.js';
+exports.HOOK_FILE_CLEAN = 'deepclean.js';
+exports.MINIMUM_TI_SDK = '3.0.0';
+exports.ITEM_TEMPLATE_VAR = '__itemTemplate';
+exports.PARENT_SYMBOL_VAR = '__parentSymbol';
+exports.WIDGET_OBJECT = 'Widget';
+exports.SKIP_EVENT_HANDLING = ['Ti.UI.ListItem', 'Alloy.Abstract.ItemTemplate'];
+exports.ADAPTERS = ['localStorage', 'properties', 'sql'];
+exports.CONTROLLER_NODES = ['Alloy.Require', 'Alloy.Widget'];
+exports.DEFAULT_BACKBONE_VERSION = '0.9.2';
+exports.SUPPORTED_BACKBONE_VERSIONS = ['0.9.2', '1.1.2', '1.3.3', '1.4.0'];
+
+// property names
+exports.CLASS_PROPERTY = 'classes';
+exports.APINAME_PROPERTY = 'apiName';
+exports.AUTOSTYLE_PROPERTY = 'autoStyle';
+exports.DOCROOT_MODULE_PROPERTY = 'module';
+exports.DOCROOT_BASECONTROLLER_PROPERTY = 'baseController';
+
+// Constants related to model-view binding
+exports.BIND_PROPERTIES = ['dataCollection', 'dataFilter', 'dataTransform', 'dataFunction'];
+exports.BIND_COLLECTION = 'dataCollection';
+exports.BIND_WHERE = 'dataFilter';
+exports.BIND_TRANSFORM = 'dataTransform';
+exports.BIND_FUNCTION = 'dataFunction';
+exports.BIND_TRANSFORM_VAR = '__transform';
+exports.BIND_MODEL_VAR = '$model';
+exports.MODEL_ELEMENTS = ['Alloy.Collection', 'Alloy.Model'];
+exports.MODEL_BINDING_EVENTS = 'fetch change destroy';
+exports.COLLECTION_BINDING_EVENTS = 'fetch destroy change add remove reset sort';
+exports.COLLECTION_BINDING_EVENTS_092 = 'fetch destroy change add remove reset';
+
+// Constants for properties shared between ActionBar and Toolbar on Android
+exports.BACKGROUND_IMAGE = 'backgroundImage';
+exports.DISPLAY_HOME_AS_UP = 'displayHomeAsUp';
+exports.HOME_BUTTON_ENABLED = 'homeButtonEnabled';
+exports.NAVIGATION_MODE = 'navigationMode';
+exports.ON_HOME_ICON_ITEM_SELECTED = 'onHomeIconItemSelected';
+
+// Listings for supported platforms and commands
+exports.INSTALL_TYPES = ['plugin'];
+exports.GENERATE_TARGETS = ['controller', 'jmk', 'model', 'migration', 'view', 'widget', 'style'];
+exports.DEPLOY_TYPES = [
+ { key: 'ENV_DEV', value: 'development' },
+ { key: 'ENV_DEVELOPMENT', value: 'development' },
+ { key: 'ENV_TEST', value: 'test' },
+ { key: 'ENV_PROD', value: 'production' },
+ { key: 'ENV_PRODUCTION', value: 'production' }
+];
+exports.DIST_TYPES = [
+ { key: 'DIST_ADHOC', value: ['dist-adhoc'] },
+ { key: 'DIST_STORE', value: ['dist-appstore', 'dist-playstore'] }
+];
+
+// mappings of file extensions and folders for each file type
+exports.FILE_EXT = {
+ VIEW: 'xml',
+ STYLE: 'tss',
+ MODEL: 'js',
+ MODELCODE: 'js',
+ MIGRATION: 'js',
+ CONTROLLER: 'js',
+ COMPONENT: 'js',
+ CONFIG: 'json',
+ JMK: 'jmk',
+ MAP: 'map'
+};
+exports.DIR = {
+ VIEW: 'views',
+ STYLE: 'styles',
+ RUNTIME_STYLE: 'styles',
+ CONTROLLER: 'controllers',
+ MODEL: 'models',
+ MODELCODE: 'models',
+ MIGRATION: 'migrations',
+ CONFIG: 'config',
+ ASSETS: 'assets',
+ WIDGET: 'widgets',
+ LIB: 'lib',
+ COMPONENT: 'controllers',
+ MAP: 'build/map',
+ VENDOR: 'vendor',
+ THEME: 'themes',
+ BUILD: 'build/alloy',
+ I18N: 'i18n',
+ PLATFORM: 'platform'
+};
+// folders/files to exclude when copying and processing files
+// RegEx format: must escape special chars - so use \.svn not .svn
+exports.EXCLUDED_FILES = [
+ '\\.svn', '\\.git'
+];
+
+// constants identifying JS reserved words
+exports.JS_RESERVED = [
+ 'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete',
+ 'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
+ 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
+ 'void', 'while', 'with'
+];
+exports.JS_RESERVED_FUTURE = [
+ 'class', 'enum', 'export', 'extends', 'import', 'super', 'implements',
+ 'interface', 'let', 'package', 'private', 'protected', 'public',
+ 'static', 'yield'
+];
+exports.JS_RESERVED_ALL = _.union(exports.JS_RESERVED, exports.JS_RESERVED_FUTURE);
+
+// constants for implicit namespaces in markup
+var NS_ALLOY = 'Alloy',
+ NS_ALLOY_ABSTRACT = 'Alloy.Abstract',
+ NS_TI_ANDROID = 'Ti.Android',
+ NS_TI_MAP = 'Ti.Map',
+ NS_TI_MEDIA = 'Ti.Media',
+ NS_TI_UI_IOS = 'Ti.UI.iOS',
+ NS_TI_UI_IPAD = 'Ti.UI.iPad',
+ NS_TI_UI_IPHONE = 'Ti.UI.iPhone',
+ NS_TI_UI_MOBILEWEB = 'Ti.UI.MobileWeb',
+ NS_TI_UI_WINDOWS = 'Ti.UI.Windows';
+
+exports.IMPLICIT_NAMESPACES = {
+ // Alloy
+ Collection: NS_ALLOY,
+ Model: NS_ALLOY,
+ Module: NS_ALLOY,
+ Require: NS_ALLOY,
+ Widget: NS_ALLOY,
+
+ // Alloy.Abstract
+ ButtonNames: NS_ALLOY_ABSTRACT,
+ ButtonName: NS_ALLOY_ABSTRACT,
+ BarItemTypes: NS_ALLOY_ABSTRACT,
+ BarItemType: NS_ALLOY_ABSTRACT,
+ CoverFlowImageTypes: NS_ALLOY_ABSTRACT,
+ CoverFlowImageType: NS_ALLOY_ABSTRACT,
+ FlexSpace: NS_ALLOY_ABSTRACT,
+ FixedSpace: NS_ALLOY_ABSTRACT,
+ Images: NS_ALLOY_ABSTRACT,
+ Item: NS_ALLOY_ABSTRACT,
+ Items: NS_ALLOY_ABSTRACT,
+ ItemTemplate: NS_ALLOY_ABSTRACT,
+ Labels: NS_ALLOY_ABSTRACT,
+ Option: NS_ALLOY_ABSTRACT,
+ Options: NS_ALLOY_ABSTRACT,
+ Templates: NS_ALLOY_ABSTRACT,
+ Preview: NS_ALLOY_ABSTRACT,
+ Actions: NS_ALLOY_ABSTRACT,
+
+ // Ti.Android
+ Menu: NS_TI_ANDROID,
+ MenuItem: NS_TI_ANDROID,
+ ActionBar: NS_TI_ANDROID,
+
+ // Ti.UI.Android
+ CardView: 'Ti.UI.Android',
+
+ // Ti.Map
+ Annotation: NS_TI_MAP,
+
+ // Ti.Media
+ VideoPlayer: NS_TI_MEDIA,
+ MusicPlayer: NS_TI_MEDIA,
+ AudioPlayer: NS_TI_MEDIA,
+
+ // Ti.UI.iOS
+ AdView: NS_TI_UI_IOS,
+ BlurView: NS_TI_UI_IOS,
+ CoverFlowView: NS_TI_UI_IOS,
+ DocumentViewer: NS_TI_UI_IOS,
+ LivePhotoView: NS_TI_UI_IOS,
+ SplitWindow: NS_TI_UI_IOS,
+ PreviewContext: NS_TI_UI_IOS,
+ PreviewAction: NS_TI_UI_IOS,
+ PreviewActionGroup: NS_TI_UI_IOS,
+ MenuPopup: NS_TI_UI_IOS,
+ Stepper: NS_TI_UI_IOS,
+
+ // Ti.UI.iPad
+ Popover: NS_TI_UI_IPAD,
+
+ // Ti.UI.iPhone
+ NavigationGroup: isTitanium && Ti.Platform.osname === 'mobileweb' ?
+ NS_TI_UI_MOBILEWEB : NS_TI_UI_IPHONE,
+ StatusBar: NS_TI_UI_IPHONE,
+
+ // Ti.UI.Windows
+ CommandBar: NS_TI_UI_WINDOWS,
+ AppBarButton: NS_TI_UI_WINDOWS,
+ AppBarToggleButton: NS_TI_UI_WINDOWS,
+ AppBarSeparator: NS_TI_UI_WINDOWS,
+
+ // Ti.UI.Window
+ LeftNavButton: 'Ti.UI.Window',
+ RightNavButton: 'Ti.UI.Window',
+ LeftNavButtons: 'Ti.UI.Window',
+ RightNavButtons: 'Ti.UI.Window',
+ TitleControl: 'Ti.UI.Window',
+ WindowToolbar: 'Ti.UI.Window',
+
+ // Ti.UI.iPad.Popover
+ ContentView: 'Ti.UI.iPad.Popover',
+
+ DrawerLayout: 'Ti.UI.Android',
+ LeftView: 'Ti.UI.Android.DrawerLayout',
+ CenterView: 'Ti.UI.Android.DrawerLayout',
+ RightView: 'Ti.UI.Android.DrawerLayout',
+
+ // Table and List proxy properties
+ FooterView: '_ProxyProperty._Lists',
+ HeaderView: '_ProxyProperty._Lists',
+ HeaderPullView: '_ProxyProperty._Lists',
+ PullView: '_ProxyProperty._Lists',
+ Search: '_ProxyProperty._Lists',
+ SearchView: '_ProxyProperty._Lists',
+
+ // misc proxy properties
+ RightButton: '_ProxyProperty',
+ LeftButton: '_ProxyProperty',
+ KeyboardToolbar: '_ProxyProperty',
+ ActionView: '_ProxyProperty'
+
+};
+
+// properties named with "on" that aren't used to signify event listeners
+exports.SPECIAL_PROPERTY_NAMES = [
+ 'onHomeIconItemSelected',
+ 'onTintColor',
+ 'onCreateOptionsMenu',
+ 'onPrepareOptionsMenu'
+];
+
+exports.COMMANDS = {
+ GENERATE: 'generate'
+};
diff --git a/app/Resources/android/alloy/controllers/BaseController.js b/app/Resources/android/alloy/controllers/BaseController.js
new file mode 100755
index 0000000..7d14be4
--- /dev/null
+++ b/app/Resources/android/alloy/controllers/BaseController.js
@@ -0,0 +1,508 @@
+var Alloy = require('/alloy'),
+ Backbone = Alloy.Backbone,
+ _ = Alloy._;
+
+/**
+ * @class Alloy.Controller
+ * @extends Backbone.Events
+ * The base class for Alloy controllers.
+ *
+ * Each controller is associated with a UI hierarchy, defined in an XML file in the
+ * `views` folder. Each element in the view hierarchy is either a Titanium {@link Titanium.UI.View View}
+ * or another Alloy controller or widget. Each Alloy controller or widget can additionally contain
+ * Titanium Views and/or more controllers and widgets.
+ *
+ */
+var Controller = function() {
+ var roots = [];
+ var self = this;
+
+ function getControllerParam() {
+ return self.__widgetId ? {
+ widgetId: self.__widgetId,
+ name: self.__controllerPath
+ } : self.__controllerPath;
+ }
+
+ this.__iamalloy = true;
+ _.extend(this, Backbone.Events, {
+ __views: {},
+ __events: [],
+ __proxyProperties: {},
+ setParent: function(parent) {
+ var len = roots.length;
+
+ if (!len) { return; }
+
+ if (parent.__iamalloy) {
+ this.parent = parent.parent;
+ } else {
+ this.parent = parent;
+ }
+
+ for (var i = 0; i < len; i++) {
+ if (roots[i].__iamalloy) {
+ roots[i].setParent(this.parent);
+ } else {
+ this.parent.add(roots[i]);
+ }
+ }
+ },
+ addTopLevelView: function(view) {
+ roots.push(view);
+ },
+ addProxyProperty: function(key, value) {
+ this.__proxyProperties[key] = value;
+ },
+ removeProxyProperty: function(key) {
+ delete this.__proxyProperties[key];
+ },
+
+ /**
+ * @method getTopLevelViews
+ * Returns a list of the root view elements associated with this controller.
+
+ * #### Example
+ * The following example displays the `id` of each top-level view associated with the
+ * controller:
+
+ // index.js
+ var views = $.getTopLevelViews();
+ for (each in views) {
+ var view = views[each];
+ console.log(view.id);
+ }
+
+ *
+ *
+ * @return {Array.<(Titanium.UI.View|Alloy.Controller)>}
+ */
+ getTopLevelViews: function() {
+ return roots;
+ },
+
+ /**
+ * @method getView
+ * Returns the specified view associated with this controller.
+ *
+ * If no `id` is specified, returns the first top-level view.
+ *
+ * #### Example
+ * The following example gets a reference to a `` object
+ * with the `id` of "loginWin" and then calls its [open()](Titanium.UI.Window) method.
+
+ var loginWindow = $.getView('loginWin');
+ loginWindow.open();
+ *
+ * @param {String} [id] ID of the view to return.
+ * @return {Titanium.UI.View/Alloy.Controller}
+ */
+ getView: function(id) {
+ if (typeof id === 'undefined' || id === null) {
+ return roots[0];
+ }
+ return this.__views[id];
+ },
+ removeView: function(id) {
+ delete this[id];
+ delete this.__views[id];
+ },
+
+ getProxyProperty: function(name) {
+ return this.__proxyProperties[name];
+ },
+
+ /**
+ * @method getViews
+ * Returns a list of all IDed view elements associated with this controller.
+ *
+ * #### Example
+ * Given the following XML view:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ * The following view-controller outputs the id of each view in the hierarchy.
+
+ var views = $.getViews();
+ for (each in views) {
+ var view = views[each];
+ console.log(view.id);
+ }
+
+ [INFO] : win1
+ [INFO] : label1
+ [INFO] : tab1
+ [INFO] : wind2
+ [INFO] : label2
+ [INFO] : tab2
+ [INFO] : tabs
+ [INFO] : otherview
+
+ * @return {Array.<(Titanium.UI.View|Alloy.Controller)>}
+ */
+ getViews: function() {
+ return this.__views;
+ },
+
+ /**
+ * @method destroy
+ * Frees binding resources associated with this controller and its
+ * UI components. It is critical that this is called when employing
+ * model/collection binding in order to avoid potential memory leaks.
+ * $.destroy() should be called whenever a controller's UI is to
+ * be "closed" or removed from the app. See the [Destroying Data Bindings](#!/guide/Destroying_Data_Bindings)
+ * test application for an example of this approach.
+
+ * #### Example
+ * In the following example the view-controller for a {@link Titanium.UI.Window Window} object named `dialog`
+ * calls its `destroy()` method in response to the Window object being closed.
+
+
+ $.dialog.addEventListener('close', function() {
+ $.destroy();
+ });
+ */
+ destroy: function() {
+ // destroy() is defined during the compile process based on
+ // the UI components and binding contained within the controller.
+ },
+
+ // getViewEx for advanced parsing and element traversal
+ getViewEx: function(opts) {
+ var recurse = opts.recurse || false;
+ if (recurse) {
+ var view = this.getView();
+ if (!view) {
+ return null;
+ } else if (view.__iamalloy) {
+ return view.getViewEx({ recurse: true });
+ } else {
+ return view;
+ }
+ } else {
+ return this.getView();
+ }
+ },
+
+ // getProxyPropertyEx for advanced parsing and element traversal
+ getProxyPropertyEx: function(name, opts) {
+ var recurse = opts.recurse || false;
+ if (recurse) {
+ var view = this.getProxyProperty(name);
+ if (!view) {
+ return null;
+ } else if (view.__iamalloy) {
+ return view.getProxyProperty(name, { recurse: true });
+ } else {
+ return view;
+ }
+ } else {
+ return this.getView(name);
+ }
+ },
+
+ /**
+ * @method createStyle
+ * Creates a dictionary of properties based on the specified styles.
+ *
+ *
+ * You can use this dictionary with the view object's
+ * {@link Titanium.UI.View#method-applyProperties applyProperties} method
+ * or a create object method, such as {@link Titanium.UI#method-createView Titanium.UI.createView}.
+ * #### Examples
+ * The following creates a new style object that is passed as a parameter
+ * to the {@link Titanium.UI#method-createLabel Ti.UI.createLabel()} method.
+
+ var styleArgs = {
+ apiName: 'Ti.UI.Label',
+ classes: ['blue','shadow','large'],
+ id: 'tester',
+ borderWidth: 2,
+ borderRadius: 16,
+ borderColor: '#000'
+ };
+ var styleObject = $.createStyle(styleArgs);
+ testLabel = Ti.UI.createLabel(styleObject);
+
+ * The next example uses the {@link Titanium#method-applyProperties applyProperties()} method
+ * to apply a style object to an existing Button control (button not shown).
+
+ var style = $.createStyle({
+ classes: args.button,
+ apiName: 'Button',
+ color: 'blue'
+ });
+ $.button.applyProperties(style);
+ * @param {AlloyStyleDict} opts Dictionary of styles to apply.
+ *
+ * @return {Dictionary}
+ * @since 1.2.0
+
+ */
+ createStyle: function(opts) {
+ return Alloy.createStyle(getControllerParam(), opts);
+ },
+
+ /*
+ * Documented in docs/apidoc/controller.js
+ */
+ UI: {
+ create: function(apiName, opts) {
+ return Alloy.UI.create(getControllerParam(), apiName, opts);
+ }
+ },
+
+ /**
+ * @method addClass
+ * Adds a TSS class to the specified view object.
+ *
+ * You can apply additional styles with the `opts` parameter. To use this method
+ * effectively you may need to enable autostyling
+ * on the target XML view. See [Autostyle](#!/guide/Dynamic_Styles-section-37530415_DynamicStyles-Autostyle)
+ * in the Alloy developer guide.
+ * #### Example
+ * The following adds the TSS classes ".redbg" and ".bigger" to a {@link Titanium.UI.Label}
+ * object proxy `label1`, and also sets the label's `text` property to "Cancel".
+
+ // index.js
+ $.addClass($.label1, 'redbg bigger', {text: "Cancel"});
+
+The 'redbg' and 'bigger' classes are shown below:
+
+ // index.tss
+ ".redbg" : {
+ color: 'red'
+ }
+ ".bigger": {
+ font : {
+ fontSize: '36'
+ }
+ }
+
+ * @param {Object} proxy View object to which to add class(es).
+ * @param {Array/String} classes Array or space-separated list of classes to apply.
+ * @param {Dictionary} [opts] Dictionary of properties to apply after classes have been added.
+ * @since 1.2.0
+ */
+ addClass: function(proxy, classes, opts) {
+ return Alloy.addClass(getControllerParam(), proxy, classes, opts);
+ },
+
+ /**
+ * @method removeClass
+ * Removes a TSS class from the specified view object.
+ *
+ * You can apply additional styles after the removal with the `opts` parameter.
+ * To use this method effectively you may need to enable autostyling
+ * on the target XML view. See [Autostyle](#!/guide/Dynamic_Styles-section-37530415_DynamicStyles-Autostyle)
+ * in the Alloy developer guide.
+ * #### Example
+ * The following removes the "redbg" and "bigger" TSS classes from a {@link Titanium.UI.Label}
+ * object proxy `label1`, and also sets the label's `text` property to "...".
+
+ $.removeClass($.label1, 'redbg bigger', {text: "..."});
+
+ * @param {Object} proxy View object from which to remove class(es).
+ * @param {Array/String} classes Array or space-separated list of classes to remove.
+ * @param {Dictionary} [opts] Dictionary of properties to apply after the class removal.
+ * @since 1.2.0
+ */
+ removeClass: function(proxy, classes, opts) {
+ return Alloy.removeClass(getControllerParam(), proxy, classes, opts);
+ },
+
+ /**
+ * @method resetClass
+ * Sets the array of TSS classes for the target View object, adding the classes specified and
+ * removing any applied classes that are not specified.
+ *
+ * You can apply classes or styles after the reset using the `classes` or `opts` parameters.
+ * To use this method effectively you may need to enable autostyling
+ * on the target XML view. See [Autostyle](#!/guide/Dynamic_Styles-section-37530415_DynamicStyles-Autostyle)
+ * in the Alloy developer guide.
+
+ * #### Example
+ * The following removes all previously applied styles on `label1` and then applies
+ * the TSS class 'no-style'.
+
+ $.resetClass($.label1, 'no-style');
+ * @param {Object} proxy View object to reset.
+ * @param {Array/String} [classes] Array or space-separated list of classes to apply after the reset.
+ * @param {Dictionary} [opts] Dictionary of properties to apply after the reset.
+ * @since 1.2.0
+ */
+ resetClass: function(proxy, classes, opts) {
+ return Alloy.resetClass(getControllerParam(), proxy, classes, opts);
+ },
+
+ /**
+ * @method updateViews
+ * Applies a set of properties to view elements associated with this controller.
+ * This method is useful for setting properties on repeated elements such as
+ * {@link Titanium.UI.TableViewRow TableViewRow} objects, rather than needing to have a controller
+ * for those child controllers.
+ * #### Example
+ * The following example uses this method to update a Label inside a TableViewRow object
+ * before adding it to a TableView.
+
+ * View-controller file: controllers/index.js
+
+ for (var i=0; i < 10; i++) {
+ var row = Alloy.createController("tablerow");
+ row.updateViews({
+ "#theLabel": {
+ text: "I am row #" + i
+ }
+ });
+ $.tableView.appendRow(row.getView());
+ };
+
+ * XML view: views/tablerow.xml
+
+
+
+
+
+
+
+ * XML view: views/index.xml
+
+
+
+ * @param {Object} args An object whose keys are the IDs (in form '#id') of views to which the styles will be applied.
+ * @since 1.4.0
+
+ */
+ updateViews: function(args) {
+ var views = this.getViews();
+ if (_.isObject(args)) {
+ _.each(_.keys(args), function(key) {
+ var elem = views[key.substring(1)];
+ if (key.indexOf('#') === 0 && key !== '#' && _.isObject(elem) && typeof elem.applyProperties === 'function') {
+ // apply the properties but make sure we're applying them to a Ti.UI object (not a controller)
+ elem.applyProperties(args[key]);
+ }
+ });
+ }
+ return this;
+ },
+
+ /**
+ * @method addListener
+ * Adds a tracked event listeners to a view proxy object.
+ * By default, any event listener declared in XML is tracked by Alloy.
+ *
+ * #### Example
+ * Add an event to the tracking target.
+
+ $.addListener($.aView, 'click', onClick);
+
+ * @param {Object} proxy Proxy view object to listen to.
+ * @param {String} type Name of the event.
+ * @param {Function} callback Callback function to invoke when the event is fired.
+ * @returns {String} ID attribute of the view object. If one does not exist, Alloy will create a unique ID.
+ * @since 1.7.0
+ */
+ addListener: function(proxy, type, callback) {
+ if (!proxy.id) {
+ proxy.id = _.uniqueId('__trackId');
+
+ if (_.has(this.__views, proxy.id)) {
+ Ti.API.error('$.addListener: ' + proxy.id + ' was conflict.');
+ return;
+ }
+ }
+
+ proxy.addEventListener(type, callback);
+ this.__events.push({
+ id: proxy.id,
+ view: proxy,
+ type: type,
+ handler: callback
+ });
+
+ return proxy.id;
+ },
+
+ /**
+ * @method getListener
+ * Gets all the tracked event listeners of the view-controller or
+ * only the ones specified by the parameters. Passing no parameters,
+ * retrieves all tracked event listeners. Set a parameter to `null`
+ * if you do not want to restrict the match to that parameter.
+ *
+ * #### Example
+ * Get all events bound to the view-controller.
+
+ var listener = $.getListener();
+
+ * @param {Object} [proxy] Proxy view object.
+ * @param {String} [type] Name of the event.
+ * @returns {Array} List of tracked event listeners.
+ * @since 1.7.0
+ */
+
+ getListener: function(proxy, type) {
+ return _.filter(this.__events, function(event, index) {
+ if ((!proxy || proxy.id === event.id) &&
+ (!type || type === event.type)) {
+ return true;
+ }
+
+ return false;
+ });
+ },
+
+ /**
+ * @method removeListener
+ * Removes all tracked event listeners or only the ones
+ * specified by the parameters. Passing no parameters,
+ * removes all tracked event listeners. Set a parameter to `null`
+ * if you do not want to restrict the match to that parameter.
+ *
+ * #### Example
+ * When the window is closed, remove all tracked event listeners.
+
+
+
+
+
+
+
+ function doClose() {
+ $.removeListener();
+ }
+ * @param {Object} [proxy] Proxy view object to remove event listeners from.
+ * @param {String} [type] Name of the event to remove.
+ * @param {Function} [callback] Callback to remove.
+ * @returns {Alloy.Controller} Controller instance.
+ * @since 1.7.0
+ */
+ removeListener: function(proxy, type, callback) {
+ this.__events.forEach(function(event, index) {
+ if ((!proxy || proxy.id === event.id) &&
+ (!type || type === event.type) &&
+ (!callback || callback === event.handler)) {
+ event.view.removeEventListener(event.type, event.handler);
+ delete self.__events[index];
+ }
+ });
+ return this;
+ }
+ });
+};
+module.exports = Controller;
diff --git a/app/Resources/android/alloy/controllers/index.js b/app/Resources/android/alloy/controllers/index.js
new file mode 100755
index 0000000..0c43ad4
--- /dev/null
+++ b/app/Resources/android/alloy/controllers/index.js
@@ -0,0 +1,156 @@
+var Alloy = require('/alloy'),
+Backbone = Alloy.Backbone,
+_ = Alloy._;
+
+
+
+
+function __processArg(obj, key) {
+ var arg = null;
+ if (obj) {
+ arg = obj[key] || null;
+ }
+ return arg;
+}
+
+function Controller() {
+
+ require('/alloy/controllers/' + 'BaseController').apply(this, Array.prototype.slice.call(arguments));
+ this.__controllerPath = 'index';
+ this.args = arguments[0] || {};
+
+ if (arguments[0]) {
+ var __parentSymbol = __processArg(arguments[0], '__parentSymbol');
+ var $model = __processArg(arguments[0], '$model');
+ var __itemTemplate = __processArg(arguments[0], '__itemTemplate');
+ }
+ var $ = this;
+ var exports = {};
+ var __defers = {};
+
+ // Generated code that must be executed before all UI and/or
+ // controller code. One example is all model and collection
+ // declarations from markup.
+
+
+ // Generated UI code
+ $.__views["index"] = Ti.UI.createWindow(
+ { backgroundColor: "white", exitOnClose: false, id: "index" });
+
+ $.__views["index"] && $.addTopLevelView($.__views["index"]);
+ onOpen ? $.addListener($.__views["index"], 'open', onOpen) : __defers['$.__views["index"]!open!onOpen'] = true;$.__views["__alloyId0"] = Ti.UI.createScrollView(
+ { width: 300, layout: "vertical", height: Ti.UI.SIZE, top: 10, id: "__alloyId0" });
+
+ $.__views["index"].add($.__views["__alloyId0"]);
+ $.__views["__alloyId1"] = Ti.UI.createLabel(
+ { color: "#000", width: Ti.UI.SIZE, height: Ti.UI.SIZE, left: 0, text: "Town 1", id: "__alloyId1" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId1"]);
+ $.__views["tf_town1"] = Ti.UI.createTextField(
+ { width: 300, borderColor: "#000", borderWidth: 1, color: "#000", id: "tf_town1" });
+
+ $.__views["__alloyId0"].add($.__views["tf_town1"]);
+ onChange ? $.addListener($.__views["tf_town1"], 'change', onChange) : __defers['$.__views["tf_town1"]!change!onChange'] = true;$.__views["__alloyId2"] = Ti.UI.createLabel(
+ { color: "#000", width: Ti.UI.SIZE, height: Ti.UI.SIZE, left: 0, text: "Lat", id: "__alloyId2" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId2"]);
+ $.__views["tf_lat1"] = Ti.UI.createTextField(
+ { width: 300, borderColor: "#000", borderWidth: 1, color: "#000", id: "tf_lat1" });
+
+ $.__views["__alloyId0"].add($.__views["tf_lat1"]);
+ onChange ? $.addListener($.__views["tf_lat1"], 'change', onChange) : __defers['$.__views["tf_lat1"]!change!onChange'] = true;$.__views["__alloyId3"] = Ti.UI.createLabel(
+ { color: "#000", width: Ti.UI.SIZE, height: Ti.UI.SIZE, left: 0, text: "Lon", id: "__alloyId3" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId3"]);
+ $.__views["tf_lon1"] = Ti.UI.createTextField(
+ { width: 300, borderColor: "#000", borderWidth: 1, color: "#000", id: "tf_lon1" });
+
+ $.__views["__alloyId0"].add($.__views["tf_lon1"]);
+ onChange ? $.addListener($.__views["tf_lon1"], 'change', onChange) : __defers['$.__views["tf_lon1"]!change!onChange'] = true;$.__views["__alloyId4"] = Ti.UI.createLabel(
+ { color: "#000", width: Ti.UI.SIZE, height: Ti.UI.SIZE, left: 0, text: "Town 2", top: 10, id: "__alloyId4" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId4"]);
+ $.__views["tf_town2"] = Ti.UI.createTextField(
+ { width: 300, borderColor: "#000", borderWidth: 1, color: "#000", id: "tf_town2" });
+
+ $.__views["__alloyId0"].add($.__views["tf_town2"]);
+ onChange ? $.addListener($.__views["tf_town2"], 'change', onChange) : __defers['$.__views["tf_town2"]!change!onChange'] = true;$.__views["__alloyId5"] = Ti.UI.createLabel(
+ { color: "#000", width: Ti.UI.SIZE, height: Ti.UI.SIZE, left: 0, text: "Lat", id: "__alloyId5" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId5"]);
+ $.__views["tf_lat2"] = Ti.UI.createTextField(
+ { width: 300, borderColor: "#000", borderWidth: 1, color: "#000", id: "tf_lat2" });
+
+ $.__views["__alloyId0"].add($.__views["tf_lat2"]);
+ onChange ? $.addListener($.__views["tf_lat2"], 'change', onChange) : __defers['$.__views["tf_lat2"]!change!onChange'] = true;$.__views["__alloyId6"] = Ti.UI.createLabel(
+ { color: "#000", width: Ti.UI.SIZE, height: Ti.UI.SIZE, left: 0, text: "Lon", id: "__alloyId6" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId6"]);
+ $.__views["tf_lon2"] = Ti.UI.createTextField(
+ { width: 300, borderColor: "#000", borderWidth: 1, color: "#000", id: "tf_lon2" });
+
+ $.__views["__alloyId0"].add($.__views["tf_lon2"]);
+ onChange ? $.addListener($.__views["tf_lon2"], 'change', onChange) : __defers['$.__views["tf_lon2"]!change!onChange'] = true;$.__views["__alloyId7"] = Ti.UI.createButton(
+ { title: "Force Update", id: "__alloyId7" });
+
+ $.__views["__alloyId0"].add($.__views["__alloyId7"]);
+ onClickButton ? $.addListener($.__views["__alloyId7"], 'click', onClickButton) : __defers['$.__views["__alloyId7"]!click!onClickButton'] = true;exports.destroy = function () {};
+
+ // make all IDed elements in $.__views available right on the $ in a
+ // controller's internal code. Externally the IDed elements will
+ // be accessed with getView().
+ _.extend($, $.__views);
+
+ // Controller code directly from the developer's controller file
+ var widgets = require("ti.widget");
+
+ $.index.open();
+
+ function onClickButton(e) {
+ require("/widget").updateData();
+ }
+
+ var MINUTES = 60 * 30 * 1000;
+ var intent = Titanium.Android.createServiceIntent({
+ url: 'service.js' });
+
+ intent.putExtra('interval', MINUTES);
+ Titanium.Android.startService(intent);
+
+
+ function onChange() {
+ Ti.App.Properties.setString("town1", $.tf_town1.value);
+ Ti.App.Properties.setString("town2", $.tf_town2.value);
+
+ Ti.App.Properties.setString("lat1", $.tf_lat1.value);
+ Ti.App.Properties.setString("lon1", $.tf_lon1.value);
+
+ Ti.App.Properties.setString("lat2", $.tf_lat2.value);
+ Ti.App.Properties.setString("lon2", $.tf_lon2.value);
+ }
+
+ function onOpen(e) {
+
+ $.tf_lat1.value = Ti.App.Properties.getString("lat1", "48.135124");
+ $.tf_lon1.value = Ti.App.Properties.getString("lon1", "11.581981");
+ $.tf_lat2.value = Ti.App.Properties.getString("lat2", "52.520008");
+ $.tf_lon2.value = Ti.App.Properties.getString("lon2", "13.404954");
+ $.tf_town1.value = Ti.App.Properties.getString("town1", "Berlin");
+ $.tf_town2.value = Ti.App.Properties.getString("town2", "Munich");
+
+ require("/widget").updateData();
+ }
+
+ // Generated code that must be executed after all UI and
+ // controller code. One example deferred event handlers whose
+ // functions are not defined until after the controller code
+ // is executed.
+ __defers['$.__views["index"]!open!onOpen'] && $.addListener($.__views["index"], 'open', onOpen);__defers['$.__views["tf_town1"]!change!onChange'] && $.addListener($.__views["tf_town1"], 'change', onChange);__defers['$.__views["tf_lat1"]!change!onChange'] && $.addListener($.__views["tf_lat1"], 'change', onChange);__defers['$.__views["tf_lon1"]!change!onChange'] && $.addListener($.__views["tf_lon1"], 'change', onChange);__defers['$.__views["tf_town2"]!change!onChange'] && $.addListener($.__views["tf_town2"], 'change', onChange);__defers['$.__views["tf_lat2"]!change!onChange'] && $.addListener($.__views["tf_lat2"], 'change', onChange);__defers['$.__views["tf_lon2"]!change!onChange'] && $.addListener($.__views["tf_lon2"], 'change', onChange);__defers['$.__views["__alloyId7"]!click!onClickButton'] && $.addListener($.__views["__alloyId7"], 'click', onClickButton);
+
+ // Extend the $ instance with all functions and properties
+ // defined on the exports object.
+ _.extend($, exports);
+}
+
+module.exports = Controller;
+//# sourceMappingURL=file:///home/miga/dev/titanium/coronaWidgetGithub/app/build/map/Resources/android/alloy/controllers/index.js.map
\ No newline at end of file
diff --git a/app/Resources/android/alloy/styles/index.js b/app/Resources/android/alloy/styles/index.js
new file mode 100755
index 0000000..60047e3
--- /dev/null
+++ b/app/Resources/android/alloy/styles/index.js
@@ -0,0 +1 @@
+module.exports = [{"isApi":true,"priority":1000.0001,"key":"TextField","style":{width:300,borderColor:"#000",borderWidth:1,color:"#000",}},{"isApi":true,"priority":1000.0002,"key":"Label","style":{color:"#000",width:Ti.UI.SIZE,height:Ti.UI.SIZE,left:0,}},{"isClass":true,"priority":10000.0003,"key":"container","style":{backgroundColor:"white",exitOnClose:false,}},{"isClass":true,"priority":10000.0004,"key":"content","style":{width:300,layout:"vertical",height:Ti.UI.SIZE,top:10,}}];
\ No newline at end of file
diff --git a/app/Resources/android/alloy/sync/localStorage.js b/app/Resources/android/alloy/sync/localStorage.js
new file mode 100755
index 0000000..b32e1ef
--- /dev/null
+++ b/app/Resources/android/alloy/sync/localStorage.js
@@ -0,0 +1,100 @@
+/*
+ * HTML5 localStorage sync adapter
+ */
+var _ = require('/alloy/underscore')._;
+
+function S4() {
+ return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
+}
+
+function guid() {
+ return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
+}
+
+function InitAdapter() {
+ if (!false) {
+ throw 'localStorage persistence supported only with MobileWeb.';
+ }
+}
+
+function Sync(method, model, opts) {
+ var name = model.config.adapter.collection_name,
+ data = model.config.data,
+ resp = null;
+
+ function storeModel(data) {
+ localStorage.setItem(name, JSON.stringify(data));
+ }
+
+ switch (method) {
+
+ case 'create':
+ if (!model.id) {
+ model.id = guid();
+ model.set(model.idAttribute, model.id);
+ }
+ data[model.id] = model;
+ storeModel(data);
+ resp = model.toJSON();
+ break;
+
+ case 'read':
+ var store = localStorage.getItem(name);
+ var store_data = store && JSON.parse(store) || {};
+
+ var len = 0;
+ for (var key in store_data) {
+ var m = new model.config.Model(store_data[key]);
+ model.models.push(m);
+ len++;
+ }
+
+ model.length = len;
+ if (len === 1) {
+ resp = model.models[0];
+ } else {
+ resp = model.models;
+ }
+ break;
+
+ case 'update':
+ data[model.id] = model;
+ storeModel(data);
+ resp = model.toJSON();
+ break;
+
+ case 'delete':
+ delete data[model.id];
+ storeModel(data);
+ resp = model.toJSON();
+ break;}
+
+
+ // process success/error handlers, if present
+ if (resp) {
+ if (_.isFunction(opts.success)) {opts.success(resp);}
+ if (method === 'read') {model.trigger('fetch');}
+ } else {
+ if (_.isFunction(opts.error)) {opts.error(resp);}
+ }
+}
+
+module.exports.sync = Sync;
+
+module.exports.beforeModelCreate = function (config) {
+ config = config || {};
+
+ config.data = {}; // for localStorage or case where entire collection is needed to maintain store
+
+ InitAdapter();
+
+ return config;
+};
+
+module.exports.afterModelCreate = function (Model) {
+ Model = Model || {};
+
+ Model.prototype.config.Model = Model; // needed for fetch operations to initialize the collection from persistent store
+
+ return Model;
+};
\ No newline at end of file
diff --git a/app/Resources/android/alloy/sync/properties.js b/app/Resources/android/alloy/sync/properties.js
new file mode 100755
index 0000000..0110b14
--- /dev/null
+++ b/app/Resources/android/alloy/sync/properties.js
@@ -0,0 +1,70 @@
+var Alloy = require('/alloy'),
+_ = require('/alloy/underscore')._,
+TAP = Ti.App.Properties;
+
+function S4() {
+ return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
+}
+
+function guid() {
+ return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
+}
+
+function Sync(method, model, opts) {
+ var prefix = model.config.adapter.collection_name ? model.config.adapter.collection_name : 'default';
+ var regex = new RegExp('^(' + prefix + ')\\-(.+)$');
+ var resp = null;
+
+ if (method === 'read') {
+ if (model instanceof Backbone.Collection) {
+ // is collection
+ var list = [];
+ _.each(TAP.listProperties(), function (prop) {
+ var match = prop.match(regex);
+ if (match !== null) {
+ list.push(TAP.getObject(prop));
+ }
+ });
+ resp = list;
+ } else {
+ // is model
+ var obj = TAP.getObject(prefix + '-' + model.id);
+ model.set(obj);
+ resp = model.toJSON();
+ }
+ } else if (method === 'create' || method === 'update') {
+ if (!model.id) {
+ model.id = guid();
+ model.set(model.idAttribute, model.id);
+ }
+ TAP.setObject(prefix + '-' + model.id, model.toJSON() || {});
+ resp = model.toJSON();
+ } else if (method === 'delete') {
+ TAP.removeProperty(prefix + '-' + model.id);
+ model.clear();
+ resp = model.toJSON();
+ }
+
+ // process success/error handlers, if present
+ if (resp) {
+ if (_.isFunction(opts.success)) {opts.success(resp);}
+ if (method === 'read') {model.trigger('fetch');}
+ } else {
+ if (_.isFunction(opts.error)) {opts.error(resp);}
+ }
+}
+
+module.exports.sync = Sync;
+module.exports.beforeModelCreate = function (config) {
+ // make sure we have a populated model object
+ config = config || {};
+ config.columns = config.columns || {};
+ config.defaults = config.defaults || {};
+
+ // give it a default id if it doesn't exist already
+ if (typeof config.columns.id === 'undefined' || config.columns.id === null) {
+ config.columns.id = 'String';
+ }
+
+ return config;
+};
\ No newline at end of file
diff --git a/app/Resources/android/alloy/sync/sql.js b/app/Resources/android/alloy/sync/sql.js
new file mode 100755
index 0000000..ac7330a
--- /dev/null
+++ b/app/Resources/android/alloy/sync/sql.js
@@ -0,0 +1,510 @@
+var _ = require('/alloy/underscore')._,
+backbone = require('/alloy/backbone');
+
+// The database name used when none is specified in the
+// model configuration.
+var ALLOY_DB_DEFAULT = '_alloy_';
+var ALLOY_ID_DEFAULT = 'alloy_id';
+
+function S4() {
+ return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
+}
+
+function guid() {
+ return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
+}
+
+var cache = {
+ config: {},
+ Model: {},
+ db: {} };
+
+
+function getDatabase(name) {
+ if (!cache.db[name]) {
+ cache.db[name] = Ti.Database.open(name);
+ }
+ return cache.db[name];
+}
+
+// The sql-specific migration object, which is the main parameter
+// to the up() and down() migration functions.
+//
+// db The database handle for migration processing. Do not open
+// or close this as it is a running transaction that ensures
+// data integrity during the migration process.
+// dbname The name of the SQLite database for this model.
+// table The name of the SQLite table for this model.
+// idAttribute The unique ID column for this model, which is
+// mapped back to Backbone.js for its update and
+// delete operations.
+function Migrator(config, transactionDb) {
+ this.db = transactionDb;
+ this.dbname = config.adapter.db_name;
+ this.table = config.adapter.collection_name;
+ this.idAttribute = config.adapter.idAttribute;
+
+ //TODO: normalize columns at compile time - https://jira.appcelerator.org/browse/ALOY-222
+ this.column = function (name) {
+ // split into parts to keep additional column characteristics like
+ // autoincrement, primary key, etc...
+ var parts = name.split(/\s+/);
+ var type = parts[0];
+ switch (type.toLowerCase()) {
+ case 'string':
+ case 'varchar':
+ case 'date':
+ case 'datetime':
+ Ti.API.warn('"' + type + '" is not a valid sqlite field, using TEXT instead');
+ case 'text':
+ type = 'TEXT';
+ break;
+ case 'int':
+ case 'tinyint':
+ case 'smallint':
+ case 'bigint':
+ case 'boolean':
+ Ti.API.warn('"' + type + '" is not a valid sqlite field, using INTEGER instead');
+ case 'integer':
+ type = 'INTEGER';
+ break;
+ case 'double':
+ case 'float':
+ case 'decimal':
+ case 'number':
+ Ti.API.warn('"' + name + '" is not a valid sqlite field, using REAL instead');
+ case 'real':
+ type = 'REAL';
+ break;
+ case 'blob':
+ type = 'BLOB';
+ break;
+ case 'null':
+ type = 'NULL';
+ break;
+ default:
+ type = 'TEXT';
+ break;}
+
+ parts[0] = type;
+ return parts.join(' ');
+ };
+
+ this.createTable = function (config) {
+ // compose the create query
+ var columns = [];
+ var found = false;
+ for (var k in config.columns) {
+ if (k === this.idAttribute) {found = true;}
+ columns.push(k + ' ' + this.column(config.columns[k]));
+ }
+
+ // add the id field if it wasn't specified
+ if (!found && this.idAttribute === ALLOY_ID_DEFAULT) {
+ columns.push(ALLOY_ID_DEFAULT + ' TEXT UNIQUE');
+ }
+ var sql = 'CREATE TABLE IF NOT EXISTS ' + this.table + ' ( ' + columns.join(',') + ')';
+
+ // execute the create
+ this.db.execute(sql);
+ };
+
+ this.dropTable = function () {
+ this.db.execute('DROP TABLE IF EXISTS ' + this.table);
+ };
+
+ this.insertRow = function (columnValues) {
+ var columns = [];
+ var values = [];
+ var qs = [];
+
+ // get arrays of column names, values, and value placeholders
+ var found = false;
+ for (var key in columnValues) {
+ if (key === this.idAttribute) {found = true;}
+ columns.push(key);
+ values.push(columnValues[key]);
+ qs.push('?');
+ }
+
+ // add the id field if it wasn't specified
+ if (!found && this.idAttribute === ALLOY_ID_DEFAULT) {
+ columns.push(this.idAttribute);
+ values.push(guid());
+ qs.push('?');
+ }
+
+ // construct and execute the query
+ this.db.execute('INSERT INTO ' + this.table + ' (' + columns.join(',') + ') VALUES (' + qs.join(',') + ');', values);
+ };
+
+ this.deleteRow = function (columns) {
+ var sql = 'DELETE FROM ' + this.table;
+ var keys = _.keys(columns);
+ var len = keys.length;
+ var conditions = [];
+ var values = [];
+
+ // construct the where clause, if necessary
+ if (len) {sql += ' WHERE ';}
+ for (var i = 0; i < len; i++) {
+ conditions.push(keys[i] + ' = ?');
+ values.push(columns[keys[i]]);
+ }
+ sql += conditions.join(' AND ');
+
+ // execute the delete
+ this.db.execute(sql, values);
+ };
+}
+
+function Sync(method, model, opts) {
+ var table = model.config.adapter.collection_name,
+ columns = model.config.columns,
+ dbName = model.config.adapter.db_name || ALLOY_DB_DEFAULT,
+ resp = null,
+ db,sql;
+
+ switch (method) {
+ case 'create':
+ case 'update':
+ resp = function () {
+ var attrObj = {};
+
+ if (!model.id) {
+ model.id = model.idAttribute === ALLOY_ID_DEFAULT ? guid() : null;
+ attrObj[model.idAttribute] = model.id;
+ backbone.VERSION === '0.9.2' ? model.set(attrObj, { silent: true }) : model.set(attrObj);
+ }
+
+ // assemble columns and values
+ var names = [],values = [],q = [];
+ for (var k in columns) {
+ names.push(k);
+ values.push(model.get(k));
+ q.push('?');
+ }
+
+ // execute the query
+ sql = 'REPLACE INTO ' + table + ' (' + names.join(',') + ') VALUES (' + q.join(',') + ');';
+ db = getDatabase(dbName);
+ db.execute(sql, values);
+
+ // if model.id is still null, grab the last inserted id
+ if (model.id === null) {
+ model.id = db.lastInsertRowId;
+ attrObj[model.idAttribute] = model.id;
+ backbone.VERSION === '0.9.2' ? model.set(attrObj, { silent: true }) : model.set(attrObj);
+ }
+
+ return model.toJSON();
+ }();
+ break;
+
+ case 'read':
+ // print warning about using both id and query
+ if (opts.query && opts.id) {
+ Ti.API.warn('Both "query" and "id" options were specified for model.fetch(). "id" will be ignored.');
+ }
+
+ // determine the query to execute
+ sql = 'SELECT * FROM ' + table;
+ if (opts.query) {
+ sql = opts.query;
+ } else if (opts.id) {
+ sql += ' WHERE ' + (model.idAttribute ? model.idAttribute : ALLOY_ID_DEFAULT) + ' = ' + (_.isString(opts.id) ? '"' + opts.id + '"' : opts.id);
+ }
+
+ // execute the select query
+ db = getDatabase(dbName);
+ var rs;
+
+ // is it a string or a prepared statement?
+ if (_.isString(sql)) {
+ rs = db.execute(sql);
+ } else {
+ rs = db.execute(sql.statement, sql.params);
+ }
+
+ var values = [];
+ var fieldNames = [];
+ var fieldCount = _.isFunction(rs.fieldCount) ? rs.fieldCount() : rs.fieldCount;
+ var i = 0;
+
+ for (; i < fieldCount; i++) {
+ fieldNames.push(rs.fieldName(i));
+ }
+
+ // iterate through all queried rows
+ while (rs.isValidRow()) {
+ var o = {};
+ for (i = 0; i < fieldCount; i++) {
+ o[fieldNames[i]] = rs.field(i);
+ }
+ values.push(o);
+ rs.next();
+ }
+ rs.close();
+
+ // shape response based on whether it's a model or collection
+ var len = values.length;
+
+ if (backbone.VERSION === '0.9.2') {
+ model.length = len;
+ }
+
+ resp = len === 1 ? values[0] : values;
+ break;
+
+ case 'delete':
+ sql = 'DELETE FROM ' + table + ' WHERE ' + model.idAttribute + '=?';
+
+ // execute the delete
+ db = getDatabase(dbName);
+ db.execute(sql, model.id);
+
+ resp = model.toJSON();
+ break;}
+
+
+ // process success/error handlers, if present
+ if (resp) {
+ if (_.isFunction(opts.success)) {opts.success(resp);}
+ if (method === 'read' && !opts.silent) {model.trigger('fetch', { fromAdapter: true });}
+ } else {
+ if (_.isFunction(opts.error)) {opts.error(resp);}
+ }
+
+}
+
+// Gets the current saved migration
+function GetMigrationFor(dbname, table) {
+ var mid = null;
+ var db = getDatabase(dbname);
+ db.execute('CREATE TABLE IF NOT EXISTS migrations (latest TEXT, model TEXT);');
+ var rs = db.execute('SELECT latest FROM migrations where model = ?;', table);
+ if (rs.isValidRow()) {
+ mid = rs.field(0) + '';
+ }
+ rs.close();
+ return mid;
+}
+
+function Migrate(Model) {
+ // get list of migrations for this model
+ var migrations = Model.migrations || [];
+
+ // get a reference to the last migration
+ var lastMigration = {};
+ if (migrations.length) {migrations[migrations.length - 1](lastMigration);}
+
+ // Get config reference
+ var config = Model.prototype.config;
+
+ // Get the db name for this model and set up the sql migration obejct
+ config.adapter.db_name = config.adapter.db_name || ALLOY_DB_DEFAULT;
+ var migrator = new Migrator(config);
+
+ // Get the migration number from the config, or use the number of
+ // the last migration if it's not present. If we still don't have a
+ // migration number after that, that means there are none. There's
+ // no migrations to perform.
+ var targetNumber = typeof config.adapter.migration === 'undefined' ||
+ config.adapter.migration === null ? lastMigration.id : config.adapter.migration;
+ if (typeof targetNumber === 'undefined' || targetNumber === null) {
+ var tmpDb = getDatabase(config.adapter.db_name);
+ migrator.db = tmpDb;
+ migrator.createTable(config);
+ return;
+ }
+ targetNumber = targetNumber + ''; // ensure that it's a string
+
+ // Create the migration tracking table if it doesn't already exist.
+ // Get the current saved migration number.
+ var currentNumber = GetMigrationFor(config.adapter.db_name, config.adapter.collection_name);
+
+ // If the current and requested migrations match, the data structures
+ // match and there is no need to run the migrations.
+ var direction;
+ if (currentNumber === targetNumber) {
+ return;
+ } else if (currentNumber && currentNumber > targetNumber) {
+ direction = 0; // rollback
+ migrations.reverse();
+ } else {
+ direction = 1; // upgrade
+ }
+
+ // open db for our migration transaction
+ var db = getDatabase(config.adapter.db_name);
+ migrator.db = db;
+ db.execute('BEGIN;');
+
+ // iterate through all migrations based on the current and requested state,
+ // applying all appropriate migrations, in order, to the database.
+ if (migrations.length) {
+ for (var i = 0; i < migrations.length; i++) {
+ // create the migration context
+ var migration = migrations[i];
+ var context = {};
+ migration(context);
+
+ // if upgrading, skip migrations higher than the target
+ // if rolling back, skip migrations lower than the target
+ if (direction) {
+ if (context.id > targetNumber) {break;}
+ if (context.id <= currentNumber) {continue;}
+ } else {
+ if (context.id <= targetNumber) {break;}
+ if (context.id > currentNumber) {continue;}
+ }
+
+ // execute the appropriate migration function
+ var funcName = direction ? 'up' : 'down';
+ if (_.isFunction(context[funcName])) {
+ context[funcName](migrator, config);
+ }
+ }
+ } else {
+ migrator.createTable(config);
+ }
+
+ // update the saved migration in the db
+ db.execute('DELETE FROM migrations where model = ?', config.adapter.collection_name);
+ db.execute('INSERT INTO migrations VALUES (?,?)', targetNumber, config.adapter.collection_name);
+
+ // end the migration transaction
+ db.execute('COMMIT;');
+ migrator.db = null;
+}
+
+function installDatabase(config) {
+ // get the database name from the db file path
+ var dbFile = _.isFunction(config.adapter.db_file) ? config.adapter.db_file(config) : config.adapter.db_file;
+ var table = config.adapter.collection_name;
+
+ var rx = /(^|.*\/)([^\/]+)\.[^\/]+$/;
+ var match = dbFile.match(rx);
+ if (match === null) {
+ throw 'Invalid sql database filename "' + dbFile + '"';
+ }
+ //var isAbsolute = match[1] ? true : false;
+ config.adapter.db_name = config.adapter.db_name || match[2];
+ var dbName = config.adapter.db_name;
+
+ // install and open the preloaded db
+ Ti.API.debug('Installing sql database "' + dbFile + '" with name "' + dbName + '"');
+ var db = Ti.Database.install(dbFile, dbName);
+ cache.db[dbName] = db;
+
+ // set remoteBackup status for iOS
+ if (config.adapter.remoteBackup === false && false) {
+ Ti.API.debug('iCloud "do not backup" flag set for database "' + dbFile + '"');
+ db.file.setRemoteBackup(false);
+ }
+
+ // compose config.columns from table definition in database
+ var rs = db.execute('pragma table_info("' + table + '");');
+ var columns = {},cName,cType;
+ if (rs) {
+ while (rs.isValidRow()) {
+ cName = rs.fieldByName('name');
+ cType = rs.fieldByName('type');
+ columns[cName] = cType;
+
+ // see if it already has the ALLOY_ID_DEFAULT
+ if (cName === ALLOY_ID_DEFAULT && !config.adapter.idAttribute) {
+ config.adapter.idAttribute = ALLOY_ID_DEFAULT;
+ }
+
+ rs.next();
+ }
+ rs.close();
+ }
+ if (Object.keys(columns).length === 0) {
+ var idAttribute = config.adapter.idAttribute ? config.adapter.idAttribute : ALLOY_ID_DEFAULT;
+ for (var k in config.columns) {
+ cName = k;
+ cType = config.columns[k];
+
+ // see if it already has the ALLOY_ID_DEFAULT
+ if (cName === ALLOY_ID_DEFAULT && !config.adapter.idAttribute) {
+ config.adapter.idAttribute = ALLOY_ID_DEFAULT;
+ } else if (k === config.adapter.idAttribute) {
+ cType += ' UNIQUE';
+ }
+ columns[cName] = cType;
+ }
+ }
+ config.columns = columns;
+
+ // make sure we have a unique id field
+ if (config.adapter.idAttribute) {
+ if (!_.contains(_.keys(config.columns), config.adapter.idAttribute)) {
+ throw 'config.adapter.idAttribute "' + config.adapter.idAttribute + '" not found in list of columns for table "' + table + '"\n' +
+ 'columns: [' + _.keys(config.columns).join(',') + ']';
+ }
+ } else {
+ Ti.API.info('No config.adapter.idAttribute specified for table "' + table + '"');
+ Ti.API.info('Adding "' + ALLOY_ID_DEFAULT + '" to uniquely identify rows');
+
+ var fullStrings = [],
+ colStrings = [];
+ _.each(config.columns, function (type, name) {
+ colStrings.push(name);
+ fullStrings.push(name + ' ' + type);
+ });
+ var colsString = colStrings.join(',');
+ db.execute('ALTER TABLE ' + table + ' RENAME TO ' + table + '_temp;');
+ db.execute('CREATE TABLE ' + table + '(' + fullStrings.join(',') + ',' + ALLOY_ID_DEFAULT + ' TEXT UNIQUE);');
+ db.execute('INSERT INTO ' + table + '(' + colsString + ',' + ALLOY_ID_DEFAULT + ') SELECT ' + colsString + ',CAST(_ROWID_ AS TEXT) FROM ' + table + '_temp;');
+ db.execute('DROP TABLE ' + table + '_temp;');
+ config.columns[ALLOY_ID_DEFAULT] = 'TEXT UNIQUE';
+ config.adapter.idAttribute = ALLOY_ID_DEFAULT;
+ }
+}
+
+module.exports.beforeModelCreate = function (config, name) {
+ // use cached config if it exists
+ if (cache.config[name]) {
+ return cache.config[name];
+ }
+
+ // check platform compatibility
+ if ("android" === 'mobileweb' || typeof Ti.Database === 'undefined') {
+ throw 'No support for Titanium.Database in MobileWeb environment.';
+ }
+
+ // install database file, if specified
+ if (config.adapter.db_file) {installDatabase(config);}
+ if (!config.adapter.idAttribute) {
+ Ti.API.info('No config.adapter.idAttribute specified for table "' + config.adapter.collection_name + '"');
+ Ti.API.info('Adding "' + ALLOY_ID_DEFAULT + '" to uniquely identify rows');
+ config.columns[ALLOY_ID_DEFAULT] = 'TEXT UNIQUE';
+ config.adapter.idAttribute = ALLOY_ID_DEFAULT;
+ }
+
+ // add this config to the cache
+ cache.config[name] = config;
+
+ return config;
+};
+
+module.exports.afterModelCreate = function (Model, name) {
+ // use cached Model class if it exists
+ if (cache.Model[name]) {
+ return cache.Model[name];
+ }
+
+ // create and migrate the Model class
+ Model = Model || {};
+ Model.prototype.idAttribute = Model.prototype.config.adapter.idAttribute;
+ Migrate(Model);
+
+ // Add the Model class to the cache
+ cache.Model[name] = Model;
+
+ return Model;
+};
+
+module.exports.sync = Sync;
\ No newline at end of file
diff --git a/app/Resources/android/alloy/underscore.js b/app/Resources/android/alloy/underscore.js
new file mode 100644
index 0000000..8219dc5
--- /dev/null
+++ b/app/Resources/android/alloy/underscore.js
@@ -0,0 +1,1692 @@
+// Underscore.js 1.9.1
+// http://underscorejs.org
+// (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+ // Baseline setup
+ // --------------
+
+ // Establish the root object, `window` (`self`) in the browser, `global`
+ // on the server, or `this` in some virtual machines. We use `self`
+ // instead of `window` for `WebWorker` support.
+ var root = typeof self == 'object' && self.self === self && self ||
+ typeof global == 'object' && global.global === global && global ||
+ this ||
+ {};
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype;
+ var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeCreate = Object.create;
+
+ // Naked function reference for surrogate-prototype-swapping.
+ var Ctor = function(){};
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this._wrapped = obj;
+ };
+
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for their old module API. If we're in
+ // the browser, add `_` as a global object.
+ // (`nodeType` is checked to ensure that `module`
+ // and `exports` are not HTML elements.)
+ if (typeof exports != 'undefined' && !exports.nodeType) {
+ if (typeof module != 'undefined' && !module.nodeType && module.exports) {
+ exports = module.exports = _;
+ }
+ exports._ = _;
+ } else {
+ root._ = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.9.1';
+
+ // Internal function that returns an efficient (for current engines) version
+ // of the passed-in callback, to be repeatedly applied in other Underscore
+ // functions.
+ var optimizeCb = function(func, context, argCount) {
+ if (context === void 0) return func;
+ switch (argCount == null ? 3 : argCount) {
+ case 1: return function(value) {
+ return func.call(context, value);
+ };
+ // The 2-argument case is omitted because we’re not using it.
+ case 3: return function(value, index, collection) {
+ return func.call(context, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(context, accumulator, value, index, collection);
+ };
+ }
+ return function() {
+ return func.apply(context, arguments);
+ };
+ };
+
+ var builtinIteratee;
+
+ // An internal function to generate callbacks that can be applied to each
+ // element in a collection, returning the desired result — either `identity`,
+ // an arbitrary callback, a property matcher, or a property accessor.
+ var cb = function(value, context, argCount) {
+ if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
+ if (value == null) return _.identity;
+ if (_.isFunction(value)) return optimizeCb(value, context, argCount);
+ if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
+ return _.property(value);
+ };
+
+ // External wrapper for our callback generator. Users may customize
+ // `_.iteratee` if they want additional predicate/iteratee shorthand styles.
+ // This abstraction hides the internal-only argCount argument.
+ _.iteratee = builtinIteratee = function(value, context) {
+ return cb(value, context, Infinity);
+ };
+
+ // Some functions take a variable number of arguments, or a few expected
+ // arguments at the beginning and then a variable number of values to operate
+ // on. This helper accumulates all remaining arguments past the function’s
+ // argument length (or an explicit `startIndex`), into an array that becomes
+ // the last argument. Similar to ES6’s "rest parameter".
+ var restArguments = function(func, startIndex) {
+ startIndex = startIndex == null ? func.length - 1 : +startIndex;
+ return function() {
+ var length = Math.max(arguments.length - startIndex, 0),
+ rest = Array(length),
+ index = 0;
+ for (; index < length; index++) {
+ rest[index] = arguments[index + startIndex];
+ }
+ switch (startIndex) {
+ case 0: return func.call(this, rest);
+ case 1: return func.call(this, arguments[0], rest);
+ case 2: return func.call(this, arguments[0], arguments[1], rest);
+ }
+ var args = Array(startIndex + 1);
+ for (index = 0; index < startIndex; index++) {
+ args[index] = arguments[index];
+ }
+ args[startIndex] = rest;
+ return func.apply(this, args);
+ };
+ };
+
+ // An internal function for creating a new object that inherits from another.
+ var baseCreate = function(prototype) {
+ if (!_.isObject(prototype)) return {};
+ if (nativeCreate) return nativeCreate(prototype);
+ Ctor.prototype = prototype;
+ var result = new Ctor;
+ Ctor.prototype = null;
+ return result;
+ };
+
+ var shallowProperty = function(key) {
+ return function(obj) {
+ return obj == null ? void 0 : obj[key];
+ };
+ };
+
+ var has = function(obj, path) {
+ return obj != null && hasOwnProperty.call(obj, path);
+ }
+
+ var deepGet = function(obj, path) {
+ var length = path.length;
+ for (var i = 0; i < length; i++) {
+ if (obj == null) return void 0;
+ obj = obj[path[i]];
+ }
+ return length ? obj : void 0;
+ };
+
+ // Helper for collection methods to determine whether a collection
+ // should be iterated as an array or as an object.
+ // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+ // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
+ var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+ var getLength = shallowProperty('length');
+ var isArrayLike = function(collection) {
+ var length = getLength(collection);
+ return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+ };
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles raw objects in addition to array-likes. Treats all
+ // sparse array-likes as if they were dense.
+ _.each = _.forEach = function(obj, iteratee, context) {
+ iteratee = optimizeCb(iteratee, context);
+ var i, length;
+ if (isArrayLike(obj)) {
+ for (i = 0, length = obj.length; i < length; i++) {
+ iteratee(obj[i], i, obj);
+ }
+ } else {
+ var keys = _.keys(obj);
+ for (i = 0, length = keys.length; i < length; i++) {
+ iteratee(obj[keys[i]], keys[i], obj);
+ }
+ }
+ return obj;
+ };
+
+ // Return the results of applying the iteratee to each element.
+ _.map = _.collect = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ results = Array(length);
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ results[index] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
+ };
+
+ // Create a reducing function iterating left or right.
+ var createReduce = function(dir) {
+ // Wrap code that reassigns argument variables in a separate function than
+ // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
+ var reducer = function(obj, iteratee, memo, initial) {
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length,
+ index = dir > 0 ? 0 : length - 1;
+ if (!initial) {
+ memo = obj[keys ? keys[index] : index];
+ index += dir;
+ }
+ for (; index >= 0 && index < length; index += dir) {
+ var currentKey = keys ? keys[index] : index;
+ memo = iteratee(memo, obj[currentKey], currentKey, obj);
+ }
+ return memo;
+ };
+
+ return function(obj, iteratee, memo, context) {
+ var initial = arguments.length >= 3;
+ return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
+ };
+ };
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`.
+ _.reduce = _.foldl = _.inject = createReduce(1);
+
+ // The right-associative version of reduce, also known as `foldr`.
+ _.reduceRight = _.foldr = createReduce(-1);
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, predicate, context) {
+ var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
+ var key = keyFinder(obj, predicate, context);
+ if (key !== void 0 && key !== -1) return obj[key];
+ };
+
+ // Return all the elements that pass a truth test.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, predicate, context) {
+ var results = [];
+ predicate = cb(predicate, context);
+ _.each(obj, function(value, index, list) {
+ if (predicate(value, index, list)) results.push(value);
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, predicate, context) {
+ return _.filter(obj, _.negate(cb(predicate)), context);
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (!predicate(obj[currentKey], currentKey, obj)) return false;
+ }
+ return true;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Aliased as `any`.
+ _.some = _.any = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = !isArrayLike(obj) && _.keys(obj),
+ length = (keys || obj).length;
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys ? keys[index] : index;
+ if (predicate(obj[currentKey], currentKey, obj)) return true;
+ }
+ return false;
+ };
+
+ // Determine if the array or object contains a given item (using `===`).
+ // Aliased as `includes` and `include`.
+ _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ if (typeof fromIndex != 'number' || guard) fromIndex = 0;
+ return _.indexOf(obj, item, fromIndex) >= 0;
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = restArguments(function(obj, path, args) {
+ var contextPath, func;
+ if (_.isFunction(path)) {
+ func = path;
+ } else if (_.isArray(path)) {
+ contextPath = path.slice(0, -1);
+ path = path[path.length - 1];
+ }
+ return _.map(obj, function(context) {
+ var method = func;
+ if (!method) {
+ if (contextPath && contextPath.length) {
+ context = deepGet(context, contextPath);
+ }
+ if (context == null) return void 0;
+ method = context[path];
+ }
+ return method == null ? method : method.apply(context, args);
+ });
+ });
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, _.property(key));
+ };
+
+ // Convenience version of a common use case of `filter`: selecting only objects
+ // containing specific `key:value` pairs.
+ _.where = function(obj, attrs) {
+ return _.filter(obj, _.matcher(attrs));
+ };
+
+ // Convenience version of a common use case of `find`: getting the first object
+ // containing specific `key:value` pairs.
+ _.findWhere = function(obj, attrs) {
+ return _.find(obj, _.matcher(attrs));
+ };
+
+ // Return the maximum element (or element-based computation).
+ _.max = function(obj, iteratee, context) {
+ var result = -Infinity, lastComputed = -Infinity,
+ value, computed;
+ if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value != null && value > result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(v, index, list) {
+ computed = iteratee(v, index, list);
+ if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+ result = v;
+ lastComputed = computed;
+ }
+ });
+ }
+ return result;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iteratee, context) {
+ var result = Infinity, lastComputed = Infinity,
+ value, computed;
+ if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
+ obj = isArrayLike(obj) ? obj : _.values(obj);
+ for (var i = 0, length = obj.length; i < length; i++) {
+ value = obj[i];
+ if (value != null && value < result) {
+ result = value;
+ }
+ }
+ } else {
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(v, index, list) {
+ computed = iteratee(v, index, list);
+ if (computed < lastComputed || computed === Infinity && result === Infinity) {
+ result = v;
+ lastComputed = computed;
+ }
+ });
+ }
+ return result;
+ };
+
+ // Shuffle a collection.
+ _.shuffle = function(obj) {
+ return _.sample(obj, Infinity);
+ };
+
+ // Sample **n** random values from a collection using the modern version of the
+ // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+ // If **n** is not specified, returns a single random element.
+ // The internal `guard` argument allows it to work with `map`.
+ _.sample = function(obj, n, guard) {
+ if (n == null || guard) {
+ if (!isArrayLike(obj)) obj = _.values(obj);
+ return obj[_.random(obj.length - 1)];
+ }
+ var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
+ var length = getLength(sample);
+ n = Math.max(Math.min(n, length), 0);
+ var last = length - 1;
+ for (var index = 0; index < n; index++) {
+ var rand = _.random(index, last);
+ var temp = sample[index];
+ sample[index] = sample[rand];
+ sample[rand] = temp;
+ }
+ return sample.slice(0, n);
+ };
+
+ // Sort the object's values by a criterion produced by an iteratee.
+ _.sortBy = function(obj, iteratee, context) {
+ var index = 0;
+ iteratee = cb(iteratee, context);
+ return _.pluck(_.map(obj, function(value, key, list) {
+ return {
+ value: value,
+ index: index++,
+ criteria: iteratee(value, key, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0) return 1;
+ if (a < b || b === void 0) return -1;
+ }
+ return left.index - right.index;
+ }), 'value');
+ };
+
+ // An internal function used for aggregate "group by" operations.
+ var group = function(behavior, partition) {
+ return function(obj, iteratee, context) {
+ var result = partition ? [[], []] : {};
+ iteratee = cb(iteratee, context);
+ _.each(obj, function(value, index) {
+ var key = iteratee(value, index, obj);
+ behavior(result, value, key);
+ });
+ return result;
+ };
+ };
+
+ // Groups the object's values by a criterion. Pass either a string attribute
+ // to group by, or a function that returns the criterion.
+ _.groupBy = group(function(result, value, key) {
+ if (has(result, key)) result[key].push(value); else result[key] = [value];
+ });
+
+ // Indexes the object's values by a criterion, similar to `groupBy`, but for
+ // when you know that your index values will be unique.
+ _.indexBy = group(function(result, value, key) {
+ result[key] = value;
+ });
+
+ // Counts instances of an object that group by a certain criterion. Pass
+ // either a string attribute to count by, or a function that returns the
+ // criterion.
+ _.countBy = group(function(result, value, key) {
+ if (has(result, key)) result[key]++; else result[key] = 1;
+ });
+
+ var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
+ // Safely create a real, live array from anything iterable.
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (_.isString(obj)) {
+ // Keep surrogate pair characters together
+ return obj.match(reStrSymbol);
+ }
+ if (isArrayLike(obj)) return _.map(obj, _.identity);
+ return _.values(obj);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ if (obj == null) return 0;
+ return isArrayLike(obj) ? obj.length : _.keys(obj).length;
+ };
+
+ // Split a collection into two arrays: one whose elements all satisfy the given
+ // predicate, and one whose elements all do not satisfy the predicate.
+ _.partition = group(function(result, value, pass) {
+ result[pass ? 0 : 1].push(value);
+ }, true);
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
+ if (array == null || array.length < 1) return n == null ? void 0 : [];
+ if (n == null || guard) return array[0];
+ return _.initial(array, array.length - n);
+ };
+
+ // Returns everything but the last entry of the array. Especially useful on
+ // the arguments object. Passing **n** will return all the values in
+ // the array, excluding the last N.
+ _.initial = function(array, n, guard) {
+ return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+ };
+
+ // Get the last element of an array. Passing **n** will return the last N
+ // values in the array.
+ _.last = function(array, n, guard) {
+ if (array == null || array.length < 1) return n == null ? void 0 : [];
+ if (n == null || guard) return array[array.length - 1];
+ return _.rest(array, Math.max(0, array.length - n));
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+ // Especially useful on the arguments object. Passing an **n** will return
+ // the rest N values in the array.
+ _.rest = _.tail = _.drop = function(array, n, guard) {
+ return slice.call(array, n == null || guard ? 1 : n);
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, Boolean);
+ };
+
+ // Internal implementation of a recursive `flatten` function.
+ var flatten = function(input, shallow, strict, output) {
+ output = output || [];
+ var idx = output.length;
+ for (var i = 0, length = getLength(input); i < length; i++) {
+ var value = input[i];
+ if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
+ // Flatten current level of array or arguments object.
+ if (shallow) {
+ var j = 0, len = value.length;
+ while (j < len) output[idx++] = value[j++];
+ } else {
+ flatten(value, shallow, strict, output);
+ idx = output.length;
+ }
+ } else if (!strict) {
+ output[idx++] = value;
+ }
+ }
+ return output;
+ };
+
+ // Flatten out an array, either recursively (by default), or just one level.
+ _.flatten = function(array, shallow) {
+ return flatten(array, shallow, false);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = restArguments(function(array, otherArrays) {
+ return _.difference(array, otherArrays);
+ });
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // The faster algorithm will not work with an iteratee if the iteratee
+ // is not a one-to-one function, so providing an iteratee will disable
+ // the faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+ if (!_.isBoolean(isSorted)) {
+ context = iteratee;
+ iteratee = isSorted;
+ isSorted = false;
+ }
+ if (iteratee != null) iteratee = cb(iteratee, context);
+ var result = [];
+ var seen = [];
+ for (var i = 0, length = getLength(array); i < length; i++) {
+ var value = array[i],
+ computed = iteratee ? iteratee(value, i, array) : value;
+ if (isSorted && !iteratee) {
+ if (!i || seen !== computed) result.push(value);
+ seen = computed;
+ } else if (iteratee) {
+ if (!_.contains(seen, computed)) {
+ seen.push(computed);
+ result.push(value);
+ }
+ } else if (!_.contains(result, value)) {
+ result.push(value);
+ }
+ }
+ return result;
+ };
+
+ // Produce an array that contains the union: each distinct element from all of
+ // the passed-in arrays.
+ _.union = restArguments(function(arrays) {
+ return _.uniq(flatten(arrays, true, true));
+ });
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersection = function(array) {
+ var result = [];
+ var argsLength = arguments.length;
+ for (var i = 0, length = getLength(array); i < length; i++) {
+ var item = array[i];
+ if (_.contains(result, item)) continue;
+ var j;
+ for (j = 1; j < argsLength; j++) {
+ if (!_.contains(arguments[j], item)) break;
+ }
+ if (j === argsLength) result.push(item);
+ }
+ return result;
+ };
+
+ // Take the difference between one array and a number of other arrays.
+ // Only the elements present in just the first array will remain.
+ _.difference = restArguments(function(array, rest) {
+ rest = flatten(rest, true, true);
+ return _.filter(array, function(value){
+ return !_.contains(rest, value);
+ });
+ });
+
+ // Complement of _.zip. Unzip accepts an array of arrays and groups
+ // each array's elements on shared indices.
+ _.unzip = function(array) {
+ var length = array && _.max(array, getLength).length || 0;
+ var result = Array(length);
+
+ for (var index = 0; index < length; index++) {
+ result[index] = _.pluck(array, index);
+ }
+ return result;
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = restArguments(_.unzip);
+
+ // Converts lists into objects. Pass either a single array of `[key, value]`
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
+ // the corresponding values. Passing by pairs is the reverse of _.pairs.
+ _.object = function(list, values) {
+ var result = {};
+ for (var i = 0, length = getLength(list); i < length; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0]] = list[i][1];
+ }
+ }
+ return result;
+ };
+
+ // Generator function to create the findIndex and findLastIndex functions.
+ var createPredicateIndexFinder = function(dir) {
+ return function(array, predicate, context) {
+ predicate = cb(predicate, context);
+ var length = getLength(array);
+ var index = dir > 0 ? 0 : length - 1;
+ for (; index >= 0 && index < length; index += dir) {
+ if (predicate(array[index], index, array)) return index;
+ }
+ return -1;
+ };
+ };
+
+ // Returns the first index on an array-like that passes a predicate test.
+ _.findIndex = createPredicateIndexFinder(1);
+ _.findLastIndex = createPredicateIndexFinder(-1);
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iteratee, context) {
+ iteratee = cb(iteratee, context, 1);
+ var value = iteratee(obj);
+ var low = 0, high = getLength(array);
+ while (low < high) {
+ var mid = Math.floor((low + high) / 2);
+ if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+ }
+ return low;
+ };
+
+ // Generator function to create the indexOf and lastIndexOf functions.
+ var createIndexFinder = function(dir, predicateFind, sortedIndex) {
+ return function(array, item, idx) {
+ var i = 0, length = getLength(array);
+ if (typeof idx == 'number') {
+ if (dir > 0) {
+ i = idx >= 0 ? idx : Math.max(idx + length, i);
+ } else {
+ length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
+ }
+ } else if (sortedIndex && idx && length) {
+ idx = sortedIndex(array, item);
+ return array[idx] === item ? idx : -1;
+ }
+ if (item !== item) {
+ idx = predicateFind(slice.call(array, i, length), _.isNaN);
+ return idx >= 0 ? idx + i : -1;
+ }
+ for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
+ if (array[idx] === item) return idx;
+ }
+ return -1;
+ };
+ };
+
+ // Return the position of the first occurrence of an item in an array,
+ // or -1 if the item is not included in the array.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
+ _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (stop == null) {
+ stop = start || 0;
+ start = 0;
+ }
+ if (!step) {
+ step = stop < start ? -1 : 1;
+ }
+
+ var length = Math.max(Math.ceil((stop - start) / step), 0);
+ var range = Array(length);
+
+ for (var idx = 0; idx < length; idx++, start += step) {
+ range[idx] = start;
+ }
+
+ return range;
+ };
+
+ // Chunk a single array into multiple arrays, each containing `count` or fewer
+ // items.
+ _.chunk = function(array, count) {
+ if (count == null || count < 1) return [];
+ var result = [];
+ var i = 0, length = array.length;
+ while (i < length) {
+ result.push(slice.call(array, i, i += count));
+ }
+ return result;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Determines whether to execute a function as a constructor
+ // or a normal function with the provided arguments.
+ var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
+ if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+ var self = baseCreate(sourceFunc.prototype);
+ var result = sourceFunc.apply(self, args);
+ if (_.isObject(result)) return result;
+ return self;
+ };
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+ // available.
+ _.bind = restArguments(function(func, context, args) {
+ if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+ var bound = restArguments(function(callArgs) {
+ return executeBound(func, bound, context, this, args.concat(callArgs));
+ });
+ return bound;
+ });
+
+ // Partially apply a function by creating a version that has had some of its
+ // arguments pre-filled, without changing its dynamic `this` context. _ acts
+ // as a placeholder by default, allowing any combination of arguments to be
+ // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
+ _.partial = restArguments(function(func, boundArgs) {
+ var placeholder = _.partial.placeholder;
+ var bound = function() {
+ var position = 0, length = boundArgs.length;
+ var args = Array(length);
+ for (var i = 0; i < length; i++) {
+ args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
+ }
+ while (position < arguments.length) args.push(arguments[position++]);
+ return executeBound(func, bound, this, this, args);
+ };
+ return bound;
+ });
+
+ _.partial.placeholder = _;
+
+ // Bind a number of an object's methods to that object. Remaining arguments
+ // are the method names to be bound. Useful for ensuring that all callbacks
+ // defined on an object belong to it.
+ _.bindAll = restArguments(function(obj, keys) {
+ keys = flatten(keys, false, false);
+ var index = keys.length;
+ if (index < 1) throw new Error('bindAll must be passed function names');
+ while (index--) {
+ var key = keys[index];
+ obj[key] = _.bind(obj[key], obj);
+ }
+ });
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memoize = function(key) {
+ var cache = memoize.cache;
+ var address = '' + (hasher ? hasher.apply(this, arguments) : key);
+ if (!has(cache, address)) cache[address] = func.apply(this, arguments);
+ return cache[address];
+ };
+ memoize.cache = {};
+ return memoize;
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = restArguments(function(func, wait, args) {
+ return setTimeout(function() {
+ return func.apply(null, args);
+ }, wait);
+ });
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = _.partial(_.delay, _, 1);
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time. Normally, the throttled function will run
+ // as much as it can, without ever going more than once per `wait` duration;
+ // but if you'd like to disable the execution on the leading edge, pass
+ // `{leading: false}`. To disable execution on the trailing edge, ditto.
+ _.throttle = function(func, wait, options) {
+ var timeout, context, args, result;
+ var previous = 0;
+ if (!options) options = {};
+
+ var later = function() {
+ previous = options.leading === false ? 0 : _.now();
+ timeout = null;
+ result = func.apply(context, args);
+ if (!timeout) context = args = null;
+ };
+
+ var throttled = function() {
+ var now = _.now();
+ if (!previous && options.leading === false) previous = now;
+ var remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout);
+ timeout = null;
+ }
+ previous = now;
+ result = func.apply(context, args);
+ if (!timeout) context = args = null;
+ } else if (!timeout && options.trailing !== false) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+
+ throttled.cancel = function() {
+ clearTimeout(timeout);
+ previous = 0;
+ timeout = context = args = null;
+ };
+
+ return throttled;
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
+ var timeout, result;
+
+ var later = function(context, args) {
+ timeout = null;
+ if (args) result = func.apply(context, args);
+ };
+
+ var debounced = restArguments(function(args) {
+ if (timeout) clearTimeout(timeout);
+ if (immediate) {
+ var callNow = !timeout;
+ timeout = setTimeout(later, wait);
+ if (callNow) result = func.apply(this, args);
+ } else {
+ timeout = _.delay(later, wait, this, args);
+ }
+
+ return result;
+ });
+
+ debounced.cancel = function() {
+ clearTimeout(timeout);
+ timeout = null;
+ };
+
+ return debounced;
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return _.partial(wrapper, func);
+ };
+
+ // Returns a negated version of the passed-in predicate.
+ _.negate = function(predicate) {
+ return function() {
+ return !predicate.apply(this, arguments);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var args = arguments;
+ var start = args.length - 1;
+ return function() {
+ var i = start;
+ var result = args[start].apply(this, arguments);
+ while (i--) result = args[i].call(this, result);
+ return result;
+ };
+ };
+
+ // Returns a function that will only be executed on and after the Nth call.
+ _.after = function(times, func) {
+ return function() {
+ if (--times < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ };
+
+ // Returns a function that will only be executed up to (but not including) the Nth call.
+ _.before = function(times, func) {
+ var memo;
+ return function() {
+ if (--times > 0) {
+ memo = func.apply(this, arguments);
+ }
+ if (times <= 1) func = null;
+ return memo;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = _.partial(_.before, 2);
+
+ _.restArguments = restArguments;
+
+ // Object Functions
+ // ----------------
+
+ // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
+ var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
+ var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
+ 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
+
+ var collectNonEnumProps = function(obj, keys) {
+ var nonEnumIdx = nonEnumerableProps.length;
+ var constructor = obj.constructor;
+ var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
+
+ // Constructor is a special case.
+ var prop = 'constructor';
+ if (has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
+
+ while (nonEnumIdx--) {
+ prop = nonEnumerableProps[nonEnumIdx];
+ if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
+ keys.push(prop);
+ }
+ }
+ };
+
+ // Retrieve the names of an object's own properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`.
+ _.keys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ if (nativeKeys) return nativeKeys(obj);
+ var keys = [];
+ for (var key in obj) if (has(obj, key)) keys.push(key);
+ // Ahem, IE < 9.
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
+
+ // Retrieve all the property names of an object.
+ _.allKeys = function(obj) {
+ if (!_.isObject(obj)) return [];
+ var keys = [];
+ for (var key in obj) keys.push(key);
+ // Ahem, IE < 9.
+ if (hasEnumBug) collectNonEnumProps(obj, keys);
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var values = Array(length);
+ for (var i = 0; i < length; i++) {
+ values[i] = obj[keys[i]];
+ }
+ return values;
+ };
+
+ // Returns the results of applying the iteratee to each element of the object.
+ // In contrast to _.map it returns an object.
+ _.mapObject = function(obj, iteratee, context) {
+ iteratee = cb(iteratee, context);
+ var keys = _.keys(obj),
+ length = keys.length,
+ results = {};
+ for (var index = 0; index < length; index++) {
+ var currentKey = keys[index];
+ results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+ }
+ return results;
+ };
+
+ // Convert an object into a list of `[key, value]` pairs.
+ // The opposite of _.object.
+ _.pairs = function(obj) {
+ var keys = _.keys(obj);
+ var length = keys.length;
+ var pairs = Array(length);
+ for (var i = 0; i < length; i++) {
+ pairs[i] = [keys[i], obj[keys[i]]];
+ }
+ return pairs;
+ };
+
+ // Invert the keys and values of an object. The values must be serializable.
+ _.invert = function(obj) {
+ var result = {};
+ var keys = _.keys(obj);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ result[obj[keys[i]]] = keys[i];
+ }
+ return result;
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`.
+ _.functions = _.methods = function(obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
+
+ // An internal function for creating assigner functions.
+ var createAssigner = function(keysFunc, defaults) {
+ return function(obj) {
+ var length = arguments.length;
+ if (defaults) obj = Object(obj);
+ if (length < 2 || obj == null) return obj;
+ for (var index = 1; index < length; index++) {
+ var source = arguments[index],
+ keys = keysFunc(source),
+ l = keys.length;
+ for (var i = 0; i < l; i++) {
+ var key = keys[i];
+ if (!defaults || obj[key] === void 0) obj[key] = source[key];
+ }
+ }
+ return obj;
+ };
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = createAssigner(_.allKeys);
+
+ // Assigns a given object with all the own properties in the passed-in object(s).
+ // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+ _.extendOwn = _.assign = createAssigner(_.keys);
+
+ // Returns the first key on an object that passes a predicate test.
+ _.findKey = function(obj, predicate, context) {
+ predicate = cb(predicate, context);
+ var keys = _.keys(obj), key;
+ for (var i = 0, length = keys.length; i < length; i++) {
+ key = keys[i];
+ if (predicate(obj[key], key, obj)) return key;
+ }
+ };
+
+ // Internal pick helper function to determine if `obj` has key `key`.
+ var keyInObj = function(value, key, obj) {
+ return key in obj;
+ };
+
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = restArguments(function(obj, keys) {
+ var result = {}, iteratee = keys[0];
+ if (obj == null) return result;
+ if (_.isFunction(iteratee)) {
+ if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
+ keys = _.allKeys(obj);
+ } else {
+ iteratee = keyInObj;
+ keys = flatten(keys, false, false);
+ obj = Object(obj);
+ }
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i];
+ var value = obj[key];
+ if (iteratee(value, key, obj)) result[key] = value;
+ }
+ return result;
+ });
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = restArguments(function(obj, keys) {
+ var iteratee = keys[0], context;
+ if (_.isFunction(iteratee)) {
+ iteratee = _.negate(iteratee);
+ if (keys.length > 1) context = keys[1];
+ } else {
+ keys = _.map(flatten(keys, false, false), String);
+ iteratee = function(value, key) {
+ return !_.contains(keys, key);
+ };
+ }
+ return _.pick(obj, iteratee, context);
+ });
+
+ // Fill in a given object with default properties.
+ _.defaults = createAssigner(_.allKeys, true);
+
+ // Creates an object that inherits from the given prototype object.
+ // If additional properties are provided then they will be added to the
+ // created object.
+ _.create = function(prototype, props) {
+ var result = baseCreate(prototype);
+ if (props) _.extendOwn(result, props);
+ return result;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (!_.isObject(obj)) return obj;
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Returns whether an object has a given set of `key:value` pairs.
+ _.isMatch = function(object, attrs) {
+ var keys = _.keys(attrs), length = keys.length;
+ if (object == null) return !length;
+ var obj = Object(object);
+ for (var i = 0; i < length; i++) {
+ var key = keys[i];
+ if (attrs[key] !== obj[key] || !(key in obj)) return false;
+ }
+ return true;
+ };
+
+
+ // Internal recursive comparison function for `isEqual`.
+ var eq, deepEq;
+ eq = function(a, b, aStack, bStack) {
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+ if (a === b) return a !== 0 || 1 / a === 1 / b;
+ // `null` or `undefined` only equal to itself (strict comparison).
+ if (a == null || b == null) return false;
+ // `NaN`s are equivalent, but non-reflexive.
+ if (a !== a) return b !== b;
+ // Exhaust primitive checks
+ var type = typeof a;
+ if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
+ return deepEq(a, b, aStack, bStack);
+ };
+
+ // Internal recursive comparison function for `isEqual`.
+ deepEq = function(a, b, aStack, bStack) {
+ // Unwrap any wrapped objects.
+ if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
+ // Compare `[[Class]]` names.
+ var className = toString.call(a);
+ if (className !== toString.call(b)) return false;
+ switch (className) {
+ // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+ case '[object RegExp]':
+ // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return '' + a === '' + b;
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive.
+ // Object(NaN) is equivalent to NaN.
+ if (+a !== +a) return +b !== +b;
+ // An `egal` comparison is performed for other numeric values.
+ return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a === +b;
+ case '[object Symbol]':
+ return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
+ }
+
+ var areArrays = className === '[object Array]';
+ if (!areArrays) {
+ if (typeof a != 'object' || typeof b != 'object') return false;
+
+ // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+ _.isFunction(bCtor) && bCtor instanceof bCtor)
+ && ('constructor' in a && 'constructor' in b)) {
+ return false;
+ }
+ }
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+
+ // Initializing stack of traversed objects.
+ // It's done here since we only need them for objects and arrays comparison.
+ aStack = aStack || [];
+ bStack = bStack || [];
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] === a) return bStack[length] === b;
+ }
+
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+
+ // Recursively compare objects and arrays.
+ if (areArrays) {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ length = a.length;
+ if (length !== b.length) return false;
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (length--) {
+ if (!eq(a[length], b[length], aStack, bStack)) return false;
+ }
+ } else {
+ // Deep compare objects.
+ var keys = _.keys(a), key;
+ length = keys.length;
+ // Ensure that both objects contain the same number of properties before comparing deep equality.
+ if (_.keys(b).length !== length) return false;
+ while (length--) {
+ // Deep compare each member
+ key = keys[length];
+ if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+ return true;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ return eq(a, b);
+ };
+
+ // Is a given array, string, or object empty?
+ // An "empty" object has no enumerable own-properties.
+ _.isEmpty = function(obj) {
+ if (obj == null) return true;
+ if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+ return _.keys(obj).length === 0;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) === '[object Array]';
+ };
+
+ // Is a given variable an object?
+ _.isObject = function(obj) {
+ var type = typeof obj;
+ return type === 'function' || type === 'object' && !!obj;
+ };
+
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
+ _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
+ _['is' + name] = function(obj) {
+ return toString.call(obj) === '[object ' + name + ']';
+ };
+ });
+
+ // Define a fallback version of the method in browsers (ahem, IE < 9), where
+ // there isn't any inspectable "Arguments" type.
+ if (!_.isArguments(arguments)) {
+ _.isArguments = function(obj) {
+ return has(obj, 'callee');
+ };
+ }
+
+ // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
+ // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
+ var nodelist = root.document && root.document.childNodes;
+ if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
+ _.isFunction = function(obj) {
+ return typeof obj == 'function' || false;
+ };
+ }
+
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
+ };
+
+ // Is the given value `NaN`?
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && isNaN(obj);
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Shortcut function for checking if an object has a given property directly
+ // on itself (in other words, not on a prototype).
+ _.has = function(obj, path) {
+ if (!_.isArray(path)) {
+ return has(obj, path);
+ }
+ var length = path.length;
+ for (var i = 0; i < length; i++) {
+ var key = path[i];
+ if (obj == null || !hasOwnProperty.call(obj, key)) {
+ return false;
+ }
+ obj = obj[key];
+ }
+ return !!length;
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iteratees.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Predicate-generating functions. Often useful outside of Underscore.
+ _.constant = function(value) {
+ return function() {
+ return value;
+ };
+ };
+
+ _.noop = function(){};
+
+ // Creates a function that, when passed an object, will traverse that object’s
+ // properties down the given `path`, specified as an array of keys or indexes.
+ _.property = function(path) {
+ if (!_.isArray(path)) {
+ return shallowProperty(path);
+ }
+ return function(obj) {
+ return deepGet(obj, path);
+ };
+ };
+
+ // Generates a function for a given object that returns a given property.
+ _.propertyOf = function(obj) {
+ if (obj == null) {
+ return function(){};
+ }
+ return function(path) {
+ return !_.isArray(path) ? obj[path] : deepGet(obj, path);
+ };
+ };
+
+ // Returns a predicate for checking whether an object has a given set of
+ // `key:value` pairs.
+ _.matcher = _.matches = function(attrs) {
+ attrs = _.extendOwn({}, attrs);
+ return function(obj) {
+ return _.isMatch(obj, attrs);
+ };
+ };
+
+ // Run a function **n** times.
+ _.times = function(n, iteratee, context) {
+ var accum = Array(Math.max(0, n));
+ iteratee = optimizeCb(iteratee, context, 1);
+ for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+ return accum;
+ };
+
+ // Return a random integer between min and max (inclusive).
+ _.random = function(min, max) {
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + Math.floor(Math.random() * (max - min + 1));
+ };
+
+ // A (possibly faster) way to get the current timestamp as an integer.
+ _.now = Date.now || function() {
+ return new Date().getTime();
+ };
+
+ // List of HTML entities for escaping.
+ var escapeMap = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '`': '`'
+ };
+ var unescapeMap = _.invert(escapeMap);
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ var createEscaper = function(map) {
+ var escaper = function(match) {
+ return map[match];
+ };
+ // Regexes for identifying a key that needs to be escaped.
+ var source = '(?:' + _.keys(map).join('|') + ')';
+ var testRegexp = RegExp(source);
+ var replaceRegexp = RegExp(source, 'g');
+ return function(string) {
+ string = string == null ? '' : '' + string;
+ return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+ };
+ };
+ _.escape = createEscaper(escapeMap);
+ _.unescape = createEscaper(unescapeMap);
+
+ // Traverses the children of `obj` along `path`. If a child is a function, it
+ // is invoked with its parent as context. Returns the value of the final
+ // child, or `fallback` if any child is undefined.
+ _.result = function(obj, path, fallback) {
+ if (!_.isArray(path)) path = [path];
+ var length = path.length;
+ if (!length) {
+ return _.isFunction(fallback) ? fallback.call(obj) : fallback;
+ }
+ for (var i = 0; i < length; i++) {
+ var prop = obj == null ? void 0 : obj[path[i]];
+ if (prop === void 0) {
+ prop = fallback;
+ i = length; // Ensure we don't continue iterating.
+ }
+ obj = _.isFunction(prop) ? prop.call(obj) : prop;
+ }
+ return obj;
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = ++idCounter + '';
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate: /<%([\s\S]+?)%>/g,
+ interpolate: /<%=([\s\S]+?)%>/g,
+ escape: /<%-([\s\S]+?)%>/g
+ };
+
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /(.)^/;
+
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
+
+ var escapeChar = function(match) {
+ return '\\' + escapes[match];
+ };
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ // NB: `oldSettings` only exists for backwards compatibility.
+ _.template = function(text, settings, oldSettings) {
+ if (!settings && oldSettings) settings = oldSettings;
+ settings = _.defaults({}, settings, _.templateSettings);
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
+ index = offset + match.length;
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ } else if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ } else if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+
+ // Adobe VMs need the match returned to produce the correct offset.
+ return match;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + 'return __p;\n';
+
+ var render;
+ try {
+ render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled source as a convenience for precompilation.
+ var argument = settings.variable || 'obj';
+ template.source = 'function(' + argument + '){\n' + source + '}';
+
+ return template;
+ };
+
+ // Add a "chain" function. Start chaining a wrapped Underscore object.
+ _.chain = function(obj) {
+ var instance = _(obj);
+ instance._chain = true;
+ return instance;
+ };
+
+ // OOP
+ // ---------------
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+
+ // Helper function to continue chaining intermediate results.
+ var chainResult = function(instance, obj) {
+ return instance._chain ? _(obj).chain() : obj;
+ };
+
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ _.each(_.functions(obj), function(name) {
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return chainResult(this, func.apply(_, args));
+ };
+ });
+ return _;
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ var obj = this._wrapped;
+ method.apply(obj, arguments);
+ if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+ return chainResult(this, obj);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ _.each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ return chainResult(this, method.apply(this._wrapped, arguments));
+ };
+ });
+
+ // Extracts the result from a wrapped and chained object.
+ _.prototype.value = function() {
+ return this._wrapped;
+ };
+
+ // Provide unwrapping proxy for some methods used in engine operations
+ // such as arithmetic and JSON stringification.
+ _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
+
+ _.prototype.toString = function() {
+ return String(this._wrapped);
+ };
+
+ // AMD registration happens at the end for compatibility with AMD loaders
+ // that may not enforce next-turn semantics on modules. Even though general
+ // practice for AMD registration is to be anonymous, underscore registers
+ // as a named module because, like jQuery, it is a base library that is
+ // popular enough to be bundled in a third party lib, but not be part of
+ // an AMD load request. Those cases could generate an error when an
+ // anonymous define() is called outside of a loader request.
+ if (typeof define == 'function' && define.amd) {
+ define('underscore', [], function() {
+ return _;
+ });
+ }
+}());
diff --git a/app/Resources/android/alloy/widget.js b/app/Resources/android/alloy/widget.js
new file mode 100644
index 0000000..f6d1346
--- /dev/null
+++ b/app/Resources/android/alloy/widget.js
@@ -0,0 +1,48 @@
+var Alloy = require('/alloy');
+
+// Hold a collection of widget objects instances. These
+// objects are not the widgets themselves, but a set of
+// auto-populated functions and properties that make
+// developing widgets easier.
+var widgets = {};
+
+function ucfirst(text) {
+ if (!text) { return text; }
+ return text[0].toUpperCase() + text.substr(1);
+}
+
+module.exports = function(widgetId) {
+ var self = this;
+
+ // return existing widget object, if present
+ if (widgets[widgetId]) {
+ return widgets[widgetId];
+ }
+
+ // properties
+ this.widgetId = widgetId;
+ this.Collections = {};
+ this.Models = {};
+ this.Shared = {};
+
+ // functions
+ this.createController = function(name, args) {
+ return new (require('/alloy/widgets/' + widgetId + '/controllers/' + name))(args);
+ };
+ this.createCollection = function(name, args) {
+ return new (require('/alloy/widgets/' + widgetId + '/models/' + ucfirst(name)).Collection)(args);
+ };
+ this.createModel = function(name, args) {
+ return new (require('/alloy/widgets/' + widgetId + '/models/' + ucfirst(name)).Model)(args);
+ };
+ this.createWidget = Alloy.createWidget; // just to be complete
+ this.Collections.instance = function(name) {
+ return self.Collections[name] || (self.Collections[name] = self.createCollection(name));
+ };
+ this.Models.instance = function(name) {
+ return self.Models[name] || (self.Models[name] = self.createModel(name));
+ };
+
+ // add to widget object instances
+ widgets[widgetId] = this;
+};
\ No newline at end of file
diff --git a/app/Resources/android/app.js b/app/Resources/android/app.js
new file mode 100644
index 0000000..457c7c8
--- /dev/null
+++ b/app/Resources/android/app.js
@@ -0,0 +1,42 @@
+/**
+ * Alloy for Titanium by Appcelerator
+ * This is generated code, DO NOT MODIFY - changes will be lost!
+ * Copyright (c) 2012 by Appcelerator, Inc.
+ */
+var Alloy = require('/alloy'),
+_ = Alloy._,
+Backbone = Alloy.Backbone;
+
+// The globals should be configured by the bootstrap script, however if anyone is using an SDK
+// older than 7.5.0 that won't get ran. So set them here if they don't exist
+if (!global.Alloy) {
+ global.Alloy = Alloy;
+ global._ = _;
+ global.Backbone = Backbone;
+}
+
+// The contents of this file will be executed before any of
+// your view controllers are ever executed, including the index.
+// You have access to all functionality on the `Alloy` namespace.
+//
+// This is a great place to do any initialization for your app
+// or create any global variables/functions that you'd like to
+// make available throughout your app. You can easily make things
+// accessible globally by attaching them to the `Alloy.Globals`
+// object. For example:
+//
+// Alloy.Globals.someGlobalFunction = function(){};
+
+
+// Open root window if a new UI session has started. Can happen more than once in app's lifetime.
+// Event can only be fired if "tiapp.xml" property "run-in-background" is set true.
+Ti.UI.addEventListener('sessionbegin', function () {
+ Alloy.createController('index');
+});
+
+// Open the root window immediately if an active UI session exists on startup.
+// Note: The Ti.UI.hasSession property was added as of Titanium 9.1.0.
+if (typeof Ti.UI.hasSession === 'undefined' || Ti.UI.hasSession) {
+ Alloy.createController('index');
+}
+//# sourceMappingURL=file:///home/miga/dev/titanium/coronaWidgetGithub/app/build/map/Resources/android/app.js.map
\ No newline at end of file
diff --git a/app/Resources/android/appicon.png b/app/Resources/android/appicon.png
new file mode 100755
index 0000000..7e73d18
Binary files /dev/null and b/app/Resources/android/appicon.png differ
diff --git a/app/Resources/android/default.png b/app/Resources/android/default.png
new file mode 100755
index 0000000..0b352a1
Binary files /dev/null and b/app/Resources/android/default.png differ
diff --git a/app/Resources/android/images/res-land-hdpi/default.png b/app/Resources/android/images/res-land-hdpi/default.png
new file mode 100755
index 0000000..39d9861
Binary files /dev/null and b/app/Resources/android/images/res-land-hdpi/default.png differ
diff --git a/app/Resources/android/images/res-land-ldpi/default.png b/app/Resources/android/images/res-land-ldpi/default.png
new file mode 100755
index 0000000..fa19681
Binary files /dev/null and b/app/Resources/android/images/res-land-ldpi/default.png differ
diff --git a/app/Resources/android/images/res-land-mdpi/default.png b/app/Resources/android/images/res-land-mdpi/default.png
new file mode 100755
index 0000000..f698615
Binary files /dev/null and b/app/Resources/android/images/res-land-mdpi/default.png differ
diff --git a/app/Resources/android/images/res-land-xhdpi/default.png b/app/Resources/android/images/res-land-xhdpi/default.png
new file mode 100755
index 0000000..c10b91c
Binary files /dev/null and b/app/Resources/android/images/res-land-xhdpi/default.png differ
diff --git a/app/Resources/android/images/res-land-xxhdpi/default.png b/app/Resources/android/images/res-land-xxhdpi/default.png
new file mode 100755
index 0000000..7269672
Binary files /dev/null and b/app/Resources/android/images/res-land-xxhdpi/default.png differ
diff --git a/app/Resources/android/images/res-land-xxxhdpi/default.png b/app/Resources/android/images/res-land-xxxhdpi/default.png
new file mode 100755
index 0000000..af513ad
Binary files /dev/null and b/app/Resources/android/images/res-land-xxxhdpi/default.png differ
diff --git a/app/Resources/android/images/res-long-land-hdpi/default.png b/app/Resources/android/images/res-long-land-hdpi/default.png
new file mode 100755
index 0000000..a3421f9
Binary files /dev/null and b/app/Resources/android/images/res-long-land-hdpi/default.png differ
diff --git a/app/Resources/android/images/res-long-land-ldpi/default.png b/app/Resources/android/images/res-long-land-ldpi/default.png
new file mode 100755
index 0000000..2902588
Binary files /dev/null and b/app/Resources/android/images/res-long-land-ldpi/default.png differ
diff --git a/app/Resources/android/images/res-long-land-mdpi/default.png b/app/Resources/android/images/res-long-land-mdpi/default.png
new file mode 100755
index 0000000..fb1c5a8
Binary files /dev/null and b/app/Resources/android/images/res-long-land-mdpi/default.png differ
diff --git a/app/Resources/android/images/res-long-port-hdpi/default.png b/app/Resources/android/images/res-long-port-hdpi/default.png
new file mode 100755
index 0000000..1f289d6
Binary files /dev/null and b/app/Resources/android/images/res-long-port-hdpi/default.png differ
diff --git a/app/Resources/android/images/res-long-port-ldpi/default.png b/app/Resources/android/images/res-long-port-ldpi/default.png
new file mode 100755
index 0000000..069e563
Binary files /dev/null and b/app/Resources/android/images/res-long-port-ldpi/default.png differ
diff --git a/app/Resources/android/images/res-notlong-land-hdpi/default.png b/app/Resources/android/images/res-notlong-land-hdpi/default.png
new file mode 100755
index 0000000..a3421f9
Binary files /dev/null and b/app/Resources/android/images/res-notlong-land-hdpi/default.png differ
diff --git a/app/Resources/android/images/res-notlong-land-ldpi/default.png b/app/Resources/android/images/res-notlong-land-ldpi/default.png
new file mode 100755
index 0000000..e5d4a7b
Binary files /dev/null and b/app/Resources/android/images/res-notlong-land-ldpi/default.png differ
diff --git a/app/Resources/android/images/res-notlong-land-mdpi/default.png b/app/Resources/android/images/res-notlong-land-mdpi/default.png
new file mode 100755
index 0000000..fb1c5a8
Binary files /dev/null and b/app/Resources/android/images/res-notlong-land-mdpi/default.png differ
diff --git a/app/Resources/android/images/res-notlong-port-hdpi/default.png b/app/Resources/android/images/res-notlong-port-hdpi/default.png
new file mode 100755
index 0000000..1f289d6
Binary files /dev/null and b/app/Resources/android/images/res-notlong-port-hdpi/default.png differ
diff --git a/app/Resources/android/images/res-notlong-port-ldpi/default.png b/app/Resources/android/images/res-notlong-port-ldpi/default.png
new file mode 100755
index 0000000..334de3b
Binary files /dev/null and b/app/Resources/android/images/res-notlong-port-ldpi/default.png differ
diff --git a/app/Resources/android/images/res-notlong-port-mdpi/default.png b/app/Resources/android/images/res-notlong-port-mdpi/default.png
new file mode 100755
index 0000000..0b352a1
Binary files /dev/null and b/app/Resources/android/images/res-notlong-port-mdpi/default.png differ
diff --git a/app/Resources/android/images/res-port-hdpi/default.png b/app/Resources/android/images/res-port-hdpi/default.png
new file mode 100755
index 0000000..a770bb4
Binary files /dev/null and b/app/Resources/android/images/res-port-hdpi/default.png differ
diff --git a/app/Resources/android/images/res-port-ldpi/default.png b/app/Resources/android/images/res-port-ldpi/default.png
new file mode 100755
index 0000000..ded3359
Binary files /dev/null and b/app/Resources/android/images/res-port-ldpi/default.png differ
diff --git a/app/Resources/android/images/res-port-mdpi/default.png b/app/Resources/android/images/res-port-mdpi/default.png
new file mode 100755
index 0000000..e281ab2
Binary files /dev/null and b/app/Resources/android/images/res-port-mdpi/default.png differ
diff --git a/app/Resources/android/images/res-port-xhdpi/default.png b/app/Resources/android/images/res-port-xhdpi/default.png
new file mode 100755
index 0000000..e5f207e
Binary files /dev/null and b/app/Resources/android/images/res-port-xhdpi/default.png differ
diff --git a/app/Resources/android/images/res-port-xxhdpi/default.png b/app/Resources/android/images/res-port-xxhdpi/default.png
new file mode 100755
index 0000000..4d10ea9
Binary files /dev/null and b/app/Resources/android/images/res-port-xxhdpi/default.png differ
diff --git a/app/Resources/android/images/res-port-xxxhdpi/default.png b/app/Resources/android/images/res-port-xxxhdpi/default.png
new file mode 100755
index 0000000..e731a35
Binary files /dev/null and b/app/Resources/android/images/res-port-xxxhdpi/default.png differ
diff --git a/app/Resources/android/service.js b/app/Resources/android/service.js
new file mode 100644
index 0000000..d866150
--- /dev/null
+++ b/app/Resources/android/service.js
@@ -0,0 +1,20 @@
+Ti.API.info("@@@ Service started.");
+require("/widget").updateData();
+
+if (Ti.Platform.Android.API_LEVEL >= 26) {
+ channel = Ti.Android.NotificationManager.createNotificationChannel({
+ id: "channel_id",
+ name: "Background Channel",
+ importance: Ti.Android.IMPORTANCE_LOW });
+
+}
+
+Ti.Android.currentService.foregroundNotify(
+123,
+Ti.Android.createNotification({
+ contentTitle: "Corona Widget",
+ icon: Ti.App.Android.R.drawable.virus,
+ contentText: "Keep the app running...",
+ channelId: channel ? channel.id : null,
+ contentIntent: Ti.Android.createPendingIntent({
+ intent: Ti.App.Android.launchIntent || Ti.Android.createIntent() }) }));
\ No newline at end of file
diff --git a/app/Resources/android/widget.js b/app/Resources/android/widget.js
new file mode 100644
index 0000000..ad8ee2b
--- /dev/null
+++ b/app/Resources/android/widget.js
@@ -0,0 +1,63 @@
+var widgets = require("ti.widget");
+
+var t = Ti.App.Properties.getObject("widgetData", {
+ value1: "-",
+ value2: "-" });
+
+
+var dataValue1 = t.value1;
+var dataValue2 = t.value2;
+var town1 = Ti.App.Properties.getString("town1", "-");
+var town2 = Ti.App.Properties.getString("town2", "-");
+
+function getData() {
+
+ town1 = Ti.App.Properties.getString("town1", "-");
+ town2 = Ti.App.Properties.getString("town2", "-");
+
+ var xhr = Ti.Network.createHTTPClient({
+ onload: function (e) {
+ var json = JSON.parse(this.responseText);
+ dataValue1 = parseFloat(json.features[0].attributes.cases7_per_100k).toFixed(1);
+
+ Ti.App.Properties.setObject("widgetData", {
+ town1: town1,
+ town2: town2,
+ value1: "🦠 " + dataValue1,
+ value2: "🦠 " + dataValue2 });
+
+ widgets.updateWidgets();
+ },
+ onerror: function (e) {},
+ timeout: 5000 // milliseconds
+ });
+ var lat1 = Ti.App.Properties.getString("lat1", "52.520008");
+ var lon1 = Ti.App.Properties.getString("lon1", "13.404954");
+ xhr.open('GET', "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=GEN,cases7_per_100k&geometry=" + lon1 + "%2C" + lat1 + "&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json");
+ xhr.send();
+
+ // second data source
+ var xhr = Ti.Network.createHTTPClient({
+ onload: function (e) {
+ var json = JSON.parse(this.responseText);
+ dataValue2 = parseFloat(json.features[0].attributes.cases7_per_100k).toFixed(1);
+
+ Ti.App.Properties.setObject("widgetData", {
+ town1: town1,
+ town2: town2,
+ value1: "🦠 " + dataValue1,
+ value2: "🦠 " + dataValue2 });
+
+ widgets.updateWidgets();
+ },
+ onerror: function (e) {},
+ timeout: 5000 });
+
+
+ lat1 = Ti.App.Properties.getString("lat2", "48.135124");
+ lon1 = Ti.App.Properties.getString("lon2", "11.581981");
+ xhr.open('GET', "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=GEN,cases7_per_100k&geometry=" + lon1 + "%2C" + lat1 + "&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json");
+ xhr.send();
+}
+
+exports.updateData = getData;
\ No newline at end of file
diff --git a/app/Resources/app.js b/app/Resources/app.js
new file mode 100644
index 0000000..e69de29
diff --git a/app/app/controllers/index.js b/app/app/controllers/index.js
index cb42f16..e7fb9ad 100755
--- a/app/app/controllers/index.js
+++ b/app/app/controllers/index.js
@@ -14,7 +14,25 @@ intent.putExtra('interval', MINUTES);
Titanium.Android.startService(intent);
+function onChange() {
+ if ($.tf_town1.value != "") Ti.App.Properties.setString("town1", $.tf_town1.value);
+ if ($.tf_town2.value != "") Ti.App.Properties.setString("town2", $.tf_town2.value);
+
+ if ($.tf_lat1.value != "") Ti.App.Properties.setString("lat1", $.tf_lat1.value);
+ if ($.tf_lon1.value != "") Ti.App.Properties.setString("lon1", $.tf_lon1.value);
+
+ if ($.tf_lat2.value != "") Ti.App.Properties.setString("lat2", $.tf_lat2.value);
+ if ($.tf_lon2.value != "") Ti.App.Properties.setString("lon2", $.tf_lon2.value);
+}
function onOpen(e) {
+
+ $.tf_lat1.value = Ti.App.Properties.getString("lat1", "48.135124");
+ $.tf_lon1.value = Ti.App.Properties.getString("lon1", "11.581981");
+ $.tf_lat2.value = Ti.App.Properties.getString("lat2", "52.520008");
+ $.tf_lon2.value = Ti.App.Properties.getString("lon2", "13.404954");
+ $.tf_town1.value = Ti.App.Properties.getString("town1", "Berlin");
+ $.tf_town2.value = Ti.App.Properties.getString("town2", "Munich");
+
require("/widget").updateData();
}
diff --git a/app/app/lib/widget.js b/app/app/lib/widget.js
index 6fba49f..39f42ec 100644
--- a/app/app/lib/widget.js
+++ b/app/app/lib/widget.js
@@ -7,10 +7,14 @@ var t = Ti.App.Properties.getObject("widgetData", {
var dataValue1 = t.value1;
var dataValue2 = t.value2;
-var town1 = "Berlin";
-var town2 = "Munich";
+var town1 = Ti.App.Properties.getString("town1", "-");
+var town2 = Ti.App.Properties.getString("town2", "-");
function getData() {
+
+ town1 = Ti.App.Properties.getString("town1", "-");
+ town2 = Ti.App.Properties.getString("town2", "-");
+
var xhr = Ti.Network.createHTTPClient({
onload: function(e) {
var json = JSON.parse(this.responseText);
@@ -27,8 +31,8 @@ function getData() {
onerror: function(e) {},
timeout: 5000 // milliseconds
});
- var lat1 = 52.520008;
- var lon1 = 13.404954;
+ var lat1 = Ti.App.Properties.getString("lat1", "52.520008");
+ var lon1 = Ti.App.Properties.getString("lon1", "13.404954");
xhr.open('GET', "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=GEN,cases7_per_100k&geometry="+lon1+"%2C"+lat1+"&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json");
xhr.send();
@@ -49,8 +53,9 @@ function getData() {
onerror: function(e) {},
timeout: 5000
});
- var lat1 = 48.135124;
- var lon1 = 11.581981;
+
+ lat1 = Ti.App.Properties.getString("lat2", "48.135124");
+ lon1 = Ti.App.Properties.getString("lon2", "11.581981");
xhr.open('GET', "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=GEN,cases7_per_100k&geometry="+lon1+"%2C"+lat1+"&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json");
xhr.send();
}
diff --git a/app/app/styles/app.tss b/app/app/styles/app.tss
index ce16b22..9f129f6 100644
--- a/app/app/styles/app.tss
+++ b/app/app/styles/app.tss
@@ -1,26 +1,13 @@
-/*
-
-This is your global styles file. Selectors and rules you define
-here will be applied throughout your app. However, these rules
-have the lowest priority of any style settings.
-
-For more information, see the "Style Priorities" section of
-http://docs.appcelerator.com/platform/latest/#!/guide/Alloy_Styles_and_Themes
-
-For example, the following would apply to all labels, windows,
-and text fields (depending on platform) in your app unless you
-overrode the settings with other TSS, XML, or JS settings:
-
-'Label[platform=android,windows]': {
- color: '#000' // all platforms except Android and Windows default to black
+"TextField": {
+ width: 300,
+ borderColor: "#000",
+ borderWidth: 1,
+ color: "#000"
}
-'Window': {
- backgroundColor: '#fff' // white background instead of default transparent or black
+'Label': {
+ color: '#000',
+ width: Ti.UI.SIZE,
+ height: Ti.UI.SIZE,
+ left: 0
}
-
-'TextField[platform=android]': {
- height: Ti.UI.SIZE
-}
-
-*/
diff --git a/app/app/styles/index.tss b/app/app/styles/index.tss
index 91e3b0a..6ef1005 100755
--- a/app/app/styles/index.tss
+++ b/app/app/styles/index.tss
@@ -1,12 +1,13 @@
".container": {
backgroundColor:"white",
- layout: 'vertical',
exitOnClose: false
}
-"#tf": {
+
+
+".content":{
width: 300,
- borderColor: "#000",
- borderWidth: 1,
- color: "#000"
+ layout: 'vertical',
+ height: Ti.UI.SIZE,
+ top: 10
}
diff --git a/app/app/views/index.xml b/app/app/views/index.xml
index 0f7d4f4..1bb1ae9 100755
--- a/app/app/views/index.xml
+++ b/app/app/views/index.xml
@@ -1,5 +1,21 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar
index e68eba0..92b3e5d 100644
Binary files a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar and b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar differ
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.md5 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.md5
index 582964a..72365c4 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.md5
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.md5
@@ -1 +1 @@
-b2793cbe9e80fa526a8ee2895f0577fd
\ No newline at end of file
+9d8c436be2609ef1774c2151fae0713c
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha1 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha1
index a5e2f64..71ddb01 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha1
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha1
@@ -1 +1 @@
-21fef234a46268e69ad0c3b656a076016f9e51f8
\ No newline at end of file
+5a38fa3dac3c56c794a0c37c95b5d219ca7386a8
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha256 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha256
index 1d89473..3e1178e 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha256
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha256
@@ -1 +1 @@
-fc267bb8368b7f1f957a64fabc4d4d719312034075e2460e9008c9616dcebc77
\ No newline at end of file
+5d7f66575bc5dbc09f2a99a62059faa84dc088dfe92254e878c6c9bb36ac64f1
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha512 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha512
index 7c9aa68..04ef0d9 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha512
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/1.0.0/widget-1.0.0.aar.sha512
@@ -1 +1 @@
-978424ac50920a3316bd280fe41f387b76a4f928f18fe9d3f361714d8db3c01b91870aec698b1f59c666e92eb7312d4c1c3cc4054e7b1d4475cad38984283d2b
\ No newline at end of file
+a6d404151b9dd456dce9179b34aab529c4cb9224f7c9d35140b50c5d877bfbb90025c793ad51c278282e1f91d4964e6363dd0deb524028498950dc3cf2dbd4bf
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml
index 3efdd58..dc8ba47 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml
@@ -6,9 +6,8 @@
1.0.01.0.0
- 2.0.01.0.0
- 20201020102903
+ 20201020161630
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.md5 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.md5
index f5e26dd..df19dd4 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.md5
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.md5
@@ -1 +1 @@
-b8e1d0a630d93336bc3d254af48a2e0e
\ No newline at end of file
+189b42373ba81976614f90f8f54e3692
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha1 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha1
index ad2fe42..f2cd270 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha1
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha1
@@ -1 +1 @@
-74bb1af5c4af1f6b02241f1d520d431fe9d5b736
\ No newline at end of file
+c1d4f780cfce16ed743a1f01b3d556dd0b0c0a4f
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha256 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha256
index f3c3e76..44c144b 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha256
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha256
@@ -1 +1 @@
-1b83c11b365f87fc12ba21a5b913c3ada26dc946e33c97af7a3ab7ecd7523710
\ No newline at end of file
+95cf3ce64da5ce5237e5319c58feefcebde1034c2894a119c6727848df1ff023
\ No newline at end of file
diff --git a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha512 b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha512
index 6c0e253..6625837 100644
--- a/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha512
+++ b/app/modules/android/ti.widget/1.0.0/m2repository/ti/widget/maven-metadata.xml.sha512
@@ -1 +1 @@
-b51c5d5502221ce1f6f270ab12ba6afdd01f52dade4866ac57a0d4b5521b7fb33da4ea5fb445ed2af7e347f4987d7e8242cecef90225766504a6cbde618b2e1c
\ No newline at end of file
+d1f7f3099ffed1936281213f01f3c879fb7cc10f101d4788c89659c4e8a14c454a3863b675392b7a34811719e31ebd1927143914d21df0dbf2af5a5c357cd8d2
\ No newline at end of file
diff --git a/preview.png b/preview.png
deleted file mode 100644
index 6e3d3f7..0000000
Binary files a/preview.png and /dev/null differ
diff --git a/preview0.jpg b/preview0.jpg
new file mode 100644
index 0000000..ae20ef1
Binary files /dev/null and b/preview0.jpg differ
diff --git a/preview1.png b/preview1.png
new file mode 100644
index 0000000..b6a66f2
Binary files /dev/null and b/preview1.png differ
diff --git a/preview2.png b/preview2.png
new file mode 100644
index 0000000..af93596
Binary files /dev/null and b/preview2.png differ