Skip to content

Commit 8039c2c

Browse files
MartoYankovmanoldonev
authored andcommitted
feat: introduce fixed font icons to tab navigation (#7691)
1 parent efdd7e6 commit 8039c2c

File tree

10 files changed

+359
-205
lines changed

10 files changed

+359
-205
lines changed

tns-core-modules/image-source/image-source.android.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,19 +163,23 @@ export class ImageSource implements ImageSourceDefinition {
163163

164164
const textBounds = new android.graphics.Rect();
165165
paint.getTextBounds(source, 0, source.length, textBounds);
166+
167+
const textWidth = textBounds.width();
168+
const textHeight = textBounds.height();
169+
if (textWidth > 0 && textHeight > 0) {
170+
const bitmap = android.graphics.Bitmap
171+
.createBitmap(
172+
textWidth,
173+
textHeight,
174+
android.graphics.Bitmap.Config.ARGB_8888
175+
);
176+
177+
const canvas = new android.graphics.Canvas(bitmap);
178+
canvas.drawText(source, -textBounds.left, -textBounds.top, paint);
179+
180+
this.android = bitmap;
181+
}
166182

167-
const bitmap = android.graphics.Bitmap
168-
.createBitmap(
169-
textBounds.width(),
170-
textBounds.height(),
171-
android.graphics.Bitmap.Config.ARGB_8888
172-
);
173-
174-
const canvas = new android.graphics.Canvas(bitmap);
175-
canvas.drawText(source, -textBounds.left, -textBounds.top, paint);
176-
177-
this.android = bitmap;
178-
179183
return this.android != null;
180184
}
181185

tns-core-modules/ui/bottom-navigation/bottom-navigation.android.ts

Lines changed: 112 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { TabContentItem } from "../tab-navigation-base/tab-content-item";
55
import { TextTransform } from "../text-base";
66

77
// Requires
8-
import { TabNavigationBase, itemsProperty, selectedIndexProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base";
8+
import {
9+
TabNavigationBase, getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty
10+
} from "../tab-navigation-base/tab-navigation-base";
911
import { Font } from "../styling/font";
1012
import { getTransformedText } from "../text-base";
1113
import { CSSType, Color } from "../core/view";
@@ -166,85 +168,6 @@ function initializeNativeClasses() {
166168
AttachStateChangeListener = new AttachListener();
167169
}
168170

169-
function createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec {
170-
const tabItemSpec = new org.nativescript.widgets.TabItemSpec();
171-
172-
if (tabStripItem.isLoaded) {
173-
const titleLabel = tabStripItem.label;
174-
let title = titleLabel.text;
175-
176-
// TEXT-TRANSFORM
177-
const textTransform = titleLabel.style.textTransform;
178-
if (textTransform) {
179-
title = getTransformedText(title, textTransform);
180-
}
181-
tabItemSpec.title = title;
182-
183-
// BACKGROUND-COLOR
184-
const backgroundColor = tabStripItem.style.backgroundColor;
185-
if (backgroundColor) {
186-
tabItemSpec.backgroundColor = backgroundColor.android;
187-
}
188-
189-
// COLOR
190-
const color = titleLabel.style.color;
191-
if (color) {
192-
tabItemSpec.color = color.android;
193-
}
194-
195-
// FONT
196-
const fontInternal = titleLabel.style.fontInternal;
197-
if (fontInternal) {
198-
tabItemSpec.fontSize = fontInternal.fontSize;
199-
tabItemSpec.typeFace = fontInternal.getAndroidTypeface();
200-
}
201-
202-
// ICON
203-
const iconSource = tabStripItem.image && tabStripItem.image.src;
204-
if (iconSource) {
205-
if (iconSource.indexOf(RESOURCE_PREFIX) === 0) {
206-
tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length));
207-
if (tabItemSpec.iconId === 0) {
208-
// TODO:
209-
// traceMissingIcon(iconSource);
210-
}
211-
} else {
212-
const icon = _getIcon(tabStripItem);
213-
214-
if (icon) {
215-
// TODO: Make this native call that accepts string so that we don't load Bitmap in JS.
216-
// tslint:disable-next-line:deprecation
217-
tabItemSpec.iconDrawable = icon;
218-
} else {
219-
// TODO:
220-
// traceMissingIcon(iconSource);
221-
}
222-
}
223-
}
224-
}
225-
226-
return tabItemSpec;
227-
}
228-
229-
function _getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable {
230-
const iconSource = tabStripItem.image && tabStripItem.image.src;
231-
232-
let is: ImageSource;
233-
if (isFontIconURI(iconSource)) {
234-
const fontIconCode = iconSource.split("//")[1];
235-
const target = tabStripItem.image ? tabStripItem.image : tabStripItem;
236-
const font = target.style.fontInternal;
237-
const color = target.style.color;
238-
is = fromFontIconCode(fontIconCode, font, color);
239-
} else {
240-
is = fromFileOrResource(iconSource);
241-
}
242-
243-
const image = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android);
244-
245-
return image;
246-
}
247-
248171
function setElevation(bottomNavigationBar: org.nativescript.widgets.BottomNavigationBar) {
249172
const compat = <any>androidx.core.view.ViewCompat;
250173
if (compat.setElevation) {
@@ -566,7 +489,7 @@ export class BottomNavigation extends TabNavigationBase {
566489
items.forEach((item, i, arr) => {
567490
(<any>item).index = i;
568491
if (items[i]) {
569-
const tabItemSpec = createTabItemSpec(items[i]);
492+
const tabItemSpec = this.createTabItemSpec(items[i]);
570493
tabItems.push(tabItemSpec);
571494
}
572495
});
@@ -579,6 +502,111 @@ export class BottomNavigation extends TabNavigationBase {
579502
});
580503
}
581504

505+
private createTabItemSpec(tabStripItem: TabStripItem): org.nativescript.widgets.TabItemSpec {
506+
const tabItemSpec = new org.nativescript.widgets.TabItemSpec();
507+
508+
if (tabStripItem.isLoaded) {
509+
const titleLabel = tabStripItem.label;
510+
let title = titleLabel.text;
511+
512+
// TEXT-TRANSFORM
513+
const textTransform = titleLabel.style.textTransform;
514+
if (textTransform) {
515+
title = getTransformedText(title, textTransform);
516+
}
517+
tabItemSpec.title = title;
518+
519+
// BACKGROUND-COLOR
520+
const backgroundColor = tabStripItem.style.backgroundColor;
521+
if (backgroundColor) {
522+
tabItemSpec.backgroundColor = backgroundColor.android;
523+
}
524+
525+
// COLOR
526+
const color = titleLabel.style.color;
527+
if (color) {
528+
tabItemSpec.color = color.android;
529+
}
530+
531+
// FONT
532+
const fontInternal = titleLabel.style.fontInternal;
533+
if (fontInternal) {
534+
tabItemSpec.fontSize = fontInternal.fontSize;
535+
tabItemSpec.typeFace = fontInternal.getAndroidTypeface();
536+
}
537+
538+
// ICON
539+
const iconSource = tabStripItem.image && tabStripItem.image.src;
540+
if (iconSource) {
541+
if (iconSource.indexOf(RESOURCE_PREFIX) === 0) {
542+
tabItemSpec.iconId = ad.resources.getDrawableId(iconSource.substr(RESOURCE_PREFIX.length));
543+
if (tabItemSpec.iconId === 0) {
544+
// TODO:
545+
// traceMissingIcon(iconSource);
546+
}
547+
} else {
548+
const icon = this.getIcon(tabStripItem);
549+
550+
if (icon) {
551+
// TODO: Make this native call that accepts string so that we don't load Bitmap in JS.
552+
// tslint:disable-next-line:deprecation
553+
tabItemSpec.iconDrawable = icon;
554+
} else {
555+
// TODO:
556+
// traceMissingIcon(iconSource);
557+
}
558+
}
559+
}
560+
}
561+
562+
return tabItemSpec;
563+
}
564+
565+
private getIcon(tabStripItem: TabStripItem): android.graphics.drawable.BitmapDrawable {
566+
const iconSource = tabStripItem.image && tabStripItem.image.src;
567+
568+
let is: ImageSource;
569+
if (isFontIconURI(iconSource)) {
570+
const fontIconCode = iconSource.split("//")[1];
571+
const target = tabStripItem.image ? tabStripItem.image : tabStripItem;
572+
const font = target.style.fontInternal;
573+
const color = target.style.color;
574+
is = fromFontIconCode(fontIconCode, font, color);
575+
} else {
576+
is = fromFileOrResource(iconSource);
577+
}
578+
579+
let imageDrawable: android.graphics.drawable.BitmapDrawable;
580+
if (is && is.android) {
581+
let image = is.android;
582+
583+
if (this.tabStrip && this.tabStrip.isIconSizeFixed) {
584+
image = this.getFixedSizeIcon(image);
585+
}
586+
587+
imageDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), image);
588+
} else {
589+
// TODO
590+
// traceMissingIcon(iconSource);
591+
}
592+
593+
return imageDrawable;
594+
}
595+
596+
private getFixedSizeIcon(image: android.graphics.Bitmap): android.graphics.Bitmap {
597+
const inWidth = image.getWidth();
598+
const inHeight = image.getHeight();
599+
600+
const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight });
601+
602+
const widthPixels = iconSpecSize.width * layout.getDisplayDensity();
603+
const heightPixels = iconSpecSize.height * layout.getDisplayDensity();
604+
605+
const scaledImage = android.graphics.Bitmap.createScaledBitmap(image, widthPixels, heightPixels, true);
606+
607+
return scaledImage;
608+
}
609+
582610
public updateAndroidItemAt(index: number, spec: org.nativescript.widgets.TabItemSpec) {
583611
this._bottomNavigationBar.updateItemAt(index, spec);
584612
}
@@ -598,14 +626,14 @@ export class BottomNavigation extends TabNavigationBase {
598626
public setTabBarItemTitle(tabStripItem: TabStripItem, value: string): void {
599627
// TODO: Should figure out a way to do it directly with the the nativeView
600628
const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem);
601-
const tabItemSpec = createTabItemSpec(tabStripItem);
629+
const tabItemSpec = this.createTabItemSpec(tabStripItem);
602630
this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec);
603631
}
604632

