@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
652 lines • 26.3 kB
JavaScript
import { Font } from '../styling/font';
import { TabViewBase, TabViewItemBase, itemsProperty, selectedIndexProperty, tabTextColorProperty, tabBackgroundColorProperty, tabTextFontSizeProperty, selectedTabTextColorProperty, androidSelectedTabHighlightColorProperty, androidOffscreenTabLimitProperty, traceCategory, traceMissingIcon, androidIconRenderingModeProperty } from './tab-view-common';
import { textTransformProperty, getTransformedText } from '../text-base';
import { ImageSource } from '../../image-source';
import { Trace } from '../../trace';
import { Color } from '../../color';
import { fontSizeProperty, fontInternalProperty } from '../styling/style-properties';
import { RESOURCE_PREFIX, ad, layout } from '../../utils';
import { Frame } from '../frame';
import { Application } from '../../application';
export * from './tab-view-common';
const ACCENT_COLOR = 'colorAccent';
const PRIMARY_COLOR = 'colorPrimary';
const DEFAULT_ELEVATION = 4;
const TABID = '_tabId';
const INDEX = '_index';
let PagerAdapter;
let appResources;
function makeFragmentName(viewId, id) {
return 'android:viewpager:' + viewId + ':' + id;
}
function getTabById(id) {
const ref = tabs.find((ref) => {
const tab = ref.get();
return tab && tab._domId === id;
});
return ref && ref.get();
}
function initializeNativeClasses() {
if (PagerAdapter) {
return;
}
var TabFragmentImplementation = /** @class */ (function (_super) {
__extends(TabFragmentImplementation, _super);
function TabFragmentImplementation() {
var _this = _super.call(this) || this;
_this.backgroundBitmap = null;
return global.__native(_this);
}
TabFragmentImplementation.newInstance = function (tabId, index) {
var args = new android.os.Bundle();
args.putInt(TABID, tabId);
args.putInt(INDEX, index);
var fragment = new TabFragmentImplementation();
fragment.setArguments(args);
return fragment;
};
TabFragmentImplementation.prototype.onCreate = function (savedInstanceState) {
_super.prototype.onCreate.call(this, savedInstanceState);
var args = this.getArguments();
this.owner = getTabById(args.getInt(TABID));
this.index = args.getInt(INDEX);
if (!this.owner) {
throw new Error("Cannot find TabView");
}
};
TabFragmentImplementation.prototype.onCreateView = function (inflater, container, savedInstanceState) {
var tabItem = this.owner.items[this.index];
return tabItem.view.nativeViewProtected;
};
TabFragmentImplementation.prototype.onDestroyView = function () {
var hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
var bitmapDrawable = new android.graphics.drawable.BitmapDrawable(appResources, this.backgroundBitmap);
this.owner._originalBackground = this.owner.backgroundColor || new Color('White');
this.owner.nativeViewProtected.setBackground(bitmapDrawable);
this.backgroundBitmap = null;
}
_super.prototype.onDestroyView.call(this);
};
TabFragmentImplementation.prototype.onPause = function () {
var hasRemovingParent = this.getRemovingParentFragment();
// Get view as bitmap and set it as background. This is workaround for the disapearing nested fragments.
// TODO: Consider removing it when update to androidx.fragment:1.2.0
if (hasRemovingParent && this.owner.selectedIndex === this.index) {
this.backgroundBitmap = this.loadBitmapFromView(this.owner.nativeViewProtected);
}
_super.prototype.onPause.call(this);
};
TabFragmentImplementation.prototype.loadBitmapFromView = function (view) {
// Another way to get view bitmap. Test performance vs setDrawingCacheEnabled
// const width = view.getWidth();
// const height = view.getHeight();
// const bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888);
// const canvas = new android.graphics.Canvas(bitmap);
// view.layout(0, 0, width, height);
// view.draw(canvas);
view.setDrawingCacheEnabled(true);
var bitmap = android.graphics.Bitmap.createBitmap(view.getDrawingCache());
view.setDrawingCacheEnabled(false);
return bitmap;
};
return TabFragmentImplementation;
}(org.nativescript.widgets.FragmentBase));
const POSITION_UNCHANGED = -1;
const POSITION_NONE = -2;
var FragmentPagerAdapter = /** @class */ (function (_super) {
__extends(FragmentPagerAdapter, _super);
function FragmentPagerAdapter(owner) {
var _this = _super.call(this) || this;
_this.owner = owner;
// in fragments 1.3+, committing a transaction may call the adapter's methods and trigger another commit
// we prevent that here.
_this.transactionRunning = false;
return global.__native(_this);
}
FragmentPagerAdapter.prototype.getCount = function () {
var items = this.items;
return items ? items.length : 0;
};
FragmentPagerAdapter.prototype.getPageTitle = function (index) {
var items = this.items;
if (index < 0 || index >= items.length) {
return '';
}
return items[index].title;
};
FragmentPagerAdapter.prototype.startUpdate = function (container) {
if (container.getId() === android.view.View.NO_ID) {
throw new Error("ViewPager with adapter ".concat(this, " requires a view containerId"));
}
};
FragmentPagerAdapter.prototype.instantiateItem = function (container, position) {
var fragmentManager = this.owner._getFragmentManager();
if (!this.mCurTransaction) {
this.mCurTransaction = fragmentManager.beginTransaction();
}
var itemId = this.getItemId(position);
var name = makeFragmentName(container.getId(), itemId);
var fragment = fragmentManager.findFragmentByTag(name);
if (fragment != null) {
this.mCurTransaction.attach(fragment);
}
else {
fragment = TabFragmentImplementation.newInstance(this.owner._domId, position);
this.mCurTransaction.add(container.getId(), fragment, name);
}
if (fragment !== this.mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
var tabItems = this.owner.items;
var tabItem = tabItems ? tabItems[position] : null;
if (tabItem) {
tabItem.canBeLoaded = true;
}
return fragment;
};
FragmentPagerAdapter.prototype.getItemPosition = function (object) {
return this.items ? POSITION_UNCHANGED : POSITION_NONE;
};
FragmentPagerAdapter.prototype.destroyItem = function (container, position, object) {
if (!this.mCurTransaction) {
var fragmentManager = this.owner._getFragmentManager();
this.mCurTransaction = fragmentManager.beginTransaction();
}
var fragment = object;
this.mCurTransaction.detach(fragment);
if (this.mCurrentPrimaryItem === fragment) {
this.mCurrentPrimaryItem = null;
}
var tabItems = this.owner.items;
var tabItem = tabItems ? tabItems[position] : null;
if (tabItem) {
tabItem.canBeLoaded = false;
}
};
FragmentPagerAdapter.prototype.setPrimaryItem = function (container, position, object) {
var fragment = object;
if (fragment !== this.mCurrentPrimaryItem) {
if (this.mCurrentPrimaryItem != null) {
this.mCurrentPrimaryItem.setMenuVisibility(false);
this.mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
this.mCurrentPrimaryItem = fragment;
this.owner.selectedIndex = position;
var tab = this.owner;
var tabItems = tab.items;
var newTabItem = tabItems ? tabItems[position] : null;
if (newTabItem) {
tab._loadUnloadTabItems(tab.selectedIndex);
}
}
};
FragmentPagerAdapter.prototype.finishUpdate = function (container) {
this._commitCurrentTransaction();
};
FragmentPagerAdapter.prototype.isViewFromObject = function (view, object) {
return object.getView() === view;
};
FragmentPagerAdapter.prototype.saveState = function () {
// Commit the current transaction on save to prevent "No view found for id 0xa" exception on restore.
// Related to: https://github.com/NativeScript/NativeScript/issues/6466
this._commitCurrentTransaction();
return null;
};
FragmentPagerAdapter.prototype.restoreState = function (state, loader) {
//
};
FragmentPagerAdapter.prototype.getItemId = function (position) {
return position;
};
FragmentPagerAdapter.prototype._commitCurrentTransaction = function () {
if (this.mCurTransaction != null && !this.transactionRunning) {
this.transactionRunning = true;
this.mCurTransaction.commitNowAllowingStateLoss();
this.transactionRunning = false;
this.mCurTransaction = null;
}
};
return FragmentPagerAdapter;
}(androidx.viewpager.widget.PagerAdapter));
PagerAdapter = FragmentPagerAdapter;
appResources = Application.android.context.getResources();
}
function createTabItemSpec(item) {
const 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));
if (result.iconId === 0) {
traceMissingIcon(item.iconSource);
}
}
else {
const is = ImageSource.fromFileOrResourceSync(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(appResources, is.android);
}
else {
traceMissingIcon(item.iconSource);
}
}
}
return result;
}
let defaultAccentColor = undefined;
function getDefaultAccentColor(context) {
if (defaultAccentColor === undefined) {
//Fallback color: https://developer.android.com/samples/SlidingTabsColors/src/com.example.android.common/view/SlidingTabStrip.html
defaultAccentColor = ad.resources.getPaletteColor(ACCENT_COLOR, context) || 0xff33b5e5;
}
return defaultAccentColor;
}
export class TabViewItem extends TabViewItemBase {
get _hasFragments() {
return true;
}
initNativeView() {
super.initNativeView();
if (this.nativeViewProtected) {
this._defaultTransformationMethod = this.nativeViewProtected.getTransformationMethod();
}
}
onLoaded() {
super.onLoaded();
}
resetNativeView() {
super.resetNativeView();
if (this.nativeViewProtected) {
// We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform
this.nativeViewProtected.setTransformationMethod(this._defaultTransformationMethod);
}
}
disposeNativeView() {
this.canBeLoaded = false;
super.disposeNativeView();
}
createNativeView() {
return this.nativeViewProtected;
}
_update() {
const tv = this.nativeViewProtected;
const tabView = this.parent;
if (tv && tabView) {
this.tabItemSpec = createTabItemSpec(this);
tabView.updateAndroidItemAt(this.index, this.tabItemSpec);
}
}
_getChildFragmentManager() {
const tabView = this.parent;
let tabFragment = null;
const fragmentManager = tabView._getFragmentManager();
const fragments = fragmentManager.getFragments().toArray();
for (let i = 0; i < fragments.length; i++) {
if (fragments[i].index === this.index) {
tabFragment = fragments[i];
break;
}
}
// TODO: can happen in a modal tabview scenario when the modal dialog fragment is already removed
if (!tabFragment) {
if (Trace.isEnabled()) {
Trace.write(`Could not get child fragment manager for tab item with index ${this.index}`, traceCategory);
}
return tabView._getRootFragmentManager();
}
return tabFragment.getChildFragmentManager();
}
[fontSizeProperty.getDefault]() {
return { nativeSize: this.nativeViewProtected.getTextSize() };
}
[fontSizeProperty.setNative](value) {
if (typeof value === 'number') {
this.nativeViewProtected.setTextSize(value);
}
else {
this.nativeViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
}
}
[fontInternalProperty.getDefault]() {
return this.nativeViewProtected.getTypeface();
}
[fontInternalProperty.setNative](value) {
this.nativeViewProtected.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
}
[textTransformProperty.getDefault]() {
return 'default';
}
[textTransformProperty.setNative](value) {
const tv = this.nativeViewProtected;
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, tabLayout) {
const compat = androidx.core.view.ViewCompat;
if (compat.setElevation) {
const val = DEFAULT_ELEVATION * layout.getDisplayDensity();
compat.setElevation(grid, val);
compat.setElevation(tabLayout, val);
}
}
export const tabs = new Array();
function iterateIndexRange(index, eps, lastIndex, callback) {
const rangeStart = Math.max(0, index - eps);
const rangeEnd = Math.min(index + eps, lastIndex);
for (let i = rangeStart; i <= rangeEnd; i++) {
callback(i);
}
}
export class TabView extends TabViewBase {
constructor() {
super();
this._androidViewId = -1;
tabs.push(new WeakRef(this));
}
get _hasFragments() {
return true;
}
onItemsChanged(oldItems, newItems) {
super.onItemsChanged(oldItems, newItems);
if (oldItems) {
oldItems.forEach((item, i, arr) => {
item.index = 0;
item.tabItemSpec = null;
item.setNativeView(null);
});
}
}
createNativeView() {
initializeNativeClasses();
if (Trace.isEnabled()) {
Trace.write('TabView._createUI(' + this + ');', traceCategory);
}
const context = this._context;
const nativeView = new org.nativescript.widgets.GridLayout(context);
const viewPager = new org.nativescript.widgets.TabViewPager(context);
const tabLayout = new org.nativescript.widgets.TabLayout(context);
const lp = new org.nativescript.widgets.CommonLayoutParams();
const primaryColor = ad.resources.getPaletteColor(PRIMARY_COLOR, context);
let accentColor = getDefaultAccentColor(context);
lp.row = 1;
if (this.androidTabsPosition === '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);
if (!this.androidSwipeEnabled) {
viewPager.setSwipePageEnabled(false);
}
}
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));
tabLayout.setLayoutParams(lp);
viewPager.setSwipePageEnabled(false);
// set completely transparent accent color for tab selected indicator.
accentColor = 0x00ffffff;
}
nativeView.addView(viewPager);
nativeView.viewPager = viewPager;
const adapter = new PagerAdapter(this);
viewPager.setAdapter(adapter);
viewPager.adapter = adapter;
nativeView.addView(tabLayout);
nativeView.tabLayout = tabLayout;
setElevation(nativeView, tabLayout);
if (accentColor) {
tabLayout.setSelectedIndicatorColors([accentColor]);
}
if (primaryColor) {
tabLayout.setBackgroundColor(primaryColor);
}
return nativeView;
}
initNativeView() {
super.initNativeView();
if (this._androidViewId < 0) {
this._androidViewId = android.view.View.generateViewId();
}
const nativeView = this.nativeViewProtected;
this._tabLayout = nativeView.tabLayout;
const viewPager = nativeView.viewPager;
viewPager.setId(this._androidViewId);
this._viewPager = viewPager;
this._pagerAdapter = viewPager.adapter;
this._pagerAdapter.owner = this;
}
_loadUnloadTabItems(newIndex) {
const items = this.items;
if (!items) {
return;
}
const lastIndex = items.length - 1;
const offsideItems = this.androidTabsPosition === 'top' ? this.androidOffscreenTabLimit : 1;
const toUnload = [];
const 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.view);
}
});
const newItem = items[newIndex];
const selectedView = newItem && newItem.view;
if (selectedView instanceof Frame) {
selectedView._pushInFrameStackRecursive();
}
toLoad.forEach((index) => {
const item = items[index];
if (this.isLoaded && items[index]) {
item.loadView(item.view);
}
});
}
onLoaded() {
super.onLoaded();
if (this._originalBackground) {
this.backgroundColor = null;
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
this.setAdapterItems(this.items);
}
onUnloaded() {
super.onUnloaded();
this.setAdapterItems(null);
}
disposeNativeView() {
this._tabLayout.setItems(null, null);
this._pagerAdapter.owner = null;
this._pagerAdapter = null;
this._tabLayout = null;
this._viewPager = null;
super.disposeNativeView();
}
_onRootViewReset() {
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();
}
disposeCurrentFragments() {
const fragmentManager = this._getFragmentManager();
const transaction = fragmentManager.beginTransaction();
const fragments = fragmentManager.getFragments().toArray();
for (let i = 0; i < fragments.length; i++) {
transaction.remove(fragments[i]);
}
transaction.commitNowAllowingStateLoss();
}
shouldUpdateAdapter(items) {
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;
}
setAdapterItems(items) {
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, 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();
}
}
getNativeRenderingMode(mode) {
switch (mode) {
case 'alwaysTemplate':
return org.nativescript.widgets.TabIconRenderingMode.template;
default:
case 'alwaysOriginal':
return org.nativescript.widgets.TabIconRenderingMode.original;
}
}
updateAndroidItemAt(index, spec) {
this._tabLayout.updateItemAt(index, spec);
}
[androidOffscreenTabLimitProperty.getDefault]() {
return this._viewPager.getOffscreenPageLimit();
}
[androidOffscreenTabLimitProperty.setNative](value) {
this._viewPager.setOffscreenPageLimit(value);
}
[androidIconRenderingModeProperty.getDefault]() {
return 'alwaysOriginal';
}
[androidIconRenderingModeProperty.setNative](value) {
this._tabLayout.setIconRenderingMode(this.getNativeRenderingMode(value));
}
[selectedIndexProperty.setNative](value) {
const smoothScroll = this.androidTabsPosition === 'top';
if (Trace.isEnabled()) {
Trace.write('TabView this._viewPager.setCurrentItem(' + value + ', ' + smoothScroll + ');', traceCategory);
}
this._viewPager.setCurrentItem(value, smoothScroll);
}
[itemsProperty.getDefault]() {
return null;
}
[itemsProperty.setNative](value) {
this.setAdapterItems(value);
selectedIndexProperty.coerce(this);
}
[tabBackgroundColorProperty.getDefault]() {
return this._tabLayout.getBackground();
}
[tabBackgroundColorProperty.setNative](value) {
if (value instanceof Color) {
this._tabLayout.setBackgroundColor(value.android);
}
else {
this._tabLayout.setBackground(tryCloneDrawable(value, this.nativeViewProtected.getResources()));
}
}
[tabTextFontSizeProperty.getDefault]() {
return this._tabLayout.getTabTextFontSize();
}
[tabTextFontSizeProperty.setNative](value) {
if (typeof value === 'number') {
this._tabLayout.setTabTextFontSize(value);
}
else {
this._tabLayout.setTabTextFontSize(value.nativeSize);
}
}
[tabTextColorProperty.getDefault]() {
return this._tabLayout.getTabTextColor();
}
[tabTextColorProperty.setNative](value) {
const color = value instanceof Color ? value.android : value;
this._tabLayout.setTabTextColor(color);
}
[selectedTabTextColorProperty.getDefault]() {
return this._tabLayout.getSelectedTabTextColor();
}
[selectedTabTextColorProperty.setNative](value) {
const color = value instanceof Color ? value.android : value;
this._tabLayout.setSelectedTabTextColor(color);
}
[androidSelectedTabHighlightColorProperty.getDefault]() {
return getDefaultAccentColor(this._context);
}
[androidSelectedTabHighlightColorProperty.setNative](value) {
const tabLayout = this._tabLayout;
const color = value instanceof Color ? value.android : value;
tabLayout.setSelectedIndicatorColors([color]);
}
}
function tryCloneDrawable(value, resources) {
if (value) {
const constantState = value.getConstantState();
if (constantState) {
return constantState.newDrawable(resources);
}
}
return value;
}
//# sourceMappingURL=index.android.js.map