nativescript
Version:
Command-line interface for building NativeScript projects
818 lines (780 loc) • 32.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WidgetIOSCommand = exports.WidgetCommand = void 0;
const fs = require("fs");
const prompts = require("prompts");
const path = require("path");
const plist = require("plist");
const yok_1 = require("../common/yok");
const utils_1 = require("../common/utils");
const os_1 = require("os");
class WidgetCommand {
constructor($projectData, $projectConfigService, $logger, $errors) {
this.$projectData = $projectData;
this.$projectConfigService = $projectConfigService;
this.$logger = $logger;
this.$errors = $errors;
this.allowedParameters = [];
this.$projectData.initializeProjectData();
}
async execute(args) {
this.failWithUsage();
return Promise.resolve();
}
failWithUsage() {
this.$errors.failWithHelp("Usage: ns widget ios");
}
async canExecute(args) {
this.failWithUsage();
return false;
}
getIosSourcePathBase() {
const resources = this.$projectData.getAppResourcesDirectoryPath();
return path.join(resources, "iOS", "src");
}
}
exports.WidgetCommand = WidgetCommand;
class WidgetIOSCommand extends WidgetCommand {
constructor($projectData, $projectConfigService, $logger, $errors) {
super($projectData, $projectConfigService, $logger, $errors);
}
async canExecute(args) {
return true;
}
async execute(args) {
this.startPrompt(args);
}
async startPrompt(args) {
let result = await prompts.prompt({
type: "text",
name: "name",
message: `What name would you like for this widget? (Default is 'widget')`,
});
const name = (result.name || "widget").toLowerCase();
result = await prompts.prompt({
type: "select",
name: "value",
message: `What type of widget would you like?`,
choices: [
{
title: "Live Activity",
description: "This will create a Live Activity that will display on the iOS Lock Screen.",
value: 0,
},
{
title: "Live Activity with Home Screen Widget",
description: "This will create a Live Activity that will display on the iOS Lock Screen with ability to also display a Home Screen Widget.",
value: 1,
},
{
title: "Home Screen Widget",
description: "This will create just a Home Screen Widget.",
value: 2,
},
],
initial: 1,
});
const bundleId = this.$projectConfigService.getValue(`id`, "");
if ([0, 1].includes(result.value)) {
// shared model only needed with live activities
await this.generateSharedWidgetPackage(this.$projectData.projectDir, name);
}
this.generateWidget(this.$projectData.projectDir, name, bundleId, result.value);
this.generateAppleUtility(this.$projectData.projectDir, name, bundleId, result.value);
}
async generateSharedWidgetPackage(projectDir, name) {
var _a;
const sharedWidgetDir = "Shared_Resources/iOS/SharedWidget";
const sharedWidgetPath = path.join(projectDir, sharedWidgetDir);
const sharedWidgetSourceDir = "Sources/SharedWidget";
const sharedWidgetPackagePath = path.join(projectDir, `${sharedWidgetDir}/Package.swift`);
const sharedWidgetSourcePath = path.join(sharedWidgetPath, `${sharedWidgetSourceDir}/${(0, utils_1.capitalizeFirstLetter)(name)}Model.swift`);
const gitIgnorePath = path.join(projectDir, ".gitignore");
if (!fs.existsSync(sharedWidgetPackagePath)) {
fs.mkdirSync(sharedWidgetPath, { recursive: true });
fs.mkdirSync(path.join(sharedWidgetPath, sharedWidgetSourceDir), {
recursive: true,
});
let content = `// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "SharedWidget",
platforms: [
.iOS(.v13)
],
products: [
.library(
name: "SharedWidget",
targets: ["SharedWidget"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
],
targets: [
.target(
name: "SharedWidget",
dependencies: []
)
]
)${os_1.EOL}`;
fs.writeFileSync(sharedWidgetPackagePath, content);
content = `import ActivityKit
import WidgetKit
public struct ${(0, utils_1.capitalizeFirstLetter)(name)}Model: ActivityAttributes {
public typealias DeliveryStatus = ContentState
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
public var message: String
public var deliveryTime: Double
public init(message: String, deliveryTime: Double) {
self.message = message
self.deliveryTime = deliveryTime
}
}
// Fixed non-changing properties about your activity go here!
public var numberOfPizzas: Int
public var totalAmount: String
public init(numberOfPizzas: Int, totalAmount: String) {
self.numberOfPizzas = numberOfPizzas
self.totalAmount = totalAmount
}
}${os_1.EOL}`;
fs.writeFileSync(sharedWidgetSourcePath, content);
// update spm package
const configData = this.$projectConfigService.readConfig(projectDir);
if (!configData.ios) {
configData.ios = {};
}
if (!configData.ios.SPMPackages) {
configData.ios.SPMPackages = [];
}
const spmPackages = configData.ios.SPMPackages;
const sharedWidgetPackage = spmPackages === null || spmPackages === void 0 ? void 0 : spmPackages.find((p) => p.name === "SharedWidget");
if (!sharedWidgetPackage) {
spmPackages.push({
name: "SharedWidget",
libs: ["SharedWidget"],
path: "./Shared_Resources/iOS/SharedWidget",
// @ts-ignore
targets: [name],
});
}
else {
// add target if needed
if (!((_a = sharedWidgetPackage.targets) === null || _a === void 0 ? void 0 : _a.includes(name))) {
sharedWidgetPackage.targets.push(name);
}
}
configData.ios.SPMPackages = spmPackages;
await this.$projectConfigService.setValue("", // root
configData);
if (fs.existsSync(gitIgnorePath)) {
const gitIgnore = fs.readFileSync(gitIgnorePath, {
encoding: "utf-8",
});
const swiftBuildIgnore = `# Swift
.build
.swiftpm`;
if (gitIgnore.indexOf(swiftBuildIgnore) === -1) {
content = `${gitIgnore}${os_1.EOL}${swiftBuildIgnore}${os_1.EOL}`;
fs.writeFileSync(gitIgnorePath, content);
}
}
console.log(`\nCreated Shared Resources: ${sharedWidgetDir}.\n`);
}
}
generateWidget(projectDir, name, bundleId, type) {
const appResourcePath = this.$projectData.appResourcesDirectoryPath;
const capitalName = (0, utils_1.capitalizeFirstLetter)(name);
const appInfoPlistPath = path.join(appResourcePath, "iOS", "Info.plist");
const extensionDir = path.join(appResourcePath, "iOS", "extensions");
const widgetPath = path.join(extensionDir, name);
const extensionProvisionPath = path.join(extensionDir, `provisioning.json`);
const extensionsInfoPath = path.join(widgetPath, `Info.plist`);
const extensionsPrivacyPath = path.join(widgetPath, `PrivacyInfo.xcprivacy`);
const extensionsConfigPath = path.join(widgetPath, `extension.json`);
const entitlementsPath = path.join(widgetPath, `${name}.entitlements`);
const widgetBundlePath = path.join(widgetPath, `${capitalName}Bundle.swift`);
const widgetHomeScreenPath = path.join(widgetPath, `${capitalName}HomeScreenWidget.swift`);
const widgetLiveActivityPath = path.join(widgetPath, `${capitalName}LiveActivity.swift`);
// const appIntentPath = path.join(widgetPath, `AppIntent.swift`);
// const widgetLockScreenControlPath = path.join(
// widgetPath,
// `${capitalName}LockScreenControl.swift`
// );
const appEntitlementsPath = path.join(appResourcePath, "iOS", "app.entitlements");
if (!fs.existsSync(extensionsConfigPath)) {
fs.mkdirSync(widgetPath, { recursive: true });
let content = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>${os_1.EOL}`;
fs.writeFileSync(extensionsInfoPath, content);
content = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>${os_1.EOL}`;
fs.writeFileSync(extensionsPrivacyPath, content);
// TODO: can add control (lock screen custom control icon handler) in future
// ${[1, 2].includes(type) ? capitalName + "LockScreenControl()" : ""}
content = `import WidgetKit
import SwiftUI
@main
struct ${capitalName}Bundle: SwiftUI.WidgetBundle {
var body: some Widget {
${[1, 2].includes(type) ? capitalName + "HomeScreenWidget()" : ""}
${[0, 1].includes(type) ? capitalName + "LiveActivity()" : ""}
}
}${os_1.EOL}`;
fs.writeFileSync(widgetBundlePath, content);
if ([0, 1].includes(type)) {
content = `import ActivityKit
import SwiftUI
import WidgetKit
import Foundation
import SharedWidget
import os
struct ${capitalName}LiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: ${capitalName}Model.self) { context in
LockScreenView(message: context.state.message, deliveryTime: context.state.deliveryTime)
.activityBackgroundTint(Color.black)
.activitySystemActionForegroundColor(Color.white)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Image(systemName: context.state.deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
.foregroundColor(context.state.deliveryTime >= 0 ? Color.green : Color.blue)
}
DynamicIslandExpandedRegion(.trailing) {
if (context.state.deliveryTime >= 0) {
ZStack {
ProgressView(value: context.state.deliveryTime, total: 60)
.progressViewStyle(.circular)
.tint(Color.green)
.frame(width: 75, height: 75)
Text("\\(formatter.string(for: context.state.deliveryTime) ?? "") mins")
.font(.system(size: 11))
.foregroundStyle(.white)
}.frame(width: 75, height: 75)
} else {
Image(systemName: "checkmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
.foregroundColor(.blue)
}
}
DynamicIslandExpandedRegion(.bottom) {
Text("\\(context.state.message)")
}
} compactLeading: {
Image(systemName: context.state.deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundColor(context.state.deliveryTime >= 0 ? .green : .blue)
} compactTrailing: {
Image(systemName: context.state.deliveryTime >= 0 ? "timer.circle.fill" : "checkmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundColor(context.state.deliveryTime >= 0 ? .green : .blue)
} minimal: {
Text(context.state.message).font(.system(size: 12))
}
.widgetURL(URL(string: "http://www.apple.com"))
.keylineTint(Color.red)
}
}
private let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 0
formatter.minimumFractionDigits = 0
return formatter
}()
}
struct LockScreenView: View {
private var message = ""
private var deliveryTime: Double = 0
// for console debugging
let logger = Logger(subsystem: "${bundleId}.${name}", category: "Widget")
var body: some View {
ZStack {
LinearGradient(
gradient: Gradient(colors: [Color.gray.opacity(0.3), Color.black]),
startPoint: .top,
endPoint: .bottom
)
VStack {
Spacer()
Image(systemName: deliveryTime >= 0 ? "car.side.arrowtriangle.up.fill" : "face.smiling.inverse")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
.foregroundColor(deliveryTime >= 0 ? .green : .blue)
Spacer()
Text("\\(message)")
.foregroundStyle(.white)
Spacer()
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
init(message: String = "", deliveryTime: Double = 0) {
_message = State(initialValue: message)
_deliveryTime = State(initialValue: deliveryTime)
// Logs the deliveryTime at init for debugging purposes if needed
logger.log("deliveryTime: \\(deliveryTime)")
}
}${os_1.EOL}`;
fs.writeFileSync(widgetLiveActivityPath, content);
}
if ([1, 2].includes(type)) {
content = `import SwiftUI
import WidgetKit
/**
* Widget data shared between the app and the widget extension.
*/
struct WidgetData: Codable {
let pizzas: [String]
let orderTime: Double
let delivered: Bool
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), pizza: "Pepperoni", delivered: false, orderTime: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), pizza: "Pepperoni", delivered: false, orderTime: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping @Sendable (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
if let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") {
let currentDate = Date()
if let jsonString = sharedDefaults.string(forKey: "widgetData") {
if let jsonData = jsonString.data(using: .utf8) {
do {
let widgetData = try JSONDecoder().decode(WidgetData.self, from: jsonData)
let pizzas = widgetData.pizzas
let orderTime = Date(timeIntervalSince1970: widgetData.orderTime/1000)
let delivered = widgetData.delivered
// Generate a timeline of entries 1 second apart, starting from the current date.
for secondOffset in 0..<pizzas.count {
let entryDate = Calendar.current.date(
byAdding: .second, value: secondOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, pizza: secondOffset < pizzas.count ? pizzas[secondOffset] : pizzas[0], delivered: delivered, orderTime: orderTime)
entries.append(entry)
}
} catch {
print("Failed to decode JSON: (error)")
}
}
} else {
let entry = SimpleEntry(date: currentDate, pizza: "", delivered: false, orderTime: nil)
entries.append(entry)
}
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
// func relevances() async -> WidgetRelevances<Void> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct SimpleEntry: TimelineEntry {
let date: Date
let pizza: String
let delivered: Bool
let orderTime: Date?
}
struct WidgetView: View {
(\\.widgetFamily) var widgetFamily
var entry: Provider.Entry
var body: some View {
VStack {
if (entry.pizza != "") {
Spacer()
Image(systemName: entry.delivered ? "face.smiling.inverse" : "car.side")
.resizable()
.scaledToFit()
.frame(width: iconSize(for: widgetFamily), height: iconSize(for: widgetFamily))
.foregroundColor(entry.delivered ? .blue : .green)
Spacer()
if (entry.delivered) {
Text("Pizza Delivered!")
.font(.system(size: fontSize(for: widgetFamily), weight: .bold))
.foregroundStyle(.white)
} else {
HStack(spacing: 4) {
Text("Ordered:")
.font(.system(size: fontSize(for: widgetFamily)))
.foregroundStyle(.white)
Text(entry.orderTime!, style: .time)
.font(.system(size: fontSize(for: widgetFamily), weight: .bold))
.foregroundStyle(.white)
}
HStack(spacing: 4) {
Text("Pizza:")
.font(.system(size: fontSize(for: widgetFamily)))
.foregroundStyle(.white)
Text(entry.pizza)
.font(.system(size: fontSize(for: widgetFamily), weight: .bold))
.foregroundStyle(.white)
}
}
Spacer()
} else {
Spacer()
Image(systemName: "car.side.rear.open")
.resizable()
.scaledToFit()
.frame(width: iconSize(for: widgetFamily), height: iconSize(for: widgetFamily))
.foregroundColor(.gray)
Spacer()
Text("Awaiting orders...")
.foregroundStyle(.white)
Spacer()
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private func iconSize(for family: WidgetFamily) -> CGFloat {
switch family {
case .systemSmall:
return 65
case .systemMedium:
return 85
case .systemLarge:
return 150
default:
return 65
}
}
private func fontSize(for family: WidgetFamily) -> CGFloat {
switch family {
case .systemSmall:
return 12
case .systemMedium:
return 14
case .systemLarge:
return 18
default:
return 14
}
}
}
@available(iOSApplicationExtension 17.0, *)
struct ${capitalName}HomeScreenWidget: Widget {
let kind: String = "widget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WidgetView(entry: entry)
.containerBackground(for: .widget) {
LinearGradient(
gradient: Gradient(colors: [Color.black.opacity(0.6), Color.black]),
startPoint: .top,
endPoint: .bottom
)
}
}
.configurationDisplayName("${capitalName} Widget")
.description("${capitalName} delivery service.")
}
}
#Preview(as: .systemSmall) {
${capitalName}HomeScreenWidget()
} timeline: {
SimpleEntry(date: .now, pizza: "Pepperoni", delivered: false, orderTime: Date())
SimpleEntry(date: .now, pizza: "Hawaiian", delivered: false, orderTime: Date())
}${os_1.EOL}`;
fs.writeFileSync(widgetHomeScreenPath, content);
}
content = `{
"${bundleId}.${name}": "{set-your-provision-profile-id}"
}`;
fs.writeFileSync(extensionProvisionPath, content);
content = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.${bundleId}</string>
</array>
</dict>
</plist>${os_1.EOL}`;
fs.writeFileSync(entitlementsPath, content);
if (fs.existsSync(appInfoPlistPath)) {
const appSupportLiveActivity = "NSSupportsLiveActivities";
const appInfoPlist = plist.parse(fs.readFileSync(appInfoPlistPath, {
encoding: "utf-8",
}));
if (!appInfoPlist[appSupportLiveActivity]) {
// @ts-ignore
appInfoPlist[appSupportLiveActivity] = true;
const appPlist = plist.build(appInfoPlist);
fs.writeFileSync(appInfoPlistPath, appPlist);
}
}
const appGroupKey = "com.apple.security.application-groups";
if (fs.existsSync(appEntitlementsPath)) {
const appEntitlementsPlist = plist.parse(fs.readFileSync(appEntitlementsPath, {
encoding: "utf-8",
}));
if (!appEntitlementsPlist[appGroupKey]) {
// @ts-ignore
appEntitlementsPlist[appGroupKey] = [`group.${bundleId}`];
const appEntitlements = plist.build(appEntitlementsPlist);
console.log("appentitlement:", appEntitlements);
fs.writeFileSync(appEntitlementsPath, appEntitlements);
}
}
else {
content = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.${bundleId}</string>
</array>
</dict>
</plist>${os_1.EOL}`;
fs.writeFileSync(appEntitlementsPath, content);
}
content = `{
"frameworks": [
"SwiftUI.framework",
"WidgetKit.framework"
],
"targetBuildConfigurationProperties": {
"ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME": "AccentColor",
"ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME": "WidgetBackground",
"CLANG_ANALYZER_NONNULL": "YES",
"CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION": "YES_AGGRESSIVE",
"CLANG_CXX_LANGUAGE_STANDARD": "\\"gnu++20\\"",
"CLANG_ENABLE_OBJC_WEAK": "YES",
"CLANG_WARN_DOCUMENTATION_COMMENTS": "YES",
"CLANG_WARN_UNGUARDED_AVAILABILITY": "YES_AGGRESSIVE",
"CURRENT_PROJECT_VERSION": 1,
"GCC_C_LANGUAGE_STANDARD": "gnu11",
"GCC_WARN_UNINITIALIZED_AUTOS": "YES_AGGRESSIVE",
"GENERATE_INFOPLIST_FILE": "YES",
"INFOPLIST_KEY_CFBundleDisplayName": "widget",
"INFOPLIST_KEY_NSHumanReadableCopyright": "\\"Copyright © All rights reserved.\\"",
"IPHONEOS_DEPLOYMENT_TARGET": 18.0,
"MARKETING_VERSION": "1.0",
"MTL_FAST_MATH": "YES",
"PRODUCT_NAME": "widget",
"SWIFT_EMIT_LOC_STRINGS": "YES",
"SWIFT_VERSION": "5.0",
"TARGETED_DEVICE_FAMILY": "\\"1,2\\"",
"MTL_ENABLE_DEBUG_INFO": "NO",
"SWIFT_OPTIMIZATION_LEVEL": "\\"-O\\"",
"COPY_PHASE_STRIP": "NO",
"SWIFT_COMPILATION_MODE": "wholemodule",
"CODE_SIGN_ENTITLEMENTS": "../../App_Resources/iOS/extensions/${name}/${name}.entitlements"
},
"targetNamedBuildConfigurationProperties": {
"debug": {
"DEBUG_INFORMATION_FORMAT": "dwarf",
"GCC_PREPROCESSOR_DEFINITIONS": "(\\"DEBUG=1\\",\\"$(inherited)\\",)",
"MTL_ENABLE_DEBUG_INFO": "INCLUDE_SOURCE",
"SWIFT_ACTIVE_COMPILATION_CONDITIONS": "DEBUG",
"SWIFT_OPTIMIZATION_LEVEL": "\\"-Onone\\""
},
"release": {
"CODE_SIGN_STYLE": "Manual",
"MTL_ENABLE_DEBUG_INFO": "NO",
"SWIFT_OPTIMIZATION_LEVEL": "\\"-O\\"",
"COPY_PHASE_STRIP": "NO",
"SWIFT_COMPILATION_MODE": "wholemodule"
}
}
}${os_1.EOL}`;
fs.writeFileSync(extensionsConfigPath, content);
console.log(`🚀 Your widget is now ready to develop: App_Resources/iOS/extensions/${name}.\n`);
const steps = ["Followup steps:"];
steps.push("- Check App_Resources/iOS/build.xcconfig uses IPHONEOS_DEPLOYMENT_TARGET = 17 or higher.");
steps.push("- Update App_Resources/iOS/extensions/provisioning.json with your profile id.");
if ([0, 1].includes(type)) {
steps.push(`- Customize App_Resources/iOS/extensions/${name}/${(0, utils_1.capitalizeFirstLetter)(name)}LiveActivity.swift for your display.`);
steps.push(`- Customize Shared_Resources/iOS/SharedWidget/Sources/SharedWidget/${(0, utils_1.capitalizeFirstLetter)(name)}Model.swift for your data.`);
}
console.log(steps.join("\n"));
}
// if (fs.existsSync(filePath)) {
// this.$errors.failWithHelp(`Error: File '${filePath}' already exists.`);
// return;
// }
}
generateAppleUtility(projectDir, name, bundleId, type) {
const capitalName = (0, utils_1.capitalizeFirstLetter)(name);
const appResourcePath = this.$projectData.appResourcesDirectoryPath;
const appResourceSrcPath = path.join(appResourcePath, "iOS", "src");
const appleUtilityPath = path.join(appResourceSrcPath, `AppleWidgetUtils.swift`);
const referenceTypesPath = path.join(projectDir, "references.d.ts");
if (!fs.existsSync(appleUtilityPath)) {
fs.mkdirSync(appResourceSrcPath, { recursive: true });
}
if (!fs.existsSync(appleUtilityPath)) {
}
const liveActivityUtilities = `// Live Activity Handling
public static func startActivity(_ data: NSDictionary) {
if ActivityAuthorizationInfo().areActivitiesEnabled {
let numberOfPizzas = data.object(forKey: "numberOfPizzas") as! Int
let totalAmount = data.object(forKey: "totalAmount") as! String
let attrs = ${capitalName}Model(numberOfPizzas: numberOfPizzas, totalAmount: totalAmount)
let message = data.object(forKey: "message") as! String
let deliveryTime = data.object(forKey: "deliveryTime") as! Double
let initialStatus = ${capitalName}Model.DeliveryStatus(
message: message, deliveryTime: deliveryTime)
let content = ActivityContent(state: initialStatus, staleDate: nil)
do {
let activity = try Activity<${capitalName}Model>.request(
attributes: attrs,
content: content,
pushType: nil)
print("Requested a Live Activity \\(activity.id)")
} catch (let error) {
print("Error requesting Live Activity \\(error.localizedDescription)")
}
}
}
public static func updateActivity(_ data: NSDictionary) {
if ActivityAuthorizationInfo().areActivitiesEnabled {
Task {
let message = data.object(forKey: "message") as! String
let deliveryTime = data.object(forKey: "deliveryTime") as! Double
let status = ${capitalName}Model.DeliveryStatus(
message: message, deliveryTime: deliveryTime)
let content = ActivityContent(state: status, staleDate: nil)
for activity in Activity<${capitalName}Model>.activities {
await activity.update(content)
}
}
}
}
public static func cancelActivity(_ data: NSDictionary) {
if ActivityAuthorizationInfo().areActivitiesEnabled {
Task {
let message = data.object(forKey: "message") as! String
let status = ${capitalName}Model.DeliveryStatus(
message: message, deliveryTime: 0)
let content = ActivityContent(state: status, staleDate: nil)
for activity in Activity<${capitalName}Model>.activities {
await activity.end(content, dismissalPolicy: .immediate)
}
}
}
}`;
let content = `import Foundation
import UIKit
import ActivityKit
import WidgetKit
${[0, 1].includes(type) ? "import SharedWidget" : ""}
@objcMembers
public class AppleWidgetUtils: NSObject {
${[0, 1].includes(type) ? liveActivityUtilities : ""}
// Shared App Group Data
public static func getData(key: String) -> String? {
guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else {
return nil
}
return sharedDefaults.object(forKey: key) as? String
}
public static func updateData(key: String, _ data: String) {
guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else {
return
}
sharedDefaults.set(data, forKey: key)
sharedDefaults.synchronize()
}
public static func removeData(key: String) {
guard let sharedDefaults = UserDefaults(suiteName: "group.${bundleId}") else {
return
}
sharedDefaults.removeObject(forKey: key)
sharedDefaults.synchronize()
}
// Home Screen Widget Handling
public static func updateWidget() {
if #available(iOS 14.0, *) {
Task.detached(priority: .userInitiated) {
WidgetCenter.shared.reloadAllTimelines()
}
}
}
}${os_1.EOL}`;
fs.writeFileSync(appleUtilityPath, content);
content = `/**
* Customize for your own Apple Widget Data
*/
declare interface AppleWidgetModelData {
numberOfPizzas: number;
totalAmount: string;
message: string;
deliveryTime: number;
}
declare class AppleWidgetUtils extends NSObject {
static startActivity(data: AppleWidgetModelData): void;
static updateActivity(
data: Pick<AppleWidgetModelData, "message" | "deliveryTime">
): void;
static cancelActivity(data: Pick<AppleWidgetModelData, "message">): void;
static updateWidget(): void;
static updateDataWithKey(key: string, data: string): void;
static getDataWithKey(key: string): string;
static removeDataWithKey(key: string): void;
}${os_1.EOL}`;
if (!fs.existsSync(referenceTypesPath)) {
const references = `/// <reference path="./node_modules/@nativescript/types-android/index.d.ts" />
/// <reference path="./node_modules/@nativescript/types-ios/complete.d.ts" />${os_1.EOL}${content}`;
fs.writeFileSync(referenceTypesPath, references);
}
else {
const references = fs.readFileSync(referenceTypesPath, {
encoding: "utf-8",
});
if ((references === null || references === void 0 ? void 0 : references.indexOf("AppleWidgetUtils")) === -1) {
content = `${references.toString()}${os_1.EOL}${content}`;
fs.writeFileSync(referenceTypesPath, content);
}
}
}
}
exports.WidgetIOSCommand = WidgetIOSCommand;
yok_1.injector.registerCommand(["widget"], WidgetCommand);
yok_1.injector.registerCommand(["widget|ios"], WidgetIOSCommand);
//# sourceMappingURL=widget.js.map