Skip to content

Commit e0f2637

Browse files
committed
feat: button basic icon support
1 parent 4c4cbf5 commit e0f2637

8 files changed

+194
-44
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
3+
style="@style/UnelevatedButton.Custom.Icon">
4+
</com.google.android.material.button.MaterialButton>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
3+
style="@style/Button.Custom.Icon">
4+
</com.google.android.material.button.MaterialButton>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
3+
style="@style/OutlinedButton.Custom.Icon">
4+
</com.google.android.material.button.MaterialButton>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
3+
style="@style/TextButton.Custom.Icon">
4+
</com.google.android.material.button.MaterialButton>
Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<resources xmlns:android="http://schemas.android.com/apk/res/android">
3-
2+
<resources
3+
xmlns:android="http://schemas.android.com/apk/res/android">
44
<style name="UnelevatedButton.Custom" parent="@style/Widget.MaterialComponents.Button.UnelevatedButton">
55
<item name="android:padding">0dp</item>
66
<item name="android:minWidth">88dp</item>
@@ -10,8 +10,26 @@
1010
<item name="android:insetTop">0dp</item>
1111
<item name="android:insetBottom">0dp</item>
1212
</style>
13+
<style name="UnelevatedButton.Custom.Icon" parent="@style/Widget.MaterialComponents.Button.UnelevatedButton.Icon">
14+
<item name="android:padding">0dp</item>
15+
<item name="android:minWidth">88dp</item>
16+
<item name="android:minHeight">36dp</item>
17+
<item name="android:insetLeft">0dp</item>
18+
<item name="android:insetRight">0dp</item>
19+
<item name="android:insetTop">0dp</item>
20+
<item name="android:insetBottom">0dp</item>
21+
</style>
22+
<style name="TextButton.Custom.Icon" parent="@style/Widget.MaterialComponents.Button.TextButton.Icon">
23+
<item name="android:padding">0dp</item>
24+
<item name="android:minWidth">88dp</item>
25+
<item name="android:minHeight">36dp</item>
26+
<item name="android:insetLeft">0dp</item>
27+
<item name="android:insetRight">0dp</item>
28+
<item name="android:insetTop">0dp</item>
29+
<item name="android:insetBottom">0dp</item>
30+
</style>
1331
<style name="TextButton.Custom" parent="@style/Widget.MaterialComponents.Button.TextButton">
14-
<item name="android:padding">0dp</item>
32+
<item name="android:padding">0dp</item>
1533
<item name="android:minWidth">88dp</item>
1634
<item name="android:minHeight">36dp</item>
1735
<item name="android:insetLeft">0dp</item>
@@ -20,15 +38,15 @@
2038
<item name="android:insetBottom">0dp</item>
2139
</style>
2240
<style name="OutlinedButton.Custom" parent="@style/Widget.MaterialComponents.Button.OutlinedButton">
23-
<item name="android:padding">0dp</item>
41+
<item name="android:padding">0dp</item>
2442
<item name="android:minWidth">88dp</item>
2543
<item name="android:minHeight">36dp</item>
2644
<item name="android:insetLeft">0dp</item>
2745
<item name="android:insetRight">0dp</item>
2846
<item name="android:insetTop">0dp</item>
2947
<item name="android:insetBottom">0dp</item>
3048
</style>
31-
<style name="Button.Custom" parent="@style/Widget.MaterialComponents.Button">
49+
<style name="OutlinedButton.Custom.Icon" parent="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon">
3250
<item name="android:padding">0dp</item>
3351
<item name="android:minWidth">88dp</item>
3452
<item name="android:minHeight">36dp</item>
@@ -37,14 +55,23 @@
3755
<item name="android:insetTop">0dp</item>
3856
<item name="android:insetBottom">0dp</item>
3957
</style>
40-
<!-- <style name="AppThemeMaterialButton" parent="Theme.MaterialComponents.Light">
41-
<item name="materialButtonStyle">@style/Button.Custom</item>
58+
<style name="Button.Custom" parent="@style/Widget.MaterialComponents.Button">
59+
<item name="android:padding">0dp</item>
60+
<item name="android:minWidth">88dp</item>
61+
<item name="android:minHeight">36dp</item>
62+
<item name="android:insetLeft">0dp</item>
63+
<item name="android:insetRight">0dp</item>
64+
<item name="android:insetTop">0dp</item>
65+
<item name="android:insetBottom">0dp</item>
4266
</style>
43-
<style name="AppThemeFlatMaterialButton" parent="Theme.MaterialComponents.Light">
44-
<item name="materialButtonStyle">@style/UnelevatedButton.Custom</item>
67+
<style name="Button.Custom.Icon" parent="@style/Widget.MaterialComponents.Button.Icon">
68+
<item name="android:padding">0dp</item>
69+
<item name="android:minWidth">88dp</item>
70+
<item name="android:minHeight">36dp</item>
71+
<item name="android:insetLeft">0dp</item>
72+
<item name="android:insetRight">0dp</item>
73+
<item name="android:insetTop">0dp</item>
74+
<item name="android:insetBottom">0dp</item>
4575
</style>
46-
<style name="AppThemeTextMaterialButton" parent="Theme.MaterialComponents.Light">
47-
<item name="materialButtonStyle">@style/TextButton.Custom</item>
48-
</style> -->
49-
76+
<!-- <style name="AppThemeMaterialButton" parent="Theme.MaterialComponents.Light"><item name="materialButtonStyle">@style/Button.Custom</item></style><style name="AppThemeFlatMaterialButton" parent="Theme.MaterialComponents.Light"><item name="materialButtonStyle">@style/UnelevatedButton.Custom</item></style><style name="AppThemeTextMaterialButton" parent="Theme.MaterialComponents.Light"><item name="materialButtonStyle">@style/TextButton.Custom</item></style> -->
5077
</resources>

