diff --git a/nativescript-core/ui/tabs/index.android.ts b/nativescript-core/ui/tabs/index.android.ts index ca10c8f0b..28a68b2d3 100644 --- a/nativescript-core/ui/tabs/index.android.ts +++ b/nativescript-core/ui/tabs/index.android.ts @@ -5,30 +5,20 @@ import { TabStripItem } from '../tab-navigation-base/tab-strip-item'; import { TextTransform } from '../text-base'; // Requires -import * as application from "../../application"; -import { ImageSource } from "../../image-source"; -import { ad, isFontIconURI, layout, RESOURCE_PREFIX } from "../../utils/utils"; -import { Color } from "../../color"; -import { Frame } from "../frame"; -import { Font } from "../styling/font"; -import { - getIconSpecSize, - itemsProperty, - selectedIndexProperty, - tabStripProperty, -} from "../tab-navigation-base/tab-navigation-base"; -import { getTransformedText } from "../text-base"; -import { - offscreenTabLimitProperty, - swipeEnabledProperty, - animationEnabledProperty, - TabsBase, -} from "./tabs-common"; +import * as application from '../../application'; +import { ImageSource } from '../../image-source'; +import { ad, isFontIconURI, layout, RESOURCE_PREFIX } from '../../utils/utils'; +import { Color } from '../../color'; +import { Frame } from '../frame'; +import { Font } from '../styling/font'; +import { getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty } from '../tab-navigation-base/tab-navigation-base'; +import { getTransformedText } from '../text-base'; +import { offscreenTabLimitProperty, swipeEnabledProperty, animationEnabledProperty, TabsBase } from './tabs-common'; -export * from "./tabs-common"; +export * from './tabs-common'; -const ACCENT_COLOR = "colorAccent"; -const PRIMARY_COLOR = "colorPrimary"; +const ACCENT_COLOR = 'colorAccent'; +const PRIMARY_COLOR = 'colorPrimary'; const DEFAULT_ELEVATION = 4; const TABID = '_tabId'; @@ -381,623 +371,623 @@ function iterateIndexRange(index: number, eps: number, lastIndex: number, callba } export class Tabs extends TabsBase { - private _tabsBar: org.nativescript.widgets.TabsBar; - private _viewPager: androidx.viewpager.widget.ViewPager; - private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; - private _androidViewId: number = -1; - public _originalBackground: any; - private _textTransform: TextTransform = "uppercase"; - private _selectedItemColor: Color; - private _unSelectedItemColor: Color; - public animationEnabled: boolean; - - constructor() { - super(); - tabs.push(new WeakRef(this)); - } - - get _hasFragments(): boolean { - return true; - } - - public onItemsChanged(oldItems: TabContentItem[], newItems: TabContentItem[]): void { - super.onItemsChanged(oldItems, newItems); - - if (oldItems) { - oldItems.forEach((item: TabContentItem, i, arr) => { - (item).index = 0; - (item).tabItemSpec = null; - item.setNativeView(null); - }); - } - } - - public createNativeView() { - initializeNativeClasses(); - // TODO - // if (Trace.isEnabled()) { - // Trace.write("TabView._createUI(" + this + ");", traceCategory); - // } - - const context: android.content.Context = this._context; - const nativeView = new org.nativescript.widgets.GridLayout(context); - const viewPager = new org.nativescript.widgets.TabViewPager(context); - const tabsBar = new TabsBar(context, this); - const lp = new org.nativescript.widgets.CommonLayoutParams(); - const primaryColor = ad.resources.getPaletteColor(PRIMARY_COLOR, context); - let accentColor = getDefaultAccentColor(context); - - lp.row = 1; - - if (this.tabsPosition === "top") { - nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto)); - nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star)); - - viewPager.setLayoutParams(lp); - } else { - nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star)); - nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto)); - - tabsBar.setLayoutParams(lp); - } - - nativeView.addView(viewPager); - (nativeView).viewPager = viewPager; - - const adapter = new PagerAdapter(this); - viewPager.setAdapter(adapter); - (viewPager).adapter = adapter; - - nativeView.addView(tabsBar); - (nativeView).tabsBar = tabsBar; - - setElevation(nativeView, tabsBar, this.tabsPosition); - - if (accentColor) { - tabsBar.setSelectedIndicatorColors([accentColor]); - } - - if (primaryColor) { - tabsBar.setBackgroundColor(primaryColor); - } - - return nativeView; - } - - public initNativeView(): void { - super.initNativeView(); - if (this._androidViewId < 0) { - this._androidViewId = android.view.View.generateViewId(); - } - - const nativeView: any = this.nativeViewProtected; - this._tabsBar = (nativeView).tabsBar; - - const viewPager = (nativeView).viewPager; - viewPager.setId(this._androidViewId); - this._viewPager = viewPager; - this._pagerAdapter = (viewPager).adapter; - (this._pagerAdapter).owner = this; - } - - public _loadUnloadTabItems(newIndex: number) { - const items = this.items; - if (!items) { - return; - } - - const lastIndex = items.length - 1; - const offsideItems = this.offscreenTabLimit; - - let toUnload = []; - let toLoad = []; - - iterateIndexRange(newIndex, offsideItems, lastIndex, (i) => toLoad.push(i)); - - items.forEach((item, i) => { - const indexOfI = toLoad.indexOf(i); - if (indexOfI < 0) { - toUnload.push(i); - } - }); - - toUnload.forEach(index => { - const item = items[index]; - if (items[index]) { - item.unloadView(item.content); - } - }); - - const newItem = items[newIndex]; - const selectedView = newItem && newItem.content; - if (selectedView instanceof Frame) { - (selectedView)._pushInFrameStackRecursive(); - } - - toLoad.forEach(index => { - const item = items[index]; - if (this.isLoaded && items[index]) { - item.loadView(item.content); - } - }); - } - - public onLoaded(): void { - super.onLoaded(); - - if (this._originalBackground) { - this.backgroundColor = null; - this.backgroundColor = this._originalBackground; - this._originalBackground = null; - } - - this.setItems((this.items)); - - if (this.tabStrip) { - this.setTabStripItems(this.tabStrip.items); - } - - // this.setAdapterItems(this.items); - } - - public onUnloaded(): void { - super.onUnloaded(); - - this.setItems(null); - this.setTabStripItems(null); - - // this.setAdapterItems(null); - } - - public disposeNativeView() { - this._tabsBar.setItems(null, null); - (this._pagerAdapter).owner = null; - this._pagerAdapter = null; - - this._tabsBar = null; - this._viewPager = null; - super.disposeNativeView(); - } - - public _onRootViewReset(): void { - super._onRootViewReset(); - - // call this AFTER the super call to ensure descendants apply their rootview-reset logic first - // i.e. in a scenario with tab frames let the frames cleanup their fragments first, and then - // cleanup the tab fragments to avoid - // android.content.res.Resources$NotFoundException: Unable to find resource ID #0xfffffff6 - this.disposeCurrentFragments(); - } - - private disposeCurrentFragments(): void { - const fragmentManager = this._getFragmentManager(); - const transaction = fragmentManager.beginTransaction(); - for (let fragment of (>fragmentManager.getFragments().toArray())) { - transaction.remove(fragment); - } - transaction.commitNowAllowingStateLoss(); - } - - private shouldUpdateAdapter(items: Array) { - if (!this._pagerAdapter) { - return false; - } - - const currentPagerAdapterItems = (this._pagerAdapter).items; - - // if both values are null, should not update - if (!items && !currentPagerAdapterItems) { - return false; - } - - // if one value is null, should update - if (!items || !currentPagerAdapterItems) { - return true; - } - - // if both are Arrays but length doesn't match, should update - if (items.length !== currentPagerAdapterItems.length) { - return true; - } - - const matchingItems = currentPagerAdapterItems.filter((currentItem) => { - return !!items.filter((item) => { - return item._domId === currentItem._domId; - })[0]; - }); - - // if both are Arrays and length matches, but not all items are the same, should update - if (matchingItems.length !== items.length) { - return true; - } - - // if both are Arrays and length matches and all items are the same, should not update - return false; - } - - private setItems(items: Array) { - if (this.shouldUpdateAdapter(items)) { - (this._pagerAdapter).items = items; - - if (items && items.length) { - items.forEach((item: TabContentItem, i) => { - (item).index = i; - }); - } - - this._pagerAdapter.notifyDataSetChanged(); - } - } - - private setTabStripItems(items: Array) { - const length = items ? items.length : 0; - if (length === 0) { - this._tabsBar.setItems(null, null); - - return; - } - - const tabItems = new Array(); - items.forEach((tabStripItem: TabStripItem, i, arr) => { - tabStripItem._index = i; - const tabItemSpec = this.createTabItemSpec(tabStripItem); - (tabStripItem).tabItemSpec = tabItemSpec; - tabItems.push(tabItemSpec); - }); - - const tabsBar = this._tabsBar; - tabsBar.setItems(tabItems, this._viewPager); - this.tabStrip.setNativeView(tabsBar); - items.forEach((item, i, arr) => { - const tv = tabsBar.getTextViewForItemAt(i); - item.setNativeView(tv); - this._setItemColor(item); - }); - } - - 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(); - - if (tabStripItem.isLoaded) { - const nestedLabel = tabStripItem.label; - let title = nestedLabel.text; - - // TEXT-TRANSFORM - const textTransform = this.getItemLabelTextTransform(tabStripItem); - title = getTransformedText(title, textTransform); - tabItemSpec.title = title; - - // BACKGROUND-COLOR - const backgroundColor = tabStripItem.style.backgroundColor; - tabItemSpec.backgroundColor = backgroundColor ? backgroundColor.android : this.getTabBarBackgroundArgbColor(); - - // COLOR - let itemColor = this.selectedIndex === tabStripItem._index ? this._selectedItemColor : this._unSelectedItemColor; - const color = itemColor || nestedLabel.style.color; - tabItemSpec.color = color && color.android; - - // FONT - const fontInternal = nestedLabel.style.fontInternal; - if (fontInternal) { - tabItemSpec.fontSize = fontInternal.fontSize; - tabItemSpec.typeFace = fontInternal.getAndroidTypeface(); - } - - // ICON - const iconSource = tabStripItem.image && tabStripItem.image.src; - if (iconSource) { - const icon = this.getIcon(tabStripItem, itemColor); - - if (icon) { - // 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; - } else { - // TODO: - // traceMissingIcon(iconSource); - } - } - } - - return tabItemSpec; - } - - private getIcon(tabStripItem: TabStripItem, color?: Color): android.graphics.drawable.BitmapDrawable { - const iconSource = tabStripItem.image && tabStripItem.image.src; - if (!iconSource) { - return null; - } - - let is: ImageSource; - if (isFontIconURI(iconSource)) { - const fontIconCode = iconSource.split("//")[1]; - const target = tabStripItem.image ? tabStripItem.image : tabStripItem; - const font = target.style.fontInternal; - if (!color) { - color = target.style.color; - } - is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color); - } else { - is = ImageSource.fromFileOrResourceSync(iconSource); - } - - let imageDrawable: android.graphics.drawable.BitmapDrawable; - if (is && is.android) { - let image = is.android; - - if (this.tabStrip && this.tabStrip.isIconSizeFixed) { - image = this.getFixedSizeIcon(image); - } - - imageDrawable = new android.graphics.drawable.BitmapDrawable(appResources, image); - } else { - // TODO - // traceMissingIcon(iconSource); - } - - return imageDrawable; - } - - private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap { - const inWidth = image.getWidth(); - const inHeight = image.getHeight(); - - const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); - - const widthPixels = iconSpecSize.width * layout.getDisplayDensity(); - const heightPixels = iconSpecSize.height * layout.getDisplayDensity(); - - const scaledImage = android.graphics.Bitmap.createScaledBitmap(image, widthPixels, heightPixels, true); - - return scaledImage; - } - - // private setAdapterItems(items: Array) { - // if (this.shouldUpdateAdapter(items)) { - // (this._pagerAdapter).items = items; - - // const length = items ? items.length : 0; - // if (length === 0) { - // this._tabLayout.setItems(null, null); - // this._pagerAdapter.notifyDataSetChanged(); - // 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); - // }); - - // this._pagerAdapter.notifyDataSetChanged(); - // } - // } - - public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) { - this._tabsBar.updateItemAt(index, spec); - } - - public getTabBarBackgroundColor(): android.graphics.drawable.Drawable { - return this._tabsBar.getBackground(); - } - - public setTabBarBackgroundColor(value: android.graphics.drawable.Drawable | Color): void { - if (value instanceof Color) { - this._tabsBar.setBackgroundColor(value.android); - } else { - this._tabsBar.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources())); - } - - this.updateTabStripItems(); - } - - private updateTabStripItems(): void { - this.tabStrip.items.forEach((tabStripItem: TabStripItem) => { - if (tabStripItem.nativeView) { - const tabItemSpec = this.createTabItemSpec(tabStripItem); - this.updateAndroidItemAt(tabStripItem._index, tabItemSpec); - } - }); - } - - public getTabBarHighlightColor(): number { - return getDefaultAccentColor(this._context); - } - - public setTabBarHighlightColor(value: number | Color) { - const color = value instanceof Color ? value.android : value; - this._tabsBar.setSelectedIndicatorColors([color]); - } - - private setItemsColors(items: Array): void { - items.forEach((item) => { - if (item.nativeView) { - this._setItemColor(item); - } - }); - } - - public getTabBarSelectedItemColor(): Color { - return this._selectedItemColor; - } - - public setTabBarSelectedItemColor(value: Color) { - this._selectedItemColor = value; - this.setItemsColors(this.tabStrip.items); - } - - public getTabBarUnSelectedItemColor(): Color { - return this._unSelectedItemColor; - } - - public setTabBarUnSelectedItemColor(value: Color) { - this._unSelectedItemColor = value; - this.setItemsColors(this.tabStrip.items); - } - - private updateItem(tabStripItem: TabStripItem): void { - // TODO: Should figure out a way to do it directly with the the nativeView - const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem); - const tabItemSpec = this.createTabItemSpec(tabStripItem); - this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec); - } - - public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void { - this.updateItem(tabStripItem); - } - - public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void { - this.updateItem(tabStripItem); - } - - public _setItemColor(tabStripItem: TabStripItem) { - const itemColor = (tabStripItem._index === this.selectedIndex) ? this._selectedItemColor : this._unSelectedItemColor; - if (!itemColor) { - return; - } - - // set label color - tabStripItem.nativeViewProtected.setTextColor(itemColor.android); - - // set icon color - this.setIconColor(tabStripItem, itemColor); - } - - private setIconColor(tabStripItem: TabStripItem, color?: Color) { - const tabBarItem = this._tabsBar.getViewForItemAt(tabStripItem._index); - - const drawable = this.getIcon(tabStripItem, color); - const imgView = tabBarItem.getChildAt(0); - imgView.setImageDrawable(drawable); - if (color) { - imgView.setColorFilter(color.android); - } - } - - public setTabBarItemColor(tabStripItem: TabStripItem, value: number | Color): void { - const itemColor = (tabStripItem._index === this.selectedIndex) ? this._selectedItemColor : this._unSelectedItemColor; - if (itemColor) { - // the itemColor is set through the selectedItemColor and unSelectedItemColor properties - // so it does not respect the css color - return; - } - - const androidColor = value instanceof Color ? value.android : value; - tabStripItem.nativeViewProtected.setTextColor(androidColor); - } - - public setTabBarIconColor(tabStripItem: TabStripItem, value: number | Color): void { - const itemColor = (tabStripItem._index === this.selectedIndex) ? this._selectedItemColor : this._unSelectedItemColor; - if (itemColor) { - // the itemColor is set through the selectedItemColor and unSelectedItemColor properties - // so it does not respect the css color - return; - } - - this.setIconColor(tabStripItem); - } - - public setTabBarIconSource(tabStripItem: TabStripItem, value: number | Color): void { - this.updateItem(tabStripItem); - } - - public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { - if (value.fontSize) { - tabStripItem.nativeViewProtected.setTextSize(value.fontSize); - } - 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) { - // TODO - // if (Trace.isEnabled()) { - // Trace.write("TabView this._viewPager.setCurrentItem(" + value + ", " + smoothScroll + ");", traceCategory); - // } - this._viewPager.setCurrentItem(value, this.animationEnabled); - } - - [itemsProperty.getDefault](): TabContentItem[] { - return null; - } - [itemsProperty.setNative](value: TabContentItem[]) { - this.setItems(value); - selectedIndexProperty.coerce(this); - } - - [tabStripProperty.getDefault](): TabStrip { - return null; - } - [tabStripProperty.setNative](value: TabStrip) { - this.setTabStripItems(value.items); - } - - [swipeEnabledProperty.getDefault](): boolean { - // TODO: create native method and get native? - return true; - } - [swipeEnabledProperty.setNative](value: boolean) { - (this._viewPager).setSwipePageEnabled(value); - } - - [offscreenTabLimitProperty.getDefault](): number { - return this._viewPager.getOffscreenPageLimit(); - } - [offscreenTabLimitProperty.setNative](value: number) { - this._viewPager.setOffscreenPageLimit(value); - } - - [animationEnabledProperty.setNative](value: number) { - (this._viewPager).setAnimationEnabled(value); - } + private _tabsBar: org.nativescript.widgets.TabsBar; + private _viewPager: androidx.viewpager.widget.ViewPager; + private _pagerAdapter: androidx.viewpager.widget.PagerAdapter; + private _androidViewId: number = -1; + public _originalBackground: any; + private _textTransform: TextTransform = 'uppercase'; + private _selectedItemColor: Color; + private _unSelectedItemColor: Color; + public animationEnabled: boolean; + + constructor() { + super(); + tabs.push(new WeakRef(this)); + } + + get _hasFragments(): boolean { + return true; + } + + public onItemsChanged(oldItems: TabContentItem[], newItems: TabContentItem[]): void { + super.onItemsChanged(oldItems, newItems); + + if (oldItems) { + oldItems.forEach((item: TabContentItem, i, arr) => { + (item).index = 0; + (item).tabItemSpec = null; + item.setNativeView(null); + }); + } + } + + public createNativeView() { + initializeNativeClasses(); + // TODO + // if (Trace.isEnabled()) { + // Trace.write("TabView._createUI(" + this + ");", traceCategory); + // } + + const context: android.content.Context = this._context; + const nativeView = new org.nativescript.widgets.GridLayout(context); + const viewPager = new org.nativescript.widgets.TabViewPager(context); + const tabsBar = new TabsBar(context, this); + const lp = new org.nativescript.widgets.CommonLayoutParams(); + const primaryColor = ad.resources.getPaletteColor(PRIMARY_COLOR, context); + let accentColor = getDefaultAccentColor(context); + + lp.row = 1; + + if (this.tabsPosition === 'top') { + nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto)); + nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star)); + + viewPager.setLayoutParams(lp); + } else { + nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star)); + nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto)); + + tabsBar.setLayoutParams(lp); + } + + nativeView.addView(viewPager); + (nativeView).viewPager = viewPager; + + const adapter = new PagerAdapter(this); + viewPager.setAdapter(adapter); + (viewPager).adapter = adapter; + + nativeView.addView(tabsBar); + (nativeView).tabsBar = tabsBar; + + setElevation(nativeView, tabsBar, this.tabsPosition); + + if (accentColor) { + tabsBar.setSelectedIndicatorColors([accentColor]); + } + + if (primaryColor) { + tabsBar.setBackgroundColor(primaryColor); + } + + return nativeView; + } + + public initNativeView(): void { + super.initNativeView(); + if (this._androidViewId < 0) { + this._androidViewId = android.view.View.generateViewId(); + } + + const nativeView: any = this.nativeViewProtected; + this._tabsBar = (nativeView).tabsBar; + + const viewPager = (nativeView).viewPager; + viewPager.setId(this._androidViewId); + this._viewPager = viewPager; + this._pagerAdapter = (viewPager).adapter; + (this._pagerAdapter).owner = this; + } + + public _loadUnloadTabItems(newIndex: number) { + const items = this.items; + if (!items) { + return; + } + + const lastIndex = items.length - 1; + const offsideItems = this.offscreenTabLimit; + + let toUnload = []; + let toLoad = []; + + iterateIndexRange(newIndex, offsideItems, lastIndex, (i) => toLoad.push(i)); + + items.forEach((item, i) => { + const indexOfI = toLoad.indexOf(i); + if (indexOfI < 0) { + toUnload.push(i); + } + }); + + toUnload.forEach((index) => { + const item = items[index]; + if (items[index]) { + item.unloadView(item.content); + } + }); + + const newItem = items[newIndex]; + const selectedView = newItem && newItem.content; + if (selectedView instanceof Frame) { + (selectedView)._pushInFrameStackRecursive(); + } + + toLoad.forEach((index) => { + const item = items[index]; + if (this.isLoaded && items[index]) { + item.loadView(item.content); + } + }); + } + + public onLoaded(): void { + super.onLoaded(); + + if (this._originalBackground) { + this.backgroundColor = null; + this.backgroundColor = this._originalBackground; + this._originalBackground = null; + } + + this.setItems(this.items); + + if (this.tabStrip) { + this.setTabStripItems(this.tabStrip.items); + } + + // this.setAdapterItems(this.items); + } + + public onUnloaded(): void { + super.onUnloaded(); + + this.setItems(null); + this.setTabStripItems(null); + + // this.setAdapterItems(null); + } + + public disposeNativeView() { + this._tabsBar.setItems(null, null); + (this._pagerAdapter).owner = null; + this._pagerAdapter = null; + + this._tabsBar = null; + this._viewPager = null; + super.disposeNativeView(); + } + + public _onRootViewReset(): void { + super._onRootViewReset(); + + // call this AFTER the super call to ensure descendants apply their rootview-reset logic first + // i.e. in a scenario with tab frames let the frames cleanup their fragments first, and then + // cleanup the tab fragments to avoid + // android.content.res.Resources$NotFoundException: Unable to find resource ID #0xfffffff6 + this.disposeCurrentFragments(); + } + + private disposeCurrentFragments(): void { + const fragmentManager = this._getFragmentManager(); + const transaction = fragmentManager.beginTransaction(); + for (let fragment of >fragmentManager.getFragments().toArray()) { + transaction.remove(fragment); + } + transaction.commitNowAllowingStateLoss(); + } + + private shouldUpdateAdapter(items: Array) { + if (!this._pagerAdapter) { + return false; + } + + const currentPagerAdapterItems = (this._pagerAdapter).items; + + // if both values are null, should not update + if (!items && !currentPagerAdapterItems) { + return false; + } + + // if one value is null, should update + if (!items || !currentPagerAdapterItems) { + return true; + } + + // if both are Arrays but length doesn't match, should update + if (items.length !== currentPagerAdapterItems.length) { + return true; + } + + const matchingItems = currentPagerAdapterItems.filter((currentItem) => { + return !!items.filter((item) => { + return item._domId === currentItem._domId; + })[0]; + }); + + // if both are Arrays and length matches, but not all items are the same, should update + if (matchingItems.length !== items.length) { + return true; + } + + // if both are Arrays and length matches and all items are the same, should not update + return false; + } + + private setItems(items: Array) { + if (this.shouldUpdateAdapter(items)) { + (this._pagerAdapter).items = items; + + if (items && items.length) { + items.forEach((item: TabContentItem, i) => { + (item).index = i; + }); + } + + this._pagerAdapter.notifyDataSetChanged(); + } + } + + private setTabStripItems(items: Array) { + const length = items ? items.length : 0; + if (length === 0) { + this._tabsBar.setItems(null, null); + + return; + } + + const tabItems = new Array(); + items.forEach((tabStripItem: TabStripItem, i, arr) => { + tabStripItem._index = i; + const tabItemSpec = this.createTabItemSpec(tabStripItem); + (tabStripItem).tabItemSpec = tabItemSpec; + tabItems.push(tabItemSpec); + }); + + const tabsBar = this._tabsBar; + tabsBar.setItems(tabItems, this._viewPager); + this.tabStrip.setNativeView(tabsBar); + items.forEach((item, i, arr) => { + const tv = tabsBar.getTextViewForItemAt(i); + item.setNativeView(tv); + this._setItemColor(item); + }); + } + + 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(); + + if (tabStripItem.isLoaded) { + const nestedLabel = tabStripItem.label; + let title = nestedLabel.text; + + // TEXT-TRANSFORM + const textTransform = this.getItemLabelTextTransform(tabStripItem); + title = getTransformedText(title, textTransform); + tabItemSpec.title = title; + + // BACKGROUND-COLOR + const backgroundColor = tabStripItem.style.backgroundColor; + tabItemSpec.backgroundColor = backgroundColor ? backgroundColor.android : this.getTabBarBackgroundArgbColor(); + + // COLOR + let itemColor = this.selectedIndex === tabStripItem._index ? this._selectedItemColor : this._unSelectedItemColor; + const color = itemColor || nestedLabel.style.color; + tabItemSpec.color = color && color.android; + + // FONT + const fontInternal = nestedLabel.style.fontInternal; + if (fontInternal) { + tabItemSpec.fontSize = fontInternal.fontSize; + tabItemSpec.typeFace = fontInternal.getAndroidTypeface(); + } + + // ICON + const iconSource = tabStripItem.image && tabStripItem.image.src; + if (iconSource) { + const icon = this.getIcon(tabStripItem, itemColor); + + if (icon) { + // 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; + } else { + // TODO: + // traceMissingIcon(iconSource); + } + } + } + + return tabItemSpec; + } + + private getIcon(tabStripItem: TabStripItem, color?: Color): android.graphics.drawable.BitmapDrawable { + const iconSource = tabStripItem.image && tabStripItem.image.src; + if (!iconSource) { + return null; + } + + let is: ImageSource; + if (isFontIconURI(iconSource)) { + const fontIconCode = iconSource.split('//')[1]; + const target = tabStripItem.image ? tabStripItem.image : tabStripItem; + const font = target.style.fontInternal; + if (!color) { + color = target.style.color; + } + is = ImageSource.fromFontIconCodeSync(fontIconCode, font, color); + } else { + is = ImageSource.fromFileOrResourceSync(iconSource); + } + + let imageDrawable: android.graphics.drawable.BitmapDrawable; + if (is && is.android) { + let image = is.android; + + if (this.tabStrip && this.tabStrip.isIconSizeFixed) { + image = this.getFixedSizeIcon(image); + } + + imageDrawable = new android.graphics.drawable.BitmapDrawable(appResources, image); + } else { + // TODO + // traceMissingIcon(iconSource); + } + + return imageDrawable; + } + + private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap { + const inWidth = image.getWidth(); + const inHeight = image.getHeight(); + + const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); + + const widthPixels = iconSpecSize.width * layout.getDisplayDensity(); + const heightPixels = iconSpecSize.height * layout.getDisplayDensity(); + + const scaledImage = android.graphics.Bitmap.createScaledBitmap(image, widthPixels, heightPixels, true); + + return scaledImage; + } + + // private setAdapterItems(items: Array) { + // if (this.shouldUpdateAdapter(items)) { + // (this._pagerAdapter).items = items; + + // const length = items ? items.length : 0; + // if (length === 0) { + // this._tabLayout.setItems(null, null); + // this._pagerAdapter.notifyDataSetChanged(); + // 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); + // }); + + // this._pagerAdapter.notifyDataSetChanged(); + // } + // } + + public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) { + this._tabsBar.updateItemAt(index, spec); + } + + public getTabBarBackgroundColor(): android.graphics.drawable.Drawable { + return this._tabsBar.getBackground(); + } + + public setTabBarBackgroundColor(value: android.graphics.drawable.Drawable | Color): void { + if (value instanceof Color) { + this._tabsBar.setBackgroundColor(value.android); + } else { + this._tabsBar.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources())); + } + + this.updateTabStripItems(); + } + + private updateTabStripItems(): void { + this.tabStrip.items.forEach((tabStripItem: TabStripItem) => { + if (tabStripItem.nativeView) { + const tabItemSpec = this.createTabItemSpec(tabStripItem); + this.updateAndroidItemAt(tabStripItem._index, tabItemSpec); + } + }); + } + + public getTabBarHighlightColor(): number { + return getDefaultAccentColor(this._context); + } + + public setTabBarHighlightColor(value: number | Color) { + const color = value instanceof Color ? value.android : value; + this._tabsBar.setSelectedIndicatorColors([color]); + } + + private setItemsColors(items: Array): void { + items.forEach((item) => { + if (item.nativeView) { + this._setItemColor(item); + } + }); + } + + public getTabBarSelectedItemColor(): Color { + return this._selectedItemColor; + } + + public setTabBarSelectedItemColor(value: Color) { + this._selectedItemColor = value; + this.setItemsColors(this.tabStrip.items); + } + + public getTabBarUnSelectedItemColor(): Color { + return this._unSelectedItemColor; + } + + public setTabBarUnSelectedItemColor(value: Color) { + this._unSelectedItemColor = value; + this.setItemsColors(this.tabStrip.items); + } + + private updateItem(tabStripItem: TabStripItem): void { + // TODO: Should figure out a way to do it directly with the the nativeView + const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem); + const tabItemSpec = this.createTabItemSpec(tabStripItem); + this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec); + } + + public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void { + this.updateItem(tabStripItem); + } + + public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void { + this.updateItem(tabStripItem); + } + + public _setItemColor(tabStripItem: TabStripItem) { + const itemColor = tabStripItem._index === this.selectedIndex ? this._selectedItemColor : this._unSelectedItemColor; + if (!itemColor) { + return; + } + + // set label color + tabStripItem.nativeViewProtected.setTextColor(itemColor.android); + + // set icon color + this.setIconColor(tabStripItem, itemColor); + } + + private setIconColor(tabStripItem: TabStripItem, color?: Color) { + const tabBarItem = this._tabsBar.getViewForItemAt(tabStripItem._index); + + const drawable = this.getIcon(tabStripItem, color); + const imgView = tabBarItem.getChildAt(0); + imgView.setImageDrawable(drawable); + if (color) { + imgView.setColorFilter(color.android); + } + } + + public setTabBarItemColor(tabStripItem: TabStripItem, value: number | Color): void { + const itemColor = tabStripItem._index === this.selectedIndex ? this._selectedItemColor : this._unSelectedItemColor; + if (itemColor) { + // the itemColor is set through the selectedItemColor and unSelectedItemColor properties + // so it does not respect the css color + return; + } + + const androidColor = value instanceof Color ? value.android : value; + tabStripItem.nativeViewProtected.setTextColor(androidColor); + } + + public setTabBarIconColor(tabStripItem: TabStripItem, value: number | Color): void { + const itemColor = tabStripItem._index === this.selectedIndex ? this._selectedItemColor : this._unSelectedItemColor; + if (itemColor) { + // the itemColor is set through the selectedItemColor and unSelectedItemColor properties + // so it does not respect the css color + return; + } + + this.setIconColor(tabStripItem); + } + + public setTabBarIconSource(tabStripItem: TabStripItem, value: number | Color): void { + this.updateItem(tabStripItem); + } + + public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { + if (value.fontSize) { + tabStripItem.nativeViewProtected.setTextSize(value.fontSize); + } + 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) { + // TODO + // if (Trace.isEnabled()) { + // Trace.write("TabView this._viewPager.setCurrentItem(" + value + ", " + smoothScroll + ");", traceCategory); + // } + this._viewPager.setCurrentItem(value, this.animationEnabled); + } + + [itemsProperty.getDefault](): TabContentItem[] { + return null; + } + [itemsProperty.setNative](value: TabContentItem[]) { + this.setItems(value); + selectedIndexProperty.coerce(this); + } + + [tabStripProperty.getDefault](): TabStrip { + return null; + } + [tabStripProperty.setNative](value: TabStrip) { + this.setTabStripItems(value.items); + } + + [swipeEnabledProperty.getDefault](): boolean { + // TODO: create native method and get native? + return true; + } + [swipeEnabledProperty.setNative](value: boolean) { + (this._viewPager).setSwipePageEnabled(value); + } + + [offscreenTabLimitProperty.getDefault](): number { + return this._viewPager.getOffscreenPageLimit(); + } + [offscreenTabLimitProperty.setNative](value: number) { + this._viewPager.setOffscreenPageLimit(value); + } + + [animationEnabledProperty.setNative](value: number) { + (this._viewPager).setAnimationEnabled(value); + } } function tryCloneDrawable(value: android.graphics.drawable.Drawable, resources: android.content.res.Resources): android.graphics.drawable.Drawable { diff --git a/nativescript-core/ui/tabs/index.ios.ts b/nativescript-core/ui/tabs/index.ios.ts index 83b862c2e..26e72f0ca 100644 --- a/nativescript-core/ui/tabs/index.ios.ts +++ b/nativescript-core/ui/tabs/index.ios.ts @@ -455,779 +455,777 @@ function updateTitleAndIconPositions(tabStripItem: TabStripItem, tabBarItem: UIT } export class Tabs extends TabsBase { - public nativeViewProtected: UIView; - public selectedIndex: number; - // public swipeEnabled: boolean; - // public offscreenTabLimit: number; - // public tabsPosition: "top" | "bottom"; - public _canSelectItem: boolean; - public isLoaded: boolean; - public viewController: UIPageViewControllerImpl; - public items: TabContentItem[]; - public _ios: UIPageViewControllerImpl; - public viewControllers: UIViewController[]; - public tabBarItems: UITabBarItem[]; - private _currentNativeSelectedIndex: number; - private _dataSource: UIPageViewControllerDataSourceImpl; - private _delegate: UIPageViewControllerDelegateImpl; - // private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl; - private _iconsCache = {}; - private _backgroundIndicatorColor: UIColor; - public _defaultItemBackgroundColor: UIColor; - private _selectedItemColor: Color; - private _unSelectedItemColor: Color; - public animationEnabled: boolean; - - constructor() { - super(); - - this.viewController = this._ios = UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); //alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null);; - } - - createNativeView() { - return this._ios.view; - } - - initNativeView() { - super.initNativeView(); - this._dataSource = UIPageViewControllerDataSourceImpl.initWithOwner(new WeakRef(this)); - this._delegate = UIPageViewControllerDelegateImpl.initWithOwner(new WeakRef(this)); - } - - disposeNativeView() { - this._dataSource = null; - this._delegate = null; - this._ios.tabBarDelegate = null; - this._ios.tabBar = null; - super.disposeNativeView(); - } - - // TODO - // @profile() - 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(); - } - - this._ios.dataSource = this._dataSource; - this._ios.delegate = this._delegate; - } - - public onUnloaded() { - this._ios.dataSource = null; - this._ios.delegate = null; - super.onUnloaded(); - } - - get ios(): UIPageViewController { - return this._ios; - } - - public layoutNativeView(left: number, top: number, right: number, bottom: number): void { - // - } - - public _setNativeViewFrame(nativeView: UIView, frame: CGRect) { - // - } - - public onSelectedIndexChanged(oldIndex: number, newIndex: number): void { - const items = this.items; - if (!items) { - return; - } - - 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].content; - if (selectedView instanceof Frame) { - selectedView._pushInFrameStackRecursive(); - } - - newItem.canBeLoaded = true; - newItem.loadView(newItem.content); - } - - const tabStripItems = this.tabStrip && this.tabStrip.items; - if (tabStripItems) { - if (tabStripItems[newIndex]) { - tabStripItems[newIndex]._emit(TabStripItem.selectEvent); - this.updateItemColors(tabStripItems[newIndex]); - } - - if (tabStripItems[oldIndex]) { - tabStripItems[oldIndex]._emit(TabStripItem.unselectEvent); - this.updateItemColors(tabStripItems[oldIndex]); - } - } - - this._loadUnloadTabItems(newIndex); - - super.onSelectedIndexChanged(oldIndex, newIndex); - } - - public _loadUnloadTabItems(newIndex: number) { - const items = this.items; - if (!items) { - return; - } - - const lastIndex = items.length - 1; - const offsideItems = this.offscreenTabLimit; - - let toUnload = []; - let toLoad = []; - - iterateIndexRange(newIndex, offsideItems, lastIndex, (i) => toLoad.push(i)); - - items.forEach((item, i) => { - const indexOfI = toLoad.indexOf(i); - if (indexOfI < 0) { - toUnload.push(i); - } - }); - - toUnload.forEach(index => { - const item = items[index]; - if (items[index]) { - item.unloadView(item.content); - } - }); - - const newItem = items[newIndex]; - const selectedView = newItem && newItem.content; - if (selectedView instanceof Frame) { - selectedView._pushInFrameStackRecursive(); - } - - toLoad.forEach(index => { - const item = items[index]; - if (this.isLoaded && items[index]) { - item.loadView(item.content); - } - }); - } - - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { - const width = layout.getMeasureSpecSize(widthMeasureSpec); - const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); - - const height = layout.getMeasureSpecSize(heightMeasureSpec); - const heightMode = layout.getMeasureSpecMode(heightMeasureSpec); - - const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0); - const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0); - - this.setMeasuredDimension(widthAndState, heightAndState); - } - - public _onViewControllerShown(viewController: UIViewController) { - // This method could be called with the moreNavigationController or its list controller, so we have to check. - // TODO - // if (Trace.isEnabled()) { - // Trace.write("TabView._onViewControllerShown(" + viewController + ");", Trace.categories.Debug); - // } - if (this._ios.viewControllers && this._ios.viewControllers.containsObject(viewController)) { - this.selectedIndex = this._ios.viewControllers.indexOfObject(viewController); - } else { - // TODO - // if (Trace.isEnabled()) { - // Trace.write("TabView._onViewControllerShown: viewController is not one of our viewControllers", Trace.categories.Debug); - // } - } - } - - private getViewController(item: TabContentItem): UIViewController { - let newController: UIViewController = item.content ? item.content.viewController : null; - - if (newController) { - (item).setViewController(newController, newController.view); - - return newController; - } - - if (item.content.ios instanceof UIViewController) { - newController = item.content.ios; - (item).setViewController(newController, newController.view); - } else if (item.content.ios && item.content.ios.controller instanceof UIViewController) { - newController = item.content.ios.controller; - (item).setViewController(newController, newController.view); - } else { - newController = IOSHelper.UILayoutViewController.initWithOwner(new WeakRef(item.content)) as UIViewController; - newController.view.addSubview(item.content.nativeViewProtected); - item.content.viewController = newController; - (item).setViewController(newController, item.content.nativeViewProtected); - } - - return newController; - } - - public _setCanBeLoaded(index: number) { - const items = this.items; - if (!this.items) { - return; - } - - const lastIndex = items.length - 1; - const offsideItems = this.offscreenTabLimit; - - iterateIndexRange(index, offsideItems, lastIndex, (i) => { - if (items[i]) { - (items[i]).canBeLoaded = true; - } - }); - } - - private setViewControllers(items: TabContentItem[]) { - const length = items ? items.length : 0; - if (length === 0) { - this.viewControllers = null; - - return; - } - - const viewControllers = []; - const tabBarItems = []; - - 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); - - this.setViewTextAttributes(tabStripItem.label, i === this.selectedIndex); - - controller.tabBarItem = tabBarItem; - tabStripItem._index = i; - tabBarItems.push(tabBarItem); - tabStripItem.setNativeView(tabBarItem); - } - - item.canBeLoaded = true; - viewControllers.push(controller); - }); - - this.setItemImages(); - - this.viewControllers = viewControllers; - this.tabBarItems = tabBarItems; - - if (this.viewController && this.viewController.tabBar) { - this.viewController.tabBar.itemAppearance = this.getTabBarItemAppearance(); - this.viewController.tabBar.items = NSArray.arrayWithArray(this.tabBarItems); - // TODO: investigate why this call is necessary to actually toggle item appearance - this.viewController.tabBar.sizeToFit(); - if (this.selectedIndex) { - this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[this.selectedIndex], false); - } - } - } - - private setItemImages() { - if (this._selectedItemColor || this._unSelectedItemColor) { - if (this.tabStrip && this.tabStrip.items) { - this.tabStrip.items.forEach(item => { - if (this._unSelectedItemColor && item.nativeView) { - item.nativeView.image = this.getIcon(item, this._unSelectedItemColor); - } - if (this._selectedItemColor && item.nativeView) { - if (this.selectedIndex === item._index) { - item.nativeView.image = this.getIcon(item, this._selectedItemColor); - } - } - }); - } - } - } - - private updateAllItemsColors() { - this._defaultItemBackgroundColor = null; - this.setItemColors(); - if (this.tabStrip && this.tabStrip.items) { - this.tabStrip.items.forEach(tabStripItem => { - this.updateItemColors(tabStripItem); - }); - } - } - - private updateItemColors(tabStripItem: TabStripItem): void { - updateBackgroundPositions(this.tabStrip, tabStripItem); - this.setIconColor(tabStripItem, true); - } - - private createTabBarItem(item: TabStripItem, index: number): UITabBarItem { - let image: UIImage; - let title: string; - - if (item.isLoaded) { - image = this.getIcon(item); - title = item.label.text; - - if (!this.tabStrip._hasImage) { - this.tabStrip._hasImage = !!image; - } - - if (!this.tabStrip._hasTitle) { - this.tabStrip._hasTitle = !!title; - } - } - - const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(title, image, index); - - return tabBarItem; - } - - private getTabBarItemAppearance(): MDCTabBarItemAppearance { - let itemAppearance; - if (this.tabStrip && this.tabStrip._hasImage && this.tabStrip._hasTitle) { - itemAppearance = MDCTabBarItemAppearance.TitledImages; - } else if (this.tabStrip && this.tabStrip._hasImage) { - itemAppearance = MDCTabBarItemAppearance.Images; - } else { - itemAppearance = MDCTabBarItemAppearance.Titles; - } - - return itemAppearance; - } - - private getIconRenderingMode(): UIImageRenderingMode { - switch (this.tabStrip && this.tabStrip.iosIconRenderingMode) { - case "alwaysOriginal": - return UIImageRenderingMode.AlwaysOriginal; - case "alwaysTemplate": - return UIImageRenderingMode.AlwaysTemplate; - case "automatic": - default: - const hasItemColor = this._selectedItemColor || this._unSelectedItemColor; - - return hasItemColor ? UIImageRenderingMode.AlwaysTemplate : UIImageRenderingMode.AlwaysOriginal; - } - } - - private getIcon(tabStripItem: TabStripItem, color?: Color): 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; - } - - const target = tabStripItem.image; - const font = target.style.fontInternal || Font.default; - if (!color) { - 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 { - is = ImageSource.fromFileOrResourceSync(iconSource); - } - - if (is && is.ios) { - image = is.ios; - - if (this.tabStrip && this.tabStrip.isIconSizeFixed) { - image = this.getFixedSizeIcon(image); - } - - let renderingMode: UIImageRenderingMode = UIImageRenderingMode.Automatic; - if (!isFontIcon) { - renderingMode = this.getIconRenderingMode(); - } - const originalRenderedImage = image.imageWithRenderingMode(renderingMode); - this._iconsCache[iconTag] = originalRenderedImage; - image = originalRenderedImage; - } - } - - return image; - } - - private getFixedSizeIcon(image: UIImage): UIImage { - const inWidth = image.size.width; - const inHeight = image.size.height; - - const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); - - const widthPts = iconSpecSize.width; - const heightPts = iconSpecSize.height; - - UIGraphicsBeginImageContextWithOptions({ width: widthPts, height: heightPts }, false, layout.getDisplayDensity()); - image.drawInRect(CGRectMake(0, 0, widthPts, heightPts)); - let resultImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return resultImage; - } - - public getTabBarBackgroundColor(): UIColor { - return this._ios.tabBar.barTintColor; - } - - public setTabBarBackgroundColor(value: UIColor | Color): void { - this._ios.tabBar.barTintColor = value instanceof Color ? value.ios : value; - this.updateAllItemsColors(); - } - - 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 { - // to find out whether the current tab strip item is active (has style with :active selector applied) - // we need to check whether its _visualState is equal to "highlighted" as when changing tabs - // we first go through setTabBarItemBackgroundColor thice, once before setting the "highlighted" state - // and once after that, but if the "highlighted" state is not set we cannot get the backgroundColor - // set using :active selector - 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); - - // As we cannot implement selected item background color in Tabs we are using the Indicator for this - // To be able to detect that there are two different background colors (one for selected and one for not selected item) - // we are checking whether the current item is not selected and higlighted and we store the value of its - // background color to _defaultItemBackgroundColor and later if we need to process a selected and highlighted item - // we are comparing it's backgroun color to the default one and if there's a difference - // we are changing the selectionIndicatorTemplate from underline to the whole item - // in that mode we are not able to show the indicator as it is used for the background of the selected item - - 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 { - this.setViewTextAttributes(tabStripItem.label); - } - - private setItemColors(): void { - if (this._selectedItemColor) { - this.viewController.tabBar.selectedItemTintColor = this._selectedItemColor.ios; - } - if (this._unSelectedItemColor) { - this.viewController.tabBar.unselectedItemTintColor = this._unSelectedItemColor.ios; - } - } - - private setIconColor(tabStripItem: TabStripItem, forceReload: boolean = false): void { - // if there is no change in the css color and there is no item color set - // we don't need to reload the icon - if (!forceReload && !this._selectedItemColor && !this._unSelectedItemColor) { - return; - } - - let image: UIImage; - - // if selectedItemColor or unSelectedItemColor is set we don't respect the color from the style - const tabStripColor = (this.selectedIndex === tabStripItem._index) ? this._selectedItemColor : this._unSelectedItemColor; - image = this.getIcon(tabStripItem, tabStripColor); - - tabStripItem.nativeView.image = image; - } - - public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { - this.setIconColor(tabStripItem, true); - } - - public setTabBarIconSource(tabStripItem: TabStripItem, value: UIColor | Color): void { - this.updateItemColors(tabStripItem); - } - - public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { - this.setViewTextAttributes(tabStripItem.label); - } - - public getTabBarFontInternal(): UIFont { - return this._ios.tabBar.unselectedItemTitleFont; - } - - public setTabBarFontInternal(value: Font): void { - const defaultTabItemFontSize = 10; - const tabItemFontSize = this.tabStrip.style.fontSize || defaultTabItemFontSize; - const font: UIFont = (this.tabStrip.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); - - this._ios.tabBar.unselectedItemTitleFont = font; - this._ios.tabBar.selectedItemTitleFont = font; - } - - public getTabBarTextTransform(): TextTransform { - 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 { - if (value === "none") { - 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); - } - - public setTabBarColor(value: UIColor | Color): void { - const nativeColor = value instanceof Color ? value.ios : value; - this._ios.tabBar.setTitleColorForState(nativeColor, MDCTabBarItemState.Normal); - this._ios.tabBar.setTitleColorForState(nativeColor, MDCTabBarItemState.Selected); - } - - 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 getTabBarSelectedItemColor(): Color { - return this._selectedItemColor; - } - - public setTabBarSelectedItemColor(value: Color) { - this._selectedItemColor = value; - this.updateAllItemsColors(); - } - - public getTabBarUnSelectedItemColor(): Color { - return this._unSelectedItemColor; - } - - public setTabBarUnSelectedItemColor(value: Color) { - this._unSelectedItemColor = value; - this.updateAllItemsColors(); - } - - private visitFrames(view: ViewBase, operation: (frame: Frame) => {}) { - if (view instanceof Frame) { - operation(view); - } - view.eachChild(child => { - this.visitFrames(child, operation); - - return true; - }); - } - - [selectedIndexProperty.setNative](value: number) { - // TODO - // if (Trace.isEnabled()) { - // Trace.write("TabView._onSelectedIndexPropertyChangedSetNativeValue(" + value + ")", Trace.categories.Debug); - // } - - if (value > -1) { - const item = this.items[value]; - const controllers = NSMutableArray.alloc().initWithCapacity(1); - - let itemController = (item).__controller; - - // if (!itemController) { - // itemController = this.getViewController(item); - // } - - controllers.addObject(itemController); - - let navigationDirection = UIPageViewControllerNavigationDirection.Forward; - - if (this._currentNativeSelectedIndex && this._currentNativeSelectedIndex > value) { - navigationDirection = UIPageViewControllerNavigationDirection.Reverse; - } - - this._currentNativeSelectedIndex = value; - - // do not make layout changes while the animation is in progress https://stackoverflow.com/a/47031524/613113 - this.visitFrames(item, frame => frame._animationInProgress = true); - - invokeOnRunLoop( () => this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, this.animationEnabled, (finished: boolean) => { - this.visitFrames(item, frame => frame._animationInProgress = false); - if (finished) { - // HACK: UIPageViewController fix; see https://stackoverflow.com/a/17330606 - invokeOnRunLoop(() => this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, false, null)); - - this._canSelectItem = true; - this._setCanBeLoaded(value); - this._loadUnloadTabItems(value); - } - })); - - if (this.tabBarItems && this.tabBarItems.length && this.viewController && this.viewController.tabBar) { - this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[value], this.animationEnabled); - } - // TODO: - // (this._ios)._willSelectViewController = this._ios.viewControllers[value]; - // this._ios.selectedIndex = value; - } - } - - [itemsProperty.getDefault](): TabContentItem[] { - return null; - } - [itemsProperty.setNative](value: TabContentItem[]) { - if (value) { - value.forEach((item: TabContentItem, i) => { - (item).index = i; - }); - } - - this.setViewControllers(value); - selectedIndexProperty.coerce(this); - } - - [tabStripProperty.getDefault](): TabStrip { - return null; - } - [tabStripProperty.setNative](value: TabStrip) { - 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; - } - - private setViewTextAttributes(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 || Font.default).getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); - - this.viewController.tabBar.unselectedItemTitleFont = font; - this.viewController.tabBar.selectedItemTitleFont = font; - - const tabItemTextColor = view.style.color; - const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null; - - if (textColor) { - this.viewController.tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Normal); - this.viewController.tabBar.setImageTintColorForState(textColor, MDCTabBarItemState.Normal); - - if (setSelected) { - this.viewController.tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Selected); - this.viewController.tabBar.setImageTintColorForState(textColor, MDCTabBarItemState.Selected); - } - } - - if (this._selectedItemColor) { - this.viewController.tabBar.selectedItemTintColor = this._selectedItemColor.ios; - } - if (this._unSelectedItemColor) { - this.viewController.tabBar.unselectedItemTintColor = this._unSelectedItemColor.ios; - } - } + public nativeViewProtected: UIView; + public selectedIndex: number; + // public swipeEnabled: boolean; + // public offscreenTabLimit: number; + // public tabsPosition: "top" | "bottom"; + public _canSelectItem: boolean; + public isLoaded: boolean; + public viewController: UIPageViewControllerImpl; + public items: TabContentItem[]; + public _ios: UIPageViewControllerImpl; + public viewControllers: UIViewController[]; + public tabBarItems: UITabBarItem[]; + private _currentNativeSelectedIndex: number; + private _dataSource: UIPageViewControllerDataSourceImpl; + private _delegate: UIPageViewControllerDelegateImpl; + // private _moreNavigationControllerDelegate: UINavigationControllerDelegateImpl; + private _iconsCache = {}; + private _backgroundIndicatorColor: UIColor; + public _defaultItemBackgroundColor: UIColor; + private _selectedItemColor: Color; + private _unSelectedItemColor: Color; + public animationEnabled: boolean; + + constructor() { + super(); + + this.viewController = this._ios = UIPageViewControllerImpl.initWithOwner(new WeakRef(this)); //alloc().initWithTransitionStyleNavigationOrientationOptions(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal, null);; + } + + createNativeView() { + return this._ios.view; + } + + initNativeView() { + super.initNativeView(); + this._dataSource = UIPageViewControllerDataSourceImpl.initWithOwner(new WeakRef(this)); + this._delegate = UIPageViewControllerDelegateImpl.initWithOwner(new WeakRef(this)); + } + + disposeNativeView() { + this._dataSource = null; + this._delegate = null; + this._ios.tabBarDelegate = null; + this._ios.tabBar = null; + super.disposeNativeView(); + } + + // TODO + // @profile() + 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(); + } + + this._ios.dataSource = this._dataSource; + this._ios.delegate = this._delegate; + } + + public onUnloaded() { + this._ios.dataSource = null; + this._ios.delegate = null; + super.onUnloaded(); + } + + get ios(): UIPageViewController { + return this._ios; + } + + public layoutNativeView(left: number, top: number, right: number, bottom: number): void { + // + } + + public _setNativeViewFrame(nativeView: UIView, frame: CGRect) { + // + } + + public onSelectedIndexChanged(oldIndex: number, newIndex: number): void { + const items = this.items; + if (!items) { + return; + } + + 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].content; + if (selectedView instanceof Frame) { + selectedView._pushInFrameStackRecursive(); + } + + newItem.canBeLoaded = true; + newItem.loadView(newItem.content); + } + + const tabStripItems = this.tabStrip && this.tabStrip.items; + if (tabStripItems) { + if (tabStripItems[newIndex]) { + tabStripItems[newIndex]._emit(TabStripItem.selectEvent); + this.updateItemColors(tabStripItems[newIndex]); + } + + if (tabStripItems[oldIndex]) { + tabStripItems[oldIndex]._emit(TabStripItem.unselectEvent); + this.updateItemColors(tabStripItems[oldIndex]); + } + } + + this._loadUnloadTabItems(newIndex); + + super.onSelectedIndexChanged(oldIndex, newIndex); + } + + public _loadUnloadTabItems(newIndex: number) { + const items = this.items; + if (!items) { + return; + } + + const lastIndex = items.length - 1; + const offsideItems = this.offscreenTabLimit; + + let toUnload = []; + let toLoad = []; + + iterateIndexRange(newIndex, offsideItems, lastIndex, (i) => toLoad.push(i)); + + items.forEach((item, i) => { + const indexOfI = toLoad.indexOf(i); + if (indexOfI < 0) { + toUnload.push(i); + } + }); + + toUnload.forEach((index) => { + const item = items[index]; + if (items[index]) { + item.unloadView(item.content); + } + }); + + const newItem = items[newIndex]; + const selectedView = newItem && newItem.content; + if (selectedView instanceof Frame) { + selectedView._pushInFrameStackRecursive(); + } + + toLoad.forEach((index) => { + const item = items[index]; + if (this.isLoaded && items[index]) { + item.loadView(item.content); + } + }); + } + + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + const width = layout.getMeasureSpecSize(widthMeasureSpec); + const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); + + const height = layout.getMeasureSpecSize(heightMeasureSpec); + const heightMode = layout.getMeasureSpecMode(heightMeasureSpec); + + const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0); + const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0); + + this.setMeasuredDimension(widthAndState, heightAndState); + } + + public _onViewControllerShown(viewController: UIViewController) { + // This method could be called with the moreNavigationController or its list controller, so we have to check. + // TODO + // if (Trace.isEnabled()) { + // Trace.write("TabView._onViewControllerShown(" + viewController + ");", Trace.categories.Debug); + // } + if (this._ios.viewControllers && this._ios.viewControllers.containsObject(viewController)) { + this.selectedIndex = this._ios.viewControllers.indexOfObject(viewController); + } else { + // TODO + // if (Trace.isEnabled()) { + // Trace.write("TabView._onViewControllerShown: viewController is not one of our viewControllers", Trace.categories.Debug); + // } + } + } + + private getViewController(item: TabContentItem): UIViewController { + let newController: UIViewController = item.content ? item.content.viewController : null; + + if (newController) { + (item).setViewController(newController, newController.view); + + return newController; + } + + if (item.content.ios instanceof UIViewController) { + newController = item.content.ios; + (item).setViewController(newController, newController.view); + } else if (item.content.ios && item.content.ios.controller instanceof UIViewController) { + newController = item.content.ios.controller; + (item).setViewController(newController, newController.view); + } else { + newController = IOSHelper.UILayoutViewController.initWithOwner(new WeakRef(item.content)) as UIViewController; + newController.view.addSubview(item.content.nativeViewProtected); + item.content.viewController = newController; + (item).setViewController(newController, item.content.nativeViewProtected); + } + + return newController; + } + + public _setCanBeLoaded(index: number) { + const items = this.items; + if (!this.items) { + return; + } + + const lastIndex = items.length - 1; + const offsideItems = this.offscreenTabLimit; + + iterateIndexRange(index, offsideItems, lastIndex, (i) => { + if (items[i]) { + (items[i]).canBeLoaded = true; + } + }); + } + + private setViewControllers(items: TabContentItem[]) { + const length = items ? items.length : 0; + if (length === 0) { + this.viewControllers = null; + + return; + } + + const viewControllers = []; + const tabBarItems = []; + + 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); + + this.setViewTextAttributes(tabStripItem.label, i === this.selectedIndex); + + controller.tabBarItem = tabBarItem; + tabStripItem._index = i; + tabBarItems.push(tabBarItem); + tabStripItem.setNativeView(tabBarItem); + } + + item.canBeLoaded = true; + viewControllers.push(controller); + }); + + this.setItemImages(); + + this.viewControllers = viewControllers; + this.tabBarItems = tabBarItems; + + if (this.viewController && this.viewController.tabBar) { + this.viewController.tabBar.itemAppearance = this.getTabBarItemAppearance(); + this.viewController.tabBar.items = NSArray.arrayWithArray(this.tabBarItems); + // TODO: investigate why this call is necessary to actually toggle item appearance + this.viewController.tabBar.sizeToFit(); + if (this.selectedIndex) { + this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[this.selectedIndex], false); + } + } + } + + private setItemImages() { + if (this._selectedItemColor || this._unSelectedItemColor) { + if (this.tabStrip && this.tabStrip.items) { + this.tabStrip.items.forEach((item) => { + if (this._unSelectedItemColor && item.nativeView) { + item.nativeView.image = this.getIcon(item, this._unSelectedItemColor); + } + if (this._selectedItemColor && item.nativeView) { + if (this.selectedIndex === item._index) { + item.nativeView.image = this.getIcon(item, this._selectedItemColor); + } + } + }); + } + } + } + + private updateAllItemsColors() { + this._defaultItemBackgroundColor = null; + this.setItemColors(); + if (this.tabStrip && this.tabStrip.items) { + this.tabStrip.items.forEach((tabStripItem) => { + this.updateItemColors(tabStripItem); + }); + } + } + + private updateItemColors(tabStripItem: TabStripItem): void { + updateBackgroundPositions(this.tabStrip, tabStripItem); + this.setIconColor(tabStripItem, true); + } + + private createTabBarItem(item: TabStripItem, index: number): UITabBarItem { + let image: UIImage; + let title: string; + + if (item.isLoaded) { + image = this.getIcon(item); + title = item.label.text; + + if (!this.tabStrip._hasImage) { + this.tabStrip._hasImage = !!image; + } + + if (!this.tabStrip._hasTitle) { + this.tabStrip._hasTitle = !!title; + } + } + + const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag(title, image, index); + + return tabBarItem; + } + + private getTabBarItemAppearance(): MDCTabBarItemAppearance { + let itemAppearance; + if (this.tabStrip && this.tabStrip._hasImage && this.tabStrip._hasTitle) { + itemAppearance = MDCTabBarItemAppearance.TitledImages; + } else if (this.tabStrip && this.tabStrip._hasImage) { + itemAppearance = MDCTabBarItemAppearance.Images; + } else { + itemAppearance = MDCTabBarItemAppearance.Titles; + } + + return itemAppearance; + } + + private getIconRenderingMode(): UIImageRenderingMode { + switch (this.tabStrip && this.tabStrip.iosIconRenderingMode) { + case 'alwaysOriginal': + return UIImageRenderingMode.AlwaysOriginal; + case 'alwaysTemplate': + return UIImageRenderingMode.AlwaysTemplate; + case 'automatic': + default: + const hasItemColor = this._selectedItemColor || this._unSelectedItemColor; + + return hasItemColor ? UIImageRenderingMode.AlwaysTemplate : UIImageRenderingMode.AlwaysOriginal; + } + } + + private getIcon(tabStripItem: TabStripItem, color?: Color): 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; + } + + const target = tabStripItem.image; + const font = target.style.fontInternal || Font.default; + if (!color) { + 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 { + is = ImageSource.fromFileOrResourceSync(iconSource); + } + + if (is && is.ios) { + image = is.ios; + + if (this.tabStrip && this.tabStrip.isIconSizeFixed) { + image = this.getFixedSizeIcon(image); + } + + let renderingMode: UIImageRenderingMode = UIImageRenderingMode.Automatic; + if (!isFontIcon) { + renderingMode = this.getIconRenderingMode(); + } + const originalRenderedImage = image.imageWithRenderingMode(renderingMode); + this._iconsCache[iconTag] = originalRenderedImage; + image = originalRenderedImage; + } + } + + return image; + } + + private getFixedSizeIcon(image: UIImage): UIImage { + const inWidth = image.size.width; + const inHeight = image.size.height; + + const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight }); + + const widthPts = iconSpecSize.width; + const heightPts = iconSpecSize.height; + + UIGraphicsBeginImageContextWithOptions({ width: widthPts, height: heightPts }, false, layout.getDisplayDensity()); + image.drawInRect(CGRectMake(0, 0, widthPts, heightPts)); + let resultImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return resultImage; + } + + public getTabBarBackgroundColor(): UIColor { + return this._ios.tabBar.barTintColor; + } + + public setTabBarBackgroundColor(value: UIColor | Color): void { + this._ios.tabBar.barTintColor = value instanceof Color ? value.ios : value; + this.updateAllItemsColors(); + } + + 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 { + // to find out whether the current tab strip item is active (has style with :active selector applied) + // we need to check whether its _visualState is equal to "highlighted" as when changing tabs + // we first go through setTabBarItemBackgroundColor thice, once before setting the "highlighted" state + // and once after that, but if the "highlighted" state is not set we cannot get the backgroundColor + // set using :active selector + 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); + + // As we cannot implement selected item background color in Tabs we are using the Indicator for this + // To be able to detect that there are two different background colors (one for selected and one for not selected item) + // we are checking whether the current item is not selected and higlighted and we store the value of its + // background color to _defaultItemBackgroundColor and later if we need to process a selected and highlighted item + // we are comparing it's backgroun color to the default one and if there's a difference + // we are changing the selectionIndicatorTemplate from underline to the whole item + // in that mode we are not able to show the indicator as it is used for the background of the selected item + + 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 { + this.setViewTextAttributes(tabStripItem.label); + } + + private setItemColors(): void { + if (this._selectedItemColor) { + this.viewController.tabBar.selectedItemTintColor = this._selectedItemColor.ios; + } + if (this._unSelectedItemColor) { + this.viewController.tabBar.unselectedItemTintColor = this._unSelectedItemColor.ios; + } + } + + private setIconColor(tabStripItem: TabStripItem, forceReload: boolean = false): void { + // if there is no change in the css color and there is no item color set + // we don't need to reload the icon + if (!forceReload && !this._selectedItemColor && !this._unSelectedItemColor) { + return; + } + + let image: UIImage; + + // if selectedItemColor or unSelectedItemColor is set we don't respect the color from the style + const tabStripColor = this.selectedIndex === tabStripItem._index ? this._selectedItemColor : this._unSelectedItemColor; + image = this.getIcon(tabStripItem, tabStripColor); + + tabStripItem.nativeView.image = image; + } + + public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void { + this.setIconColor(tabStripItem, true); + } + + public setTabBarIconSource(tabStripItem: TabStripItem, value: UIColor | Color): void { + this.updateItemColors(tabStripItem); + } + + public setTabBarItemFontInternal(tabStripItem: TabStripItem, value: Font): void { + this.setViewTextAttributes(tabStripItem.label); + } + + public getTabBarFontInternal(): UIFont { + return this._ios.tabBar.unselectedItemTitleFont; + } + + public setTabBarFontInternal(value: Font): void { + const defaultTabItemFontSize = 10; + const tabItemFontSize = this.tabStrip.style.fontSize || defaultTabItemFontSize; + const font: UIFont = (this.tabStrip.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); + + this._ios.tabBar.unselectedItemTitleFont = font; + this._ios.tabBar.selectedItemTitleFont = font; + } + + public getTabBarTextTransform(): TextTransform { + 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 { + if (value === 'none') { + 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); + } + + public setTabBarColor(value: UIColor | Color): void { + const nativeColor = value instanceof Color ? value.ios : value; + this._ios.tabBar.setTitleColorForState(nativeColor, MDCTabBarItemState.Normal); + this._ios.tabBar.setTitleColorForState(nativeColor, MDCTabBarItemState.Selected); + } + + 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 getTabBarSelectedItemColor(): Color { + return this._selectedItemColor; + } + + public setTabBarSelectedItemColor(value: Color) { + this._selectedItemColor = value; + this.updateAllItemsColors(); + } + + public getTabBarUnSelectedItemColor(): Color { + return this._unSelectedItemColor; + } + + public setTabBarUnSelectedItemColor(value: Color) { + this._unSelectedItemColor = value; + this.updateAllItemsColors(); + } + + private visitFrames(view: ViewBase, operation: (frame: Frame) => {}) { + if (view instanceof Frame) { + operation(view); + } + view.eachChild((child) => { + this.visitFrames(child, operation); + + return true; + }); + } + + [selectedIndexProperty.setNative](value: number) { + // TODO + // if (Trace.isEnabled()) { + // Trace.write("TabView._onSelectedIndexPropertyChangedSetNativeValue(" + value + ")", Trace.categories.Debug); + // } + + if (value > -1) { + const item = this.items[value]; + const controllers = NSMutableArray.alloc().initWithCapacity(1); + + let itemController = (item).__controller; + + // if (!itemController) { + // itemController = this.getViewController(item); + // } + + controllers.addObject(itemController); + + let navigationDirection = UIPageViewControllerNavigationDirection.Forward; + + if (this._currentNativeSelectedIndex && this._currentNativeSelectedIndex > value) { + navigationDirection = UIPageViewControllerNavigationDirection.Reverse; + } + + this._currentNativeSelectedIndex = value; + + // do not make layout changes while the animation is in progress https://stackoverflow.com/a/47031524/613113 + this.visitFrames(item, (frame) => (frame._animationInProgress = true)); + + invokeOnRunLoop(() => + this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, this.animationEnabled, (finished: boolean) => { + this.visitFrames(item, (frame) => (frame._animationInProgress = false)); + if (finished) { + // HACK: UIPageViewController fix; see https://stackoverflow.com/a/17330606 + invokeOnRunLoop(() => this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, false, null)); + + this._canSelectItem = true; + this._setCanBeLoaded(value); + this._loadUnloadTabItems(value); + } + }) + ); + + if (this.tabBarItems && this.tabBarItems.length && this.viewController && this.viewController.tabBar) { + this.viewController.tabBar.setSelectedItemAnimated(this.tabBarItems[value], this.animationEnabled); + } + // TODO: + // (this._ios)._willSelectViewController = this._ios.viewControllers[value]; + // this._ios.selectedIndex = value; + } + } + + [itemsProperty.getDefault](): TabContentItem[] { + return null; + } + [itemsProperty.setNative](value: TabContentItem[]) { + if (value) { + value.forEach((item: TabContentItem, i) => { + (item).index = i; + }); + } + + this.setViewControllers(value); + selectedIndexProperty.coerce(this); + } + + [tabStripProperty.getDefault](): TabStrip { + return null; + } + [tabStripProperty.setNative](value: TabStrip) { + 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; + } + + private setViewTextAttributes(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 || Font.default).getUIFont(UIFont.systemFontOfSize(tabItemFontSize)); + + this.viewController.tabBar.unselectedItemTitleFont = font; + this.viewController.tabBar.selectedItemTitleFont = font; + + const tabItemTextColor = view.style.color; + const textColor = tabItemTextColor instanceof Color ? tabItemTextColor.ios : null; + + if (textColor) { + this.viewController.tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Normal); + this.viewController.tabBar.setImageTintColorForState(textColor, MDCTabBarItemState.Normal); + + if (setSelected) { + this.viewController.tabBar.setTitleColorForState(textColor, MDCTabBarItemState.Selected); + this.viewController.tabBar.setImageTintColorForState(textColor, MDCTabBarItemState.Selected); + } + } + + if (this._selectedItemColor) { + this.viewController.tabBar.selectedItemTintColor = this._selectedItemColor.ios; + } + if (this._unSelectedItemColor) { + this.viewController.tabBar.unselectedItemTintColor = this._unSelectedItemColor.ios; + } + } } diff --git a/nativescript-core/ui/tabs/tabs-common.ts b/nativescript-core/ui/tabs/tabs-common.ts index d01549559..bd0bb466e 100644 --- a/nativescript-core/ui/tabs/tabs-common.ts +++ b/nativescript-core/ui/tabs/tabs-common.ts @@ -53,5 +53,5 @@ export type IOSTabBarItemsAlignment = 'leading' | 'justified' | 'center' | 'cent export const iOSTabBarItemsAlignmentProperty = new Property({ name: 'iOSTabBarItemsAlignment', defaultValue: 'justified' }); iOSTabBarItemsAlignmentProperty.register(TabsBase); -export const animationEnabledProperty = new Property({ name: "animationEnabled", defaultValue: true, valueConverter: booleanConverter }); +export const animationEnabledProperty = new Property({ name: 'animationEnabled', defaultValue: true, valueConverter: booleanConverter }); animationEnabledProperty.register(TabsBase); diff --git a/tns-platform-declarations/package.json b/tns-platform-declarations/package.json index 3d1e6eb70..a019dd150 100644 --- a/tns-platform-declarations/package.json +++ b/tns-platform-declarations/package.json @@ -1,13 +1,7 @@ { -<<<<<<< HEAD - "name": "tns-platform-declarations", - "version": "6.5.10", - "description": "Platform-specific TypeScript declarations for NativeScript for accessing native objects", -======= "name": "@nativescript/types", "version": "7.0.0-rc.0", "description": "Platform-specific TypeScript declarations for NativeScript", ->>>>>>> chore: cleanup "main": "", "scripts": { "test": "tsc",