diff --git a/demos/i18n/index.ts b/demos/i18n/index.ts new file mode 100644 index 0000000000..bdf66c9b61 --- /dev/null +++ b/demos/i18n/index.ts @@ -0,0 +1,27 @@ +import {Component} from 'angular2/angular2'; +import {Control, ControlGroup} from 'angular2/forms'; + +import {IonicApp, App, Http} from 'ionic/ionic'; + +import {Translate, TranslatePipe} from 'ionic/ionic'; + +@App({ + templateUrl: 'main.html', + pipes: [TranslatePipe] +}) +class MyApp { + constructor(app: IonicApp, trans: Translate) { + this.app = app; + this.trans = trans; + + this.trans.translations('de', { + 'Location': 'lage' + }); + + console.log(this.trans.translate('Location')); + console.log(this.trans.translate('Location', 'de')); + //this.trans.setLanguage('de'); + console.log(this.trans.translate('Location')); + + } +} diff --git a/demos/i18n/main.html b/demos/i18n/main.html new file mode 100644 index 0000000000..d73601f91c --- /dev/null +++ b/demos/i18n/main.html @@ -0,0 +1,5 @@ + + + Hello! What is your {{ 'Location' | translate }} + + diff --git a/demos/navigation/index.ts b/demos/navigation/index.ts index 632fa32380..8b6cd3ad9c 100644 --- a/demos/navigation/index.ts +++ b/demos/navigation/index.ts @@ -11,7 +11,7 @@ import {NavParams, NavController} from 'ionic/ionic'; '' + '

{{title}}

' + '

' + - '

' + + '

' + '

' + '
' }) diff --git a/ionic/animations/animation.ts b/ionic/animations/animation.ts index 4a0bd81689..c88febc0eb 100644 --- a/ionic/animations/animation.ts +++ b/ionic/animations/animation.ts @@ -408,6 +408,21 @@ export class Animation { } } + /** + * Get the current time of the first animation + * in the list. To get a specific time of an animation, call + * subAnimationInstance.getCurrentTime() + */ + getCurrentTime() { + if(this._chld.length > 0) { + return this._chld[0].getCurrentTime(); + } + if(this._ani.length > 0) { + return this._ani[0].getCurrentTime(); + } + return 0; + } + progressEnd(shouldComplete, rate=3) { let promises = []; @@ -604,6 +619,10 @@ class Animate { } } + getCurrentTime() { + return this.ani && this.ani.currentTime; + } + playbackRate(value) { this.rate = value; if (this.ani) { diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts index def11107a2..56d46155e3 100644 --- a/ionic/components/app/app.ts +++ b/ionic/components/app/app.ts @@ -14,6 +14,7 @@ import {Popup} from '../popup/popup'; import {FocusHolder} from '../form/focus-holder'; import {Events} from '../../util/events'; import {NavRegistry} from '../nav/nav-registry'; +import {Translate} from '../../translation/translate'; /** * @name IonicApp @@ -298,6 +299,8 @@ export function ionicBootstrap(rootComponentType, views, config) { let modal = new Modal(app, config); let popup = new Popup(app, config); let events = new Events(); + let translate = new Translate(); + console.log('Translate', translate); let navRegistry = new NavRegistry(views); // add injectables that will be available to all child components @@ -312,6 +315,7 @@ export function ionicBootstrap(rootComponentType, views, config) { bind(Events).toValue(events), ROUTER_BINDINGS, bind(LocationStrategy).toClass(HashLocationStrategy), + bind(Translate).toValue(translate), bind(NavRegistry).toValue(navRegistry) ]); diff --git a/ionic/components/tap-click/ripple.ts b/ionic/components/tap-click/ripple.ts index 5d34126edb..e0b259de78 100644 --- a/ionic/components/tap-click/ripple.ts +++ b/ionic/components/tap-click/ripple.ts @@ -4,6 +4,16 @@ import {Animation} from '../../animations/animation'; export class RippleActivator extends Activator { + static TOUCH_DOWN_ACCEL = 512; + static TOUCH_UP_ACCEL = 1024; + static OPACITY_DECAY_VEL = 1.6; + static OUTER_OPACITY_VEL = 1.2; + + static OPACITY_OUT_DURATION = 750; + + static EXPAND_OUT_PLAYBACK_RATE = 3.5; + static DOWN_PLAYBACK_RATE = 0.65; + static OPACITY_OUT_PLAYBACK_RATE = 1; constructor(app, config) { super(app, config); @@ -17,7 +27,19 @@ export class RippleActivator extends Activator { super.downAction(ev, activatableEle, pointerX, pointerY); // create a new ripple element - let r = activatableEle.getBoundingClientRect(); + let r = targetEle.getBoundingClientRect(); + let w = r.width; + let h = r.height; + + let halfW = w/2; + let halfH = h/2; + let outerRadius = Math.sqrt(halfW * halfW + halfH * halfH); + + let radiusDuration = (1000 * Math.sqrt(outerRadius / RippleActivator.TOUCH_DOWN_ACCEL) + 0.5); + let outerDuration = 1000 * (1/RippleActivator.OUTER_OPACITY_VEL); + + //console.log('Rippling', radiusDuration); + let x = Math.max(Math.abs(r.width - pointerX), pointerX) * 2; let y = Math.max(Math.abs(r.height - pointerY), pointerY) * 2; let size = (Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))) - 10; @@ -34,14 +56,16 @@ export class RippleActivator extends Activator { activatableEle.appendChild(rippleEle); let ripple = this.ripples[Date.now()] = { ele: rippleEle }; + ripple.outerRadius = outerRadius; + ripple.radiusDuration = radiusDuration; // expand the circle from the users starting point // start slow, and when they let up, then speed up the animation ripple.expand = new Animation(rippleEle, {renderDelay: 0}); ripple.expand .fromTo('scale', '0.001', '1') - .duration(300) - .playbackRate(0.35) + .duration(radiusDuration) + .playbackRate(RippleActivator.DOWN_PLAYBACK_RATE) .onFinish(()=> { // finished expanding ripple.expand && ripple.expand.dispose(); @@ -60,16 +84,26 @@ export class RippleActivator extends Activator { let ripple; for (let rippleId in this.ripples) { ripple = this.ripples[rippleId]; + if(ripple.expand) { + let currentTime = ripple.expand.getCurrentTime(); + + // How much more time do we need to finish the radius animation? + // Math: (radius/second) * ((total_radius_time) - current_time) + let remainingTime = (ripple.outerRadius / ripple.radiusDuration) * + ((ripple.radiusDuration / RippleActivator.DOWN_PLAYBACK_RATE)- (currentTime)); + ripple.expand.remainingTime = remainingTime; + } if (!ripple.fade) { // ripple has not been let up yet // spped up the rate if the animation is still going setTimeout(() => { - ripple.expand && ripple.expand.playbackRate(1); + ripple.expand && ripple.expand.playbackRate(RippleActivator.EXPAND_OUT_PLAYBACK_RATE); ripple.fade = new Animation(ripple.ele); ripple.fade .fadeOut() - .duration(750) + .duration(ripple.epxand && ripple.expand.remaingTime || RippleActivator.OPACITY_OUT_DURATION) + .playbackRate(RippleActivator.OPACITY_OUT_PLAYBACK_RATE) .onFinish(() => { ripple.fade && ripple.fade.dispose(); ripple.fade = null; diff --git a/ionic/ionic.ts b/ionic/ionic.ts index 6ccc5c4203..c7fa97ad3f 100644 --- a/ionic/ionic.ts +++ b/ionic/ionic.ts @@ -24,3 +24,6 @@ export * from './transitions/ios-transition' export * from './transitions/md-transition' export * from './platform/plugins' + +export * from './translation/translate' +export * from './translation/translate_pipe' diff --git a/ionic/translation/translate.ts b/ionic/translation/translate.ts new file mode 100644 index 0000000000..835433e57d --- /dev/null +++ b/ionic/translation/translate.ts @@ -0,0 +1,41 @@ +import {Injectable} from 'angular2/angular2'; + +@Injectable() +export class Translate { + constructor() { + this._transMap = {}; + } + + translations(lang, map) { + this._transMap[lang] = map; + } + + setLanguage(lang) { + this._language = lang; + } + + getTranslations(lang) { + return this._transMap[lang]; + } + + translate(key, lang) { + // If the language isn't specified and we have no overridden one, return the string passed. + if(!lang && !this._language) { + return key; + } + + let setLanguage = lang || this._language; + + let map = this.getTranslations(setLanguage); + + if(!map) { + console.warn('I18N: No translation for key', key, 'using language', setLanguage); + return ''; + } + return this._getTranslation(map, key); + } + + _getTranslation(map, key) { + return map && map[key] || ''; + } +} diff --git a/ionic/translation/translate_pipe.ts b/ionic/translation/translate_pipe.ts new file mode 100644 index 0000000000..34181b6240 --- /dev/null +++ b/ionic/translation/translate_pipe.ts @@ -0,0 +1,30 @@ +import {Injectable, Pipe, PipeTransform} from 'angular2/angular2'; + +import {Translate} from './translate'; + +/** + * The Translate pipe makes it easy to translate strings. + * + * @usage + * Translate using the current language or language set through Translate.setLanguage + * {{ 'Please enter your location' | translate }} + * + * Translate using a specific language + * {{ 'Please enter your location' | translate:"de" }} + */ +@Pipe({name: 'translate'}) +@Injectable() +export class TranslatePipe implements PipeTransform { + constructor(translate: Translate) { + this.translate = translate; + } + transform(value, args) { + let lang; + if(args.length > 0) { + lang = args[0]; + } + return this.translate.translate(value, lang); + } + + supports(obj) { return true; } +}