Skip to content

Commit 2c869e6

Browse files
committedNov 10, 2022
Widgets
1 parent b8b83b7 commit 2c869e6

30 files changed

+752
-184
lines changed
 

‎.swiftlint.yml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
disabled_rules:
2+
- empty_enum_arguments
3+
4+
identifier_name:
5+
min_length: # only min_length
6+
error: 4 # only error
7+
excluded: # excluded via string array
8+
- app
9+
- acc
10+
- id
11+
- URL
12+
- url
13+
- GlobalAPIKey
14+
reporter: "xcode"

‎Assets/Logo.afdesign

255 Bytes
Binary file not shown.

‎Assets/Logo.png

68 Bytes
Loading

‎Assets/Logo.psd

1.21 KB
Binary file not shown.

‎OneKDay.xcodeproj/project.pbxproj

+243-8
Large diffs are not rendered by default.
Loading

‎OneKDay/HealthData/HealthKitSupport.swift

-71
This file was deleted.

‎OneKDay/Info.plist

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5-
<key>BGTaskSchedulerPermittedIdentifiers</key>
5+
<key>INIntentsRestrictedWhileLocked</key>
66
<array>
7-
<string>com.hbiede.OneKDay.notify</string>
7+
<string>StepCountConfigurationIntent</string>
88
</array>
9-
<key>UIBackgroundModes</key>
9+
<key>INIntentsSupported</key>
1010
<array>
11-
<string>processing</string>
11+
<string>StepCountConfigurationIntent</string>
1212
</array>
13-
<key>privacy</key>
14-
<string></string>
1513
</dict>
1614
</plist>

‎OneKDay/OneKDay.entitlements

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5-
<key>aps-environment</key>
6-
<string>development</string>
75
<key>com.apple.developer.healthkit</key>
86
<true/>
97
<key>com.apple.developer.healthkit.access</key>

‎OneKDay/Views/ContentView.swift

