405 lines
13 KiB
Swift
405 lines
13 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 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]()
|
|
|
|
for day in 0..<daysBack{
|
|
let day = Calendar.current.date(byAdding: .day, value: -day, to: Date())!
|
|
|
|
let dayStart = Calendar.current.startOfDay(for: day)
|
|
let dayEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: dayStart)!
|
|
|
|
if let todayEntry = PersistenceController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first {
|
|
timeLineView.append(WatchTimelineView(image: todayEntry.mood.icon,
|
|
date: dayStart,
|
|
color: todayEntry.mood.color,
|
|
graphic: todayEntry.mood.graphic))
|
|
} else {
|
|
timeLineView.append(WatchTimelineView(image: Mood.missing.icon,
|
|
date: dayStart,
|
|
color: Mood.missing.color,
|
|
graphic: Mood.missing.graphic))
|
|
}
|
|
}
|
|
timeLineView = timeLineView.sorted(by: { $0.date > $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: TimeLineCreator.createViews(daysBack: 10))
|
|
}
|
|
|
|
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
|
let entry = SimpleEntry(date: Date(),
|
|
configuration: ConfigurationIntent(),
|
|
timeLineViews: TimeLineCreator.createViews(daysBack: 10))
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
|
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: 1)
|
|
}
|
|
|
|
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 = TimeLineCreator.createViews(daysBack: 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: 1)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|