From 4727c84a18dddb824ab0049fb12c9518dbd3b4ca Mon Sep 17 00:00:00 2001 From: Manuel Saelices Date: Mon, 25 Feb 2019 13:49:45 +0100 Subject: [PATCH 01/11] chore: Make NodeRequire declaration match the latest webpack-env one. --- tns-core-modules/tns-core-modules.d.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tns-core-modules/tns-core-modules.d.ts b/tns-core-modules/tns-core-modules.d.ts index 495b0ee5b..1a9ec6d30 100644 --- a/tns-core-modules/tns-core-modules.d.ts +++ b/tns-core-modules/tns-core-modules.d.ts @@ -22,12 +22,15 @@ declare var console: Console; declare var require: NodeRequire; // Extend NodeRequire with the webpack's require context extension. +interface RequireContext { + keys(): string[]; + (id: string): any; + (id: string): T; + resolve(id: string): string; +} + interface NodeRequire { - context(root: string, recursive: boolean, filter: RegExp): { - (module: string): any; - id: number; - keys(): string[]; - } + context(path: string, deep?: boolean, filter?: RegExp): RequireContext; } declare var __dirname: string; From 9c3bbd699b660fe3dac8b7e8011380023626c160 Mon Sep 17 00:00:00 2001 From: Manuel Saelices Date: Tue, 18 Jun 2019 09:49:02 +0200 Subject: [PATCH 02/11] chore: Fix http-request short imports on Android. --- .../http/http-request/http-request.android.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tns-core-modules/http/http-request/http-request.android.ts b/tns-core-modules/http/http-request/http-request.android.ts index 150b2ba89..47a314bcd 100644 --- a/tns-core-modules/http/http-request/http-request.android.ts +++ b/tns-core-modules/http/http-request/http-request.android.ts @@ -25,24 +25,24 @@ let requestIdCounter = 0; const pendingRequests = {}; let imageSource: typeof imageSourceModule; -function ensureImageSource() { - if (!imageSource) { - imageSource = require("image-source"); - } +function ensureImageSource() { + if (!imageSource) { + imageSource = require("tns-core-modules/image-source"); + } } -let platform: typeof platformModule; -function ensurePlatform() { - if (!platform) { - platform = require("platform"); - } +let platform: typeof platformModule; +function ensurePlatform() { + if (!platform) { + platform = require("tns-core-modules/platform"); + } } let fs: typeof fsModule; -function ensureFileSystem() { - if (!fs) { - fs = require("file-system"); - } +function ensureFileSystem() { + if (!fs) { + fs = require("tns-core-modules/file-system"); + } } let completeCallback: org.nativescript.widgets.Async.CompleteCallback; From 6d422a3dba7f2080b5a65524b4e100dc6a6ec68f Mon Sep 17 00:00:00 2001 From: Manuel Saelices Date: Tue, 18 Jun 2019 09:49:02 +0200 Subject: [PATCH 03/11] chore: Fix http-request short imports on Android. --- tns-core-modules/http/http-request/http-request.android.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tns-core-modules/http/http-request/http-request.android.ts b/tns-core-modules/http/http-request/http-request.android.ts index 58969f60f..8800046c5 100644 --- a/tns-core-modules/http/http-request/http-request.android.ts +++ b/tns-core-modules/http/http-request/http-request.android.ts @@ -27,21 +27,21 @@ const pendingRequests = {}; let imageSource: typeof imageSourceModule; function ensureImageSource() { if (!imageSource) { - imageSource = require("image-source"); + imageSource = require("../../image-source"); } } let platform: typeof platformModule; function ensurePlatform() { if (!platform) { - platform = require("platform"); + platform = require("../../platform"); } } let fs: typeof fsModule; function ensureFileSystem() { if (!fs) { - fs = require("file-system"); + fs = require("../../file-system"); } } From a14d2ee7206a604a08d841a583232f8f018365c4 Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Thu, 27 Jun 2019 18:24:20 +0300 Subject: [PATCH 04/11] chore: require relative path within tns-core-modules --- .../application/application-common.ts | 2 +- tns-core-modules/bundle-entry-points.ts | 80 +++++++++---------- tns-core-modules/file-system/file-system.ts | 2 +- tns-core-modules/globals/globals.ts | 18 ++--- .../http/http-request/http-request-common.ts | 2 +- .../http/http-request/http-request.ios.ts | 4 +- .../component-builder/component-builder.ts | 2 +- .../ui/core/view-base/view-base.ts | 2 +- tns-core-modules/ui/core/view/view-common.ts | 2 +- tns-core-modules/ui/dialogs/dialogs-common.ts | 8 +- tns-core-modules/ui/enums/enums.ts | 2 +- tns-core-modules/ui/frame/frame.android.ts | 4 +- tns-core-modules/ui/frame/frame.d.ts | 2 +- .../ui/image-cache/image-cache.ios.ts | 2 +- tns-core-modules/ui/styling/style-scope.ts | 2 +- 15 files changed, 67 insertions(+), 67 deletions(-) diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index e0434eb67..0ca6c76a1 100644 --- a/tns-core-modules/application/application-common.ts +++ b/tns-core-modules/application/application-common.ts @@ -1,5 +1,5 @@ // Require globals first so that snapshot takes __extends function. -require("globals"); +require("../globals"); import { Observable, EventData } from "../data/observable"; import { View } from "../ui/core/view"; diff --git a/tns-core-modules/bundle-entry-points.ts b/tns-core-modules/bundle-entry-points.ts index df94028bf..372b75b16 100644 --- a/tns-core-modules/bundle-entry-points.ts +++ b/tns-core-modules/bundle-entry-points.ts @@ -4,44 +4,44 @@ if (global.TNS_WEBPACK) { // Register "dynamically" loaded module that need to be resolved by the // XML/component builders. - global.registerModule("text/formatted-string", () => require("text/formatted-string")); - global.registerModule("text/span", () => require("text/span")); - global.registerModule("ui/action-bar", () => require("ui/action-bar")); - global.registerModule("ui/activity-indicator", () => require("ui/activity-indicator")); - global.registerModule("ui/border", () => require("ui/border")); - global.registerModule("ui/bottom-navigation", () => require("ui/bottom-navigation")); - global.registerModule("ui/button", () => require("ui/button")); - global.registerModule("ui/content-view", () => require("ui/content-view")); - global.registerModule("ui/date-picker", () => require("ui/date-picker")); - global.registerModule("ui/frame", () => require("ui/frame")); - global.registerModule("ui/html-view", () => require("ui/html-view")); - global.registerModule("ui/image", () => require("ui/image")); - global.registerModule("ui/label", () => require("ui/label")); - global.registerModule("ui/layouts/absolute-layout", () => require("ui/layouts/absolute-layout")); - global.registerModule("ui/layouts/dock-layout", () => require("ui/layouts/dock-layout")); - global.registerModule("ui/layouts/grid-layout", () => require("ui/layouts/grid-layout")); - global.registerModule("ui/layouts/stack-layout", () => require("ui/layouts/stack-layout")); - global.registerModule("ui/layouts/flexbox-layout", () => require("ui/layouts/flexbox-layout")); - global.registerModule("ui/layouts/wrap-layout", () => require("ui/layouts/wrap-layout")); - global.registerModule("ui/list-picker", () => require("ui/list-picker")); - global.registerModule("ui/page", () => require("ui/page")); - global.registerModule("ui/placeholder", () => require("ui/placeholder")); - global.registerModule("ui/progress", () => require("ui/progress")); - global.registerModule("ui/proxy-view-container", () => require("ui/proxy-view-container")); - global.registerModule("ui/repeater", () => require("ui/repeater")); - global.registerModule("ui/scroll-view", () => require("ui/scroll-view")); - global.registerModule("ui/search-bar", () => require("ui/search-bar")); - global.registerModule("ui/segmented-bar", () => require("ui/segmented-bar")); - global.registerModule("ui/slider", () => require("ui/slider")); - global.registerModule("ui/switch", () => require("ui/switch")); - global.registerModule("ui/tab-view", () => require("ui/tab-view")); - global.registerModule("ui/tab-navigation-base/tab-strip", () => require("ui/tab-navigation-base/tab-strip")); - global.registerModule("ui/tab-navigation-base/tab-strip-item", () => require("ui/tab-navigation-base/tab-strip-item")); - global.registerModule("ui/tab-navigation-base/tab-content-item", () => require("ui/tab-navigation-base/tab-content-item")); - global.registerModule("ui/tabs", () => require("ui/tabs")); - global.registerModule("ui/web-view", () => require("ui/web-view")); - global.registerModule("ui/text-field", () => require("ui/text-field")); - global.registerModule("ui/text-view", () => require("ui/text-view")); - global.registerModule("ui/time-picker", () => require("ui/time-picker")); - global.registerModule("ui/list-view", () => require("ui/list-view")); + global.registerModule("text/formatted-string", () => require("./text/formatted-string")); + global.registerModule("text/span", () => require("./text/span")); + global.registerModule("ui/action-bar", () => require("./ui/action-bar")); + global.registerModule("ui/activity-indicator", () => require("./ui/activity-indicator")); + global.registerModule("ui/border", () => require("./ui/border")); + global.registerModule("ui/bottom-navigation", () => require("./ui/bottom-navigation")); + global.registerModule("ui/button", () => require("./ui/button")); + global.registerModule("ui/content-view", () => require("./ui/content-view")); + global.registerModule("ui/date-picker", () => require("./ui/date-picker")); + global.registerModule("ui/frame", () => require("./ui/frame")); + global.registerModule("ui/html-view", () => require("./ui/html-view")); + global.registerModule("ui/image", () => require("./ui/image")); + global.registerModule("ui/label", () => require("./ui/label")); + global.registerModule("ui/layouts/absolute-layout", () => require("./ui/layouts/absolute-layout")); + global.registerModule("ui/layouts/dock-layout", () => require("./ui/layouts/dock-layout")); + global.registerModule("ui/layouts/grid-layout", () => require("./ui/layouts/grid-layout")); + global.registerModule("ui/layouts/stack-layout", () => require("./ui/layouts/stack-layout")); + global.registerModule("ui/layouts/flexbox-layout", () => require("./ui/layouts/flexbox-layout")); + global.registerModule("ui/layouts/wrap-layout", () => require("./ui/layouts/wrap-layout")); + global.registerModule("ui/list-picker", () => require("./ui/list-picker")); + global.registerModule("ui/page", () => require("./ui/page")); + global.registerModule("ui/placeholder", () => require("./ui/placeholder")); + global.registerModule("ui/progress", () => require("./ui/progress")); + global.registerModule("ui/proxy-view-container", () => require("./ui/proxy-view-container")); + global.registerModule("ui/repeater", () => require("./ui/repeater")); + global.registerModule("ui/scroll-view", () => require("./ui/scroll-view")); + global.registerModule("ui/search-bar", () => require("./ui/search-bar")); + global.registerModule("ui/segmented-bar", () => require("./ui/segmented-bar")); + global.registerModule("ui/slider", () => require("./ui/slider")); + global.registerModule("ui/switch", () => require("./ui/switch")); + global.registerModule("ui/tab-view", () => require("./ui/tab-view")); + global.registerModule("ui/tab-navigation-base/tab-strip", () => require("./ui/tab-navigation-base/tab-strip")); + global.registerModule("ui/tab-navigation-base/tab-strip-item", () => require("./ui/tab-navigation-base/tab-strip-item")); + global.registerModule("ui/tab-navigation-base/tab-content-item", () => require("./ui/tab-navigation-base/tab-content-item")); + global.registerModule("ui/tabs", () => require("./ui/tabs")); + global.registerModule("ui/web-view", () => require("./ui/web-view")); + global.registerModule("ui/text-field", () => require("./ui/text-field")); + global.registerModule("ui/text-view", () => require("./ui/text-view")); + global.registerModule("ui/time-picker", () => require("./ui/time-picker")); + global.registerModule("ui/list-view", () => require("./ui/list-view")); } diff --git a/tns-core-modules/file-system/file-system.ts b/tns-core-modules/file-system/file-system.ts index 183003f2c..aa1209716 100644 --- a/tns-core-modules/file-system/file-system.ts +++ b/tns-core-modules/file-system/file-system.ts @@ -17,7 +17,7 @@ function getFileAccess(): FileSystemAccess { let platform: typeof platformModule; function ensurePlatform() { if (!platform) { - platform = require("platform"); + platform = require("../platform"); } } diff --git a/tns-core-modules/globals/globals.ts b/tns-core-modules/globals/globals.ts index 1725e4c44..af2f62197 100644 --- a/tns-core-modules/globals/globals.ts +++ b/tns-core-modules/globals/globals.ts @@ -129,10 +129,10 @@ global.zonedCallback = function (callback: Function): Function { } }; -global.registerModule("timer", () => require("timer")); -global.registerModule("ui/dialogs", () => require("ui/dialogs")); -global.registerModule("xhr", () => require("xhr")); -global.registerModule("fetch", () => require("fetch")); +global.registerModule("timer", () => require("../timer")); +global.registerModule("ui/dialogs", () => require("../ui/dialogs")); +global.registerModule("xhr", () => require("../xhr")); +global.registerModule("fetch", () => require("../fetch")); (global).System = { import(path) { @@ -167,10 +167,10 @@ export function install() { if ((global).__snapshot || (global).__snapshotEnabled) { if (!snapshotGlobals) { // require in snapshot mode is cheap - const timer: typeof timerModule = require("timer"); - const dialogs: typeof dialogsModule = require("ui/dialogs"); - const xhr = require("xhr"); - const fetch = require("fetch"); + const timer: typeof timerModule = require("../timer"); + const dialogs: typeof dialogsModule = require("../ui/dialogs"); + const xhr = require("../xhr"); + const fetch = require("../fetch"); snapshotGlobals = snapshotGlobals || { setTimeout: timer.setTimeout, @@ -193,7 +193,7 @@ export function install() { Response: fetch.Response, }; } - const consoleModule = require("console").Console; + const consoleModule = require("../console").Console; // Object.assign call will fire an error when trying to write to a read-only property of an object, such as 'console' global.console = global.console || new consoleModule(); Object.assign(global, snapshotGlobals); diff --git a/tns-core-modules/http/http-request/http-request-common.ts b/tns-core-modules/http/http-request/http-request-common.ts index aff2f4ceb..15df5fc09 100644 --- a/tns-core-modules/http/http-request/http-request-common.ts +++ b/tns-core-modules/http/http-request/http-request-common.ts @@ -1,7 +1,7 @@ import * as fsModule from "../../file-system"; export function getFilenameFromUrl(url: string) { - const fs: typeof fsModule = require("file-system"); + const fs: typeof fsModule = require("../../file-system"); const slashPos = url.lastIndexOf("/") + 1; const questionMarkPos = url.lastIndexOf("?"); diff --git a/tns-core-modules/http/http-request/http-request.ios.ts b/tns-core-modules/http/http-request/http-request.ios.ts index cff881b1d..f54d1c496 100644 --- a/tns-core-modules/http/http-request/http-request.ios.ts +++ b/tns-core-modules/http/http-request/http-request.ios.ts @@ -56,14 +56,14 @@ function ensureSessionNotFollowingRedirects() { let imageSource: typeof imageSourceModule; function ensureImageSource() { if (!imageSource) { - imageSource = require("image-source"); + imageSource = require("../../image-source"); } } let fs: typeof fsModule; function ensureFileSystem() { if (!fs) { - fs = require("file-system"); + fs = require("../../file-system"); } } diff --git a/tns-core-modules/ui/builder/component-builder/component-builder.ts b/tns-core-modules/ui/builder/component-builder/component-builder.ts index 7e04c16d5..89987f393 100644 --- a/tns-core-modules/ui/builder/component-builder/component-builder.ts +++ b/tns-core-modules/ui/builder/component-builder/component-builder.ts @@ -51,7 +51,7 @@ const createComponentInstance = profile("createComponentInstance", (elementName: // Create instance of the component. instance = new instanceType(); } catch (ex) { - const debug: typeof debugModule = require("utils/debug"); + const debug: typeof debugModule = require("../../../utils/debug"); throw new debug.ScopeError(ex, "Module '" + resolvedModuleName + "' not found for element '" + (namespace ? namespace + ":" : "") + elementName + "'."); } diff --git a/tns-core-modules/ui/core/view-base/view-base.ts b/tns-core-modules/ui/core/view-base/view-base.ts index c85cce7d3..a282768bc 100644 --- a/tns-core-modules/ui/core/view-base/view-base.ts +++ b/tns-core-modules/ui/core/view-base/view-base.ts @@ -37,7 +37,7 @@ function ensuredomNodeModule(): void { let styleScopeModule: typeof ssm; function ensureStyleScopeModule() { if (!styleScopeModule) { - styleScopeModule = require("ui/styling/style-scope"); + styleScopeModule = require("../../styling/style-scope"); } } diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts index 791a8c4af..6eb43e383 100644 --- a/tns-core-modules/ui/core/view/view-common.ts +++ b/tns-core-modules/ui/core/view/view-common.ts @@ -34,7 +34,7 @@ import * as am from "../../animation"; let animationModule: typeof am; function ensureAnimationModule() { if (!animationModule) { - animationModule = require("ui/animation"); + animationModule = require("../../animation"); } } diff --git a/tns-core-modules/ui/dialogs/dialogs-common.ts b/tns-core-modules/ui/dialogs/dialogs-common.ts index e80159127..58cd326e9 100644 --- a/tns-core-modules/ui/dialogs/dialogs-common.ts +++ b/tns-core-modules/ui/dialogs/dialogs-common.ts @@ -78,7 +78,7 @@ export module capitalizationType { let frame: typeof frameModule; export function getCurrentPage(): Page { if (!frame) { - frame = require("ui/frame"); + frame = require("../frame"); } let topmostFrame = frame.topmost(); @@ -108,7 +108,7 @@ let textField: View; export function getButtonColors(): { color: Color, backgroundColor: Color } { if (!button) { - const Button = require("ui/button").Button; + const Button = require("../button").Button; button = new Button; if (isIOS) { button._setupUI({}); @@ -127,7 +127,7 @@ export function getButtonColors(): { color: Color, backgroundColor: Color } { export function getLabelColor(): Color { if (!label) { - const Label = require("ui/label").Label; + const Label = require("../label").Label; label = new Label; if (isIOS) { label._setupUI({}); @@ -144,7 +144,7 @@ export function getLabelColor(): Color { export function getTextFieldColor(): Color { if (!textField) { - const TextField = require("ui/text-field").TextField; + const TextField = require("../text-field").TextField; textField = new TextField(); if (isIOS) { textField._setupUI({}); diff --git a/tns-core-modules/ui/enums/enums.ts b/tns-core-modules/ui/enums/enums.ts index a82824e96..a39c8295f 100644 --- a/tns-core-modules/ui/enums/enums.ts +++ b/tns-core-modules/ui/enums/enums.ts @@ -177,7 +177,7 @@ export module AnimationCurve { export const linear = "linear"; export const spring = "spring"; export function cubicBezier(x1: number, y1: number, x2: number, y2: number): Object { - animation = animation || require("ui/animation"); + animation = animation || require("../animation"); return new animation.CubicBezierAnimationCurve(x1, y1 , x2, y2); } diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index c697cd87a..f94a91282 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -46,7 +46,7 @@ let fragmentId = -1; export let moduleLoaded: boolean; if (global && global.__inspector) { - const devtools = require("tns-core-modules/debugger/devtools-elements"); + const devtools = require("../../debugger/devtools-elements"); devtools.attachDOMInspectorEventCallbacks(global.__inspector); devtools.attachDOMInspectorCommandCallbacks(global.__inspector); } @@ -754,7 +754,7 @@ function ensureFragmentClass() { } // this require will apply the FragmentClass implementation - require("ui/frame/fragment"); + require("./fragment"); if (!fragmentClass) { throw new Error("Failed to initialize the extended androidx.fragment.app.Fragment class"); diff --git a/tns-core-modules/ui/frame/frame.d.ts b/tns-core-modules/ui/frame/frame.d.ts index 5d5eaf324..14a642904 100644 --- a/tns-core-modules/ui/frame/frame.d.ts +++ b/tns-core-modules/ui/frame/frame.d.ts @@ -30,7 +30,7 @@ export class Frame extends View { * This method will require the module and will check for a Page property in the exports of the module. * @param pageModuleName The name of the module to require starting from the application root. * For example if you want to navigate to page called "myPage.js" in a folder called "subFolder" and your root folder is "app" you can call navigate method like this: - * const frames = require("ui/frame"); + * const frames = require("tns-core-modules/ui/frame"); * frames.topmost().navigate("app/subFolder/myPage"); */ navigate(pageModuleName: string); diff --git a/tns-core-modules/ui/image-cache/image-cache.ios.ts b/tns-core-modules/ui/image-cache/image-cache.ios.ts index 595cc0657..27b61d4e4 100644 --- a/tns-core-modules/ui/image-cache/image-cache.ios.ts +++ b/tns-core-modules/ui/image-cache/image-cache.ios.ts @@ -8,7 +8,7 @@ import * as utils from "../../utils/utils"; let httpRequest: typeof httpRequestModule; function ensureHttpRequest() { if (!httpRequest) { - httpRequest = require("http/http-request"); + httpRequest = require("../../http/http-request"); } } diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index d2995f6af..cecab4e2b 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -35,7 +35,7 @@ import * as kam from "../animation/keyframe-animation"; let keyframeAnimationModule: typeof kam; function ensureKeyframeAnimationModule() { if (!keyframeAnimationModule) { - keyframeAnimationModule = require("ui/animation/keyframe-animation"); + keyframeAnimationModule = require("../animation/keyframe-animation"); } } From 9830be7230355638ace604145a3e3fad393c2a93 Mon Sep 17 00:00:00 2001 From: Vasil Trifonov Date: Thu, 5 Mar 2020 15:35:07 +0200 Subject: [PATCH 05/11] fix(bottom-nav): align text with different icons --- .../bottom-navigation.android.ts | 40 +++++++++++++----- .../widgets/BottomNavigationBar.java | 41 ++++++++++++------- .../org/nativescript/widgets/TabItemSpec.java | 1 + .../android/org.nativescript.widgets.d.ts | 1 + 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts index 34de5c4ef..c315b7c40 100644 --- a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts +++ b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts @@ -36,6 +36,11 @@ let BottomNavigationBar: any; let AttachStateChangeListener: any; let appResources: android.content.res.Resources; +class IconInfo { + drawable: android.graphics.drawable.BitmapDrawable; + height: number; +} + function makeFragmentName(viewId: number, id: number): string { return "android:bottomnavigation:" + viewId + ":" + id; } @@ -596,12 +601,13 @@ export class BottomNavigation extends TabNavigationBase { // ICON const iconSource = tabStripItem.image && tabStripItem.image.src; if (iconSource) { - const icon = this.getIcon(tabStripItem); + const iconInfo = this.getIconInfo(tabStripItem); - if (icon) { + if (iconInfo) { // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. // tslint:disable-next-line:deprecation - tabItemSpec.iconDrawable = icon; + tabItemSpec.iconDrawable = iconInfo.drawable; + tabItemSpec.imageHeight = iconInfo.height; } else { // TODO: // traceMissingIcon(iconSource); @@ -612,7 +618,7 @@ export class BottomNavigation extends TabNavigationBase { return tabItemSpec; } - private getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable { + private getOriginalIcon(tabStripItem: TabStripItem): android.graphics.Bitmap { const iconSource = tabStripItem.image && tabStripItem.image.src; if (!iconSource) { return null; @@ -629,21 +635,33 @@ export class BottomNavigation extends TabNavigationBase { is = ImageSource.fromFileOrResourceSync(iconSource); } - let imageDrawable: android.graphics.drawable.BitmapDrawable; - if (is && is.android) { - let image = is.android; + return is && is.android; + } + private getDrawableInfo(image: android.graphics.Bitmap): IconInfo { + if (image) { if (this.tabStrip && this.tabStrip.isIconSizeFixed) { image = this.getFixedSizeIcon(image); } - imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image); + let imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image); + + return { + drawable: imageDrawable, + height: image.getHeight() + }; } else { // TODO // traceMissingIcon(iconSource); } - return imageDrawable; + return null; + } + + private getIconInfo(tabStripItem: TabStripItem): IconInfo { + let originalIcon = this.getOriginalIcon(tabStripItem); + + return this.getDrawableInfo(originalIcon); } private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap { @@ -702,9 +720,9 @@ export class BottomNavigation extends TabNavigationBase { const index = tabStripItem._index; const tabBarItem = this._bottomNavigationBar.getViewForItemAt(index); const imgView = tabBarItem.getChildAt(0); - const drawable = this.getIcon(tabStripItem); + const drawableInfo = this.getIconInfo(tabStripItem); - imgView.setImageDrawable(drawable); + imgView.setImageDrawable(drawableInfo.drawable); } public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java index 4e2cd7eeb..cc6ba72e2 100644 --- a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java @@ -19,6 +19,7 @@ package org.nativescript.widgets; import android.content.Context; import android.graphics.Typeface; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.SparseArray; @@ -54,6 +55,7 @@ public class BottomNavigationBar extends LinearLayout { private SparseArray mContentDescriptions = new SparseArray(); private final TabStrip mTabStrip; + private int mMaxImageHeight; public BottomNavigationBar(Context context) { this(context, null); @@ -108,6 +110,7 @@ public class BottomNavigationBar extends LinearLayout { public void setItems(TabItemSpec[] items) { mTabStrip.removeAllViews(); mTabItems = items; + setImageHeights(); populateTabStrip(); } @@ -120,25 +123,25 @@ public class BottomNavigationBar extends LinearLayout { TextView textView = (TextView)ll.getChildAt(1); this.setupItem(ll, textView, imgView, tabItem); } - + /** * Gets the TextView for tab item at index */ public TextView getTextViewForItemAt(int index){ LinearLayout ll = this.getViewForItemAt(index); - return (ll != null) ? (TextView)ll.getChildAt(1) : null; + return (ll != null) ? (TextView)ll.getChildAt(1) : null; } - + /** * Gets the LinearLayout container for tab item at index */ public LinearLayout getViewForItemAt(int index){ LinearLayout result = null; - + if(this.mTabStrip.getChildCount() > index){ result = (LinearLayout)this.mTabStrip.getChildAt(index); } - + return result; } @@ -165,7 +168,7 @@ public class BottomNavigationBar extends LinearLayout { ImageView imgView = new ImageView(context); imgView.setScaleType(ScaleType.FIT_CENTER); - LinearLayout.LayoutParams imgLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + LinearLayout.LayoutParams imgLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, this.mMaxImageHeight > 0 ? this.mMaxImageHeight : ViewGroup.LayoutParams.WRAP_CONTENT); imgLP.gravity = Gravity.CENTER; imgView.setLayoutParams(imgLP); @@ -184,10 +187,9 @@ public class BottomNavigationBar extends LinearLayout { ll.addView(textView); return ll; } - + private void setupItem(LinearLayout ll, TextView textView,ImageView imgView, TabItemSpec tabItem){ float density = getResources().getDisplayMetrics().density; - if (tabItem.iconId != 0) { imgView.setImageResource(tabItem.iconId); imgView.setVisibility(VISIBLE); @@ -205,14 +207,14 @@ public class BottomNavigationBar extends LinearLayout { if (tabItem.typeFace != null) { textView.setTypeface(tabItem.typeFace); } - + if (tabItem.fontSize != 0) { textView.setTextSize(tabItem.fontSize); } - + if (tabItem.color != 0) { textView.setTextColor(tabItem.color); - mTabStrip.setShouldUpdateTabsTextColor(false); + mTabStrip.setShouldUpdateTabsTextColor(false); } } else { textView.setVisibility(GONE); @@ -238,17 +240,28 @@ public class BottomNavigationBar extends LinearLayout { // to be overridden in JS } + private void setImageHeights(){ + for (int i = 0; i < this.mTabItems.length; i++) { + TabItemSpec tabItem = this.mTabItems[i]; + if(tabItem.imageHeight == 0 && tabItem.iconId != 0) { + Drawable drawable = getResources().getDrawable(tabItem.iconId); + tabItem.imageHeight = drawable.getIntrinsicHeight(); + } + if(tabItem.imageHeight > this.mMaxImageHeight) { + this.mMaxImageHeight = tabItem.imageHeight; + } + } + } + private void populateTabStrip() { final OnClickListener tabClickListener = new TabClickListener(); if (this.mTabItems != null) { int count = this.mTabItems.length < 5 ? this.mTabItems.length : 5; for (int i = 0; i < count; i++) { - View tabView = null; - TabItemSpec tabItem; tabItem = this.mTabItems[i]; - tabView = createDefaultTabView(getContext(), tabItem); + View tabView = createDefaultTabView(getContext(), tabItem); tabView.setOnClickListener(tabClickListener); String desc = mContentDescriptions.get(i, null); diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java index 1494e407f..43299710d 100644 --- a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java @@ -9,6 +9,7 @@ public class TabItemSpec { public Typeface typeFace; public int iconId; public Drawable iconDrawable; + public int imageHeight; public int backgroundColor; public int color; } \ No newline at end of file diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts index 264abe4b1..35da58adb 100644 --- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts +++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts @@ -468,6 +468,7 @@ typeFace: android.graphics.Typeface; iconId: number; iconDrawable: android.graphics.drawable.Drawable; + imageHeight: number; backgroundColor: number; color: number; } From 1c46d748a0cbacda04e2ccecd13664b6e0d9a808 Mon Sep 17 00:00:00 2001 From: Vasko Date: Fri, 6 Mar 2020 11:12:22 +0200 Subject: [PATCH 06/11] chore: addressing review comments --- .../bottom-navigation.android.ts | 5 +- .../widgets/BottomNavigationBar.java | 53 ++++++++++--------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts index c315b7c40..18ebd2d02 100644 --- a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts +++ b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts @@ -650,12 +650,9 @@ export class BottomNavigation extends TabNavigationBase { drawable: imageDrawable, height: image.getHeight() }; - } else { - // TODO - // traceMissingIcon(iconSource); } - return null; + return new IconInfo(); } private getIconInfo(tabStripItem: TabStripItem): IconInfo { diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java index cc6ba72e2..db83158a5 100644 --- a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java @@ -158,34 +158,36 @@ public class BottomNavigationBar extends LinearLayout { protected View createDefaultTabView(Context context, TabItemSpec tabItem) { float density = getResources().getDisplayMetrics().density; - LinearLayout ll = new LinearLayout(context); - ll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); - ll.setGravity(Gravity.CENTER); - ll.setOrientation(LinearLayout.VERTICAL); - TypedValue outValue = new TypedValue(); - getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); - ll.setBackgroundResource(outValue.resourceId); + LinearLayout tabItemLayout = new LinearLayout(context); + tabItemLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); + tabItemLayout.setGravity(Gravity.CENTER); + tabItemLayout.setOrientation(LinearLayout.VERTICAL); + TypedValue backgroundOutValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, backgroundOutValue, true); + tabItemLayout.setBackgroundResource(backgroundOutValue.resourceId); - ImageView imgView = new ImageView(context); - imgView.setScaleType(ScaleType.FIT_CENTER); - LinearLayout.LayoutParams imgLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, this.mMaxImageHeight > 0 ? this.mMaxImageHeight : ViewGroup.LayoutParams.WRAP_CONTENT); - imgLP.gravity = Gravity.CENTER; - imgView.setLayoutParams(imgLP); + ImageView iconImageView = new ImageView(context); + iconImageView.setScaleType(ScaleType.FIT_CENTER); + int iconImageHeight = this.mMaxImageHeight > 0 ? this.mMaxImageHeight : ViewGroup.LayoutParams.WRAP_CONTENT; + int iconImageWidth = ViewGroup.LayoutParams.WRAP_CONTENT; + LinearLayout.LayoutParams iconImageLayoutParams = new LinearLayout.LayoutParams(iconImageWidth, iconImageHeight); + iconImageLayoutParams.gravity = Gravity.CENTER; + iconImageView.setLayoutParams(iconImageLayoutParams); - TextView textView = new TextView(context); - textView.setGravity(Gravity.CENTER); - textView.setMaxWidth((int) (ITEM_TEXT_MAX_WIDTH * density)); - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ITEM_TEXT_SIZE_SP); - textView.setTypeface(Typeface.DEFAULT_BOLD); - textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setMaxLines(1); - textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + TextView titleTextView = new TextView(context); + titleTextView.setGravity(Gravity.CENTER); + titleTextView.setMaxWidth((int) (ITEM_TEXT_MAX_WIDTH * density)); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, ITEM_TEXT_SIZE_SP); + titleTextView.setTypeface(Typeface.DEFAULT_BOLD); + titleTextView.setEllipsize(TextUtils.TruncateAt.END); + titleTextView.setMaxLines(1); + titleTextView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - this.setupItem(ll, textView, imgView, tabItem); + this.setupItem(tabItemLayout, titleTextView, iconImageView, tabItem); - ll.addView(imgView); - ll.addView(textView); - return ll; + tabItemLayout.addView(iconImageView); + tabItemLayout.addView(titleTextView); + return tabItemLayout; } private void setupItem(LinearLayout ll, TextView textView,ImageView imgView, TabItemSpec tabItem){ @@ -241,8 +243,7 @@ public class BottomNavigationBar extends LinearLayout { } private void setImageHeights(){ - for (int i = 0; i < this.mTabItems.length; i++) { - TabItemSpec tabItem = this.mTabItems[i]; + for (TabItemSpec tabItem : this.mTabItems) { if(tabItem.imageHeight == 0 && tabItem.iconId != 0) { Drawable drawable = getResources().getDrawable(tabItem.iconId); tabItem.imageHeight = drawable.getIntrinsicHeight(); From 9d4e482b3896d7c8ac98ef01acbc8b8bd0780f8e Mon Sep 17 00:00:00 2001 From: Vasko Date: Fri, 6 Mar 2020 14:35:57 +0200 Subject: [PATCH 07/11] fix: added null guard --- .../widgets/BottomNavigationBar.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java index db83158a5..13760a683 100644 --- a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/BottomNavigationBar.java @@ -243,13 +243,15 @@ public class BottomNavigationBar extends LinearLayout { } private void setImageHeights(){ - for (TabItemSpec tabItem : this.mTabItems) { - if(tabItem.imageHeight == 0 && tabItem.iconId != 0) { - Drawable drawable = getResources().getDrawable(tabItem.iconId); - tabItem.imageHeight = drawable.getIntrinsicHeight(); - } - if(tabItem.imageHeight > this.mMaxImageHeight) { - this.mMaxImageHeight = tabItem.imageHeight; + if (this.mTabItems != null) { + for (TabItemSpec tabItem : this.mTabItems) { + if(tabItem.imageHeight == 0 && tabItem.iconId != 0) { + Drawable drawable = getResources().getDrawable(tabItem.iconId); + tabItem.imageHeight = drawable.getIntrinsicHeight(); + } + if(tabItem.imageHeight > this.mMaxImageHeight) { + this.mMaxImageHeight = tabItem.imageHeight; + } } } } From 1a316b8a7267163f70ff8c6bc2d09bb435754ab3 Mon Sep 17 00:00:00 2001 From: mylmz10 Date: Sat, 7 Mar 2020 20:32:48 +0300 Subject: [PATCH 08/11] Set length value to auto when length is undefined --- nativescript-core/ui/core/view/view.android.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-core/ui/core/view/view.android.ts b/nativescript-core/ui/core/view/view.android.ts index 2388aa67c..bbfab0194 100644 --- a/nativescript-core/ui/core/view/view.android.ts +++ b/nativescript-core/ui/core/view/view.android.ts @@ -1116,7 +1116,7 @@ function createNativePercentLengthProperty(options: NativePercentLengthPropertyO setPercent = options.setPercent || percentNotSupported; options = null; } - if (length == "auto") { // tslint:disable-line + if (length == "auto" || !length) { // tslint:disable-line setPixels(this.nativeViewProtected, auto); } else if (typeof length === "number") { setPixels(this.nativeViewProtected, layout.round(layout.toDevicePixels(length))); From b31114b9d7a2d7d49dc6277ebca1d0739c90c3de Mon Sep 17 00:00:00 2001 From: Manuel Saelices Date: Mon, 9 Mar 2020 01:11:50 +0100 Subject: [PATCH 09/11] Solving previous merge conflict --- .../http/http-request/http-request.android.ts | 1 - .../application/application-common.ts | 131 --------- tns-core-modules/bundle-entry-points.ts | 47 ---- tns-core-modules/globals/globals.ts | 261 ------------------ 4 files changed, 440 deletions(-) delete mode 100644 tns-core-modules/application/application-common.ts delete mode 100644 tns-core-modules/bundle-entry-points.ts delete mode 100644 tns-core-modules/globals/globals.ts diff --git a/nativescript-core/http/http-request/http-request.android.ts b/nativescript-core/http/http-request/http-request.android.ts index 794405e24..b9be9098b 100644 --- a/nativescript-core/http/http-request/http-request.android.ts +++ b/nativescript-core/http/http-request/http-request.android.ts @@ -28,7 +28,6 @@ let imageSource: typeof imageSourceModule; function ensureImageSource() { if (!imageSource) { imageSource = require("../../image-source"); - imageSource = require("../../image-source"); } } diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts deleted file mode 100644 index 0ca6c76a1..000000000 --- a/tns-core-modules/application/application-common.ts +++ /dev/null @@ -1,131 +0,0 @@ -// Require globals first so that snapshot takes __extends function. -require("../globals"); - -import { Observable, EventData } from "../data/observable"; -import { View } from "../ui/core/view"; -import { - trace as profilingTrace, - time, - uptime, - level as profilingLevel, -} from "../profiling"; - -const events = new Observable(); -let launched = false; -function setLaunched() { - launched = true; - events.off("launch", setLaunched); -} -events.on("launch", setLaunched); - -if (profilingLevel() > 0) { - events.on("displayed", () => { - const duration = uptime(); - const end = time(); - const start = end - duration; - profilingTrace(`Displayed in ${duration.toFixed(2)}ms`, start, end); - }); -} - -export function hasLaunched(): boolean { - return launched; -} - -export { Observable }; - -import { - AndroidApplication, - CssChangedEventData, - iOSApplication, - LoadAppCSSEventData, - UnhandledErrorEventData, - DiscardedErrorEventData, -} from "./application"; - -export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData }; - -export const launchEvent = "launch"; -export const suspendEvent = "suspend"; -export const displayedEvent = "displayed"; -export const resumeEvent = "resume"; -export const exitEvent = "exit"; -export const lowMemoryEvent = "lowMemory"; -export const uncaughtErrorEvent = "uncaughtError"; -export const discardedErrorEvent = "discardedError"; -export const orientationChangedEvent = "orientationChanged"; - -let cssFile: string = "./app.css"; - -let resources: any = {}; - -export function getResources() { - return resources; -} - -export function setResources(res: any) { - resources = res; -} - -export let android = undefined; -export let ios = undefined; - -export const on: typeof events.on = events.on.bind(events); -export const off: typeof events.off = events.off.bind(events); -export const notify: typeof events.notify = events.notify.bind(events); -export const hasListeners: typeof events.hasListeners = events.hasListeners.bind(events); - -let app: iOSApplication | AndroidApplication; -export function setApplication(instance: iOSApplication | AndroidApplication): void { - app = instance; -} - -export function livesync(rootView: View, context?: ModuleContext) { - events.notify({ eventName: "livesync", object: app }); - const liveSyncCore = global.__onLiveSyncCore; - let reapplyAppStyles = false; - - // ModuleContext is available only for Hot Module Replacement - if (context && context.path) { - const styleExtensions = ["css", "scss"]; - const appStylesFullFileName = getCssFileName(); - const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf(".") + 1); - reapplyAppStyles = styleExtensions.some(ext => context.path === appStylesFileName.concat(ext)); - } - - // Handle application styles - if (reapplyAppStyles && rootView) { - rootView._onCssStateChange(); - } else if (liveSyncCore) { - liveSyncCore(context); - } -} - -export function setCssFileName(cssFileName: string) { - cssFile = cssFileName; - events.notify({ eventName: "cssChanged", object: app, cssFile: cssFileName }); -} - -export function getCssFileName(): string { - return cssFile; -} - -export function loadAppCss(): void { - try { - events.notify({ eventName: "loadAppCss", object: app, cssFile: getCssFileName() }); - } catch (e) { - throw new Error(`The file ${getCssFileName()} couldn't be loaded! ` + - `You may need to register it inside ./app/vendor.ts.`); - } -} - -export function addCss(cssText: string): void { - events.notify({ eventName: "cssChanged", object: app, cssText: cssText }); -} - -global.__onUncaughtError = function (error: NativeScriptError) { - events.notify({ eventName: uncaughtErrorEvent, object: app, android: error, ios: error, error: error }); -}; - -global.__onDiscardedError = function (error: NativeScriptError) { - events.notify({ eventName: discardedErrorEvent, object: app, error: error }); -}; diff --git a/tns-core-modules/bundle-entry-points.ts b/tns-core-modules/bundle-entry-points.ts deleted file mode 100644 index 372b75b16..000000000 --- a/tns-core-modules/bundle-entry-points.ts +++ /dev/null @@ -1,47 +0,0 @@ -if (global.TNS_WEBPACK) { - require("globals"); - - // Register "dynamically" loaded module that need to be resolved by the - // XML/component builders. - - global.registerModule("text/formatted-string", () => require("./text/formatted-string")); - global.registerModule("text/span", () => require("./text/span")); - global.registerModule("ui/action-bar", () => require("./ui/action-bar")); - global.registerModule("ui/activity-indicator", () => require("./ui/activity-indicator")); - global.registerModule("ui/border", () => require("./ui/border")); - global.registerModule("ui/bottom-navigation", () => require("./ui/bottom-navigation")); - global.registerModule("ui/button", () => require("./ui/button")); - global.registerModule("ui/content-view", () => require("./ui/content-view")); - global.registerModule("ui/date-picker", () => require("./ui/date-picker")); - global.registerModule("ui/frame", () => require("./ui/frame")); - global.registerModule("ui/html-view", () => require("./ui/html-view")); - global.registerModule("ui/image", () => require("./ui/image")); - global.registerModule("ui/label", () => require("./ui/label")); - global.registerModule("ui/layouts/absolute-layout", () => require("./ui/layouts/absolute-layout")); - global.registerModule("ui/layouts/dock-layout", () => require("./ui/layouts/dock-layout")); - global.registerModule("ui/layouts/grid-layout", () => require("./ui/layouts/grid-layout")); - global.registerModule("ui/layouts/stack-layout", () => require("./ui/layouts/stack-layout")); - global.registerModule("ui/layouts/flexbox-layout", () => require("./ui/layouts/flexbox-layout")); - global.registerModule("ui/layouts/wrap-layout", () => require("./ui/layouts/wrap-layout")); - global.registerModule("ui/list-picker", () => require("./ui/list-picker")); - global.registerModule("ui/page", () => require("./ui/page")); - global.registerModule("ui/placeholder", () => require("./ui/placeholder")); - global.registerModule("ui/progress", () => require("./ui/progress")); - global.registerModule("ui/proxy-view-container", () => require("./ui/proxy-view-container")); - global.registerModule("ui/repeater", () => require("./ui/repeater")); - global.registerModule("ui/scroll-view", () => require("./ui/scroll-view")); - global.registerModule("ui/search-bar", () => require("./ui/search-bar")); - global.registerModule("ui/segmented-bar", () => require("./ui/segmented-bar")); - global.registerModule("ui/slider", () => require("./ui/slider")); - global.registerModule("ui/switch", () => require("./ui/switch")); - global.registerModule("ui/tab-view", () => require("./ui/tab-view")); - global.registerModule("ui/tab-navigation-base/tab-strip", () => require("./ui/tab-navigation-base/tab-strip")); - global.registerModule("ui/tab-navigation-base/tab-strip-item", () => require("./ui/tab-navigation-base/tab-strip-item")); - global.registerModule("ui/tab-navigation-base/tab-content-item", () => require("./ui/tab-navigation-base/tab-content-item")); - global.registerModule("ui/tabs", () => require("./ui/tabs")); - global.registerModule("ui/web-view", () => require("./ui/web-view")); - global.registerModule("ui/text-field", () => require("./ui/text-field")); - global.registerModule("ui/text-view", () => require("./ui/text-view")); - global.registerModule("ui/time-picker", () => require("./ui/time-picker")); - global.registerModule("ui/list-view", () => require("./ui/list-view")); -} diff --git a/tns-core-modules/globals/globals.ts b/tns-core-modules/globals/globals.ts deleted file mode 100644 index af2f62197..000000000 --- a/tns-core-modules/globals/globals.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Required by TypeScript compiler -require("./ts-helpers"); - -// This method iterates all the keys in the source exports object and copies them to the destination exports one. -// Note: the method will not check for naming collisions and will override any already existing entries in the destination exports. -global.moduleMerge = function (sourceExports: any, destExports: any) { - for (let key in sourceExports) { - destExports[key] = sourceExports[key]; - } -}; - -import * as timerModule from "../timer"; -import * as dialogsModule from "../ui/dialogs"; - -type ModuleLoader = (name?: string) => any; -const modules: Map = new Map(); - -(global).moduleResolvers = [global.require]; - -global.registerModule = function (name: string, loader: ModuleLoader): void { - // console.log("[global.registerModule]", name); - modules.set(name, loader); -}; - -global._unregisterModule = function (name: string): void { - // console.log("[global._unregisterModule]", name); - modules.delete(name); -}; - -interface Context { - keys(): string[]; - (key: string): any; -} -interface ExtensionMap { - [originalFileExtension: string]: string; -} - -const defaultExtensionMap = { - ".js": ".js", - ".ts": ".js", - ".css": ".css", - ".scss": ".css", - ".less": ".css", - ".sass": ".css", - ".xml": ".xml" -}; - -global.registerWebpackModules = function registerWebpackModules(context: Context, extensionMap: ExtensionMap = {}) { - context.keys().forEach(key => { - const extDotIndex = key.lastIndexOf("."); - const base = key.substr(0, extDotIndex); - const originalExt = key.substr(extDotIndex); - const registerExt = extensionMap[originalExt] || defaultExtensionMap[originalExt] || originalExt; - - // We prefer source files for webpack scenarios before compilation leftovers, - // e. g. if we get a .js and .ts for the same module, the .js is probably the compiled version of the .ts file, - // so we register the .ts with higher priority, similar is the case with us preferring the .scss to .css - const isSourceFile = originalExt !== registerExt; - - const registerName = base + registerExt; - if (registerName.startsWith("./") && registerName.endsWith(".js")) { - const jsNickNames = [ - // This is extremely short version like "main-page" that was promoted to be used with global.registerModule("module-name", loaderFunc); - registerName.substr(2, registerName.length - 5), - // This is for supporting module names like "./main/main-page" - registerName.substr(0, registerName.length - 3), - // This is for supporting module names like "main/main-page.js" - registerName.substr(2), - ]; - - jsNickNames.forEach(jsNickName => { - if (isSourceFile || !global.moduleExists(jsNickName)) { - global.registerModule(jsNickName, () => context(key)); - } - }); - } else if (registerName.startsWith("./")) { - const moduleNickNames = [ - // This is for supporting module names like "main/main-page.xml" - registerName.substr(2), - ]; - - moduleNickNames.forEach(moduleNickName => { - if (!global.moduleExists(moduleNickName)) { - global.registerModule(moduleNickName, () => context(key)); - } - }); - } - - if (isSourceFile || !global.moduleExists(registerName)) { - global.registerModule(registerName, () => context(key)); - } - }); -}; - -global.moduleExists = function (name: string): boolean { - return modules.has(name); -}; - -global.loadModule = function (name: string): any { - const loader = modules.get(name); - if (loader) { - return loader(name); - } - - for (let resolver of (global).moduleResolvers) { - const result = resolver(name); - if (result) { - modules.set(name, () => result); - - return result; - } - } -}; - -global.getRegisteredModules = function (): string[] { - return Array.from(modules.keys()); -}; - -global.zonedCallback = function (callback: Function): Function { - if ((global).zone) { - // Zone v0.5.* style callback wrapping - return (global).zone.bind(callback); - } - if ((global).Zone) { - // Zone v0.6.* style callback wrapping - return (global).Zone.current.wrap(callback); - } else { - return callback; - } -}; - -global.registerModule("timer", () => require("../timer")); -global.registerModule("ui/dialogs", () => require("../ui/dialogs")); -global.registerModule("xhr", () => require("../xhr")); -global.registerModule("fetch", () => require("../fetch")); - -(global).System = { - import(path) { - return new Promise((resolve, reject) => { - try { - resolve(global.require(path)); - } catch (e) { - reject(e); - } - }); - } -}; - -function registerOnGlobalContext(name: string, module: string): void { - Object.defineProperty(global, name, { - get: function () { - // We do not need to cache require() call since it is already cached in the runtime. - let m = global.loadModule(module); - - // Redefine the property to make sure the above code is executed only once. - let resolvedValue = m[name]; - Object.defineProperty(global, name, { value: resolvedValue, configurable: true, writable: true }); - - return resolvedValue; - }, - configurable: true - }); -} - -let snapshotGlobals; -export function install() { - if ((global).__snapshot || (global).__snapshotEnabled) { - if (!snapshotGlobals) { - // require in snapshot mode is cheap - const timer: typeof timerModule = require("../timer"); - const dialogs: typeof dialogsModule = require("../ui/dialogs"); - const xhr = require("../xhr"); - const fetch = require("../fetch"); - - snapshotGlobals = snapshotGlobals || { - setTimeout: timer.setTimeout, - clearTimeout: timer.clearTimeout, - setInterval: timer.setInterval, - clearInterval: timer.clearInterval, - - alert: dialogs.alert, - confirm: dialogs.confirm, - prompt: dialogs.prompt, - login: dialogs.login, - action: dialogs.action, - - XMLHttpRequest: xhr.XMLHttpRequest, - FormData: xhr.FormData, - - fetch: fetch.fetch, - Headers: fetch.Headers, - Request: fetch.Request, - Response: fetch.Response, - }; - } - const consoleModule = require("../console").Console; - // Object.assign call will fire an error when trying to write to a read-only property of an object, such as 'console' - global.console = global.console || new consoleModule(); - Object.assign(global, snapshotGlobals); - } else { - registerOnGlobalContext("setTimeout", "timer"); - registerOnGlobalContext("clearTimeout", "timer"); - registerOnGlobalContext("setInterval", "timer"); - registerOnGlobalContext("clearInterval", "timer"); - - registerOnGlobalContext("alert", "ui/dialogs"); - registerOnGlobalContext("confirm", "ui/dialogs"); - registerOnGlobalContext("prompt", "ui/dialogs"); - registerOnGlobalContext("login", "ui/dialogs"); - registerOnGlobalContext("action", "ui/dialogs"); - - registerOnGlobalContext("XMLHttpRequest", "xhr"); - registerOnGlobalContext("FormData", "xhr"); - - registerOnGlobalContext("fetch", "fetch"); - registerOnGlobalContext("Headers", "fetch"); - registerOnGlobalContext("Request", "fetch"); - registerOnGlobalContext("Response", "fetch"); - } -} -install(); - -export function Deprecated(target: Object, key?: string | symbol, descriptor?: any) { - if (descriptor) { - const originalMethod = descriptor.value; - - descriptor.value = function (...args: any[]) { - console.log(`${key.toString()} is deprecated`); - - return originalMethod.apply(this, args); - }; - - return descriptor; - } else { - console.log(`${(target && (target).name || target)} is deprecated`); - - return target; - } -} - -global.Deprecated = Deprecated; - -export function Experimental(target: Object, key?: string | symbol, descriptor?: any) { - if (descriptor) { - const originalMethod = descriptor.value; - - descriptor.value = function (...args: any[]) { - console.log(`${key.toString()} is experimental`); - - return originalMethod.apply(this, args); - }; - - return descriptor; - } else { - console.log(`${(target && (target).name || target)} is experimental`); - - return target; - } -} - -global.Experimental = Experimental; From a3c881fd5ef9d453501b54b31ca58ff1b9543c93 Mon Sep 17 00:00:00 2001 From: Manuel Saelices Date: Mon, 9 Mar 2020 01:15:50 +0100 Subject: [PATCH 10/11] TypeScript definitions for the css-tree-parser.ts file --- nativescript-core/css/css-tree-parser.d.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 nativescript-core/css/css-tree-parser.d.ts diff --git a/nativescript-core/css/css-tree-parser.d.ts b/nativescript-core/css/css-tree-parser.d.ts new file mode 100644 index 000000000..263200320 --- /dev/null +++ b/nativescript-core/css/css-tree-parser.d.ts @@ -0,0 +1 @@ +export declare function cssTreeParse(css: any, source: any): any; From 458943111e909fcdad47d12e7ee4bcd9732f4e90 Mon Sep 17 00:00:00 2001 From: Vasil Trifonov Date: Mon, 9 Mar 2020 15:24:26 +0200 Subject: [PATCH 11/11] Tabs styling improvements (#8366) * fix(tabs): delay loadView when animation runs * chore: update api.md * chore: remove unnecessary casting * test: Added disabled test for changing tabs * tabs(ios): added tabs styling in ios * tabs: added iosAlignment property * tabs: textTransform support * tabs: iosAlignment moved to tabstrip * test: add frame-in-tabs test * chore: addressing PR comments * chore: addressing PR comments * chore: call method on the instance instead of call * chore: move IOSAlignment property * chore: update comments * fix: texttransform to tabstrip in bottomnavigation * chore: add new item to native-api-usage * chore: remove unneeded setNativeView call * chore: removed unneeded check Co-authored-by: Dimitar Topuzov --- api-reports/NativeScript.api.md | 5 + e2e/ui-tests-app/app/tabs/main-page.ts | 1 + .../app/tabs/tab-strip-items-page.xml | 2 +- .../platforms/ios/native-api-usage.json | 1 + .../ios/typings/objc!MaterialComponents.d.ts | 12 +- .../bottom-navigation.android.ts | 37 +- .../bottom-navigation.ios.ts | 90 +-- nativescript-core/ui/core/view/view-common.ts | 8 + nativescript-core/ui/core/view/view.ios.ts | 7 +- nativescript-core/ui/index.d.ts | 2 +- .../tab-navigation-base.ts | 2 +- .../tab-strip-item/tab-strip-item.ts | 14 + nativescript-core/ui/tab-view/tab-view.ios.ts | 13 +- nativescript-core/ui/tabs/tabs-common.ts | 5 + nativescript-core/ui/tabs/tabs.android.ts | 43 +- nativescript-core/ui/tabs/tabs.d.ts | 19 + nativescript-core/ui/tabs/tabs.ios.ts | 635 ++++++++---------- 17 files changed, 474 insertions(+), 422 deletions(-) diff --git a/api-reports/NativeScript.api.md b/api-reports/NativeScript.api.md index 6bb4354fc..834f4fa39 100644 --- a/api-reports/NativeScript.api.md +++ b/api-reports/NativeScript.api.md @@ -1239,6 +1239,9 @@ export class iOSApplication { window: any /* UIWindow */; } +// @public +export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected"; + // @public export const isAndroid: boolean; @@ -2274,6 +2277,8 @@ export class Tabs extends TabNavigationBase { ios: any /* UITabBarController */; + iOSTabBarItemsAlignment: IOSTabBarItemsAlignment; + items: Array; offscreenTabLimit: number; diff --git a/e2e/ui-tests-app/app/tabs/main-page.ts b/e2e/ui-tests-app/app/tabs/main-page.ts index 0edd590c1..de21578a6 100644 --- a/e2e/ui-tests-app/app/tabs/main-page.ts +++ b/e2e/ui-tests-app/app/tabs/main-page.ts @@ -32,6 +32,7 @@ export function loadExamples() { examples.set("nested-layout", "tabs/nested-layout-page"); examples.set("nested-bottom-navigation", "tabs/nested-bottom-navigation-page"); examples.set("custom-tabstrip", "tabs/custom-tabstrip-page"); + examples.set("frame-in-tabs", "tabs/frame-in-tabs"); return examples; } diff --git a/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml b/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml index e98df7aa5..b27c15739 100644 --- a/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml +++ b/e2e/ui-tests-app/app/tabs/tab-strip-items-page.xml @@ -1,5 +1,5 @@ - + diff --git a/nativescript-core/platforms/ios/native-api-usage.json b/nativescript-core/platforms/ios/native-api-usage.json index 7e5dff0bd..9d144e8fd 100644 --- a/nativescript-core/platforms/ios/native-api-usage.json +++ b/nativescript-core/platforms/ios/native-api-usage.json @@ -14,6 +14,7 @@ "Foundation.*:*", "MaterialComponents.MDCTabBar:*", + "MaterialComponents.MDCTabBarIndicatorTemplate:*", "NativeScriptEmbedder:*", diff --git a/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts b/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts index 92ce4a5b4..71b6b1478 100644 --- a/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts +++ b/nativescript-core/platforms/ios/typings/objc!MaterialComponents.d.ts @@ -442,13 +442,17 @@ declare class MDCTabBar extends UIView implements UIBarPositioning { delegate: MDCTabBarDelegate; - displaysUppercaseTitles: boolean; + displaysUppercaseTitles: boolean; - inkColor: UIColor; + enableRippleBehavior: boolean; - itemAppearance: MDCTabBarItemAppearance; + inkColor: UIColor; - items: NSArray; + itemAppearance: MDCTabBarItemAppearance; + + items: NSArray; + + rippleColor: UIColor; selectedItem: UITabBarItem; diff --git a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts index 18ebd2d02..b59f0414b 100644 --- a/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts +++ b/nativescript-core/ui/bottom-navigation/bottom-navigation.android.ts @@ -248,6 +248,7 @@ export class BottomNavigation extends TabNavigationBase { private _currentTransaction: androidx.fragment.app.FragmentTransaction; private _attachedToWindow = false; public _originalBackground: any; + private _textTransform: TextTransform = "none"; constructor() { super(); @@ -567,6 +568,18 @@ export class BottomNavigation extends TabNavigationBase { }); } + private getItemLabelTextTransform(tabStripItem: TabStripItem): TextTransform { + const nestedLabel = tabStripItem.label; + let textTransform: TextTransform = null; + if (nestedLabel && nestedLabel.style.textTransform !== "initial") { + textTransform = nestedLabel.style.textTransform; + } else if (tabStripItem.style.textTransform !== "initial") { + textTransform = tabStripItem.style.textTransform; + } + + return textTransform || this._textTransform; + } + private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); @@ -575,10 +588,8 @@ export class BottomNavigation extends TabNavigationBase { let title = titleLabel.text; // TEXT-TRANSFORM - const textTransform = titleLabel.style.textTransform; - if (textTransform) { - title = getTransformedText(title, textTransform); - } + const textTransform = this.getItemLabelTextTransform(tabStripItem); + title = getTransformedText(title, textTransform); tabItemSpec.title = title; // BACKGROUND-COLOR @@ -736,6 +747,24 @@ export class BottomNavigation extends TabNavigationBase { tabStripItem.nativeViewProtected.setText(title); } + public getTabBarTextTransform(): TextTransform { + return this._textTransform; + } + + public setTabBarTextTransform(value: TextTransform): void { + let items = this.tabStrip && this.tabStrip.items; + if (items) { + items.forEach((tabStripItem) => { + if (tabStripItem.label && tabStripItem.nativeViewProtected) { + const nestedLabel = tabStripItem.label; + const title = getTransformedText(nestedLabel.text, value); + tabStripItem.nativeViewProtected.setText(title); + } + }); + } + this._textTransform = value; + } + [selectedIndexProperty.setNative](value: number) { // const smoothScroll = false; diff --git a/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts b/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts index f80207282..9c0f4b703 100644 --- a/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts +++ b/nativescript-core/ui/bottom-navigation/bottom-navigation.ios.ts @@ -71,20 +71,19 @@ class UITabBarControllerImpl extends UITabBarController { public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); - UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion - .call(coordinator, () => { - const owner = this._owner.get(); - if (owner && owner.tabStrip && owner.tabStrip.items) { - const tabStrip = owner.tabStrip; - tabStrip.items.forEach(tabStripItem => { - updateBackgroundPositions(tabStrip, tabStripItem); + coordinator.animateAlongsideTransitionCompletion(() => { + const owner = this._owner.get(); + if (owner && owner.tabStrip && owner.tabStrip.items) { + const tabStrip = owner.tabStrip; + tabStrip.items.forEach(tabStripItem => { + updateBackgroundPositions(tabStrip, tabStripItem); - const index = tabStripItem._index; - const tabBarItemController = this.viewControllers[index]; - updateTitleAndIconPositions(tabStripItem, tabBarItemController.tabBarItem, tabBarItemController); - }); - } - }, null); + const index = tabStripItem._index; + const tabBarItemController = this.viewControllers[index]; + updateTitleAndIconPositions(tabStripItem, tabBarItemController.tabBarItem, tabBarItemController); + }); + } + }, null); } // Mind implementation for other controllers @@ -207,17 +206,14 @@ class UINavigationControllerDelegateImpl extends NSObject implements UINavigatio function updateBackgroundPositions(tabStrip: TabStrip, tabStripItem: TabStripItem) { let bgView = (tabStripItem).bgView; + const index = tabStripItem._index; + const width = tabStrip.nativeView.frame.size.width / tabStrip.items.length; + const frame = CGRectMake(width * index, 0, width, tabStrip.nativeView.frame.size.width); if (!bgView) { - const index = tabStripItem._index; - const width = tabStrip.nativeView.frame.size.width / tabStrip.items.length; - const frame = CGRectMake(width * index, 0, width, tabStrip.nativeView.frame.size.width); bgView = UIView.alloc().initWithFrame(frame); tabStrip.nativeView.insertSubviewAtIndex(bgView, 0); (tabStripItem).bgView = bgView; } else { - const index = tabStripItem._index; - const width = tabStrip.nativeView.frame.size.width / tabStrip.items.length; - const frame = CGRectMake(width * index, 0, width, tabStrip.nativeView.frame.size.width); bgView.frame = frame; } @@ -376,8 +372,7 @@ export class BottomNavigation extends TabNavigationBase { } public setTabBarItemColor(tabStripItem: TabStripItem, value: UIColor | Color): void { - const states = getTitleAttributesForStates(tabStripItem.label); - applyStatesToItem(tabStripItem.nativeView, states, this.viewController.tabBar); + setViewTextAttributes(tabStripItem.nativeView, tabStripItem.label, this.viewController.tabBar); } public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { @@ -388,8 +383,7 @@ export class BottomNavigation extends TabNavigationBase { } public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { - const states = getTitleAttributesForStates(tabStripItem.label); - applyStatesToItem(tabStripItem.nativeView, states, this.viewController.tabBar); + setViewTextAttributes(tabStripItem.nativeView, tabStripItem.label, this.viewController.tabBar); } public setTabBarItemTextTransform(tabStripItem: TabStripItem, value: TextTransform): void { @@ -397,6 +391,15 @@ export class BottomNavigation extends TabNavigationBase { tabStripItem.nativeView.title = title; } + public getTabBarHighlightColor(): UIColor { + return this._ios.tabBar.tintColor; + } + + public setTabBarHighlightColor(value: UIColor | Color) { + const nativeColor = value instanceof Color ? value.ios : value; + this._ios.tabBar.tintColor = nativeColor; + } + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { const width = layout.getMeasureSpecSize(widthMeasureSpec); const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); @@ -518,8 +521,7 @@ export class BottomNavigation extends TabNavigationBase { const tabBarItem = this.createTabBarItem(tabStripItem, i); updateTitleAndIconPositions(tabStripItem, tabBarItem, controller); - const states = getTitleAttributesForStates(tabStripItem.label); - applyStatesToItem(tabBarItem, states, this.viewController.tabBar); + setViewTextAttributes(tabBarItem, tabStripItem.label, this.viewController.tabBar); controller.tabBarItem = tabBarItem; tabStripItem._index = i; @@ -688,51 +690,31 @@ export class BottomNavigation extends TabNavigationBase { } } -interface TabStates { - normalState?: any; - selectedState?: any; -} - -function getTitleAttributesForStates(view: View): TabStates { +function setViewTextAttributes(item: UITabBarItem, view: View, tabBar: UITabBar): any { if (!view) { return null; } - const result: TabStates = {}; const defaultTabItemFontSize = 10; const tabItemFontSize = view.style.fontSize || defaultTabItemFontSize; const font: UIFont = view.style.fontInternal.getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); const tabItemTextColor = view.style.color; const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null; - result.normalState = { [NSFontAttributeName]: font }; + let attributes: any = { [NSFontAttributeName]: font }; if (textColor) { - result.normalState[UITextAttributeTextColor] = textColor; + attributes[UITextAttributeTextColor] = textColor; + attributes[NSForegroundColorAttributeName] = textColor; } - const tabSelectedItemTextColor = view.style.color; - const selectedTextColor = tabSelectedItemTextColor instanceof Color ? tabSelectedItemTextColor.ios : null; - result.selectedState = { [NSFontAttributeName]: font }; - if (selectedTextColor) { - result.selectedState[UITextAttributeTextColor] = selectedTextColor; - } - - return result; -} - -function applyStatesToItem(item: UITabBarItem, states: TabStates, tabBar: UITabBar) { - if (!states) { - return; - } - - item.setTitleTextAttributesForState(states.normalState, UIControlState.Normal); - item.setTitleTextAttributesForState(states.selectedState, UIControlState.Selected); + item.setTitleTextAttributesForState(attributes, UIControlState.Selected); + item.setTitleTextAttributesForState(attributes, UIControlState.Normal); // there's a bug when setting the item color on ios 13 if there's no background set to the tabstrip // https://books.google.bg/books?id=99_BDwAAQBAJ&q=tabBar.unselectedItemTintColor // to fix the above issue we are applying the selected fix only for the case, when there is no background set // in that case we have the following known issue: // we will set the color to all unselected items, so you won't be able to set different colors for the different not selected items - if (!tabBar.barTintColor && states.normalState[UITextAttributeTextColor] && (majorVersion > 9)) { - tabBar.unselectedItemTintColor = states.normalState[UITextAttributeTextColor]; + if (!tabBar.barTintColor && attributes[UITextAttributeTextColor] && (majorVersion > 9)) { + tabBar.unselectedItemTintColor = attributes[UITextAttributeTextColor]; } -} +} \ No newline at end of file diff --git a/nativescript-core/ui/core/view/view-common.ts b/nativescript-core/ui/core/view/view-common.ts index d81d27ee2..8a8cea523 100644 --- a/nativescript-core/ui/core/view/view-common.ts +++ b/nativescript-core/ui/core/view/view-common.ts @@ -26,6 +26,7 @@ import { sanitizeModuleName } from "../../builder/module-name-sanitizer"; import { StyleScope } from "../../styling/style-scope"; import { LinearGradient } from "../../styling/linear-gradient"; import { BackgroundRepeat } from "../../styling/style-properties"; +import { TextTransform } from "../../text-base"; export * from "../../styling/style-properties"; export * from "../view-base"; @@ -718,6 +719,13 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { this.style.perspective = value; } + get textTransform(): TextTransform { + return this.style.textTransform; + } + set textTransform(value: TextTransform) { + this.style.textTransform = value; + } + get translateX(): dip { return this.style.translateX; } diff --git a/nativescript-core/ui/core/view/view.ios.ts b/nativescript-core/ui/core/view/view.ios.ts index 18ab564a6..789d76577 100644 --- a/nativescript-core/ui/core/view/view.ios.ts +++ b/nativescript-core/ui/core/view/view.ios.ts @@ -180,7 +180,7 @@ export class View extends ViewCommon implements ViewDefinition { if (adjustedFrame) { nativeView.frame = adjustedFrame; } - + if (this._hasTransfrom) { // re-apply the transform after the frame is adjusted nativeView.layer.transform = transform; @@ -359,7 +359,7 @@ export class View extends ViewCommon implements ViewDefinition { if (this.rotateX || this.rotateY) { transform.m34 = -1 / perspective; } - + transform = CATransform3DTranslate(transform, this.translateX, this.translateY, 0); transform = iosNativeHelper.applyRotateTransform(transform, this.rotateX, this.rotateY, this.rotate); transform = CATransform3DScale(transform, scaleX, scaleY, 1); @@ -479,8 +479,7 @@ export class View extends ViewCommon implements ViewDefinition { parentController.presentViewControllerAnimatedCompletion(controller, animated, null); const transitionCoordinator = parentController.transitionCoordinator; if (transitionCoordinator) { - UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion - .call(transitionCoordinator, null, () => this._raiseShownModallyEvent()); + transitionCoordinator.animateAlongsideTransitionCompletion(null, () => this._raiseShownModallyEvent()); } else { // Apparently iOS 9+ stops all transitions and animations upon application suspend and transitionCoordinator becomes null here in this case. // Since we are not waiting for any transition to complete, i.e. transitionCoordinator is null, we can directly raise our shownModally event. diff --git a/nativescript-core/ui/index.d.ts b/nativescript-core/ui/index.d.ts index 3ac104e6c..ac5934ff0 100644 --- a/nativescript-core/ui/index.d.ts +++ b/nativescript-core/ui/index.d.ts @@ -34,7 +34,7 @@ export { TabNavigationBase } from "./tab-navigation-base/tab-navigation-base"; export { TabStrip, TabStripItemEventData } from "./tab-navigation-base/tab-strip"; export { TabStripItem } from "./tab-navigation-base/tab-strip-item"; export { TabView, TabViewItem } from "./tab-view"; -export { Tabs } from "./tabs"; +export { Tabs, IOSTabBarItemsAlignment } from "./tabs"; export { TextBase } from "./text-base"; export { FormattedString } from "./text-base/formatted-string"; export { Span } from "./text-base/span"; diff --git a/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts b/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts index ee035bf1f..011d283ef 100644 --- a/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts +++ b/nativescript-core/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.ts @@ -286,4 +286,4 @@ export const tabStripProperty = new Property({ target.onTabStripChanged(oldValue, newValue); } }); -tabStripProperty.register(TabNavigationBase); +tabStripProperty.register(TabNavigationBase); \ No newline at end of file diff --git a/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts b/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts index ead226c81..240cebec7 100644 --- a/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts +++ b/nativescript-core/ui/tab-navigation-base/tab-strip-item/tab-strip-item.ts @@ -13,6 +13,7 @@ import { import { isIOS } from "../../../platform"; import { Image } from "../../image/image"; import { Label } from "../../label/label"; +import { textTransformProperty, TextTransform } from "../../text-base"; export * from "../../core/view"; export const traceCategory = "TabView"; @@ -236,6 +237,19 @@ export class TabStripItem extends View implements TabStripItemDefinition, AddChi return tabStripParent && tabStripParent.setTabBarItemBackgroundColor(this, value); } + [textTransformProperty.getDefault](): TextTransform { + const parent = this.parent; + const tabStripParent = parent && parent.parent; + + return tabStripParent && tabStripParent.getTabBarItemTextTransform(this); + } + [textTransformProperty.setNative](value: TextTransform) { + const parent = this.parent; + const tabStripParent = parent && parent.parent; + + return tabStripParent && tabStripParent.setTabBarItemTextTransform(this, value); + } + [backgroundInternalProperty.getDefault](): any { return null; } diff --git a/nativescript-core/ui/tab-view/tab-view.ios.ts b/nativescript-core/ui/tab-view/tab-view.ios.ts index 7d0cef33c..ec96eaaab 100644 --- a/nativescript-core/ui/tab-view/tab-view.ios.ts +++ b/nativescript-core/ui/tab-view/tab-view.ios.ts @@ -63,13 +63,12 @@ class UITabBarControllerImpl extends UITabBarController { public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); - UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion - .call(coordinator, null, () => { - const owner = this._owner.get(); - if (owner && owner.items) { - owner.items.forEach(tabItem => tabItem._updateTitleAndIconPositions()); - } - }); + coordinator.animateAlongsideTransitionCompletion(null, () => { + const owner = this._owner.get(); + if (owner && owner.items) { + owner.items.forEach(tabItem => tabItem._updateTitleAndIconPositions()); + } + }); } // Mind implementation for other controllers diff --git a/nativescript-core/ui/tabs/tabs-common.ts b/nativescript-core/ui/tabs/tabs-common.ts index 71ff7f7d8..56f3f2ad9 100644 --- a/nativescript-core/ui/tabs/tabs-common.ts +++ b/nativescript-core/ui/tabs/tabs-common.ts @@ -22,6 +22,7 @@ export class TabsBase extends TabNavigationBase implements TabsDefinition { public swipeEnabled: boolean; public offscreenTabLimit: number; public tabsPosition: "top" | "bottom"; + public iOSTabBarItemsAlignment: IOSTabBarItemsAlignment; } // TODO: Add Unit tests @@ -39,3 +40,7 @@ offscreenTabLimitProperty.register(TabsBase); export const tabsPositionProperty = new Property({ name: "tabsPosition", defaultValue: "top" }); tabsPositionProperty.register(TabsBase); + +export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected"; +export const iOSTabBarItemsAlignmentProperty = new Property({ name: "iOSTabBarItemsAlignment", defaultValue: "justified" }); +iOSTabBarItemsAlignmentProperty.register(TabsBase); \ No newline at end of file diff --git a/nativescript-core/ui/tabs/tabs.android.ts b/nativescript-core/ui/tabs/tabs.android.ts index a2ab442df..afdbab527 100644 --- a/nativescript-core/ui/tabs/tabs.android.ts +++ b/nativescript-core/ui/tabs/tabs.android.ts @@ -373,6 +373,7 @@ export class Tabs extends TabsBase { private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; private _androidViewId: number = -1; public _originalBackground: any; + private _textTransform: TextTransform = "uppercase"; constructor() { super(); @@ -637,6 +638,18 @@ export class Tabs extends TabsBase { }); } + private getItemLabelTextTransform(tabStripItem: TabStripItem): TextTransform { + const nestedLabel = tabStripItem.label; + let textTransform: TextTransform = null; + if (nestedLabel && nestedLabel.style.textTransform !== "initial") { + textTransform = nestedLabel.style.textTransform; + } else if (tabStripItem.style.textTransform !== "initial") { + textTransform = tabStripItem.style.textTransform; + } + + return textTransform || this._textTransform; + } + private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec { const tabItemSpec = new org.nativescript.widgets.TabItemSpec(); @@ -645,10 +658,8 @@ export class Tabs extends TabsBase { let title = nestedLabel.text; // TEXT-TRANSFORM - const textTransform = nestedLabel.style.textTransform; - if (textTransform) { - title = getTransformedText(title, textTransform); - } + const textTransform = this.getItemLabelTextTransform(tabStripItem); + title = getTransformedText(title, textTransform); tabItemSpec.title = title; // BACKGROUND-COLOR @@ -817,7 +828,7 @@ export class Tabs extends TabsBase { const tabBarItem = this._tabsBar.getViewForItemAt(index); const imgView = tabBarItem.getChildAt(0); const drawable = this.getIcon(tabStripItem); - + imgView.setImageDrawable(drawable); } @@ -828,12 +839,34 @@ export class Tabs extends TabsBase { tabStripItem.nativeViewProtected.setTypeface(value.getAndroidTypeface()); } + public getTabBarItemTextTransform(tabStripItem: TabStripItem): TextTransform { + return this.getItemLabelTextTransform(tabStripItem); + } + public setTabBarItemTextTransform(tabStripItem: TabStripItem, value: TextTransform): void { const nestedLabel = tabStripItem.label; const title = getTransformedText(nestedLabel.text, value); tabStripItem.nativeViewProtected.setText(title); } + public getTabBarTextTransform(): TextTransform { + return this._textTransform; + } + + public setTabBarTextTransform(value: TextTransform): void { + let items = this.tabStrip && this.tabStrip.items; + if (items) { + items.forEach((tabStripItem) => { + if (tabStripItem.label && tabStripItem.nativeViewProtected) { + const nestedLabel = tabStripItem.label; + const title = getTransformedText(nestedLabel.text, value); + tabStripItem.nativeViewProtected.setText(title); + } + }); + } + this._textTransform = value; + } + [selectedIndexProperty.setNative](value: number) { const smoothScroll = true; diff --git a/nativescript-core/ui/tabs/tabs.d.ts b/nativescript-core/ui/tabs/tabs.d.ts index 17e740e91..feb397d9e 100644 --- a/nativescript-core/ui/tabs/tabs.d.ts +++ b/nativescript-core/ui/tabs/tabs.d.ts @@ -49,6 +49,16 @@ export class Tabs extends TabNavigationBase { */ tabsPosition: "top" | "bottom"; + /** + * Gets or set the MDCTabBarAlignment of the tab bar icons in iOS. Defaults to "justified" + * Valid values are: + * - leading + * - justified + * - center + * - centerSelected + */ + iOSTabBarItemsAlignment: IOSTabBarItemsAlignment; + /** * Gets the native [android widget](http://developer.android.com/reference/android/support/v4/view/ViewPager.html) that represents the user interface for this component. Valid only when running on Android OS. */ @@ -81,3 +91,12 @@ export class Tabs extends TabNavigationBase { export const itemsProperty: Property; export const tabStripProperty: Property export const selectedIndexProperty: Property; + +/** + * IOS Alignment of the Tabs TabStrip to use. + * - `leading` - tab items are aligned to the left + * - `justified` - tab strip is split equally to all the tab items + * - `center` - tabs items are aligned in the center + * - `centerSelected` - tab items move to make the selected tab in the center + */ +export type IOSTabBarItemsAlignment = "leading" | "justified" | "center" | "centerSelected"; diff --git a/nativescript-core/ui/tabs/tabs.ios.ts b/nativescript-core/ui/tabs/tabs.ios.ts index 9a59ce5b1..c8ce759d4 100644 --- a/nativescript-core/ui/tabs/tabs.ios.ts +++ b/nativescript-core/ui/tabs/tabs.ios.ts @@ -7,14 +7,15 @@ import { TextTransform, ViewBase } from "../text-base"; // Requires import { Color } from "../../color"; import { ImageSource } from "../../image-source"; +import { device } from "../../platform"; import { ios as iosUtils, isFontIconURI, layout } from "../../utils/utils"; import { ios as iosView, View } from "../core/view"; import { Frame } from "../frame"; import { Font } from "../styling/font"; import { - getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty + getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty, } from "../tab-navigation-base/tab-navigation-base"; -import { swipeEnabledProperty, TabsBase } from "./tabs-common"; +import { swipeEnabledProperty, TabsBase, IOSTabBarItemsAlignment, iOSTabBarItemsAlignmentProperty } from "./tabs-common"; // TODO // import { profile } from "../../profiling"; @@ -22,6 +23,7 @@ import { swipeEnabledProperty, TabsBase } from "./tabs-common"; export * from "./tabs-common"; const majorVersion = iosUtils.MajorVersion; +const isPhone = device.deviceType === "Phone"; // Equivalent to dispatch_async(dispatch_get_main_queue(...)) call const invokeOnRunLoop = (function () { @@ -74,6 +76,17 @@ class MDCTabBarDelegateImpl extends NSObject implements MDCTabBarDelegate { } } +class BackgroundIndicatorTemplate extends NSObject implements MDCTabBarIndicatorTemplate { + public static ObjCProtocols = [MDCTabBarIndicatorTemplate]; + + public indicatorAttributesForContext(context: MDCTabBarIndicatorContext): MDCTabBarIndicatorAttributes { + let attributes = new MDCTabBarIndicatorAttributes(); + attributes.path = UIBezierPath.bezierPathWithRect(context.bounds); + + return attributes; + } +} + class UIPageViewControllerImpl extends UIPageViewController { tabBar: MDCTabBar; scrollView: UIScrollView; @@ -114,7 +127,7 @@ class UIPageViewControllerImpl extends UIPageViewController { } tabBar.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleBottomMargin; - tabBar.alignment = MDCTabBarAlignment.Leading; + tabBar.alignment = MDCTabBarAlignment.Justified; tabBar.sizeToFit(); this.tabBar = tabBar; @@ -223,6 +236,24 @@ class UIPageViewControllerImpl extends UIPageViewController { } } } + + public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { + super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); + coordinator.animateAlongsideTransitionCompletion(() => { + const owner = this._owner.get(); + if (owner && owner.tabStrip && owner.tabStrip.items) { + const tabStrip = owner.tabStrip; + tabStrip.items.forEach(tabStripItem => { + updateBackgroundPositions(tabStrip, tabStripItem, + this.tabBar.alignment !== MDCTabBarAlignment.Justified || (owner.selectedIndex !== tabStripItem._index) ? owner._defaultItemBackgroundColor : null); + + const index = tabStripItem._index; + const tabBarItemController = owner.viewControllers[index]; + updateTitleAndIconPositions(tabStripItem, tabBarItemController.tabBarItem, tabBarItemController); + }); + } + }, null); + } } class UIPageViewControllerDataSourceImpl extends NSObject implements UIPageViewControllerDataSource { @@ -355,167 +386,6 @@ class UIPageViewControllerDelegateImpl extends NSObject implements UIPageViewCon } } -// class UITabBarControllerImpl extends UITabBarController { - -// private _owner: WeakRef; - -// public static initWithOwner(owner: WeakRef): UITabBarControllerImpl { -// let handler = UITabBarControllerImpl.new(); -// handler._owner = owner; -// return handler; -// } - -// @profile -// public viewWillAppear(animated: boolean): void { -// super.viewWillAppear(animated); -// const owner = this._owner.get(); -// if (!owner) { -// return; -// } - -// // Unify translucent and opaque bars layout -// this.extendedLayoutIncludesOpaqueBars = true; - -// iosView.updateAutoAdjustScrollInsets(this, owner); - -// if (!owner.parent) { -// owner.callLoaded(); -// } -// } - -// @profile -// public viewDidDisappear(animated: boolean): void { -// super.viewDidDisappear(animated); -// const owner = this._owner.get(); -// if (owner && !owner.parent && owner.isLoaded && !this.presentedViewController) { -// owner.callUnloaded(); -// } -// } - -// public viewWillTransitionToSizeWithTransitionCoordinator(size: CGSize, coordinator: UIViewControllerTransitionCoordinator): void { -// super.viewWillTransitionToSizeWithTransitionCoordinator(size, coordinator); -// UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion -// .call(coordinator, null, () => { -// const owner = this._owner.get(); -// if (owner && owner.items) { -// // owner.items.forEach(tabItem => tabItem._updateTitleAndIconPositions()); TODO: -// } -// }); -// } -// } - -// class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate { -// public static ObjCProtocols = [UITabBarControllerDelegate]; - -// private _owner: WeakRef; - -// public static initWithOwner(owner: WeakRef): UITabBarControllerDelegateImpl { -// let delegate = UITabBarControllerDelegateImpl.new(); -// delegate._owner = owner; -// return delegate; -// } - -// public tabBarControllerShouldSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): boolean { -// if (traceEnabled()) { -// traceWrite("TabView.delegate.SHOULD_select(" + tabBarController + ", " + viewController + ");", traceCategories.Debug); -// } - -// let owner = this._owner.get(); -// if (owner) { -// // "< More" cannot be visible after clicking on the main tab bar buttons. -// let backToMoreWillBeVisible = false; -// owner._handleTwoNavigationBars(backToMoreWillBeVisible); -// } - -// if ((tabBarController).selectedViewController === viewController) { -// return false; -// } - -// (tabBarController)._willSelectViewController = viewController; - -// return true; -// } - -// public tabBarControllerDidSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): void { -// if (traceEnabled()) { -// traceWrite("TabView.delegate.DID_select(" + tabBarController + ", " + viewController + ");", traceCategories.Debug); -// } - -// const owner = this._owner.get(); -// if (owner) { -// owner._onViewControllerShown(viewController); -// } - -// (tabBarController)._willSelectViewController = undefined; -// } -// } - -// class UINavigationControllerDelegateImpl extends NSObject implements UINavigationControllerDelegate { -// public static ObjCProtocols = [UINavigationControllerDelegate]; - -// private _owner: WeakRef; - -// public static initWithOwner(owner: WeakRef): UINavigationControllerDelegateImpl { -// let delegate = UINavigationControllerDelegateImpl.new(); -// delegate._owner = owner; -// return delegate; -// } - -// navigationControllerWillShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void { -// if (traceEnabled()) { -// traceWrite("TabView.moreNavigationController.WILL_show(" + navigationController + ", " + viewController + ", " + animated + ");", traceCategories.Debug); -// } - -// let owner = this._owner.get(); -// if (owner) { -// // If viewController is one of our tab item controllers, then "< More" will be visible shortly. -// // Otherwise viewController is the UIMoreListController which shows the list of all tabs beyond the 4th tab. -// let backToMoreWillBeVisible = owner._ios.viewControllers.containsObject(viewController); -// owner._handleTwoNavigationBars(backToMoreWillBeVisible); -// } -// } - -// navigationControllerDidShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void { -// if (traceEnabled()) { -// traceWrite("TabView.moreNavigationController.DID_show(" + navigationController + ", " + viewController + ", " + animated + ");", traceCategories.Debug); -// } -// // We don't need Edit button in More screen. -// navigationController.navigationBar.topItem.rightBarButtonItem = null; -// let owner = this._owner.get(); -// if (owner) { -// owner._onViewControllerShown(viewController); -// } -// } -// } - -// function updateTitleAndIconPositions(tabStripItem: TabStripItem, tabBarItem: UITabBarItem, controller: UIViewController) { -// if (!tabStripItem || !tabBarItem) { -// return; -// } - -// // For iOS <11 icon is *always* above the text. -// // For iOS 11 icon is above the text *only* on phones in portrait mode. -// const orientation = controller.interfaceOrientation; -// const isPortrait = orientation !== UIInterfaceOrientation.LandscapeLeft && orientation !== UIInterfaceOrientation.LandscapeRight; -// const isIconAboveTitle = (majorVersion < 11) || (isPhone && isPortrait); - -// if (!tabStripItem.iconSource) { -// if (isIconAboveTitle) { -// tabBarItem.titlePositionAdjustment = { horizontal: 0, vertical: -20 }; -// } else { -// tabBarItem.titlePositionAdjustment = { horizontal: 0, vertical: 0 }; -// } -// } - -// if (!tabStripItem.title) { -// if (isIconAboveTitle) { -// tabBarItem.imageInsets = new UIEdgeInsets({ top: 6, left: 0, bottom: -6, right: 0 }); -// } else { -// tabBarItem.imageInsets = new UIEdgeInsets({ top: 0, left: 0, bottom: 0, right: 0 }); -// } -// } -// } - function iterateIndexRange(index: number, eps: number, lastIndex: number, callback: (i) => void) { const rangeStart = Math.max(0, index - eps); const rangeEnd = Math.min(index + eps, lastIndex); @@ -524,6 +394,53 @@ function iterateIndexRange(index: number, eps: number, lastIndex: number, callba } } +function updateBackgroundPositions(tabStrip: TabStrip, tabStripItem: TabStripItem, color: UIColor = null) { + let bgView = (tabStripItem).bgView; + const index = tabStripItem._index; + let width = tabStrip.nativeView.frame.size.width / tabStrip.items.length; + const frame = CGRectMake(width * index, 0, width, tabStrip.nativeView.frame.size.width); + if (!bgView) { + bgView = UIView.alloc().initWithFrame(frame); + tabStrip.nativeView.insertSubviewAtIndex(bgView, 0); + (tabStripItem).bgView = bgView; + } + else { + bgView.frame = frame; + } + + const backgroundColor = tabStripItem.style.backgroundColor; + bgView.backgroundColor = color || (backgroundColor instanceof Color ? backgroundColor.ios : backgroundColor); + +} + +function updateTitleAndIconPositions(tabStripItem: TabStripItem, tabBarItem: UITabBarItem, controller: UIViewController) { + if (!tabStripItem || !tabBarItem) { + return; + } + + // For iOS <11 icon is *always* above the text. + // For iOS 11 icon is above the text *only* on phones in portrait mode. + const orientation = controller.interfaceOrientation; + const isPortrait = orientation !== UIInterfaceOrientation.LandscapeLeft && orientation !== UIInterfaceOrientation.LandscapeRight; + const isIconAboveTitle = (majorVersion < 11) || (isPhone && isPortrait); + + if (!tabStripItem.iconSource) { + if (isIconAboveTitle) { + tabBarItem.titlePositionAdjustment = { horizontal: 0, vertical: -20 }; + } else { + tabBarItem.titlePositionAdjustment = { horizontal: 0, vertical: 0 }; + } + } + + if (!tabStripItem.title) { + if (isIconAboveTitle) { + tabBarItem.imageInsets = new UIEdgeInsets({ top: 6, left: 0, bottom: -6, right: 0 }); + } else { + tabBarItem.imageInsets = new UIEdgeInsets({ top: 0, left: 0, bottom: 0, right: 0 }); + } + } +} + export class Tabs extends TabsBase { public nativeViewProtected: UIView; public selectedIndex: number; @@ -542,11 +459,12 @@ export class Tabs extends TabsBase { private _delegate: UIPageViewControllerDelegateImpl; // private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl; private _iconsCache = {}; + private _backgroundIndicatorColor: UIColor; + public _defaultItemBackgroundColor: UIColor; constructor() { super(); - // this.viewController = this._ios = UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); // .alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null); // UITabBarControllerImpl.initWithOwner(new WeakRef(this)); this.viewController = this._ios = UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); //alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null);; } @@ -558,8 +476,6 @@ export class Tabs extends TabsBase { super.initNativeView(); this._dataSource = UIPageViewControllerDataSourceImpl.initWithOwner(new WeakRef(this)); this._delegate = UIPageViewControllerDelegateImpl.initWithOwner(new WeakRef(this)); - // this._delegate = UITabBarControllerDelegateImpl.initWithOwner(new WeakRef(this)); - // this._moreNavigationControllerDelegate = UINavigationControllerDelegateImpl.initWithOwner(new WeakRef(this)); } disposeNativeView() { @@ -567,7 +483,6 @@ export class Tabs extends TabsBase { this._delegate = null; this._ios.tabBarDelegate = null; this._ios.tabBar = null; - // this._moreNavigationControllerDelegate = null; super.disposeNativeView(); } @@ -576,23 +491,21 @@ export class Tabs extends TabsBase { public onLoaded() { super.onLoaded(); + this.setViewControllers(this.items); + const selectedIndex = this.selectedIndex; const selectedView = this.items && this.items[selectedIndex] && this.items[selectedIndex].content; if (selectedView instanceof Frame) { - (selectedView)._pushInFrameStackRecursive(); + selectedView._pushInFrameStackRecursive(); } this._ios.dataSource = this._dataSource; this._ios.delegate = this._delegate; - - const tabStripItems = this.tabStrip ? this.tabStrip.items : null; - this.setTabStripItems(tabStripItems); } public onUnloaded() { this._ios.dataSource = null; this._ios.delegate = null; - // this._ios.moreNavigationController.delegate = null; super.onUnloaded(); } @@ -614,20 +527,33 @@ export class Tabs extends TabsBase { return; } - // const oldItem = items[oldIndex]; - // if (oldItem) { - // oldItem.unloadView(oldItem.view); - // } + const oldItem = items[oldIndex]; + if (oldItem) { + oldItem.canBeLoaded = false; + oldItem.unloadView(oldItem.content); + } - // const newItem = items[newIndex]; - // if (newItem && this.isLoaded) { - // const selectedView = items[newIndex].view; - // if (selectedView instanceof Frame) { - // selectedView._pushInFrameStackRecursive(); - // } + const newItem = items[newIndex]; + if (newItem && this.isLoaded) { + const selectedView = items[newIndex].content; + if (selectedView instanceof Frame) { + selectedView._pushInFrameStackRecursive(); + } - // newItem.loadView(newItem.view); - // } + newItem.canBeLoaded = true; + newItem.loadView(newItem.content); + } + + const tabStripItems = this.tabStrip && this.tabStrip.items; + if (tabStripItems) { + if (tabStripItems[newIndex]) { + tabStripItems[newIndex]._emit(TabStripItem.selectEvent); + } + + if (tabStripItems[oldIndex]) { + tabStripItems[oldIndex]._emit(TabStripItem.unselectEvent); + } + } this._loadUnloadTabItems(newIndex); @@ -705,46 +631,6 @@ export class Tabs extends TabsBase { } } - // private _actionBarHiddenByTabView: boolean; - // public _handleTwoNavigationBars(backToMoreWillBeVisible: boolean) { - // // TODO - // // if (traceEnabled()) { - // // traceWrite(`TabView._handleTwoNavigationBars(backToMoreWillBeVisible: ${backToMoreWillBeVisible})`, traceCategories.Debug); - // // } - - // // The "< Back" and "< More" navigation bars should not be visible simultaneously. - // const page = this.page || this._selectedView.page || (this)._selectedView.currentPage; - // if (!page || !page.frame) { - // return; - // } - - // let actionBarVisible = page.frame._getNavBarVisible(page); - - // if (backToMoreWillBeVisible && actionBarVisible) { - // page.frame.ios._disableNavBarAnimation = true; - // page.actionBarHidden = true; - // page.frame.ios._disableNavBarAnimation = false; - // this._actionBarHiddenByTabView = true; - // // TODO - // // if (traceEnabled()) { - // // traceWrite(`TabView hid action bar`, traceCategories.Debug); - // // } - // return; - // } - - // if (!backToMoreWillBeVisible && this._actionBarHiddenByTabView) { - // page.frame.ios._disableNavBarAnimation = true; - // page.actionBarHidden = false; - // page.frame.ios._disableNavBarAnimation = false; - // this._actionBarHiddenByTabView = undefined; - // // TODO - // // if (traceEnabled()) { - // // traceWrite(`TabView restored action bar`, traceCategories.Debug); - // // } - // return; - // } - // } - private getViewController(item: TabContentItem): UIViewController { let newController: UIViewController = item.content ? item.content.viewController : null; @@ -789,115 +675,57 @@ export class Tabs extends TabsBase { private setViewControllers(items: TabContentItem[]) { const length = items ? items.length : 0; if (length === 0) { - // this._ios.setViewControllersDirectionAnimatedCompletion(null, null, false, null); + this.viewControllers = null; + return; } const viewControllers = []; + const tabBarItems = []; - items.forEach((item) => { + if (this.tabStrip) { + this.tabStrip.setNativeView(this._ios.tabBar); + } + + const tabStripItems = this.tabStrip && this.tabStrip.items; + if (tabStripItems) { + if (tabStripItems[this.selectedIndex]) { + tabStripItems[this.selectedIndex]._emit(TabStripItem.selectEvent); + } + } + + items.forEach((item, i) => { const controller = this.getViewController(item); + + if (this.tabStrip && this.tabStrip.items && this.tabStrip.items[i]) { + const tabStripItem = this.tabStrip.items[i]; + const tabBarItem = this.createTabBarItem(tabStripItem, i); + updateTitleAndIconPositions(tabStripItem, tabBarItem, controller); + + setViewTextAttributes(this._ios.tabBar, tabStripItem.label, i === this.selectedIndex); + + controller.tabBarItem = tabBarItem; + tabStripItem._index = i; + tabBarItems.push(tabBarItem); + tabStripItem.setNativeView(tabBarItem); + } + + item.canBeLoaded = true; viewControllers.push(controller); }); this.viewControllers = viewControllers; - - // const controllers = NSMutableArray.alloc().initWithCapacity(length); - - // const selectedItem = items[this.selectedIndex]; - // const controller = this.getViewController(selectedItem); - // controllers.addObject(controller); - - // this._ios.setViewControllersDirectionAnimatedCompletion(controllers, UIPageViewControllerNavigationDirection.Forward, false, null); - - iterateIndexRange(this.selectedIndex, 1, this.items.length, (index) => { - (items[index]).canBeLoaded = true; - }); - - // (selectedItem).canBeLoaded = true; - - // const nextItem = items[this.selectedIndex + 1]; - // (nextItem).canBeLoaded = true; - - // const states = getTitleAttributesForStates(this); - - // items.forEach((item, i) => { - // const controller = this.getViewController(item); - - // let icon = null; - // let title = ""; - - // if (this.tabStrip && this.tabStrip.items && this.tabStrip.items[i]) { - // const tabStripItem = this.tabStrip.items[i]; - // icon = this._getIcon(tabStripItem.iconSource); - // title = tabStripItem.title; - - // const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag((title || ""), icon, i); - // updateTitleAndIconPositions(tabStripItem, tabBarItem, controller); - - // applyStatesToItem(tabBarItem, states); - - // controller.tabBarItem = tabBarItem; - // } - - // controllers.addObject(controller); - // (item).canBeLoaded = true; - // }); - - // this._ios.viewControllers = controllers; - // this._ios.customizableViewControllers = null; - - // // When we set this._ios.viewControllers, someone is clearing the moreNavigationController.delegate, so we have to reassign it each time here. - // this._ios.moreNavigationController.delegate = this._moreNavigationControllerDelegate; - } - - private setTabStripItems(items: Array) { - if (!this.tabStrip || !items) { - return; - } - - const tabBarItems = []; - - items.forEach((tabStripItem: TabStripItem, i) => { - tabStripItem._index = i; - const tabBarItem = this.createTabBarItem(tabStripItem, i); - tabBarItems.push(tabBarItem); - tabStripItem.setNativeView(tabBarItem); - }); - this.tabBarItems = tabBarItems; if (this.viewController && this.viewController.tabBar) { this.viewController.tabBar.itemAppearance = this.getTabBarItemAppearance(); - this.viewController.tabBar.items = NSArray.arrayWithArray(tabBarItems); + this.viewController.tabBar.items = NSArray.arrayWithArray(this.tabBarItems); // TODO: investigate why this call is necessary to actually toggle item appearance this.viewController.tabBar.sizeToFit(); - this.tabStrip.setNativeView(this.viewController.tabBar); if (this.selectedIndex) { this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[this.selectedIndex], false); } } - - // const length = items ? items.length : 0; - // if (length === 0) { - // this._tabLayout.setItems(null, null); - // return; - // } - - // const tabItems = new Array(); - // items.forEach((item: TabStripItem, i, arr) => { - // const tabItemSpec = createTabItemSpec(item); - // (item).index = i; - // (item).tabItemSpec = tabItemSpec; - // tabItems.push(tabItemSpec); - // }); - - // const tabLayout = this._tabLayout; - // tabLayout.setItems(tabItems, this._viewPager); - // items.forEach((item, i, arr) => { - // const tv = tabLayout.getTextViewForItemAt(i); - // item.setNativeView(tv); - // }); } private createTabBarItem(item: TabStripItem, index: number): UITabBarItem { @@ -924,9 +752,9 @@ export class Tabs extends TabsBase { private getTabBarItemAppearance(): MDCTabBarItemAppearance { let itemAppearance; - if (this.tabStrip._hasImage && this.tabStrip._hasTitle) { + if (this.tabStrip && this.tabStrip._hasImage && this.tabStrip._hasTitle) { itemAppearance = MDCTabBarItemAppearance.TitledImages; - } else if (this.tabStrip._hasImage) { + } else if (this.tabStrip && this.tabStrip._hasImage) { itemAppearance = MDCTabBarItemAppearance.Images; } else { itemAppearance = MDCTabBarItemAppearance.Titles; @@ -941,6 +769,8 @@ export class Tabs extends TabsBase { } private getIcon(tabStripItem: TabStripItem): UIImage { + // Image and Label children of TabStripItem + // take priority over its `iconSource` and `title` properties const iconSource = tabStripItem.image && tabStripItem.image.src; if (!iconSource) { return null; @@ -948,13 +778,15 @@ export class Tabs extends TabsBase { const target = tabStripItem.image; const font = target.style.fontInternal; - const color = tabStripItem.parent.style.color; + const color = target.style.color; const iconTag = [iconSource, font.fontStyle, font.fontWeight, font.fontSize, font.fontFamily, color].join(";"); + let isFontIcon = false; let image: UIImage = this._iconsCache[iconTag]; if (!image) { let is = new ImageSource; if (isFontIconURI(iconSource)) { + isFontIcon = true; const fontIconCode = iconSource.split("//")[1]; is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color); } else { @@ -968,7 +800,11 @@ export class Tabs extends TabsBase { image = this.getFixedSizeIcon(image); } - const originalRenderedImage = image.imageWithRenderingMode(this.getIconRenderingMode()); + let renderingMode: UIImageRenderingMode = UIImageRenderingMode.AlwaysOriginal; + if (!isFontIcon) { + renderingMode = this.getIconRenderingMode(); + } + const originalRenderedImage = image.imageWithRenderingMode(renderingMode); this._iconsCache[iconTag] = originalRenderedImage; image = originalRenderedImage; } else { @@ -997,26 +833,6 @@ export class Tabs extends TabsBase { return resultImage; } - // private _updateIOSTabBarColorsAndFonts(): void { - // if (!this.tabStrip || !this.tabStrip.items || !this.tabStrip.items.length) { - // return; - // } - - // const tabBar = this.ios.tabBar; - // const states = getTitleAttributesForStates(this); - // for (let i = 0; i < tabBar.items.count; i++) { - // applyStatesToItem(tabBar.items[i], states); - // } - // } - - // TODO: Move this to TabStripItem - // [fontInternalProperty.getDefault](): Font { - // return null; - // } - // [fontInternalProperty.setNative](value: Font) { - // this._updateIOSTabBarColorsAndFonts(); - // } - public getTabBarBackgroundColor(): UIColor { return this._ios.tabBar.barTintColor; } @@ -1025,6 +841,69 @@ export class Tabs extends TabsBase { this._ios.tabBar.barTintColor = value instanceof Color ? value.ios : value; } + public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void { + tabStripItem.nativeView.title = value; + } + + private equalUIColor(first: UIColor, second: UIColor): boolean { + if (!first && !second) { + return true; + } + if (!first || !second) { + return false; + } + const firstComponents = CGColorGetComponents(first.CGColor); + const secondComponents = CGColorGetComponents(second.CGColor); + + return firstComponents[0] === secondComponents[0] + && firstComponents[1] === secondComponents[1] + && firstComponents[2] === secondComponents[2] + && firstComponents[3] === secondComponents[3]; + } + + private isSelectedAndHightlightedItem(tabStripItem: TabStripItem): boolean { + return (tabStripItem._index === this.selectedIndex && tabStripItem["_visualState"] === "highlighted"); + } + + public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + if (!this.tabStrip || !tabStripItem) { + return; + } + + let newColor = value instanceof Color ? value.ios : value; + const itemSelectedAndHighlighted = this.isSelectedAndHightlightedItem(tabStripItem); + + if (!this._defaultItemBackgroundColor && !itemSelectedAndHighlighted) { + this._defaultItemBackgroundColor = newColor; + } + + if (this.viewController.tabBar.alignment !== MDCTabBarAlignment.Justified && itemSelectedAndHighlighted + && !this.equalUIColor(this._defaultItemBackgroundColor, newColor)) { + if (!this._backgroundIndicatorColor) { + this._backgroundIndicatorColor = newColor; + this._ios.tabBar.selectionIndicatorTemplate = new BackgroundIndicatorTemplate(); + this._ios.tabBar.tintColor = newColor; + } + } else { + updateBackgroundPositions(this.tabStrip, tabStripItem, newColor); + } + } + + public setTabBarItemColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + setViewTextAttributes(this._ios.tabBar, tabStripItem.label); + } + + public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + const image = this.getIcon(tabStripItem); + + tabStripItem.nativeView.image = image; + tabStripItem.nativeView.selectedImage = image; + } + + public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { + setViewTextAttributes(this._ios.tabBar, tabStripItem.label); + } + public getTabBarFontInternal(): UIFont { return this._ios.tabBar.unselectedItemTitleFont; } @@ -1039,7 +918,15 @@ export class Tabs extends TabsBase { } public getTabBarTextTransform(): TextTransform { - return null; + switch (this._ios.tabBar.titleTextTransform) { + case MDCTabBarTextTransform.None: + return "none"; + case MDCTabBarTextTransform.Automatic: + return "initial"; + case MDCTabBarTextTransform.Uppercase: + default: + return "uppercase"; + } } public setTabBarTextTransform(value: TextTransform): void { @@ -1047,11 +934,13 @@ export class Tabs extends TabsBase { this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.None; } else if (value === "uppercase") { this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.Uppercase; + } else if (value === "initial") { + this._ios.tabBar.titleTextTransform = MDCTabBarTextTransform.Automatic; } } public getTabBarColor(): UIColor { - return this._ios.tabBar.titleColorForState(MDCTabBarItemState.Normal); + return this._ios.tabBar.titleColorForState(MDCTabBarItemState.Normal); } public setTabBarColor(value: UIColor | Color): void { @@ -1134,6 +1023,12 @@ export class Tabs extends TabsBase { return null; } [itemsProperty.setNative](value: TabContentItem[]) { + if (value) { + value.forEach((item: TabContentItem, i) => { + (item).index = i; + }); + } + this.setViewControllers(value); selectedIndexProperty.coerce(this); } @@ -1141,16 +1036,74 @@ export class Tabs extends TabsBase { [tabStripProperty.getDefault](): TabStrip { return null; } + [tabStripProperty.setNative](value: TabStrip) { - this.setTabStripItems(value.items); + this.setViewControllers(this.items); + selectedIndexProperty.coerce(this); } [swipeEnabledProperty.getDefault](): boolean { return true; } + [swipeEnabledProperty.setNative](value: boolean) { if (this.viewController && this.viewController.scrollView) { this.viewController.scrollView.scrollEnabled = value; } } + + [iOSTabBarItemsAlignmentProperty.getDefault](): IOSTabBarItemsAlignment { + if (!this.viewController || !this.viewController.tabBar) { + return "justified"; + } + + let alignment = this.viewController.tabBar.alignment.toString(); + + return (alignment.charAt(0).toLowerCase() + alignment.substring(1)); + } + + [iOSTabBarItemsAlignmentProperty.setNative](value: IOSTabBarItemsAlignment) { + if (!this.viewController || !this.viewController.tabBar) { + return; + } + + let alignment = MDCTabBarAlignment.Justified; + switch (value) { + case "leading": + alignment = MDCTabBarAlignment.Leading; + break; + case "center": + alignment = MDCTabBarAlignment.Center; + break; + case "centerSelected": + alignment = MDCTabBarAlignment.CenterSelected; + break; + } + + this.viewController.tabBar.alignment = alignment; + } } + +function setViewTextAttributes(tabBar: MDCTabBar, view: View, setSelected: boolean = false): any { + if (!view) { + return null; + } + + const defaultTabItemFontSize = 10; + const tabItemFontSize = view.style.fontSize || defaultTabItemFontSize; + const font: UIFont = view.style.fontInternal.getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); + + tabBar.unselectedItemTitleFont = font; + tabBar.selectedItemTitleFont = font; + + const tabItemTextColor = view.style.color; + const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null; + if (textColor) { + tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Normal); + if (setSelected) { + tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Selected); + } + } + + tabBar.inkColor = UIColor.clearColor; +} \ No newline at end of file