+1-13
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ struct ContentView: View {
125125

126126
func getBarGraphStyle(for entry: Double) -> some ShapeStyle {
127127
if currentMetricIndex == 0 {
128-
return Int(entry) >= stepGoal ? Color.green : Color.red
128+
return getGoalComparisonColor(Int(entry), goal: stepGoal)
129129
} else {
130130
let metricList = metricCounts[metricOptions[currentMetricIndex], default: []]
131131
let min = metricList.min { a, b in
@@ -134,8 +134,6 @@ struct ContentView: View {
134134
let max = metricList.max { a, b in
135135
a.metric < b.metric
136136
}?.metric ?? 0.5
137-
print(min)
138-
print(max)
139137
let gap = 1 / 3 * (max - min)
140138
let lowerBound = min + gap
141139
let upperBound = max - gap
@@ -150,16 +148,6 @@ struct ContentView: View {
150148
}
151149
}
152150

153-
func formatDate(_ date: Date) -> String {
154-
let is12HourClock = NSLocale.current.hourCycle == .oneToTwelve
155-
let formatter: DateFormatter = {
156-
let temp = DateFormatter()
157-
temp.dateFormat = is12HourClock ? "MMM d, h:mm a" : "MMM d, hh:mm"
158-
return temp
159-
}()
160-
return formatter.string(from: date)
161-
}
162-
163151
func setValuesFromDefault() {
164152
let stepGoalDefault = userDefaults.integer(forKey: STEP_GOAL_KEY)
165153
if stepGoalDefault == 0 {

‎OneKDay/Views/OneKDayApp.swift

+20-71
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,41 @@
22
// OneKDayApp.swift
33
// OneKDay
44
//
5-
// Created by Elijah Biede on 11/8/22.
5+
// Created by Hundter Biede on 11/8/22.
66
//
77

8-
import BackgroundTasks
98
import HealthKit
10-
import UserNotifications
119
import SwiftUI
10+
import WidgetKit
1211

1312
@main
1413
struct OneKDayApp: App {
1514
private let userDefaults = UserDefaults()
1615
private let notificationCenter = UNUserNotificationCenter.current()
16+
17+
@State private var hasCheckedHealthAccess = false
18+
@State private var canReadHealthData = false
1719

20+
@ViewBuilder
1821
var body: some Scene {
1922
WindowGroup {
20-
ContentView()
21-
.onAppear {
22-
notificationCenter.requestAuthorization(options: [.alert, .provisional, .sound]) { granted, _ in
23-
if granted {
24-
print("User allowed alerts")
25-
} else {
26-
print("User denied alerts")
23+
if canReadHealthData {
24+
ContentView()
25+
} else if hasCheckedHealthAccess {
26+
Text("Enable access to health data to use OneK Day")
27+
} else {
28+
VStack {}
29+
.onAppear {
30+
WidgetCenter.shared.reloadTimelines(ofKind: STEP_COUNT_WIDGET_KIND)
31+
HealthData.requestHealthDataAccessIfNeeded { success in
32+
print("didLoadHealthData: \(success)")
33+
hasCheckedHealthAccess.toggle()
34+
if success {
35+
canReadHealthData.toggle()
36+
}
2737
}
2838
}
29-
BGTaskScheduler.shared.register(forTaskWithIdentifier: BACKGROUND_ID, using: .main) { task in
30-
// swiftlint:disable:next force_cast
31-
handleBackgroundUpdate(task: task as! BGAppRefreshTask)
32-
}
33-
requestAppRefresh()
34-
HealthData.requestHealthDataAccessIfNeeded { success in
35-
print("didLoadHealthData: \(success)")
36-
}
37-
let content = UNMutableNotificationContent()
38-
content.title = "Get Stepping!"
39-
content.body = "Get your \(1000) steps in the next \(15) minutes!"
40-
let request = UNNotificationRequest(
41-
identifier: "stepGoalNotAchieved",
42-
content: content,
43-
trigger: nil
44-
)
45-
notificationCenter.add(request)
46-
}
47-
}
48-
}
49-
50-
func handleBackgroundUpdate(task: BGAppRefreshTask) {
51-
let components = Calendar.current.dateComponents([.hour, .minute], from: Date())
52-
let contentTest = UNMutableNotificationContent()
53-
contentTest.title = "TEST: Get Stepping!"
54-
contentTest.body = "Get your \(1000) steps in the next \(components.hour!) minutes!"
55-
let request1 = UNNotificationRequest(identifier: "testNotifSteps", content: contentTest, trigger: nil)
56-
notificationCenter.add(request1)
57-
58-
// Only run from 8 AM to 8 PM
59-
if components.hour! >= 8 && components.hour! < 20 {
60-
HealthData.getHourlyMetricCount(for: .stepCount) { result in
61-
let stepGoal = userDefaults.integer(forKey: STEP_GOAL_KEY)
62-
63-
let minutesEarly = 60 - components.minute!
64-
if result.isEmpty || (Int(result[0].metric) < minutesEarly && Int(result[0].metric) > 0) {
65-
let content = UNMutableNotificationContent()
66-
content.title = "Get Stepping!"
67-
content.body = "Get your \(stepGoal) steps in the next \(minutesEarly) minutes!"
68-
let request = UNNotificationRequest(
69-
identifier: "stepGoalNotAchieved",
70-
content: content,
71-
trigger: nil
72-
)
73-
notificationCenter.add(request)
74-
}
7539
}
7640
}
77-
requestAppRefresh()
78-
}
79-
80-
func requestAppRefresh() {
81-
let components = Calendar.current.dateComponents([.hour], from: Date())
82-
let request = BGAppRefreshTaskRequest(identifier: BACKGROUND_ID)
83-
request.earliestBeginDate = Date(
84-
// 15 minutes in the future, unless it's between 8 PM or before 8 AM, in which case 60 minutes
85-
timeIntervalSinceNow: components.hour! >= 8 && components.hour! < 20 ? 900 : 3600
86-
)
87-
do {
88-
try BGTaskScheduler.shared.submit(request)
89-
} catch {
90-
print("Could not schedule app refresh: \(error)")
91-
}
9241
}
9342
}

‎OneKDay/Views/SettingsSheet.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SettingsSheet.swift
33
// OneKDay
44
//
5-
// Created by Elijah Biede on 11/8/22.
5+
// Created by Hundter Biede on 11/8/22.
66
//
77

88
import Foundation

‎Shared/ChartUtils.swift

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// ChartUtils.swift
3+
// OneKDay
4+
//
5+
// Created by Hundter Biede on 11/9/22.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
11+
func getGoalComparisonColor(_ entry: Int, goal: Int) -> Color {
12+
return entry >= goal ? Color.green : Color.red
13+
}
14+
15+
func formatDate(_ date: Date) -> String {
16+
let is12HourClock = NSLocale.current.hourCycle == .oneToTwelve
17+
let formatter: DateFormatter = {
18+
let temp = DateFormatter()
19+
temp.dateFormat = is12HourClock ? "MMM d, h:mm a" : "MMM d, hh:mm"
20+
return temp
21+
}()
22+
return formatter.string(from: date)
23+
}

‎OneKDay/HealthData/HealthData.swift ‎Shared/HealthData/HealthData.swift

+24-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/*
2-
See LICENSE folder for this sample’s licensing information.
3-
4-
Abstract:
5-
A collection of HealthKit properties, functions, and utilities.
6-
*/
2+
* A modified version of a file included in an Apple example project
3+
*
4+
* See LICENSE folder for this sample’s licensing information.
5+
*
6+
* Abstract:
7+
* A collection of HealthKit properties, functions, and utilities.
8+
*/
79

810
import Foundation
911
import HealthKit
@@ -119,7 +121,7 @@ class HealthData {
119121
if let myResults = results {
120122
var stepsArray: [MetricEntry] = []
121123
myResults.enumerateStatistics(from: startDate, to: endDate) { statistics, _ in
122-
if let quantity = getStatisticsQuantity(for: statistics, with: .cumulativeSum) {
124+
if let quantity = statistics.sumQuantity() {
123125
stepsArray.append(
124126
MetricEntry(
125127
metric: quantity.doubleValue(
@@ -128,7 +130,8 @@ class HealthData {
128130
)!
129131
),
130132
startDate: statistics.startDate,
131-
endDate: statistics.endDate
133+
endDate: statistics.endDate,
134+
type: identifier
132135
)
133136
)
134137
}
@@ -140,3 +143,17 @@ class HealthData {
140143
healthStore.execute(query)
141144
}
142145
}
146+
147+
func getSampleType(for identifier: String) -> HKSampleType? {
148+
if let quantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier(rawValue: identifier)) {
149+
return quantityType
150+
}
151+
152+
if let categoryType = HKCategoryType.categoryType(forIdentifier: HKCategoryTypeIdentifier(rawValue: identifier)) {
153+
return categoryType
154+
}
155+
156+
return nil
157+
}
158+
159+

‎OneKDay/HealthData/HealthKitSupport+Strings.swift ‎Shared/HealthData/HealthKitSupport+Strings.swift

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/*
2-
See LICENSE folder for this sample’s licensing information.
3-
4-
Abstract:
5-
A collection of utility functions used for displaying strings related to HealthKit.
6-
*/
2+
* A modified version of a file included in an Apple example project
3+
*
4+
* See LICENSE folder for this sample’s licensing information.
5+
*
6+
* Abstract:
7+
* A collection of utility functions used for displaying strings related to HealthKit.
8+
*/
79

810
import Foundation
911
import HealthKit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
CareKit is licensed under the BSD license, see https://github.com/carekit-apple/CareKit/blob/master/LICENSE for more information.
2+
3+
CareKit License
4+
5+
Copyright (c) 2016, Apple Inc. All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification,
8+
are permitted provided that the following conditions are met:
9+
10+
1. Redistributions of source code must retain the above copyright notice, this
11+
list of conditions and the following disclaimer.
12+
13+
2. Redistributions in binary form must reproduce the above copyright notice,
14+
this list of conditions and the following disclaimer in the documentation and/or
15+
other materials provided with the distribution.
16+
17+
3. Neither the name of the copyright holder(s) nor the names of any contributors
18+
may be used to endorse or promote products derived from this software without
19+
specific prior written permission. No license is granted to the trademarks of
20+
the copyright holders even if such marks are included in this software.
21+
22+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
26+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+

‎Shared/HealthData/LICENSE/LICENSE.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CareKit is a dependency in this project and is licensed under the BSD license, see the ACKNOWLEDGMENTS.txt file for more information.
2+
3+
Copyright © 2020 Apple Inc.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10+

‎OneKDay/Types.swift ‎Shared/Types.swift

+4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
//
77

88
import Foundation
9+
import HealthKit
910

1011
// swiftlint:disable identifier_name
1112
let BACKGROUND_ID = "com.hbiede.OneKDay.notify"
1213
let NOTIFICATION_UUID = "notificationUUID"
14+
let STEP_COUNT_WIDGET_KIND = "StepCountWidget"
1315
let STEP_GOAL_KEY = "stepGoal"
1416
// swiftlint:enable identifier_name
1517

@@ -19,4 +21,6 @@ struct MetricEntry {
1921
var startDate: Date
2022

2123
var endDate: Date
24+
25+
var type: HKQuantityTypeIdentifier
2226
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"platform" : "ios",
6+
"size" : "1024x1024"
7+
}
8+
],
9+
"info" : {
10+
"author" : "xcode",
11+
"version" : 1
12+
}
13+
}

‎Widget/Assets.xcassets/Contents.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}

‎Widget/Info.plist

+11
Original file line numberDiff line numberDiff line change
@@ -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>

‎Widget/Widget.intentdefinition

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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>21G83</string>
13+
<key>INIntentDefinitionToolsBuildVersion</key>
14+
<string>14B47b</string>
15+
<key>INIntentDefinitionToolsVersion</key>
16+
<string>14.1</string>
17+
<key>INIntents</key>
18+
<array>
19+
<dict>
20+
<key>INIntentCategory</key>
21+
<string>information</string>
22+
<key>INIntentDescriptionID</key>
23+
<string>tVvJ9c</string>
24+
<key>INIntentEligibleForWidgets</key>
25+
<true/>
26+
<key>INIntentIneligibleForSuggestions</key>
27+
<true/>
28+
<key>INIntentName</key>
29+
<string>StepCountConfiguration</string>
30+
<key>INIntentResponse</key>
31+
<dict>
32+
<key>INIntentResponseCodes</key>
33+
<array>
34+
<dict>
35+
<key>INIntentResponseCodeName</key>
36+
<string>success</string>
37+
<key>INIntentResponseCodeSuccess</key>
38+
<true/>
39+
</dict>
40+
<dict>
41+
<key>INIntentResponseCodeName</key>
42+
<string>failure</string>
43+
</dict>
44+
</array>
45+
</dict>
46+
<key>INIntentTitle</key>
47+
<string>StepCountConfiguration</string>
48+
<key>INIntentTitleID</key>
49+
<string>gpCwrM</string>
50+
<key>INIntentType</key>
51+
<string>Custom</string>
52+
<key>INIntentVerb</key>
53+
<string>View</string>
54+
</dict>
55+
</array>
56+
<key>INTypes</key>
57+
<array/>
58+
</dict>
59+
</plist>

‎Widget/Widget/FullWidget.swift

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// FullWidget.swift
3+
// WidgetExtension
4+
//
5+
// Created by Hundter Biede on 11/9/22.
6+
//
7+
8+
import Charts
9+
import Foundation
10+
import SwiftUI
11+
import WidgetKit
12+
13+
struct FullWidget: View {
14+
@State var metrics: [MetricEntry] = []
15+
16+
var body: some View {
17+
if metrics.isEmpty {
18+
Text(formattedValue(
19+
0,
20+
typeIdentifier: .stepCount
21+
)!
22+
)
23+
.font(.largeTitle)
24+
} else {
25+
HStack {
26+
ZStack(alignment: .center) {
27+
Circle()
28+
.foregroundColor(.accentColor)
29+
.frame(maxWidth: 48)
30+
Text("\(Int(metrics[metrics.count - 1].metric))")
31+
.font(.title2)
32+
.multilineTextAlignment(.center)
33+
}
34+
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 0))
35+
Chart(metrics, id: \.startDate) {
36+
BarMark(
37+
x: .value("Time", $0.startDate, unit: .hour),
38+
y: .value("Steps", $0.metric)
39+
)
40+
.foregroundStyle(Color.accentColor)
41+
.accessibilityLabel(formatDate($0.startDate))
42+
.accessibilityValue(formattedValue(
43+
$0.metric,
44+
typeIdentifier: .stepCount
45+
) ?? "X")
46+
}
47+
.padding(EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16))
48+
}
49+
}
50+
}
51+
}

‎Widget/Widget/SmallWidget.swift

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// SmallWidget.swift
3+
// WidgetExtension
4+
//
5+
// Created by Hundter Biede on 11/9/22.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
import WidgetKit
11+
12+
import Foundation
13+
import SwiftUI
14+
import WidgetKit
15+
16+
struct SmallWidget: View {
17+
@State var stepCount: Int = 1
18+
19+
@ViewBuilder
20+
var body: some View {
21+
ZStack {
22+
Circle()
23+
.foregroundColor(Color.green)
24+
VStack {
25+
Text("")
26+
.accessibilityHidden(true)
27+
.font(.title3)
28+
Text("\(stepCount)")
29+
.multilineTextAlignment(.center)
30+
.foregroundColor(.white)
31+
.font(.system(size: 500))
32+
.minimumScaleFactor(0.01)
33+
Text(getUnitSuffix(for: .count())!.capitalized)
34+
.foregroundColor(.white)
35+
.font(.title3)
36+
}
37+
.padding(EdgeInsets(top: 16, leading: 0, bottom: 16, trailing: 0))
38+
}
39+
.padding(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
40+
.accessibilityLabel(formattedValue(
41+
Double(stepCount),
42+
typeIdentifier: .stepCount
43+
)!
44+
)
45+
}
46+
}
47+

‎Widget/Widget/WidgetBaseView.swift

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// WidgetBaseView.swift
3+
// WidgetExtension
4+
//
5+
// Created by Hundter Biede on 11/9/22.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
import WidgetKit
11+
12+
struct StepCountWidgetEntryView : View {
13+
@Environment(\.widgetFamily) var widgetFamily
14+
var entry: Provider.Entry
15+
16+
@ViewBuilder
17+
var body: some View {
18+
let metrics = entry.metrics
19+
let lastCount = metrics.isEmpty ? 0.0 : metrics[metrics.count - 1].metric
20+
21+
switch widgetFamily {
22+
case .accessoryCircular:
23+
ZStack {
24+
Circle()
25+
.foregroundColor(.black)
26+
Text("\(Int(lastCount))")
27+
.font(.largeTitle)
28+
}
29+
case .accessoryRectangular, .accessoryInline:
30+
Text(formattedValue(
31+
lastCount,
32+
typeIdentifier: .stepCount
33+
)!
34+
)
35+
.font(.largeTitle)
36+
case .systemSmall:
37+
SmallWidget(stepCount: metrics.isEmpty ? 0 : Int(lastCount))
38+
default:
39+
FullWidget(metrics: metrics)
40+
}
41+
}
42+
}
43+
44+
struct Widget_Previews: PreviewProvider {
45+
static var previews: some View {
46+
StepCountWidgetEntryView(
47+
entry: Provider.Entry(
48+
date: Date(),
49+
configuration: Provider.Intent(),
50+
metrics: []
51+
)
52+
)
53+
.previewContext(WidgetPreviewContext(family: .systemSmall))
54+
}
55+
}
56+
57+
struct StepCountWidget: Widget {
58+
let kind: String = STEP_COUNT_WIDGET_KIND
59+
60+
var body: some WidgetConfiguration {
61+
IntentConfiguration(kind: kind, intent: Provider.Intent.self, provider: Provider()) { entry in
62+
StepCountWidgetEntryView(entry: entry)
63+
}
64+
.configurationDisplayName("OneK Day")
65+
.description("Get your steps in for the hour")
66+
.supportedFamilies([
67+
.accessoryCircular,
68+
.accessoryInline,
69+
.accessoryRectangular,
70+
.systemSmall,
71+
.systemMedium,
72+
.systemLarge,
73+
])
74+
}
75+
}

‎Widget/WidgetBundle.swift

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// WidgetBundle.swift
3+
// WidgetExtension
4+
//
5+
// Created by Hundter Biede on 11/9/22.
6+
//
7+
8+
import WidgetKit
9+
import SwiftUI
10+
11+
@main
12+
struct OneKDayWidgetBundle: WidgetBundle {
13+
var body: some Widget {
14+
StepCountWidget()
15+
}
16+
}

‎Widget/WidgetProvider.swift

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// WidgetProvider.swift
3+
// WidgetExtension
4+
//
5+
// Created by Hundter Biede on 11/9/22.
6+
//
7+
8+
import Intents
9+
import WidgetKit
10+
11+
struct Provider: IntentTimelineProvider {
12+
13+
typealias Entry = StepCountEntry
14+
15+
typealias Intent = StepCountConfigurationIntent
16+
17+
let stepGoal = UserDefaults().integer(forKey: STEP_GOAL_KEY)
18+
19+
func placeholder(in context: Context) -> StepCountEntry {
20+
print(stepGoal)
21+
return Entry(date: Date(), configuration: Intent(), metrics: [])
22+
}
23+
24+
func getSnapshot(for configuration: Intent, in context: Context, completion: @escaping (Entry) -> ()) {
25+
print(stepGoal)
26+
getStepCounts { metrics in
27+
completion(
28+
Entry(date: Date(), configuration: configuration, metrics: metrics)
29+
)
30+
}
31+
}
32+
33+
func getTimeline(for configuration: Intent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
34+
print(stepGoal)
35+
getStepCounts { metrics in
36+
completion(
37+
Timeline(
38+
entries: [
39+
Entry(date: Date(), configuration: configuration, metrics: metrics)
40+
],
41+
// Clear every 10 minutes
42+
policy: .after(Date(timeIntervalSinceNow: 600))
43+
)
44+
)
45+
}
46+
}
47+
48+
func getStepCounts(completion: @escaping ([MetricEntry]) -> Void) {
49+
HealthData.getHourlyMetricCount(for: .stepCount, completion: completion)
50+
}
51+
}
52+
53+
struct StepCountEntry: TimelineEntry {
54+
let date: Date
55+
let configuration: Provider.Intent
56+
let metrics: [MetricEntry]
57+
}

‎WidgetExtension.entitlements

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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>com.apple.developer.healthkit</key>
6+
<true/>
7+
<key>com.apple.developer.healthkit.access</key>
8+
<array/>
9+
<key>com.apple.developer.healthkit.background-delivery</key>
10+
<true/>
11+
</dict>
12+
</plist>

0 commit comments

Comments
 (0)
Please sign in to comment.