import { Font } from "../styling/font"; import { TabViewBase, TabViewItemBase, itemsProperty, selectedIndexProperty, tabTextColorProperty, tabBackgroundColorProperty, selectedTabTextColorProperty, androidSelectedTabHighlightColorProperty, androidOffscreenTabLimitProperty, fontSizeProperty, fontInternalProperty, View, layout, traceCategory, traceEnabled, traceWrite, initNativeView, Color } from "./tab-view-common" import { textTransformProperty, TextTransform, getTransformedText } from "../text-base"; import { fromFileOrResource } from "../../image-source"; import { RESOURCE_PREFIX, ad } from "../../utils/utils"; export * from "./tab-view-common"; const VIEWS_STATES = "_viewStates"; const ACCENT_COLOR = "colorAccent"; const PRIMARY_COLOR = "colorPrimary"; const DEFAULT_ELEVATION = 4; interface PagerAdapter { new (owner: TabView, items: Array): android.support.v4.view.PagerAdapter; } interface PageChangedListener { new (owner: TabView): android.support.v4.view.ViewPager.SimpleOnPageChangeListener; } let PagerAdapter: PagerAdapter; let PageChangedListener: PageChangedListener; function initializeNativeClasses() { if (PagerAdapter) { return; } class PagerAdapterImpl extends android.support.v4.view.PagerAdapter { constructor(public owner: TabView, public items: Array) { super(); return global.__native(this); } getCount() { return this.items ? this.items.length : 0; } getPageTitle(index: number) { if (index < 0 || index >= this.items.length) { return ""; } return this.items[index].title; } instantiateItem(container: android.view.ViewGroup, index: number) { if (traceEnabled()) { traceWrite("TabView.PagerAdapter.instantiateItem; container: " + container + "; index: " + index, traceCategory); } let item = this.items[index]; // if (item.view.parent !== this.owner) { // this.owner._addView(item.view); // } if (this[VIEWS_STATES]) { if (traceEnabled()) { traceWrite("TabView.PagerAdapter.instantiateItem; restoreHierarchyState: " + item.view, traceCategory); } item.view.nativeView.restoreHierarchyState(this[VIEWS_STATES]); } if (item.view.nativeView) { container.addView(item.view.nativeView); } return item.view.nativeView; } destroyItem(container: android.view.ViewGroup, index: number, _object: any) { if (traceEnabled()) { traceWrite("TabView.PagerAdapter.destroyItem; container: " + container + "; index: " + index + "; _object: " + _object, traceCategory); } let item = this.items[index]; let nativeView = item.view.nativeView; if (!nativeView || !_object) { return; } if (nativeView.toString() !== _object.toString()) { throw new Error("Expected " + nativeView.toString() + " to equal " + _object.toString()); } container.removeView(nativeView); // Note: this.owner._removeView will clear item.view.nativeView. // So call this after the native instance is removed form the container. // if (item.view.parent === this.owner) { // this.owner._removeView(item.view); // } } isViewFromObject(view: android.view.View, _object: any) { return view === _object; } saveState(): android.os.Parcelable { if (traceEnabled()) { traceWrite("TabView.PagerAdapter.saveState", traceCategory); } const owner: TabView = this.owner; if (owner._childrenCount === 0) { return null; } if (!this[VIEWS_STATES]) { this[VIEWS_STATES] = new android.util.SparseArray(); } let viewStates = this[VIEWS_STATES]; let childCallback = function (view: View): boolean { let nativeView: android.view.View = view.nativeView; if (nativeView && nativeView.isSaveFromParentEnabled && nativeView.isSaveFromParentEnabled()) { nativeView.saveHierarchyState(viewStates); } return true; } owner.eachChildView(childCallback); let bundle = new android.os.Bundle(); bundle.putSparseParcelableArray(VIEWS_STATES, viewStates); return bundle; } restoreState(state: android.os.Parcelable, loader: java.lang.ClassLoader) { if (traceEnabled()) { traceWrite("TabView.PagerAdapter.restoreState", traceCategory); } let bundle: android.os.Bundle = state; bundle.setClassLoader(loader); this[VIEWS_STATES] = bundle.getSparseParcelableArray(VIEWS_STATES); } }; class PageChangedListenerImpl extends android.support.v4.view.ViewPager.SimpleOnPageChangeListener { private _owner: TabView; constructor(owner: TabView) { super(); this._owner = owner; return global.__native(this); } public onPageSelected(position: number) { this._owner.selectedIndex = position; } } PagerAdapter = PagerAdapterImpl; PageChangedListener = PageChangedListenerImpl; } function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemSpec { let result = new org.nativescript.widgets.TabItemSpec(); result.title = item.title; if (item.iconSource) { if (item.iconSource.indexOf(RESOURCE_PREFIX) === 0) { result.iconId = ad.resources.getDrawableId(item.iconSource.substr(RESOURCE_PREFIX.length)); } else { let is = fromFileOrResource(item.iconSource); if (is) { // TODO: Make this native call that accepts string so that we don't load Bitmap in JS. result.iconDrawable = new android.graphics.drawable.BitmapDrawable(is.android); } } } return result; } let defaultAccentColor: number = undefined; function getDefaultAccentColor(context: android.content.Context): number { if (defaultAccentColor === undefined) { //Fallback color: https://developer.android.com/samples/SlidingTabsColors/src/com.example.android.common/view/SlidingTabStrip.html defaultAccentColor = ad.resources.getPalleteColor(ACCENT_COLOR, context) || 0xFF33B5E5; } return defaultAccentColor; } export class TabViewItem extends TabViewItemBase { nativeView: android.widget.TextView; public tabItemSpec: org.nativescript.widgets.TabItemSpec; public index: number; private _defaultTransformationMethod: android.text.method.TransformationMethod; public initNativeView(): void { super.initNativeView(); if (this.nativeView) { this._defaultTransformationMethod = this.nativeView.getTransformationMethod(); } } public resetNativeView(): void { super.resetNativeView(); if (this.nativeView) { // We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform this.nativeView.setTransformationMethod(this._defaultTransformationMethod); } } public createNativeView() { return this.nativeView; } public setNativeView(textView: android.widget.TextView): void { this.nativeView = textView; if (textView) { initNativeView(this); } } public _update(): void { const tv = this.nativeView; if (tv) { const tabLayout = tv.getParent(); tabLayout.updateItemAt(this.index, this.tabItemSpec); } } [fontSizeProperty.getDefault](): { nativeSize: number } { return { nativeSize: this.nativeView.getTextSize() }; } [fontSizeProperty.setNative](value: number | { nativeSize: number }) { if (typeof value === "number") { this.nativeView.setTextSize(value); } else { this.nativeView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize); } } [fontInternalProperty.getDefault](): android.graphics.Typeface { return this.nativeView.getTypeface(); } [fontInternalProperty.setNative](value: Font | android.graphics.Typeface) { this.nativeView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value); } [textTransformProperty.getDefault](): "default" { return "default"; } [textTransformProperty.setNative](value: TextTransform | "default") { const tv = this.nativeView; if (value === "default") { tv.setTransformationMethod(this._defaultTransformationMethod); tv.setText(this.title); } else { const result = getTransformedText(this.title, value); tv.setText(result); tv.setTransformationMethod(null); } } } function setElevation(grid: org.nativescript.widgets.GridLayout, tabLayout: org.nativescript.widgets.TabLayout) { const compat = android.support.v4.view.ViewCompat; if (compat.setElevation) { const val = DEFAULT_ELEVATION * layout.getDisplayDensity(); compat.setElevation(grid, val); compat.setElevation(tabLayout, val); } } export class TabView extends TabViewBase { private _tabLayout: org.nativescript.widgets.TabLayout; private _viewPager: android.support.v4.view.ViewPager; private _pagerAdapter: android.support.v4.view.PagerAdapter; private _androidViewId: number = -1; public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void { super.onItemsChanged(oldItems, newItems); if (oldItems) { oldItems.forEach((item: TabViewItem, i, arr) => { item.index = 0; item.tabItemSpec = null; item.setNativeView(null); }); } } public createNativeView() { initializeNativeClasses(); if (traceEnabled()) { traceWrite("TabView._createUI(" + this + ");", traceCategory); } const context: android.content.Context = this._context; const nativeView = new org.nativescript.widgets.GridLayout(context); 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)); const tabLayout = new org.nativescript.widgets.TabLayout(context); nativeView.addView(tabLayout); (nativeView).tabLayout = tabLayout; setElevation(nativeView, tabLayout); const accentColor = getDefaultAccentColor(context); if (accentColor) { tabLayout.setSelectedIndicatorColors([accentColor]); } const primaryColor = ad.resources.getPalleteColor(PRIMARY_COLOR, context); if (primaryColor) { tabLayout.setBackgroundColor(primaryColor); } const viewPager = new android.support.v4.view.ViewPager(context); const lp = new org.nativescript.widgets.CommonLayoutParams(); lp.row = 1; viewPager.setLayoutParams(lp); nativeView.addView(viewPager); (nativeView).viewPager = viewPager; const listener = new PageChangedListener(this); (viewPager).addOnPageChangeListener(listener); (viewPager).listener = listener; const adapter = new PagerAdapter(this, null); viewPager.setAdapter(adapter); (viewPager).adapter = adapter; return nativeView; } public initNativeView(): void { super.initNativeView(); if (this._androidViewId < 0) { this._androidViewId = android.view.View.generateViewId(); } const nativeView: any = this.nativeView; this._tabLayout = (nativeView).tabLayout; const viewPager = (nativeView).viewPager; viewPager.setId(this._androidViewId); this._viewPager = viewPager; (viewPager).listener.owner = this; this._pagerAdapter = (viewPager).adapter; (this._pagerAdapter).owner = this; } public disposeNativeView() { this._pagerAdapter.notifyDataSetChanged(); (this._pagerAdapter).owner = null; this._pagerAdapter = null; this._tabLayout = null; (this._viewPager).listener.owner = null; this._viewPager = null; super.disposeNativeView(); } private setAdapterItems(items: Array) { (this._pagerAdapter).items = items; const length = items ? items.length : 0; if (length === 0) { this._tabLayout.setItems(null, null); return; } const tabItems = new Array(); items.forEach((item, i, arr) => { const tabItemSpec = createTabItemSpec(item); item.index = i; item.tabItemSpec = tabItemSpec; tabItems.push(tabItemSpec); }); // // TODO: optimize by reusing the adapter and calling setAdapter(null) then the same adapter. // this._pagerAdapter = new PagerAdapter(this, items); // this._viewPager.setAdapter(this._pagerAdapter); 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(); } [androidOffscreenTabLimitProperty.getDefault](): number { return this._viewPager.getOffscreenPageLimit(); } [androidOffscreenTabLimitProperty.setNative](value: number) { this._viewPager.setOffscreenPageLimit(value); } [selectedIndexProperty.getDefault](): number { return -1; } [selectedIndexProperty.setNative](value: number) { if (traceEnabled()) { traceWrite("TabView this._viewPager.setCurrentItem(" + value + ", true);", traceCategory); } this._viewPager.setCurrentItem(value, true); } [itemsProperty.getDefault](): TabViewItem[] { return null; } [itemsProperty.setNative](value: TabViewItem[]) { this.setAdapterItems(value); selectedIndexProperty.coerce(this); } [tabBackgroundColorProperty.getDefault](): android.graphics.drawable.Drawable.ConstantState { return this._tabLayout.getBackground().getConstantState(); } [tabBackgroundColorProperty.setNative](value: android.graphics.drawable.Drawable.ConstantState | Color) { if (value instanceof Color) { this._tabLayout.setBackgroundColor(value.android); } else { this._tabLayout.setBackground(value ? value.newDrawable() : null); } } [tabTextColorProperty.getDefault](): number { return this._tabLayout.getTabTextColor(); } [tabTextColorProperty.setNative](value: number | Color) { const color = value instanceof Color ? value.android : value; this._tabLayout.setTabTextColor(color); } [selectedTabTextColorProperty.getDefault](): number { return this._tabLayout.getSelectedTabTextColor(); } [selectedTabTextColorProperty.setNative](value: number | Color) { const color = value instanceof Color ? value.android : value; this._tabLayout.setSelectedTabTextColor(color); } [androidSelectedTabHighlightColorProperty.getDefault](): number { return getDefaultAccentColor(this._context); } [androidSelectedTabHighlightColorProperty.setNative](value: number | Color) { let tabLayout = this._tabLayout; const color = value instanceof Color ? value.android : value; tabLayout.setSelectedIndicatorColors([color]); } }