// // FeelsWidget.swift // FeelsWidget // // Created by Trey Tartt on 1/7/22. // import WidgetKit import SwiftUI import Intents import CoreData class WatchTimelineView: Identifiable { let id = UUID() let image: Image let graphic: Image let date: Date let color: Color init(image: Image, date: Date, color: Color, graphic: Image) { self.image = image self.date = date self.color = color self.graphic = graphic } } struct TimeLineCreator { static func createViews(daysBack: Int) -> [WatchTimelineView] { var timeLineView = [WatchTimelineView]() var startDayOffset = 0 if !UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime() { startDayOffset = 1 } for day in startDayOffset.. $1.date }) return timeLineView } } struct Provider: IntentTimelineProvider { let timeLineCreator = TimeLineCreator() /* placeholder for widget, no data gets redacted auto */ func placeholder(in context: Context) -> SimpleEntry { return SimpleEntry(date: Date(), configuration: ConfigurationIntent(), timeLineViews: Array(TimeLineCreator.createViews(daysBack: 11).prefix(10))) } func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), configuration: ConfigurationIntent(), timeLineViews: Array(TimeLineCreator.createViews(daysBack: 11).prefix(10))) completion(entry) } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { let entry = SimpleEntry(date: Calendar.current.date(byAdding: .second, value: 15, to: Date())!, configuration: ConfigurationIntent(), timeLineViews: nil) let midNightEntry = SimpleEntry(date: Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: Date())!, configuration: ConfigurationIntent(), timeLineViews: nil) let date = Calendar.current.date(byAdding: .second, value: 10, to: Date())! let timeline = Timeline(entries: [entry, midNightEntry], policy: .after(date)) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationIntent let timeLineViews: [WatchTimelineView]? let showStats: Bool init(date: Date, configuration: ConfigurationIntent, timeLineViews: [WatchTimelineView]?, showStats: Bool = false) { self.date = date self.configuration = configuration self.timeLineViews = timeLineViews self.showStats = showStats } } /**********************************************************/ struct FeelsWidgetEntryView : View { @Environment(\.sizeCategory) var sizeCategory @Environment(\.widgetFamily) var family var entry: Provider.Entry @ViewBuilder var body: some View { ZStack { Color(UIColor.systemBackground) switch family { case .systemSmall: SmallWidgetView(entry: entry) case .systemMedium: MediumWidgetView(entry: entry) case .systemLarge: MediumWidgetView(entry: entry) case .systemExtraLarge: MediumWidgetView(entry: entry) @unknown default: fatalError() } }.onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange)) { _ in // make sure you don't call this too often WidgetCenter.shared.reloadAllTimelines() } } } struct SmallWidgetView: View { var entry: Provider.Entry var timeLineView = [WatchTimelineView]() init(entry: Provider.Entry) { self.entry = entry timeLineView = [TimeLineCreator.createViews(daysBack: 2).first!] } var body: some View { ZStack { Color(UIColor.secondarySystemBackground) HStack { ForEach(self.timeLineView) { watchView in EntryCard(timeLineView: watchView) } } .padding() } .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous)) .frame(minHeight: 0, maxHeight: 55) .padding() } } struct MediumWidgetView: View { var entry: Provider.Entry var timeLineView = [WatchTimelineView]() init(entry: Provider.Entry) { self.entry = entry timeLineView = Array(TimeLineCreator.createViews(daysBack: 6).prefix(5)) } var body: some View { VStack { Spacer() TimeHeaderView(startDate: timeLineView.first!.date, endDate: timeLineView.last!.date) .frame(minWidth: 0, maxWidth: .infinity) .multilineTextAlignment(.leading) TimeBodyView(group: timeLineView) .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous)) .frame(minHeight: 0, maxHeight: 55) .padding() Spacer() } } } /**********************************************************/ struct FeelsGraphicWidgetEntryView : View { @Environment(\.sizeCategory) var sizeCategory @Environment(\.widgetFamily) var family var entry: Provider.Entry @ViewBuilder var body: some View { SmallGraphicWidgetView(entry: entry) .onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange)) { _ in // make sure you don't call this too often WidgetCenter.shared.reloadAllTimelines() } } } struct SmallGraphicWidgetView: View { var entry: Provider.Entry var timeLineView = [WatchTimelineView]() init(entry: Provider.Entry) { self.entry = entry timeLineView = [TimeLineCreator.createViews(daysBack: 2).first!] } var body: some View { GeometryReader { geo in timeLineView.first!.graphic .resizable() .scaledToFit() } } } /**********************************************************/ struct FeelsIconWidgetEntryView : View { @Environment(\.sizeCategory) var sizeCategory @Environment(\.widgetFamily) var family var entry: Provider.Entry @ViewBuilder var body: some View { SmallIconView(entry: entry) } } struct SmallIconView: View { var entry: Provider.Entry var body: some View { GeometryReader { geo in Mood.missing.graphic .resizable() .scaledToFit() } } } /**********************************************************/ struct TimeHeaderView: View { let startDate: Date let endDate: Date var formatter: DateFormatter { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium return dateFormatter } var body: some View { HStack { Text(startDate, formatter: formatter) .font(.system(.footnote)) Text(" - ") .font(.system(.footnote)) Text(endDate, formatter: formatter) .font(.system(.footnote)) } } } struct TimeBodyView: View { let group: [WatchTimelineView] var body: some View { ZStack { Color(UIColor.secondarySystemBackground) HStack { ForEach(group) { watchView in EntryCard(timeLineView: watchView) } } .padding() } } } //struct LargeWidgetView: View { // var entry: Provider.Entry // // var formatter: DateFormatter { // let dateFormatter = DateFormatter() // dateFormatter.dateStyle = .medium // return dateFormatter // } // // // var body: some View { // VStack { // Spacer() // // ForEach([Array(self.entry.timeLineViews.prefix(5)), Array(self.entry.timeLineViews.suffix(5))]) { group in // // TimeHeaderView(startDate: group.first!, endDate: group.last!) // .frame(minWidth: 0, maxWidth: .infinity) // .multilineTextAlignment(.leading) // // TimeBodyView(group: group) // .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous)) // .frame(minHeight: 0, maxHeight: 55) // .padding() // // Spacer() // } // } // } //} struct EntryCard: View { var timeLineView: WatchTimelineView var body: some View { timeLineView.image .resizable() .aspectRatio(contentMode: .fit) .frame(width: 50, height: 50, alignment: .center) .foregroundColor(timeLineView.color) } } @main struct FeelsBundle: WidgetBundle { var body: some Widget { FeelsWidget() FeelsGraphicWidget() FeelsIconWidget() } } struct FeelsWidget: Widget { let kind: String = "FeelsWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in FeelsWidgetEntryView(entry: entry) } .configurationDisplayName("Feels") .description("") .supportedFamilies([.systemSmall, .systemMedium]) } } struct FeelsIconWidget: Widget { let kind: String = "FeelsIconWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in FeelsIconWidgetEntryView(entry: entry) } .configurationDisplayName("Feels Icon") .description("") .supportedFamilies([.systemSmall]) } } struct FeelsGraphicWidget: Widget { let kind: String = "FeelsGraphicWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in FeelsGraphicWidgetEntryView(entry: entry) } .configurationDisplayName("Mood Graphic") .description("") .supportedFamilies([.systemSmall]) } } struct FeelsWidget_Previews: PreviewProvider { static var previews: some View { Group { // FeelsWidgetEntryView(entry: SimpleEntry(date: Date(), // configuration: ConfigurationIntent(), // timeLineViews: FeelsWidget_Previews.data)) // .previewContext(WidgetPreviewContext(family: .systemSmall)) // .environment(\.sizeCategory, .small) // // FeelsWidgetEntryView(entry: SimpleEntry(date: Date(), // configuration: ConfigurationIntent(), // timeLineViews: FeelsWidget_Previews.data)) // .previewContext(WidgetPreviewContext(family: .systemMedium)) // .environment(\.sizeCategory, .medium) // // FeelsWidgetEntryView(entry: SimpleEntry(date: Date(), // configuration: ConfigurationIntent(), // timeLineViews: FeelsWidget_Previews.data)) // .previewContext(WidgetPreviewContext(family: .systemLarge)) // .environment(\.sizeCategory, .large) } } }