From 762112c0587284abc4cd91989dbbaf97b881e19c Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Tue, 15 Sep 2015 09:15:23 -0500 Subject: [PATCH] Revert "Temporarily removing native plugins" This reverts commit 81d907e8b95eb3286200a5c5ab479bbade56f03b. --- ionic/ionic.ts | 2 + ionic/native/battery/battery.ts | 43 +++++++ ionic/native/camera/camera.ts | 46 +++++++ ionic/native/contacts/contacts.ts | 91 ++++++++++++++ ionic/native/device-motion/device-motion.ts | 90 +++++++++++++ .../device-orientation/device-orientation.ts | 86 +++++++++++++ ionic/native/device/device.ts | 119 ++++++++++++++++++ ionic/native/dialogs/dialogs.ts | 85 +++++++++++++ ionic/native/geolocation/geolocation.ts | 46 +++++++ ionic/native/plugin.ts | 48 +++++++ ionic/native/plugins.ts | 10 ++ ionic/native/vibration/vibration.ts | 21 ++++ 12 files changed, 687 insertions(+) create mode 100644 ionic/native/battery/battery.ts create mode 100644 ionic/native/camera/camera.ts create mode 100644 ionic/native/contacts/contacts.ts create mode 100644 ionic/native/device-motion/device-motion.ts create mode 100644 ionic/native/device-orientation/device-orientation.ts create mode 100644 ionic/native/device/device.ts create mode 100644 ionic/native/dialogs/dialogs.ts create mode 100644 ionic/native/geolocation/geolocation.ts create mode 100644 ionic/native/plugin.ts create mode 100644 ionic/native/plugins.ts create mode 100644 ionic/native/vibration/vibration.ts diff --git a/ionic/ionic.ts b/ionic/ionic.ts index 4766290aeb..aaa889dc98 100644 --- a/ionic/ionic.ts +++ b/ionic/ionic.ts @@ -23,3 +23,5 @@ export * from './animations/builtins' export * from './transitions/transition' export * from './transitions/ios-transition' export * from './transitions/md-transition' + +export * from './native/plugins' diff --git a/ionic/native/battery/battery.ts b/ionic/native/battery/battery.ts new file mode 100644 index 0000000000..16dee7b891 --- /dev/null +++ b/ionic/native/battery/battery.ts @@ -0,0 +1,43 @@ +import * as util from 'ionic/util'; + +import {NativePlugin} from '../plugin'; + +@NativePlugin({ + name: 'Battery', + platforms: { + cordova: 'cordova-plugin-battery-status' + } +}) +export class Battery { + static getStatus() { + return new Promise((resolve, reject) => { + if(navigator.getBattery) { + + navigator.getBattery().then((battery) => { + this.battery = battery; + resolve(Battery._format(battery)); + }); + + } else { + + var fnCb = function fnCb(battery) { + resolve(battery); + window.removeEventListener('batterystatus', fnCb); + } + window.addEventListener('batterystatus', fnCb); + } + }); + } + static _format(batteryObj) { + if(typeof batteryObj.isPlugged !== 'undefined') { + // This is the old format, map it to the new format + util.extend(batteryObj, { + charging: batteryObj.isPlugged, + level: batteryObj.level / 100, + chargingTime: 0, //not provided, + dischargingTime: 0 //not provided + }); + } + return batteryObj; + } +} diff --git a/ionic/native/camera/camera.ts b/ionic/native/camera/camera.ts new file mode 100644 index 0000000000..072e66351a --- /dev/null +++ b/ionic/native/camera/camera.ts @@ -0,0 +1,46 @@ +import * as util from 'ionic/util'; + +import {NativePlugin} from '../plugin'; + +@NativePlugin({ + name: 'Camera', + platforms: { + cordova: 'cordova-plugin-camera' + } +}) +export class Camera { + static getPicture(options) { + return new Promise((resolve, reject) => { + if (!navigator.camera) { + this.pluginWarn(); + resolve(null); + return; + } + + var options = util.defaults({ + quality: 80, + destinationType: window.Camera.DestinationType.DATA_URL, + sourceType: window.Camera.PictureSourceType.CAMERA, + allowEdit: true, + encodingType: window.Camera.EncodingType.JPEG, + popoverOptions: window.CameraPopoverOptions, + saveToPhotoAlbum: false + }, options); + + navigator.camera.getPicture(function (imageData) { + resolve(imageData); + }, function (err) { + reject(err); + }, options); + }); + } + static cleanup() { + return new Promise((resolve, reject) => { + navigator.camera.cleanup(function () { + resolve(); + }, function (err) { + reject(err); + }); + }); + } +} diff --git a/ionic/native/contacts/contacts.ts b/ionic/native/contacts/contacts.ts new file mode 100644 index 0000000000..b1421158b1 --- /dev/null +++ b/ionic/native/contacts/contacts.ts @@ -0,0 +1,91 @@ +// install : cordova plugin add cordova-plugin-contacts +// link : https://github.com/apache/cordova-plugin-contacts +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + +@NativePlugin({ + name: 'Contacts', + platforms: { + cordova: 'cordova-plugin-contacts' + } +}) +export class Contacts { + static save(contact) { + return new Promise((resolve, reject) => { + if(!navigator.contacts) { + this.pluginWarn(); + reject('Contacts plugin not installed'); + } + var deviceContact = navigator.contacts.create(contact); + + deviceContact.save(function (result) { + resolve(result); + }, function (err) { + reject(err); + }); + }); + } + + static remove(contact) { + return new Promise((resolve, reject) => { + if(!navigator.contacts) { + this.pluginWarn(); + reject('Contacts plugin not installed'); + } + var deviceContact = navigator.contacts.create(contact); + + deviceContact.remove(function (result) { + resolve(result); + }, function (err) { + reject(err); + }); + }) + } + + static clone(contact) { + if(!navigator.contacts) { + this.pluginWarn(); + return null; + } + var deviceContact = navigator.contacts.create(contact); + return deviceContact.clone(contact); + } + + static find(options) { + return new Promise((resolve, reject) => { + var fields = options.fields || ['id', 'displayName']; + delete options.fields; + if (Object.keys(options).length === 0) { + navigator.contacts.find(fields, function (results) { + resolve(results); + },function (err) { + reject(err); + }); + } else { + navigator.contacts.find(fields, function (results) { + resolve(results); + }, function (err) { + reject(err); + }, options); + } + }); + } + + static pickContact() { + return new Promise((resolve, reject) => { + navigator.contacts.pickContact(function (contact) { + resolve(contact); + }, function (err) { + reject(err); + }); + }) + } + + // TODO: method to set / get ContactAddress + // TODO: method to set / get ContactError + // TODO: method to set / get ContactField + // TODO: method to set / get ContactName + // TODO: method to set / get ContactOrganization +} diff --git a/ionic/native/device-motion/device-motion.ts b/ionic/native/device-motion/device-motion.ts new file mode 100644 index 0000000000..acfc9defc3 --- /dev/null +++ b/ionic/native/device-motion/device-motion.ts @@ -0,0 +1,90 @@ +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + + +@NativePlugin({ + name: 'Device Motion', + platforms: { + cordova: 'cordova-plugin-device-motion' + } +}) +export class DeviceMotion { + static _wrap(result) { + // Mimic the DeviceMotionEvent + return util.extend({ + acceleration: result, // result will be x/y/z accel + accelerationIncludingGravity: result, //TODO: I know this isn't correct but not sure how to normalize from native plugin + rotationRate: 0, + interval: 0, + native: true + }, result); + } + + static getCurrentAcceleration() { + return new Promise((resolve, reject) => { + if(window.DeviceMotionEvent || ('listenForDeviceMovement' in window)) { + var fnCb = function fnCb(eventData) { + resolve(DeviceMotion._wrap(eventData)); + window.removeEventListener('devicemotion', fnCb); + } + window.addEventListener('devicemotion', fnCb); + } else if(navigator.accelerometer) { + navigator.accelerometer.getCurrentAcceleration(function (result) { + resolve(DeviceMotion._wrap(result)); + }, function (err) { + reject(err); + }); + } else { + this.pluginWarn(); + reject('The Device does not support device motion events.'); + return; + } + }); + } + + static watchAcceleration(options) { + if(window.DeviceMotionEvent || ('listenForDeviceMovement' in window)) { + let watchID; + + let source = Rx.Observable.create((observer) => { + + var fnCb = function fnCb(eventData) { + observer.onNext(DeviceMotion._wrap(eventData)); + }; + + window.addEventListener('devicemotion', fnCb); + + }); + + return { + source: source, + watchID: watchID, + clear: () => { + window.removeEventListener('devicemotion', fnCb); + } + } + } else if(navigator.accelerometer) { + let watchID; + + let source = Rx.Observable.create((observer) => { + + watchID = navigator.accelerometer.watchAcceleration(function (result) { + observer.onNext(DeviceMotion._wrap(result)); + }, function (err) { + observer.onError(err, observer); + }, options); + + }); + + return { + source: source, + watchID: watchID, + clear: () => { + navigator.accelerometer.clearWatch(watchID); + } + } + } + } +} diff --git a/ionic/native/device-orientation/device-orientation.ts b/ionic/native/device-orientation/device-orientation.ts new file mode 100644 index 0000000000..279a5b9f8b --- /dev/null +++ b/ionic/native/device-orientation/device-orientation.ts @@ -0,0 +1,86 @@ +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + + +@NativePlugin({ + name: 'Device Orientation', + platforms: { + cordova: 'cordova-plugin-device-orientation' + } +}) +export class DeviceOrientation { + static _wrap(result) { + return util.extend({ + alpha: result.magneticHeading, + magneticHeading: result.webkitCompassHeading || result.alpha + }, result); + } + + static getCurrentHeading() { + return new Promise((resolve, reject) => { + if(window.DeviceOrientationEvent) { + var fnCb = function fnCb(eventData) { + resolve(DeviceOrientation._wrap(eventData)); + window.removeEventListener('deviceorientation', fnCb); + } + window.addEventListener('deviceorientation', fnCb); + } else if(navigator.compass) { + navigator.compass.getCurrentHeading(function (result) { + resolve(DeviceOrientation._wrap(result)); + }, function (err) { + reject(err); + }); + } else { + this.pluginWarn(); + reject('The Device does not support device orientation events.'); + return; + } + }); + } + + static watchHeading(options) { + if(window.DeviceOrientationEvent) { + let watchID; + + let source = Rx.Observable.create((observer) => { + + var fnCb = function fnCb(eventData) { + observer.onNext(DeviceOrientation._wrap(eventData)); + }; + + window.addEventListener('deviceorientation', fnCb); + + }); + + return { + source: source, + watchID: watchID, + clear: () => { + window.removeEventListener('deviceorientation', fnCb); + } + } + } else if(navigator.accelerometer) { + let watchID; + + let source = Rx.Observable.create((observer) => { + + watchID = navigator.compass.watchHeading(function (result) { + observer.onNext(DeviceOrientation._wrap(result)); + }, function (err) { + observer.onError(err, observer); + }, options); + + }); + + return { + source: source, + watchID: watchID, + clear: () => { + navigator.compass.clearWatch(watchID); + } + } + } + } +} diff --git a/ionic/native/device/device.ts b/ionic/native/device/device.ts new file mode 100644 index 0000000000..660e39933c --- /dev/null +++ b/ionic/native/device/device.ts @@ -0,0 +1,119 @@ +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + + +@NativePlugin({ + name: 'Device', + platforms: { + cordova: 'cordova-plugin-device' + } +}) +export class Device { + /** + * Returns the whole device object. + * @see https://github.com/apache/cordova-plugin-device + * @returns {Object} The device object. + */ + static getDevice() { + return this.ifPlugin(window.device, () => { + return device; + }, () => { + return { + name: Device.getName(), + model: Device.getModel(), + platform: Device.getPlatform(), + uuid: Device.getUUID(), + version: Device.getVersion() + } + }); + } + + /** + * Returns the Cordova version. + * @see https://github.com/apache/cordova-plugin-device#devicecordova + * @returns {String} The Cordova version. + */ + static getCordova() { + this.ifPlugin(window.device, () => { + return device.cordova; + }); + } + + /** + * Returns the name of the device's model or product. + * @see https://github.com/apache/cordova-plugin-device#devicemodel + * @returns {String} The name of the device's model or product. + */ + static getModel() { + this.ifPlugin(window.device, () => { + return device.model; + }, () => { + return 'unknown' + }); + } + + /** + * @deprecated device.name is deprecated as of version 2.3.0. Use device.model instead. + * @returns {String} + */ + static getName() { + this.ifPlugin(window.device, () => { + return device.name; + }, () => { + return 'unknown' + }); + } + + /** + * Returns the device's operating system name. + * @see https://github.com/apache/cordova-plugin-device#deviceplatform + * @returns {String} The device's operating system name. + */ + static getPlatform() { + this.ifPlugin(window.device, () => { + return device.name; + }, () => { + return 'unknown' + }); + } + + /** + * Returns the device's Universally Unique Identifier. + * @see https://github.com/apache/cordova-plugin-device#deviceuuid + * @returns {String} The device's Universally Unique Identifier + */ + static getUUID() { + this.ifPlugin(window.device, () => { + return device.uuid; + }, () => { + return 'unknown'; + }); + } + + /** + * Returns the operating system version. + * @see https://github.com/apache/cordova-plugin-device#deviceversion + * @returns {String} + */ + static getVersion() { + this.ifPlugin(window.device, () => { + return device.version; + }, () => { + return 'unknown'; + }); + } + + /** + * Returns the device manufacturer. + * @returns {String} + */ + static getManufacturer() { + this.ifPlugin(window.device, () => { + return device.manufacturer; + }, () => { + return 'unknown'; + }); + } +} diff --git a/ionic/native/dialogs/dialogs.ts b/ionic/native/dialogs/dialogs.ts new file mode 100644 index 0000000000..1890478747 --- /dev/null +++ b/ionic/native/dialogs/dialogs.ts @@ -0,0 +1,85 @@ +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + +/** + * A native dialogs system. Native dialogs can give you a bit more + * control over the UI than the browser built-ins, though the Dialogs + * plugin will fall back to the built-ins when necessary. + */ +@NativePlugin({ + name: 'Dialogs', + platforms: { + cordova: 'cordova-plugin-dialogs' + } +}) +export class Dialogs { + /** + * Trigger an alert prompt. + * + * @param message the message to show + * @param title the title to show + * @param buttonName the button label to use (not available on browser fallback) + * @return Promise + */ + static alert(message, title, buttonName) { + return new Promise((resolve,reject) => { + if(!navigator.notification) { + this.pluginWarn(); + alert(message); + resolve(); + } else { + navigator.notification.alert(message, () => { + resolve(); + }, title, buttonName); + } + }); + } + + /** + * Trigger a confirm prompt. + * + * @param message the message to show + * @param title the title to show + * @param buttonLabels the button labels to use (not available on browser fallback) + * @return Promise that resolves with the index of the button selected (zero indexed). 1 is OK on browser fallback + */ + static confirm(message, title, buttonLabels) { + return new Promise((resolve,reject) => { + if(!navigator.notification) { + this.pluginWarn(); + var ok = confirm(message); + // Use 2 as OK + resolve(ok ? 2 : 0); + } else { + navigator.notification.confirm(message, (buttonIndex) => { + resolve(buttonIndex - 1); + }, title, buttonLabels); + } + }); + } + + static prompt(message, title, buttonLabels, defaultText) { + return new Promise((resolve,reject) => { + if(!navigator.notification) { + this.pluginWarn(); + var response = prompt(message); + // Use 1 as OK + resolve(response); + } else { + navigator.notification.prompt(message, (results) => { + resolve(results.input1, buttonIndex - 1); + }, title, buttonLabels, defaultText); + } + }); + } + + /** + * Beep n times. Not available on browser. + * @param times the number of times to beep + */ + static beep(times) { + navigator.notification && navigator.notification.beep(times); + } +} diff --git a/ionic/native/geolocation/geolocation.ts b/ionic/native/geolocation/geolocation.ts new file mode 100644 index 0000000000..d5a146e450 --- /dev/null +++ b/ionic/native/geolocation/geolocation.ts @@ -0,0 +1,46 @@ +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + +@NativePlugin({ + name: 'Geolocation', + platforms: { + cordova: 'cordova-plugin-geolocation' + } +}) +export class Geolocation { + static getCurrentPosition(options) { + return new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(function (result) { + resolve(result); + }, function (err) { + reject(err); + }, options); + }); + } + + static watchPosition(options) { + let watchID; + + let source = Rx.Observable.create((observer) => { + watchID = navigator.geolocation.watchPosition(function (result) { + observer.onNext(result) + }, function(err) { + observer.onError(err, observer); + }, options); + }) + + return { + source: source, + watchID: watchID, + clear: () => { + navigator.geolocation.clearWatch(watchID); + } + } + } + + static clearWatch(watchID) { + return navigator.geolocation.clearWatch(watchID); + } +} diff --git a/ionic/native/plugin.ts b/ionic/native/plugin.ts new file mode 100644 index 0000000000..2c5f660185 --- /dev/null +++ b/ionic/native/plugin.ts @@ -0,0 +1,48 @@ +export class NativePluginDecorator { + constructor(cls, config) { + this.cls = cls; + this.config = config; + + cls.ifPlugin = (check, cb, returnType=null) => { + // Convert to boolean the plugin param + var exists = !!check; + if(typeof check === 'function') { + exists = check(); + } + if(exists) { + return cb(); + } + + cls.pluginWarn(); + + return (typeof returnType === 'function') ? returnType() : returnType; + }; + + cls.pluginWarn = () => { + if(cls._pluginWarned) { + // Only warn once + return; + } + + let platformString = []; + for(var k in this.config.platforms) { + platformString.push('\t' + k + ': '+ this.config.platforms[k]); + } + console.warn('Plugin for ' + this.config.name + + ' not installed. For native functionality, please install the correct plugin for your platform:\n' + + platformString.join('\n')); + + // Set a flag so we don't warn again + cls._pluginWarned = true; + } + } +} + +export function NativePlugin(config) { + return function(cls) { + var annotations = Reflect.getMetadata('annotations', cls) || []; + annotations.push(new NativePluginDecorator(cls, config)); + Reflect.defineMetadata('annotations', annotations, cls); + return cls; + } +} diff --git a/ionic/native/plugins.ts b/ionic/native/plugins.ts new file mode 100644 index 0000000000..7a3b3c79c8 --- /dev/null +++ b/ionic/native/plugins.ts @@ -0,0 +1,10 @@ +export * from './plugin' +export * from './battery/battery' +export * from './camera/camera' +export * from './contacts/contacts' +export * from './dialogs/dialogs' +export * from './device/device' +export * from './device-motion/device-motion' +export * from './device-orientation/device-orientation' +export * from './geolocation/geolocation' +export * from './vibration/vibration' diff --git a/ionic/native/vibration/vibration.ts b/ionic/native/vibration/vibration.ts new file mode 100644 index 0000000000..4b2c24d871 --- /dev/null +++ b/ionic/native/vibration/vibration.ts @@ -0,0 +1,21 @@ +import * as Rx from 'rx'; + +import * as util from 'ionic/util'; +import {NativePlugin} from '../plugin'; + +@NativePlugin({ + name: 'Vibration', + platforms: { + cordova: 'cordova-plugin-vibration' + } +}) +export class Vibration { + static vibrate(pattern) { + if(!navigator.vibrate) { + this.pluginWarn(); + console.log('Vibrate (dev): ', pattern); + } else { + navigator.vibrate(pattern); + } + } +}