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
package/.eslintrc.js
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# react-native-widget-extension
|
|
2
|
+
|
|
3
|
+
Expo config plugin to add widgets and live activities to a React Native app
|
|
4
|
+
|
|
5
|
+
The widgets still need to be written in Swift (I think there's no way around that). But you can simply add a folder with Swift files to a React Native project (see folder \_widgets in this repository for examples) and then add the plugin via:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx expo install react-native-widget-extension
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
And add the following config to app.json (where widgetsFolder is the path to the folder with the Swift files):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
"expo": {
|
|
15
|
+
"name": "my-app",
|
|
16
|
+
"plugins": [
|
|
17
|
+
[
|
|
18
|
+
"react-native-widget-extension",
|
|
19
|
+
{ "frequentUpdates": true, "widgetsFolder": "_widgets/PizzaDelivery" },
|
|
20
|
+
],
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then in React land, you can use the following:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import {
|
|
29
|
+
areActivitiesEnabled,
|
|
30
|
+
startActivity,
|
|
31
|
+
updateActivity,
|
|
32
|
+
endActivity,
|
|
33
|
+
} from "react-native-widget-extension";
|
|
34
|
+
|
|
35
|
+
startActivity(3, "4343", "$32.23", driverName, 47, 43);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Plugin configuration options
|
|
39
|
+
|
|
40
|
+
- frequentUpdates (boolean, default: false): Depending on this param, NSSupportsLiveActivitiesFrequentUpdates will be set
|
|
41
|
+
- widgetsFolder (string, default: "widgets"): Path from the project root to the folder containing the Swift widget files
|
|
42
|
+
- deploymentTarget (string, default: "16.2"): The minimum deployment target for the app
|
|
43
|
+
<!--
|
|
44
|
+
- moduleFileName (string, default: "Module.swift"): File within the widget folder that defines the native module
|
|
45
|
+
- attributesFileName (string): File within the widget folder that defined the widget attributes
|
|
46
|
+
-->
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
|
|
50
|
+
For a minimal example app, see the folder **example**. Example code for widgets can be found in the \*\*\_widgets\_\_ folder.
|
|
51
|
+
|
|
52
|
+
Some background on how the **PizzaDelivery** example works:
|
|
53
|
+
|
|
54
|
+
- Assets.xcassets and Info.plist: Automatically created by Xcode when you create a widget
|
|
55
|
+
- Attributes.swift: The ActivityAttributes for the Live Activity are defined here. By default, this file should be named "Attributes.swift".
|
|
56
|
+
- Module.swift: This file defined the native module that can then be used from React land to start/stop/update the live activity. By default, this file should be named "Module.swift".
|
|
57
|
+
- The rest of the folder can be Swift files that define widgets, views, etc. and can be named and divided between files however you want.
|
|
58
|
+
|
|
59
|
+
## Deployment Target
|
|
60
|
+
|
|
61
|
+
By default, this module adds a minimum deployment target of iOS 16.2, because otherwise Swift compilation fails if you try to use Live Activities. If you want to support earliert versions of iOS, you can manually set the deployment target via plugin config:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
"expo": {
|
|
65
|
+
"name": "my-app",
|
|
66
|
+
"plugins": [
|
|
67
|
+
[
|
|
68
|
+
"react-native-widget-extension",
|
|
69
|
+
{ "deploymentTarget": "14.0" },
|
|
70
|
+
],
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If you do this and you still use Live Activities in Swift, you have to make sure to guard the code that can only run on iOS 16.2 and later like this:
|
|
76
|
+
|
|
77
|
+
```swift
|
|
78
|
+
import SwiftUI
|
|
79
|
+
import WidgetKit
|
|
80
|
+
|
|
81
|
+
@main
|
|
82
|
+
struct PizzaDeliveryWidgetBundle: WidgetBundle {
|
|
83
|
+
var body: some Widget {
|
|
84
|
+
PizzaDeliveryWidgets()
|
|
85
|
+
|
|
86
|
+
if #available(iOS 16.2, *) {
|
|
87
|
+
PizzaDeliveryLiveActivity()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
and
|
|
94
|
+
|
|
95
|
+
```swift
|
|
96
|
+
@available(iOS 16.2, *)
|
|
97
|
+
struct LockScreenLiveActivityView: View {
|
|
98
|
+
...
|
|
99
|
+
}
|
|
100
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
The adventure activity configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import WidgetKit
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
struct AdventureActivityConfiguration: Widget {
|
|
12
|
+
var body: some WidgetConfiguration {
|
|
13
|
+
ActivityConfiguration(for: AdventureAttributes.self) { context in
|
|
14
|
+
AdventureLiveActivityView(
|
|
15
|
+
hero: context.attributes.hero,
|
|
16
|
+
isStale: context.isStale,
|
|
17
|
+
contentState: context.state
|
|
18
|
+
)
|
|
19
|
+
.activityBackgroundTint(Color.liveActivityBackground.opacity(0.25))
|
|
20
|
+
} dynamicIsland: { context in
|
|
21
|
+
DynamicIsland {
|
|
22
|
+
expandedContent(
|
|
23
|
+
hero: context.attributes.hero,
|
|
24
|
+
contentState: context.state,
|
|
25
|
+
isStale: context.isStale
|
|
26
|
+
)
|
|
27
|
+
} compactLeading: {
|
|
28
|
+
Avatar(hero: context.attributes.hero, includeBackground: true)
|
|
29
|
+
} compactTrailing: {
|
|
30
|
+
ProgressView(value: context.state.currentHealthLevel, total: 1) {
|
|
31
|
+
Text("\(Int(context.state.currentHealthLevel * 100))")
|
|
32
|
+
}
|
|
33
|
+
.progressViewStyle(.circular)
|
|
34
|
+
.tint(context.state.currentHealthLevel <= 0.2 ? Color.red : Color.green)
|
|
35
|
+
} minimal: {
|
|
36
|
+
ProgressView(value: context.state.currentHealthLevel, total: 1) {
|
|
37
|
+
Avatar(hero: context.attributes.hero, includeBackground: false)
|
|
38
|
+
}
|
|
39
|
+
.progressViewStyle(.circular)
|
|
40
|
+
.tint(context.state.currentHealthLevel <= 0.2 ? Color.red : Color.green)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@DynamicIslandExpandedContentBuilder
|
|
47
|
+
private func expandedContent(hero: EmojiRanger,
|
|
48
|
+
contentState: AdventureAttributes.ContentState,
|
|
49
|
+
isStale: Bool) -> DynamicIslandExpandedContent<some View> {
|
|
50
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
51
|
+
LiveActivityAvatarView(hero: hero)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
55
|
+
StatsView(
|
|
56
|
+
hero: hero,
|
|
57
|
+
isStale: isStale
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
62
|
+
|
|
63
|
+
HealthBar(currentHealthLevel: contentState.currentHealthLevel)
|
|
64
|
+
|
|
65
|
+
EventDescriptionView(
|
|
66
|
+
hero: hero,
|
|
67
|
+
contentState: contentState
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
A view that shows a list of heroes sorted by their health level.
|
|
6
|
+
*/
|
|
7
|
+
import SwiftUI
|
|
8
|
+
|
|
9
|
+
struct AllCharactersView: View {
|
|
10
|
+
|
|
11
|
+
let heros: [EmojiRanger]
|
|
12
|
+
|
|
13
|
+
init(heros: [EmojiRanger]? = EmojiRanger.availableHeros) {
|
|
14
|
+
self.heros = heros ?? EmojiRanger.availableHeros
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var body: some View {
|
|
18
|
+
VStack(spacing: 48) {
|
|
19
|
+
ForEach(heros.sorted { $0.healthLevel > $1.healthLevel }, id: \.self) { hero in
|
|
20
|
+
Link(destination: hero.url) {
|
|
21
|
+
HStack {
|
|
22
|
+
Avatar(hero: hero)
|
|
23
|
+
VStack(alignment: .leading) {
|
|
24
|
+
Text(hero.name)
|
|
25
|
+
.font(.headline)
|
|
26
|
+
.foregroundColor(.white)
|
|
27
|
+
Text("Level \(hero.level)")
|
|
28
|
+
.foregroundColor(.white)
|
|
29
|
+
HealthLevelShape(level: hero.healthLevel)
|
|
30
|
+
.frame(height: 10)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
struct AllCharactersView_Previews: PreviewProvider {
|
|
40
|
+
static var previews: some View {
|
|
41
|
+
AllCharactersView()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
The adventure attributes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#if canImport(ActivityKit)
|
|
9
|
+
|
|
10
|
+
import ActivityKit
|
|
11
|
+
|
|
12
|
+
struct AdventureAttributes: ActivityAttributes {
|
|
13
|
+
struct ContentState: Codable & Hashable {
|
|
14
|
+
let currentHealthLevel: Double
|
|
15
|
+
let eventDescription: String
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let hero: EmojiRanger
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#endif
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/*
|
|
2
|
+
See the LICENSE.txt file for this sample’s licensing information.
|
|
3
|
+
|
|
4
|
+
Abstract:
|
|
5
|
+
Details about a hero, including a name, health level, avatar, and other properties.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import AppIntents
|
|
9
|
+
import WidgetKit
|
|
10
|
+
|
|
11
|
+
struct EmojiRanger: Hashable, Codable, Identifiable {
|
|
12
|
+
|
|
13
|
+
static var typeDisplayRepresentation: TypeDisplayRepresentation = "EmojiRanger"
|
|
14
|
+
|
|
15
|
+
static let LeaderboardWidgetKind: String = "LeaderboardWidget"
|
|
16
|
+
static let EmojiRangerWidgetKind: String = "EmojiRangerWidget"
|
|
17
|
+
|
|
18
|
+
var displayRepresentation: DisplayRepresentation {
|
|
19
|
+
DisplayRepresentation(title: "\(avatar) \(name)")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let name: String
|
|
23
|
+
let avatar: String
|
|
24
|
+
let healthLevel: Double
|
|
25
|
+
let heroType: String
|
|
26
|
+
let healthRecoveryRatePerHour: Double
|
|
27
|
+
let url: URL
|
|
28
|
+
let battleCode: URL
|
|
29
|
+
let level: Int
|
|
30
|
+
let exp: Int
|
|
31
|
+
let bio: String
|
|
32
|
+
|
|
33
|
+
var id: String {
|
|
34
|
+
name
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static let panda = EmojiRanger(
|
|
38
|
+
name: "Power Panda",
|
|
39
|
+
avatar: "🐼",
|
|
40
|
+
healthLevel: 0.14,
|
|
41
|
+
heroType: "Forest Dweller",
|
|
42
|
+
healthRecoveryRatePerHour: 0.25,
|
|
43
|
+
url: URL(string: "game:///panda")!,
|
|
44
|
+
battleCode: URL(string: "game:///panda/battle")!,
|
|
45
|
+
level: 3,
|
|
46
|
+
exp: 600,
|
|
47
|
+
bio: "Power Panda loves eating bamboo shoots and leaves.")
|
|
48
|
+
|
|
49
|
+
static let egghead = EmojiRanger(
|
|
50
|
+
name: "Egghead",
|
|
51
|
+
avatar: "🦄",
|
|
52
|
+
healthLevel: 0.67,
|
|
53
|
+
heroType: "Free Ranger",
|
|
54
|
+
healthRecoveryRatePerHour: 0.22,
|
|
55
|
+
url: URL(string: "game:///egghead")!,
|
|
56
|
+
battleCode: URL(string: "game:///egghead/battle")!,
|
|
57
|
+
level: 5,
|
|
58
|
+
exp: 1000,
|
|
59
|
+
bio: "Egghead comes from the magical land of Eggopolis and flies through the air with their magnificent mane billowing.")
|
|
60
|
+
|
|
61
|
+
static let spouty = EmojiRanger(
|
|
62
|
+
name: "Spouty",
|
|
63
|
+
avatar: "🐳",
|
|
64
|
+
healthLevel: 0.99,
|
|
65
|
+
heroType: "Deep Sea Goer",
|
|
66
|
+
healthRecoveryRatePerHour: 0.59,
|
|
67
|
+
url: URL(string: "game:///spouty")!,
|
|
68
|
+
battleCode: URL(string: "game:///spouty/battle")!,
|
|
69
|
+
level: 50,
|
|
70
|
+
exp: 20_000,
|
|
71
|
+
bio: "Spouty rises from the depths to bring joy and laughter to everyone. They are best friends with Octo.")
|
|
72
|
+
|
|
73
|
+
static let availableHeros = [panda, egghead, spouty]
|
|
74
|
+
|
|
75
|
+
func hash(into hasher: inout Hasher) {
|
|
76
|
+
hasher.combine(name)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var fullHealthDate: Date {
|
|
80
|
+
let healthNeeded = min(1 - healthLevel, 1)
|
|
81
|
+
let hoursUntilFullHealth = healthNeeded / healthRecoveryRatePerHour
|
|
82
|
+
let minutesUntilFullHealth = (hoursUntilFullHealth * 60)
|
|
83
|
+
let date = Calendar.current.date(byAdding: .minute, value: Int(minutesUntilFullHealth), to: Date())
|
|
84
|
+
|
|
85
|
+
return date ?? Date()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var injuryDate: Date {
|
|
89
|
+
let totalInjurySeconds = 3600 / healthRecoveryRatePerHour
|
|
90
|
+
let injuryDate = fullHealthDate.advanced(by: -totalInjurySeconds)
|
|
91
|
+
return injuryDate
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static func heroFromName(name: String?) -> EmojiRanger {
|
|
95
|
+
guard let hero = (allHeros).first(where: { (hero) -> Bool in
|
|
96
|
+
return hero.name == name
|
|
97
|
+
}) else {
|
|
98
|
+
return .panda
|
|
99
|
+
}
|
|
100
|
+
return hero
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static func heroFromURL(url: URL) -> EmojiRanger? {
|
|
104
|
+
guard let hero = (allHeros).first(where: { (hero) -> Bool in
|
|
105
|
+
return hero.url == url
|
|
106
|
+
}) else {
|
|
107
|
+
return .panda
|
|
108
|
+
}
|
|
109
|
+
return hero
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static let session = ImageURLProtocol.urlSession()
|
|
113
|
+
|
|
114
|
+
static func loadLeaderboardData(completion:@escaping ([EmojiRanger]?, Error?) -> Void) {
|
|
115
|
+
// Save a faux API to the temporary directory and fetch it.
|
|
116
|
+
// In your app, you fetch it from a real API.
|
|
117
|
+
do {
|
|
118
|
+
let responseURL = FileManager.default.temporaryDirectory.appendingPathComponent("userData.json")
|
|
119
|
+
|
|
120
|
+
try fauxResponse.data(using: .utf8)?.write(to: responseURL)
|
|
121
|
+
session.dataTask(with: responseURL) { (data, response, error) in
|
|
122
|
+
if let playerData = data {
|
|
123
|
+
do {
|
|
124
|
+
let hero = try JSONDecoder().decode([EmojiRanger].self, from: playerData)
|
|
125
|
+
.sorted { $0.healthLevel > $1.healthLevel }
|
|
126
|
+
completion(hero, error)
|
|
127
|
+
} catch {
|
|
128
|
+
completion(nil, error)
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
completion(nil, error)
|
|
132
|
+
}
|
|
133
|
+
}.resume()
|
|
134
|
+
} catch {
|
|
135
|
+
completion(nil, error)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static let appGroup = "<App Group Here>"
|
|
141
|
+
|
|
142
|
+
static func setLastSelectedHero(heroName: String) {
|
|
143
|
+
UserDefaults(suiteName: appGroup)?.setValue(heroName, forKey: "hero")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static func getLastSelectedHero() -> EmojiRanger? {
|
|
147
|
+
guard let name = UserDefaults(suiteName: appGroup)?.value(forKey: "hero") as? String else {
|
|
148
|
+
return nil
|
|
149
|
+
}
|
|
150
|
+
return EmojiRanger.heroFromName(name: name)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static func superchargeHeros() {
|
|
154
|
+
var val = herosAreSupercharged()
|
|
155
|
+
val.toggle()
|
|
156
|
+
UserDefaults(suiteName: appGroup)?.setValue(val, forKey: "supercharged")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
static func herosAreSupercharged() -> Bool {
|
|
160
|
+
guard let areCharged = UserDefaults(suiteName: appGroup)?.value(forKey: "supercharged") as? Bool else {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
return areCharged
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let fauxResponse =
|
|
168
|
+
"""
|
|
169
|
+
[
|
|
170
|
+
{
|
|
171
|
+
"name": "Power Panda",
|
|
172
|
+
"avatar": "🐼",
|
|
173
|
+
"healthLevel": 0.99,
|
|
174
|
+
"heroType": "Forest Dweller",
|
|
175
|
+
"healthRecoveryRatePerHour": 0.25
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "Egghead",
|
|
179
|
+
"avatar": "🦄",
|
|
180
|
+
"healthLevel": 0.84,
|
|
181
|
+
"heroType": "Free Ranger",
|
|
182
|
+
"healthRecoveryRatePerHour": 0.22
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "Spouty",
|
|
186
|
+
"avatar": "🐳",
|
|
187
|
+
"healthLevel": 0.72,
|
|
188
|
+
"heroType": "Deep Sea Goer",
|
|
189
|
+
"healthRecoveryRatePerHour": 0.29
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
extension EmojiRanger {
|
|
195
|
+
static let spook = EmojiRanger(
|
|
196
|
+
name: "Mr. Spook",
|
|
197
|
+
avatar: "💀",
|
|
198
|
+
healthLevel: 0.14,
|
|
199
|
+
heroType: "Calcium Lover",
|
|
200
|
+
healthRecoveryRatePerHour: 0.25,
|
|
201
|
+
url: URL(string: "game:///spook")!,
|
|
202
|
+
battleCode: URL(string: "game:///spook/battle")!,
|
|
203
|
+
level: 13,
|
|
204
|
+
exp: 2640,
|
|
205
|
+
bio: "Loves dancing, spooking, and playing their trumpet 🎺.")
|
|
206
|
+
|
|
207
|
+
static let cake = EmojiRanger(
|
|
208
|
+
name: "Cake",
|
|
209
|
+
avatar: "🎂",
|
|
210
|
+
healthLevel: 0.67,
|
|
211
|
+
heroType: "Literally Cake",
|
|
212
|
+
healthRecoveryRatePerHour: 0.22,
|
|
213
|
+
url: URL(string: "game:///cake")!,
|
|
214
|
+
battleCode: URL(string: "game:///cake/battle")!,
|
|
215
|
+
level: 15,
|
|
216
|
+
exp: 3121,
|
|
217
|
+
bio: """
|
|
218
|
+
• 1 cake mix
|
|
219
|
+
• 2 tbsp butter
|
|
220
|
+
• 4 large eggs
|
|
221
|
+
• 1 cup semi-sweet chocolate chips
|
|
222
|
+
""")
|
|
223
|
+
|
|
224
|
+
static let octo = EmojiRanger(
|
|
225
|
+
name: "Octo",
|
|
226
|
+
avatar: "🐙",
|
|
227
|
+
healthLevel: 0.83,
|
|
228
|
+
heroType: "Etymology Aficionado",
|
|
229
|
+
healthRecoveryRatePerHour: 0.29,
|
|
230
|
+
url: URL(string: "game:///octo")!,
|
|
231
|
+
battleCode: URL(string: "game:///octo/battle")!,
|
|
232
|
+
level: 43,
|
|
233
|
+
exp: 86_463,
|
|
234
|
+
bio: "Can give eight hugs simultaneously. They are best friends with Spouty.")
|
|
235
|
+
|
|
236
|
+
static let additionalHeros = [spook, cake, octo]
|
|
237
|
+
|
|
238
|
+
static let allHeros = EmojiRanger.availableHeros + EmojiRanger.additionalHeros
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
extension DateFormatter {
|
|
242
|
+
static let emojiFormatter: DateFormatter = {
|
|
243
|
+
let formatter = DateFormatter()
|
|
244
|
+
formatter.dateStyle = .short
|
|
245
|
+
formatter.timeStyle = .medium
|
|
246
|
+
return formatter
|
|
247
|
+
}()
|
|
248
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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>INEnums</key>
|
|
6
|
+
<array/>
|
|
7
|
+
<key>INIntentDefinitionModelVersion</key>
|
|
8
|
+
<string>1.2</string>
|
|
9
|
+
<key>INIntentDefinitionNamespace</key>
|
|
10
|
+
<string>88xZPY</string>
|
|
11
|
+
<key>INIntentDefinitionSystemVersion</key>
|
|
12
|
+
<string>22D49</string>
|
|
13
|
+
<key>INIntentDefinitionToolsBuildVersion</key>
|
|
14
|
+
<string>14E222b</string>
|
|
15
|
+
<key>INIntentDefinitionToolsVersion</key>
|
|
16
|
+
<string>14.3</string>
|
|
17
|
+
<key>INIntents</key>
|
|
18
|
+
<array>
|
|
19
|
+
<dict>
|
|
20
|
+
<key>INIntentCategory</key>
|
|
21
|
+
<string>information</string>
|
|
22
|
+
<key>INIntentConfigurable</key>
|
|
23
|
+
<true/>
|
|
24
|
+
<key>INIntentDescription</key>
|
|
25
|
+
<string>Select Hero</string>
|
|
26
|
+
<key>INIntentDescriptionID</key>
|
|
27
|
+
<string>meoGMm</string>
|
|
28
|
+
<key>INIntentEligibleForWidgets</key>
|
|
29
|
+
<true/>
|
|
30
|
+
<key>INIntentIneligibleForSuggestions</key>
|
|
31
|
+
<true/>
|
|
32
|
+
<key>INIntentLastParameterTag</key>
|
|
33
|
+
<integer>12</integer>
|
|
34
|
+
<key>INIntentManagedParameterCombinations</key>
|
|
35
|
+
<dict>
|
|
36
|
+
<key>heroName</key>
|
|
37
|
+
<dict>
|
|
38
|
+
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
|
|
39
|
+
<true/>
|
|
40
|
+
<key>INIntentParameterCombinationUpdatesLinked</key>
|
|
41
|
+
<true/>
|
|
42
|
+
</dict>
|
|
43
|
+
</dict>
|
|
44
|
+
<key>INIntentName</key>
|
|
45
|
+
<string>EmojiRangerSelection</string>
|
|
46
|
+
<key>INIntentParameters</key>
|
|
47
|
+
<array>
|
|
48
|
+
<dict>
|
|
49
|
+
<key>INIntentParameterConfigurable</key>
|
|
50
|
+
<true/>
|
|
51
|
+
<key>INIntentParameterDisplayName</key>
|
|
52
|
+
<string>Selected Hero</string>
|
|
53
|
+
<key>INIntentParameterDisplayNameID</key>
|
|
54
|
+
<string>uHu7jj</string>
|
|
55
|
+
<key>INIntentParameterDisplayPriority</key>
|
|
56
|
+
<integer>1</integer>
|
|
57
|
+
<key>INIntentParameterMetadata</key>
|
|
58
|
+
<dict>
|
|
59
|
+
<key>INIntentParameterMetadataCapitalization</key>
|
|
60
|
+
<string>Sentences</string>
|
|
61
|
+
<key>INIntentParameterMetadataDefaultValueID</key>
|
|
62
|
+
<string>HhCW9t</string>
|
|
63
|
+
</dict>
|
|
64
|
+
<key>INIntentParameterName</key>
|
|
65
|
+
<string>heroName</string>
|
|
66
|
+
<key>INIntentParameterPromptDialogs</key>
|
|
67
|
+
<array>
|
|
68
|
+
<dict>
|
|
69
|
+
<key>INIntentParameterPromptDialogCustom</key>
|
|
70
|
+
<true/>
|
|
71
|
+
<key>INIntentParameterPromptDialogType</key>
|
|
72
|
+
<string>Configuration</string>
|
|
73
|
+
</dict>
|
|
74
|
+
<dict>
|
|
75
|
+
<key>INIntentParameterPromptDialogCustom</key>
|
|
76
|
+
<true/>
|
|
77
|
+
<key>INIntentParameterPromptDialogType</key>
|
|
78
|
+
<string>Primary</string>
|
|
79
|
+
</dict>
|
|
80
|
+
</array>
|
|
81
|
+
<key>INIntentParameterSupportsDynamicEnumeration</key>
|
|
82
|
+
<true/>
|
|
83
|
+
<key>INIntentParameterTag</key>
|
|
84
|
+
<integer>12</integer>
|
|
85
|
+
<key>INIntentParameterType</key>
|
|
86
|
+
<string>String</string>
|
|
87
|
+
</dict>
|
|
88
|
+
</array>
|
|
89
|
+
<key>INIntentResponse</key>
|
|
90
|
+
<dict>
|
|
91
|
+
<key>INIntentResponseCodes</key>
|
|
92
|
+
<array>
|
|
93
|
+
<dict>
|
|
94
|
+
<key>INIntentResponseCodeName</key>
|
|
95
|
+
<string>success</string>
|
|
96
|
+
<key>INIntentResponseCodeSuccess</key>
|
|
97
|
+
<true/>
|
|
98
|
+
</dict>
|
|
99
|
+
<dict>
|
|
100
|
+
<key>INIntentResponseCodeName</key>
|
|
101
|
+
<string>failure</string>
|
|
102
|
+
</dict>
|
|
103
|
+
</array>
|
|
104
|
+
</dict>
|
|
105
|
+
<key>INIntentTitle</key>
|
|
106
|
+
<string>Emoji Ranger Selection</string>
|
|
107
|
+
<key>INIntentTitleID</key>
|
|
108
|
+
<string>VT2aX1</string>
|
|
109
|
+
<key>INIntentType</key>
|
|
110
|
+
<string>Custom</string>
|
|
111
|
+
<key>INIntentVerb</key>
|
|
112
|
+
<string>View</string>
|
|
113
|
+
</dict>
|
|
114
|
+
</array>
|
|
115
|
+
<key>INTypes</key>
|
|
116
|
+
<array/>
|
|
117
|
+
</dict>
|
|
118
|
+
</plist>
|