src/button/button-common.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, CSSType, Color, Property } from '@nativescript/core';
1+
import { Button, CSSType, Color, ImageAsset, ImageSource, Property, Utils } from '@nativescript/core';
22
import { cssProperty } from '@nativescript-community/ui-material-core';
33
import { VerticalTextAlignment } from '@nativescript-community/text';
44

@@ -9,9 +9,77 @@ export abstract class ButtonBase extends Button {
99
@cssProperty dynamicElevationOffset: number;
1010
@cssProperty rippleColor: Color;
1111
@cssProperty verticalTextAlignment: VerticalTextAlignment;
12+
13+
public imageSource: ImageSource;
14+
public src: string | ImageSource;
15+
public isLoading: boolean;
16+
/**
17+
* @internal //copied from image common
18+
*/
19+
protected async _createImageSourceFromSrc(value: string | ImageSource | ImageAsset) {
20+
const originalValue = value;
21+
if (typeof value === 'string' || value instanceof String) {
22+
value = value.trim();
23+
this.imageSource = null;
24+
this['_url'] = value;
25+
26+
this.isLoading = true;
27+
28+
let source: ImageSource;
29+
const imageLoaded = () => {
30+
const currentValue = this.src;
31+
if (currentValue !== originalValue) {
32+
return;
33+
}
34+
this.imageSource = source;
35+
this.isLoading = false;
36+
};
37+
38+
if (Utils.isDataURI(value)) {
39+
const base64Data = value.split(',')[1];
40+
if (base64Data !== undefined) {
41+
source = await ImageSource.fromBase64(base64Data);
42+
imageLoaded();
43+
}
44+
} else if (Utils.isFileOrResourcePath(value)) {
45+
if (value.indexOf(Utils.RESOURCE_PREFIX) === 0) {
46+
const resPath = value.substr(Utils.RESOURCE_PREFIX.length);
47+
source = await ImageSource.fromResource(resPath);
48+
imageLoaded();
49+
} else {
50+
source = await ImageSource.fromFile(value);
51+
imageLoaded();
52+
}
53+
} else {
54+
this.imageSource = null;
55+
source = await ImageSource.fromUrl(value);
56+
imageLoaded();
57+
}
58+
} else if (value instanceof ImageSource) {
59+
// Support binding the imageSource trough the src property
60+
this.imageSource = value;
61+
this.isLoading = false;
62+
} else if (value instanceof ImageAsset) {
63+
ImageSource.fromAsset(value).then((result) => {
64+
this.imageSource = result;
65+
this.isLoading = false;
66+
});
67+
} else {
68+
// native source
69+
this.imageSource = new ImageSource(value);
70+
this.isLoading = false;
71+
}
72+
}
1273
}
1374

