diff --git a/Feels.xcodeproj/project.pbxproj b/Feels.xcodeproj/project.pbxproj index 33d2ea0..ac2183e 100644 --- a/Feels.xcodeproj/project.pbxproj +++ b/Feels.xcodeproj/project.pbxproj @@ -7,13 +7,14 @@ objects = { /* Begin PBXBuildFile section */ - 1C412080278E23CC00D9153A /* Charts in Frameworks */ = {isa = PBXBuildFile; productRef = 1C41207F278E23CC00D9153A /* Charts */; }; 1C412082278F2B8800D9153A /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C412081278F2B8800D9153A /* FilterView.swift */; }; 1C412083278F2B8800D9153A /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C412081278F2B8800D9153A /* FilterView.swift */; }; 1C683FCA2792281400745862 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C683FC92792281400745862 /* Stats.swift */; }; 1C683FCB2792281400745862 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C683FC92792281400745862 /* Stats.swift */; }; 1C683FCC2792281400745862 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C683FC92792281400745862 /* Stats.swift */; }; 1C744F2C278CE15600953A57 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C744F2B278CE15600953A57 /* AppDelegate.swift */; }; + 1CA2662A2793843200C0E12C /* Charts in Frameworks */ = {isa = PBXBuildFile; productRef = 1CA266292793843200C0E12C /* Charts */; }; + 1CA2662D2793908700C0E12C /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEF278C7DDF001C4FEA /* Persistence.swift */; }; 1CC469AA278F30A0003E0C6E /* BGTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC469A9278F30A0003E0C6E /* BGTask.swift */; }; 1CC469AC27907D48003E0C6E /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC469AB27907D48003E0C6E /* CircleView.swift */; }; 1CD90B07278C7DE0001C4FEA /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B06278C7DE0001C4FEA /* Tests_iOS.swift */; }; @@ -25,7 +26,6 @@ 1CD90B18278C7DE0001C4FEA /* FeelsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */; }; 1CD90B19278C7DE0001C4FEA /* FeelsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */; }; 1CD90B1C278C7DE0001C4FEA /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEF278C7DDF001C4FEA /* Persistence.swift */; }; - 1CD90B1D278C7DE0001C4FEA /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEF278C7DDF001C4FEA /* Persistence.swift */; }; 1CD90B1E278C7DE0001C4FEA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CD90AF0278C7DE0001C4FEA /* Assets.xcassets */; }; 1CD90B1F278C7DE0001C4FEA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CD90AF0278C7DE0001C4FEA /* Assets.xcassets */; }; 1CD90B37278C7E38001C4FEA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B32278C7E38001C4FEA /* SettingsView.swift */; }; @@ -45,7 +45,6 @@ 1CD90B52278C7E7A001C4FEA /* FeelsWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B4E278C7E7A001C4FEA /* FeelsWidget.intentdefinition */; }; 1CD90B53278C7E7A001C4FEA /* FeelsWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B4E278C7E7A001C4FEA /* FeelsWidget.intentdefinition */; }; 1CD90B56278C7E7A001C4FEA /* FeelsWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1CD90B45278C7E7A001C4FEA /* FeelsWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 1CD90B5B278C7E91001C4FEA /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEF278C7DDF001C4FEA /* Persistence.swift */; }; 1CD90B5D278C7EAD001C4FEA /* Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B5C278C7EAD001C4FEA /* Random.swift */; }; 1CD90B5E278C7EAD001C4FEA /* Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B5C278C7EAD001C4FEA /* Random.swift */; }; 1CD90B5F278C7EAD001C4FEA /* Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B5C278C7EAD001C4FEA /* Random.swift */; }; @@ -149,7 +148,7 @@ buildActionMask = 2147483647; files = ( 1CD90B6C278C7F78001C4FEA /* CloudKit.framework in Frameworks */, - 1C412080278E23CC00D9153A /* Charts in Frameworks */, + 1CA2662A2793843200C0E12C /* Charts in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -324,7 +323,7 @@ ); name = "Feels (iOS)"; packageProductDependencies = ( - 1C41207F278E23CC00D9153A /* Charts */, + 1CA266292793843200C0E12C /* Charts */, ); productName = "Feels (iOS)"; productReference = 1CD90AF5278C7DE0001C4FEA /* Feels.app */; @@ -439,7 +438,7 @@ ); mainGroup = 1CD90AE5278C7DDF001C4FEA; packageReferences = ( - 1C41207E278E23CB00D9153A /* XCRemoteSwiftPackageReference "Charts" */, + 1CA266282793843200C0E12C /* XCRemoteSwiftPackageReference "Charts" */, ); productRefGroup = 1CD90AF6278C7DE0001C4FEA /* Products */; projectDirPath = ""; @@ -535,7 +534,6 @@ 1CD90B38278C7E38001C4FEA /* SettingsView.swift in Sources */, 1CD90B67278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */, 1CD90B77278C8119001C4FEA /* LocalNotification.swift in Sources */, - 1CD90B1D278C7DE0001C4FEA /* Persistence.swift in Sources */, 1CD90B19278C7DE0001C4FEA /* FeelsApp.swift in Sources */, 1CD90B5E278C7EAD001C4FEA /* Random.swift in Sources */, ); @@ -565,8 +563,8 @@ buildActionMask = 2147483647; files = ( 1CD90B65278C7EBA001C4FEA /* Mood.swift in Sources */, + 1CA2662D2793908700C0E12C /* Persistence.swift in Sources */, 1CD90B5F278C7EAD001C4FEA /* Random.swift in Sources */, - 1CD90B5B278C7E91001C4FEA /* Persistence.swift in Sources */, 1CD90B68278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */, 1CD90B71278C80CA001C4FEA /* Feels.xcdatamodeld in Sources */, 1C683FCB2792281400745862 /* Stats.swift in Sources */, @@ -1040,7 +1038,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 1C41207E278E23CB00D9153A /* XCRemoteSwiftPackageReference "Charts" */ = { + 1CA266282793843200C0E12C /* XCRemoteSwiftPackageReference "Charts" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/danielgindi/Charts"; requirement = { @@ -1051,9 +1049,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1C41207F278E23CC00D9153A /* Charts */ = { + 1CA266292793843200C0E12C /* Charts */ = { isa = XCSwiftPackageProductDependency; - package = 1C41207E278E23CB00D9153A /* XCRemoteSwiftPackageReference "Charts" */; + package = 1CA266282793843200C0E12C /* XCRemoteSwiftPackageReference "Charts" */; productName = Charts; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist index ff9be8c..ed7ab37 100644 --- a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ Feels (iOS).xcscheme_^#shared#^_ orderHint - 0 + 1 Feels (macOS).xcscheme_^#shared#^_ orderHint - 1 + 0 FeelsWidgetExtension.xcscheme_^#shared#^_ diff --git a/FeelsWidget/Assets.xcassets/average.imageset/Contents.json b/FeelsWidget/Assets.xcassets/average.imageset/Contents.json new file mode 100644 index 0000000..00fb6a2 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/average.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "meh-regular.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FeelsWidget/Assets.xcassets/average.imageset/meh-regular.svg b/FeelsWidget/Assets.xcassets/average.imageset/meh-regular.svg new file mode 100644 index 0000000..9ad9cf9 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/average.imageset/meh-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FeelsWidget/Assets.xcassets/bad.imageset/Contents.json b/FeelsWidget/Assets.xcassets/bad.imageset/Contents.json new file mode 100644 index 0000000..a8ac09d --- /dev/null +++ b/FeelsWidget/Assets.xcassets/bad.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "frown-regular.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FeelsWidget/Assets.xcassets/bad.imageset/frown-regular.svg b/FeelsWidget/Assets.xcassets/bad.imageset/frown-regular.svg new file mode 100644 index 0000000..e32249b --- /dev/null +++ b/FeelsWidget/Assets.xcassets/bad.imageset/frown-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FeelsWidget/Assets.xcassets/good.imageset/Contents.json b/FeelsWidget/Assets.xcassets/good.imageset/Contents.json new file mode 100644 index 0000000..a685e3c --- /dev/null +++ b/FeelsWidget/Assets.xcassets/good.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "grin-regular.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FeelsWidget/Assets.xcassets/good.imageset/grin-regular.svg b/FeelsWidget/Assets.xcassets/good.imageset/grin-regular.svg new file mode 100644 index 0000000..380aed4 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/good.imageset/grin-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FeelsWidget/Assets.xcassets/great.imageset/Contents.json b/FeelsWidget/Assets.xcassets/great.imageset/Contents.json new file mode 100644 index 0000000..f3c745f --- /dev/null +++ b/FeelsWidget/Assets.xcassets/great.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "smile-beam-regular.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FeelsWidget/Assets.xcassets/great.imageset/smile-beam-regular.svg b/FeelsWidget/Assets.xcassets/great.imageset/smile-beam-regular.svg new file mode 100644 index 0000000..3c09689 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/great.imageset/smile-beam-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FeelsWidget/Assets.xcassets/horrible.imageset/Contents.json b/FeelsWidget/Assets.xcassets/horrible.imageset/Contents.json new file mode 100644 index 0000000..d7421a5 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/horrible.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "sad-tear-regular.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FeelsWidget/Assets.xcassets/horrible.imageset/sad-tear-regular.svg b/FeelsWidget/Assets.xcassets/horrible.imageset/sad-tear-regular.svg new file mode 100644 index 0000000..edae2d2 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/horrible.imageset/sad-tear-regular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FeelsWidget/Assets.xcassets/missing.imageset/Contents.json b/FeelsWidget/Assets.xcassets/missing.imageset/Contents.json new file mode 100644 index 0000000..c82dff9 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/missing.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "exclamation-solid.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FeelsWidget/Assets.xcassets/missing.imageset/exclamation-solid.svg b/FeelsWidget/Assets.xcassets/missing.imageset/exclamation-solid.svg new file mode 100644 index 0000000..32de166 --- /dev/null +++ b/FeelsWidget/Assets.xcassets/missing.imageset/exclamation-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FeelsWidget/FeelsWidget.swift b/FeelsWidget/FeelsWidget.swift index 4b4ea31..ab0b98a 100644 --- a/FeelsWidget/FeelsWidget.swift +++ b/FeelsWidget/FeelsWidget.swift @@ -10,39 +10,97 @@ 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 { - let date = Date() - let moodEntry = PersistenceController.shared.moodEntries(forStartDate: date, count: 10) - return SimpleEntry(date: date, configuration: ConfigurationIntent(), mood: moodEntry) + 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) } - var calendar = Calendar.current - calendar.timeZone = NSTimeZone.local - let todayStart = calendar.startOfDay(for: Date()) - let userEntries = PersistenceController.shared.moodEntries(forStartDate: todayStart, count: 10) - - let entry = SimpleEntry(date: Date(), configuration: configuration, mood: userEntries) + let entry = SimpleEntry(date: Date(), configuration: configuration, timeLineViews: timeLineViews) completion(entry) } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { - var calendar = Calendar.current - calendar.timeZone = NSTimeZone.local - let todayStart = calendar.startOfDay(for: Date()) - let userEntries = PersistenceController.shared.moodEntries(forStartDate: todayStart, count: 10) + let data = TimeLineCreator.getData() + let views = TimeLineCreator.createTimeLineViews(fromEntries: data) - let entry = SimpleEntry(date: Date(), configuration: configuration, mood: userEntries) - let timeline = Timeline(entries: [entry], policy: .after(Random.widgetUpdateTime)) + let entry = SimpleEntry(date: Date(), configuration: configuration, timeLineViews: views) + let timeline = Timeline(entries: [entry], policy: .after(Random.tomorrowMidnightThirty)) completion(timeline) } } @@ -50,13 +108,13 @@ struct Provider: IntentTimelineProvider { struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationIntent - let mood: [MoodEntry] + let timeLineViews: [WatchTimelineView] let showStats: Bool - init(date: Date, configuration: ConfigurationIntent, mood: [MoodEntry], showStats: Bool = false) { + init(date: Date, configuration: ConfigurationIntent, timeLineViews: [WatchTimelineView], showStats: Bool = false) { self.date = date self.configuration = configuration - self.mood = mood + self.timeLineViews = timeLineViews self.showStats = showStats } } @@ -71,19 +129,21 @@ struct FeelsWidgetEntryView : View { var body: some View { ZStack { Color(UIColor.systemBackground) - switch family { case .systemSmall: SmallWidgetView(entry: entry) case .systemMedium: MediumWidgetView(entry: entry) case .systemLarge: - LargeWidgetView(entry: entry) + MediumWidgetView(entry: entry) case .systemExtraLarge: - LargeWidgetView(entry: entry) + 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() } } } @@ -92,14 +152,55 @@ struct SmallWidgetView: View { var entry: Provider.Entry var body: some View { - VStack { - if let first = entry.mood.first { - EntryCardCollectionView(moodEntries: Array([first])) - .padding() - } else { - Text("🤷‍♂️") - .font(.system(size: 50)) + 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() } } } @@ -107,32 +208,20 @@ struct SmallWidgetView: View { struct MediumWidgetView: View { var entry: Provider.Entry - var formatter: DateFormatter { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - return dateFormatter - } - - var firstGroup: [MoodEntry] { - Array(self.entry.mood.prefix(5)) + var firstGroup: [WatchTimelineView] { + Array(self.entry.timeLineViews.prefix(5)) } var body: some View { VStack { Spacer() - HStack { - Text(firstGroup.first?.forDate ?? Date(), formatter: formatter) - .font(.system(.footnote)) - Text(" - ") - .font(.system(.footnote)) - Text(firstGroup.last?.forDate ?? Date(), formatter: formatter) - .font(.system(.footnote)) - } - .frame(minWidth: 0, maxWidth: .infinity) - .multilineTextAlignment(.leading) + TimeHeaderView(startDate: firstGroup.first!.date, endDate: firstGroup.last!.date) + .frame(minWidth: 0, maxWidth: .infinity) + .multilineTextAlignment(.leading) - EntryCardCollectionView(moodEntries: firstGroup) + TimeBodyView(group: firstGroup) + .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous)) .frame(minHeight: 0, maxHeight: 55) .padding() @@ -141,86 +230,47 @@ struct MediumWidgetView: View { } } -struct LargeWidgetView: View { - var entry: Provider.Entry - - var formatter: DateFormatter { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - return dateFormatter - } - - var firstGroup: [MoodEntry] { - Array(self.entry.mood.prefix(5)) - } - - var lastGroup: [MoodEntry] { - Array(self.entry.mood.suffix(5)) - } - - - var body: some View { - VStack { - Spacer() - - HStack { - Text(firstGroup.first?.forDate ?? Date(), formatter: formatter) - .font(.system(.footnote)) - Text(" - ") - .font(.system(.footnote)) - Text(firstGroup.last?.forDate ?? Date(), formatter: formatter) - .font(.system(.footnote)) - } - .frame(minWidth: 0, maxWidth: .infinity) - .multilineTextAlignment(.leading) - - EntryCardCollectionView(moodEntries: firstGroup) - .frame(minHeight: 0, maxHeight: 55) - .padding() - - Spacer() - - HStack { - Text(lastGroup.first?.forDate ?? Date(), formatter: formatter) - .font(.system(.footnote)) - Text(" - ") - Text(lastGroup.last?.forDate ?? Date(), formatter: formatter) - .font(.system(.footnote)) - } - .frame(minWidth: 0, maxWidth: .infinity) - .multilineTextAlignment(.leading) - - EntryCardCollectionView(moodEntries: lastGroup) - .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 EntryCardCollectionView: View { - var moodEntries: [MoodEntry] - - var body: some View { - ZStack { - Color(UIColor.secondarySystemBackground) - HStack { - ForEach(moodEntries) { mood in - EntryCard(moodEntry: mood) - } - } - .padding() - } - .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous)) - } -} struct EntryCard: View { - var moodEntry: MoodEntry + var timeLineView: WatchTimelineView var body: some View { - moodEntry.mood.icon.font(.system(size: 50)) + timeLineView.image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 50, height: 50, alignment: .center) + .foregroundColor(timeLineView.color) } } @@ -236,30 +286,37 @@ struct FeelsWidget: Widget { } .configurationDisplayName("Feels") .description("") - .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + .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(), - mood: PersistenceController.shared.randomEntries(count: 1))) + timeLineViews: FeelsWidget_Previews.data)) .previewContext(WidgetPreviewContext(family: .systemSmall)) .environment(\.sizeCategory, .small) FeelsWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), - mood: PersistenceController.shared.randomEntries(count: 3))) + timeLineViews: FeelsWidget_Previews.data)) .previewContext(WidgetPreviewContext(family: .systemMedium)) .environment(\.sizeCategory, .medium) - - FeelsWidgetEntryView(entry: SimpleEntry(date: Date(), - configuration: ConfigurationIntent(), - mood: PersistenceController.shared.randomEntries(count: 10))) - .previewContext(WidgetPreviewContext(family: .systemLarge)) - .environment(\.sizeCategory, .large) +// +// FeelsWidgetEntryView(entry: SimpleEntry(date: Date(), +// configuration: ConfigurationIntent(), +// timeLineViews: FeelsWidget_Previews.data)) +// .previewContext(WidgetPreviewContext(family: .systemLarge)) +// .environment(\.sizeCategory, .large) } } } diff --git a/Shared/AppDelegate.swift b/Shared/AppDelegate.swift index 97eff28..84649a7 100644 --- a/Shared/AppDelegate.swift +++ b/Shared/AppDelegate.swift @@ -12,21 +12,15 @@ import WidgetKit // AppDelegate.swift class AppDelegate: NSObject, UIApplicationDelegate { + + + func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // PersistenceController.shared.clearDB() - NotificationCenter.default.addObserver(self, - selector: #selector(fetchChanges), - name: .NSPersistentStoreRemoteChange, - object: PersistenceController.shared.container.persistentStoreCoordinator) - application.registerForRemoteNotifications() UNUserNotificationCenter.current().delegate = self return true } - - @objc func fetchChanges(note: Notification) { - WidgetCenter.shared.reloadAllTimelines() - } } extension AppDelegate: UNUserNotificationCenterDelegate { diff --git a/Shared/Assets.xcassets/missing.imageset/Contents.json b/Shared/Assets.xcassets/missing.imageset/Contents.json index dabdec4..c82dff9 100644 --- a/Shared/Assets.xcassets/missing.imageset/Contents.json +++ b/Shared/Assets.xcassets/missing.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Screen Shot 2022-01-13 at 5.02.34 PM.png", + "filename" : "exclamation-solid.svg", "idiom" : "universal", "scale" : "1x" }, diff --git a/Shared/Assets.xcassets/missing.imageset/Screen Shot 2022-01-13 at 5.02.34 PM.png b/Shared/Assets.xcassets/missing.imageset/Screen Shot 2022-01-13 at 5.02.34 PM.png deleted file mode 100644 index f2e0326..0000000 Binary files a/Shared/Assets.xcassets/missing.imageset/Screen Shot 2022-01-13 at 5.02.34 PM.png and /dev/null differ diff --git a/Shared/Assets.xcassets/missing.imageset/exclamation-solid.svg b/Shared/Assets.xcassets/missing.imageset/exclamation-solid.svg new file mode 100644 index 0000000..32de166 --- /dev/null +++ b/Shared/Assets.xcassets/missing.imageset/exclamation-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Shared/Persistence.swift b/Shared/Persistence.swift index 275866d..e6e5909 100644 --- a/Shared/Persistence.swift +++ b/Shared/Persistence.swift @@ -6,8 +6,14 @@ // import CoreData +import WidgetKit +import Foundation +import UIKit -struct PersistenceController { +class PersistenceController { + private var dataUpdateCall: (() -> Void)? + private let callDelay = 10 + static let shared = PersistenceController.persistenceController private static var persistenceController: PersistenceController { @@ -38,28 +44,6 @@ struct PersistenceController { } } - public func moodEntries(forStartDate date: Date, count: Int) -> [MoodEntry] { - let fetchRequest = NSFetchRequest(entityName: "MoodEntry") - - fetchRequest.fetchLimit = count - fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: false)] - -// var calendar = Calendar.current -// calendar.timeZone = NSTimeZone.local -// let dateFrom = calendar.startOfDay(for: Date()) - // Set predicate as date being today's date -// let fromPredicate = NSPredicate(format: "date <= %@", dateFrom as NSDate) -// let datePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate]) -// fetchRequest.predicate = datePredicate - let entries = try! viewContext.fetch(fetchRequest) - - if entries.count >= count { - return Array(entries) - } else { - return entries - } - } - public var earliestEntry: MoodEntry? { let fetchRequest = NSFetchRequest(entityName: "MoodEntry") fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)] @@ -75,6 +59,14 @@ struct PersistenceController { } public func getData(startDate: Date, endDate: Date, includedDays: [Int]) -> [MoodEntry] { + try! viewContext.setQueryGenerationFrom(.current) + viewContext.refreshAllObjects() + + let fakeRequest = NSFetchRequest(entityName: "MoodEntry") + let fakeData = try! viewContext.fetch(fakeRequest) + for item in fakeData { + print(item.forDate!) + } var includedDays16 = [Int16]() if includedDays.isEmpty { @@ -84,17 +76,16 @@ struct PersistenceController { Int16($0) }) } - let predicate = NSPredicate(format: "%K >= %@ && %K <= %@ && weekDay IN %@", - "forDate", + let predicate = NSPredicate(format: "forDate >= %@ && forDate <= %@ && weekDay IN %@", startDate as NSDate, - "forDate", endDate as NSDate, includedDays16) let fetchRequest = NSFetchRequest(entityName: "MoodEntry") fetchRequest.predicate = predicate fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)] - return try! viewContext.fetch(fetchRequest) + let data = try! viewContext.fetch(fetchRequest) + return data } func populateTestData() { diff --git a/Shared/Random.swift b/Shared/Random.swift index ff689b3..45f39fe 100644 --- a/Shared/Random.swift +++ b/Shared/Random.swift @@ -8,7 +8,7 @@ import Foundation class Random { - static var widgetUpdateTime: Date { + static var tomorrowMidnightThirty: Date { let components = DateComponents(hour: 0, minute: 30, second: 0) var updateTime = Date() if let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()), diff --git a/Shared/views/AddMoodHeaderView.swift b/Shared/views/AddMoodHeaderView.swift index 89dba7f..b0aff8e 100644 --- a/Shared/views/AddMoodHeaderView.swift +++ b/Shared/views/AddMoodHeaderView.swift @@ -26,7 +26,7 @@ struct AddMoodHeaderView: View { ForEach(Mood.allValues.reversed()) { mood in VStack { Button(action: { - addItem(withMoodValue: mood.rawValue) + addItem(withMood: mood) }, label: { mood.icon .resizable() @@ -47,21 +47,9 @@ struct AddMoodHeaderView: View { .padding() } - private func addItem(withMoodValue moodValue: Int) { + private func addItem(withMood mood: Mood) { withAnimation { - let newItem = MoodEntry(context: viewContext) - newItem.timestamp = Date() - newItem.moodValue = Int16(moodValue) - newItem.forDate = Date() - - do { - try viewContext.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") - } + PersistenceController.shared.add(mood: mood, forDate: Date()) } } } diff --git a/Shared/views/FilterView.swift b/Shared/views/FilterView.swift index 4234988..8eaa47e 100644 --- a/Shared/views/FilterView.swift +++ b/Shared/views/FilterView.swift @@ -122,7 +122,7 @@ struct FilterView: View { for day in 1...numDays { if let item = monthEntries.filter({ entry in - let components = calendar.dateComponents([.day, .weekday], from: entry.forDate!) + let components = calendar.dateComponents([.day], from: entry.forDate!) let date = components.day return day == date }).first {