@use-voltra/android 2.0.0 → 2.1.1
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/build/commonjs/index.js +20 -0
- package/build/commonjs/index.js.map +1 -1
- package/build/module/index.js +6 -0
- package/build/module/index.js.map +1 -1
- package/build/typescript/commonjs/index.d.ts +3 -0
- package/build/typescript/commonjs/index.d.ts.map +1 -1
- package/build/typescript/module/index.d.ts +3 -0
- package/build/typescript/module/index.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/dynamic-colors.ts +33 -0
- package/src/index.ts +99 -0
- package/src/internal.ts +2 -0
- package/src/jsx/AreaMark.tsx +16 -0
- package/src/jsx/BarMark.tsx +18 -0
- package/src/jsx/Box.tsx +5 -0
- package/src/jsx/Button.tsx +5 -0
- package/src/jsx/Chart.tsx +123 -0
- package/src/jsx/CheckBox.tsx +5 -0
- package/src/jsx/CircleIconButton.tsx +14 -0
- package/src/jsx/CircularProgressIndicator.tsx +7 -0
- package/src/jsx/Column.tsx +5 -0
- package/src/jsx/FilledButton.tsx +14 -0
- package/src/jsx/Image.tsx +21 -0
- package/src/jsx/LazyColumn.tsx +5 -0
- package/src/jsx/LazyVerticalGrid.tsx +45 -0
- package/src/jsx/LineMark.tsx +17 -0
- package/src/jsx/LinearProgressIndicator.tsx +7 -0
- package/src/jsx/OutlineButton.tsx +14 -0
- package/src/jsx/PointMark.tsx +16 -0
- package/src/jsx/RadioButton.tsx +5 -0
- package/src/jsx/Row.tsx +5 -0
- package/src/jsx/RuleMark.tsx +15 -0
- package/src/jsx/Scaffold.tsx +5 -0
- package/src/jsx/SectorMark.tsx +17 -0
- package/src/jsx/Spacer.tsx +5 -0
- package/src/jsx/SquareIconButton.tsx +14 -0
- package/src/jsx/Switch.tsx +5 -0
- package/src/jsx/Text.tsx +10 -0
- package/src/jsx/TitleBar.tsx +18 -0
- package/src/jsx/baseProps.tsx +13 -0
- package/src/jsx/chart-types.ts +2 -0
- package/src/jsx/createVoltraComponent.ts +2 -0
- package/src/jsx/index.ts +1 -0
- package/src/jsx/primitives.ts +28 -0
- package/src/jsx/props/.generated +15 -0
- package/src/jsx/props/AndroidBox.ts +19 -0
- package/src/jsx/props/AndroidButton.ts +10 -0
- package/src/jsx/props/AndroidChart.ts +20 -0
- package/src/jsx/props/AndroidCheckBox.ts +20 -0
- package/src/jsx/props/AndroidCircleIconButton.ts +18 -0
- package/src/jsx/props/AndroidCircularProgressIndicator.ts +10 -0
- package/src/jsx/props/AndroidColumn.ts +12 -0
- package/src/jsx/props/AndroidFilledButton.ts +20 -0
- package/src/jsx/props/AndroidImage.ts +26 -0
- package/src/jsx/props/AndroidLazyColumn.ts +10 -0
- package/src/jsx/props/AndroidLazyVerticalGrid.ts +12 -0
- package/src/jsx/props/AndroidLinearProgressIndicator.ts +14 -0
- package/src/jsx/props/AndroidOutlineButton.ts +18 -0
- package/src/jsx/props/AndroidRadioButton.ts +22 -0
- package/src/jsx/props/AndroidRow.ts +12 -0
- package/src/jsx/props/AndroidScaffold.ts +12 -0
- package/src/jsx/props/AndroidSpacer.ts +7 -0
- package/src/jsx/props/AndroidSquareIconButton.ts +18 -0
- package/src/jsx/props/AndroidSwitch.ts +24 -0
- package/src/jsx/props/AndroidText.ts +14 -0
- package/src/jsx/props/AndroidTitleBar.ts +18 -0
- package/src/live-update/renderer.ts +36 -0
- package/src/live-update/types.ts +44 -0
- package/src/ongoing-notification/components.tsx +49 -0
- package/src/ongoing-notification/renderer.ts +360 -0
- package/src/ongoing-notification/types.ts +199 -0
- package/src/payload/component-ids.ts +89 -0
- package/src/preload-utils.ts +78 -0
- package/src/renderer/index.ts +6 -0
- package/src/renderer/renderer.ts +25 -0
- package/src/server.ts +19 -0
- package/src/styles/types.ts +114 -0
- package/src/types.ts +38 -0
- package/src/utils/index.ts +0 -0
- package/src/widgets/renderer.ts +90 -0
- package/src/widgets/types.ts +62 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AndroidOngoingNotificationActionProps,
|
|
5
|
+
AndroidOngoingNotificationBigTextProps,
|
|
6
|
+
AndroidOngoingNotificationProgressProps,
|
|
7
|
+
} from './types.js'
|
|
8
|
+
|
|
9
|
+
export const ANDROID_ONGOING_NOTIFICATION_COMPONENT_TAG = Symbol.for('VOLTRA_ANDROID_ONGOING_NOTIFICATION_COMPONENT')
|
|
10
|
+
|
|
11
|
+
type AndroidOngoingNotificationComponentKind = 'progress' | 'bigText' | 'action'
|
|
12
|
+
|
|
13
|
+
type AndroidOngoingNotificationComponent<TProps extends Record<string, unknown>> = ComponentType<TProps> & {
|
|
14
|
+
displayName: string
|
|
15
|
+
[ANDROID_ONGOING_NOTIFICATION_COMPONENT_TAG]: AndroidOngoingNotificationComponentKind
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const createAndroidOngoingNotificationComponent = <TProps extends Record<string, unknown>>(
|
|
19
|
+
displayName: string,
|
|
20
|
+
kind: AndroidOngoingNotificationComponentKind
|
|
21
|
+
): AndroidOngoingNotificationComponent<TProps> => {
|
|
22
|
+
const Component = (_props: TProps) => null
|
|
23
|
+
|
|
24
|
+
Component.displayName = displayName
|
|
25
|
+
;(Component as AndroidOngoingNotificationComponent<TProps>)[ANDROID_ONGOING_NOTIFICATION_COMPONENT_TAG] = kind
|
|
26
|
+
|
|
27
|
+
return Component as AndroidOngoingNotificationComponent<TProps>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Progress = createAndroidOngoingNotificationComponent<AndroidOngoingNotificationProgressProps>(
|
|
31
|
+
'AndroidOngoingNotification.Progress',
|
|
32
|
+
'progress'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const BigText = createAndroidOngoingNotificationComponent<AndroidOngoingNotificationBigTextProps>(
|
|
36
|
+
'AndroidOngoingNotification.BigText',
|
|
37
|
+
'bigText'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export const Action = createAndroidOngoingNotificationComponent<AndroidOngoingNotificationActionProps>(
|
|
41
|
+
'AndroidOngoingNotification.Action',
|
|
42
|
+
'action'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
export const AndroidOngoingNotification = {
|
|
46
|
+
Progress,
|
|
47
|
+
BigText,
|
|
48
|
+
Action,
|
|
49
|
+
} as const
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { Children, Fragment, isValidElement, type ReactElement, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { ImageSource } from '../jsx/Image.js'
|
|
4
|
+
import { getAndroidComponentId } from '../payload/component-ids.js'
|
|
5
|
+
import { ANDROID_ONGOING_NOTIFICATION_COMPONENT_TAG } from './components.js'
|
|
6
|
+
import type {
|
|
7
|
+
AndroidOngoingNotificationActionPayload,
|
|
8
|
+
AndroidOngoingNotificationActionProps,
|
|
9
|
+
AndroidOngoingNotificationBigTextPayload,
|
|
10
|
+
AndroidOngoingNotificationBigTextProps,
|
|
11
|
+
AndroidOngoingNotificationContent,
|
|
12
|
+
AndroidOngoingNotificationPayload,
|
|
13
|
+
AndroidOngoingNotificationProgressPayload,
|
|
14
|
+
AndroidOngoingNotificationProgressPoint,
|
|
15
|
+
AndroidOngoingNotificationProgressProps,
|
|
16
|
+
AndroidOngoingNotificationProgressSegment,
|
|
17
|
+
} from './types.js'
|
|
18
|
+
|
|
19
|
+
void getAndroidComponentId
|
|
20
|
+
|
|
21
|
+
const PAYLOAD_VERSION = 1 as const
|
|
22
|
+
|
|
23
|
+
const flattenChildren = (node: ReactNode): ReactNode[] => {
|
|
24
|
+
if (node === null || node === undefined || typeof node === 'boolean') {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(node)) {
|
|
29
|
+
return node.flatMap((child) => flattenChildren(child))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [node]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const getSingleRootElement = (content: ReactNode): ReactElement<Record<string, unknown>> => {
|
|
36
|
+
const children = flattenChildren(content)
|
|
37
|
+
|
|
38
|
+
if (children.length !== 1) {
|
|
39
|
+
throw new Error('[Voltra] [Android] Ongoing notification content must contain exactly one root element.')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [root] = children
|
|
43
|
+
|
|
44
|
+
if (!isValidElement(root)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'[Voltra] [Android] Ongoing notification content must be a valid AndroidOngoingNotification element.'
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (root.type === Fragment) {
|
|
51
|
+
return getSingleRootElement((root.props as { children?: ReactNode }).children)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return root as ReactElement<Record<string, unknown>>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const assertString = (value: unknown, propName: string): string => {
|
|
58
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
59
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "${propName}" must be a non-empty string.`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const assertOptionalNonEmptyString = (value: unknown, propName: string): string | undefined => {
|
|
66
|
+
if (value === undefined) {
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[Voltra] [Android] Ongoing notification prop "${propName}" must be a non-empty string when provided.`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return value
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const assertOptionalString = (value: unknown, propName: string): string | undefined => {
|
|
80
|
+
if (value === undefined) {
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof value !== 'string') {
|
|
85
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "${propName}" must be a string.`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return value
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isImageSource = (value: unknown): value is ImageSource => {
|
|
92
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const keys = Object.keys(value)
|
|
97
|
+
if (keys.length !== 1) {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ('assetName' in value) {
|
|
102
|
+
return typeof value.assetName === 'string' && value.assetName.length > 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if ('base64' in value) {
|
|
106
|
+
return typeof value.base64 === 'string' && value.base64.length > 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const assertOptionalImageSource = (value: unknown, propName: string): ImageSource | undefined => {
|
|
113
|
+
if (value === undefined) {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!isImageSource(value)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[Voltra] [Android] Ongoing notification prop "${propName}" must be an image source with either assetName or base64.`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return value
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const assertBoolean = (value: unknown, propName: string): boolean | undefined => {
|
|
127
|
+
if (value === undefined) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typeof value !== 'boolean') {
|
|
132
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "${propName}" must be a boolean.`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return value
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const assertFiniteNumber = (value: unknown, propName: string): number => {
|
|
139
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
140
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "${propName}" must be a finite number.`)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return value
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const assertOptionalColorString = (value: unknown, propName: string): string | undefined => {
|
|
147
|
+
if (value === undefined) {
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
152
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "${propName}" must be a non-empty color string.`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return value
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const normalizeProgressSegments = (value: unknown): AndroidOngoingNotificationProgressSegment[] | undefined => {
|
|
159
|
+
if (value === undefined) {
|
|
160
|
+
return undefined
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!Array.isArray(value)) {
|
|
164
|
+
throw new Error('[Voltra] [Android] Ongoing notification prop "segments" must be an array.')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return value.map((segment, index) => {
|
|
168
|
+
if (!segment || typeof segment !== 'object' || Array.isArray(segment)) {
|
|
169
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "segments[${index}]" must be an object.`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const length = assertFiniteNumber((segment as { length?: unknown }).length, `segments[${index}].length`)
|
|
173
|
+
if (length <= 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`[Voltra] [Android] Ongoing notification prop "segments[${index}].length" must be greater than 0.`
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
length,
|
|
181
|
+
color: assertOptionalColorString((segment as { color?: unknown }).color, `segments[${index}].color`),
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const normalizeProgressPoints = (value: unknown): AndroidOngoingNotificationProgressPoint[] | undefined => {
|
|
187
|
+
if (value === undefined) {
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!Array.isArray(value)) {
|
|
192
|
+
throw new Error('[Voltra] [Android] Ongoing notification prop "points" must be an array.')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return value.map((point, index) => {
|
|
196
|
+
if (!point || typeof point !== 'object' || Array.isArray(point)) {
|
|
197
|
+
throw new Error(`[Voltra] [Android] Ongoing notification prop "points[${index}]" must be an object.`)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const position = assertFiniteNumber((point as { position?: unknown }).position, `points[${index}].position`)
|
|
201
|
+
if (position < 0) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`[Voltra] [Android] Ongoing notification prop "points[${index}].position" must be greater than or equal to 0.`
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
position,
|
|
209
|
+
color: assertOptionalColorString((point as { color?: unknown }).color, `points[${index}].color`),
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const normalizeWhen = (value: unknown): number | undefined => {
|
|
215
|
+
if (value === undefined) {
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (value instanceof Date) {
|
|
220
|
+
const timestamp = value.getTime()
|
|
221
|
+
if (!Number.isFinite(timestamp)) {
|
|
222
|
+
throw new Error('[Voltra] [Android] Ongoing notification prop "when" must be a valid Date or timestamp.')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return timestamp
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
229
|
+
return value
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
throw new Error('[Voltra] [Android] Ongoing notification prop "when" must be a valid Date or timestamp.')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const getElementKind = (element: ReactElement<Record<string, unknown>>) => {
|
|
236
|
+
const elementType = element.type as unknown
|
|
237
|
+
|
|
238
|
+
return typeof elementType === 'function' || (typeof elementType === 'object' && elementType !== null)
|
|
239
|
+
? (elementType as Record<PropertyKey, unknown>)[ANDROID_ONGOING_NOTIFICATION_COMPONENT_TAG]
|
|
240
|
+
: undefined
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const normalizeActionPayload = (
|
|
244
|
+
props: AndroidOngoingNotificationActionProps
|
|
245
|
+
): AndroidOngoingNotificationActionPayload => {
|
|
246
|
+
return {
|
|
247
|
+
title: assertString(props.title, 'title'),
|
|
248
|
+
deepLinkUrl: assertString(props.deepLinkUrl, 'deepLinkUrl'),
|
|
249
|
+
icon: assertOptionalImageSource(props.icon, 'icon'),
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const normalizeActions = (value: ReactNode): AndroidOngoingNotificationActionPayload[] | undefined => {
|
|
254
|
+
const actions: AndroidOngoingNotificationActionPayload[] = []
|
|
255
|
+
|
|
256
|
+
Children.forEach(value, (child) => {
|
|
257
|
+
if (!isValidElement(child)) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (child.type === Fragment) {
|
|
262
|
+
const fragmentChildren = normalizeActions((child.props as { children?: ReactNode }).children)
|
|
263
|
+
if (fragmentChildren) {
|
|
264
|
+
actions.push(...fragmentChildren)
|
|
265
|
+
}
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (getElementKind(child as ReactElement<Record<string, unknown>>) !== 'action') {
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
actions.push(normalizeActionPayload(child.props as AndroidOngoingNotificationActionProps))
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
return actions.length > 0 ? actions : undefined
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const normalizeProgressPayload = (
|
|
280
|
+
props: AndroidOngoingNotificationProgressProps
|
|
281
|
+
): AndroidOngoingNotificationProgressPayload => {
|
|
282
|
+
const value = assertFiniteNumber(props.value, 'value')
|
|
283
|
+
const max = assertFiniteNumber(props.max, 'max')
|
|
284
|
+
|
|
285
|
+
if (max <= 0) {
|
|
286
|
+
throw new Error('[Voltra] [Android] Ongoing notification prop "max" must be greater than 0.')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (value < 0 || value > max) {
|
|
290
|
+
throw new Error('[Voltra] [Android] Ongoing notification prop "value" must be between 0 and max.')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
v: PAYLOAD_VERSION,
|
|
295
|
+
kind: 'progress',
|
|
296
|
+
title: assertOptionalNonEmptyString(props.title, 'title'),
|
|
297
|
+
subText: assertOptionalString(props.subText, 'subText'),
|
|
298
|
+
text: assertOptionalString(props.text, 'text'),
|
|
299
|
+
value,
|
|
300
|
+
max,
|
|
301
|
+
indeterminate: assertBoolean(props.indeterminate, 'indeterminate'),
|
|
302
|
+
shortCriticalText: assertOptionalString(props.shortCriticalText, 'shortCriticalText'),
|
|
303
|
+
when: normalizeWhen(props.when),
|
|
304
|
+
chronometer: assertBoolean(props.chronometer, 'chronometer'),
|
|
305
|
+
largeIcon: assertOptionalImageSource(props.largeIcon, 'largeIcon'),
|
|
306
|
+
progressTrackerIcon: assertOptionalImageSource(props.progressTrackerIcon, 'progressTrackerIcon'),
|
|
307
|
+
progressStartIcon: assertOptionalImageSource(props.progressStartIcon, 'progressStartIcon'),
|
|
308
|
+
progressEndIcon: assertOptionalImageSource(props.progressEndIcon, 'progressEndIcon'),
|
|
309
|
+
segments: normalizeProgressSegments(props.segments),
|
|
310
|
+
points: normalizeProgressPoints(props.points),
|
|
311
|
+
actions: normalizeActions(props.children),
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const normalizeBigTextPayload = (
|
|
316
|
+
props: AndroidOngoingNotificationBigTextProps
|
|
317
|
+
): AndroidOngoingNotificationBigTextPayload => {
|
|
318
|
+
const text = assertString(props.text, 'text')
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
v: PAYLOAD_VERSION,
|
|
322
|
+
kind: 'bigText',
|
|
323
|
+
title: assertOptionalNonEmptyString(props.title, 'title'),
|
|
324
|
+
subText: assertOptionalString(props.subText, 'subText'),
|
|
325
|
+
text,
|
|
326
|
+
bigText: assertOptionalString(props.bigText, 'bigText') ?? text,
|
|
327
|
+
shortCriticalText: assertOptionalString(props.shortCriticalText, 'shortCriticalText'),
|
|
328
|
+
when: normalizeWhen(props.when),
|
|
329
|
+
chronometer: assertBoolean(props.chronometer, 'chronometer'),
|
|
330
|
+
largeIcon: assertOptionalImageSource(props.largeIcon, 'largeIcon'),
|
|
331
|
+
actions: normalizeActions(props.children),
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export const renderAndroidOngoingNotificationPayloadToJson = (
|
|
336
|
+
content: ReactNode
|
|
337
|
+
): AndroidOngoingNotificationPayload => {
|
|
338
|
+
const element = getSingleRootElement(content)
|
|
339
|
+
const kind = getElementKind(element)
|
|
340
|
+
|
|
341
|
+
if (kind === 'progress') {
|
|
342
|
+
return normalizeProgressPayload(element.props as AndroidOngoingNotificationProgressProps)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (kind === 'bigText') {
|
|
346
|
+
return normalizeBigTextPayload(element.props as AndroidOngoingNotificationBigTextProps)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
throw new Error(
|
|
350
|
+
'[Voltra] [Android] Ongoing notification content must use AndroidOngoingNotification.Progress or AndroidOngoingNotification.BigText.'
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export const renderAndroidOngoingNotificationPayload = (content: ReactNode): string => {
|
|
355
|
+
return JSON.stringify(renderAndroidOngoingNotificationPayloadToJson(content))
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export const renderAndroidOngoingNotificationContent = (content: AndroidOngoingNotificationContent): string => {
|
|
359
|
+
return renderAndroidOngoingNotificationPayload(content)
|
|
360
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { ImageSource } from '../jsx/Image.js'
|
|
4
|
+
|
|
5
|
+
export type AndroidOngoingNotificationFallbackBehavior = 'standard' | 'error'
|
|
6
|
+
|
|
7
|
+
export type AndroidOngoingNotificationCommonDisplayProps = {
|
|
8
|
+
title?: string
|
|
9
|
+
subText?: string
|
|
10
|
+
shortCriticalText?: string
|
|
11
|
+
when?: Date | number
|
|
12
|
+
chronometer?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type AndroidOngoingNotificationProgressSegment = {
|
|
16
|
+
length: number
|
|
17
|
+
color?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AndroidOngoingNotificationProgressPoint = {
|
|
21
|
+
position: number
|
|
22
|
+
color?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type AndroidOngoingNotificationActionProps = {
|
|
26
|
+
title: string
|
|
27
|
+
deepLinkUrl: string
|
|
28
|
+
icon?: ImageSource
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type AndroidOngoingNotificationActionPayload = {
|
|
32
|
+
title: string
|
|
33
|
+
deepLinkUrl: string
|
|
34
|
+
icon?: ImageSource
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type AndroidOngoingNotificationProgressProps = AndroidOngoingNotificationCommonDisplayProps & {
|
|
38
|
+
text?: string
|
|
39
|
+
value: number
|
|
40
|
+
max: number
|
|
41
|
+
indeterminate?: boolean
|
|
42
|
+
largeIcon?: ImageSource
|
|
43
|
+
progressTrackerIcon?: ImageSource
|
|
44
|
+
progressStartIcon?: ImageSource
|
|
45
|
+
progressEndIcon?: ImageSource
|
|
46
|
+
segments?: AndroidOngoingNotificationProgressSegment[]
|
|
47
|
+
points?: AndroidOngoingNotificationProgressPoint[]
|
|
48
|
+
children?: ReactNode
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type AndroidOngoingNotificationBigTextProps = AndroidOngoingNotificationCommonDisplayProps & {
|
|
52
|
+
text: string
|
|
53
|
+
bigText?: string
|
|
54
|
+
largeIcon?: ImageSource
|
|
55
|
+
children?: ReactNode
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type AndroidOngoingNotificationProgressPayload = {
|
|
59
|
+
v: 1
|
|
60
|
+
kind: 'progress'
|
|
61
|
+
title?: string
|
|
62
|
+
subText?: string
|
|
63
|
+
text?: string
|
|
64
|
+
value: number
|
|
65
|
+
max: number
|
|
66
|
+
indeterminate?: boolean
|
|
67
|
+
shortCriticalText?: string
|
|
68
|
+
when?: number
|
|
69
|
+
chronometer?: boolean
|
|
70
|
+
largeIcon?: ImageSource
|
|
71
|
+
progressTrackerIcon?: ImageSource
|
|
72
|
+
progressStartIcon?: ImageSource
|
|
73
|
+
progressEndIcon?: ImageSource
|
|
74
|
+
segments?: AndroidOngoingNotificationProgressSegment[]
|
|
75
|
+
points?: AndroidOngoingNotificationProgressPoint[]
|
|
76
|
+
actions?: AndroidOngoingNotificationActionPayload[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type AndroidOngoingNotificationBigTextPayload = {
|
|
80
|
+
v: 1
|
|
81
|
+
kind: 'bigText'
|
|
82
|
+
title?: string
|
|
83
|
+
subText?: string
|
|
84
|
+
text: string
|
|
85
|
+
bigText?: string
|
|
86
|
+
shortCriticalText?: string
|
|
87
|
+
when?: number
|
|
88
|
+
chronometer?: boolean
|
|
89
|
+
largeIcon?: ImageSource
|
|
90
|
+
actions?: AndroidOngoingNotificationActionPayload[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type AndroidOngoingNotificationPayload =
|
|
94
|
+
| AndroidOngoingNotificationProgressPayload
|
|
95
|
+
| AndroidOngoingNotificationBigTextPayload
|
|
96
|
+
|
|
97
|
+
export type AndroidOngoingNotificationContent = ReactNode
|
|
98
|
+
|
|
99
|
+
export type AndroidOngoingNotificationInput =
|
|
100
|
+
| AndroidOngoingNotificationContent
|
|
101
|
+
| AndroidOngoingNotificationPayload
|
|
102
|
+
| string
|
|
103
|
+
|
|
104
|
+
export type StartAndroidOngoingNotificationOptions = {
|
|
105
|
+
notificationId?: string
|
|
106
|
+
channelId: string
|
|
107
|
+
smallIcon?: string
|
|
108
|
+
deepLinkUrl?: string
|
|
109
|
+
requestPromotedOngoing?: boolean
|
|
110
|
+
fallbackBehavior?: AndroidOngoingNotificationFallbackBehavior
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type UpdateAndroidOngoingNotificationOptions = Omit<
|
|
114
|
+
Partial<StartAndroidOngoingNotificationOptions>,
|
|
115
|
+
'notificationId'
|
|
116
|
+
>
|
|
117
|
+
|
|
118
|
+
export type UseAndroidOngoingNotificationOptions = StartAndroidOngoingNotificationOptions & {
|
|
119
|
+
autoStart?: boolean
|
|
120
|
+
autoUpdate?: boolean
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type AndroidOngoingNotificationCapabilities = {
|
|
124
|
+
apiLevel: number
|
|
125
|
+
notificationsEnabled: boolean
|
|
126
|
+
supportsPromotedNotifications: boolean
|
|
127
|
+
canPostPromotedNotifications: boolean
|
|
128
|
+
canRequestPromotedOngoing: boolean
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type AndroidOngoingNotificationStatus = {
|
|
132
|
+
isActive: boolean
|
|
133
|
+
isDismissed: boolean
|
|
134
|
+
isPromoted?: boolean
|
|
135
|
+
hasPromotableCharacteristics?: boolean
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type AndroidOngoingNotificationStartResult =
|
|
139
|
+
| {
|
|
140
|
+
ok: true
|
|
141
|
+
notificationId: string
|
|
142
|
+
action: 'started'
|
|
143
|
+
reason?: undefined
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
ok: false
|
|
147
|
+
notificationId: string
|
|
148
|
+
action?: undefined
|
|
149
|
+
reason: 'already_exists'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type AndroidOngoingNotificationUpdateResult =
|
|
153
|
+
| {
|
|
154
|
+
ok: true
|
|
155
|
+
notificationId: string
|
|
156
|
+
action: 'updated'
|
|
157
|
+
reason?: undefined
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
ok: false
|
|
161
|
+
notificationId: string
|
|
162
|
+
action?: undefined
|
|
163
|
+
reason: 'not_found' | 'dismissed'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type AndroidOngoingNotificationUpsertResult =
|
|
167
|
+
| {
|
|
168
|
+
ok: true
|
|
169
|
+
notificationId: string
|
|
170
|
+
action: 'started' | 'updated'
|
|
171
|
+
reason?: undefined
|
|
172
|
+
}
|
|
173
|
+
| {
|
|
174
|
+
ok: false
|
|
175
|
+
notificationId: string
|
|
176
|
+
action?: undefined
|
|
177
|
+
reason: 'already_exists' | 'dismissed'
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type AndroidOngoingNotificationStopResult =
|
|
181
|
+
| {
|
|
182
|
+
ok: true
|
|
183
|
+
notificationId: string
|
|
184
|
+
action: 'stopped'
|
|
185
|
+
reason?: undefined
|
|
186
|
+
}
|
|
187
|
+
| {
|
|
188
|
+
ok: false
|
|
189
|
+
notificationId: string
|
|
190
|
+
action?: undefined
|
|
191
|
+
reason: 'not_found'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export type UseAndroidOngoingNotificationResult = {
|
|
195
|
+
start: (options?: Partial<StartAndroidOngoingNotificationOptions>) => Promise<AndroidOngoingNotificationStartResult>
|
|
196
|
+
update: (options?: UpdateAndroidOngoingNotificationOptions) => Promise<AndroidOngoingNotificationUpdateResult>
|
|
197
|
+
end: () => Promise<AndroidOngoingNotificationStopResult>
|
|
198
|
+
isActive: boolean
|
|
199
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// 🤖 AUTO-GENERATED from data/components.json
|
|
3
|
+
// DO NOT EDIT MANUALLY - Changes will be overwritten
|
|
4
|
+
// Schema version: 1.0.0
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mapping from Android component name to numeric ID
|
|
8
|
+
* Component IDs are assigned sequentially based on order in components.json (0-indexed)
|
|
9
|
+
*/
|
|
10
|
+
export const ANDROID_COMPONENT_NAME_TO_ID: Record<string, number> = {
|
|
11
|
+
AndroidFilledButton: 0,
|
|
12
|
+
AndroidImage: 1,
|
|
13
|
+
AndroidSwitch: 2,
|
|
14
|
+
AndroidCheckBox: 3,
|
|
15
|
+
AndroidRadioButton: 4,
|
|
16
|
+
AndroidBox: 5,
|
|
17
|
+
AndroidButton: 6,
|
|
18
|
+
AndroidCircleIconButton: 7,
|
|
19
|
+
AndroidCircularProgressIndicator: 8,
|
|
20
|
+
AndroidColumn: 9,
|
|
21
|
+
AndroidLazyColumn: 10,
|
|
22
|
+
AndroidLazyVerticalGrid: 11,
|
|
23
|
+
AndroidLinearProgressIndicator: 12,
|
|
24
|
+
AndroidOutlineButton: 13,
|
|
25
|
+
AndroidRow: 14,
|
|
26
|
+
AndroidScaffold: 15,
|
|
27
|
+
AndroidSpacer: 16,
|
|
28
|
+
AndroidSquareIconButton: 17,
|
|
29
|
+
AndroidText: 18,
|
|
30
|
+
AndroidTitleBar: 19,
|
|
31
|
+
AndroidChart: 20,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Mapping from numeric ID to Android component name
|
|
36
|
+
*/
|
|
37
|
+
export const ANDROID_COMPONENT_ID_TO_NAME: Record<number, string> = {
|
|
38
|
+
0: 'AndroidFilledButton',
|
|
39
|
+
1: 'AndroidImage',
|
|
40
|
+
2: 'AndroidSwitch',
|
|
41
|
+
3: 'AndroidCheckBox',
|
|
42
|
+
4: 'AndroidRadioButton',
|
|
43
|
+
5: 'AndroidBox',
|
|
44
|
+
6: 'AndroidButton',
|
|
45
|
+
7: 'AndroidCircleIconButton',
|
|
46
|
+
8: 'AndroidCircularProgressIndicator',
|
|
47
|
+
9: 'AndroidColumn',
|
|
48
|
+
10: 'AndroidLazyColumn',
|
|
49
|
+
11: 'AndroidLazyVerticalGrid',
|
|
50
|
+
12: 'AndroidLinearProgressIndicator',
|
|
51
|
+
13: 'AndroidOutlineButton',
|
|
52
|
+
14: 'AndroidRow',
|
|
53
|
+
15: 'AndroidScaffold',
|
|
54
|
+
16: 'AndroidSpacer',
|
|
55
|
+
17: 'AndroidSquareIconButton',
|
|
56
|
+
18: 'AndroidText',
|
|
57
|
+
19: 'AndroidTitleBar',
|
|
58
|
+
20: 'AndroidChart',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get Android component ID from name
|
|
63
|
+
* @throws Error if component name is not found
|
|
64
|
+
*/
|
|
65
|
+
export function getAndroidComponentId(name: string): number {
|
|
66
|
+
const id = ANDROID_COMPONENT_NAME_TO_ID[name]
|
|
67
|
+
if (id === undefined) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Unknown Android component name: "${name}". Available components: ${Object.keys(
|
|
70
|
+
ANDROID_COMPONENT_NAME_TO_ID
|
|
71
|
+
).join(', ')}`
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
return id
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get Android component name from ID
|
|
79
|
+
* @throws Error if component ID is not found
|
|
80
|
+
*/
|
|
81
|
+
export function getAndroidComponentName(id: number): string {
|
|
82
|
+
const name = ANDROID_COMPONENT_ID_TO_NAME[id]
|
|
83
|
+
if (name === undefined) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Unknown Android component ID: ${id}. Valid IDs: 0-${Object.keys(ANDROID_COMPONENT_ID_TO_NAME).length - 1}`
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return name
|
|
89
|
+
}
|