1475
export const variantProperty = new Property<ButtonBase, string>({
1576
name: 'variant'
1677
});
1778
variantProperty.register(ButtonBase);
79+
export const imageSourceProperty = new Property<ButtonBase, ImageSource>({ name: 'imageSource' });
80+
81+
export const srcProperty = new Property<ButtonBase, any>({
82+
name: 'src'
83+
});
84+
imageSourceProperty.register(ButtonBase);
85+
srcProperty.register(ButtonBase);

src/button/button.android.ts

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@ import { createStateListAnimator, getLayout, isPostLollipop } from '@nativescrip
44
import {
55
Background,
66
Color,
7+
Font,
8+
ImageSource,
79
Length,
810
TextTransform,
11+
Utils,
912
androidDynamicElevationOffsetProperty,
1013
androidElevationProperty,
1114
backgroundInternalProperty,
15+
colorProperty,
1216
profile,
13-
textTransformProperty,
17+
textTransformProperty
1418
} from '@nativescript/core';
15-
import { ButtonBase } from './button-common';
19+
import { ButtonBase, imageSourceProperty, srcProperty } from './button-common';
1620

1721
let LayoutInflater: typeof android.view.LayoutInflater;
1822

19-
let textId;
20-
let containedId;
21-
let flatId;
22-
let outlineId;
23-
let grayColorStateList: android.content.res.ColorStateList;
23+
const layoutIds = {};
2424

2525
export class Button extends ButtonBase {
2626
nativeViewProtected: com.google.android.material.button.MaterialButton;
@@ -33,39 +33,39 @@ export class Button extends ButtonBase {
3333
let layoutId;
3434
const variant = this.variant;
3535
// let layoutIdName = 'material_button';
36+
let layoutStringId: string;
3637
if (variant === 'text') {
37-
if (!textId) {
38-
textId = getLayout('material_button_text');
39-
}
40-
layoutId = textId;
38+
layoutStringId = 'material_button_text';
4139
} else if (variant === 'flat') {
42-
if (!flatId) {
43-
flatId = getLayout('material_button_flat');
44-
}
45-
layoutId = flatId;
40+
layoutStringId = 'material_button_flat';
4641
} else if (variant === 'outline') {
47-
if (!outlineId) {
48-
outlineId = getLayout('material_button_outline');
49-
}
50-
layoutId = outlineId;
42+
layoutStringId = 'material_button_outline';
5143
} else {
52-
if (!containedId) {
53-
containedId = getLayout('material_button');
54-
}
55-
layoutId = containedId;
44+
layoutStringId = 'material_button';
5645
// contained
5746
// we need to set the default through css or user would not be able to overload it through css...
5847
this.style['css:margin-left'] = 10;
5948
this.style['css:margin-right'] = 10;
6049
this.style['css:margin-top'] = 12;
6150
this.style['css:margin-bottom'] = 12;
6251
}
52+
if (this.src) {
53+
layoutStringId += '_icon';
54+
}
55+
layoutId = layoutIds[layoutStringId];
56+
if (!layoutId) {
57+
layoutId = layoutIds[layoutStringId] = getLayout(layoutStringId);
58+
}
6359
// const layoutId = getLayout(layoutIdName);
6460
if (!LayoutInflater) {
6561
LayoutInflater = android.view.LayoutInflater;
6662
}
6763
const view = android.view.LayoutInflater.from(this._context).inflate(layoutId, null, false) as com.google.android.material.button.MaterialButton;
68-
64+
if (this.src) {
65+
layoutStringId += '_icon';
66+
view.setIconGravity(0x2); //com.google.android.material.button.MaterialButton.ICON_GRAVITY_TEXT_START
67+
// view.setIconSize(Utils.layout.toDevicePixels(24));
68+
}
6969
// if (variant === 'outline') {
7070
// view.setStrokeWidth(1);
7171
// if (!grayColorStateList) {
@@ -170,4 +170,24 @@ export class Button extends ButtonBase {
170170
break;
171171
}
172172
}
173+
174+
[imageSourceProperty.setNative](value: ImageSource) {
175+
const nativeView = this.nativeViewProtected;
176+
if (value && value.android) {
177+
const fontSize = this.fontSize || nativeView.getTextSize();
178+
nativeView.setIconSize(Math.min(value.width, Utils.layout.toDevicePixels(fontSize)));
179+
nativeView.setIcon(new android.graphics.drawable.BitmapDrawable(value.android));
180+
} else {
181+
nativeView.setIcon(null);
182+
}
183+
}
184+
185+
[srcProperty.setNative](value: any) {
186+
this._createImageSourceFromSrc(value);
187+
}
188+
[colorProperty.setNative](value) {
189+
const color = value instanceof Color ? value.android : value;
190+
super[colorProperty.setNative](color);
191+
this.nativeViewProtected.setIconTint(android.content.res.ColorStateList.valueOf(color));
192+
}
173193
}