605633
public setTabBarItemBackgroundColor(tabStripItem: TabStripItem, value: android.graphics.drawable.Drawable | Color): void {
606634
// TODO: Should figure out a way to do it directly with the the nativeView
607635
const tabStripItemIndex = this.tabStrip.items.indexOf(tabStripItem);
608-
const tabItemSpec = createTabItemSpec(tabStripItem);
636+
const tabItemSpec = this.createTabItemSpec(tabStripItem);
609637
this.updateAndroidItemAt(tabStripItemIndex, tabItemSpec);
610638
}
611639

@@ -621,7 +649,7 @@ export class BottomNavigation extends TabNavigationBase {
621649
const index = (<any>tabStripItem).index;
622650
const tabBarItem = this._bottomNavigationBar.getViewForItemAt(index);
623651
const imgView = <android.widget.ImageView>tabBarItem.getChildAt(0);
624-
const drawable = _getIcon(tabStripItem);
652+
const drawable = this.getIcon(tabStripItem);
625653

626654
imgView.setImageDrawable(drawable);
627655
}

tns-core-modules/ui/bottom-navigation/bottom-navigation.ios.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { TabStripItem } from "../tab-navigation-base/tab-strip-item";
55
import { TextTransform } from "../text-base";
66

77
//Requires
8-
import { TabNavigationBase, itemsProperty, selectedIndexProperty, tabStripProperty } from "../tab-navigation-base/tab-navigation-base";
8+
import {
9+
TabNavigationBase, getIconSpecSize, itemsProperty, selectedIndexProperty, tabStripProperty
10+
} from "../tab-navigation-base/tab-navigation-base";
911
import { Font } from "../styling/font";
1012
import { getTransformedText } from "../text-base";
1113
import { Frame } from "../frame";
@@ -350,7 +352,7 @@ export class BottomNavigation extends TabNavigationBase {
350352
}
351353

