diff --git a/Source Code/Brushr/Brushr watchOS Watch App/ContentView.swift b/Source Code/Brushr/Brushr watchOS Watch App/ContentView.swift index f01eeb4..4e961df 100644 --- a/Source Code/Brushr/Brushr watchOS Watch App/ContentView.swift +++ b/Source Code/Brushr/Brushr watchOS Watch App/ContentView.swift @@ -8,8 +8,8 @@ import SwiftUI struct ContentView: View { - @State var customMinuteSelection = 1 - @State var customSecondSelection = 30 + @AppStorage("customMinuteSelection") var customMinuteSelection = 1 + @AppStorage("customSecondSelection") var customSecondSelection = 30 @State var showingCurrentTimer = false @State var disabledCustomStart = false @State var disabledResume = true @@ -22,6 +22,8 @@ struct ContentView: View { @Environment(\.scenePhase) var scenePhase @State var isActive = true @StateObject private var healthKitManager = HealthKitManager() + @State var showingCustomTimer = false + @State var showingCustomCurrentTimer = false var body: some View { NavigationStack { ScrollView { @@ -336,6 +338,192 @@ struct ContentView: View { .onAppear() { healthKitManager.requestAuthorization() } + .onOpenURL { url in + guard url.scheme == "brushr" else { return } + if url == URL(string: "brushr://30sec") { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + timeRemaining = 30 + startTime = 30 + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + showingCurrentTimer = true + } else if url == URL(string: "brushr://1min") { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + timeRemaining = 60 + startTime = 60 + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + showingCurrentTimer = true + } else if url == URL(string: "brushr://2min") { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + timeRemaining = 120 + startTime = 120 + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + showingCurrentTimer = true + } else if url == URL(string: "brushr://3min") { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + timeRemaining = 180 + startTime = 180 + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + showingCurrentTimer = true + } else if url == URL(string: "brushr://4min") { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + timeRemaining = 240 + startTime = 240 + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + showingCurrentTimer = true + } else if url == URL(string: "brushr://5min") { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + timeRemaining = 300 + startTime = 300 + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + showingCurrentTimer = true + } else if url == URL(string: "brushr://custom") { + showingCustomTimer = true + } else { + print("URL Error") + } + } + .sheet(isPresented: $showingCustomTimer) { + customTimer + } + } + var customTimer: some View { + ScrollView { + VStack { + Spacer() + Text("Minutes") + Stepper("\(customMinuteSelection)", value: $customMinuteSelection, in: 0...10) + Text("Seconds") + Stepper("\(customSecondSelection)", value: $customSecondSelection, in: 0...59) + Button(action: { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + WKInterfaceDevice.current().play(.start) + customMinutes = customMinuteSelection * 60 + timeRemaining = customMinutes + customSecondSelection + startTime = customMinutes + customSecondSelection + disabledResume = true + disabledPause = false + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + //showingCustomTimer = false + showingCustomCurrentTimer = true + //showingCurrentTimer = true + }) { + Text("Start Timer") + .bold() + } + .buttonStyle(.borderedProminent) + .disabled(disabledCustomStart) + .padding() + Spacer() + } + .padding(.horizontal) + } + .sheet(isPresented: $showingCustomCurrentTimer) { + ScrollView { + VStack { + if timeRemaining <= 59 { + Text("\(formattedTimeSeconds) Seconds") + .bold() + .font(.title3) + } else { + Text("\(formattedTimeSeconds)") + .bold() + .font(.largeTitle) + } + ProgressView(value: Double(timeRemaining), total: Double(startTime)) + .progressViewStyle(.linear) + .padding(.bottom) + Button(action: { + self.timer.upstream.connect().cancel() + WKInterfaceDevice.current().play(.stop) + disabledPause = true + disabledResume = false + }) { + Text("Pause") + .bold() + } + .buttonStyle(.borderedProminent) + .disabled(disabledPause) + .padding(.bottom) + Button(action: { + WKInterfaceDevice.current().play(.start) + timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() + disabledPause = false + disabledResume = true + }) { + Text("Resume") + .bold() + } + .buttonStyle(.borderedProminent) + .disabled(disabledResume) + .padding(.bottom) + Button(action: { + WKInterfaceDevice.current().play(.failure) + showingCustomCurrentTimer = false + }) { + Text("End") + .bold() + } + .buttonStyle(.borderedProminent) + .padding(.bottom) + } + .padding(.horizontal) + .onChange(of: scenePhase) { newPhase in + if newPhase == .active { + isActive = true + } else { + isActive = false + } + } + .onReceive(timer) { time in + guard isActive else { return } + if timeRemaining > 0 { + timeRemaining -= 1 + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.minute, .second] + formatter.unitsStyle = .positional + formattedTimeSeconds = formatter.string(from: TimeInterval(timeRemaining))! + } else { + WKInterfaceDevice.current().play(.success) + healthKitManager.saveToothbrushingEvent(timeInSeconds: startTime) + showingCustomCurrentTimer = false + } + } + } + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) + } } } diff --git a/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AccentColor.colorset/Contents.json b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..d90b065 --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,15 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "universal", + "reference" : "systemOrangeColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AppIcon.appiconset/Brushr App Icon.png b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AppIcon.appiconset/Brushr App Icon.png new file mode 100644 index 0000000..0b2bf36 Binary files /dev/null and b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AppIcon.appiconset/Brushr App Icon.png differ diff --git a/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AppIcon.appiconset/Contents.json b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..1a0296a --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Brushr App Icon.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/Contents.json b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source Code/Brushr/Brushr watchOS Widget/Brushr_watchOS_Widget.swift b/Source Code/Brushr/Brushr watchOS Widget/Brushr_watchOS_Widget.swift new file mode 100644 index 0000000..5539f19 --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Brushr_watchOS_Widget.swift @@ -0,0 +1,535 @@ +// +// Brushr_watchOS_Widget.swift +// Brushr watchOS Widget +// +// Created by Mark Howard on 01/08/2023. +// + +import WidgetKit +import SwiftUI + +struct Provider: TimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date()) + } + + func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { + let entry = SimpleEntry(date: Date()) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + var entries: [SimpleEntry] = [] + + let currentDate = Date() + for hourOffset in 0 ..< 24 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + let entry = SimpleEntry(date: entryDate) + entries.append(entry) + } + + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date +} + +struct Brushr_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("Custom") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("Custom") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A Custom Timer") + } + Spacer() + } + case .accessoryInline: + Label("Custom", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_30_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("00:30") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("00:30") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A 00:30 Timer") + } + Spacer() + } + case .accessoryInline: + Label("00:30", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_1_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("01:00") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("01:00") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A 01:00 Timer") + } + Spacer() + } + case .accessoryInline: + Label("01:00", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_2_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("02:00") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("02:00") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A 02:00 Timer") + } + Spacer() + } + case .accessoryInline: + Label("02:00", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_3_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("03:00") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("03:00") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A 03:00 Timer") + } + Spacer() + } + case .accessoryInline: + Label("03:00", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_4_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("04:00") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("04:00") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A 04:00 Timer") + } + Spacer() + } + case .accessoryInline: + Label("04:00", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_5_watchOS_WidgetEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var widgetFamily + var body: some View { + switch widgetFamily { + case .systemSmall: + Text("N/A") + case .systemMedium: + Text("N/A") + case .systemLarge: + Text("N/A") + case .systemExtraLarge: + Text("N/A") + case .accessoryCorner: + VStack { + Image(systemName: "mouth") + .resizable() + .foregroundColor(.orange) + .scaledToFit() + .widgetAccentable() + .widgetLabel { + Text("05:00") + } + } + case .accessoryCircular: + ZStack { + AccessoryWidgetBackground() + Image(systemName: "mouth") + .resizable() + .padding(.all) + .scaledToFit() + .foregroundColor(.orange) + .widgetAccentable() + .widgetLabel { + Text("05:00") + } + } + case .accessoryRectangular: + HStack { + VStack(alignment: .leading) { + Label("Brushr", systemImage: "mouth") + .bold() + .foregroundColor(.orange) + .widgetAccentable() + Text("Start A 05:00 Timer") + } + Spacer() + } + case .accessoryInline: + Label("05:00", systemImage: "mouth") + .foregroundColor(.orange) + @unknown default: + Text("Unknown") + } + } +} + +struct Brushr_watchOS_Widget: Widget { + let kind: String = "Brushr_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://custom")) + } + .configurationDisplayName("Start A Custom Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A Custom Timer.") + } +} + +struct Brushr_30_watchOS_Widget: Widget { + let kind: String = "Brushr_30_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_30_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://30sec")!) + } + .configurationDisplayName("Start A 00:30 Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A 00:30 Timer.") + } +} + +struct Brushr_1_watchOS_Widget: Widget { + let kind: String = "Brushr_1_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_1_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://1min")!) + } + .configurationDisplayName("Start A 01:00 Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A 01:00 Timer.") + } +} + +struct Brushr_2_watchOS_Widget: Widget { + let kind: String = "Brushr_2_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_2_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://2min")!) + } + .configurationDisplayName("Start A 02:00 Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A 02:00 Timer.") + } +} + +struct Brushr_3_watchOS_Widget: Widget { + let kind: String = "Brushr_3_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_3_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://3min")!) + } + .configurationDisplayName("Start A 03:00 Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A 03:00 Timer.") + } +} + +struct Brushr_4_watchOS_Widget: Widget { + let kind: String = "Brushr_4_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_4_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://4min")!) + } + .configurationDisplayName("Start A 04:00 Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A 04:00 Timer.") + } +} + +struct Brushr_5_watchOS_Widget: Widget { + let kind: String = "Brushr_5_watchOS_Widget" + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + Brushr_5_watchOS_WidgetEntryView(entry: entry) + .widgetURL(URL(string: "brushr://5min")!) + } + .configurationDisplayName("Start A 05:00 Timer") + .supportedFamilies([.accessoryCircular, .accessoryCorner, .accessoryRectangular, .accessoryInline]) + .description("Open The App And Start A 055:00 Timer.") + } +} + +struct Brushr_watchOS_Widget_Previews: PreviewProvider { + static var previews: some View { + Brushr_watchOS_WidgetEntryView(entry: SimpleEntry(date: Date())) + .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) + } +} diff --git a/Source Code/Brushr/Brushr watchOS Widget/Brushr_watchOS_WidgetBundle.swift b/Source Code/Brushr/Brushr watchOS Widget/Brushr_watchOS_WidgetBundle.swift new file mode 100644 index 0000000..8828903 --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Brushr_watchOS_WidgetBundle.swift @@ -0,0 +1,22 @@ +// +// Brushr_watchOS_WidgetBundle.swift +// Brushr watchOS WidgetExtension +// +// Created by Mark Howard on 01/08/2023. +// + +import WidgetKit +import SwiftUI + +@main +struct Brushr_watchOS_WidgetBundle: WidgetBundle { + var body: some Widget { + Brushr_watchOS_Widget() + Brushr_30_watchOS_Widget() + Brushr_1_watchOS_Widget() + Brushr_2_watchOS_Widget() + Brushr_3_watchOS_Widget() + Brushr_4_watchOS_Widget() + Brushr_5_watchOS_Widget() + } +} diff --git a/Source Code/Brushr/Brushr watchOS Widget/Info.plist b/Source Code/Brushr/Brushr watchOS Widget/Info.plist new file mode 100644 index 0000000..0f118fb --- /dev/null +++ b/Source Code/Brushr/Brushr watchOS Widget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/Source Code/Brushr/Brushr.xcodeproj/project.pbxproj b/Source Code/Brushr/Brushr.xcodeproj/project.pbxproj index 32500ec..60a255f 100644 --- a/Source Code/Brushr/Brushr.xcodeproj/project.pbxproj +++ b/Source Code/Brushr/Brushr.xcodeproj/project.pbxproj @@ -14,6 +14,12 @@ D59F92B32A784EA70063AA5E /* Brushr_iOS_Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59F92B22A784EA70063AA5E /* Brushr_iOS_Widget.swift */; }; D59F92B52A784EA90063AA5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D59F92B42A784EA90063AA5E /* Assets.xcassets */; }; D59F92B92A784EA90063AA5E /* Brushr iOS WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D59F92A92A784EA70063AA5E /* Brushr iOS WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D59F92C52A79803B0063AA5E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D59F92AB2A784EA70063AA5E /* WidgetKit.framework */; }; + D59F92C62A79803B0063AA5E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D59F92AD2A784EA70063AA5E /* SwiftUI.framework */; }; + D59F92C92A79803B0063AA5E /* Brushr_watchOS_Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59F92C82A79803B0063AA5E /* Brushr_watchOS_Widget.swift */; }; + D59F92CB2A79803C0063AA5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D59F92CA2A79803C0063AA5E /* Assets.xcassets */; }; + D59F92CF2A79803C0063AA5E /* Brushr watchOS WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D59F92C42A79803B0063AA5E /* Brushr watchOS WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D59F92D52A7980B30063AA5E /* Brushr_watchOS_WidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59F92D42A7980B30063AA5E /* Brushr_watchOS_WidgetBundle.swift */; }; D5EAB91B2A7310A40086E666 /* BrushrApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EAB91A2A7310A40086E666 /* BrushrApp.swift */; }; D5EAB91D2A7310A40086E666 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5EAB91C2A7310A40086E666 /* ContentView.swift */; }; D5EAB91F2A7310A50086E666 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5EAB91E2A7310A50086E666 /* Assets.xcassets */; }; @@ -34,6 +40,13 @@ remoteGlobalIDString = D59F92A82A784EA70063AA5E; remoteInfo = "Brushr iOS WidgetExtension"; }; + D59F92CD2A79803C0063AA5E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D5EAB90F2A7310A40086E666 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D59F92C32A79803B0063AA5E; + remoteInfo = "Brushr watchOS WidgetExtension"; + }; D5EAB9382A73B8D40086E666 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D5EAB90F2A7310A40086E666 /* Project object */; @@ -55,6 +68,17 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + D59F92D02A79803C0063AA5E /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + D59F92CF2A79803C0063AA5E /* Brushr watchOS WidgetExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; D5EAB93E2A73B8D40086E666 /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -79,6 +103,11 @@ D59F92B62A784EA90063AA5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D59F92BE2A784EC20063AA5E /* Brushr iOS WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Brushr iOS WidgetExtension.entitlements"; sourceTree = ""; }; D59F92BF2A79754F0063AA5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + D59F92C42A79803B0063AA5E /* Brushr watchOS WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Brushr watchOS WidgetExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + D59F92C82A79803B0063AA5E /* Brushr_watchOS_Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brushr_watchOS_Widget.swift; sourceTree = ""; }; + D59F92CA2A79803C0063AA5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D59F92CC2A79803C0063AA5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D59F92D42A7980B30063AA5E /* Brushr_watchOS_WidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brushr_watchOS_WidgetBundle.swift; sourceTree = ""; }; D5EAB9172A7310A40086E666 /* Brushr.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Brushr.app; sourceTree = BUILT_PRODUCTS_DIR; }; D5EAB91A2A7310A40086E666 /* BrushrApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrushrApp.swift; sourceTree = ""; }; D5EAB91C2A7310A40086E666 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -104,6 +133,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D59F92C12A79803B0063AA5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D59F92C62A79803B0063AA5E /* SwiftUI.framework in Frameworks */, + D59F92C52A79803B0063AA5E /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D5EAB9142A7310A40086E666 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -141,6 +179,17 @@ path = "Brushr iOS Widget"; sourceTree = ""; }; + D59F92C72A79803B0063AA5E /* Brushr watchOS Widget */ = { + isa = PBXGroup; + children = ( + D59F92C82A79803B0063AA5E /* Brushr_watchOS_Widget.swift */, + D59F92CA2A79803C0063AA5E /* Assets.xcassets */, + D59F92CC2A79803C0063AA5E /* Info.plist */, + D59F92D42A7980B30063AA5E /* Brushr_watchOS_WidgetBundle.swift */, + ); + path = "Brushr watchOS Widget"; + sourceTree = ""; + }; D5EAB90E2A7310A40086E666 = { isa = PBXGroup; children = ( @@ -148,6 +197,7 @@ D5EAB9192A7310A40086E666 /* Brushr */, D5EAB92E2A73B8D30086E666 /* Brushr watchOS Watch App */, D59F92AF2A784EA70063AA5E /* Brushr iOS Widget */, + D59F92C72A79803B0063AA5E /* Brushr watchOS Widget */, D59F92AA2A784EA70063AA5E /* Frameworks */, D5EAB9182A7310A40086E666 /* Products */, ); @@ -159,6 +209,7 @@ D5EAB9172A7310A40086E666 /* Brushr.app */, D5EAB92D2A73B8D30086E666 /* Brushr.app */, D59F92A92A784EA70063AA5E /* Brushr iOS WidgetExtension.appex */, + D59F92C42A79803B0063AA5E /* Brushr watchOS WidgetExtension.appex */, ); name = Products; sourceTree = ""; @@ -226,6 +277,23 @@ productReference = D59F92A92A784EA70063AA5E /* Brushr iOS WidgetExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + D59F92C32A79803B0063AA5E /* Brushr watchOS WidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = D59F92D32A79803C0063AA5E /* Build configuration list for PBXNativeTarget "Brushr watchOS WidgetExtension" */; + buildPhases = ( + D59F92C02A79803B0063AA5E /* Sources */, + D59F92C12A79803B0063AA5E /* Frameworks */, + D59F92C22A79803B0063AA5E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Brushr watchOS WidgetExtension"; + productName = "Brushr watchOS WidgetExtension"; + productReference = D59F92C42A79803B0063AA5E /* Brushr watchOS WidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; D5EAB9162A7310A40086E666 /* Brushr */ = { isa = PBXNativeTarget; buildConfigurationList = D5EAB9252A7310A50086E666 /* Build configuration list for PBXNativeTarget "Brushr" */; @@ -254,10 +322,12 @@ D5EAB9292A73B8D30086E666 /* Sources */, D5EAB92A2A73B8D30086E666 /* Frameworks */, D5EAB92B2A73B8D30086E666 /* Resources */, + D59F92D02A79803C0063AA5E /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + D59F92CE2A79803C0063AA5E /* PBXTargetDependency */, ); name = "Brushr watchOS Watch App"; productName = "Brushr watchOS Watch App"; @@ -277,6 +347,9 @@ D59F92A82A784EA70063AA5E = { CreatedOnToolsVersion = 14.3; }; + D59F92C32A79803B0063AA5E = { + CreatedOnToolsVersion = 14.3; + }; D5EAB9162A7310A40086E666 = { CreatedOnToolsVersion = 14.3; }; @@ -301,6 +374,7 @@ D5EAB9162A7310A40086E666 /* Brushr */, D5EAB92C2A73B8D30086E666 /* Brushr watchOS Watch App */, D59F92A82A784EA70063AA5E /* Brushr iOS WidgetExtension */, + D59F92C32A79803B0063AA5E /* Brushr watchOS WidgetExtension */, ); }; /* End PBXProject section */ @@ -314,6 +388,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D59F92C22A79803B0063AA5E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D59F92CB2A79803C0063AA5E /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D5EAB9152A7310A40086E666 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -344,6 +426,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D59F92C02A79803B0063AA5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D59F92C92A79803B0063AA5E /* Brushr_watchOS_Widget.swift in Sources */, + D59F92D52A7980B30063AA5E /* Brushr_watchOS_WidgetBundle.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D5EAB9132A7310A40086E666 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -372,6 +463,11 @@ target = D59F92A82A784EA70063AA5E /* Brushr iOS WidgetExtension */; targetProxy = D59F92B72A784EA90063AA5E /* PBXContainerItemProxy */; }; + D59F92CE2A79803C0063AA5E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D59F92C32A79803B0063AA5E /* Brushr watchOS WidgetExtension */; + targetProxy = D59F92CD2A79803C0063AA5E /* PBXContainerItemProxy */; + }; D5EAB9392A73B8D40086E666 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D5EAB92C2A73B8D30086E666 /* Brushr watchOS Watch App */; @@ -442,6 +538,70 @@ }; name = Release; }; + D59F92D12A79803C0063AA5E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZWASU9HFBU; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Brushr watchOS Widget/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Brushr watchOS Widget"; + INFOPLIST_KEY_NSHealthShareUsageDescription = "To Read The Data In The Heath App, You Must Allow Access."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "To Write Data To The Health App, You Must Allow Access."; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.MSJ.Brushr.watchkitapp.Brushr-watchOS-Widget"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 9.4; + }; + name = Debug; + }; + D59F92D22A79803C0063AA5E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZWASU9HFBU; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Brushr watchOS Widget/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Brushr watchOS Widget"; + INFOPLIST_KEY_NSHealthShareUsageDescription = "To Read The Data In The Heath App, You Must Allow Access."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "To Write Data To The Health App, You Must Allow Access."; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.MSJ.Brushr.watchkitapp.Brushr-watchOS-Widget"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 9.4; + }; + name = Release; + }; D5EAB9232A7310A50086E666 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -639,6 +799,7 @@ D5EAB93C2A73B8D40086E666 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Brushr watchOS Watch App/Brushr watchOS Watch App.entitlements"; @@ -672,6 +833,7 @@ D5EAB93D2A73B8D40086E666 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Brushr watchOS Watch App/Brushr watchOS Watch App.entitlements"; @@ -714,6 +876,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D59F92D32A79803C0063AA5E /* Build configuration list for PBXNativeTarget "Brushr watchOS WidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D59F92D12A79803C0063AA5E /* Debug */, + D59F92D22A79803C0063AA5E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D5EAB9122A7310A40086E666 /* Build configuration list for PBXProject "Brushr" */ = { isa = XCConfigurationList; buildConfigurations = (