react-native-ios-widget 0.0.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/.eslintrc.js +5 -0
- package/README.md +100 -0
- package/_widgets/EmojiRanger/AdventureActivityConfiguration.swift +71 -0
- package/_widgets/EmojiRanger/AllCharactersView.swift +43 -0
- package/_widgets/EmojiRanger/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/_widgets/EmojiRanger/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/_widgets/EmojiRanger/Assets.xcassets/Contents.json +6 -0
- package/_widgets/EmojiRanger/Assets.xcassets/WidgetBackground.colorset/Contents.json +11 -0
- package/_widgets/EmojiRanger/Attributes.swift +21 -0
- package/_widgets/EmojiRanger/EmojiRanger.swift +248 -0
- package/_widgets/EmojiRanger/EmojiRangersWidget.intentdefinition +118 -0
- package/_widgets/EmojiRanger/EmojiRangersWidget.swift +189 -0
- package/_widgets/EmojiRanger/EmojiRangersWidgetBundle.swift +21 -0
- package/_widgets/EmojiRanger/ImageURLProtocol.swift +66 -0
- package/_widgets/EmojiRanger/Info.plist +11 -0
- package/_widgets/EmojiRanger/LeaderboardWidget.swift +93 -0
- package/_widgets/EmojiRanger/Module.swift +94 -0
- package/_widgets/PizzaDelivery/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/_widgets/PizzaDelivery/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/_widgets/PizzaDelivery/Assets.xcassets/Contents.json +6 -0
- package/_widgets/PizzaDelivery/Assets.xcassets/WidgetBackground.colorset/Contents.json +11 -0
- package/_widgets/PizzaDelivery/Attributes.swift +15 -0
- package/_widgets/PizzaDelivery/Info.plist +11 -0
- package/_widgets/PizzaDelivery/LiveActivity.swift +108 -0
- package/_widgets/PizzaDelivery/Module.swift +94 -0
- package/_widgets/PizzaDelivery/WidgetBundle.swift +13 -0
- package/_widgets/PizzaDelivery/Widgets.swift +59 -0
- package/app.plugin.js +1 -0
- package/build/ExpoWidget.d.ts +3 -0
- package/build/ExpoWidget.d.ts.map +1 -0
- package/build/ExpoWidget.js +3 -0
- package/build/ExpoWidget.js.map +1 -0
- package/build/ReactNativeWidgetExtensionModule.d.ts +3 -0
- package/build/ReactNativeWidgetExtensionModule.d.ts.map +1 -0
- package/build/ReactNativeWidgetExtensionModule.js +5 -0
- package/build/ReactNativeWidgetExtensionModule.js.map +1 -0
- package/build/Widget.d.ts +2 -0
- package/build/Widget.d.ts.map +1 -0
- package/build/Widget.js +19 -0
- package/build/Widget.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/ios/ReactNativeWidgetExtension.podspec +27 -0
- package/package.json +48 -0
- package/plugin/build/android/index.d.ts +10 -0
- package/plugin/build/android/index.js +18 -0
- package/plugin/build/android/withWidgetAndroidManifest.d.ts +5 -0
- package/plugin/build/android/withWidgetAndroidManifest.js +55 -0
- package/plugin/build/android/withWidgetAppBuildGradle.d.ts +7 -0
- package/plugin/build/android/withWidgetAppBuildGradle.js +20 -0
- package/plugin/build/android/withWidgetProjectBuildGradle.d.ts +8 -0
- package/plugin/build/android/withWidgetProjectBuildGradle.js +21 -0
- package/plugin/build/android/withWidgetSourceCode.d.ts +5 -0
- package/plugin/build/android/withWidgetSourceCode.js +51 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +34 -0
- package/plugin/build/ios/index.d.ts +9 -0
- package/plugin/build/ios/index.js +34 -0
- package/plugin/build/ios/withConfig.d.ts +6 -0
- package/plugin/build/ios/withConfig.js +55 -0
- package/plugin/build/ios/withPodfile.d.ts +5 -0
- package/plugin/build/ios/withPodfile.js +83 -0
- package/plugin/build/ios/withWidgetExtensionEntitlements.d.ts +7 -0
- package/plugin/build/ios/withWidgetExtensionEntitlements.js +47 -0
- package/plugin/build/ios/withXcode.d.ts +7 -0
- package/plugin/build/ios/withXcode.js +79 -0
- package/plugin/build/lib/getWidgetExtensionEntitlements.d.ts +5 -0
- package/plugin/build/lib/getWidgetExtensionEntitlements.js +20 -0
- package/plugin/build/lib/getWidgetFiles.d.ts +9 -0
- package/plugin/build/lib/getWidgetFiles.js +117 -0
- package/plugin/build/withConfig.d.ts +6 -0
- package/plugin/build/withConfig.js +55 -0
- package/plugin/build/withPodfile.d.ts +5 -0
- package/plugin/build/withPodfile.js +83 -0
- package/plugin/build/withWidgetExtensionEntitlements.d.ts +7 -0
- package/plugin/build/withWidgetExtensionEntitlements.js +47 -0
- package/plugin/build/withXcode.d.ts +7 -0
- package/plugin/build/withXcode.js +79 -0
- package/plugin/build/xcode/addBuildPhases.d.ts +13 -0
- package/plugin/build/xcode/addBuildPhases.js +48 -0
- package/plugin/build/xcode/addPbxGroup.d.ts +6 -0
- package/plugin/build/xcode/addPbxGroup.js +25 -0
- package/plugin/build/xcode/addProductFile.d.ts +5 -0
- package/plugin/build/xcode/addProductFile.js +22 -0
- package/plugin/build/xcode/addTargetDependency.d.ts +4 -0
- package/plugin/build/xcode/addTargetDependency.js +15 -0
- package/plugin/build/xcode/addToPbxNativeTargetSection.d.ts +24 -0
- package/plugin/build/xcode/addToPbxNativeTargetSection.js +30 -0
- package/plugin/build/xcode/addToPbxProjectSection.d.ts +4 -0
- package/plugin/build/xcode/addToPbxProjectSection.js +15 -0
- package/plugin/build/xcode/addXCConfigurationList.d.ts +8 -0
- package/plugin/build/xcode/addXCConfigurationList.js +62 -0
- package/plugin/src/index.ts +52 -0
- package/plugin/src/lib/getWidgetExtensionEntitlements.ts +33 -0
- package/plugin/src/lib/getWidgetFiles.ts +109 -0
- package/plugin/src/withConfig.ts +71 -0
- package/plugin/src/withPodfile.ts +73 -0
- package/plugin/src/withWidgetExtensionEntitlements.ts +30 -0
- package/plugin/src/withXcode.ts +73 -0
- package/plugin/src/xcode/addBuildPhases.ts +83 -0
- package/plugin/src/xcode/addPbxGroup.ts +46 -0
- package/plugin/src/xcode/addProductFile.ts +25 -0
- package/plugin/src/xcode/addTargetDependency.ts +17 -0
- package/plugin/src/xcode/addToPbxNativeTargetSection.ts +46 -0
- package/plugin/src/xcode/addToPbxProjectSection.ts +23 -0
- package/plugin/src/xcode/addXCConfigurationList.ts +83 -0
- package/plugin/tsconfig.json +16 -0
- package/src/ReactNativeWidgetExtensionModule.ts +5 -0
- package/src/Widget.ts +25 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
A widget that shows the avatar for a single hero.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import WidgetKit
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
struct SimpleEntry: TimelineEntry {
|
|
12
|
+
public let date: Date
|
|
13
|
+
let relevance: TimelineEntryRelevance?
|
|
14
|
+
let hero: EmojiRanger
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
struct PlaceholderView: View {
|
|
18
|
+
var body: some View {
|
|
19
|
+
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, hero: .spouty))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
extension View {
|
|
24
|
+
func widgetBackground() -> some View {
|
|
25
|
+
if #available(iOSApplicationExtension 17.0, *) {
|
|
26
|
+
return containerBackground(for: .widget) {
|
|
27
|
+
Color.gameBackgroundColor
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
return background {
|
|
31
|
+
Color.gameBackgroundColor
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
struct EmojiRangerWidgetEntryView: View {
|
|
38
|
+
var entry: SimpleEntry
|
|
39
|
+
|
|
40
|
+
@Environment(\.widgetFamily) var family
|
|
41
|
+
|
|
42
|
+
@AppStorage("supercharged", store: UserDefaults(suiteName: EmojiRanger.appGroup))
|
|
43
|
+
var supercharged: Bool = EmojiRanger.herosAreSupercharged()
|
|
44
|
+
|
|
45
|
+
var body: some View {
|
|
46
|
+
switch family {
|
|
47
|
+
case .accessoryCircular:
|
|
48
|
+
ProgressView(timerInterval: entry.hero.injuryDate...entry.hero.fullHealthDate,
|
|
49
|
+
countsDown: false,
|
|
50
|
+
label: { Text(entry.hero.name) },
|
|
51
|
+
currentValueLabel: {
|
|
52
|
+
Avatar(hero: entry.hero, includeBackground: false)
|
|
53
|
+
})
|
|
54
|
+
.progressViewStyle(.circular)
|
|
55
|
+
|
|
56
|
+
case .accessoryRectangular:
|
|
57
|
+
HStack(alignment: .center, spacing: 0) {
|
|
58
|
+
VStack(alignment: .leading) {
|
|
59
|
+
Text(entry.hero.name)
|
|
60
|
+
.font(.headline)
|
|
61
|
+
.widgetAccentable()
|
|
62
|
+
Text("Level \(entry.hero.level)")
|
|
63
|
+
Text(entry.hero.fullHealthDate, style: .timer)
|
|
64
|
+
}.frame(maxWidth: .infinity, alignment: .leading)
|
|
65
|
+
Avatar(hero: entry.hero, includeBackground: false)
|
|
66
|
+
}
|
|
67
|
+
.widgetBackground()
|
|
68
|
+
|
|
69
|
+
case .accessoryInline:
|
|
70
|
+
ViewThatFits {
|
|
71
|
+
Text("\(entry.hero.name) is healing, ready in \(entry.hero.fullHealthDate, style: .relative)")
|
|
72
|
+
Text("\(entry.hero.name) ready in \(entry.hero.fullHealthDate, style: .relative)")
|
|
73
|
+
Text("\(entry.hero.name) \(entry.hero.fullHealthDate, style: .timer)")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case .systemSmall:
|
|
77
|
+
AvatarView(entry.hero)
|
|
78
|
+
.widgetURL(entry.hero.url)
|
|
79
|
+
.foregroundColor(.white)
|
|
80
|
+
.widgetBackground()
|
|
81
|
+
.widgetURL(entry.hero.url)
|
|
82
|
+
|
|
83
|
+
case .systemLarge:
|
|
84
|
+
VStack {
|
|
85
|
+
HStack(alignment: .top) {
|
|
86
|
+
AvatarView(entry.hero)
|
|
87
|
+
.foregroundColor(.white)
|
|
88
|
+
Text(entry.hero.bio)
|
|
89
|
+
.foregroundColor(.white)
|
|
90
|
+
}
|
|
91
|
+
.padding()
|
|
92
|
+
#if os(iOS)
|
|
93
|
+
if #available(iOSApplicationExtension 17.0, *) {
|
|
94
|
+
Button(intent: SuperCharge()) {
|
|
95
|
+
Text("⚡️")
|
|
96
|
+
.lineLimit(1)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
#endif
|
|
100
|
+
}
|
|
101
|
+
.widgetBackground()
|
|
102
|
+
.widgetURL(entry.hero.url)
|
|
103
|
+
case .systemMedium:
|
|
104
|
+
HStack(alignment: .top) {
|
|
105
|
+
AvatarView(entry.hero)
|
|
106
|
+
.foregroundColor(.white)
|
|
107
|
+
Text(entry.hero.bio)
|
|
108
|
+
.foregroundColor(.white)
|
|
109
|
+
}
|
|
110
|
+
.widgetBackground()
|
|
111
|
+
.widgetURL(entry.hero.url)
|
|
112
|
+
default:
|
|
113
|
+
AvatarView(entry.hero)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
struct EmojiRangerWidget: Widget {
|
|
120
|
+
|
|
121
|
+
func makeWidgetConfiguration() -> some WidgetConfiguration {
|
|
122
|
+
#if os(watchOS)
|
|
123
|
+
return IntentConfiguration(kind: EmojiRanger.EmojiRangerWidgetKind,
|
|
124
|
+
intent: EmojiRangerSelectionIntent.self,
|
|
125
|
+
provider: SiriKitWatchIntentProvider()) { entry in
|
|
126
|
+
EmojiRangerWidgetEntryView(entry: entry)
|
|
127
|
+
}
|
|
128
|
+
.supportedFamilies(supportedFamilies)
|
|
129
|
+
#else
|
|
130
|
+
if #available(iOS 17.0, macOS 14.0, *) {
|
|
131
|
+
return AppIntentConfiguration(kind: EmojiRanger.EmojiRangerWidgetKind,
|
|
132
|
+
intent: EmojiRangerSelection.self,
|
|
133
|
+
provider: AppIntentProvider()) { entry in
|
|
134
|
+
EmojiRangerWidgetEntryView(entry: entry)
|
|
135
|
+
}
|
|
136
|
+
.supportedFamilies(supportedFamilies)
|
|
137
|
+
} else {
|
|
138
|
+
return IntentConfiguration(kind: EmojiRanger.EmojiRangerWidgetKind,
|
|
139
|
+
intent: EmojiRangerSelectionIntent.self,
|
|
140
|
+
provider: SiriKitIntentProvider()) { entry in
|
|
141
|
+
EmojiRangerWidgetEntryView(entry: entry)
|
|
142
|
+
}
|
|
143
|
+
.supportedFamilies(supportedFamilies)
|
|
144
|
+
}
|
|
145
|
+
#endif
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private var supportedFamilies: [WidgetFamily] {
|
|
149
|
+
#if os(watchOS)
|
|
150
|
+
[.accessoryCircular,
|
|
151
|
+
.accessoryRectangular, .accessoryInline]
|
|
152
|
+
#else
|
|
153
|
+
[.accessoryCircular,
|
|
154
|
+
.accessoryRectangular, .accessoryInline,
|
|
155
|
+
.systemSmall, .systemMedium, .systemLarge]
|
|
156
|
+
#endif
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public var body: some WidgetConfiguration {
|
|
160
|
+
makeWidgetConfiguration()
|
|
161
|
+
.configurationDisplayName("Ranger Detail")
|
|
162
|
+
.description("See your favorite ranger.")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
struct Widget_Previews: PreviewProvider {
|
|
167
|
+
static var previews: some View {
|
|
168
|
+
Group {
|
|
169
|
+
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, hero: .spouty))
|
|
170
|
+
.previewContext(WidgetPreviewContext(family: .accessoryCircular))
|
|
171
|
+
.previewDisplayName("Circular")
|
|
172
|
+
|
|
173
|
+
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, hero: .spouty))
|
|
174
|
+
.previewContext(WidgetPreviewContext(family: .accessoryRectangular))
|
|
175
|
+
.previewDisplayName("Rectangular")
|
|
176
|
+
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, hero: .spouty))
|
|
177
|
+
.previewContext(WidgetPreviewContext(family: .accessoryInline))
|
|
178
|
+
.previewDisplayName("Inline")
|
|
179
|
+
|
|
180
|
+
#if os(iOS)
|
|
181
|
+
|
|
182
|
+
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, hero: .spouty))
|
|
183
|
+
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
|
184
|
+
EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, hero: .spouty))
|
|
185
|
+
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
|
186
|
+
#endif
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
The widget bundle.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import WidgetKit
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
@main
|
|
12
|
+
struct EmojiRangersWidgetBundle: WidgetBundle {
|
|
13
|
+
var body: some Widget {
|
|
14
|
+
EmojiRangerWidget()
|
|
15
|
+
LeaderboardWidget()
|
|
16
|
+
#if canImport(ActivityKit)
|
|
17
|
+
AdventureActivityConfiguration()
|
|
18
|
+
#endif
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
A protocol that defines methods to load images.
|
|
6
|
+
*/
|
|
7
|
+
import Foundation
|
|
8
|
+
|
|
9
|
+
class ImageURLProtocol: URLProtocol {
|
|
10
|
+
|
|
11
|
+
var cancelledOrComplete: Bool = false
|
|
12
|
+
var block: DispatchWorkItem!
|
|
13
|
+
|
|
14
|
+
private static let queue = DispatchSerialQueue(label: "com.apple.imageLoaderURLProtocol")
|
|
15
|
+
|
|
16
|
+
override class func canInit(with request: URLRequest) -> Bool {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
|
21
|
+
return request
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class override func requestIsCacheEquivalent(_ aRequest: URLRequest, to bRequest: URLRequest) -> Bool {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
final override func startLoading() {
|
|
29
|
+
guard let reqURL = request.url, let urlClient = client else {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
block = DispatchWorkItem(block: {
|
|
34
|
+
if self.cancelledOrComplete == false {
|
|
35
|
+
let fileURL = URL(fileURLWithPath: reqURL.path)
|
|
36
|
+
|
|
37
|
+
if let data = try? Data(contentsOf: fileURL) {
|
|
38
|
+
urlClient.urlProtocol(self, didLoad: data)
|
|
39
|
+
urlClient.urlProtocolDidFinishLoading(self)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
self.cancelledOrComplete = true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
ImageURLProtocol.queue.asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 500 * NSEC_PER_MSEC), execute: block)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
final override func stopLoading() {
|
|
49
|
+
ImageURLProtocol.queue.async {
|
|
50
|
+
if self.cancelledOrComplete == false, let cancelBlock = self.block {
|
|
51
|
+
cancelBlock.cancel()
|
|
52
|
+
|
|
53
|
+
self.cancelledOrComplete = true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static func urlSession() -> URLSession {
|
|
59
|
+
let config = URLSessionConfiguration.ephemeral
|
|
60
|
+
|
|
61
|
+
config.protocolClasses = [ImageURLProtocol.classForCoder()]
|
|
62
|
+
|
|
63
|
+
return URLSession(configuration: config)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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>NSExtension</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>NSExtensionPointIdentifier</key>
|
|
8
|
+
<string>com.apple.widgetkit-extension</string>
|
|
9
|
+
</dict>
|
|
10
|
+
</dict>
|
|
11
|
+
</plist>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
A widget that shows a leaderboard of all available heroes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import WidgetKit
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
struct LeaderboardProvider: TimelineProvider {
|
|
12
|
+
|
|
13
|
+
public typealias Entry = LeaderboardEntry
|
|
14
|
+
|
|
15
|
+
func placeholder(in context: Context) -> LeaderboardEntry {
|
|
16
|
+
return LeaderboardEntry(date: Date(), heros: EmojiRanger.availableHeros)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func getSnapshot(in context: Context, completion: @escaping (LeaderboardEntry) -> Void) {
|
|
20
|
+
let entry = LeaderboardEntry(date: Date(), heros: EmojiRanger.availableHeros)
|
|
21
|
+
completion(entry)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func getTimeline(in context: Context, completion: @escaping (Timeline<LeaderboardEntry>) -> Void) {
|
|
25
|
+
EmojiRanger.loadLeaderboardData { (heros, error) in
|
|
26
|
+
guard let heros = heros else {
|
|
27
|
+
let timeline = Timeline(entries: [LeaderboardEntry(date: Date(), heros: EmojiRanger.availableHeros)], policy: .atEnd)
|
|
28
|
+
|
|
29
|
+
completion(timeline)
|
|
30
|
+
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
let timeline = Timeline(entries: [LeaderboardEntry(date: Date(), heros: heros)], policy: .atEnd)
|
|
34
|
+
completion(timeline)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
struct LeaderboardEntry: TimelineEntry {
|
|
40
|
+
public let date: Date
|
|
41
|
+
var heros: [EmojiRanger]?
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
struct LeaderboardPlaceholderView: View {
|
|
45
|
+
var body: some View {
|
|
46
|
+
LeaderboardWidgetEntryView(entry: LeaderboardEntry(date: Date(), heros: nil))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
struct LeaderboardWidgetEntryView: View {
|
|
51
|
+
var entry: LeaderboardProvider.Entry
|
|
52
|
+
|
|
53
|
+
var body: some View {
|
|
54
|
+
AllCharactersView(heros: entry.heros)
|
|
55
|
+
.padding()
|
|
56
|
+
.widgetBackground()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
struct LeaderboardWidget: Widget {
|
|
61
|
+
|
|
62
|
+
private static var supportedFamilies: [WidgetFamily] {
|
|
63
|
+
#if os(iOS)
|
|
64
|
+
if #available(iOS 15, *) {
|
|
65
|
+
return [.systemLarge, .systemExtraLarge]
|
|
66
|
+
} else {
|
|
67
|
+
return [.systemLarge]
|
|
68
|
+
}
|
|
69
|
+
#else
|
|
70
|
+
return []
|
|
71
|
+
#endif
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public var body: some WidgetConfiguration {
|
|
75
|
+
StaticConfiguration(kind: EmojiRanger.LeaderboardWidgetKind, provider: LeaderboardProvider()) { entry in
|
|
76
|
+
LeaderboardWidgetEntryView(entry: entry)
|
|
77
|
+
}
|
|
78
|
+
.configurationDisplayName("Ranger Leaderboard")
|
|
79
|
+
.description("See all the rangers.")
|
|
80
|
+
.supportedFamilies(LeaderboardWidget.supportedFamilies)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
struct LeaderboardWidget_Previews: PreviewProvider {
|
|
85
|
+
static var previews: some View {
|
|
86
|
+
Group {
|
|
87
|
+
#if os(iOS)
|
|
88
|
+
LeaderboardWidgetEntryView(entry: LeaderboardEntry(date: Date(), heros: nil))
|
|
89
|
+
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
|
90
|
+
#endif
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import ActivityKit
|
|
3
|
+
|
|
4
|
+
internal class MissingCurrentWindowSceneException: Exception {
|
|
5
|
+
override var reason: String {
|
|
6
|
+
"Cannot determine the current window scene in which to present the modal for requesting a review."
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public class ReactNativeWidgetExtensionModule: Module {
|
|
11
|
+
// Each module class must implement the definition function. The definition consists of components
|
|
12
|
+
// that describes the module's functionality and behavior.
|
|
13
|
+
// See https://docs.expo.dev/modules/module-api for more details about available components.
|
|
14
|
+
public func definition() -> ModuleDefinition {
|
|
15
|
+
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
|
|
16
|
+
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
|
|
17
|
+
// The module will be accessible from `requireNativeModule('ReactNativeAppClip')` in JavaScript.
|
|
18
|
+
Name("ReactNativeWidgetExtension")
|
|
19
|
+
|
|
20
|
+
// Defines event names that the module can send to JavaScript.
|
|
21
|
+
Events("onChange")
|
|
22
|
+
|
|
23
|
+
Function("areActivitiesEnabled") { () -> Bool in
|
|
24
|
+
let logger = Logger()
|
|
25
|
+
logger.info("areActivitiesEnabled()")
|
|
26
|
+
|
|
27
|
+
if #available(iOS 16.2, *) {
|
|
28
|
+
return ActivityAuthorizationInfo().areActivitiesEnabled
|
|
29
|
+
} else {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Function("startActivity") { (numberOfPizzas: Int, totalAmount: String, orderNumber: String, driverName: String, minutes: Int, seconds: Int) -> Void in
|
|
35
|
+
let logger = Logger()
|
|
36
|
+
logger.info("startActivity()")
|
|
37
|
+
|
|
38
|
+
if #available(iOS 16.2, *) {
|
|
39
|
+
var future = Calendar.current.date(byAdding: .minute, value: (Int(minutes) ?? 0), to: Date())!
|
|
40
|
+
future = Calendar.current.date(byAdding: .second, value: (Int(seconds) ?? 0), to: future)!
|
|
41
|
+
let date = Date.now...future
|
|
42
|
+
let initialContentState = AdventureAttributes.ContentState(driverName: driverName, deliveryTimer: date)
|
|
43
|
+
let activityAttributes = AdventureAttributes(numberOfPizzas: numberOfPizzas, totalAmount: totalAmount, orderNumber: orderNumber)
|
|
44
|
+
|
|
45
|
+
let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 30, to: Date())!)
|
|
46
|
+
|
|
47
|
+
do {
|
|
48
|
+
let deliveryActivity = try Activity.request(attributes: activityAttributes, content: activityContent)
|
|
49
|
+
logger.info("Requested a pizza delivery Live Activity \(String(describing: deliveryActivity.id)).")
|
|
50
|
+
} catch (let error) {
|
|
51
|
+
logger.info("Error requesting pizza delivery Live Activity \(error.localizedDescription).")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Function("updateActivity") { (driverName: String, minutes: Int, seconds: Int) -> Void in
|
|
57
|
+
let logger = Logger()
|
|
58
|
+
logger.info("updateActivity()")
|
|
59
|
+
|
|
60
|
+
if #available(iOS 16.2, *) {
|
|
61
|
+
var future = Calendar.current.date(byAdding: .minute, value: (Int(minutes) ?? 0), to: Date())!
|
|
62
|
+
future = Calendar.current.date(byAdding: .second, value: (Int(seconds) ?? 0), to: future)!
|
|
63
|
+
let date = Date.now...future
|
|
64
|
+
let updatedDeliveryStatus = AdventureAttributes.PizzaDeliveryStatus(driverName: driverName, deliveryTimer: date)
|
|
65
|
+
let alertConfiguration = AlertConfiguration(title: "Delivery update", body: "Your pizza order will arrive soon.", sound: .default)
|
|
66
|
+
let updatedContent = ActivityContent(state: updatedDeliveryStatus, staleDate: nil)
|
|
67
|
+
|
|
68
|
+
Task {
|
|
69
|
+
for activity in Activity<AdventureAttributes>.activities {
|
|
70
|
+
await activity.update(updatedContent, alertConfiguration: alertConfiguration)
|
|
71
|
+
logger.info("Updated the Live Activity: \(activity.id)")
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Function("endActivity") { (driverName: String) -> Void in
|
|
78
|
+
let logger = Logger()
|
|
79
|
+
logger.info("endActivity()")
|
|
80
|
+
|
|
81
|
+
if #available(iOS 16.2, *) {
|
|
82
|
+
let finalDeliveryStatus = AdventureAttributes.PizzaDeliveryStatus(driverName: driverName, deliveryTimer: Date.now...Date())
|
|
83
|
+
let finalContent = ActivityContent(state: finalDeliveryStatus, staleDate: nil)
|
|
84
|
+
|
|
85
|
+
Task {
|
|
86
|
+
for activity in Activity<AdventureAttributes>.activities {
|
|
87
|
+
await activity.end(finalContent, dismissalPolicy: .default)
|
|
88
|
+
logger.info("Ending the Live Activity: \(activity.id)")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ActivityKit
|
|
3
|
+
|
|
4
|
+
struct Attributes: ActivityAttributes {
|
|
5
|
+
public typealias PizzaDeliveryStatus = ContentState
|
|
6
|
+
|
|
7
|
+
public struct ContentState: Codable, Hashable {
|
|
8
|
+
var driverName: String
|
|
9
|
+
var deliveryTimer: ClosedRange<Date>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
var numberOfPizzas: Int
|
|
13
|
+
var totalAmount: String
|
|
14
|
+
var orderNumber: String
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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>NSExtension</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>NSExtensionPointIdentifier</key>
|
|
8
|
+
<string>com.apple.widgetkit-extension</string>
|
|
9
|
+
</dict>
|
|
10
|
+
</dict>
|
|
11
|
+
</plist>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import ActivityKit
|
|
2
|
+
import WidgetKit
|
|
3
|
+
import SwiftUI
|
|
4
|
+
|
|
5
|
+
@available(iOS 16.2, *)
|
|
6
|
+
struct PizzaDeliveryLiveActivity: Widget {
|
|
7
|
+
var body: some WidgetConfiguration {
|
|
8
|
+
ActivityConfiguration(for: Attributes.self) { context in
|
|
9
|
+
LockScreenLiveActivityView(context: context)
|
|
10
|
+
} dynamicIsland: { context in
|
|
11
|
+
DynamicIsland {
|
|
12
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
13
|
+
Label("\(context.attributes.numberOfPizzas) Pizzas", systemImage: "bag")
|
|
14
|
+
// .foregroundColor(.indigo)
|
|
15
|
+
.font(.caption)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
19
|
+
Label {
|
|
20
|
+
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
|
|
21
|
+
.monospacedDigit()
|
|
22
|
+
.frame(maxWidth: .infinity, alignment: .center)
|
|
23
|
+
} icon: {
|
|
24
|
+
Image(systemName: "timer")
|
|
25
|
+
// .foregroundColor(.red)
|
|
26
|
+
}
|
|
27
|
+
.font(.caption)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
DynamicIslandExpandedRegion(.center) {
|
|
31
|
+
Text("\(context.state.driverName) is on their way!")
|
|
32
|
+
.lineLimit(1)
|
|
33
|
+
.font(.caption)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
37
|
+
Button {
|
|
38
|
+
// Deep link into your app.
|
|
39
|
+
} label: {
|
|
40
|
+
Label("Call driver", systemImage: "phone")
|
|
41
|
+
}
|
|
42
|
+
.foregroundColor(.green)
|
|
43
|
+
}
|
|
44
|
+
} compactLeading: {
|
|
45
|
+
Label {
|
|
46
|
+
Text("\(context.attributes.numberOfPizzas) Pizzas")
|
|
47
|
+
} icon: {
|
|
48
|
+
Image(systemName: "bag")
|
|
49
|
+
// .foregroundColor(.indigo)
|
|
50
|
+
}
|
|
51
|
+
// .font(.caption2)
|
|
52
|
+
} compactTrailing: {
|
|
53
|
+
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
|
|
54
|
+
.monospacedDigit()
|
|
55
|
+
// .multilineTextAlignment(.center)
|
|
56
|
+
// .frame(width: 40)
|
|
57
|
+
.font(.caption)
|
|
58
|
+
} minimal: {
|
|
59
|
+
VStack(alignment: .center) {
|
|
60
|
+
Image(systemName: "timer")
|
|
61
|
+
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
|
|
62
|
+
// .multilineTextAlignment(.center)
|
|
63
|
+
.monospacedDigit()
|
|
64
|
+
.font(.caption)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
.keylineTint(.cyan)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@available(iOS 16.2, *)
|
|
73
|
+
struct LockScreenLiveActivityView: View {
|
|
74
|
+
let context: ActivityViewContext<Attributes>
|
|
75
|
+
|
|
76
|
+
var body: some View {
|
|
77
|
+
VStack {
|
|
78
|
+
Spacer()
|
|
79
|
+
Text("\(context.state.driverName) is on their way with your pizza!")
|
|
80
|
+
Spacer()
|
|
81
|
+
HStack {
|
|
82
|
+
Spacer()
|
|
83
|
+
Label {
|
|
84
|
+
Text("\(context.attributes.numberOfPizzas) Pizzas")
|
|
85
|
+
} icon: {
|
|
86
|
+
Image(systemName: "bag")
|
|
87
|
+
.foregroundColor(.indigo)
|
|
88
|
+
}
|
|
89
|
+
.font(.title2)
|
|
90
|
+
Spacer()
|
|
91
|
+
Label {
|
|
92
|
+
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
|
|
93
|
+
.multilineTextAlignment(.center)
|
|
94
|
+
.frame(width: 75)
|
|
95
|
+
.monospacedDigit()
|
|
96
|
+
} icon: {
|
|
97
|
+
Image(systemName: "timer")
|
|
98
|
+
.foregroundColor(.indigo)
|
|
99
|
+
}
|
|
100
|
+
.font(.title2)
|
|
101
|
+
Spacer()
|
|
102
|
+
}
|
|
103
|
+
Spacer()
|
|
104
|
+
}
|
|
105
|
+
.activitySystemActionForegroundColor(.cyan)
|
|
106
|
+
.activityBackgroundTint(.cyan)
|
|
107
|
+
}
|
|
108
|
+
}
|