Files
Reflect/FeelsWidget/FeelsWidget.swift
2022-01-15 18:05:48 -06:00

323 lines
11 KiB
Swift

//
// 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 date: Date
let color: Color
init(image: Image, date: Date, color: Color) {
self.image = image
self.date = date
self.color = color
}
}
struct TimeLineCreator {
static func getData() -> [MoodEntry] {
let dateAtEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: Date())!
var tenDaysAgo = Calendar.current.date(byAdding: .day, value: -10, to: dateAtEnd)!
tenDaysAgo = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: tenDaysAgo)!
let moodEntry = PersistenceController.shared.getData(startDate: tenDaysAgo, endDate: dateAtEnd, includedDays: [1,2,3,4,5,6,7])
return moodEntry
}
static func createTimeLineViews(fromEntries: [MoodEntry]) -> [WatchTimelineView] {
var returnViews = [WatchTimelineView]()
for pastDays in 0...10 {
let pastDate = Calendar.current.date(byAdding: .day, value: -pastDays, to: Date())!
if let item = fromEntries.filter({ entry in
let components = Calendar.current.dateComponents([.day, .month, .year], from: pastDate)
let day = components.day
let month = components.month
let year = components.year
let entryComponents = Calendar.current.dateComponents([.day, .month, .year], from: entry.forDate!)
let entryDay = entryComponents.day
let entryMonth = entryComponents.month
let entryYear = entryComponents.year
return day == entryDay && month == entryMonth && year == entryYear
}).first {
let timeLineView = WatchTimelineView(image: item.mood.icon, date: pastDate, color: item.mood.color)
returnViews.append(timeLineView)
} else {
let timeLineView = WatchTimelineView(image: Mood.missing.icon, date: pastDate, color: Mood.missing.color)
returnViews.append(timeLineView)
}
}
return returnViews
}
}
struct Provider: IntentTimelineProvider {
/*
placeholder for widget, no data
gets redacted auto
*/
func placeholder(in context: Context) -> SimpleEntry {
var sampleViews = [WatchTimelineView]()
for pastDay in 0...10 {
let pastDate = Calendar.current.date(byAdding: .day, value: -pastDay, to: Date())!
let mood = Mood.allValues.randomElement()!
sampleViews.append( WatchTimelineView(image: mood.icon, date: pastDate, color: mood.color) )
}
return SimpleEntry(date: Date(), configuration: ConfigurationIntent(), timeLineViews: sampleViews)
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
var timeLineViews = [WatchTimelineView]()
if context.isPreview {
for pastDay in 0...10 {
let pastDate = Calendar.current.date(byAdding: .day, value: -pastDay, to: Date())!
let mood = Mood.allValues.randomElement()!
timeLineViews.append( WatchTimelineView(image: mood.icon, date: pastDate, color: mood.color) )
}
} else {
let data = TimeLineCreator.getData()
timeLineViews = TimeLineCreator.createTimeLineViews(fromEntries: data)
}
let entry = SimpleEntry(date: Date(), configuration: configuration, timeLineViews: timeLineViews)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let data = TimeLineCreator.getData()
let views = TimeLineCreator.createTimeLineViews(fromEntries: data)
let entry = SimpleEntry(date: Date(), configuration: configuration, timeLineViews: views)
let timeline = Timeline(entries: [entry], policy: .after(Random.tomorrowMidnightThirty))
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 body: some View {
ZStack {
Color(UIColor.secondarySystemBackground)
HStack {
ForEach([entry.timeLineViews.first!]) { watchView in
EntryCard(timeLineView: watchView)
}
}
.padding()
}
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
.frame(minHeight: 0, maxHeight: 55)
.padding()
}
}
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 MediumWidgetView: View {
var entry: Provider.Entry
var firstGroup: [WatchTimelineView] {
Array(self.entry.timeLineViews.prefix(5))
}
var body: some View {
VStack {
Spacer()
TimeHeaderView(startDate: firstGroup.first!.date, endDate: firstGroup.last!.date)
.frame(minWidth: 0, maxWidth: .infinity)
.multilineTextAlignment(.leading)
TimeBodyView(group: firstGroup)
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
.frame(minHeight: 0, maxHeight: 55)
.padding()
Spacer()
}
}
}
//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 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 FeelsWidget_Previews: PreviewProvider {
static var data: [WatchTimelineView] {
var data = PersistenceController.shared.randomEntries(count: 10)
data.remove(at: 2)
let views = TimeLineCreator.createTimeLineViews(fromEntries: data)
return views
}
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)
}
}
}