craft-native 0.0.37 → 0.0.49
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/android/src/index.js +268 -0
- package/dist/android/src/index.js.map +10 -0
- package/dist/android/templates/AndroidManifest.xml.template +69 -0
- package/dist/android/templates/CraftBridge.kt.template +3748 -0
- package/dist/android/templates/CraftBridgeExtensions.kt.template +383 -0
- package/dist/android/templates/CraftWidgetProvider.kt.template +246 -0
- package/dist/android/templates/MainActivity.kt.template +109 -0
- package/dist/android/templates/build.gradle.kts.app.template +80 -0
- package/dist/android/templates/build.gradle.kts.project.template +5 -0
- package/dist/android/templates/fastlane/Appfile +25 -0
- package/dist/android/templates/fastlane/Fastfile +192 -0
- package/dist/android/templates/fastlane/Gemfile +7 -0
- package/dist/android/templates/github-workflow-android.yml +248 -0
- package/dist/android/templates/github-workflow-test.yml +329 -0
- package/dist/android/templates/settings.gradle.kts.template +18 -0
- package/dist/android/templates/test-bridges.html +1463 -0
- package/dist/api/window.d.ts +18 -0
- package/dist/api/window.d.ts.map +1 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/bridge/core.d.ts +2 -0
- package/dist/bridge/core.d.ts.map +1 -1
- package/dist/cli.js +4168 -0
- package/dist/components/sidebar.d.ts +5 -1
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/dev/devtools.d.ts +1 -1
- package/dist/dev/devtools.d.ts.map +1 -1
- package/dist/dev/hot-reload.d.ts +12 -3
- package/dist/dev/hot-reload.d.ts.map +1 -1
- package/dist/index.cjs +573 -208
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +585 -216
- package/dist/index.js.map +45 -0
- package/dist/ios/src/index.js +186 -0
- package/dist/ios/src/index.js.map +10 -0
- package/dist/ios/templates/CraftApp.swift +4241 -0
- package/dist/ios/templates/CraftAppExtensions.swift +361 -0
- package/dist/ios/templates/CraftWidget.swift +247 -0
- package/dist/ios/templates/Info.plist.template +65 -0
- package/dist/ios/templates/fastlane/Appfile +45 -0
- package/dist/ios/templates/fastlane/Fastfile +189 -0
- package/dist/ios/templates/fastlane/Gemfile +7 -0
- package/dist/ios/templates/fastlane/Matchfile +58 -0
- package/dist/ios/templates/github-workflow-ios.yml +192 -0
- package/dist/ios/templates/github-workflow-release.yml +175 -0
- package/dist/ios/templates/github-workflow-test.yml +272 -0
- package/dist/ios/templates/project.yml.template +25 -0
- package/dist/ios/templates/test-bridges.html +1463 -0
- package/dist/package.d.ts +106 -2
- package/dist/package.d.ts.map +1 -1
- package/dist/package.test.d.ts +2 -0
- package/dist/package.test.d.ts.map +1 -0
- package/dist/styles/sidebar-templates.d.ts.map +1 -1
- package/dist/styles/sidebars.d.ts +12 -8
- package/dist/styles/sidebars.d.ts.map +1 -1
- package/dist/templates/projects/blank/craft.config.ts +19 -0
- package/dist/templates/projects/blank/index.html +27 -0
- package/dist/templates/projects/blank/package.json +19 -0
- package/dist/templates/projects/blank/src/main.ts +26 -0
- package/dist/templates/projects/dashboard/craft.config.ts +42 -0
- package/dist/templates/projects/dashboard/index.html +13 -0
- package/dist/templates/projects/dashboard/package.json +20 -0
- package/dist/templates/projects/dashboard/src/main.ts +163 -0
- package/dist/templates/projects/dashboard/src/styles.css +280 -0
- package/dist/templates/projects/drawer/craft.config.ts +47 -0
- package/dist/templates/projects/drawer/index.html +13 -0
- package/dist/templates/projects/drawer/package.json +21 -0
- package/dist/templates/projects/drawer/src/main.ts +406 -0
- package/dist/templates/projects/drawer/src/styles.css +468 -0
- package/dist/templates/projects/ecommerce/craft.config.ts +32 -0
- package/dist/templates/projects/ecommerce/index.html +13 -0
- package/dist/templates/projects/ecommerce/package.json +19 -0
- package/dist/templates/projects/ecommerce/src/main.ts +194 -0
- package/dist/templates/projects/ecommerce/src/styles.css +169 -0
- package/dist/templates/projects/social/craft.config.ts +32 -0
- package/dist/templates/projects/social/index.html +13 -0
- package/dist/templates/projects/social/package.json +19 -0
- package/dist/templates/projects/social/src/main.ts +270 -0
- package/dist/templates/projects/social/src/styles.css +216 -0
- package/dist/templates/projects/tabs/craft.config.ts +47 -0
- package/dist/templates/projects/tabs/index.html +13 -0
- package/dist/templates/projects/tabs/package.json +21 -0
- package/dist/templates/projects/tabs/src/main.ts +177 -0
- package/dist/templates/projects/tabs/src/styles.css +163 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/updater/index.d.ts +1 -0
- package/dist/updater/index.d.ts.map +1 -1
- package/package.json +5 -12
- package/bin/cli.ts +0 -937
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import ActivityKit
|
|
3
|
+
import AppIntents
|
|
4
|
+
import TipKit
|
|
5
|
+
|
|
6
|
+
// MARK: - iOS 16+ Advanced Features
|
|
7
|
+
|
|
8
|
+
/// Live Activities (Dynamic Island) Support
|
|
9
|
+
@available(iOS 16.1, *)
|
|
10
|
+
struct CraftLiveActivity: ActivityAttributes {
|
|
11
|
+
public struct ContentState: Codable, Hashable {
|
|
12
|
+
var status: String
|
|
13
|
+
var progress: Double
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var name: String
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@available(iOS 16.1, *)
|
|
20
|
+
class LiveActivityManager {
|
|
21
|
+
static let shared = LiveActivityManager()
|
|
22
|
+
|
|
23
|
+
private var currentActivity: Activity<CraftLiveActivity>?
|
|
24
|
+
|
|
25
|
+
func startActivity(name: String, status: String, progress: Double) async throws {
|
|
26
|
+
let attributes = CraftLiveActivity(name: name)
|
|
27
|
+
let contentState = CraftLiveActivity.ContentState(status: status, progress: progress)
|
|
28
|
+
|
|
29
|
+
currentActivity = try Activity.request(
|
|
30
|
+
attributes: attributes,
|
|
31
|
+
content: .init(state: contentState, staleDate: nil)
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func updateActivity(status: String, progress: Double) async {
|
|
36
|
+
guard let activity = currentActivity else { return }
|
|
37
|
+
|
|
38
|
+
let contentState = CraftLiveActivity.ContentState(status: status, progress: progress)
|
|
39
|
+
await activity.update(.init(state: contentState, staleDate: nil))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func endActivity() async {
|
|
43
|
+
guard let activity = currentActivity else { return }
|
|
44
|
+
await activity.end(nil, dismissalPolicy: .immediate)
|
|
45
|
+
currentActivity = nil
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - App Intents (iOS 16+)
|
|
50
|
+
|
|
51
|
+
@available(iOS 16.0, *)
|
|
52
|
+
struct OpenCraftIntent: AppIntent {
|
|
53
|
+
static var title: LocalizedStringResource = "Open Craft App"
|
|
54
|
+
static var description = IntentDescription("Opens the Craft application")
|
|
55
|
+
|
|
56
|
+
func perform() async throws -> some IntentResult {
|
|
57
|
+
// Open app logic
|
|
58
|
+
return .result()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@available(iOS 16.0, *)
|
|
63
|
+
struct CraftShortcuts: AppShortcutsProvider {
|
|
64
|
+
static var appShortcuts: [AppShortcut] {
|
|
65
|
+
AppShortcut(
|
|
66
|
+
intent: OpenCraftIntent(),
|
|
67
|
+
phrases: [
|
|
68
|
+
"Open \(.applicationName)",
|
|
69
|
+
"Launch \(.applicationName)"
|
|
70
|
+
],
|
|
71
|
+
shortTitle: "Open App",
|
|
72
|
+
systemImageName: "app.fill"
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// MARK: - TipKit Integration (iOS 17+)
|
|
78
|
+
|
|
79
|
+
@available(iOS 17.0, *)
|
|
80
|
+
struct CraftWelcomeTip: Tip {
|
|
81
|
+
var title: Text {
|
|
82
|
+
Text("Welcome to Craft")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
var message: Text? {
|
|
86
|
+
Text("Get started by exploring the features")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var image: Image? {
|
|
90
|
+
Image(systemName: "star.fill")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@available(iOS 17.0, *)
|
|
95
|
+
class TipKitManager {
|
|
96
|
+
static let shared = TipKitManager()
|
|
97
|
+
|
|
98
|
+
func configure() {
|
|
99
|
+
try? Tips.configure([
|
|
100
|
+
.displayFrequency(.immediate),
|
|
101
|
+
.datastoreLocation(.applicationDefault)
|
|
102
|
+
])
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// MARK: - SharePlay Integration
|
|
107
|
+
|
|
108
|
+
@available(iOS 15.0, *)
|
|
109
|
+
class SharePlayManager: ObservableObject {
|
|
110
|
+
static let shared = SharePlayManager()
|
|
111
|
+
|
|
112
|
+
@Published var isActive = false
|
|
113
|
+
|
|
114
|
+
func startSharePlay() async throws {
|
|
115
|
+
// SharePlay session logic
|
|
116
|
+
isActive = true
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func endSharePlay() {
|
|
120
|
+
isActive = false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// MARK: - App Clips Support
|
|
125
|
+
|
|
126
|
+
class AppClipManager {
|
|
127
|
+
static let shared = AppClipManager()
|
|
128
|
+
|
|
129
|
+
var isRunningInAppClip: Bool {
|
|
130
|
+
#if APPCLIP
|
|
131
|
+
return true
|
|
132
|
+
#else
|
|
133
|
+
return false
|
|
134
|
+
#endif
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func configureAppClip() {
|
|
138
|
+
// App Clip specific configuration
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// MARK: - Focus Filters (iOS 16+)
|
|
143
|
+
|
|
144
|
+
@available(iOS 16.0, *)
|
|
145
|
+
class FocusFilterManager {
|
|
146
|
+
static let shared = FocusFilterManager()
|
|
147
|
+
|
|
148
|
+
func getCurrentFocus() -> String? {
|
|
149
|
+
// Return current Focus mode if available
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func registerFocusFilter() {
|
|
154
|
+
// Register app-specific Focus filter
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// MARK: - StoreKit 2 Full Implementation
|
|
159
|
+
|
|
160
|
+
@available(iOS 15.0, *)
|
|
161
|
+
class StoreKitManager: ObservableObject {
|
|
162
|
+
static let shared = StoreKitManager()
|
|
163
|
+
|
|
164
|
+
@Published var products: [Product] = []
|
|
165
|
+
@Published var purchasedProductIDs: Set<String> = []
|
|
166
|
+
|
|
167
|
+
private var updateListenerTask: Task<Void, Error>?
|
|
168
|
+
|
|
169
|
+
func loadProducts(productIDs: [String]) async throws {
|
|
170
|
+
products = try await Product.products(for: productIDs)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func purchase(_ product: Product) async throws -> Transaction? {
|
|
174
|
+
let result = try await product.purchase()
|
|
175
|
+
|
|
176
|
+
switch result {
|
|
177
|
+
case .success(let verification):
|
|
178
|
+
let transaction = try checkVerified(verification)
|
|
179
|
+
await transaction.finish()
|
|
180
|
+
await updatePurchasedProducts()
|
|
181
|
+
return transaction
|
|
182
|
+
|
|
183
|
+
case .userCancelled, .pending:
|
|
184
|
+
return nil
|
|
185
|
+
|
|
186
|
+
@unknown default:
|
|
187
|
+
return nil
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func restorePurchases() async throws {
|
|
192
|
+
for await result in Transaction.currentEntitlements {
|
|
193
|
+
let transaction = try checkVerified(result)
|
|
194
|
+
await updatePurchasedProducts()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func startObservingTransactions() {
|
|
199
|
+
updateListenerTask = Task.detached {
|
|
200
|
+
for await result in Transaction.updates {
|
|
201
|
+
do {
|
|
202
|
+
let transaction = try self.checkVerified(result)
|
|
203
|
+
await transaction.finish()
|
|
204
|
+
await self.updatePurchasedProducts()
|
|
205
|
+
} catch {
|
|
206
|
+
print("Transaction verification failed: \(error)")
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
func stopObservingTransactions() {
|
|
213
|
+
updateListenerTask?.cancel()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
|
|
217
|
+
switch result {
|
|
218
|
+
case .unverified:
|
|
219
|
+
throw StoreError.failedVerification
|
|
220
|
+
case .verified(let safe):
|
|
221
|
+
return safe
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@MainActor
|
|
226
|
+
private func updatePurchasedProducts() async {
|
|
227
|
+
for await result in Transaction.currentEntitlements {
|
|
228
|
+
guard case .verified(let transaction) = result else {
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if transaction.revocationDate == nil {
|
|
233
|
+
purchasedProductIDs.insert(transaction.productID)
|
|
234
|
+
} else {
|
|
235
|
+
purchasedProductIDs.remove(transaction.productID)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
enum StoreError: Error {
|
|
241
|
+
case failedVerification
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// MARK: - CarPlay Support
|
|
246
|
+
|
|
247
|
+
#if canImport(CarPlay)
|
|
248
|
+
import CarPlay
|
|
249
|
+
|
|
250
|
+
@available(iOS 12.0, *)
|
|
251
|
+
class CarPlayManager: NSObject, CPApplicationDelegate {
|
|
252
|
+
static let shared = CarPlayManager()
|
|
253
|
+
|
|
254
|
+
private var interfaceController: CPInterfaceController?
|
|
255
|
+
|
|
256
|
+
func application(_ application: UIApplication, didConnectCarInterfaceController interfaceController: CPInterfaceController, to window: CPWindow) {
|
|
257
|
+
self.interfaceController = interfaceController
|
|
258
|
+
|
|
259
|
+
let template = CPListTemplate(title: "Craft", sections: [
|
|
260
|
+
CPListSection(items: [
|
|
261
|
+
CPListItem(text: "Home", detailText: "Go to home screen")
|
|
262
|
+
])
|
|
263
|
+
])
|
|
264
|
+
|
|
265
|
+
interfaceController.setRootTemplate(template, animated: true)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
func application(_ application: UIApplication, didDisconnectCarInterfaceController interfaceController: CPInterfaceController, from window: CPWindow) {
|
|
269
|
+
self.interfaceController = nil
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
#endif
|
|
273
|
+
|
|
274
|
+
// MARK: - Bridge Integration Extensions
|
|
275
|
+
|
|
276
|
+
extension CraftWebView.Coordinator {
|
|
277
|
+
|
|
278
|
+
@available(iOS 16.1, *)
|
|
279
|
+
func startLiveActivity(name: String, status: String, progress: Double, callbackId: String?) {
|
|
280
|
+
Task {
|
|
281
|
+
do {
|
|
282
|
+
try await LiveActivityManager.shared.startActivity(name: name, status: status, progress: progress)
|
|
283
|
+
resolveCallback(callbackId, result: ["started": true])
|
|
284
|
+
} catch {
|
|
285
|
+
rejectCallback(callbackId, error: error.localizedDescription)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@available(iOS 16.1, *)
|
|
291
|
+
func updateLiveActivity(status: String, progress: Double, callbackId: String?) {
|
|
292
|
+
Task {
|
|
293
|
+
await LiveActivityManager.shared.updateActivity(status: status, progress: progress)
|
|
294
|
+
resolveCallback(callbackId, result: ["updated": true])
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@available(iOS 16.1, *)
|
|
299
|
+
func endLiveActivity(callbackId: String?) {
|
|
300
|
+
Task {
|
|
301
|
+
await LiveActivityManager.shared.endActivity()
|
|
302
|
+
resolveCallback(callbackId, result: ["ended": true])
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@available(iOS 15.0, *)
|
|
307
|
+
func loadStoreProducts(productIDs: [String], callbackId: String?) {
|
|
308
|
+
Task {
|
|
309
|
+
do {
|
|
310
|
+
try await StoreKitManager.shared.loadProducts(productIDs: productIDs)
|
|
311
|
+
let products = StoreKitManager.shared.products.map { product in
|
|
312
|
+
[
|
|
313
|
+
"id": product.id,
|
|
314
|
+
"displayName": product.displayName,
|
|
315
|
+
"description": product.description,
|
|
316
|
+
"price": product.displayPrice
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
resolveCallback(callbackId, result: products)
|
|
320
|
+
} catch {
|
|
321
|
+
rejectCallback(callbackId, error: error.localizedDescription)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@available(iOS 15.0, *)
|
|
327
|
+
func purchaseProduct(productId: String, callbackId: String?) {
|
|
328
|
+
Task {
|
|
329
|
+
do {
|
|
330
|
+
guard let product = StoreKitManager.shared.products.first(where: { $0.id == productId }) else {
|
|
331
|
+
rejectCallback(callbackId, error: "Product not found")
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if let transaction = try await StoreKitManager.shared.purchase(product) {
|
|
336
|
+
resolveCallback(callbackId, result: [
|
|
337
|
+
"transactionId": transaction.id,
|
|
338
|
+
"productId": transaction.productID,
|
|
339
|
+
"purchased": true
|
|
340
|
+
])
|
|
341
|
+
} else {
|
|
342
|
+
resolveCallback(callbackId, result: ["purchased": false])
|
|
343
|
+
}
|
|
344
|
+
} catch {
|
|
345
|
+
rejectCallback(callbackId, error: error.localizedDescription)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@available(iOS 15.0, *)
|
|
351
|
+
func restorePurchases(callbackId: String?) {
|
|
352
|
+
Task {
|
|
353
|
+
do {
|
|
354
|
+
try await StoreKitManager.shared.restorePurchases()
|
|
355
|
+
resolveCallback(callbackId, result: ["restored": true])
|
|
356
|
+
} catch {
|
|
357
|
+
rejectCallback(callbackId, error: error.localizedDescription)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import WidgetKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
// MARK: - Widget Entry
|
|
5
|
+
struct CraftWidgetEntry: TimelineEntry {
|
|
6
|
+
let date: Date
|
|
7
|
+
let title: String
|
|
8
|
+
let subtitle: String
|
|
9
|
+
let value: String
|
|
10
|
+
let iconName: String?
|
|
11
|
+
let configuration: ConfigurationIntent?
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// MARK: - Widget Provider
|
|
15
|
+
struct CraftWidgetProvider: IntentTimelineProvider {
|
|
16
|
+
typealias Entry = CraftWidgetEntry
|
|
17
|
+
typealias Intent = ConfigurationIntent
|
|
18
|
+
|
|
19
|
+
// Shared UserDefaults for app-to-widget communication
|
|
20
|
+
private let sharedDefaults = UserDefaults(suiteName: "group.{{BUNDLE_ID}}.widget")
|
|
21
|
+
|
|
22
|
+
func placeholder(in context: Context) -> CraftWidgetEntry {
|
|
23
|
+
CraftWidgetEntry(
|
|
24
|
+
date: Date(),
|
|
25
|
+
title: "Craft Widget",
|
|
26
|
+
subtitle: "Loading...",
|
|
27
|
+
value: "",
|
|
28
|
+
iconName: nil,
|
|
29
|
+
configuration: nil
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (CraftWidgetEntry) -> Void) {
|
|
34
|
+
let entry = loadWidgetData(configuration: configuration)
|
|
35
|
+
completion(entry)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<CraftWidgetEntry>) -> Void) {
|
|
39
|
+
let entry = loadWidgetData(configuration: configuration)
|
|
40
|
+
|
|
41
|
+
// Refresh every 15 minutes
|
|
42
|
+
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
|
|
43
|
+
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
|
|
44
|
+
completion(timeline)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func loadWidgetData(configuration: ConfigurationIntent?) -> CraftWidgetEntry {
|
|
48
|
+
let title = sharedDefaults?.string(forKey: "widget_title") ?? "Craft Widget"
|
|
49
|
+
let subtitle = sharedDefaults?.string(forKey: "widget_subtitle") ?? ""
|
|
50
|
+
let value = sharedDefaults?.string(forKey: "widget_value") ?? ""
|
|
51
|
+
let iconName = sharedDefaults?.string(forKey: "widget_icon")
|
|
52
|
+
|
|
53
|
+
return CraftWidgetEntry(
|
|
54
|
+
date: Date(),
|
|
55
|
+
title: title,
|
|
56
|
+
subtitle: subtitle,
|
|
57
|
+
value: value,
|
|
58
|
+
iconName: iconName,
|
|
59
|
+
configuration: configuration
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// MARK: - Small Widget View
|
|
65
|
+
struct CraftWidgetSmallView: View {
|
|
66
|
+
var entry: CraftWidgetEntry
|
|
67
|
+
|
|
68
|
+
var body: some View {
|
|
69
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
70
|
+
if let iconName = entry.iconName {
|
|
71
|
+
Image(systemName: iconName)
|
|
72
|
+
.font(.title2)
|
|
73
|
+
.foregroundColor(.accentColor)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Text(entry.title)
|
|
77
|
+
.font(.headline)
|
|
78
|
+
.lineLimit(2)
|
|
79
|
+
|
|
80
|
+
if !entry.subtitle.isEmpty {
|
|
81
|
+
Text(entry.subtitle)
|
|
82
|
+
.font(.caption)
|
|
83
|
+
.foregroundColor(.secondary)
|
|
84
|
+
.lineLimit(1)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Spacer()
|
|
88
|
+
|
|
89
|
+
if !entry.value.isEmpty {
|
|
90
|
+
Text(entry.value)
|
|
91
|
+
.font(.title)
|
|
92
|
+
.fontWeight(.bold)
|
|
93
|
+
.foregroundColor(.accentColor)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
.padding()
|
|
97
|
+
.widgetBackground(Color(.systemBackground))
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// MARK: - Medium Widget View
|
|
102
|
+
struct CraftWidgetMediumView: View {
|
|
103
|
+
var entry: CraftWidgetEntry
|
|
104
|
+
|
|
105
|
+
var body: some View {
|
|
106
|
+
HStack {
|
|
107
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
108
|
+
if let iconName = entry.iconName {
|
|
109
|
+
Image(systemName: iconName)
|
|
110
|
+
.font(.title)
|
|
111
|
+
.foregroundColor(.accentColor)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Text(entry.title)
|
|
115
|
+
.font(.headline)
|
|
116
|
+
|
|
117
|
+
if !entry.subtitle.isEmpty {
|
|
118
|
+
Text(entry.subtitle)
|
|
119
|
+
.font(.subheadline)
|
|
120
|
+
.foregroundColor(.secondary)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Spacer()
|
|
125
|
+
|
|
126
|
+
if !entry.value.isEmpty {
|
|
127
|
+
Text(entry.value)
|
|
128
|
+
.font(.largeTitle)
|
|
129
|
+
.fontWeight(.bold)
|
|
130
|
+
.foregroundColor(.accentColor)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
.padding()
|
|
134
|
+
.widgetBackground(Color(.systemBackground))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// MARK: - Large Widget View
|
|
139
|
+
struct CraftWidgetLargeView: View {
|
|
140
|
+
var entry: CraftWidgetEntry
|
|
141
|
+
|
|
142
|
+
var body: some View {
|
|
143
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
144
|
+
HStack {
|
|
145
|
+
if let iconName = entry.iconName {
|
|
146
|
+
Image(systemName: iconName)
|
|
147
|
+
.font(.title)
|
|
148
|
+
.foregroundColor(.accentColor)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
VStack(alignment: .leading) {
|
|
152
|
+
Text(entry.title)
|
|
153
|
+
.font(.headline)
|
|
154
|
+
|
|
155
|
+
if !entry.subtitle.isEmpty {
|
|
156
|
+
Text(entry.subtitle)
|
|
157
|
+
.font(.subheadline)
|
|
158
|
+
.foregroundColor(.secondary)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
Spacer()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Divider()
|
|
166
|
+
|
|
167
|
+
if !entry.value.isEmpty {
|
|
168
|
+
Text(entry.value)
|
|
169
|
+
.font(.system(size: 48, weight: .bold))
|
|
170
|
+
.foregroundColor(.accentColor)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
Spacer()
|
|
174
|
+
|
|
175
|
+
Text("Updated: \(entry.date, style: .time)")
|
|
176
|
+
.font(.caption2)
|
|
177
|
+
.foregroundColor(.secondary)
|
|
178
|
+
}
|
|
179
|
+
.padding()
|
|
180
|
+
.widgetBackground(Color(.systemBackground))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// MARK: - Widget Entry View
|
|
185
|
+
struct CraftWidgetEntryView: View {
|
|
186
|
+
var entry: CraftWidgetEntry
|
|
187
|
+
@Environment(\.widgetFamily) var family
|
|
188
|
+
|
|
189
|
+
var body: some View {
|
|
190
|
+
switch family {
|
|
191
|
+
case .systemSmall:
|
|
192
|
+
CraftWidgetSmallView(entry: entry)
|
|
193
|
+
case .systemMedium:
|
|
194
|
+
CraftWidgetMediumView(entry: entry)
|
|
195
|
+
case .systemLarge:
|
|
196
|
+
CraftWidgetLargeView(entry: entry)
|
|
197
|
+
default:
|
|
198
|
+
CraftWidgetSmallView(entry: entry)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// MARK: - Widget Configuration
|
|
204
|
+
@main
|
|
205
|
+
struct CraftWidget: Widget {
|
|
206
|
+
let kind: String = "CraftWidget"
|
|
207
|
+
|
|
208
|
+
var body: some WidgetConfiguration {
|
|
209
|
+
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: CraftWidgetProvider()) { entry in
|
|
210
|
+
CraftWidgetEntryView(entry: entry)
|
|
211
|
+
}
|
|
212
|
+
.configurationDisplayName("{{APP_NAME}} Widget")
|
|
213
|
+
.description("Display information from {{APP_NAME}}")
|
|
214
|
+
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// MARK: - Widget Background Extension
|
|
219
|
+
extension View {
|
|
220
|
+
func widgetBackground(_ color: Color) -> some View {
|
|
221
|
+
if #available(iOS 17.0, *) {
|
|
222
|
+
return containerBackground(color, for: .widget)
|
|
223
|
+
} else {
|
|
224
|
+
return background(color)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// MARK: - Configuration Intent (placeholder)
|
|
230
|
+
class ConfigurationIntent: INIntent {
|
|
231
|
+
// Add configuration properties here
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// MARK: - Preview
|
|
235
|
+
struct CraftWidget_Previews: PreviewProvider {
|
|
236
|
+
static var previews: some View {
|
|
237
|
+
CraftWidgetEntryView(entry: CraftWidgetEntry(
|
|
238
|
+
date: Date(),
|
|
239
|
+
title: "My Widget",
|
|
240
|
+
subtitle: "Subtitle text",
|
|
241
|
+
value: "42",
|
|
242
|
+
iconName: "star.fill",
|
|
243
|
+
configuration: nil
|
|
244
|
+
))
|
|
245
|
+
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>en</string>
|
|
7
|
+
<key>CFBundleDisplayName</key>
|
|
8
|
+
<string>{{APP_NAME}}</string>
|
|
9
|
+
<key>CFBundleExecutable</key>
|
|
10
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
11
|
+
<key>CFBundleIdentifier</key>
|
|
12
|
+
<string>{{BUNDLE_ID}}</string>
|
|
13
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
14
|
+
<string>6.0</string>
|
|
15
|
+
<key>CFBundleName</key>
|
|
16
|
+
<string>{{APP_NAME}}</string>
|
|
17
|
+
<key>CFBundlePackageType</key>
|
|
18
|
+
<string>APPL</string>
|
|
19
|
+
<key>CFBundleShortVersionString</key>
|
|
20
|
+
<string>{{VERSION}}</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>{{BUILD_NUMBER}}</string>
|
|
23
|
+
<key>LSRequiresIPhoneOS</key>
|
|
24
|
+
<true/>
|
|
25
|
+
<key>UILaunchStoryboardName</key>
|
|
26
|
+
<string>LaunchScreen</string>
|
|
27
|
+
<key>UIRequiredDeviceCapabilities</key>
|
|
28
|
+
<array>
|
|
29
|
+
<string>arm64</string>
|
|
30
|
+
</array>
|
|
31
|
+
<key>UISupportedInterfaceOrientations</key>
|
|
32
|
+
<array>
|
|
33
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
34
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
35
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
36
|
+
</array>
|
|
37
|
+
<key>UIUserInterfaceStyle</key>
|
|
38
|
+
<string>{{UI_STYLE}}</string>
|
|
39
|
+
<key>UIStatusBarStyle</key>
|
|
40
|
+
<string>UIStatusBarStyleLightContent</string>
|
|
41
|
+
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
42
|
+
<false/>
|
|
43
|
+
<key>NSSpeechRecognitionUsageDescription</key>
|
|
44
|
+
<string>{{APP_NAME}} uses speech recognition for voice commands.</string>
|
|
45
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
46
|
+
<string>{{APP_NAME}} needs microphone access for voice input.</string>
|
|
47
|
+
<key>NSAppTransportSecurity</key>
|
|
48
|
+
<dict>
|
|
49
|
+
<key>NSAllowsLocalNetworking</key>
|
|
50
|
+
<true/>
|
|
51
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
52
|
+
<false/>
|
|
53
|
+
<key>NSExceptionDomains</key>
|
|
54
|
+
<dict>
|
|
55
|
+
<key>localhost</key>
|
|
56
|
+
<dict>
|
|
57
|
+
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
58
|
+
<true/>
|
|
59
|
+
<key>NSIncludesSubdomains</key>
|
|
60
|
+
<true/>
|
|
61
|
+
</dict>
|
|
62
|
+
</dict>
|
|
63
|
+
</dict>
|
|
64
|
+
</dict>
|
|
65
|
+
</plist>
|