vxrn 2.15.0 → 2.15.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/dist/exports/prebuild.mjs +37 -0
- package/dist/exports/prebuild.mjs.map +1 -1
- package/dist/exports/prebuild.native.js +55 -0
- package/dist/exports/prebuild.native.js.map +1 -1
- package/dist/exports/prebuildWithoutExpo.mjs +197 -12
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +197 -10
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/package.json +13 -13
- package/src/exports/prebuild.ts +30 -0
- package/src/exports/prebuildWithoutExpo.test.ts +74 -2
- package/src/exports/prebuildWithoutExpo.ts +204 -12
- package/types/exports/prebuild.d.ts.map +1 -1
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vxrn",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.1",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
"@react-native-community/cli": "^20.2.0",
|
|
81
81
|
"@react-native-community/cli-config": "^20.2.0",
|
|
82
82
|
"@react-native/dev-middleware": "^0.88.0",
|
|
83
|
-
"@vxrn/compiler": "2.15.
|
|
84
|
-
"@vxrn/debug": "2.15.
|
|
85
|
-
"@vxrn/query-string": "2.15.
|
|
86
|
-
"@vxrn/resolve": "2.15.
|
|
87
|
-
"@vxrn/url-parse": "2.15.
|
|
88
|
-
"@vxrn/utils": "2.15.
|
|
89
|
-
"@vxrn/vendor": "2.15.
|
|
90
|
-
"@vxrn/vite-flow": "2.15.
|
|
83
|
+
"@vxrn/compiler": "2.15.1",
|
|
84
|
+
"@vxrn/debug": "2.15.1",
|
|
85
|
+
"@vxrn/query-string": "2.15.1",
|
|
86
|
+
"@vxrn/resolve": "2.15.1",
|
|
87
|
+
"@vxrn/url-parse": "2.15.1",
|
|
88
|
+
"@vxrn/utils": "2.15.1",
|
|
89
|
+
"@vxrn/vendor": "2.15.1",
|
|
90
|
+
"@vxrn/vite-flow": "2.15.1",
|
|
91
91
|
"citty": "^0.1.6",
|
|
92
92
|
"dotenv": "^17.2.1",
|
|
93
93
|
"dotenv-expand": "^12.0.2",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"devDependencies": {
|
|
113
113
|
"@biomejs/biome": "2.5.15",
|
|
114
114
|
"@jridgewell/gen-mapping": "^0.3.5",
|
|
115
|
-
"@tamagui/build": "3.
|
|
115
|
+
"@tamagui/build": "3.3.0",
|
|
116
116
|
"@types/node": "^24.10.0",
|
|
117
117
|
"@types/ws": "^8.18.1",
|
|
118
118
|
"depcheck": "^1.4.7",
|
|
@@ -121,20 +121,20 @@
|
|
|
121
121
|
"react-native": "0.88.0",
|
|
122
122
|
"typescript": "7.0.2",
|
|
123
123
|
"vitest": "^4.1.0",
|
|
124
|
-
"@vxrn/vite-plugin-metro": "2.15.
|
|
124
|
+
"@vxrn/vite-plugin-metro": "2.15.1"
|
|
125
125
|
},
|
|
126
126
|
"publishConfig": {
|
|
127
127
|
"access": "public"
|
|
128
128
|
},
|
|
129
129
|
"peerDependencies": {
|
|
130
|
-
"@vxrn/vite-plugin-metro": "2.15.
|
|
130
|
+
"@vxrn/vite-plugin-metro": "2.15.1"
|
|
131
131
|
},
|
|
132
132
|
"peerDependenciesMeta": {
|
|
133
133
|
"@vxrn/vite-plugin-metro": {
|
|
134
134
|
"optional": true
|
|
135
135
|
}
|
|
136
136
|
},
|
|
137
|
-
"releaseSourceCommit": "
|
|
137
|
+
"releaseSourceCommit": "84d72d51f9619c438190e70050d616a67c5f5bd1",
|
|
138
138
|
"repository": {
|
|
139
139
|
"type": "git",
|
|
140
140
|
"url": "git+https://github.com/onejs/one.git",
|
package/src/exports/prebuild.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { detectPackageManager, type PackageManagerName } from '@vxrn/utils'
|
|
2
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs'
|
|
2
3
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
3
4
|
import path from 'node:path'
|
|
4
5
|
import { fillOptions } from '../config/getOptionsFilled'
|
|
@@ -64,6 +65,12 @@ export const prebuild = async ({
|
|
|
64
65
|
throw new Error('package.json checks are failing:\n' + error)
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
const ios = path.join(root, 'ios')
|
|
69
|
+
const previousIos = `${ios}.previous`
|
|
70
|
+
rmSync(previousIos, { recursive: true, force: true })
|
|
71
|
+
const reusesIos = (platform == 'ios' || !platform) && existsSync(ios)
|
|
72
|
+
if (reusesIos) renameSync(ios, previousIos)
|
|
73
|
+
|
|
67
74
|
if (platform == 'ios' || !platform) {
|
|
68
75
|
await generateForPlatform(root, 'ios', app)
|
|
69
76
|
}
|
|
@@ -73,6 +80,8 @@ export const prebuild = async ({
|
|
|
73
80
|
|
|
74
81
|
await beforeInstall?.()
|
|
75
82
|
|
|
83
|
+
if (reusesIos) reuseUnchangedIosProject(previousIos, ios)
|
|
84
|
+
|
|
76
85
|
// community config reads the generated projects, so discover after generation
|
|
77
86
|
// and before installation. `--no-install` never fakes the linked package set.
|
|
78
87
|
const inventory = await getNativeDependencyInventory(root)
|
|
@@ -121,6 +130,27 @@ Android:
|
|
|
121
130
|
}
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
// xcode keys each build input on its file identity and modification time, so a
|
|
134
|
+
// regenerated file it already compiled must be the same file, not equal bytes.
|
|
135
|
+
// what pod install and xcodebuild wrote stays too, so both resume incrementally.
|
|
136
|
+
function reuseUnchangedIosProject(previous: string, next: string) {
|
|
137
|
+
for (const entry of readdirSync(next, { recursive: true, withFileTypes: true })) {
|
|
138
|
+
if (!entry.isFile()) continue
|
|
139
|
+
const file = path.join(entry.parentPath, entry.name)
|
|
140
|
+
const old = path.join(previous, path.relative(next, file))
|
|
141
|
+
if (!existsSync(old) || !lstatSync(old).isFile()) continue
|
|
142
|
+
if (lstatSync(old).size !== lstatSync(file).size) continue
|
|
143
|
+
if (!readFileSync(old).equals(readFileSync(file))) continue
|
|
144
|
+
renameSync(old, file)
|
|
145
|
+
}
|
|
146
|
+
for (const name of ['Pods', 'Podfile.lock', 'build']) {
|
|
147
|
+
const from = path.join(previous, name)
|
|
148
|
+
const to = path.join(next, name)
|
|
149
|
+
if (existsSync(from) && !existsSync(to)) renameSync(from, to)
|
|
150
|
+
}
|
|
151
|
+
rmSync(previous, { recursive: true, force: true })
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
export async function replaceInUTF8File(
|
|
125
155
|
filePath: string,
|
|
126
156
|
findThis: string,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
generateForPlatform,
|
|
24
24
|
generateKotlinSources,
|
|
25
25
|
getNativeDependencyInventory,
|
|
26
|
+
renderIosAppIntentsSwift,
|
|
26
27
|
renderPrebuildFile,
|
|
27
28
|
renderSceneDelegateSwift,
|
|
28
29
|
type PrebuildAppConfig,
|
|
@@ -2198,9 +2199,12 @@ describe('ios widgets', () => {
|
|
|
2198
2199
|
'rectangle',
|
|
2199
2200
|
'rounded-rectangle',
|
|
2200
2201
|
'link',
|
|
2202
|
+
'button',
|
|
2201
2203
|
]) {
|
|
2202
2204
|
expect(rendered).toContain(`case "${type}":`)
|
|
2203
2205
|
}
|
|
2206
|
+
expect(rendered).toContain('struct OneWidgetButtonIntent: AppIntent')
|
|
2207
|
+
expect(rendered).toContain('.supportedFamilies([.systemSmall, .systemMedium])')
|
|
2204
2208
|
// every ActivityView slot needs a decode path in both presentations
|
|
2205
2209
|
for (const slot of [
|
|
2206
2210
|
'lockScreen',
|
|
@@ -2214,13 +2218,81 @@ describe('ios widgets', () => {
|
|
|
2214
2218
|
expect(rendered).toContain(slot)
|
|
2215
2219
|
}
|
|
2216
2220
|
// the JSX bridge methods exist on both sides of the React Native bridge
|
|
2217
|
-
for (const method of ['writeView', 'startView', 'updateView']) {
|
|
2221
|
+
for (const method of ['writeView', 'startView', 'updateView', 'claimActions']) {
|
|
2218
2222
|
expect(readGenerated('MyApp/OneWidgetsBridge.swift')).toContain(method)
|
|
2219
2223
|
expect(readGenerated('MyApp/OneWidgetsBridge.m')).toContain(method)
|
|
2220
2224
|
}
|
|
2221
|
-
|
|
2225
|
+
expect(readGenerated('MyApp/OneWidgetsBridge.swift')).toContain('"oneWidgetAction"')
|
|
2226
|
+
// the shared contract carries the serialized layouts and interactive actions
|
|
2222
2227
|
const contract = readGenerated('MyApp/OneWidgetContract.swift')
|
|
2223
2228
|
expect(contract.match(/let layout: String\?/g)).toHaveLength(2)
|
|
2229
|
+
expect(contract).toContain('static let actionKeyPrefix = "one.widget.action."')
|
|
2230
|
+
expect(contract).toContain('struct Action: Codable')
|
|
2231
|
+
expect(rendered).toContain('OneWidgetContract.actionKeyPrefix + item.id')
|
|
2232
|
+
expect(rendered).toContain('OneWidgetButtonIntent(action: action, value: node.value?.string)')
|
|
2233
|
+
expect(readGenerated('MyApp/OneWidgetsBridge.swift')).toContain(
|
|
2234
|
+
'key.hasPrefix(OneWidgetContract.actionKeyPrefix)'
|
|
2235
|
+
)
|
|
2236
|
+
}, 180000)
|
|
2237
|
+
|
|
2238
|
+
it('emits configured widget families and App Intents spoken dialog metadata', async () => {
|
|
2239
|
+
const workspaceRoot = fileURLToPath(new URL('../../../..', import.meta.url))
|
|
2240
|
+
const output = mkdtempSync(join(tmpdir(), 'vxrn-prebuild-widgets-families-'))
|
|
2241
|
+
await generateForPlatform(
|
|
2242
|
+
workspaceRoot,
|
|
2243
|
+
'ios',
|
|
2244
|
+
{
|
|
2245
|
+
...widgetsApp,
|
|
2246
|
+
ios: {
|
|
2247
|
+
...widgetsApp.ios,
|
|
2248
|
+
widgets: {
|
|
2249
|
+
...widgetsApp.ios.widgets,
|
|
2250
|
+
families: ['systemSmall', 'systemLarge', 'accessoryCircular', 'accessoryRectangular'],
|
|
2251
|
+
},
|
|
2252
|
+
},
|
|
2253
|
+
},
|
|
2254
|
+
join(output, 'ios')
|
|
2255
|
+
)
|
|
2256
|
+
const renderedWidget = readFileSync(
|
|
2257
|
+
join(output, 'ios', 'OneWidgets', 'OneWidget.swift'),
|
|
2258
|
+
'utf8'
|
|
2259
|
+
)
|
|
2260
|
+
expect(renderedWidget).toContain(
|
|
2261
|
+
'.supportedFamilies([.systemSmall, .systemLarge, .accessoryCircular, .accessoryRectangular])'
|
|
2262
|
+
)
|
|
2263
|
+
|
|
2264
|
+
const swift = renderIosAppIntentsSwift([
|
|
2265
|
+
{
|
|
2266
|
+
id: 'dev.one.myapp.ask',
|
|
2267
|
+
title: 'Ask assistant',
|
|
2268
|
+
description: 'Run a voice query and speak the answer.',
|
|
2269
|
+
textParameterTitle: 'Prompt',
|
|
2270
|
+
shortcutPhrase: 'Ask {app} a question',
|
|
2271
|
+
shortcutPhrases: ['Query {app}', 'Ask {app} a question'],
|
|
2272
|
+
systemImageName: 'waveform.circle.fill',
|
|
2273
|
+
openAppWhenRun: true,
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
id: 'dev.one.myapp.ping',
|
|
2277
|
+
title: 'Ping',
|
|
2278
|
+
shortcutPhrase: 'Ping {app}',
|
|
2279
|
+
},
|
|
2280
|
+
])
|
|
2281
|
+
expect(swift).toContain(
|
|
2282
|
+
'func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog'
|
|
2283
|
+
)
|
|
2284
|
+
expect(swift).toContain(
|
|
2285
|
+
'return .result(value: result, dialog: IntentDialog(stringLiteral: result))'
|
|
2286
|
+
)
|
|
2287
|
+
expect(swift).toContain(
|
|
2288
|
+
'static let description = IntentDescription("Run a voice query and speak the answer.")'
|
|
2289
|
+
)
|
|
2290
|
+
expect(swift).toContain('static let openAppWhenRun: Bool = true')
|
|
2291
|
+
expect(swift).toContain(
|
|
2292
|
+
'phrases: ["Ask \\(.applicationName) a question", "Query \\(.applicationName)"]'
|
|
2293
|
+
)
|
|
2294
|
+
expect(swift).toContain('systemImageName: "waveform.circle.fill"')
|
|
2295
|
+
expect(swift).toContain('systemImageName: "bolt.fill"')
|
|
2224
2296
|
}, 180000)
|
|
2225
2297
|
|
|
2226
2298
|
it('regenerates byte-identical widget projects and adds nothing without the config', async () => {
|
|
@@ -372,6 +372,12 @@ function swiftShortcutPhrase(value: string): string {
|
|
|
372
372
|
export function renderIosAppIntentsSwift(actions: NonNullable<NonNullable<NativeAppManifest['ios']>['appIntents']>['actions']): string {
|
|
373
373
|
const types = actions.map((action, index) => {
|
|
374
374
|
const summaryTitle = swiftLiteral(action.title).slice(1, -1)
|
|
375
|
+
const description =
|
|
376
|
+
action.description === undefined
|
|
377
|
+
? ''
|
|
378
|
+
: `\n static let description = IntentDescription(${swiftLiteral(action.description)})`
|
|
379
|
+
const openAppWhenRun =
|
|
380
|
+
action.openAppWhenRun === true ? '\n static let openAppWhenRun: Bool = true' : ''
|
|
375
381
|
const text = action.textParameterTitle === undefined ? '' : `
|
|
376
382
|
@Parameter(title: ${swiftLiteral(action.textParameterTitle)}) var text: String
|
|
377
383
|
|
|
@@ -380,20 +386,25 @@ export function renderIosAppIntentsSwift(actions: NonNullable<NonNullable<Native
|
|
|
380
386
|
}
|
|
381
387
|
`
|
|
382
388
|
return `struct OneAppIntent${index}: AppIntent {
|
|
383
|
-
static let title: LocalizedStringResource = ${swiftLiteral(action.title)}
|
|
389
|
+
static let title: LocalizedStringResource = ${swiftLiteral(action.title)}${description}${openAppWhenRun}
|
|
384
390
|
${text}
|
|
385
|
-
func perform() async throws -> some IntentResult & ReturnsValue<String> {
|
|
391
|
+
func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
|
|
386
392
|
let result = try await oneAppIntentsRun(${swiftLiteral(action.id)}, ${text ? 'text' : 'nil'})
|
|
387
|
-
return .result(value: result)
|
|
393
|
+
return .result(value: result, dialog: IntentDialog(stringLiteral: result))
|
|
388
394
|
}
|
|
389
395
|
}`
|
|
390
396
|
})
|
|
391
|
-
const shortcuts = actions.map((action, index) =>
|
|
397
|
+
const shortcuts = actions.map((action, index) => {
|
|
398
|
+
const phrases = Array.from(
|
|
399
|
+
new Set([action.shortcutPhrase, ...(action.shortcutPhrases ?? [])])
|
|
400
|
+
).map(swiftShortcutPhrase)
|
|
401
|
+
return ` AppShortcut(
|
|
392
402
|
intent: OneAppIntent${index}(),
|
|
393
|
-
phrases: [${
|
|
403
|
+
phrases: [${phrases.join(', ')}],
|
|
394
404
|
shortTitle: ${swiftLiteral(action.title)},
|
|
395
|
-
systemImageName:
|
|
396
|
-
)`
|
|
405
|
+
systemImageName: ${swiftLiteral(action.systemImageName ?? 'bolt.fill')}
|
|
406
|
+
)`
|
|
407
|
+
}).join('\n')
|
|
397
408
|
return `import AppIntents
|
|
398
409
|
import Foundation
|
|
399
410
|
|
|
@@ -871,6 +882,9 @@ function generateIosWidgets(dest: string, app: NativeAppManifest): void {
|
|
|
871
882
|
</dict></plist>
|
|
872
883
|
`
|
|
873
884
|
)
|
|
885
|
+
const supportedFamilies = (widgets.families ?? ['systemSmall', 'systemMedium'])
|
|
886
|
+
.map((family) => `.${family}`)
|
|
887
|
+
.join(', ')
|
|
874
888
|
writeFileSync(
|
|
875
889
|
path.join(appDir, 'OneWidgetContract.swift'),
|
|
876
890
|
`import Foundation
|
|
@@ -880,6 +894,7 @@ enum OneWidgetContract {
|
|
|
880
894
|
static let appGroup = ${JSON.stringify(widgets.appGroup)}
|
|
881
895
|
static let kind = ${JSON.stringify(widgets.kind)}
|
|
882
896
|
static let dataKey = "one.widget.data"
|
|
897
|
+
static let actionKeyPrefix = "one.widget.action."
|
|
883
898
|
|
|
884
899
|
struct Data: Codable {
|
|
885
900
|
let title: String
|
|
@@ -888,6 +903,13 @@ enum OneWidgetContract {
|
|
|
888
903
|
let layout: String?
|
|
889
904
|
}
|
|
890
905
|
|
|
906
|
+
struct Action: Codable {
|
|
907
|
+
let id: String
|
|
908
|
+
let action: String
|
|
909
|
+
let value: String?
|
|
910
|
+
let timestampMs: Double
|
|
911
|
+
}
|
|
912
|
+
|
|
891
913
|
struct Attributes: ActivityAttributes {
|
|
892
914
|
struct ContentState: Codable, Hashable {
|
|
893
915
|
let status: String
|
|
@@ -904,6 +926,7 @@ enum OneWidgetContract {
|
|
|
904
926
|
`import SwiftUI
|
|
905
927
|
import WidgetKit
|
|
906
928
|
import ActivityKit
|
|
929
|
+
import AppIntents
|
|
907
930
|
|
|
908
931
|
struct OneWidgetNode: Decodable {
|
|
909
932
|
let type: String
|
|
@@ -911,11 +934,36 @@ struct OneWidgetNode: Decodable {
|
|
|
911
934
|
let style: Style?
|
|
912
935
|
let children: [OneWidgetNode]?
|
|
913
936
|
let systemName: String?
|
|
914
|
-
let value:
|
|
937
|
+
let value: Value?
|
|
915
938
|
let total: Double?
|
|
916
939
|
let fill: String?
|
|
917
940
|
let cornerRadius: Double?
|
|
918
941
|
let url: String?
|
|
942
|
+
let action: String?
|
|
943
|
+
|
|
944
|
+
enum Value: Decodable {
|
|
945
|
+
case number(Double)
|
|
946
|
+
case string(String)
|
|
947
|
+
|
|
948
|
+
var number: Double? {
|
|
949
|
+
if case .number(let value) = self { return value }
|
|
950
|
+
return nil
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
var string: String? {
|
|
954
|
+
if case .string(let value) = self { return value }
|
|
955
|
+
return nil
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
init(from decoder: Decoder) throws {
|
|
959
|
+
let container = try decoder.singleValueContainer()
|
|
960
|
+
if let number = try? container.decode(Double.self) {
|
|
961
|
+
self = .number(number)
|
|
962
|
+
} else {
|
|
963
|
+
self = .string(try container.decode(String.self))
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
919
967
|
|
|
920
968
|
struct Style: Decodable {
|
|
921
969
|
let color: String?
|
|
@@ -934,6 +982,43 @@ struct OneWidgetNode: Decodable {
|
|
|
934
982
|
}
|
|
935
983
|
}
|
|
936
984
|
|
|
985
|
+
struct OneWidgetButtonIntent: AppIntent {
|
|
986
|
+
static var title: LocalizedStringResource = "Widget Action"
|
|
987
|
+
|
|
988
|
+
@Parameter(title: "Action") var action: String
|
|
989
|
+
@Parameter(title: "Value") var value: String?
|
|
990
|
+
|
|
991
|
+
init() {}
|
|
992
|
+
|
|
993
|
+
init(action: String, value: String?) {
|
|
994
|
+
self.action = action
|
|
995
|
+
self.value = value
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
func perform() async throws -> some IntentResult {
|
|
999
|
+
if let defaults = UserDefaults(suiteName: OneWidgetContract.appGroup) {
|
|
1000
|
+
let item = OneWidgetContract.Action(
|
|
1001
|
+
id: UUID().uuidString,
|
|
1002
|
+
action: action,
|
|
1003
|
+
value: value,
|
|
1004
|
+
timestampMs: Date().timeIntervalSince1970 * 1000
|
|
1005
|
+
)
|
|
1006
|
+
if let encoded = try? JSONEncoder().encode(item) {
|
|
1007
|
+
defaults.set(encoded, forKey: OneWidgetContract.actionKeyPrefix + item.id)
|
|
1008
|
+
}
|
|
1009
|
+
CFNotificationCenterPostNotification(
|
|
1010
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
1011
|
+
CFNotificationName("dev.onejs.widget.action" as CFString),
|
|
1012
|
+
nil,
|
|
1013
|
+
nil,
|
|
1014
|
+
true
|
|
1015
|
+
)
|
|
1016
|
+
}
|
|
1017
|
+
WidgetCenter.shared.reloadTimelines(ofKind: OneWidgetContract.kind)
|
|
1018
|
+
return .result()
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
937
1022
|
struct OneActivityView: Decodable {
|
|
938
1023
|
let lockScreen: OneWidgetNode
|
|
939
1024
|
let compactLeading: OneWidgetNode?
|
|
@@ -1026,10 +1111,10 @@ struct OneWidgetRendered: View {
|
|
|
1026
1111
|
.font(.system(size: CGFloat(node.style?.fontSize ?? 20), weight: weight))
|
|
1027
1112
|
}
|
|
1028
1113
|
case "progress":
|
|
1029
|
-
ProgressView(value: node.value ?? 0, total: node.total ?? 1)
|
|
1114
|
+
ProgressView(value: node.value?.number ?? 0, total: node.total ?? 1)
|
|
1030
1115
|
.tint(oneColor(node.style?.color))
|
|
1031
1116
|
case "gauge":
|
|
1032
|
-
Gauge(value: node.value ?? 0, in: 0...(node.total ?? 1)) { EmptyView() }
|
|
1117
|
+
Gauge(value: node.value?.number ?? 0, in: 0...(node.total ?? 1)) { EmptyView() }
|
|
1033
1118
|
.gaugeStyle(.accessoryCircular)
|
|
1034
1119
|
.tint(oneColor(node.style?.color))
|
|
1035
1120
|
case "circle":
|
|
@@ -1047,6 +1132,15 @@ struct OneWidgetRendered: View {
|
|
|
1047
1132
|
}
|
|
1048
1133
|
}
|
|
1049
1134
|
}
|
|
1135
|
+
case "button":
|
|
1136
|
+
if let action = node.action {
|
|
1137
|
+
Button(intent: OneWidgetButtonIntent(action: action, value: node.value?.string)) {
|
|
1138
|
+
ForEach(Array((node.children ?? []).enumerated()), id: \\.offset) { _, child in
|
|
1139
|
+
OneWidgetRendered(node: child)
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
.buttonStyle(.plain)
|
|
1143
|
+
}
|
|
1050
1144
|
default: EmptyView()
|
|
1051
1145
|
}
|
|
1052
1146
|
}
|
|
@@ -1107,7 +1201,7 @@ struct OneWidget: Widget {
|
|
|
1107
1201
|
}
|
|
1108
1202
|
.configurationDisplayName(${JSON.stringify(widgets.displayName)})
|
|
1109
1203
|
.description(${JSON.stringify(widgets.description)})
|
|
1110
|
-
.supportedFamilies([
|
|
1204
|
+
.supportedFamilies([${supportedFamilies}])
|
|
1111
1205
|
}
|
|
1112
1206
|
}
|
|
1113
1207
|
|
|
@@ -1204,31 +1298,120 @@ RCT_EXTERN_METHOD(update:(NSString *)identifier status:(NSString *)status value:
|
|
|
1204
1298
|
RCT_EXTERN_METHOD(updateView:(NSString *)identifier layout:(NSString *)layout resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
1205
1299
|
RCT_EXTERN_METHOD(end:(NSString *)identifier resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
1206
1300
|
RCT_EXTERN_METHOD(pushToken:(NSString *)identifier resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
1301
|
+
RCT_EXTERN_METHOD(claimActions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
1207
1302
|
@end
|
|
1208
1303
|
`
|
|
1209
1304
|
)
|
|
1210
1305
|
writeFileSync(
|
|
1211
1306
|
path.join(appDir, 'OneWidgetsBridge.swift'),
|
|
1212
1307
|
`import Foundation
|
|
1308
|
+
import UIKit
|
|
1213
1309
|
import React
|
|
1214
1310
|
import WidgetKit
|
|
1215
1311
|
import ActivityKit
|
|
1216
1312
|
|
|
1313
|
+
private func oneWidgetsDarwinCallback(
|
|
1314
|
+
_ center: CFNotificationCenter?,
|
|
1315
|
+
_ observer: UnsafeMutableRawPointer?,
|
|
1316
|
+
_ name: CFNotificationName?,
|
|
1317
|
+
_ object: UnsafeRawPointer?,
|
|
1318
|
+
_ userInfo: CFDictionary?
|
|
1319
|
+
) {
|
|
1320
|
+
guard let observer else { return }
|
|
1321
|
+
let bridge = Unmanaged<OneWidgetsBridge>.fromOpaque(observer).takeUnretainedValue()
|
|
1322
|
+
bridge.flushObservedActions()
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1217
1325
|
@objc(OneWidgetsBridge)
|
|
1218
1326
|
class OneWidgetsBridge: RCTEventEmitter {
|
|
1219
1327
|
private var tokenTasks: [String: Task<Void, Never>] = [:]
|
|
1328
|
+
private var isObservingEvents = false
|
|
1329
|
+
private var foregroundObserver: NSObjectProtocol?
|
|
1330
|
+
private let actionLock = NSLock()
|
|
1220
1331
|
|
|
1221
|
-
override func supportedEvents() -> [String]! { ["oneLiveActivityPushToken"] }
|
|
1332
|
+
override func supportedEvents() -> [String]! { ["oneLiveActivityPushToken", "oneWidgetAction"] }
|
|
1222
1333
|
|
|
1223
1334
|
override func startObserving() {
|
|
1335
|
+
isObservingEvents = true
|
|
1224
1336
|
for activity in Activity<OneWidgetContract.Attributes>.activities {
|
|
1225
1337
|
observeToken(activity)
|
|
1226
1338
|
}
|
|
1339
|
+
CFNotificationCenterAddObserver(
|
|
1340
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
1341
|
+
Unmanaged.passUnretained(self).toOpaque(),
|
|
1342
|
+
oneWidgetsDarwinCallback,
|
|
1343
|
+
"dev.onejs.widget.action" as CFString,
|
|
1344
|
+
nil,
|
|
1345
|
+
.deliverImmediately
|
|
1346
|
+
)
|
|
1347
|
+
foregroundObserver = NotificationCenter.default.addObserver(
|
|
1348
|
+
forName: UIApplication.didBecomeActiveNotification,
|
|
1349
|
+
object: nil,
|
|
1350
|
+
queue: .main
|
|
1351
|
+
) { [weak self] _ in
|
|
1352
|
+
self?.flushObservedActions()
|
|
1353
|
+
}
|
|
1354
|
+
flushObservedActions()
|
|
1227
1355
|
}
|
|
1228
1356
|
|
|
1229
1357
|
override func stopObserving() {
|
|
1358
|
+
isObservingEvents = false
|
|
1230
1359
|
for task in tokenTasks.values { task.cancel() }
|
|
1231
1360
|
tokenTasks.removeAll()
|
|
1361
|
+
CFNotificationCenterRemoveObserver(
|
|
1362
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
1363
|
+
Unmanaged.passUnretained(self).toOpaque(),
|
|
1364
|
+
CFNotificationName("dev.onejs.widget.action" as CFString),
|
|
1365
|
+
nil
|
|
1366
|
+
)
|
|
1367
|
+
if let foregroundObserver {
|
|
1368
|
+
NotificationCenter.default.removeObserver(foregroundObserver)
|
|
1369
|
+
self.foregroundObserver = nil
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
deinit {
|
|
1374
|
+
CFNotificationCenterRemoveObserver(
|
|
1375
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
1376
|
+
Unmanaged.passUnretained(self).toOpaque(),
|
|
1377
|
+
CFNotificationName("dev.onejs.widget.action" as CFString),
|
|
1378
|
+
nil
|
|
1379
|
+
)
|
|
1380
|
+
if let foregroundObserver {
|
|
1381
|
+
NotificationCenter.default.removeObserver(foregroundObserver)
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
fileprivate func flushObservedActions() {
|
|
1386
|
+
guard isObservingEvents else { return }
|
|
1387
|
+
for item in drainStoredActions() {
|
|
1388
|
+
sendEvent(withName: "oneWidgetAction", body: item)
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
private func drainStoredActions() -> [[String: Any]] {
|
|
1393
|
+
actionLock.lock()
|
|
1394
|
+
defer { actionLock.unlock() }
|
|
1395
|
+
guard let defaults = UserDefaults(suiteName: OneWidgetContract.appGroup) else {
|
|
1396
|
+
return []
|
|
1397
|
+
}
|
|
1398
|
+
var actions: [OneWidgetContract.Action] = []
|
|
1399
|
+
for key in defaults.dictionaryRepresentation().keys where key.hasPrefix(OneWidgetContract.actionKeyPrefix) {
|
|
1400
|
+
guard let data = defaults.data(forKey: key) else { continue }
|
|
1401
|
+
defaults.removeObject(forKey: key)
|
|
1402
|
+
if let action = try? JSONDecoder().decode(OneWidgetContract.Action.self, from: data) {
|
|
1403
|
+
actions.append(action)
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
actions.sort { $0.timestampMs < $1.timestampMs }
|
|
1407
|
+
return actions.map { action in
|
|
1408
|
+
[
|
|
1409
|
+
"id": action.id,
|
|
1410
|
+
"action": action.action,
|
|
1411
|
+
"value": action.value ?? NSNull(),
|
|
1412
|
+
"timestampMs": action.timestampMs,
|
|
1413
|
+
]
|
|
1414
|
+
}
|
|
1232
1415
|
}
|
|
1233
1416
|
|
|
1234
1417
|
private func observeToken(_ activity: Activity<OneWidgetContract.Attributes>) {
|
|
@@ -1370,6 +1553,15 @@ class OneWidgetsBridge: RCTEventEmitter {
|
|
|
1370
1553
|
resolve(NSNull())
|
|
1371
1554
|
}
|
|
1372
1555
|
}
|
|
1556
|
+
|
|
1557
|
+
@objc(claimActions:rejecter:)
|
|
1558
|
+
func claimActions(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
|
|
1559
|
+
guard UserDefaults(suiteName: OneWidgetContract.appGroup) != nil else {
|
|
1560
|
+
reject("app_group", "Cannot open the configured App Group", nil)
|
|
1561
|
+
return
|
|
1562
|
+
}
|
|
1563
|
+
resolve(drainStoredActions())
|
|
1564
|
+
}
|
|
1373
1565
|
}
|
|
1374
1566
|
`
|
|
1375
1567
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prebuild.d.ts","sourceRoot":"","sources":["../../src/exports/prebuild.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prebuild.d.ts","sourceRoot":"","sources":["../../src/exports/prebuild.ts"],"names":[],"mappings":"AAMA,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAA;AAK9B,eAAO,MAAM,QAAQ,qEAMlB;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,GAAG,EAAE,iBAAiB,CAAA;IAGtB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,kBAkGA,CAAA;AAuBD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,iBAOpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prebuildWithoutExpo.d.ts","sourceRoot":"","sources":["../../src/exports/prebuildWithoutExpo.ts"],"names":[],"mappings":"AAkBA,OAAO,EAKL,iBAAiB,EAEjB,KAAK,iBAAiB,EAEvB,MAAM,+BAA+B,CAAA;AAwEtC,YAAY,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AAE3F,eAAO,MAAM,mBAAmB,0BAAoB,CAAA;AA6EpD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwChE;AAyJD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"prebuildWithoutExpo.d.ts","sourceRoot":"","sources":["../../src/exports/prebuildWithoutExpo.ts"],"names":[],"mappings":"AAkBA,OAAO,EAKL,iBAAiB,EAEjB,KAAK,iBAAiB,EAEvB,MAAM,+BAA+B,CAAA;AAwEtC,YAAY,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AAE3F,eAAO,MAAM,mBAAmB,0BAAoB,CAAA;AA6EpD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwChE;AAyJD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,MAAM,CAyDrI;AAqnCD,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AA6sBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,KAAK,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,iBAAiB,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,GAAG,oBAAoB,CA0xBvB;AAED,eAAO,MAAM,mBAAmB,SACxB,MAAM,YACF,KAAK,GAAG,SAAS,OACtB,iBAAiB,WACd,MAAM,kBAwEf,CAAA;AA6DD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,QA6EvD;AAID,wBAAsB,qBAAqB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,iBAsDzF;AAyFD,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,iBAAiB,CAAA;IACtB,SAAS,EAAE,SAAS,yBAAyB,EAAE,CAAA;CAChD,GAAG,IAAI,CAmCP;AAID,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,yBAAyB,EAAE,CAAC,CA+BtC;AAKD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;CACtC,GAAG,IAAI,CAaP"}
|