diff --git a/lib/touch/module.dart b/lib/touch/module.dart new file mode 100644 index 000000000..3a3e171ec --- /dev/null +++ b/lib/touch/module.dart @@ -0,0 +1,27 @@ +/** + * Touch related functionality for AngularDart apps. + * + * To use, install the TouchModule into your main module: + * + * var module = new Module() + * ..install(new TouchModule()); + * + * Once the module is installed, you can use decorators such + * as ng-swipe-left or ng-swipe right + */ + +library angular.touch; + +import 'dart:html' as dom; +import 'package:di/di.dart'; +import 'package:angular/core/annotation.dart'; + +part 'ng_swipe.dart'; + +class TouchModule extends Module { + TouchModule() { + bind(NgSwipeLeft, toValue: null); + bind(NgSwipeRight, toValue: null); + } +} + \ No newline at end of file diff --git a/lib/touch/ng_swipe.dart b/lib/touch/ng_swipe.dart new file mode 100644 index 000000000..c62a78db4 --- /dev/null +++ b/lib/touch/ng_swipe.dart @@ -0,0 +1,106 @@ +part of angular.touch; + +/** + * Base class for Swipe Gesture. Decides whether a swipe is performed + * and gives the x, y direction of the swipe. + */ +abstract class _SwipeGesture { + static const int NO_SWIPE = -1; + static const int DOWN = 0; + static const int UP = 1; + static const int LEFT = 2; + static const int RIGHT = 3; + + // Less than 100 pixels of move in each direction does not count. + static const int _POS_TOLERANCE = 100; + // If swipe lasts more than 1s, it is not a swipe. + static const int _TIME_TOLERANCE = 1000; + + // Function to be called on swipe. + Function fn; + + // Subclasses decide on swipe direction whether to call fn or not. + bool get shouldFire; + + int _startX; + int _startY; + int _startTime; + int xDirection; + int yDirection; + + _SwipeGesture(dom.Element target) { + target.onTouchStart.listen(_handleTouchStartEvent); + target.onTouchEnd.listen(_handleTouchEndEvent); + } + + void handleTouchStart(int x, int y, int timestamp) { + // Reset values every time swipe starts + xDirection = NO_SWIPE; + yDirection = NO_SWIPE; + _startX = x; + _startY = y; + _startTime = timestamp; + } + + void handleTouchEnd(int x, int y, int timestamp) { + int touchDuration = timestamp - _startTime; + if (touchDuration > _TIME_TOLERANCE) { + return; + } + if (y > _startY + _POS_TOLERANCE) { + yDirection = DOWN; + } else if (y < _startY - _POS_TOLERANCE) { + yDirection = UP; + } + if (x > _startX + _POS_TOLERANCE) { + xDirection = RIGHT; + } else if (x < _startX - _POS_TOLERANCE) { + xDirection = LEFT; + } + if (fn != null && shouldFire) { + fn(); + } + } + + void _handleTouchStartEvent(dom.TouchEvent ev) { + // Guaranteed to have at least one touch in changedTouches. + dom.Touch t = ev.changedTouches.first; + handleTouchStart(t.client.x, t.client.y, ev.timeStamp); + } + + void _handleTouchEndEvent(dom.TouchEvent ev) { + // Guaranteed to have at least one touch in changedTouches. + dom.Touch t = ev.changedTouches.first; + handleTouchEnd(t.client.x, t.client.y, ev.timeStamp); + } +} + +/** + * The `ng-swipe-right` directive allows execution of callbacks when user + * swipes her finger to the right. + * Also see [NgSwipeLeft]. + */ +@Decorator( + selector: '[ng-swipe-right]', + map: const {'ng-swipe-right':'&fn'}) +class NgSwipeRight extends _SwipeGesture { + NgSwipeRight(dom.Element target): super(target); + + bool get shouldFire => xDirection == _SwipeGesture.RIGHT && + yDirection == _SwipeGesture.NO_SWIPE; +} + +/** + * The `ng-swipe-left` directive allows execution of callbacks when user + * swipes his finger to the left. + * Also see [NgSwipeRight]. + */ +@Decorator( + selector: '[ng-swipe-left]', + map: const {'ng-swipe-left':'&fn'}) +class NgSwipeLeft extends _SwipeGesture { + NgSwipeLeft(dom.Element target): super(target); + + bool get shouldFire => xDirection == _SwipeGesture.LEFT && + yDirection == _SwipeGesture.NO_SWIPE; +} diff --git a/test/_specs.dart b/test/_specs.dart index ead725598..e9d3b65bc 100644 --- a/test/_specs.dart +++ b/test/_specs.dart @@ -30,6 +30,7 @@ export 'package:angular/directive/module.dart'; export 'package:angular/formatter/module.dart'; export 'package:angular/routing/module.dart'; export 'package:angular/animate/module.dart'; +export 'package:angular/touch/module.dart'; export 'package:angular/mock/module.dart'; export 'package:perf_api/perf_api.dart'; diff --git a/test/angular_spec.dart b/test/angular_spec.dart index bc30ab523..401111d8f 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -70,6 +70,23 @@ main() { assertSymbolNamesAreOk(ALLOWED_NAMES, libraryInfo); }); + + it('should not export unknown symbols from touch', () { + LibraryInfo libraryInfo; + try { + libraryInfo = getSymbolsFromLibrary("angular.touch"); + } on UnimplementedError catch (e) { + return; // Not implemented, quietly skip. + } + + var ALLOWED_NAMES = [ + "angular.touch.NgSwipeLeft", + "angular.touch.NgSwipeRight", + "angular.touch.TouchModule" + ]; + assertSymbolNamesAreOk(ALLOWED_NAMES, libraryInfo); + + }); it('should not export unknown symbols from angular', () { // Test is failing? Add new symbols to the "ALLOWED_NAMES" list below. diff --git a/test/touch/ng_swipe_spec.dart b/test/touch/ng_swipe_spec.dart new file mode 100644 index 000000000..5f25af765 --- /dev/null +++ b/test/touch/ng_swipe_spec.dart @@ -0,0 +1,71 @@ +library ng_swipe_spec; + +import '../_specs.dart'; + +/** + * Unfortunately, it is not possible to test swipe using events since + * TouchEvent cannot be constructed using fake dom.Touch elements. + * See: dartbug.com/8314 + * TODO(8314): Once this is fixed, should update the tests. + */ +void main() { + describe('ng-swipe-right', () { + NgSwipeRight swipe = new NgSwipeRight(new DivElement()); + + it('should not fire when distance is not enough', () { + swipe.handleTouchStart(10, 10, 0); + swipe.handleTouchEnd(15, 15, 1); + expect(swipe.shouldFire).toBeFalse(); + }); + + it('should fire on swipe to the right', () { + swipe.handleTouchStart(10, 10, 0); + swipe.handleTouchEnd(130, 15, 1); + expect(swipe.shouldFire).toBeTrue(); + }); + + it('should not fire on swipe to the left', () { + swipe.handleTouchStart(130, 10, 0); + swipe.handleTouchEnd(10, 15, 1); + expect(swipe.shouldFire).toBeFalse(); + }); + + it('should not fire on slow swipe', () { + swipe.handleTouchStart(10, 10, 0); + // 2 seconds later + swipe.handleTouchEnd(130, 15, 2000); + expect(swipe.shouldFire).toBeFalse(); + }); + + + }); + + describe('ng-swipe-left', () { + NgSwipeLeft swipe = new NgSwipeLeft(new DivElement()); + + it('should not fire when distance is not enough', () { + swipe.handleTouchStart(10, 10, 0); + swipe.handleTouchEnd(15, 15, 1); + expect(swipe.shouldFire).toBeFalse(); + }); + + it('should not fire on swipe to the right', () { + swipe.handleTouchStart(10, 10, 0); + swipe.handleTouchEnd(130, 15, 1); + expect(swipe.shouldFire).toBeFalse(); + }); + + it('should fire on swipe to the left', () { + swipe.handleTouchStart(130, 10, 0); + swipe.handleTouchEnd(10, 15, 1); + expect(swipe.shouldFire).toBeTrue(); + }); + + it('should not fire on swipe on slow swipe', () { + swipe.handleTouchStart(130, 10, 0); + // 2 seconds later + swipe.handleTouchEnd(10, 15, 2000); + expect(swipe.shouldFire).toBeFalse(); + }); + }); +}