diff --git a/apps/camera/js/controllers/preview-gallery.js b/apps/camera/js/controllers/preview-gallery.js index 91521be0bd6d..d45d1ae4bf76 100644 --- a/apps/camera/js/controllers/preview-gallery.js +++ b/apps/camera/js/controllers/preview-gallery.js @@ -66,7 +66,7 @@ PreviewGalleryController.prototype.openPreview = function() { this.view.on('click:share', this.shareCurrentItem); this.view.on('click:delete', this.deleteCurrentItem); this.view.on('click:back', this.closePreview); - this.view.on('itemChange', this.handleItemChange); + this.view.on('swipe', this.handleSwipe); // If lockscreen is locked, hide all control buttons var secureMode = this.app.inSecureMode; @@ -204,15 +204,12 @@ PreviewGalleryController.prototype.updatePreviewGallery = function(index) { /** * To Do: Image Swipe Transition */ -PreviewGalleryController.prototype.handleItemChange = function(e) { - var direction = e.detail.direction; - switch (direction) { - case 'left': // go to next image +PreviewGalleryController.prototype.handleSwipe = function(direction) { + if (direction === 'left') { this.next(); - break; - case 'right': // go to previous + } + else if (direction === 'right') { this.previous(); - break; } }; diff --git a/apps/camera/js/lib/orientation.js b/apps/camera/js/lib/orientation.js index 050a7e6ad1cb..6e4f0d9a0bf6 100644 --- a/apps/camera/js/lib/orientation.js +++ b/apps/camera/js/lib/orientation.js @@ -15,6 +15,25 @@ define(function(require, exports, module) { current = degrees; } + // Camera normally has its orientation locked to portrait mode. + // But we unlock orientation when displaying image and video previews. + // When orientation is unlocked, we call listener.stop(). + // We calls call stop() when recording a video, and then restart + // when recording is done. If our app ever changes so that we can call + // unlock while the orientation listener is in the stopped state, then + // we would need to modify the lock() function so that it did not + // restart the listener. That is not needed now, however and is omitted. + + function unlock() { + screen.mozUnlockOrientation(); + listener.stop(); + } + + function lock() { + screen.mozLockOrientation('portrait-primary'); + listener.start(); + } + /** * Exports */ @@ -24,6 +43,8 @@ define(function(require, exports, module) { off: listener.off, start: listener.start, stop: listener.stop, + unlock: unlock, + lock: lock, get: function() { return current; } diff --git a/apps/camera/js/lib/panzoom.js b/apps/camera/js/lib/panzoom.js index 436771edded1..4e46fa7c4f42 100644 --- a/apps/camera/js/lib/panzoom.js +++ b/apps/camera/js/lib/panzoom.js @@ -6,7 +6,6 @@ define(function(require, exports, module) { */ var GestureDetector = require('GestureDetector'); -var orientation = require('lib/orientation'); /** * Exports @@ -16,22 +15,33 @@ module.exports = addPanAndZoomHandlers; /* * This module adds pan-and-zoom capability to images displayed by - * shared/js/media/media_frame.js. + * shared/js/media/media_frame.js. * It is used by preview-gallery.js and confirm.js */ -function addPanAndZoomHandlers(frame) { - // frame is the MediaFrame object. container is its the DOM element. +function addPanAndZoomHandlers(frame, swipeCallback) { + // frame is the MediaFrame object. container is its DOM element. var container = frame.container; // Generate gesture events for the container var gestureDetector = new GestureDetector(container); gestureDetector.startDetecting(); + // When the user touches the screen and moves their finger left or + // right, they might want to pan within a zoomed-in image, or they + // might want to swipe between multiple items in the camera preview + // gallery. We pass the amount of motion to the MediaFrame pan() method, + // and it returns the amount that cannot be used to pan the displayed + // item. We track this returned amount as how far left or right the + // image has been swiped, and pass the number to the swipeCallback. + var swipeAmount = 0; + // And handle them with these listeners container.addEventListener('dbltap', handleDoubleTap); container.addEventListener('transform', handleTransform); container.addEventListener('pan', handlePan); - container.addEventListener('swipe', handleSwipe); + if (swipeCallback) { + container.addEventListener('swipe', handleSwipe); + } function handleDoubleTap(e) { var scale; @@ -42,104 +52,32 @@ function addPanAndZoomHandlers(frame) { scale = 2; } - // If the phone orientation is 0 (unrotated) then the gesture detector's - // event coordinates match what's on the screen, and we use them to - // specify a point to zoom in or out on. For other orientations we could - // calculate the correct point, but instead just use the midpoint. - var x, y; - if (orientation.get() === 0) { - x = e.detail.clientX; - y = e.detail.clientY; - } - else { - x = container.offsetWidth / 2; - y = container.offsetHeight / 2; - } - - frame.zoom(scale, x, y, 200); + frame.zoom(scale, e.detail.clientX, e.detail.clientY, 200); } function handleTransform(e) { - // If the phone orientation is 0 (unrotated) then the gesture detector's - // event coordinates match what's on the screen, and we use them to - // specify a point to zoom in or out on. For other orientations we could - // calculate the correct point, but instead just use the midpoint. - var x, y; - if (orientation.get() === 0) { - x = e.detail.midpoint.clientX; - y = e.detail.midpoint.clientY; - } - else { - x = container.offsetWidth / 2; - y = container.offsetHeight / 2; - } - - frame.zoom(e.detail.relative.scale, x, y); + frame.zoom(e.detail.relative.scale, + e.detail.midpoint.clientX, e.detail.midpoint.clientY); } function handlePan(e) { - // The gesture detector event does not take our CSS rotation into - // account, so we have to pan by a dx and dy that depend on how - // the MediaFrame is rotated - var dx, dy; - switch (orientation.get()) { - case 0: - dx = e.detail.relative.dx; - dy = e.detail.relative.dy; - break; - case 90: - dx = -e.detail.relative.dy; - dy = e.detail.relative.dx; - break; - case 180: - dx = -e.detail.relative.dx; - dy = -e.detail.relative.dy; - break; - case 270: - dx = e.detail.relative.dy; - dy = -e.detail.relative.dx; - break; + var dx = e.detail.relative.dx; + var dy = e.detail.relative.dy; + + if (swipeCallback) { + dx += swipeAmount; + swipeAmount = frame.pan(dx, dy); + swipeCallback(swipeAmount); + } else { + frame.pan(dx, dy); } - - frame.pan(dx, dy); } function handleSwipe(e) { - var direction = e.detail.direction; - switch (orientation.get()) { - case 90: - switch (e.detail.direction) { - case 'up': direction = 'right'; break; - case 'down': direction = 'left'; break; - case 'left': direction = 'up'; break; - case 'right': direction = 'down'; break; - } - break; - case 180: - switch (e.detail.direction) { - case 'up': direction = 'down'; break; - case 'down': direction = 'up'; break; - case 'left': direction = 'right'; break; - case 'right': direction = 'left'; break; - } - break; - case 270: - switch (e.detail.direction) { - case 'up': direction = 'left'; break; - case 'down': direction = 'right'; break; - case 'left': direction = 'down'; break; - case 'right': direction = 'up'; break; - } - break; + if (swipeAmount !== 0) { + swipeCallback(swipeAmount, e.detail.vx); + swipeAmount = 0; } - e.detail.direction = direction; - - var itemChangeEvent = new CustomEvent('orientationSwipe', { - detail: e.detail - }); - /*jshint validthis:true */ - this.dispatchEvent(itemChangeEvent); - } } diff --git a/apps/camera/js/vendor/orientation.js b/apps/camera/js/vendor/orientation.js index 14f011fbea15..bc75c2eed45f 100644 --- a/apps/camera/js/vendor/orientation.js +++ b/apps/camera/js/vendor/orientation.js @@ -23,7 +23,7 @@ define(function() { var lastMotionFilteredTime = 0; var lastMotionData = {x: 0, y: 0, z: 0, t: 0}; - var pendingOrientation = 0; + var pendingOrientation = null; var orientationChangeTimer = 0; var eventListeners = {'orientation': []}; @@ -123,20 +123,35 @@ define(function() { window.clearTimeout(orientationChangeTimer); } - // create timer for waiting to rotate the phone - pendingOrientation = orientation; - orientationChangeTimer = window.setTimeout(function doOrient() { + // If we don't have any current orientation, then send an event right away + // Otherwise, wait to make sure we're stable before sending it. + if (pendingOrientation === null) { + pendingOrientation = orientation; fireOrientationChangeEvent(pendingOrientation); - orientationChangeTimer = 0; - }, ORIENTATION_CHANGE_INTERVAL); + } + else { + // create timer for waiting to rotate the phone + pendingOrientation = orientation; + orientationChangeTimer = window.setTimeout(function doOrient() { + fireOrientationChangeEvent(pendingOrientation); + orientationChangeTimer = 0; + }, ORIENTATION_CHANGE_INTERVAL); + } } function start() { + // Reset our state so that the first devicemotion event we get + // will always generate an orientation event. + pendingOrientation = null; window.addEventListener('devicemotion', handleMotionEvent); } function stop() { window.removeEventListener('devicemotion', handleMotionEvent); + if (orientationChangeTimer) { + clearTimeout(orientationChangeTimer); + orientationChangeTimer = 0; + } } function addEventListener(type, listener) { diff --git a/apps/camera/js/views/confirm.js b/apps/camera/js/views/confirm.js index 2086699d03aa..6dc34b715d46 100644 --- a/apps/camera/js/views/confirm.js +++ b/apps/camera/js/views/confirm.js @@ -9,6 +9,7 @@ var addPanAndZoomHandlers = require('lib/panzoom'); var MediaFrame = require('MediaFrame'); var View = require('vendor/view'); var bind = require('lib/bind'); +var orientation = require('lib/orientation'); /** * Exports @@ -47,15 +48,18 @@ module.exports = View.extend({ setupMediaFrame: function() { this.mediaFrame = new MediaFrame(this.els.mediaFrame); addPanAndZoomHandlers(this.mediaFrame); + window.addEventListener('resize', this.onResize); return this; }, hide: function() { this.el.classList.add('hidden'); + orientation.lock(); }, show: function() { this.el.classList.remove('hidden'); + orientation.unlock(); }, showImage: function(image) { @@ -96,7 +100,15 @@ module.exports = View.extend({ this.emit('click:' + name); }, + onResize: function() { + this.mediaFrame.resize(); + if (this.mediaFrame.displayingVideo) { + this.mediaFrame.video.setPlayerSize(); + } + }, + onDestroy: function() { + window.removeEventListener('resize', this.onResize); this.mediaFrame.clear(); } }); diff --git a/apps/camera/js/views/preview-gallery.js b/apps/camera/js/views/preview-gallery.js index 69232b0796d3..bc5eb4721243 100644 --- a/apps/camera/js/views/preview-gallery.js +++ b/apps/camera/js/views/preview-gallery.js @@ -13,6 +13,16 @@ var orientation = require('lib/orientation'); var addPanAndZoomHandlers = require('lib/panzoom'); var MediaFrame = require('MediaFrame'); +/** + * Constants + */ +var SWIPE_DISTANCE_THRESHOLD = window.innerWidth / 3; // pixels +var SWIPE_VELOCITY_THRESHOLD = 1.0; // pixels/ms + +var SWIPE_DURATION = 250; // How long to animate the swipe +var FADE_IN_DURATION = 500; // How long to animate the fade in after swipe + + /** * Locals */ @@ -34,10 +44,8 @@ return View.extend({ this.els.previewMenu = this.find('.js-preview-menu'); bind(this.el, 'click', this.onClick); - bind(this.els.mediaFrame, 'orientationSwipe', this.orientationSwipe); attach.on(this.els.previewMenu, 'click', '.js-btn', this.onButtonClick); - orientation.on('orientation', this.setOrientation); this.configure(); return this; }, @@ -55,8 +63,8 @@ return View.extend({ bind(this.els.player, 'pause', this.handleVideoStop); bind(this.els.player, 'ended', this.handleVideoStop); - this.items = []; - addPanAndZoomHandlers(this.frame); + this.currentIndex = this.lastIndex = 0; + addPanAndZoomHandlers(this.frame, this.swipeCallback); }, template: function() { @@ -65,7 +73,7 @@ return View.extend({ '
' + '' + '
' + - '
' + + '
' + '
' + '