From c30be67f65320a0d8bc32d60ac4f9aecc12f905d Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Wed, 11 Jun 2014 16:48:53 -0500 Subject: [PATCH] feat(platforms): Android and iOS Specific Styles and Transitions --- config/lib/testutil.js | 15 ++ js/angular/directive/navBackButton.js | 18 +- js/angular/directive/navBar.js | 13 +- js/angular/directive/navView.js | 4 + js/angular/directive/tab.js | 4 + js/angular/directive/tabs.js | 13 +- js/angular/service/platform.js | 66 +++++- js/angular/service/viewService.js | 9 +- scss/_animations.scss | 174 +++++++++++++- scss/_bar.scss | 10 + scss/_platform.scss | 90 ++++++++ scss/_tabs.scss | 75 +++++- scss/_variables.scss | 4 + test/html/nav.html | 114 +++++++++ test/html/platforms.html | 217 ++++++++++-------- .../angular/directive/headerFooterBar.unit.js | 3 +- .../angular/directive/navBackButton.unit.js | 26 +++ test/unit/angular/directive/navBar.unit.js | 56 +++++ test/unit/angular/directive/tabs.unit.js | 29 +++ 19 files changed, 828 insertions(+), 112 deletions(-) create mode 100644 test/html/nav.html diff --git a/config/lib/testutil.js b/config/lib/testutil.js index 0b2e4e697e3..9b1edf7b887 100644 --- a/config/lib/testutil.js +++ b/config/lib/testutil.js @@ -38,5 +38,20 @@ var TestUtil = { } }; return timeout; + }, + + setPlatform: function(platformName) { + switch(platformName) { + case 'ios': + ionic.Platform.ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25'; + ionic.Platform.setPlatform('ios'); + ionic.Platform.setVersion(null); + break; + case 'android': + ionic.Platform.ua = 'Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1'; + ionic.Platform.setPlatform('android'); + ionic.Platform.setVersion(undefined); + break; + } } }; diff --git a/js/angular/directive/navBackButton.js b/js/angular/directive/navBackButton.js index c82024b38a2..12eba0a7ce6 100644 --- a/js/angular/directive/navBackButton.js +++ b/js/angular/directive/navBackButton.js @@ -64,7 +64,11 @@ IonicModule .directive('ionNavBackButton', [ '$animate', '$rootScope', -function($animate, $rootScope) { + '$sanitize', + + '$ionicNavBarConfig', + +function($animate, $rootScope, $sanitize, $ionicNavBarConfig) { var backIsShown = false; //If the current viewstate does not allow a back button, //always hide it. @@ -76,7 +80,15 @@ function($animate, $rootScope) { require: '^ionNavBar', compile: function(tElement, tAttrs) { tElement.addClass('button back-button ng-hide'); + return function($scope, $element, $attr, navBarCtrl) { + console.log($attr.textFromTitle); + + // Add a default back button icon based on the nav config, unless one is set + if($element[0].className.indexOf('ion-') < 0) { + $element.addClass($ionicNavBarConfig.backButtonIcon); + } + if (!$attr.ngClick) { $scope.$navBack = navBarCtrl.back; $element.on('click', function(event){ @@ -85,10 +97,12 @@ function($animate, $rootScope) { }); }); } - //Make sure both that a backButton is allowed in the first place, //and that it is shown by the current view. $scope.$watch(function() { + if(isDefined($attr.fromTitle)) { + $element[0].innerHTML = '' + $sanitize($scope.oldTitle) + ''; + } return !!(backIsShown && $scope.backButtonShown); }, ionic.animationFrameThrottle(function(show) { if (show) $animate.removeClass($element, 'ng-hide'); diff --git a/js/angular/directive/navBar.js b/js/angular/directive/navBar.js index 0c81bd3c393..5cf214d3bd9 100644 --- a/js/angular/directive/navBar.js +++ b/js/angular/directive/navBar.js @@ -1,4 +1,10 @@ +IonicModule.constant('$ionicNavBarConfig', { + transition: 'nav-title-slide-ios7', + alignTitle: 'center', + backButtonIcon: 'ion-ios7-arrow-back' +}); + /** * @ngdoc directive * @name ionNavBar @@ -78,7 +84,8 @@ IonicModule '$rootScope', '$animate', '$compile', -function($ionicViewService, $rootScope, $animate, $compile) { + '$ionicNavBarConfig', +function($ionicViewService, $rootScope, $animate, $compile, $ionicNavBarConfig) { return { restrict: 'E', @@ -87,7 +94,7 @@ function($ionicViewService, $rootScope, $animate, $compile) { compile: function(tElement, tAttrs) { //We cannot transclude here because it breaks element.data() inheritance on compile tElement - .addClass('bar bar-header nav-bar') + .addClass('bar bar-header nav-bar ' + $ionicNavBarConfig.transition) .append( '
' + '
' + @@ -100,7 +107,7 @@ function($ionicViewService, $rootScope, $animate, $compile) { function prelink($scope, $element, $attr, navBarCtrl) { navBarCtrl._headerBarView = new ionic.views.HeaderBar({ el: $element[0], - alignTitle: $attr.alignTitle || 'center' + alignTitle: $attr.alignTitle || $ionicNavBarConfig.alignTitle || 'center' }); //defaults diff --git a/js/angular/directive/navView.js b/js/angular/directive/navView.js index bcc7f3f8c0e..fa2a2405d69 100644 --- a/js/angular/directive/navView.js +++ b/js/angular/directive/navView.js @@ -1,3 +1,7 @@ +IonicModule.constant('$ionicNavViewConfig', { + transition: 'slide-left-right-ios7' +}); + /** * @ngdoc directive * @name ionNavView diff --git a/js/angular/directive/tab.js b/js/angular/directive/tab.js index f53ecce0de1..db06203e4b3 100644 --- a/js/angular/directive/tab.js +++ b/js/angular/directive/tab.js @@ -1,3 +1,7 @@ +IonicModule.constant('$ionicTabConfig', { + type: '' +}); + /** * @ngdoc directive * @name ionTab diff --git a/js/angular/directive/tabs.js b/js/angular/directive/tabs.js index f4330643166..c228f51edaa 100644 --- a/js/angular/directive/tabs.js +++ b/js/angular/directive/tabs.js @@ -1,3 +1,7 @@ +IonicModule.constant('$ionicTabsConfig', { + position: '', + type: '' +}); /** * @ngdoc directive @@ -46,9 +50,10 @@ IonicModule .directive('ionTabs', [ - '$ionicViewService', - '$ionicTabsDelegate', -function($ionicViewService, $ionicTabsDelegate) { + '$ionicViewService', + '$ionicTabsDelegate', + '$ionicTabsConfig', +function($ionicViewService, $ionicTabsDelegate, $ionicTabsConfig) { return { restrict: 'E', scope: true, @@ -60,6 +65,8 @@ function($ionicViewService, $ionicTabsDelegate) { var innerElement = jqLite('
'); innerElement.append(element.contents()); element.append(innerElement); + element.addClass($ionicTabsConfig.position); + element.addClass($ionicTabsConfig.type); return { pre: prelink }; function prelink($scope, $element, $attr, tabsCtrl) { diff --git a/js/angular/service/platform.js b/js/angular/service/platform.js index 053425c2c23..17d4fd4c951 100644 --- a/js/angular/service/platform.js +++ b/js/angular/service/platform.js @@ -4,6 +4,69 @@ var PLATFORM_BACK_BUTTON_PRIORITY_MODAL = 200; var PLATFORM_BACK_BUTTON_PRIORITY_ACTION_SHEET = 300; var PLATFORM_BACK_BUTTON_PRIORITY_POPUP = 400; var PLATFORM_BACK_BUTTON_PRIORITY_LOADING = 500; + +function componentConfig(defaults) { + defaults.$get = function() { return defaults; } + return defaults; +} + +IonicModule +.constant('$ionicPlatformDefaults', { + 'ios': { + '$ionicNavBarConfig': { + transition: 'nav-title-slide-ios7', + alignTitle: 'center', + backButtonIcon: 'ion-ios7-arrow-back' + }, + '$ionicNavViewConfig': { + transition: 'slide-left-right-ios7' + }, + '$ionicTabsConfig': { + type: '', + position: '' + } + }, + 'android': { + '$ionicNavBarConfig': { + transition: 'no-animation', + alignTitle: 'left', + backButtonIcon: 'ion-android-arrow-back' + }, + '$ionicNavViewConfig': { + transition: 'fade-implode' + }, + '$ionicTabsConfig': { + type: 'tabs-striped', + position: '' + } + } +}) + + +IonicModule.config([ + '$ionicPlatformDefaults', + + '$injector', + +function($ionicPlatformDefaults, $injector) { + var platform = ionic.Platform.platform(); + + var applyConfig = function(platformDefaults) { + forEach(platformDefaults, function(defaults, constantName) { + extend($injector.get(constantName), defaults); + }); + }; + + switch(platform) { + case 'ios': + applyConfig($ionicPlatformDefaults.ios); + break; + case 'android': + applyConfig($ionicPlatformDefaults.android); + break; + } +}]); + /** * @ngdoc service * @name $ionicPlatform @@ -16,10 +79,10 @@ var PLATFORM_BACK_BUTTON_PRIORITY_LOADING = 500; */ IonicModule .provider('$ionicPlatform', function() { - return { $get: ['$q', '$rootScope', function($q, $rootScope) { var self = { + /** * @ngdoc method * @name $ionicPlatform#onHardwareBackButton @@ -138,3 +201,4 @@ IonicModule }; }); + diff --git a/js/angular/service/viewService.js b/js/angular/service/viewService.js index 769ab119af3..64a5082e15c 100644 --- a/js/angular/service/viewService.js +++ b/js/angular/service/viewService.js @@ -93,7 +93,8 @@ function($rootScope, $state, $location, $document, $animate, $ionicPlatform, $io '$window', '$injector', '$animate', -function($rootScope, $state, $location, $window, $injector, $animate) { + '$ionicNavViewConfig', +function($rootScope, $state, $location, $window, $injector, $animate, $ionicNavViewConfig) { var View = function(){}; View.prototype.initialize = function(data) { @@ -440,6 +441,12 @@ function($rootScope, $state, $location, $window, $injector, $animate) { className = el.getAttribute('animation'); el = el.parentElement; } + + // If they don't have an animation set explicitly, use the value in the config + if(!className) { + return $ionicNavViewConfig.transition; + } + return className; } diff --git a/scss/_animations.scss b/scss/_animations.scss index 9f8f4e79c22..80194458d21 100644 --- a/scss/_animations.scss +++ b/scss/_animations.scss @@ -334,6 +334,8 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1); */ $ios7-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1); $ios7-transition-duration: 340ms; +$ios-transition-box-shadow-start: -500px 0px 500px rgba(0,0,0,0), -5px 0px 5px rgba(0,0,0,0); +$ios-transition-box-shadow-end: -500px 0px 500px rgba(0,0,0,0.10), -5px 0px 5px rgba(0,0,0,0.2); .slide-left-right-ios7, .slide-right-left-ios7.reverse { @@ -347,9 +349,11 @@ $ios7-transition-duration: 340ms; left: -1px; width: auto; &:not(.bar) { - border-right: 1px solid #ddd; - border-left: 1px solid #ddd; + border-right: 1px solid transparent; + border-left: 1px solid transparent; } + border-right: none; + border-left: none; } > .ng-enter, &.ng-enter { /* NEW content placed far RIGHT BEFORE it slides IN from the RIGHT */ @@ -377,8 +381,8 @@ $ios7-transition-duration: 340ms; bottom: 0; left: -1px; width: auto; - border-right: 1px solid #ddd; - border-left: 1px solid #ddd; + border-right: 1px solid transparent; + border-left: 1px solid transparent; } > .ng-enter, &.ng-enter { /* NEW content placed far LEFT BEFORE it slides IN from the LEFT */ @@ -396,9 +400,169 @@ $ios7-transition-duration: 340ms; @include translate3d(100%, 0, 0); } } +.grade-a { + .slide-left-right-ios7, .slide-right-left-ios7.reverse { + > .ng-enter, &.ng-enter { + box-shadow: $ios-transition-box-shadow-start; + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + box-shadow: $ios-transition-box-shadow-end; + } + } + .slide-left-right-ios7.reverse, .slide-right-left-ios7 { + > .ng-leave, &.ng-leave { + box-shadow: $ios-transition-box-shadow-end; + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + box-shadow: $ios-transition-box-shadow-start; + } + } +} +/** + * Android style "pop in" with fade and scale + */ +.fade-explode { + > .ng-enter, &.ng-enter, + > .ng-leave, &.ng-leave { + @include transition(all ease-out 300ms); + position: absolute; + top: 0; + right: -1px; + bottom: 0; + left: -1px; + width: auto; + &:not(.bar) { + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; + } + } + > .ng-enter, &.ng-enter { + /* NEW content placed far RIGHT BEFORE it slides IN from the RIGHT */ + @include scale(1.6); + opacity: 0; + z-index: 2; + } + > .ng-leave, &.ng-leave { + z-index: 1; + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + /* NEW content ACTIVELY sliding IN from the RIGHT */ + @include scale(1); + opacity: 1; + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + /* OLD content ACTIVELY sliding OUT to the LEFT */ + @include scale(0.95); + } +} +.fade-explode.reverse { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + @include transition(all ease-out 300ms); + position: absolute; + top: 0; + right: -1px; + bottom: 0; + left: -1px; + width: auto; + &:not(.bar) { + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; + } + } + > .ng-enter, &.ng-enter { + /* NEW content placed far LEFT BEFORE it slides IN from the LEFT */ + @include scale(0.95); + opacity: 0; + z-index: 1; + } + > .ng-leave, &.ng-leave { + @include scale(1); + opacity: 1; + z-index: 2; + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + @include scale(1); + opacity: 1; + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + @include scale(1.6); + opacity: 0; + } +} + +/** + * Android style "pop in" with fade and scale + */ +.fade-implode { + > .ng-enter, &.ng-enter, + > .ng-leave, &.ng-leave { + @include transition(all ease-out 200ms); + position: absolute; + top: 0; + right: -1px; + bottom: 0; + left: -1px; + width: auto; + &:not(.bar) { + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; + } + } + > .ng-enter, &.ng-enter { + /* NEW content placed far RIGHT BEFORE it slides IN from the RIGHT */ + @include scale(0.8); + opacity: 0; + z-index: 2; + } + > .ng-leave, &.ng-leave { + z-index: 1; + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + /* NEW content */ + @include scale(1); + opacity: 1; + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + } +} + +.fade-implode.reverse { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + @include transition(all ease-out 200ms); + position: absolute; + top: 0; + right: -1px; + bottom: 0; + left: -1px; + width: auto; + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; + } + > .ng-enter, &.ng-enter { + @include scale(1); + opacity: 1; + z-index: 1; + } + > .ng-leave, &.ng-leave { + @include scale(1); + opacity: 1; + z-index: 2; + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + opacity: 1; + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + @include scale(0.8); + opacity: 0; + } +} + +/** + * Simple slide-in animation + */ .slide-in-left { @include translate3d(0%,0,0); &.ng-enter, > .ng-enter { @@ -619,3 +783,5 @@ $nav-title-slide-ios7-delay: $ios7-transition-duration; } } } + + diff --git a/scss/_bar.scss b/scss/_bar.scss index cf0997a5205..eba991c8dff 100644 --- a/scss/_bar.scss +++ b/scss/_bar.scss @@ -177,6 +177,16 @@ } } + &.back-button { + padding: 0; + opacity: 0.8; + .back-button-title { + display: inline-block; + vertical-align: middle; + margin-left: 4px; + } + } + &.back-button.active, &.back-button.activated { opacity: 1; diff --git a/scss/_platform.scss b/scss/_platform.scss index d9db29a325d..01c2427628f 100644 --- a/scss/_platform.scss +++ b/scss/_platform.scss @@ -5,6 +5,96 @@ * Platform specific tweaks */ + +/** + * Apply roboto font + */ + +.roboto { + font-family: "Roboto", $font-family-base; + + input { + font-family: "Roboto", $font-family-base; + } +} +.platform-android { + + + .bar { + padding: 0; + + line-height: 40px; + + .button { + line-height: 40px; + } + + .button-icon:before { + font-size: 24px; + } + } + + .back-button { + &.button-icon:before { + line-height: 40px; + } + margin-left: -3px; + padding: 0px 2px !important; + &.ion-android-arrow-back:before { + font-size: 12px; + } + + &.back-button.active, + &.back-button.activated { + background-color: rgba(0,0,0,0.1); + } + } + + .item-divider { + background: none; + border-top-width: 0; + border-bottom-width: 2px; + text-transform: uppercase; + margin-top: 10px; + font-size: 14px; + } + .item { + border-left-width: 0; + border-right-width: 0; + } + + .item-divider ~ .item:not(.item-divider) { + border-bottom-width: 0; + } + + .back-button:not(.ng-hide) + .left-buttons + .title { + // Don't allow normal titles in this mode + display: none; + } + + .bar .title { + text-align: left; + font-weight: normal; + } + + /* + font-family: 'Roboto'; + + h1, h2, h3, h4, h5 { + font-family: 'Roboto', $font-family-base; + } + + .tab-item { + font-family: 'Roboto', $font-family-base; + } + + + input, button, select, textarea { + font-family: 'Roboto', $font-family-base; + } + */ +} + .platform-ios7.platform-cordova { // iOS7 has a status bar which sits on top of the header. // Bump down everything to make room for it. However, if diff --git a/scss/_tabs.scss b/scss/_tabs.scss index 580958a201e..945dd82b4c8 100644 --- a/scss/_tabs.scss +++ b/scss/_tabs.scss @@ -1,4 +1,3 @@ - /** * Tabs * -------------------------------------------------- @@ -89,6 +88,69 @@ @include tab-badge-style($tabs-dark-text, $tabs-dark-bg); } +@mixin tabs-striped($style, $color) { + &.#{$style} { + .tab-item.tab-item-active, + .tab-item.active, + .tab-item.activated { + margin-top: -4px; + color: $color; + border-style: solid; + border-width: $tabs-striped-border-width 0 0 0; + border-color: $color; + } + } +} + +.tabs-striped { + + .tabs { + background-color: white; + background-image: none; + border: none; + } + + @include tabs-striped('tabs-light', $light); + @include tabs-striped('tabs-stable', $stable); + @include tabs-striped('tabs-positive', $positive); + @include tabs-striped('tabs-calm', $calm); + @include tabs-striped('tabs-assertive', $assertive); + @include tabs-striped('tabs-balanced', $balanced); + @include tabs-striped('tabs-energized', $energized); + @include tabs-striped('tabs-royal', $royal); + @include tabs-striped('tabs-dark', $dark); + + &.tabs-icon-only .icon { + } + + .tab-item { + color: $tabs-striped-off-color; + opacity: $tabs-striped-off-opacity; + } + .tab-item + .tab-item:before { + border-left: 1px solid #ccc; + display: block; + float: left; + width: 2px; + height: 26px; + margin: 11px auto; + content: " "; + } + +} + +.tabs-top { + &.tabs-striped { + .tab-item.tab-item-active, + .tab-item.active, + .tab-item.activated { + margin-top: 0; + margin-bottom: -4px; + border-width: 0px 0px $tabs-striped-border-width 0px !important; + } + } +} + /* Allow parent element to have tabs-top */ /* If you change this, change platform.scss as well */ .tabs-top > .tabs, @@ -97,6 +159,7 @@ padding-top: 0; padding-bottom: 2px; background-position: bottom; + } .tab-item { @@ -176,6 +239,7 @@ line-height: inherit; } + .tab-item.has-badge { position: relative; } @@ -191,6 +255,7 @@ line-height: $tabs-badge-font-size + 4; } + /* Navigational tab */ /* Active state for tab */ @@ -243,3 +308,11 @@ cursor: default; pointer-events: none; } + +/** Platform styles **/ + +.tab-item.tab-item-ios { +} +.tab-item.tab-item-android { + border-top: 2px solid inherit; +} diff --git a/scss/_variables.scss b/scss/_variables.scss index b35043defcc..bb53aca2fa8 100644 --- a/scss/_variables.scss +++ b/scss/_variables.scss @@ -276,6 +276,10 @@ $tabs-default-text: $tabs-stable-text !default; $tab-item-max-width: 150px !default; +$tabs-striped-off-color: #000; +$tabs-striped-off-opacity: 0.4; +$tabs-striped-border-width: 4px; + // Items // ------------------------------- diff --git a/test/html/nav.html b/test/html/nav.html new file mode 100644 index 00000000000..7f41eda3ac3 --- /dev/null +++ b/test/html/nav.html @@ -0,0 +1,114 @@ + + + + + + navViews and ion-tabs w/ nested navViews + + + + + + + +
+ + + + + +
+ + + + + + + diff --git a/test/html/platforms.html b/test/html/platforms.html index af4016451b9..3b489b21144 100644 --- a/test/html/platforms.html +++ b/test/html/platforms.html @@ -8,130 +8,155 @@ + +
- - - Back - - - +