nativescript 8.9.0-dev.1 → 8.9.0-dev.3
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/config/test-deps-versions-generated.json +1 -1
- package/lib/.d.ts +1 -0
- package/lib/bootstrap.js +1 -0
- package/lib/commands/widget.js +805 -0
- package/lib/constants.js +1 -1
- package/lib/definitions/project.d.ts +50 -24
- package/lib/services/ios-project-service.js +28 -11
- package/lib/services/project-config-service.js +12 -3
- package/lib/tools/config-manipulation/config-transformer.js +9 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"@jsdevtools/coverage-istanbul-loader":"3.0.5","karma":"6.4.4","karma-coverage":"2.2.1","karma-nativescript-launcher":"0.4.0","mocha":"11.1.0","karma-mocha":"2.0.1","karma-chai":"0.1.0","karma-jasmine":"4.0.2","karma-qunit":"4.2.1","@types/karma-chai":"0.1.7","@types/mocha":"10.0.10","@types/jasmine":"5.1.
|
|
1
|
+
{"@jsdevtools/coverage-istanbul-loader":"3.0.5","karma":"6.4.4","karma-coverage":"2.2.1","karma-nativescript-launcher":"0.4.0","mocha":"11.1.0","karma-mocha":"2.0.1","karma-chai":"0.1.0","karma-jasmine":"4.0.2","karma-qunit":"4.2.1","@types/karma-chai":"0.1.7","@types/mocha":"10.0.10","@types/jasmine":"5.1.7","@types/qunit":"2.19.12","nyc":"17.1.0"}
|
package/lib/.d.ts
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
/// <reference path="commands/typings.ts" />
|
|
49
49
|
/// <reference path="commands/update-platform.ts" />
|
|
50
50
|
/// <reference path="commands/update.ts" />
|
|
51
|
+
/// <reference path="commands/widget.ts" />
|
|
51
52
|
/// <reference path="common/bootstrap.ts" />
|
|
52
53
|
/// <reference path="common/child-process.ts" />
|
|
53
54
|
/// <reference path="common/codeGeneration/code-entity.ts" />
|
package/lib/bootstrap.js
CHANGED
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WidgetIOSCommand = exports.WidgetCommand = void 0;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const prompts = require("prompts");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const plist = require("plist");
|
|
8
|
+
const yok_1 = require("../common/yok");
|
|
9
|
+
const utils_1 = require("../common/utils");
|
|
10
|
+
const os_1 = require("os");
|
|
11
|
+
class WidgetCommand {
|
|
12
|
+
constructor($projectData, $projectConfigService, $logger, $errors) {
|
|
13
|
+
this.$projectData = $projectData;
|
|
14
|
+
this.$projectConfigService = $projectConfigService;
|
|
15
|
+
this.$logger = $logger;
|
|
16
|
+
this.$errors = $errors;
|
|
17
|
+
this.allowedParameters = [];
|
|
18
|
+
this.$projectData.initializeProjectData();
|
|
19
|
+
}
|
|
20
|
+
async execute(args) {
|
|
21
|
+
this.failWithUsage();
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
failWithUsage() {
|
|
25
|
+
this.$errors.failWithHelp("Usage: ns widget ios");
|
|
26
|
+
}
|
|
27
|
+
async canExecute(args) {
|
|
28
|
+
this.failWithUsage();
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
getIosSourcePathBase() {
|
|
32
|
+
const resources = this.$projectData.getAppResourcesDirectoryPath();
|
|
33
|
+
return path.join(resources, "iOS", "src");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.WidgetCommand = WidgetCommand;
|
|
37
|
+
class WidgetIOSCommand extends WidgetCommand {
|
|
38
|
+
constructor($projectData, $projectConfigService, $logger, $errors) {
|
|
39
|
+
super($projectData, $projectConfigService, $logger, $errors);
|
|
40
|
+
}
|
|
41
|
+
async canExecute(args) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
async execute(args) {
|
|
45
|
+
this.startPrompt(args);
|
|
46
|
+
}
|
|
47
|
+
async startPrompt(args) {
|
|
48
|
+
let result = await prompts.prompt({
|
|
49
|
+
type: "text",
|
|
50
|
+
name: "name",
|
|
51
|
+
message: `What name would you like for this widget? (Default is 'widget')`,
|
|
52
|
+
});
|
|
53
|
+
const name = (result.name || "widget").toLowerCase();
|
|
54
|
+
result = await prompts.prompt({
|
|
55
|
+
type: "select",
|
|
56
|
+
name: "value",
|
|
57
|
+
message: `What type of widget would you like?`,
|
|
58
|
+
choices: [
|
|
59
|
+
{
|
|
60
|
+
title: "Live Activity",
|
|
61
|
+
description: "This will create a Live Activity that will display on the iOS Lock Screen.",
|
|
62
|
+
value: 0,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: "Live Activity with Home Screen Widget",
|
|
66
|
+
description: "This will create a Live Activity that will display on the iOS Lock Screen with an optional Widget.",
|
|
67
|
+
value: 1,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: "Home Screen Widget",
|
|
71
|
+
description: "This will create just a Home Screen Widget.",
|
|
72
|
+
value: 2,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
initial: 1,
|
|
76
|
+
});
|
|
77
|
+
const bundleId = this.$projectConfigService.getValue(`id`, "");
|
|
78
|
+
switch (result.value) {
|
|
79
|
+
case 0:
|
|
80
|
+
this.$logger.info("TODO");
|
|
81
|
+
break;
|
|
82
|
+
case 1:
|
|
83
|
+
await this.generateSharedWidgetPackage(this.$projectData.projectDir, name);
|
|
84
|
+
this.generateWidget(this.$projectData.projectDir, name, bundleId, result.value);
|
|
85
|
+
this.generateAppleUtility(this.$projectData.projectDir, name, bundleId);
|
|
86
|
+
break;
|
|
87
|
+
case 2:
|
|
88
|
+
this.$logger.info("TODO");
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async generateSharedWidgetPackage(projectDir, name) {
|
|
93
|
+
var _a;
|
|
94
|
+
const sharedWidgetDir = "Shared_Resources/iOS/SharedWidget";
|
|
95
|
+
const sharedWidgetPath = path.join(projectDir, sharedWidgetDir);
|
|
96
|
+
const sharedWidgetSourceDir = "Sources/SharedWidget";
|
|
97
|
+
const sharedWidgetPackagePath = path.join(projectDir, `${sharedWidgetDir}/Package.swift`);
|
|
98
|
+
const sharedWidgetSourcePath = path.join(sharedWidgetPath, `${sharedWidgetSourceDir}/${(0, utils_1.capitalizeFirstLetter)(name)}Model.swift`);
|
|
99
|
+
const gitIgnorePath = path.join(projectDir, ".gitignore");
|
|
100
|
+
if (!fs.existsSync(sharedWidgetPackagePath)) {
|
|
101
|
+
fs.mkdirSync(sharedWidgetPath, { recursive: true });
|
|
102
|
+
fs.mkdirSync(path.join(sharedWidgetPath, sharedWidgetSourceDir), {
|
|
103
|
+
recursive: true,
|
|
104
|
+
});
|
|
105
|
+
let content = `// swift-tools-version:5.9
|
|
106
|
+
import PackageDescription
|
|
107
|
+
|
|
108
|
+
let package = Package(
|
|
109
|
+
name: "SharedWidget",
|
|
110
|
+
platforms: [
|
|
111
|
+
.iOS(.v13)
|
|
112
|
+
],
|
|
113
|
+
products: [
|
|
114
|
+
.library(
|
|
115
|
+
name: "SharedWidget",
|
|
116
|
+
targets: ["SharedWidget"])
|
|
117
|
+
],
|
|
118
|
+
dependencies: [
|
|
119
|
+
// Dependencies declare other packages that this package depends on.
|
|
120
|
+
],
|
|
121
|
+
targets: [
|
|
122
|
+
.target(
|
|
123
|
+
name: "SharedWidget",
|
|
124
|
+
dependencies: []
|
|
125
|
+
)
|
|
126
|
+
]
|
|
127
|
+
)${os_1.EOL}`;
|
|
128
|
+
fs.writeFileSync(sharedWidgetPackagePath, content);
|
|
129
|
+
content = `import ActivityKit
|
|
130
|
+
import WidgetKit
|
|
131
|
+
|
|
132
|
+
public struct ${(0, utils_1.capitalizeFirstLetter)(name)}Model: ActivityAttributes {
|
|
133
|
+
public typealias DeliveryStatus = ContentState
|
|
134
|
+
|
|
135
|
+
public struct ContentState: Codable, Hashable {
|
|
136
|
+
// Dynamic stateful properties about your activity go here!
|
|
137
|
+
public var message: String
|
|
138
|
+
public var deliveryTime: Double
|
|
139
|
+
|
|
140
|
+
public init(message: String, deliveryTime: Double) {
|
|
141
|
+
self.message = message
|
|
142
|
+
self.deliveryTime = deliveryTime
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Fixed non-changing properties about your activity go here!
|
|
147
|
+
public var numberOfPizzas: Int
|
|
148
|
+
public var totalAmount: String
|
|
149
|
+
|
|
150
|
+
public init(numberOfPizzas: Int, totalAmount: String) {
|
|
151
|
+
self.numberOfPizzas = numberOfPizzas
|
|
152
|
+
self.totalAmount = totalAmount
|
|
153
|
+
}
|
|
154
|
+
}${os_1.EOL}`;
|
|
155
|
+
fs.writeFileSync(sharedWidgetSourcePath, content);
|
|
156
|
+
const configData = this.$projectConfigService.readConfig(projectDir);
|
|
157
|
+
if (!configData.ios) {
|
|
158
|
+
configData.ios = {};
|
|
159
|
+
}
|
|
160
|
+
if (!configData.ios.SPMPackages) {
|
|
161
|
+
configData.ios.SPMPackages = [];
|
|
162
|
+
}
|
|
163
|
+
const spmPackages = configData.ios.SPMPackages;
|
|
164
|
+
const sharedWidgetPackage = spmPackages === null || spmPackages === void 0 ? void 0 : spmPackages.find((p) => p.name === "SharedWidget");
|
|
165
|
+
if (!sharedWidgetPackage) {
|
|
166
|
+
spmPackages.push({
|
|
167
|
+
name: "SharedWidget",
|
|
168
|
+
libs: ["SharedWidget"],
|
|
169
|
+
path: "./Shared_Resources/iOS/SharedWidget",
|
|
170
|
+
targets: [name],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (!((_a = sharedWidgetPackage.targets) === null || _a === void 0 ? void 0 : _a.includes(name))) {
|
|
175
|
+
sharedWidgetPackage.targets.push(name);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
configData.ios.SPMPackages = spmPackages;
|
|
179
|
+
await this.$projectConfigService.setValue("", configData);
|
|
180
|
+
if (fs.existsSync(gitIgnorePath)) {
|
|
181
|
+
const gitIgnore = fs.readFileSync(gitIgnorePath, {
|
|
182
|
+
encoding: "utf-8",
|
|
183
|
+
});
|
|
184
|
+
const swiftBuildIgnore = `# Swift
|
|
185
|
+
.build
|
|
186
|
+
.swiftpm`;
|
|
187
|
+
if (gitIgnore.indexOf(swiftBuildIgnore) === -1) {
|
|
188
|
+
content = `${gitIgnore}${os_1.EOL}${swiftBuildIgnore}${os_1.EOL}`;
|
|
189
|
+
fs.writeFileSync(gitIgnorePath, content);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
console.log(`\nCreated Shared Resources: ${sharedWidgetDir}.\n`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
generateWidget(projectDir, name, bundleId, type) {
|
|
196
|
+
const appResourcePath = this.$projectData.appResourcesDirectoryPath;
|
|
197
|
+
const capitalName = (0, utils_1.capitalizeFirstLetter)(name);
|
|
198
|
+
const appInfoPlistPath = path.join(appResourcePath, "iOS", "Info.plist");
|
|
199
|
+
const extensionDir = path.join(appResourcePath, "iOS", "extensions");
|
|
200
|
+
const widgetPath = path.join(extensionDir, name);
|
|
201
|
+
const extensionProvisionPath = path.join(extensionDir, `provisioning.json`);
|
|
202
|
+
const extensionsInfoPath = path.join(widgetPath, `Info.plist`);
|
|
203
|
+
const extensionsPrivacyPath = path.join(widgetPath, `PrivacyInfo.xcprivacy`);
|
|
204
|
+
const extensionsConfigPath = path.join(widgetPath, `extension.json`);
|
|
205
|
+
const entitlementsPath = path.join(widgetPath, `${name}.entitlements`);
|
|
206
|
+
const widgetBundlePath = path.join(widgetPath, `${capitalName}Bundle.swift`);
|
|
207
|
+
const widgetHomeScreenPath = path.join(widgetPath, `${capitalName}HomeScreenWidget.swift`);
|
|
208
|
+
const widgetLiveActivityPath = path.join(widgetPath, `${capitalName}LiveActivity.swift`);
|
|
209
|
+
const appEntitlementsPath = path.join(appResourcePath, "iOS", "app.entitlements");
|
|
210
|
+
if (!fs.existsSync(extensionsConfigPath)) {
|
|
211
|
+
fs.mkdirSync(widgetPath, { recursive: true });
|
|
212
|
+
let content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
213
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
214
|
+
<plist version="1.0">
|
|
215
|
+
<dict>
|
|
216
|
+
<key>NSExtension</key>
|
|
217
|
+
<dict>
|
|
218
|
+
<key>NSExtensionPointIdentifier</key>
|
|
219
|
+
<string>com.apple.widgetkit-extension</string>
|
|
220
|
+
</dict>
|
|
221
|
+
<key>CFBundleShortVersionString</key>
|
|
222
|
+
<string>1.0</string>
|
|
223
|
+
<key>CFBundleVersion</key>
|
|
224
|
+
<string>1.0</string>
|
|
225
|
+
</dict>
|
|
226
|
+
</plist>${os_1.EOL}`;
|
|
227
|
+
fs.writeFileSync(extensionsInfoPath, content);
|
|
228
|
+
content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
229
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
230
|
+
<plist version="1.0">
|
|
231
|
+
<dict>
|
|
232
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
233
|
+
<array>
|
|
234
|
+
<dict>
|
|
235
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
236
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
237
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
238
|
+
<array>
|
|
239
|
+
<string>CA92.1</string>
|
|
240
|
+
</array>
|
|
241
|
+
</dict>
|
|
242
|
+
</array>
|
|
243
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
244
|
+
<array/>
|
|
245
|
+
<key>NSPrivacyTracking</key>
|
|
246
|
+
<false/>
|
|
247
|
+
</dict>
|
|
248
|
+
</plist>${os_1.EOL}`;
|
|
249
|
+
fs.writeFileSync(extensionsPrivacyPath, content);
|
|
250
|
+
content = `import WidgetKit
|
|
251
|
+
import SwiftUI
|
|
252
|
+
|
|
253
|
+
@main
|
|
254
|
+
struct ${capitalName}Bundle: SwiftUI.WidgetBundle {
|
|
255
|
+
var body: some Widget {
|
|
256
|
+
${[1, 2].includes(type) ? capitalName + "HomeScreenWidget()" : ""}
|
|
257
|
+
${[0, 1].includes(type) ? capitalName + "LiveActivity()" : ""}
|
|
258
|
+
}
|
|
259
|
+
}${os_1.EOL}`;
|
|
260
|
+
fs.writeFileSync(widgetBundlePath, content);
|
|
261
|
+
if ([0, 1].includes(type)) {
|
|
262
|
+
content = `import ActivityKit
|
|
263
|
+
import SwiftUI
|
|
264
|
+
import WidgetKit
|
|
265
|
+
import Foundation
|
|
266
|
+
import SharedWidget
|
|
267
|
+
import os
|
|
268
|
+
|
|
269
|
+
struct ${capitalName}LiveActivity: Widget {
|
|
270
|
+
|
|
271
|
+
var body: some WidgetConfiguration {
|
|
272
|
+
ActivityConfiguration(for: ${capitalName}Model.self) { context in
|
|
273
|
+
|
|
274
|
+
LockScreenView(message: context.state.message, deliveryTime: context.state.deliveryTime)
|
|
275
|
+
.activityBackgroundTint(Color.black)
|
|
276
|
+
.activitySystemActionForegroundColor(Color.white)
|
|
277
|
+
|
|
278
|
+
} dynamicIsland: { context in
|
|
279
|
+
DynamicIsland {
|
|
280
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
281
|
+
Image(systemName: context.state.deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse")
|
|
282
|
+
.resizable()
|
|
283
|
+
.scaledToFit()
|
|
284
|
+
.frame(width: 50, height: 50)
|
|
285
|
+
.foregroundColor(context.state.deliveryTime >= 0 ? Color.green : Color.blue)
|
|
286
|
+
}
|
|
287
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
288
|
+
if (context.state.deliveryTime >= 0) {
|
|
289
|
+
ZStack {
|
|
290
|
+
ProgressView(value: context.state.deliveryTime, total: 60)
|
|
291
|
+
.progressViewStyle(.circular)
|
|
292
|
+
.tint(Color.green)
|
|
293
|
+
.frame(width: 75, height: 75)
|
|
294
|
+
Text("\\(formatter.string(for: context.state.deliveryTime) ?? "") mins")
|
|
295
|
+
.font(.system(size: 11))
|
|
296
|
+
.foregroundStyle(.white)
|
|
297
|
+
}.frame(width: 75, height: 75)
|
|
298
|
+
} else {
|
|
299
|
+
Image(systemName: "checkmark.circle.fill")
|
|
300
|
+
.resizable()
|
|
301
|
+
.scaledToFit()
|
|
302
|
+
.frame(width: 50, height: 50)
|
|
303
|
+
.foregroundColor(.blue)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
307
|
+
Text("\\(context.state.message)")
|
|
308
|
+
}
|
|
309
|
+
} compactLeading: {
|
|
310
|
+
Image(systemName: context.state.deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse")
|
|
311
|
+
.resizable()
|
|
312
|
+
.scaledToFit()
|
|
313
|
+
.frame(width: 20, height: 20)
|
|
314
|
+
.foregroundColor(context.state.deliveryTime >= 0 ? .green : .blue)
|
|
315
|
+
} compactTrailing: {
|
|
316
|
+
Image(systemName: context.state.deliveryTime >= 0 ? "timer.circle.fill" : "checkmark.circle.fill")
|
|
317
|
+
.resizable()
|
|
318
|
+
.scaledToFit()
|
|
319
|
+
.frame(width: 20, height: 20)
|
|
320
|
+
.foregroundColor(context.state.deliveryTime >= 0 ? .green : .blue)
|
|
321
|
+
} minimal: {
|
|
322
|
+
Text(context.state.message).font(.system(size: 12))
|
|
323
|
+
}
|
|
324
|
+
.widgetURL(URL(string: "http://www.apple.com"))
|
|
325
|
+
.keylineTint(Color.red)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private let formatter: NumberFormatter = {
|
|
330
|
+
let formatter = NumberFormatter()
|
|
331
|
+
formatter.maximumFractionDigits = 0
|
|
332
|
+
formatter.minimumFractionDigits = 0
|
|
333
|
+
return formatter
|
|
334
|
+
}()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
struct LockScreenView: View {
|
|
338
|
+
@State private var message = ""
|
|
339
|
+
@State private var deliveryTime: Double = 0
|
|
340
|
+
// for console debugging
|
|
341
|
+
let logger = Logger(subsystem: "${bundleId}.${name}", category: "Widget")
|
|
342
|
+
|
|
343
|
+
var body: some View {
|
|
344
|
+
ZStack {
|
|
345
|
+
LinearGradient(
|
|
346
|
+
gradient: Gradient(colors: [Color.gray.opacity(0.3), Color.black]),
|
|
347
|
+
startPoint: .top,
|
|
348
|
+
endPoint: .bottom
|
|
349
|
+
)
|
|
350
|
+
VStack {
|
|
351
|
+
Spacer()
|
|
352
|
+
Image(systemName: deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse")
|
|
353
|
+
.resizable()
|
|
354
|
+
.scaledToFit()
|
|
355
|
+
.frame(width: 50, height: 50)
|
|
356
|
+
.foregroundColor(deliveryTime >= 0 ? .green : .blue)
|
|
357
|
+
Spacer()
|
|
358
|
+
Text("\\(message)")
|
|
359
|
+
.foregroundStyle(.white)
|
|
360
|
+
Spacer()
|
|
361
|
+
}
|
|
362
|
+
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
init(message: String = "", deliveryTime: Double = 0) {
|
|
366
|
+
_message = State(initialValue: message)
|
|
367
|
+
_deliveryTime = State(initialValue: deliveryTime)
|
|
368
|
+
|
|
369
|
+
// Logs the deliveryTime at init for debugging purposes if needed
|
|
370
|
+
logger.log("deliveryTime: \\(deliveryTime)")
|
|
371
|
+
}
|
|
372
|
+
}${os_1.EOL}`;
|
|
373
|
+
fs.writeFileSync(widgetLiveActivityPath, content);
|
|
374
|
+
}
|
|
375
|
+
if ([1, 2].includes(type)) {
|
|
376
|
+
content = `import SwiftUI
|
|
377
|
+
import WidgetKit
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Widget data shared between the app and the widget extension.
|
|
381
|
+
*/
|
|
382
|
+
struct WidgetData: Codable {
|
|
383
|
+
let pizzas: [String]
|
|
384
|
+
let orderTime: Double
|
|
385
|
+
let delivered: Bool
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
struct Provider: TimelineProvider {
|
|
389
|
+
|
|
390
|
+
func placeholder(in context: Context) -> SimpleEntry {
|
|
391
|
+
SimpleEntry(date: Date(), pizza: "Pepperoni", delivered: false, orderTime: Date())
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
|
395
|
+
let entry = SimpleEntry(date: Date(), pizza: "Pepperoni", delivered: false, orderTime: Date())
|
|
396
|
+
completion(entry)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
func getTimeline(in context: Context, completion: @escaping @Sendable (Timeline<Entry>) -> ()) {
|
|
400
|
+
var entries: [SimpleEntry] = []
|
|
401
|
+
|
|
402
|
+
if let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") {
|
|
403
|
+
let currentDate = Date()
|
|
404
|
+
if let jsonString = sharedDefaults.string(forKey: "widgetData") {
|
|
405
|
+
if let jsonData = jsonString.data(using: .utf8) {
|
|
406
|
+
do {
|
|
407
|
+
let widgetData = try JSONDecoder().decode(WidgetData.self, from: jsonData)
|
|
408
|
+
let pizzas = widgetData.pizzas
|
|
409
|
+
let orderTime = Date(timeIntervalSince1970: widgetData.orderTime/1000)
|
|
410
|
+
let delivered = widgetData.delivered
|
|
411
|
+
|
|
412
|
+
// Generate a timeline of entries 1 second apart, starting from the current date.
|
|
413
|
+
for secondOffset in 0..<pizzas.count {
|
|
414
|
+
let entryDate = Calendar.current.date(
|
|
415
|
+
byAdding: .second, value: secondOffset, to: currentDate)!
|
|
416
|
+
let entry = SimpleEntry(date: entryDate, pizza: secondOffset < pizzas.count ? pizzas[secondOffset] : pizzas[0], delivered: delivered, orderTime: orderTime)
|
|
417
|
+
entries.append(entry)
|
|
418
|
+
}
|
|
419
|
+
} catch {
|
|
420
|
+
print("Failed to decode JSON: (error)")
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
let entry = SimpleEntry(date: currentDate, pizza: "", delivered: false, orderTime: nil)
|
|
425
|
+
entries.append(entry)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let timeline = Timeline(entries: entries, policy: .atEnd)
|
|
430
|
+
completion(timeline)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// func relevances() async -> WidgetRelevances<Void> {
|
|
434
|
+
// // Generate a list containing the contexts this widget is relevant in.
|
|
435
|
+
// }
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
struct SimpleEntry: TimelineEntry {
|
|
439
|
+
let date: Date
|
|
440
|
+
let pizza: String
|
|
441
|
+
let delivered: Bool
|
|
442
|
+
let orderTime: Date?
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
struct WidgetView: View {
|
|
446
|
+
@Environment(\\.widgetFamily) var widgetFamily
|
|
447
|
+
var entry: Provider.Entry
|
|
448
|
+
|
|
449
|
+
var body: some View {
|
|
450
|
+
VStack {
|
|
451
|
+
if (entry.pizza != "") {
|
|
452
|
+
Spacer()
|
|
453
|
+
Image(systemName: entry.delivered ? "face.smiling.inverse" : "car.side")
|
|
454
|
+
.resizable()
|
|
455
|
+
.scaledToFit()
|
|
456
|
+
.frame(width: iconSize(for: widgetFamily), height: iconSize(for: widgetFamily))
|
|
457
|
+
.foregroundColor(entry.delivered ? .blue : .green)
|
|
458
|
+
Spacer()
|
|
459
|
+
if (entry.delivered) {
|
|
460
|
+
Text("Pizza Delivered!")
|
|
461
|
+
.font(.system(size: fontSize(for: widgetFamily), weight: .bold))
|
|
462
|
+
.foregroundStyle(.white)
|
|
463
|
+
} else {
|
|
464
|
+
HStack(spacing: 4) {
|
|
465
|
+
Text("Ordered:")
|
|
466
|
+
.font(.system(size: fontSize(for: widgetFamily)))
|
|
467
|
+
.foregroundStyle(.white)
|
|
468
|
+
Text(entry.orderTime!, style: .time)
|
|
469
|
+
.font(.system(size: fontSize(for: widgetFamily), weight: .bold))
|
|
470
|
+
.foregroundStyle(.white)
|
|
471
|
+
}
|
|
472
|
+
HStack(spacing: 4) {
|
|
473
|
+
Text("Pizza:")
|
|
474
|
+
.font(.system(size: fontSize(for: widgetFamily)))
|
|
475
|
+
.foregroundStyle(.white)
|
|
476
|
+
Text(entry.pizza)
|
|
477
|
+
.font(.system(size: fontSize(for: widgetFamily), weight: .bold))
|
|
478
|
+
.foregroundStyle(.white)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
Spacer()
|
|
482
|
+
} else {
|
|
483
|
+
Spacer()
|
|
484
|
+
Image(systemName: "car.side.rear.open")
|
|
485
|
+
.resizable()
|
|
486
|
+
.scaledToFit()
|
|
487
|
+
.frame(width: iconSize(for: widgetFamily), height: iconSize(for: widgetFamily))
|
|
488
|
+
.foregroundColor(.gray)
|
|
489
|
+
Spacer()
|
|
490
|
+
Text("Awaiting orders...")
|
|
491
|
+
.foregroundStyle(.white)
|
|
492
|
+
Spacer()
|
|
493
|
+
}
|
|
494
|
+
}.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private func iconSize(for family: WidgetFamily) -> CGFloat {
|
|
498
|
+
switch family {
|
|
499
|
+
case .systemSmall:
|
|
500
|
+
return 65
|
|
501
|
+
case .systemMedium:
|
|
502
|
+
return 85
|
|
503
|
+
case .systemLarge:
|
|
504
|
+
return 150
|
|
505
|
+
default:
|
|
506
|
+
return 65
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private func fontSize(for family: WidgetFamily) -> CGFloat {
|
|
511
|
+
switch family {
|
|
512
|
+
case .systemSmall:
|
|
513
|
+
return 12
|
|
514
|
+
case .systemMedium:
|
|
515
|
+
return 14
|
|
516
|
+
case .systemLarge:
|
|
517
|
+
return 18
|
|
518
|
+
default:
|
|
519
|
+
return 14
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@available(iOSApplicationExtension 17.0, *)
|
|
525
|
+
struct ${capitalName}HomeScreenWidget: Widget {
|
|
526
|
+
let kind: String = "widget"
|
|
527
|
+
|
|
528
|
+
var body: some WidgetConfiguration {
|
|
529
|
+
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
|
530
|
+
WidgetView(entry: entry)
|
|
531
|
+
.containerBackground(for: .widget) {
|
|
532
|
+
LinearGradient(
|
|
533
|
+
gradient: Gradient(colors: [Color.black.opacity(0.6), Color.black]),
|
|
534
|
+
startPoint: .top,
|
|
535
|
+
endPoint: .bottom
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
.configurationDisplayName("${capitalName} Widget")
|
|
540
|
+
.description("${capitalName} delivery service.")
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
#Preview(as: .systemSmall) {
|
|
545
|
+
${capitalName}HomeScreenWidget()
|
|
546
|
+
} timeline: {
|
|
547
|
+
SimpleEntry(date: .now, pizza: "Pepperoni", delivered: false, orderTime: Date())
|
|
548
|
+
SimpleEntry(date: .now, pizza: "Hawaiian", delivered: false, orderTime: Date())
|
|
549
|
+
}${os_1.EOL}`;
|
|
550
|
+
fs.writeFileSync(widgetHomeScreenPath, content);
|
|
551
|
+
}
|
|
552
|
+
content = `{
|
|
553
|
+
"${bundleId}.${name}": "{set-your-provision-profile-id}"
|
|
554
|
+
}`;
|
|
555
|
+
fs.writeFileSync(extensionProvisionPath, content);
|
|
556
|
+
content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
557
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
558
|
+
<plist version="1.0">
|
|
559
|
+
<dict>
|
|
560
|
+
<key>com.apple.security.application-groups</key>
|
|
561
|
+
<array>
|
|
562
|
+
<string>group.${bundleId}</string>
|
|
563
|
+
</array>
|
|
564
|
+
</dict>
|
|
565
|
+
</plist>${os_1.EOL}`;
|
|
566
|
+
fs.writeFileSync(entitlementsPath, content);
|
|
567
|
+
if (fs.existsSync(appInfoPlistPath)) {
|
|
568
|
+
const appSupportLiveActivity = "NSSupportsLiveActivities";
|
|
569
|
+
const appInfoPlist = plist.parse(fs.readFileSync(appInfoPlistPath, {
|
|
570
|
+
encoding: "utf-8",
|
|
571
|
+
}));
|
|
572
|
+
if (!appInfoPlist[appSupportLiveActivity]) {
|
|
573
|
+
appInfoPlist[appSupportLiveActivity] = true;
|
|
574
|
+
const appPlist = plist.build(appInfoPlist);
|
|
575
|
+
fs.writeFileSync(appInfoPlistPath, appPlist);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const appGroupKey = "com.apple.security.application-groups";
|
|
579
|
+
if (fs.existsSync(appEntitlementsPath)) {
|
|
580
|
+
const appEntitlementsPlist = plist.parse(fs.readFileSync(appEntitlementsPath, {
|
|
581
|
+
encoding: "utf-8",
|
|
582
|
+
}));
|
|
583
|
+
if (!appEntitlementsPlist[appGroupKey]) {
|
|
584
|
+
appEntitlementsPlist[appGroupKey] = [`group.${bundleId}`];
|
|
585
|
+
const appEntitlements = plist.build(appEntitlementsPlist);
|
|
586
|
+
console.log("appentitlement:", appEntitlements);
|
|
587
|
+
fs.writeFileSync(appEntitlementsPath, appEntitlements);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
592
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
593
|
+
<plist version="1.0">
|
|
594
|
+
<dict>
|
|
595
|
+
<key>com.apple.security.application-groups</key>
|
|
596
|
+
<array>
|
|
597
|
+
<string>group.${bundleId}</string>
|
|
598
|
+
</array>
|
|
599
|
+
</dict>
|
|
600
|
+
</plist>${os_1.EOL}`;
|
|
601
|
+
fs.writeFileSync(appEntitlementsPath, content);
|
|
602
|
+
}
|
|
603
|
+
content = `{
|
|
604
|
+
"frameworks": [
|
|
605
|
+
"SwiftUI.framework",
|
|
606
|
+
"WidgetKit.framework"
|
|
607
|
+
],
|
|
608
|
+
"targetBuildConfigurationProperties": {
|
|
609
|
+
"ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME": "AccentColor",
|
|
610
|
+
"ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME": "WidgetBackground",
|
|
611
|
+
"CLANG_ANALYZER_NONNULL": "YES",
|
|
612
|
+
"CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION": "YES_AGGRESSIVE",
|
|
613
|
+
"CLANG_CXX_LANGUAGE_STANDARD": "\\"gnu++20\\"",
|
|
614
|
+
"CLANG_ENABLE_OBJC_WEAK": "YES",
|
|
615
|
+
"CLANG_WARN_DOCUMENTATION_COMMENTS": "YES",
|
|
616
|
+
"CLANG_WARN_UNGUARDED_AVAILABILITY": "YES_AGGRESSIVE",
|
|
617
|
+
"CURRENT_PROJECT_VERSION": 1,
|
|
618
|
+
"GCC_C_LANGUAGE_STANDARD": "gnu11",
|
|
619
|
+
"GCC_WARN_UNINITIALIZED_AUTOS": "YES_AGGRESSIVE",
|
|
620
|
+
"GENERATE_INFOPLIST_FILE": "YES",
|
|
621
|
+
"INFOPLIST_KEY_CFBundleDisplayName": "widget",
|
|
622
|
+
"INFOPLIST_KEY_NSHumanReadableCopyright": "\\"Copyright © All rights reserved.\\"",
|
|
623
|
+
"IPHONEOS_DEPLOYMENT_TARGET": 18.0,
|
|
624
|
+
"MARKETING_VERSION": "1.0",
|
|
625
|
+
"MTL_FAST_MATH": "YES",
|
|
626
|
+
"PRODUCT_NAME": "widget",
|
|
627
|
+
"SWIFT_EMIT_LOC_STRINGS": "YES",
|
|
628
|
+
"SWIFT_VERSION": "5.0",
|
|
629
|
+
"TARGETED_DEVICE_FAMILY": "\\"1,2\\"",
|
|
630
|
+
"MTL_ENABLE_DEBUG_INFO": "NO",
|
|
631
|
+
"SWIFT_OPTIMIZATION_LEVEL": "\\"-O\\"",
|
|
632
|
+
"COPY_PHASE_STRIP": "NO",
|
|
633
|
+
"SWIFT_COMPILATION_MODE": "wholemodule",
|
|
634
|
+
"CODE_SIGN_ENTITLEMENTS": "../../App_Resources/iOS/extensions/${name}/${name}.entitlements"
|
|
635
|
+
},
|
|
636
|
+
"targetNamedBuildConfigurationProperties": {
|
|
637
|
+
"debug": {
|
|
638
|
+
"DEBUG_INFORMATION_FORMAT": "dwarf",
|
|
639
|
+
"GCC_PREPROCESSOR_DEFINITIONS": "(\\"DEBUG=1\\",\\"$(inherited)\\",)",
|
|
640
|
+
"MTL_ENABLE_DEBUG_INFO": "INCLUDE_SOURCE",
|
|
641
|
+
"SWIFT_ACTIVE_COMPILATION_CONDITIONS": "DEBUG",
|
|
642
|
+
"SWIFT_OPTIMIZATION_LEVEL": "\\"-Onone\\""
|
|
643
|
+
},
|
|
644
|
+
"release": {
|
|
645
|
+
"CODE_SIGN_STYLE": "Manual",
|
|
646
|
+
"MTL_ENABLE_DEBUG_INFO": "NO",
|
|
647
|
+
"SWIFT_OPTIMIZATION_LEVEL": "\\"-O\\"",
|
|
648
|
+
"COPY_PHASE_STRIP": "NO",
|
|
649
|
+
"SWIFT_COMPILATION_MODE": "wholemodule"
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}${os_1.EOL}`;
|
|
653
|
+
fs.writeFileSync(extensionsConfigPath, content);
|
|
654
|
+
console.log(`🚀 Your widget is now ready to develop: App_Resources/iOS/extensions/${name}.\n`);
|
|
655
|
+
console.log(`Followup steps:\n
|
|
656
|
+
- Check App_Resources/iOS/build.xcconfig uses IPHONEOS_DEPLOYMENT_TARGET=17 or higher.
|
|
657
|
+
- Update App_Resources/iOS/extensions/provisioning.json with your profile id.
|
|
658
|
+
- Customize App_Resources/iOS/extensions/${name}/${(0, utils_1.capitalizeFirstLetter)(name)}LiveActivity.swift for your display.
|
|
659
|
+
- Customize Shared_Resources/iOS/SharedWidget/Sources/SharedWidget/${(0, utils_1.capitalizeFirstLetter)(name)}Model.swift for your data.
|
|
660
|
+
`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
generateAppleUtility(projectDir, name, bundleId) {
|
|
664
|
+
const capitalName = (0, utils_1.capitalizeFirstLetter)(name);
|
|
665
|
+
const appResourcePath = this.$projectData.appResourcesDirectoryPath;
|
|
666
|
+
const appResourceSrcPath = path.join(appResourcePath, "iOS", "src");
|
|
667
|
+
const appleUtilityPath = path.join(appResourceSrcPath, `AppleWidgetUtils.swift`);
|
|
668
|
+
const referenceTypesPath = path.join(projectDir, "references.d.ts");
|
|
669
|
+
if (!fs.existsSync(appleUtilityPath)) {
|
|
670
|
+
fs.mkdirSync(appResourceSrcPath, { recursive: true });
|
|
671
|
+
}
|
|
672
|
+
if (!fs.existsSync(appleUtilityPath)) {
|
|
673
|
+
}
|
|
674
|
+
let content = `import Foundation
|
|
675
|
+
import UIKit
|
|
676
|
+
import ActivityKit
|
|
677
|
+
import WidgetKit
|
|
678
|
+
import SharedWidget
|
|
679
|
+
|
|
680
|
+
@objcMembers
|
|
681
|
+
public class AppleWidgetUtils: NSObject {
|
|
682
|
+
|
|
683
|
+
// Live Activity Handling
|
|
684
|
+
public static func startActivity(_ data: NSDictionary) {
|
|
685
|
+
if ActivityAuthorizationInfo().areActivitiesEnabled {
|
|
686
|
+
let numberOfPizzas = data.object(forKey: "numberOfPizzas") as! Int
|
|
687
|
+
let totalAmount = data.object(forKey: "totalAmount") as! String
|
|
688
|
+
let attrs = ${capitalName}Model(numberOfPizzas: numberOfPizzas, totalAmount: totalAmount)
|
|
689
|
+
|
|
690
|
+
let message = data.object(forKey: "message") as! String
|
|
691
|
+
let deliveryTime = data.object(forKey: "deliveryTime") as! Double
|
|
692
|
+
let initialStatus = ${capitalName}Model.DeliveryStatus(
|
|
693
|
+
message: message, deliveryTime: deliveryTime)
|
|
694
|
+
let content = ActivityContent(state: initialStatus, staleDate: nil)
|
|
695
|
+
|
|
696
|
+
do {
|
|
697
|
+
let activity = try Activity<${capitalName}Model>.request(
|
|
698
|
+
attributes: attrs,
|
|
699
|
+
content: content,
|
|
700
|
+
pushType: nil)
|
|
701
|
+
print("Requested a Live Activity \\(activity.id)")
|
|
702
|
+
} catch (let error) {
|
|
703
|
+
print("Error requesting Live Activity \\(error.localizedDescription)")
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
public static func updateActivity(_ data: NSDictionary) {
|
|
708
|
+
if ActivityAuthorizationInfo().areActivitiesEnabled {
|
|
709
|
+
Task {
|
|
710
|
+
let message = data.object(forKey: "message") as! String
|
|
711
|
+
let deliveryTime = data.object(forKey: "deliveryTime") as! Double
|
|
712
|
+
let status = ${capitalName}Model.DeliveryStatus(
|
|
713
|
+
message: message, deliveryTime: deliveryTime)
|
|
714
|
+
let content = ActivityContent(state: status, staleDate: nil)
|
|
715
|
+
|
|
716
|
+
for activity in Activity<${capitalName}Model>.activities {
|
|
717
|
+
await activity.update(content)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
public static func cancelActivity(_ data: NSDictionary) {
|
|
723
|
+
if ActivityAuthorizationInfo().areActivitiesEnabled {
|
|
724
|
+
Task {
|
|
725
|
+
let message = data.object(forKey: "message") as! String
|
|
726
|
+
let status = ${capitalName}Model.DeliveryStatus(
|
|
727
|
+
message: message, deliveryTime: 0)
|
|
728
|
+
let content = ActivityContent(state: status, staleDate: nil)
|
|
729
|
+
|
|
730
|
+
for activity in Activity<${capitalName}Model>.activities {
|
|
731
|
+
await activity.end(content, dismissalPolicy: .immediate)
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
public static func getData(key: String) -> String? {
|
|
737
|
+
guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else {
|
|
738
|
+
return nil
|
|
739
|
+
}
|
|
740
|
+
return sharedDefaults.object(forKey: key) as? String
|
|
741
|
+
}
|
|
742
|
+
public static func updateData(key: String, _ data: String) {
|
|
743
|
+
guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else {
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
sharedDefaults.set(data, forKey: key)
|
|
747
|
+
sharedDefaults.synchronize()
|
|
748
|
+
}
|
|
749
|
+
public static func removeData(key: String) {
|
|
750
|
+
guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else {
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
sharedDefaults.removeObject(forKey: key)
|
|
754
|
+
sharedDefaults.synchronize()
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Home Screen Widget Handling
|
|
758
|
+
public static func updateWidget() {
|
|
759
|
+
if #available(iOS 14.0, *) {
|
|
760
|
+
Task.detached(priority: .userInitiated) {
|
|
761
|
+
WidgetCenter.shared.reloadAllTimelines()
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}${os_1.EOL}`;
|
|
766
|
+
fs.writeFileSync(appleUtilityPath, content);
|
|
767
|
+
content = `/**
|
|
768
|
+
* Customize for your own Apple Widget Data
|
|
769
|
+
*/
|
|
770
|
+
declare interface AppleWidgetModelData {
|
|
771
|
+
numberOfPizzas: number;
|
|
772
|
+
totalAmount: string;
|
|
773
|
+
driverName: string;
|
|
774
|
+
deliveryTime: number;
|
|
775
|
+
}
|
|
776
|
+
declare class AppleWidgetUtils extends NSObject {
|
|
777
|
+
static startActivity(data: AppleWidgetModelData): void;
|
|
778
|
+
static updateActivity(
|
|
779
|
+
data: Pick<AppleWidgetModelData, "message" | "deliveryTime">
|
|
780
|
+
): void;
|
|
781
|
+
static cancelActivity(data: Pick<AppleWidgetModelData, "message">): void;
|
|
782
|
+
static updateWidget(): void;
|
|
783
|
+
static updateDataWithKey(key: string, data: string): void;
|
|
784
|
+
static getDataWithKey(key: string): string;
|
|
785
|
+
static removeDataWithKey(key: string): void;
|
|
786
|
+
}${os_1.EOL}`;
|
|
787
|
+
if (!fs.existsSync(referenceTypesPath)) {
|
|
788
|
+
const references = `/// <reference path="./node_modules/@nativescript/types-android/index.d.ts" />
|
|
789
|
+
/// <reference path="./node_modules/@nativescript/types-ios/complete.d.ts" />${os_1.EOL}${content}`;
|
|
790
|
+
fs.writeFileSync(referenceTypesPath, references);
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
const references = fs.readFileSync(referenceTypesPath, {
|
|
794
|
+
encoding: "utf-8",
|
|
795
|
+
});
|
|
796
|
+
if ((references === null || references === void 0 ? void 0 : references.indexOf("AppleWidgetUtils")) === -1) {
|
|
797
|
+
content = `${references.toString()}${os_1.EOL}${content}`;
|
|
798
|
+
fs.writeFileSync(referenceTypesPath, content);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
exports.WidgetIOSCommand = WidgetIOSCommand;
|
|
804
|
+
yok_1.injector.registerCommand(["widget"], WidgetCommand);
|
|
805
|
+
yok_1.injector.registerCommand(["widget|ios"], WidgetIOSCommand);
|
package/lib/constants.js
CHANGED
|
@@ -68,7 +68,7 @@ exports.APK_EXTENSION_NAME = ".apk";
|
|
|
68
68
|
exports.AAB_EXTENSION_NAME = ".aab";
|
|
69
69
|
exports.APKS_EXTENSION_NAME = ".apks";
|
|
70
70
|
exports.HASHES_FILE_NAME = ".nshashes";
|
|
71
|
-
exports.TNS_NATIVE_SOURCE_GROUP_NAME = "
|
|
71
|
+
exports.TNS_NATIVE_SOURCE_GROUP_NAME = "AppResourcesSrc";
|
|
72
72
|
exports.NATIVE_SOURCE_FOLDER = "src";
|
|
73
73
|
exports.APPLICATION_RESPONSE_TIMEOUT_SECONDS = 60;
|
|
74
74
|
exports.NATIVE_EXTENSION_FOLDER = "extensions";
|
|
@@ -101,8 +101,34 @@ interface INsConfigPlaform {
|
|
|
101
101
|
id?: string;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
interface IOSSPMPackageBase {
|
|
105
|
+
name: string;
|
|
106
|
+
libs: string[];
|
|
107
|
+
/**
|
|
108
|
+
* Optional: If you have more targets (like widgets for example)
|
|
109
|
+
* you can list their names here to include the Swift Package with them
|
|
110
|
+
*/
|
|
111
|
+
targets?: string[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface IOSRemoteSPMPackage extends IOSSPMPackageBase {
|
|
115
|
+
repositoryURL: string;
|
|
116
|
+
version: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface IOSLocalSPMPackage extends IOSSPMPackageBase {
|
|
120
|
+
path: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type IOSSPMPackage = IOSRemoteSPMPackage | IOSLocalSPMPackage;
|
|
124
|
+
|
|
104
125
|
interface INsConfigIOS extends INsConfigPlaform {
|
|
105
126
|
discardUncaughtJsExceptions?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Swift Package Manager
|
|
129
|
+
* List packages to be included in the iOS build.
|
|
130
|
+
*/
|
|
131
|
+
SPMPackages?: Array<IOSSPMPackage>;
|
|
106
132
|
}
|
|
107
133
|
|
|
108
134
|
interface INSConfigVisionOS extends INsConfigIOS {}
|
|
@@ -206,7 +232,7 @@ interface IProjectData extends ICreateProjectData {
|
|
|
206
232
|
initializeProjectData(projectDir?: string): void;
|
|
207
233
|
initializeProjectDataFromContent(
|
|
208
234
|
packageJsonContent: string,
|
|
209
|
-
projectDir?: string
|
|
235
|
+
projectDir?: string,
|
|
210
236
|
): void;
|
|
211
237
|
getAppDirectoryPath(projectDir?: string): string;
|
|
212
238
|
getAppDirectoryRelativePath(): string;
|
|
@@ -296,7 +322,7 @@ interface IProjectDataService {
|
|
|
296
322
|
*/
|
|
297
323
|
getRuntimePackage(
|
|
298
324
|
projectDir: string,
|
|
299
|
-
platform: SupportedPlatform
|
|
325
|
+
platform: SupportedPlatform,
|
|
300
326
|
): IBasePluginData;
|
|
301
327
|
|
|
302
328
|
/**
|
|
@@ -316,7 +342,7 @@ interface IProjectCleanupService {
|
|
|
316
342
|
*/
|
|
317
343
|
clean(
|
|
318
344
|
pathsToClean: string[],
|
|
319
|
-
options?: IProjectCleanupOptions
|
|
345
|
+
options?: IProjectCleanupOptions,
|
|
320
346
|
): Promise<IProjectCleanupResult>;
|
|
321
347
|
|
|
322
348
|
/**
|
|
@@ -325,7 +351,7 @@ interface IProjectCleanupService {
|
|
|
325
351
|
*/
|
|
326
352
|
cleanPath(
|
|
327
353
|
pathToClean: string,
|
|
328
|
-
options?: IProjectCleanupOptions
|
|
354
|
+
options?: IProjectCleanupOptions,
|
|
329
355
|
): Promise<IProjectCleanupResult>;
|
|
330
356
|
}
|
|
331
357
|
|
|
@@ -427,7 +453,7 @@ interface IProjectConfigService {
|
|
|
427
453
|
|
|
428
454
|
writeLegacyNSConfigIfNeeded(
|
|
429
455
|
projectDir: string,
|
|
430
|
-
runtimePackage: IBasePluginData
|
|
456
|
+
runtimePackage: IBasePluginData,
|
|
431
457
|
): Promise<void>;
|
|
432
458
|
}
|
|
433
459
|
|
|
@@ -530,14 +556,14 @@ interface IProjectTemplatesService {
|
|
|
530
556
|
*/
|
|
531
557
|
prepareTemplate(
|
|
532
558
|
templateName: string,
|
|
533
|
-
projectDir: string
|
|
559
|
+
projectDir: string,
|
|
534
560
|
): Promise<ITemplateData>;
|
|
535
561
|
}
|
|
536
562
|
|
|
537
563
|
interface IPlatformProjectServiceBase {
|
|
538
564
|
getPluginPlatformsFolderPath(
|
|
539
565
|
pluginData: IPluginData,
|
|
540
|
-
platform: string
|
|
566
|
+
platform: string,
|
|
541
567
|
): string;
|
|
542
568
|
getFrameworkVersion(projectData: IProjectData): string;
|
|
543
569
|
}
|
|
@@ -599,7 +625,7 @@ interface ILocalBuildService {
|
|
|
599
625
|
*/
|
|
600
626
|
build(
|
|
601
627
|
platform: string,
|
|
602
|
-
platformBuildOptions: IPlatformBuildData
|
|
628
|
+
platformBuildOptions: IPlatformBuildData,
|
|
603
629
|
): Promise<string>;
|
|
604
630
|
/**
|
|
605
631
|
* Removes build artifacts specific to the platform
|
|
@@ -619,7 +645,7 @@ interface ITestExecutionService {
|
|
|
619
645
|
startKarmaServer(
|
|
620
646
|
platform: string,
|
|
621
647
|
liveSyncInfo: ILiveSyncInfo,
|
|
622
|
-
deviceDescriptors: ILiveSyncDeviceDescriptor[]
|
|
648
|
+
deviceDescriptors: ILiveSyncDeviceDescriptor[],
|
|
623
649
|
): Promise<void>;
|
|
624
650
|
canStartKarmaServer(projectData: IProjectData): Promise<boolean>;
|
|
625
651
|
}
|
|
@@ -653,17 +679,17 @@ interface ICocoaPodsService {
|
|
|
653
679
|
*/
|
|
654
680
|
applyPodfileFromAppResources(
|
|
655
681
|
projectData: IProjectData,
|
|
656
|
-
platformData: IPlatformData
|
|
682
|
+
platformData: IPlatformData,
|
|
657
683
|
): Promise<void>;
|
|
658
684
|
|
|
659
685
|
applyPodfileArchExclusions(
|
|
660
686
|
projectData: IProjectData,
|
|
661
|
-
platformData: IPlatformData
|
|
687
|
+
platformData: IPlatformData,
|
|
662
688
|
): Promise<void>;
|
|
663
689
|
|
|
664
690
|
applyPodfileFromExtensions(
|
|
665
691
|
projectData: IProjectData,
|
|
666
|
-
platformData: IPlatformData
|
|
692
|
+
platformData: IPlatformData,
|
|
667
693
|
): Promise<void>;
|
|
668
694
|
|
|
669
695
|
/**
|
|
@@ -678,7 +704,7 @@ interface ICocoaPodsService {
|
|
|
678
704
|
moduleName: string,
|
|
679
705
|
podfilePath: string,
|
|
680
706
|
projectData: IProjectData,
|
|
681
|
-
platformData: IPlatformData
|
|
707
|
+
platformData: IPlatformData,
|
|
682
708
|
): Promise<void>;
|
|
683
709
|
|
|
684
710
|
/**
|
|
@@ -700,7 +726,7 @@ interface ICocoaPodsService {
|
|
|
700
726
|
moduleName: string,
|
|
701
727
|
podfilePath: string,
|
|
702
728
|
projectData: IProjectData,
|
|
703
|
-
nativeProjectPath: string
|
|
729
|
+
nativeProjectPath: string,
|
|
704
730
|
): void;
|
|
705
731
|
|
|
706
732
|
/**
|
|
@@ -718,7 +744,7 @@ interface ICocoaPodsService {
|
|
|
718
744
|
*/
|
|
719
745
|
executePodInstall(
|
|
720
746
|
projectRoot: string,
|
|
721
|
-
xcodeProjPath: string
|
|
747
|
+
xcodeProjPath: string,
|
|
722
748
|
): Promise<ISpawnResult>;
|
|
723
749
|
|
|
724
750
|
/**
|
|
@@ -730,7 +756,7 @@ interface ICocoaPodsService {
|
|
|
730
756
|
mergePodXcconfigFile(
|
|
731
757
|
projectData: IProjectData,
|
|
732
758
|
platformData: IPlatformData,
|
|
733
|
-
opts: IRelease
|
|
759
|
+
opts: IRelease,
|
|
734
760
|
): Promise<void>;
|
|
735
761
|
}
|
|
736
762
|
|
|
@@ -738,16 +764,16 @@ interface ICocoaPodsPlatformManager {
|
|
|
738
764
|
addPlatformSection(
|
|
739
765
|
projectData: IProjectData,
|
|
740
766
|
podfilePlatformData: IPodfilePlatformData,
|
|
741
|
-
projectPodfileContent: string
|
|
767
|
+
projectPodfileContent: string,
|
|
742
768
|
): string;
|
|
743
769
|
removePlatformSection(
|
|
744
770
|
moduleName: string,
|
|
745
771
|
projectPodFileContent: string,
|
|
746
|
-
podfilePath: string
|
|
772
|
+
podfilePath: string,
|
|
747
773
|
): string;
|
|
748
774
|
replacePlatformRow(
|
|
749
775
|
podfileContent: string,
|
|
750
|
-
podfilePath: string
|
|
776
|
+
podfilePath: string,
|
|
751
777
|
): { replacedContent: string; podfilePlatformData: IPodfilePlatformData };
|
|
752
778
|
}
|
|
753
779
|
|
|
@@ -772,24 +798,24 @@ interface IIOSNativeTargetService {
|
|
|
772
798
|
targetType: string,
|
|
773
799
|
project: IXcode.project,
|
|
774
800
|
platformData: IPlatformData,
|
|
775
|
-
parentTarget?: string
|
|
801
|
+
parentTarget?: string,
|
|
776
802
|
): IXcode.target;
|
|
777
803
|
prepareSigning(
|
|
778
804
|
targetUuids: string[],
|
|
779
805
|
projectData: IProjectData,
|
|
780
|
-
projectPath: string
|
|
806
|
+
projectPath: string,
|
|
781
807
|
): void;
|
|
782
808
|
getTargetDirectories(folderPath: string): string[];
|
|
783
809
|
setXcodeTargetBuildConfigurationProperties(
|
|
784
810
|
properties: IXcodeTargetBuildConfigurationProperty[],
|
|
785
811
|
targetName: string,
|
|
786
|
-
project: IXcode.project
|
|
812
|
+
project: IXcode.project,
|
|
787
813
|
): void;
|
|
788
814
|
setConfigurationsFromJsonFile(
|
|
789
815
|
jsonPath: string,
|
|
790
816
|
targetUuid: string,
|
|
791
817
|
targetName: string,
|
|
792
|
-
project: IXcode.project
|
|
818
|
+
project: IXcode.project,
|
|
793
819
|
): void;
|
|
794
820
|
}
|
|
795
821
|
|
|
@@ -798,7 +824,7 @@ interface IIOSNativeTargetService {
|
|
|
798
824
|
*/
|
|
799
825
|
interface IIOSExtensionsService {
|
|
800
826
|
addExtensionsFromPath(
|
|
801
|
-
options: IAddExtensionsFromPathOptions
|
|
827
|
+
options: IAddExtensionsFromPathOptions,
|
|
802
828
|
): Promise<boolean>;
|
|
803
829
|
removeExtensions(options: IRemoveExtensionsOptions): void;
|
|
804
830
|
}
|
|
@@ -18,6 +18,7 @@ const projectServiceBaseLib = require("./platform-project-service-base");
|
|
|
18
18
|
const plist_merge_patch_1 = require("plist-merge-patch");
|
|
19
19
|
const os_1 = require("os");
|
|
20
20
|
const plist = require("plist");
|
|
21
|
+
const fastGlob = require("fast-glob");
|
|
21
22
|
const constants_2 = require("../constants");
|
|
22
23
|
const helpers_2 = require("../common/helpers");
|
|
23
24
|
const yok_1 = require("../common/yok");
|
|
@@ -363,6 +364,12 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
|
|
|
363
364
|
resourcesNativeCodePath = path.join(resourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.NATIVE_SOURCE_FOLDER);
|
|
364
365
|
}
|
|
365
366
|
await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData);
|
|
367
|
+
const nativeSource = this.$projectConfigService.getValue(`${this._platformData.platformNameLowerCase}.NativeSource`, []);
|
|
368
|
+
if (nativeSource === null || nativeSource === void 0 ? void 0 : nativeSource.length) {
|
|
369
|
+
for (const source of nativeSource) {
|
|
370
|
+
await this.prepareNativeSourceCode(source.name, source.path, projectData);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
366
373
|
}
|
|
367
374
|
this.$iOSWatchAppService.removeWatchApp({ pbxProjPath });
|
|
368
375
|
const addedWatchApp = await this.$iOSWatchAppService.addWatchAppFromPath({
|
|
@@ -637,7 +644,7 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
|
|
|
637
644
|
}
|
|
638
645
|
async prepareNativeSourceCode(groupName, sourceFolderPath, projectData) {
|
|
639
646
|
const project = this.createPbxProj(projectData);
|
|
640
|
-
const group = this.getRootGroup(groupName, sourceFolderPath);
|
|
647
|
+
const group = await this.getRootGroup(groupName, sourceFolderPath);
|
|
641
648
|
project.addPbxGroup(group.files, group.name, group.path, null, {
|
|
642
649
|
isMain: true,
|
|
643
650
|
filesRelativeToProject: true,
|
|
@@ -680,20 +687,30 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
|
|
|
680
687
|
this.$logger.warn("Let us know if there are other Extension features you'd like! https://github.com/NativeScript/NativeScript/issues");
|
|
681
688
|
}
|
|
682
689
|
}
|
|
683
|
-
getRootGroup(name, rootPath) {
|
|
690
|
+
async getRootGroup(name, rootPath) {
|
|
684
691
|
const filePathsArr = [];
|
|
685
692
|
const rootGroup = {
|
|
686
693
|
name: name,
|
|
687
694
|
files: filePathsArr,
|
|
688
695
|
path: rootPath,
|
|
689
696
|
};
|
|
690
|
-
if (
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
+
if (fastGlob.isDynamicPattern(rootPath)) {
|
|
698
|
+
const projectRoot = this.$projectDataService.getProjectData().projectDir;
|
|
699
|
+
const filePaths = await fastGlob(rootPath);
|
|
700
|
+
for (const filePath of filePaths) {
|
|
701
|
+
const sourceFilePath = path.normalize(path.join(projectRoot, filePath));
|
|
702
|
+
filePathsArr.push(sourceFilePath);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
if (this.$fs.exists(rootPath)) {
|
|
707
|
+
const stats = this.$fs.getFsStats(rootPath);
|
|
708
|
+
if (stats.isDirectory() && !this.$fs.isEmptyDir(rootPath)) {
|
|
709
|
+
this.$fs.readDirectory(rootPath).forEach((fileName) => {
|
|
710
|
+
const filePath = path.join(rootGroup.path, fileName);
|
|
711
|
+
filePathsArr.push(filePath);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
697
714
|
}
|
|
698
715
|
}
|
|
699
716
|
return rootGroup;
|
|
@@ -719,9 +736,9 @@ class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase
|
|
|
719
736
|
await this.addStaticLibrary(path.join(pluginPlatformsFolderPath, fileName), projectData);
|
|
720
737
|
}
|
|
721
738
|
}
|
|
722
|
-
removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData) {
|
|
739
|
+
async removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData) {
|
|
723
740
|
const project = this.createPbxProj(projectData);
|
|
724
|
-
const group = this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);
|
|
741
|
+
const group = await this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);
|
|
725
742
|
project.removePbxGroup(group.name, group.path);
|
|
726
743
|
project.removeFromHeaderSearchPaths(group.path);
|
|
727
744
|
this.savePbxProj(project, projectData);
|
|
@@ -167,7 +167,7 @@ export default {
|
|
|
167
167
|
if (!this.$fs.exists(configFilePath)) {
|
|
168
168
|
this.writeDefaultConfig(this.projectHelper.projectDir);
|
|
169
169
|
}
|
|
170
|
-
if (typeof value === "object") {
|
|
170
|
+
if (!Array.isArray(value) && typeof value === "object") {
|
|
171
171
|
let allSuccessful = true;
|
|
172
172
|
for (const prop of this.flattenObjectToPaths(value)) {
|
|
173
173
|
if (!(await this.setValue(prop.key, prop.value))) {
|
|
@@ -195,7 +195,7 @@ export default {
|
|
|
195
195
|
this.$logger.error(`Failed to update config.` + error);
|
|
196
196
|
}
|
|
197
197
|
finally {
|
|
198
|
-
if (this.getValue(key) !== value) {
|
|
198
|
+
if (!Array.isArray(this.getValue(key)) && this.getValue(key) !== value) {
|
|
199
199
|
this.$logger.error(`${os_1.EOL}Failed to update ${hasTSConfig ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}.${os_1.EOL}`);
|
|
200
200
|
this.$logger.printMarkdown(`Please manually update \`${hasTSConfig ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}\` and set \`${key}\` to \`${value}\`.${os_1.EOL}`);
|
|
201
201
|
this.$fs.writeFile(configFilePath, configContent);
|
|
@@ -297,7 +297,16 @@ You may add \`nsconfig.json\` to \`.gitignore\` as the CLI will regenerate it as
|
|
|
297
297
|
flattenObjectToPaths(obj, basePath) {
|
|
298
298
|
const toPath = (key) => [basePath, key].filter(Boolean).join(".");
|
|
299
299
|
return Object.keys(obj).reduce((all, key) => {
|
|
300
|
-
if (
|
|
300
|
+
if (Array.isArray(obj[key])) {
|
|
301
|
+
return [
|
|
302
|
+
...all,
|
|
303
|
+
{
|
|
304
|
+
key: toPath(key),
|
|
305
|
+
value: obj[key],
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
else if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
301
310
|
return [...all, ...this.flattenObjectToPaths(obj[key], toPath(key))];
|
|
302
311
|
}
|
|
303
312
|
return [
|
|
@@ -102,6 +102,15 @@ class ConfigTransformer {
|
|
|
102
102
|
else if (typeof value === "number" || typeof value === "boolean") {
|
|
103
103
|
return `${value}`;
|
|
104
104
|
}
|
|
105
|
+
else if (Array.isArray(value)) {
|
|
106
|
+
return `[${value.map((v) => this.createInitializer(v)).join(", ")}]`;
|
|
107
|
+
}
|
|
108
|
+
else if (typeof value === "object" && value !== null) {
|
|
109
|
+
const properties = Object.entries(value)
|
|
110
|
+
.map(([key, val]) => `${key}: ${this.createInitializer(val)}`)
|
|
111
|
+
.join(", ");
|
|
112
|
+
return `{ ${properties} }`;
|
|
113
|
+
}
|
|
105
114
|
return `{}`;
|
|
106
115
|
}
|
|
107
116
|
setInitializerValue(initializer, newValue) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nativescript",
|
|
3
3
|
"main": "./lib/nativescript-cli-lib.js",
|
|
4
|
-
"version": "8.9.0-dev.
|
|
4
|
+
"version": "8.9.0-dev.3",
|
|
5
5
|
"author": "NativeScript <oss@nativescript.org>",
|
|
6
6
|
"description": "Command-line interface for building NativeScript projects",
|
|
7
7
|
"bin": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"minimatch": "10.0.1",
|
|
85
85
|
"mkdirp": "3.0.1",
|
|
86
86
|
"mute-stream": "2.0.0",
|
|
87
|
-
"nativescript-dev-xcode": "0.8.
|
|
87
|
+
"nativescript-dev-xcode": "0.8.1",
|
|
88
88
|
"open": "10.1.0",
|
|
89
89
|
"ora": "8.1.1",
|
|
90
90
|
"pacote": "21.0.0",
|