react-native-platform-components 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -30
- package/android/src/main/java/com/platformcomponents/PCButtonGroupView.kt +498 -0
- package/android/src/main/java/com/platformcomponents/PCButtonGroupViewManager.kt +191 -0
- package/android/src/main/java/com/platformcomponents/PCButtonSupport.kt +127 -0
- package/android/src/main/java/com/platformcomponents/PCButtonView.kt +345 -0
- package/android/src/main/java/com/platformcomponents/PCButtonViewManager.kt +126 -0
- package/android/src/main/java/com/platformcomponents/PCExpressive.kt +107 -0
- package/android/src/main/java/com/platformcomponents/PCFloatingToolbarView.kt +115 -0
- package/android/src/main/java/com/platformcomponents/PCFloatingToolbarViewManager.kt +65 -0
- package/android/src/main/java/com/platformcomponents/PCSegmentedControlView.kt +11 -3
- package/android/src/main/java/com/platformcomponents/PCSegmentedControlViewManager.kt +6 -1
- package/android/src/main/java/com/platformcomponents/PCTextFieldView.kt +1018 -0
- package/android/src/main/java/com/platformcomponents/PCTextFieldViewManager.kt +248 -0
- package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +4 -0
- package/android/src/main/res/values/attrs.xml +13 -0
- package/android/src/main/res/values/styles.xml +183 -0
- package/ios/PCButton.h +10 -0
- package/ios/PCButton.mm +196 -0
- package/ios/PCButtonGroup.h +10 -0
- package/ios/PCButtonGroup.mm +256 -0
- package/ios/PCButtonGroupView.swift +254 -0
- package/ios/PCButtonSupport.swift +141 -0
- package/ios/PCButtonView.swift +155 -0
- package/ios/PCFloatingToolbar.h +10 -0
- package/ios/PCFloatingToolbar.mm +76 -0
- package/ios/PCFloatingToolbarView.swift +95 -0
- package/ios/PCTextField.h +10 -0
- package/ios/PCTextField.mm +391 -0
- package/ios/PCTextFieldView.swift +1012 -0
- package/lib/commonjs/Button.js +77 -0
- package/lib/commonjs/Button.js.map +1 -0
- package/lib/commonjs/ButtonGroup.js +99 -0
- package/lib/commonjs/ButtonGroup.js.map +1 -0
- package/lib/commonjs/ButtonGroupNativeComponent.ts +121 -0
- package/lib/commonjs/ButtonNativeComponent.ts +76 -0
- package/lib/commonjs/FloatingToolbar.js +69 -0
- package/lib/commonjs/FloatingToolbar.js.map +1 -0
- package/lib/commonjs/FloatingToolbarNativeComponent.ts +31 -0
- package/lib/commonjs/SegmentedControl.js +11 -85
- package/lib/commonjs/SegmentedControl.js.map +1 -1
- package/lib/commonjs/SegmentedControlNativeComponent.ts +3 -0
- package/lib/commonjs/TextField.js +195 -0
- package/lib/commonjs/TextField.js.map +1 -0
- package/lib/commonjs/TextFieldNativeComponent.ts +218 -0
- package/lib/commonjs/focusRegistry.js +78 -0
- package/lib/commonjs/focusRegistry.js.map +1 -0
- package/lib/commonjs/icons.js +92 -0
- package/lib/commonjs/icons.js.map +1 -0
- package/lib/commonjs/index.js +66 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +32 -2
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/labelStyle.js +27 -0
- package/lib/commonjs/labelStyle.js.map +1 -0
- package/lib/commonjs/reactNativeInternals.d.js +2 -0
- package/lib/commonjs/reactNativeInternals.d.js.map +1 -0
- package/lib/module/Button.js +70 -0
- package/lib/module/Button.js.map +1 -0
- package/lib/module/ButtonGroup.js +92 -0
- package/lib/module/ButtonGroup.js.map +1 -0
- package/lib/module/ButtonGroupNativeComponent.ts +121 -0
- package/lib/module/ButtonNativeComponent.ts +76 -0
- package/lib/module/FloatingToolbar.js +62 -0
- package/lib/module/FloatingToolbar.js.map +1 -0
- package/lib/module/FloatingToolbarNativeComponent.ts +31 -0
- package/lib/module/SegmentedControl.js +12 -87
- package/lib/module/SegmentedControl.js.map +1 -1
- package/lib/module/SegmentedControlNativeComponent.ts +3 -0
- package/lib/module/TextField.js +189 -0
- package/lib/module/TextField.js.map +1 -0
- package/lib/module/TextFieldNativeComponent.ts +218 -0
- package/lib/module/focusRegistry.js +74 -0
- package/lib/module/focusRegistry.js.map +1 -0
- package/lib/module/icons.js +87 -0
- package/lib/module/icons.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +28 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/labelStyle.js +23 -0
- package/lib/module/labelStyle.js.map +1 -0
- package/lib/module/reactNativeInternals.d.js +2 -0
- package/lib/module/reactNativeInternals.d.js.map +1 -0
- package/lib/typescript/commonjs/docs/sidebars.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Button.d.ts +76 -0
- package/lib/typescript/commonjs/src/Button.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/ButtonGroup.d.ts +102 -0
- package/lib/typescript/commonjs/src/ButtonGroup.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts +93 -0
- package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts +57 -0
- package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/FloatingToolbar.d.ts +45 -0
- package/lib/typescript/commonjs/src/FloatingToolbar.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts +24 -0
- package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SegmentedControl.d.ts +17 -46
- package/lib/typescript/commonjs/src/SegmentedControl.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts +2 -0
- package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/TextField.d.ts +174 -0
- package/lib/typescript/commonjs/src/TextField.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts +162 -0
- package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/focusRegistry.d.ts +13 -0
- package/lib/typescript/commonjs/src/focusRegistry.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/icons.d.ts +52 -0
- package/lib/typescript/commonjs/src/icons.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.web.d.ts +121 -0
- package/lib/typescript/commonjs/src/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/labelStyle.d.ts +20 -0
- package/lib/typescript/commonjs/src/labelStyle.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/sharedTypes.d.ts +6 -1
- package/lib/typescript/commonjs/src/sharedTypes.d.ts.map +1 -1
- package/lib/typescript/module/docs/sidebars.d.ts.map +1 -1
- package/lib/typescript/module/src/Button.d.ts +76 -0
- package/lib/typescript/module/src/Button.d.ts.map +1 -0
- package/lib/typescript/module/src/ButtonGroup.d.ts +102 -0
- package/lib/typescript/module/src/ButtonGroup.d.ts.map +1 -0
- package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts +93 -0
- package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/ButtonNativeComponent.d.ts +57 -0
- package/lib/typescript/module/src/ButtonNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/FloatingToolbar.d.ts +45 -0
- package/lib/typescript/module/src/FloatingToolbar.d.ts.map +1 -0
- package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts +24 -0
- package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/SegmentedControl.d.ts +17 -46
- package/lib/typescript/module/src/SegmentedControl.d.ts.map +1 -1
- package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts +2 -0
- package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/TextField.d.ts +174 -0
- package/lib/typescript/module/src/TextField.d.ts.map +1 -0
- package/lib/typescript/module/src/TextFieldNativeComponent.d.ts +162 -0
- package/lib/typescript/module/src/TextFieldNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/focusRegistry.d.ts +13 -0
- package/lib/typescript/module/src/focusRegistry.d.ts.map +1 -0
- package/lib/typescript/module/src/icons.d.ts +52 -0
- package/lib/typescript/module/src/icons.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +6 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/index.web.d.ts +121 -0
- package/lib/typescript/module/src/index.web.d.ts.map +1 -1
- package/lib/typescript/module/src/labelStyle.d.ts +20 -0
- package/lib/typescript/module/src/labelStyle.d.ts.map +1 -0
- package/lib/typescript/module/src/sharedTypes.d.ts +6 -1
- package/lib/typescript/module/src/sharedTypes.d.ts.map +1 -1
- package/package.json +25 -4
- package/react-native.config.js +3 -0
- package/shared/PCButtonComponentDescriptors-custom.h +22 -0
- package/shared/PCButtonGroupComponentDescriptors-custom.h +22 -0
- package/shared/PCButtonGroupShadowNode-custom.cpp +48 -0
- package/shared/PCButtonGroupShadowNode-custom.h +55 -0
- package/shared/PCButtonGroupState-custom.h +61 -0
- package/shared/PCButtonShadowNode-custom.cpp +48 -0
- package/shared/PCButtonShadowNode-custom.h +55 -0
- package/shared/PCButtonState-custom.h +61 -0
- package/shared/PCTextFieldComponentDescriptors-custom.h +22 -0
- package/shared/PCTextFieldShadowNode-custom.cpp +47 -0
- package/shared/PCTextFieldShadowNode-custom.h +55 -0
- package/shared/PCTextFieldState-custom.h +61 -0
- package/shared/react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h +3 -0
- package/src/Button.tsx +144 -0
- package/src/ButtonGroup.tsx +233 -0
- package/src/ButtonGroupNativeComponent.ts +121 -0
- package/src/ButtonNativeComponent.ts +76 -0
- package/src/FloatingToolbar.tsx +113 -0
- package/src/FloatingToolbarNativeComponent.ts +31 -0
- package/src/SegmentedControl.tsx +26 -118
- package/src/SegmentedControlNativeComponent.ts +3 -0
- package/src/TextField.tsx +494 -0
- package/src/TextFieldNativeComponent.ts +218 -0
- package/src/focusRegistry.ts +89 -0
- package/src/icons.ts +116 -0
- package/src/index.tsx +6 -0
- package/src/index.web.tsx +194 -0
- package/src/labelStyle.ts +36 -0
- package/src/reactNativeInternals.d.ts +22 -0
- package/src/sharedTypes.ts +7 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
package com.platformcomponents
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReadableArray
|
|
5
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.facebook.react.uimanager.ReactStylesDiffMap
|
|
7
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
8
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
11
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
12
|
+
import com.facebook.react.uimanager.events.Event
|
|
13
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
14
|
+
import com.facebook.react.viewmanagers.PCButtonGroupManagerDelegate
|
|
15
|
+
import com.facebook.react.viewmanagers.PCButtonGroupManagerInterface
|
|
16
|
+
import com.platformcomponents.PCButtonSupport.doubleOr
|
|
17
|
+
import com.platformcomponents.PCButtonSupport.stringOr
|
|
18
|
+
|
|
19
|
+
class PCButtonGroupViewManager :
|
|
20
|
+
SimpleViewManager<PCButtonGroupView>(),
|
|
21
|
+
PCButtonGroupManagerInterface<PCButtonGroupView> {
|
|
22
|
+
|
|
23
|
+
companion object {
|
|
24
|
+
private const val TAG = "PCButtonGroup"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private val delegate: ViewManagerDelegate<PCButtonGroupView> =
|
|
28
|
+
PCButtonGroupManagerDelegate(this)
|
|
29
|
+
|
|
30
|
+
override fun getName(): String = "PCButtonGroup"
|
|
31
|
+
|
|
32
|
+
override fun getDelegate(): ViewManagerDelegate<PCButtonGroupView> = delegate
|
|
33
|
+
|
|
34
|
+
override fun createViewInstance(reactContext: ThemedReactContext): PCButtonGroupView {
|
|
35
|
+
return PCButtonGroupView(reactContext)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pass the StateWrapper to the view so it can update Fabric state with its
|
|
40
|
+
* natural size, and report it right away so the first layout already fits.
|
|
41
|
+
*/
|
|
42
|
+
override fun updateState(
|
|
43
|
+
view: PCButtonGroupView,
|
|
44
|
+
props: ReactStylesDiffMap,
|
|
45
|
+
stateWrapper: StateWrapper
|
|
46
|
+
): Any? {
|
|
47
|
+
view.stateWrapper = stateWrapper
|
|
48
|
+
view.reportIntrinsicSize()
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: PCButtonGroupView) {
|
|
53
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
|
|
54
|
+
|
|
55
|
+
view.onPress = { index, value ->
|
|
56
|
+
dispatcher?.dispatchEvent(PressEvent(view.id, index, value))
|
|
57
|
+
}
|
|
58
|
+
view.onSelectionChange = { values ->
|
|
59
|
+
dispatcher?.dispatchEvent(SelectionEvent(view.id, values))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// buttons: array of {label, value, disabled, iconType, iconName, iconUri,
|
|
64
|
+
// iconScale, iconTinted, accessibilityLabel}
|
|
65
|
+
override fun setButtons(view: PCButtonGroupView, value: ReadableArray?) {
|
|
66
|
+
val out = ArrayList<PCButtonGroupView.Item>()
|
|
67
|
+
if (value != null) {
|
|
68
|
+
for (i in 0 until value.size()) {
|
|
69
|
+
val m = value.getMap(i) ?: continue
|
|
70
|
+
out.add(
|
|
71
|
+
PCButtonGroupView.Item(
|
|
72
|
+
label = m.stringOr("label", ""),
|
|
73
|
+
value = m.stringOr("value", ""),
|
|
74
|
+
disabled = m.stringOr("disabled", "enabled") == "disabled",
|
|
75
|
+
icon = PCButtonSupport.parseIcon(m),
|
|
76
|
+
accessibilityLabel = m.stringOr("accessibilityLabel", "")
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
view.applyItems(out)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override fun setVariant(view: PCButtonGroupView, value: String?) {
|
|
85
|
+
view.applyVariant(value)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
override fun setSize(view: PCButtonGroupView, value: String?) {
|
|
89
|
+
view.applySize(value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override fun setShape(view: PCButtonGroupView, value: String?) {
|
|
93
|
+
view.applyShape(value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Spec sentinel: "true" | "false"
|
|
97
|
+
override fun setConnected(view: PCButtonGroupView, value: String?) {
|
|
98
|
+
view.applyConnected(value == "true")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Negative means the style's default spacing
|
|
102
|
+
override fun setSpacing(view: PCButtonGroupView, value: Double) {
|
|
103
|
+
view.applySpacing(value.toFloat())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun setSelection(view: PCButtonGroupView, value: String?) {
|
|
107
|
+
view.applySelection(value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override fun setSelectedValues(view: PCButtonGroupView, value: ReadableArray?) {
|
|
111
|
+
val out = ArrayList<String>()
|
|
112
|
+
if (value != null) {
|
|
113
|
+
for (i in 0 until value.size()) {
|
|
114
|
+
value.getString(i)?.let { out.add(it) }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
view.applySelectedValues(out)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override fun setSelectionRequired(view: PCButtonGroupView, value: String?) {
|
|
121
|
+
view.applySelectionRequired(value == "true")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override fun setInteractivity(view: PCButtonGroupView, value: String?) {
|
|
125
|
+
view.applyInteractivity(value)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Colors arrive already processed by React Native (ARGB ints)
|
|
129
|
+
override fun setColor(view: PCButtonGroupView, value: Int?) {
|
|
130
|
+
view.applyColors(value, view.foregroundColor, view.rippleColor, view.strokeColor)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
override fun setForegroundColor(view: PCButtonGroupView, value: Int?) {
|
|
134
|
+
view.applyColors(view.containerColor, value, view.rippleColor, view.strokeColor)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override fun setAndroidRippleColor(view: PCButtonGroupView, value: Int?) {
|
|
138
|
+
view.applyColors(view.containerColor, view.foregroundColor, value, view.strokeColor)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override fun setAndroidStrokeColor(view: PCButtonGroupView, value: Int?) {
|
|
142
|
+
view.applyColors(view.containerColor, view.foregroundColor, view.rippleColor, value)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// labelStyle: {fontFamily, fontSize, fontWeight, fontStyle}; empty / 0 = default
|
|
146
|
+
override fun setLabelStyle(view: PCButtonGroupView, value: ReadableMap?) {
|
|
147
|
+
view.applyLabelStyle(
|
|
148
|
+
fontFamily = value?.stringOr("fontFamily", "") ?: "",
|
|
149
|
+
fontSize = value?.doubleOr("fontSize", 0.0)?.toFloat() ?: 0f,
|
|
150
|
+
fontWeight = value?.stringOr("fontWeight", "") ?: "",
|
|
151
|
+
fontStyle = value?.stringOr("fontStyle", "") ?: ""
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// android: {overflow, material}
|
|
156
|
+
override fun setAndroid(view: PCButtonGroupView, value: ReadableMap?) {
|
|
157
|
+
view.applyOverflow(value?.stringOr("overflow", "none"))
|
|
158
|
+
view.applyMaterial(value?.stringOr("material", "expressive"))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- Events ---
|
|
162
|
+
private class PressEvent(
|
|
163
|
+
surfaceId: Int,
|
|
164
|
+
private val index: Int,
|
|
165
|
+
private val value: String
|
|
166
|
+
) : Event<PressEvent>(surfaceId) {
|
|
167
|
+
override fun getEventName(): String = "topButtonPress"
|
|
168
|
+
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
169
|
+
val payload = Arguments.createMap().apply {
|
|
170
|
+
putInt("index", index)
|
|
171
|
+
putString("value", value)
|
|
172
|
+
}
|
|
173
|
+
rctEventEmitter.receiveEvent(viewTag, eventName, payload)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private class SelectionEvent(
|
|
178
|
+
surfaceId: Int,
|
|
179
|
+
private val values: List<String>
|
|
180
|
+
) : Event<SelectionEvent>(surfaceId) {
|
|
181
|
+
override fun getEventName(): String = "topGroupSelectionChange"
|
|
182
|
+
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
183
|
+
val array = Arguments.createArray()
|
|
184
|
+
values.forEach { array.pushString(it) }
|
|
185
|
+
val payload = Arguments.createMap().apply {
|
|
186
|
+
putArray("values", array)
|
|
187
|
+
}
|
|
188
|
+
rctEventEmitter.receiveEvent(viewTag, eventName, payload)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package com.platformcomponents
|
|
2
|
+
|
|
3
|
+
import android.content.res.ColorStateList
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.drawable.BitmapDrawable
|
|
6
|
+
import android.util.TypedValue
|
|
7
|
+
import android.widget.TextView
|
|
8
|
+
import com.facebook.react.bridge.ReadableMap
|
|
9
|
+
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper
|
|
10
|
+
import com.facebook.react.views.text.ReactTypefaceUtils
|
|
11
|
+
import com.google.android.material.button.MaterialButton
|
|
12
|
+
|
|
13
|
+
/** Icon, font and color handling shared by Button and ButtonGroup. */
|
|
14
|
+
object PCButtonSupport {
|
|
15
|
+
data class Icon(
|
|
16
|
+
/** "", "sfSymbol" (ignored on Android), "drawable" or "image" */
|
|
17
|
+
val type: String,
|
|
18
|
+
val name: String,
|
|
19
|
+
val uri: String,
|
|
20
|
+
val scale: Float,
|
|
21
|
+
val tinted: Boolean
|
|
22
|
+
) {
|
|
23
|
+
val isPresent: Boolean get() = type == "drawable" || type == "image"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
val NO_ICON = Icon("", "", "", 1f, true)
|
|
27
|
+
|
|
28
|
+
fun ReadableMap.stringOr(key: String, fallback: String): String =
|
|
29
|
+
if (hasKey(key) && !isNull(key)) getString(key) ?: fallback else fallback
|
|
30
|
+
|
|
31
|
+
fun ReadableMap.doubleOr(key: String, fallback: Double): Double =
|
|
32
|
+
if (hasKey(key) && !isNull(key)) getDouble(key) else fallback
|
|
33
|
+
|
|
34
|
+
/** Reads the flat icon fields (iconType, iconName, ...) from a props map. */
|
|
35
|
+
fun parseIcon(map: ReadableMap?): Icon {
|
|
36
|
+
if (map == null) return NO_ICON
|
|
37
|
+
val scale = map.doubleOr("iconScale", 1.0)
|
|
38
|
+
return Icon(
|
|
39
|
+
type = map.stringOr("iconType", ""),
|
|
40
|
+
name = map.stringOr("iconName", ""),
|
|
41
|
+
uri = map.stringOr("iconUri", ""),
|
|
42
|
+
scale = if (scale > 0) scale.toFloat() else 1f,
|
|
43
|
+
tinted = map.stringOr("iconTinted", "true") != "false"
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves a button's icon. Drawable names and release-bundled assets (which
|
|
49
|
+
* React Native ships as drawable resources) resolve synchronously; other image
|
|
50
|
+
* URIs load in the background and are applied when they arrive, if
|
|
51
|
+
* [isCurrent] still holds.
|
|
52
|
+
*/
|
|
53
|
+
fun applyIcon(button: MaterialButton, icon: Icon, isCurrent: () -> Boolean, onLoaded: () -> Unit) {
|
|
54
|
+
if (!icon.tinted) {
|
|
55
|
+
button.iconTint = null
|
|
56
|
+
}
|
|
57
|
+
val context = button.context
|
|
58
|
+
when (icon.type) {
|
|
59
|
+
"drawable" -> {
|
|
60
|
+
button.icon = ResourceDrawableIdHelper.instance.getResourceDrawable(context, icon.name)
|
|
61
|
+
}
|
|
62
|
+
"image" -> {
|
|
63
|
+
val uri = icon.uri
|
|
64
|
+
if (!uri.contains(':')) {
|
|
65
|
+
button.icon = ResourceDrawableIdHelper.instance.getResourceDrawable(context, uri)
|
|
66
|
+
} else {
|
|
67
|
+
PCImageLoader.load(context, uri, icon.scale) { bitmap ->
|
|
68
|
+
if (bitmap == null || !isCurrent()) return@load
|
|
69
|
+
button.icon = BitmapDrawable(context.resources, bitmap)
|
|
70
|
+
onLoaded()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Applies the label font on top of the style's; empty / 0 keep the default. */
|
|
78
|
+
fun applyFont(button: TextView, family: String, size: Float, weight: String, style: String) {
|
|
79
|
+
if (size > 0) {
|
|
80
|
+
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
|
81
|
+
}
|
|
82
|
+
if (family.isNotEmpty() || weight.isNotEmpty() || style.isNotEmpty()) {
|
|
83
|
+
button.typeface = ReactTypefaceUtils.applyStyles(
|
|
84
|
+
button.typeface,
|
|
85
|
+
ReactTypefaceUtils.parseFontStyle(style.ifEmpty { null }),
|
|
86
|
+
ReactTypefaceUtils.parseFontWeight(weight.ifEmpty { null }),
|
|
87
|
+
family.ifEmpty { null },
|
|
88
|
+
button.context.assets
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Applies the color props on top of the style's colors, keeping the style's
|
|
95
|
+
* disabled colors.
|
|
96
|
+
*/
|
|
97
|
+
fun applyColors(
|
|
98
|
+
button: MaterialButton,
|
|
99
|
+
container: Int?,
|
|
100
|
+
foreground: Int?,
|
|
101
|
+
ripple: Int?,
|
|
102
|
+
stroke: Int?,
|
|
103
|
+
iconTinted: Boolean
|
|
104
|
+
) {
|
|
105
|
+
val disabledState = intArrayOf(-android.R.attr.state_enabled)
|
|
106
|
+
val states = arrayOf(disabledState, intArrayOf())
|
|
107
|
+
|
|
108
|
+
container?.let { color ->
|
|
109
|
+
val theme = button.backgroundTintList
|
|
110
|
+
val disabled = theme?.getColorForState(disabledState, theme.defaultColor) ?: Color.TRANSPARENT
|
|
111
|
+
button.backgroundTintList = ColorStateList(states, intArrayOf(disabled, color))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
foreground?.let { color ->
|
|
115
|
+
val theme = button.textColors
|
|
116
|
+
val disabled = theme.getColorForState(disabledState, theme.defaultColor)
|
|
117
|
+
val tint = ColorStateList(states, intArrayOf(disabled, color))
|
|
118
|
+
button.setTextColor(tint)
|
|
119
|
+
if (iconTinted) {
|
|
120
|
+
button.iconTint = tint
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
ripple?.let { button.rippleColor = ColorStateList.valueOf(it) }
|
|
125
|
+
stroke?.let { button.strokeColor = ColorStateList.valueOf(it) }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
package com.platformcomponents
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.text.TextUtils
|
|
5
|
+
import android.view.View
|
|
6
|
+
import android.view.ViewParent
|
|
7
|
+
import android.widget.FrameLayout
|
|
8
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
9
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
10
|
+
import com.facebook.react.uimanager.ReactCompoundViewGroup
|
|
11
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
12
|
+
import com.facebook.react.views.scroll.ReactScrollViewHelper
|
|
13
|
+
import com.google.android.material.button.MaterialButton
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A Material 3 Expressive button. Hosts a MaterialButton, rebuilt whenever a
|
|
17
|
+
* prop that the widget reads at construction changes, and reports its
|
|
18
|
+
* intrinsic size to Fabric so the React view wraps its content.
|
|
19
|
+
*/
|
|
20
|
+
class PCButtonView(context: Context) :
|
|
21
|
+
FrameLayout(context),
|
|
22
|
+
ReactScrollViewHelper.HasStateWrapper,
|
|
23
|
+
ReactCompoundViewGroup {
|
|
24
|
+
|
|
25
|
+
companion object {
|
|
26
|
+
private const val TAG = "PCButton"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// --- State Wrapper for Fabric state updates ---
|
|
30
|
+
override var stateWrapper: StateWrapper? = null
|
|
31
|
+
|
|
32
|
+
private var lastReportedWidth: Float = 0f
|
|
33
|
+
private var lastReportedHeight: Float = 0f
|
|
34
|
+
|
|
35
|
+
// --- Props ---
|
|
36
|
+
var label: String = ""
|
|
37
|
+
var icon: PCButtonSupport.Icon = PCButtonSupport.NO_ICON
|
|
38
|
+
var variant: PCExpressive.Variant = PCExpressive.Variant.FILLED
|
|
39
|
+
var size: String = "small"
|
|
40
|
+
var shape: String = "round"
|
|
41
|
+
var interactivity: String = "enabled" // "enabled" | "disabled"
|
|
42
|
+
var spokenLabel: String = ""
|
|
43
|
+
var expressive: Boolean = true // android.material: "expressive" | "m3"
|
|
44
|
+
|
|
45
|
+
// --- Styling (null / empty = Material theme default) ---
|
|
46
|
+
var containerColor: Int? = null
|
|
47
|
+
var foregroundColor: Int? = null
|
|
48
|
+
var rippleColor: Int? = null
|
|
49
|
+
var strokeColor: Int? = null
|
|
50
|
+
var labelFontFamily: String = ""
|
|
51
|
+
var labelFontSize: Float = 0f
|
|
52
|
+
var labelFontWeight: String = ""
|
|
53
|
+
var labelFontStyle: String = ""
|
|
54
|
+
|
|
55
|
+
// --- Events ---
|
|
56
|
+
var onPress: (() -> Unit)? = null
|
|
57
|
+
|
|
58
|
+
// --- UI ---
|
|
59
|
+
private var button: MaterialButton? = null
|
|
60
|
+
|
|
61
|
+
/** Bumped on every rebuild so late image loads can't touch a stale button. */
|
|
62
|
+
private var rebuildGeneration = 0
|
|
63
|
+
|
|
64
|
+
/** The FloatingToolbar button overlay the widget was built with (0 = none). */
|
|
65
|
+
private var builtToolbarOverlay = 0
|
|
66
|
+
|
|
67
|
+
// --- Native theme ---
|
|
68
|
+
// Widgets read theme colors when they are created, so they are rebuilt when the
|
|
69
|
+
// native theme changes (brand color, light/dark switch).
|
|
70
|
+
private var builtThemeVersion = PCNativeTheme.version
|
|
71
|
+
private val nativeThemeListener = PCNativeTheme.Listener {
|
|
72
|
+
PCThemeSupport.clearColorStateListCaches(this)
|
|
73
|
+
rebuildUI()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
init {
|
|
77
|
+
clipChildren = false
|
|
78
|
+
clipToPadding = false
|
|
79
|
+
rebuildUI()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- Public apply* (called by manager) ----
|
|
83
|
+
|
|
84
|
+
fun applyLabel(value: String) {
|
|
85
|
+
if (label == value) return
|
|
86
|
+
label = value
|
|
87
|
+
rebuildUI()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fun applyIcon(value: PCButtonSupport.Icon) {
|
|
91
|
+
if (icon == value) return
|
|
92
|
+
icon = value
|
|
93
|
+
rebuildUI()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun applyVariant(value: String?) {
|
|
97
|
+
val parsed = PCExpressive.parseVariant(value)
|
|
98
|
+
if (variant == parsed) return
|
|
99
|
+
variant = parsed
|
|
100
|
+
rebuildUI()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun applySize(value: String?) {
|
|
104
|
+
val parsed = PCExpressive.parseSize(value)
|
|
105
|
+
if (size == parsed) return
|
|
106
|
+
size = parsed
|
|
107
|
+
rebuildUI()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fun applyShape(value: String?) {
|
|
111
|
+
val parsed = PCExpressive.parseShape(value)
|
|
112
|
+
if (shape == parsed) return
|
|
113
|
+
shape = parsed
|
|
114
|
+
rebuildUI()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fun applyInteractivity(value: String?) {
|
|
118
|
+
val newValue = if (value == "disabled") "disabled" else "enabled"
|
|
119
|
+
if (interactivity == newValue) return
|
|
120
|
+
interactivity = newValue
|
|
121
|
+
button?.isEnabled = newValue == "enabled"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fun applyMaterial(value: String?) {
|
|
125
|
+
val parsed = PCExpressive.parseExpressive(value)
|
|
126
|
+
if (expressive == parsed) return
|
|
127
|
+
expressive = parsed
|
|
128
|
+
rebuildUI()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fun applySpokenLabel(value: String) {
|
|
132
|
+
if (spokenLabel == value) return
|
|
133
|
+
spokenLabel = value
|
|
134
|
+
button?.contentDescription = spokenLabel.ifEmpty { null }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fun applyColors(container: Int?, foreground: Int?, ripple: Int?, stroke: Int?) {
|
|
138
|
+
if (containerColor == container && foregroundColor == foreground &&
|
|
139
|
+
rippleColor == ripple && strokeColor == stroke
|
|
140
|
+
) return
|
|
141
|
+
containerColor = container
|
|
142
|
+
foregroundColor = foreground
|
|
143
|
+
rippleColor = ripple
|
|
144
|
+
strokeColor = stroke
|
|
145
|
+
rebuildUI()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fun applyLabelStyle(fontFamily: String, fontSize: Float, fontWeight: String, fontStyle: String) {
|
|
149
|
+
if (labelFontFamily == fontFamily && labelFontSize == fontSize &&
|
|
150
|
+
labelFontWeight == fontWeight && labelFontStyle == fontStyle
|
|
151
|
+
) return
|
|
152
|
+
labelFontFamily = fontFamily
|
|
153
|
+
labelFontSize = fontSize
|
|
154
|
+
labelFontWeight = fontWeight
|
|
155
|
+
labelFontStyle = fontStyle
|
|
156
|
+
rebuildUI()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Called by the enclosing FloatingToolbar when its variant changes. */
|
|
160
|
+
fun refreshToolbarStyle() {
|
|
161
|
+
if (toolbarOverlay() != builtToolbarOverlay) rebuildUI()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The button has a generated view id, which React Native's touch handling would
|
|
165
|
+
// take for a React tag and dispatch JS touch events to an unrelated view. Claim
|
|
166
|
+
// the touch for this view instead; the button still receives it natively.
|
|
167
|
+
override fun interceptsTouchEvent(touchX: Float, touchY: Float): Boolean = true
|
|
168
|
+
|
|
169
|
+
override fun reactTagForTouch(touchX: Float, touchY: Float): Int = id
|
|
170
|
+
|
|
171
|
+
override fun onAttachedToWindow() {
|
|
172
|
+
super.onAttachedToWindow()
|
|
173
|
+
PCNativeTheme.addListener(nativeThemeListener)
|
|
174
|
+
PCNativeTheme.attach(context)
|
|
175
|
+
if (builtThemeVersion != PCNativeTheme.version) {
|
|
176
|
+
nativeThemeListener.onNativeThemeChanged()
|
|
177
|
+
} else {
|
|
178
|
+
// Inside a FloatingToolbar the toolbar's button styles apply; the parent
|
|
179
|
+
// chain is only complete once attached.
|
|
180
|
+
refreshToolbarStyle()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override fun onDetachedFromWindow() {
|
|
185
|
+
PCNativeTheme.removeListener(nativeThemeListener)
|
|
186
|
+
super.onDetachedFromWindow()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private fun toolbarOverlay(): Int {
|
|
190
|
+
var p: ViewParent? = parent
|
|
191
|
+
while (p != null) {
|
|
192
|
+
if (p is PCFloatingToolbarView) return p.buttonOverlay
|
|
193
|
+
p = p.parent
|
|
194
|
+
}
|
|
195
|
+
return 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---- UI Building ----
|
|
199
|
+
|
|
200
|
+
private fun rebuildUI() {
|
|
201
|
+
builtThemeVersion = PCNativeTheme.version
|
|
202
|
+
builtToolbarOverlay = toolbarOverlay()
|
|
203
|
+
removeAllViews()
|
|
204
|
+
rebuildGeneration += 1
|
|
205
|
+
val generation = rebuildGeneration
|
|
206
|
+
|
|
207
|
+
// Material widgets need a Material theme; fall back to Material 3 defaults instead of crashing.
|
|
208
|
+
val base = PCThemeSupport.materialContext(context, "Button")
|
|
209
|
+
val iconOnly = label.isEmpty() && icon.isPresent
|
|
210
|
+
|
|
211
|
+
val b = PCExpressive.createButton(
|
|
212
|
+
base, variant, size, shape, iconOnly, builtToolbarOverlay, expressive
|
|
213
|
+
).apply {
|
|
214
|
+
id = View.generateViewId()
|
|
215
|
+
text = label
|
|
216
|
+
isAllCaps = false // Preserve original text casing
|
|
217
|
+
ellipsize = TextUtils.TruncateAt.END
|
|
218
|
+
maxLines = 1
|
|
219
|
+
contentDescription = spokenLabel.ifEmpty { null }
|
|
220
|
+
isEnabled = interactivity == "enabled"
|
|
221
|
+
if (iconOnly) {
|
|
222
|
+
// Centre the icon instead of leaving it at the start edge
|
|
223
|
+
iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START
|
|
224
|
+
}
|
|
225
|
+
setOnClickListener { onPress?.invoke() }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
PCButtonSupport.applyIcon(b, icon, { generation == rebuildGeneration }) { requestLayout() }
|
|
229
|
+
PCButtonSupport.applyFont(b, labelFontFamily, labelFontSize, labelFontWeight, labelFontStyle)
|
|
230
|
+
PCButtonSupport.applyColors(b, containerColor, foregroundColor, rippleColor, strokeColor, icon.tinted)
|
|
231
|
+
|
|
232
|
+
addView(b, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
|
|
233
|
+
button = b
|
|
234
|
+
requestLayout()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ---- Layout ----
|
|
238
|
+
|
|
239
|
+
private var manualLayoutPending = false
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* React Native's root view ignores requestLayout after the initial pass, so
|
|
243
|
+
* a native view that changes its own subtree later (rebuilding for a prop
|
|
244
|
+
* change, an icon finishing loading) has to measure and lay itself out.
|
|
245
|
+
* Coalesced to one pass per frame.
|
|
246
|
+
*/
|
|
247
|
+
override fun requestLayout() {
|
|
248
|
+
super.requestLayout()
|
|
249
|
+
if (!manualLayoutPending) {
|
|
250
|
+
manualLayoutPending = true
|
|
251
|
+
post { runManualLayout() }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private fun runManualLayout() {
|
|
256
|
+
manualLayoutPending = false
|
|
257
|
+
// Nothing to do until Yoga has placed us; the regular pass covers that.
|
|
258
|
+
if (width == 0 || height == 0) return
|
|
259
|
+
measure(
|
|
260
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
261
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
|
262
|
+
)
|
|
263
|
+
layout(left, top, right, bottom)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---- Measurement ----
|
|
267
|
+
|
|
268
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
269
|
+
val child = button
|
|
270
|
+
if (child == null) {
|
|
271
|
+
setMeasuredDimension(0, 0)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// The button's natural size; Yoga normally hands us exactly that (see
|
|
276
|
+
// updateFrameSizeState), or a narrower width when the row is full.
|
|
277
|
+
val unspecified = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
|
278
|
+
child.measure(unspecified, unspecified)
|
|
279
|
+
val width = resolveDimension(widthMeasureSpec, child.measuredWidth)
|
|
280
|
+
val height = resolveDimension(heightMeasureSpec, child.measuredHeight)
|
|
281
|
+
|
|
282
|
+
child.measure(
|
|
283
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
284
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
|
285
|
+
)
|
|
286
|
+
setMeasuredDimension(width, height)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private fun resolveDimension(spec: Int, desired: Int): Int {
|
|
290
|
+
val size = MeasureSpec.getSize(spec)
|
|
291
|
+
return when (MeasureSpec.getMode(spec)) {
|
|
292
|
+
// Fabric may give us 0 before the state is applied; keep the natural size then.
|
|
293
|
+
MeasureSpec.EXACTLY -> if (size > 0) size else desired
|
|
294
|
+
MeasureSpec.AT_MOST -> minOf(desired, size)
|
|
295
|
+
else -> desired
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
300
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
301
|
+
updateFrameSizeState()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Reports the intrinsic size to Fabric; called as soon as the state wrapper is available. */
|
|
305
|
+
fun reportIntrinsicSize() = updateFrameSizeState()
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Update Fabric state with the button's natural size, so the shadow node
|
|
309
|
+
* wraps its content in Yoga layout.
|
|
310
|
+
*/
|
|
311
|
+
private fun updateFrameSizeState() {
|
|
312
|
+
val wrapper = stateWrapper ?: return
|
|
313
|
+
val child = button ?: return
|
|
314
|
+
|
|
315
|
+
// The natural width, and the height at the width we actually have when
|
|
316
|
+
// that is narrower.
|
|
317
|
+
val unspecified = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
|
318
|
+
child.measure(unspecified, unspecified)
|
|
319
|
+
val naturalWidth = child.measuredWidth
|
|
320
|
+
val fittedWidth = if (width in 1 until naturalWidth) width else naturalWidth
|
|
321
|
+
child.measure(MeasureSpec.makeMeasureSpec(fittedWidth, MeasureSpec.EXACTLY), unspecified)
|
|
322
|
+
val widthDp = PixelUtil.toDIPFromPixel(naturalWidth.toFloat())
|
|
323
|
+
val heightDp = PixelUtil.toDIPFromPixel(child.measuredHeight.toFloat())
|
|
324
|
+
|
|
325
|
+
// Measuring re-wraps the text; restore the laid-out size so the label is
|
|
326
|
+
// ellipsized to the bounds it is drawn in.
|
|
327
|
+
if (child.width > 0 && child.height > 0) {
|
|
328
|
+
child.measure(
|
|
329
|
+
MeasureSpec.makeMeasureSpec(child.width, MeasureSpec.EXACTLY),
|
|
330
|
+
MeasureSpec.makeMeasureSpec(child.height, MeasureSpec.EXACTLY)
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (widthDp != lastReportedWidth || heightDp != lastReportedHeight) {
|
|
335
|
+
lastReportedWidth = widthDp
|
|
336
|
+
lastReportedHeight = heightDp
|
|
337
|
+
|
|
338
|
+
val stateData = WritableNativeMap().apply {
|
|
339
|
+
putDouble("width", widthDp.toDouble())
|
|
340
|
+
putDouble("height", heightDp.toDouble())
|
|
341
|
+
}
|
|
342
|
+
wrapper.updateState(stateData)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|