352354
public setTabBarIconColor(tabStripItem: TabStripItem, value: UIColor | Color): void {
353-
const image = this._getIcon(tabStripItem);
355+
const image = this.getIcon(tabStripItem);
354356

355357
tabStripItem.nativeView.image = image;
356358
tabStripItem.nativeView.selectedImage = image;
@@ -510,7 +512,7 @@ export class BottomNavigation extends TabNavigationBase {
510512
let title: string;
511513

512514
if (item.isLoaded) {
513-
image = this._getIcon(item);
515+
image = this.getIcon(item);
514516
title = item.label.text;
515517

516518
const textTransform = item.label.style.textTransform;
@@ -524,11 +526,11 @@ export class BottomNavigation extends TabNavigationBase {
524526
return tabBarItem;
525527
}
526528

527-
private _getIconRenderingMode(): UIImageRenderingMode {
529+
private getIconRenderingMode(): UIImageRenderingMode {
528530
return UIImageRenderingMode.AlwaysOriginal;
529531
}
530532

531-
public _getIcon(tabStripItem: TabStripItem): UIImage {
533+
private getIcon(tabStripItem: TabStripItem): UIImage {
532534
// Image and Label children of TabStripItem
533535
// take priority over its `iconSource` and `title` properties
534536
const iconSource = tabStripItem.image && tabStripItem.image.src;
@@ -552,7 +554,13 @@ export class BottomNavigation extends TabNavigationBase {
552554
}
553555

554556
if (is && is.ios) {
555-
const originalRenderedImage = is.ios.imageWithRenderingMode(this._getIconRenderingMode());
557+
image = is.ios;
558+
559+
if (this.tabStrip && this.tabStrip.isIconSizeFixed) {
560+
image = this.getFixedSizeIcon(image);
561+
}
562+
563+
const originalRenderedImage = image.imageWithRenderingMode(this.getIconRenderingMode());
556564
this._iconsCache[iconTag] = originalRenderedImage;
557565
image = originalRenderedImage;
558566
} else {
@@ -564,6 +572,23 @@ export class BottomNavigation extends TabNavigationBase {
564572
return image;
565573
}
566574

575+
private getFixedSizeIcon(image: UIImage): UIImage {
576+
const inWidth = image.size.width;
577+
const inHeight = image.size.height;
578+
579+
const iconSpecSize = getIconSpecSize({ width: inWidth, height: inHeight });
580+
581+
const widthPts = iconSpecSize.width;
582+
const heightPts = iconSpecSize.height;
583+
584+
UIGraphicsBeginImageContextWithOptions({ width: widthPts, height: heightPts }, false, layout.getDisplayDensity());
585+
image.drawInRect(CGRectMake(0, 0, widthPts, heightPts));
586+
let resultImage = UIGraphicsGetImageFromCurrentImageContext();
587+
UIGraphicsEndImageContext();
588+
589+
return resultImage;
590+
}
591+
567592
// private _updateIOSTabBarColorsAndFonts(): void {
568593
// if (!this.tabStrip || !this.tabStrip.items || !this.tabStrip.items.length) {
569594
// return;

tns-core-modules/ui/tab-navigation-base/tab-navigation-base/tab-navigation-base.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ export class TabNavigationBase extends View {
217217
setTabBarItemTextTransform(tabStripItem: TabStripItem, value: any): void
218218
}
219219

220+
export function getIconSpecSize(size: { width: number, height: number }): { width: number, height: number }
221+
220222
export const itemsProperty: Property<TabNavigationBase, TabContentItem[]>;
221223
export const tabStripProperty: Property<TabNavigationBase, TabStrip>
222224
export const selectedIndexProperty: CoercibleProperty<TabNavigationBase, number>;

0 commit comments

Comments
 (0)