src/button/button.ios.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Background,
44
Color,
55
Font,
6+
ImageSource,
67
Screen,
78
TextTransform,
89
Utils,
@@ -11,10 +12,11 @@ import {
1112
borderBottomRightRadiusProperty,
1213
borderTopLeftRadiusProperty,
1314
borderTopRightRadiusProperty,
15+
colorProperty,
1416
fontInternalProperty,
15-
textTransformProperty,
17+
textTransformProperty
1618
} from '@nativescript/core';
17-
import { ButtonBase } from './button-common';
19+
import { ButtonBase, imageSourceProperty, srcProperty } from './button-common';
1820

1921
let buttonScheme: MDCContainerScheme;
2022
function getButtonScheme() {
@@ -53,7 +55,7 @@ class MDButtonObserverClass extends NSObject {
5355
top,
5456
left: inset.left,
5557
bottom: inset.bottom,
56-
right: inset.right,
58+
right: inset.right
5759
};
5860
break;
5961

@@ -66,7 +68,7 @@ class MDButtonObserverClass extends NSObject {
6668
top: top + topCorrect,
6769
left: inset.left,
6870
bottom: inset.bottom,
69-
right: inset.right,
71+
right: inset.right
7072
};
7173
break;
7274
}
@@ -80,7 +82,7 @@ class MDButtonObserverClass extends NSObject {
8082
top: top + bottomCorrect,
8183
left: inset.left,
8284
bottom: inset.bottom,
83-
right: inset.right,
85+
right: inset.right
8486
};
8587
break;
8688
}
@@ -124,6 +126,8 @@ export class Button extends ButtonBase {
124126

125127
public createNativeView() {
126128
const view = MDCButton.new();
129+
view.imageView.contentMode = UIViewContentMode.ScaleAspectFit;
130+
127131
const colorScheme = themer.getAppColorScheme() as MDCSemanticColorScheme;
128132
const scheme = MDCContainerScheme.new();
129133
scheme.colorScheme = colorScheme;
@@ -164,7 +168,7 @@ export class Button extends ButtonBase {
164168
}
165169

166170
[textTransformProperty.setNative](value: TextTransform) {
167-
this.nativeViewProtected.uppercaseTitle = (value !== 'none');
171+
this.nativeViewProtected.uppercaseTitle = value !== 'none';
168172
}
169173
[rippleColorProperty.setNative](color: Color) {
170174
this.nativeViewProtected.inkColor = getRippleColor(color);
@@ -247,4 +251,19 @@ export class Button extends ButtonBase {
247251
nativeView.setTitleFontForState(font, UIControlState.Normal);
248252
}
249253
}
254+
public _setNativeImage(nativeImage: UIImage) {
255+
this.nativeViewProtected.setImageForState(nativeImage ? nativeImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate) : nativeImage, UIControlState.Normal);
256+
}
257+
[imageSourceProperty.setNative](value: ImageSource) {
258+
this._setNativeImage(value ? value.ios : null);
259+
}
260+
261+
[srcProperty.setNative](value: any) {
262+
this._createImageSourceFromSrc(value);
263+
}
264+
[colorProperty.setNative](value) {
265+
const color = value instanceof Color ? value.ios : value;
266+
super[colorProperty.setNative](color);
267+
this.nativeViewProtected.setImageTintColorForState(color, UIControlState.Normal);
268+
}
250269
}

0 commit comments

Comments
 (0)