on widgets if its before the voting time show yesterdays vote, if after either show no vote or current vote user shared user defaults
410 lines
13 KiB
Swift
410 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]()
|
|
var startDayOffset = 0
|
|
|
|
if !UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime() {
|
|
startDayOffset = 1
|
|
}
|
|
|
|
for day in startDayOffset..<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: 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<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: 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)
|
|
}
|
|
}
|
|
}
|