diff --git a/Feels (iOS).entitlements b/Feels (iOS).entitlements
new file mode 100644
index 0000000..2ead4a3
--- /dev/null
+++ b/Feels (iOS).entitlements
@@ -0,0 +1,16 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.icloud-container-identifiers
+
+ iCloud.com.88oak.feels
+
+ com.apple.developer.icloud-services
+
+ CloudKit
+
+
+
diff --git a/Feels (iOS)Dev.entitlements b/Feels (iOS)Dev.entitlements
new file mode 100644
index 0000000..1662af0
--- /dev/null
+++ b/Feels (iOS)Dev.entitlements
@@ -0,0 +1,16 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.icloud-container-identifiers
+
+ iCloud.com.88oak.feelsDev
+
+ com.apple.developer.icloud-services
+
+ CloudKit
+
+
+
diff --git a/Feels--iOS--Info.plist b/Feels--iOS--Info.plist
new file mode 100644
index 0000000..d1d3f19
--- /dev/null
+++ b/Feels--iOS--Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ UIBackgroundModes
+
+ processing
+ remote-notification
+
+
+
diff --git a/Feels.xcodeproj/project.pbxproj b/Feels.xcodeproj/project.pbxproj
index ef5ff24..d7e09e7 100644
--- a/Feels.xcodeproj/project.pbxproj
+++ b/Feels.xcodeproj/project.pbxproj
@@ -15,12 +15,44 @@
1CD90B17278C7DE0001C4FEA /* Feels.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEB278C7DDF001C4FEA /* Feels.xcdatamodeld */; };
1CD90B18278C7DE0001C4FEA /* FeelsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */; };
1CD90B19278C7DE0001C4FEA /* FeelsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */; };
- 1CD90B1A278C7DE0001C4FEA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEE278C7DDF001C4FEA /* ContentView.swift */; };
- 1CD90B1B278C7DE0001C4FEA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEE278C7DDF001C4FEA /* ContentView.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 */; };
+ 1CD90B38278C7E38001C4FEA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B32278C7E38001C4FEA /* SettingsView.swift */; };
+ 1CD90B39278C7E38001C4FEA /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B33278C7E38001C4FEA /* GraphView.swift */; };
+ 1CD90B3A278C7E38001C4FEA /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B33278C7E38001C4FEA /* GraphView.swift */; };
+ 1CD90B3B278C7E38001C4FEA /* AddMoodHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B34278C7E38001C4FEA /* AddMoodHeaderView.swift */; };
+ 1CD90B3C278C7E38001C4FEA /* AddMoodHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B34278C7E38001C4FEA /* AddMoodHeaderView.swift */; };
+ 1CD90B3D278C7E38001C4FEA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B35278C7E38001C4FEA /* ContentView.swift */; };
+ 1CD90B3E278C7E38001C4FEA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B35278C7E38001C4FEA /* ContentView.swift */; };
+ 1CD90B3F278C7E38001C4FEA /* HeaderStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B36278C7E38001C4FEA /* HeaderStatsView.swift */; };
+ 1CD90B40278C7E38001C4FEA /* HeaderStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B36278C7E38001C4FEA /* HeaderStatsView.swift */; };
+ 1CD90B48278C7E7A001C4FEA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */; platformFilter = maccatalyst; };
+ 1CD90B4A278C7E7A001C4FEA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B49278C7E7A001C4FEA /* SwiftUI.framework */; platformFilter = maccatalyst; };
+ 1CD90B4D278C7E7A001C4FEA /* FeelsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B4C278C7E7A001C4FEA /* FeelsWidget.swift */; };
+ 1CD90B50278C7E7A001C4FEA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CD90B4F278C7E7A001C4FEA /* Assets.xcassets */; };
+ 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 */; };
+ 1CD90B63278C7EBA001C4FEA /* Mood.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B61278C7EBA001C4FEA /* Mood.swift */; };
+ 1CD90B64278C7EBA001C4FEA /* Mood.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B61278C7EBA001C4FEA /* Mood.swift */; };
+ 1CD90B65278C7EBA001C4FEA /* Mood.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B61278C7EBA001C4FEA /* Mood.swift */; };
+ 1CD90B66278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */; };
+ 1CD90B67278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */; };
+ 1CD90B68278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */; };
+ 1CD90B6C278C7F78001C4FEA /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B6B278C7F78001C4FEA /* CloudKit.framework */; };
+ 1CD90B6E278C7F8B001C4FEA /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD90B6B278C7F78001C4FEA /* CloudKit.framework */; };
+ 1CD90B71278C80CA001C4FEA /* Feels.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90AEB278C7DDF001C4FEA /* Feels.xcdatamodeld */; };
+ 1CD90B76278C8119001C4FEA /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B75278C8119001C4FEA /* LocalNotification.swift */; };
+ 1CD90B77278C8119001C4FEA /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B75278C8119001C4FEA /* LocalNotification.swift */; };
+ 1CD90B78278C8119001C4FEA /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD90B75278C8119001C4FEA /* LocalNotification.swift */; };
+ 1CD90B7B278C8146001C4FEA /* Charts in Frameworks */ = {isa = PBXBuildFile; productRef = 1CD90B7A278C8146001C4FEA /* Charts */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -38,12 +70,32 @@
remoteGlobalIDString = 1CD90AFA278C7DE0001C4FEA;
remoteInfo = "Feels (macOS)";
};
+ 1CD90B54278C7E7A001C4FEA /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 1CD90AE6278C7DDF001C4FEA /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 1CD90B44278C7E7A001C4FEA;
+ remoteInfo = FeelsWidgetExtension;
+ };
/* End PBXContainerItemProxy section */
+/* Begin PBXCopyFilesBuildPhase section */
+ 1CD90B5A278C7E7A001C4FEA /* Embed App Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ 1CD90B56278C7E7A001C4FEA /* FeelsWidgetExtension.appex in Embed App Extensions */,
+ );
+ name = "Embed App Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
1CD90AEC278C7DDF001C4FEA /* Shared.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Shared.xcdatamodel; sourceTree = ""; };
1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeelsApp.swift; sourceTree = ""; };
- 1CD90AEE278C7DDF001C4FEA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
1CD90AEF278C7DDF001C4FEA /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; };
1CD90AF0278C7DE0001C4FEA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
1CD90AF5278C7DE0001C4FEA /* Feels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Feels.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -55,6 +107,28 @@
1CD90B0E278C7DE0001C4FEA /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
1CD90B12278C7DE0001C4FEA /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; };
1CD90B14278C7DE0001C4FEA /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = ""; };
+ 1CD90B32278C7E38001C4FEA /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; };
+ 1CD90B33278C7E38001C4FEA /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; };
+ 1CD90B34278C7E38001C4FEA /* AddMoodHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddMoodHeaderView.swift; sourceTree = ""; };
+ 1CD90B35278C7E38001C4FEA /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ 1CD90B36278C7E38001C4FEA /* HeaderStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderStatsView.swift; sourceTree = ""; };
+ 1CD90B45278C7E7A001C4FEA /* FeelsWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FeelsWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
+ 1CD90B49278C7E7A001C4FEA /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ 1CD90B4C278C7E7A001C4FEA /* FeelsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeelsWidget.swift; sourceTree = ""; };
+ 1CD90B4E278C7E7A001C4FEA /* FeelsWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = FeelsWidget.intentdefinition; sourceTree = ""; };
+ 1CD90B4F278C7E7A001C4FEA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 1CD90B51278C7E7A001C4FEA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 1CD90B5C278C7EAD001C4FEA /* Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Random.swift; sourceTree = ""; };
+ 1CD90B61278C7EBA001C4FEA /* Mood.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mood.swift; sourceTree = ""; };
+ 1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoodEntryExtension.swift; sourceTree = ""; };
+ 1CD90B69278C7F65001C4FEA /* Feels--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Feels--iOS--Info.plist"; sourceTree = ""; };
+ 1CD90B6A278C7F75001C4FEA /* Feels (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS).entitlements"; sourceTree = ""; };
+ 1CD90B6B278C7F78001C4FEA /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; };
+ 1CD90B6D278C7F89001C4FEA /* FeelsWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FeelsWidgetExtension.entitlements; sourceTree = ""; };
+ 1CD90B6F278C8000001C4FEA /* FeelsWidgetExtensionDev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = FeelsWidgetExtensionDev.entitlements; sourceTree = ""; };
+ 1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS)Dev.entitlements"; sourceTree = ""; };
+ 1CD90B75278C8119001C4FEA /* LocalNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -62,6 +136,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 1CD90B6C278C7F78001C4FEA /* CloudKit.framework in Frameworks */,
+ 1CD90B7B278C8146001C4FEA /* Charts in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -86,16 +162,33 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 1CD90B42278C7E7A001C4FEA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1CD90B6E278C7F8B001C4FEA /* CloudKit.framework in Frameworks */,
+ 1CD90B4A278C7E7A001C4FEA /* SwiftUI.framework in Frameworks */,
+ 1CD90B48278C7E7A001C4FEA /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1CD90AE5278C7DDF001C4FEA = {
isa = PBXGroup;
children = (
+ 1CD90B6A278C7F75001C4FEA /* Feels (iOS).entitlements */,
+ 1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */,
+ 1CD90B6D278C7F89001C4FEA /* FeelsWidgetExtension.entitlements */,
+ 1CD90B6F278C8000001C4FEA /* FeelsWidgetExtensionDev.entitlements */,
+ 1CD90B69278C7F65001C4FEA /* Feels--iOS--Info.plist */,
1CD90AEA278C7DDF001C4FEA /* Shared */,
+ 1CD90B4B278C7E7A001C4FEA /* FeelsWidget */,
1CD90AFC278C7DE0001C4FEA /* macOS */,
1CD90B05278C7DE0001C4FEA /* Tests iOS */,
1CD90B11278C7DE0001C4FEA /* Tests macOS */,
+ 1CD90B46278C7E7A001C4FEA /* Frameworks */,
1CD90AF6278C7DE0001C4FEA /* Products */,
);
sourceTree = "";
@@ -103,8 +196,11 @@
1CD90AEA278C7DDF001C4FEA /* Shared */ = {
isa = PBXGroup;
children = (
+ 1CD90B75278C8119001C4FEA /* LocalNotification.swift */,
+ 1CD90B31278C7E38001C4FEA /* views */,
+ 1CD90B60278C7EBA001C4FEA /* Models */,
+ 1CD90B5C278C7EAD001C4FEA /* Random.swift */,
1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */,
- 1CD90AEE278C7DDF001C4FEA /* ContentView.swift */,
1CD90AEF278C7DDF001C4FEA /* Persistence.swift */,
1CD90AF0278C7DE0001C4FEA /* Assets.xcassets */,
1CD90AEB278C7DDF001C4FEA /* Feels.xcdatamodeld */,
@@ -119,6 +215,7 @@
1CD90AFB278C7DE0001C4FEA /* Feels.app */,
1CD90B02278C7DE0001C4FEA /* Tests iOS.xctest */,
1CD90B0E278C7DE0001C4FEA /* Tests macOS.xctest */,
+ 1CD90B45278C7E7A001C4FEA /* FeelsWidgetExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -149,6 +246,48 @@
path = "Tests macOS";
sourceTree = "";
};
+ 1CD90B31278C7E38001C4FEA /* views */ = {
+ isa = PBXGroup;
+ children = (
+ 1CD90B32278C7E38001C4FEA /* SettingsView.swift */,
+ 1CD90B33278C7E38001C4FEA /* GraphView.swift */,
+ 1CD90B34278C7E38001C4FEA /* AddMoodHeaderView.swift */,
+ 1CD90B35278C7E38001C4FEA /* ContentView.swift */,
+ 1CD90B36278C7E38001C4FEA /* HeaderStatsView.swift */,
+ );
+ path = views;
+ sourceTree = "";
+ };
+ 1CD90B46278C7E7A001C4FEA /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 1CD90B6B278C7F78001C4FEA /* CloudKit.framework */,
+ 1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */,
+ 1CD90B49278C7E7A001C4FEA /* SwiftUI.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 1CD90B4B278C7E7A001C4FEA /* FeelsWidget */ = {
+ isa = PBXGroup;
+ children = (
+ 1CD90B4C278C7E7A001C4FEA /* FeelsWidget.swift */,
+ 1CD90B4E278C7E7A001C4FEA /* FeelsWidget.intentdefinition */,
+ 1CD90B4F278C7E7A001C4FEA /* Assets.xcassets */,
+ 1CD90B51278C7E7A001C4FEA /* Info.plist */,
+ );
+ path = FeelsWidget;
+ sourceTree = "";
+ };
+ 1CD90B60278C7EBA001C4FEA /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 1CD90B61278C7EBA001C4FEA /* Mood.swift */,
+ 1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */,
+ );
+ path = Models;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -159,12 +298,17 @@
1CD90AF1278C7DE0001C4FEA /* Sources */,
1CD90AF2278C7DE0001C4FEA /* Frameworks */,
1CD90AF3278C7DE0001C4FEA /* Resources */,
+ 1CD90B5A278C7E7A001C4FEA /* Embed App Extensions */,
);
buildRules = (
);
dependencies = (
+ 1CD90B55278C7E7A001C4FEA /* PBXTargetDependency */,
);
name = "Feels (iOS)";
+ packageProductDependencies = (
+ 1CD90B7A278C8146001C4FEA /* Charts */,
+ );
productName = "Feels (iOS)";
productReference = 1CD90AF5278C7DE0001C4FEA /* Feels.app */;
productType = "com.apple.product-type.application";
@@ -222,6 +366,23 @@
productReference = 1CD90B0E278C7DE0001C4FEA /* Tests macOS.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
+ 1CD90B44278C7E7A001C4FEA /* FeelsWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1CD90B57278C7E7A001C4FEA /* Build configuration list for PBXNativeTarget "FeelsWidgetExtension" */;
+ buildPhases = (
+ 1CD90B41278C7E7A001C4FEA /* Sources */,
+ 1CD90B42278C7E7A001C4FEA /* Frameworks */,
+ 1CD90B43278C7E7A001C4FEA /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = FeelsWidgetExtension;
+ productName = FeelsWidgetExtension;
+ productReference = 1CD90B45278C7E7A001C4FEA /* FeelsWidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -246,6 +407,9 @@
CreatedOnToolsVersion = 13.2.1;
TestTargetID = 1CD90AFA278C7DE0001C4FEA;
};
+ 1CD90B44278C7E7A001C4FEA = {
+ CreatedOnToolsVersion = 13.2.1;
+ };
};
};
buildConfigurationList = 1CD90AE9278C7DDF001C4FEA /* Build configuration list for PBXProject "Feels" */;
@@ -257,6 +421,9 @@
Base,
);
mainGroup = 1CD90AE5278C7DDF001C4FEA;
+ packageReferences = (
+ 1CD90B79278C8146001C4FEA /* XCRemoteSwiftPackageReference "Charts" */,
+ );
productRefGroup = 1CD90AF6278C7DE0001C4FEA /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -265,6 +432,7 @@
1CD90AFA278C7DE0001C4FEA /* Feels (macOS) */,
1CD90B01278C7DE0001C4FEA /* Tests iOS */,
1CD90B0D278C7DE0001C4FEA /* Tests macOS */,
+ 1CD90B44278C7E7A001C4FEA /* FeelsWidgetExtension */,
);
};
/* End PBXProject section */
@@ -300,6 +468,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 1CD90B43278C7E7A001C4FEA /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1CD90B50278C7E7A001C4FEA /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -307,10 +483,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 1CD90B39278C7E38001C4FEA /* GraphView.swift in Sources */,
+ 1CD90B76278C8119001C4FEA /* LocalNotification.swift in Sources */,
1CD90B16278C7DE0001C4FEA /* Feels.xcdatamodeld in Sources */,
+ 1CD90B5D278C7EAD001C4FEA /* Random.swift in Sources */,
+ 1CD90B63278C7EBA001C4FEA /* Mood.swift in Sources */,
+ 1CD90B53278C7E7A001C4FEA /* FeelsWidget.intentdefinition in Sources */,
+ 1CD90B3D278C7E38001C4FEA /* ContentView.swift in Sources */,
+ 1CD90B3F278C7E38001C4FEA /* HeaderStatsView.swift in Sources */,
+ 1CD90B3B278C7E38001C4FEA /* AddMoodHeaderView.swift in Sources */,
+ 1CD90B37278C7E38001C4FEA /* SettingsView.swift in Sources */,
+ 1CD90B66278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */,
1CD90B1C278C7DE0001C4FEA /* Persistence.swift in Sources */,
1CD90B18278C7DE0001C4FEA /* FeelsApp.swift in Sources */,
- 1CD90B1A278C7DE0001C4FEA /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -318,10 +503,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 1CD90B64278C7EBA001C4FEA /* Mood.swift in Sources */,
+ 1CD90B3A278C7E38001C4FEA /* GraphView.swift in Sources */,
1CD90B17278C7DE0001C4FEA /* Feels.xcdatamodeld in Sources */,
+ 1CD90B3E278C7E38001C4FEA /* ContentView.swift in Sources */,
+ 1CD90B40278C7E38001C4FEA /* HeaderStatsView.swift in Sources */,
+ 1CD90B3C278C7E38001C4FEA /* AddMoodHeaderView.swift in Sources */,
+ 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 */,
- 1CD90B1B278C7DE0001C4FEA /* ContentView.swift in Sources */,
+ 1CD90B5E278C7EAD001C4FEA /* Random.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -343,6 +536,21 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 1CD90B41278C7E7A001C4FEA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1CD90B65278C7EBA001C4FEA /* Mood.swift in Sources */,
+ 1CD90B5F278C7EAD001C4FEA /* Random.swift in Sources */,
+ 1CD90B5B278C7E91001C4FEA /* Persistence.swift in Sources */,
+ 1CD90B68278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */,
+ 1CD90B78278C8119001C4FEA /* LocalNotification.swift in Sources */,
+ 1CD90B71278C80CA001C4FEA /* Feels.xcdatamodeld in Sources */,
+ 1CD90B52278C7E7A001C4FEA /* FeelsWidget.intentdefinition in Sources */,
+ 1CD90B4D278C7E7A001C4FEA /* FeelsWidget.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -356,6 +564,11 @@
target = 1CD90AFA278C7DE0001C4FEA /* Feels (macOS) */;
targetProxy = 1CD90B0F278C7DE0001C4FEA /* PBXContainerItemProxy */;
};
+ 1CD90B55278C7E7A001C4FEA /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 1CD90B44278C7E7A001C4FEA /* FeelsWidgetExtension */;
+ targetProxy = 1CD90B54278C7E7A001C4FEA /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -473,13 +686,16 @@
1CD90B23278C7DE0001C4FEA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = "Feels (iOS)Dev.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = V3PF3M6B6U;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "Feels--iOS--Info.plist";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -491,7 +707,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.88oak.Feels;
+ PRODUCT_BUNDLE_IDENTIFIER = com.88oak.FeelsDev;
PRODUCT_NAME = Feels;
SDKROOT = iphoneos;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -503,13 +719,16 @@
1CD90B24278C7DE0001C4FEA /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = "Feels (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = V3PF3M6B6U;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = "Feels--iOS--Info.plist";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -666,6 +885,67 @@
};
name = Release;
};
+ 1CD90B58278C7E7A001C4FEA /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_ENTITLEMENTS = FeelsWidgetExtensionDev.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = V3PF3M6B6U;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = FeelsWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = FeelsWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.88oak.FeelsDev.FeelsWidgetDev;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 1CD90B59278C7E7A001C4FEA /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_ENTITLEMENTS = FeelsWidgetExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = V3PF3M6B6U;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = FeelsWidget/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = FeelsWidget;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 15.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.88oak.Feels.FeelsWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -714,8 +994,36 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 1CD90B57278C7E7A001C4FEA /* Build configuration list for PBXNativeTarget "FeelsWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1CD90B58278C7E7A001C4FEA /* Debug */,
+ 1CD90B59278C7E7A001C4FEA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
+/* Begin XCRemoteSwiftPackageReference section */
+ 1CD90B79278C8146001C4FEA /* XCRemoteSwiftPackageReference "Charts" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/danielgindi/Charts";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 3.0.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 1CD90B7A278C8146001C4FEA /* Charts */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 1CD90B79278C8146001C4FEA /* XCRemoteSwiftPackageReference "Charts" */;
+ productName = Charts;
+ };
+/* End XCSwiftPackageProductDependency section */
+
/* Begin XCVersionGroup section */
1CD90AEB278C7DDF001C4FEA /* Feels.xcdatamodeld */ = {
isa = XCVersionGroup;
diff --git a/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..0878b14
--- /dev/null
+++ b/Feels.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,16 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "Charts",
+ "repositoryURL": "https://github.com/danielgindi/Charts",
+ "state": {
+ "branch": null,
+ "revision": "66546404a6739173b8e436ab6bc1f2897cd08594",
+ "version": "3.6.0"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist
index b37aaf7..ff9be8c 100644
--- a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -14,6 +14,53 @@
orderHint
1
+ FeelsWidgetExtension.xcscheme_^#shared#^_
+
+ orderHint
+ 2
+
+ PlaygroundChart (Playground) 1.xcscheme
+
+ isShown
+
+ orderHint
+ 4
+
+ PlaygroundChart (Playground) 2.xcscheme
+
+ isShown
+
+ orderHint
+ 5
+
+ PlaygroundChart (Playground) 3.xcscheme
+
+ isShown
+
+ orderHint
+ 6
+
+ PlaygroundChart (Playground) 4.xcscheme
+
+ isShown
+
+ orderHint
+ 7
+
+ PlaygroundChart (Playground) 5.xcscheme
+
+ isShown
+
+ orderHint
+ 8
+
+ PlaygroundChart (Playground).xcscheme
+
+ isShown
+
+ orderHint
+ 3
+
diff --git a/FeelsWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/FeelsWidget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/FeelsWidget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FeelsWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/FeelsWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/FeelsWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FeelsWidget/Assets.xcassets/Contents.json b/FeelsWidget/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/FeelsWidget/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FeelsWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/FeelsWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/FeelsWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FeelsWidget/FeelsWidget.intentdefinition b/FeelsWidget/FeelsWidget.intentdefinition
new file mode 100644
index 0000000..bdb4045
--- /dev/null
+++ b/FeelsWidget/FeelsWidget.intentdefinition
@@ -0,0 +1,59 @@
+
+
+
+
+ INEnums
+
+ INIntentDefinitionModelVersion
+ 1.2
+ INIntentDefinitionNamespace
+ 88xZPY
+ INIntentDefinitionSystemVersion
+ 20A294
+ INIntentDefinitionToolsBuildVersion
+ 12A6144
+ INIntentDefinitionToolsVersion
+ 12.0
+ INIntents
+
+
+ INIntentCategory
+ information
+ INIntentDescriptionID
+ tVvJ9c
+ INIntentEligibleForWidgets
+
+ INIntentIneligibleForSuggestions
+
+ INIntentName
+ Configuration
+ INIntentResponse
+
+ INIntentResponseCodes
+
+
+ INIntentResponseCodeName
+ success
+ INIntentResponseCodeSuccess
+
+
+
+ INIntentResponseCodeName
+ failure
+
+
+
+ INIntentTitle
+ Configuration
+ INIntentTitleID
+ gpCwrM
+ INIntentType
+ Custom
+ INIntentVerb
+ View
+
+
+ INTypes
+
+
+
diff --git a/FeelsWidget/FeelsWidget.swift b/FeelsWidget/FeelsWidget.swift
new file mode 100644
index 0000000..f972641
--- /dev/null
+++ b/FeelsWidget/FeelsWidget.swift
@@ -0,0 +1,259 @@
+//
+// FeelsWidget.swift
+// FeelsWidget
+//
+// Created by Trey Tartt on 1/7/22.
+//
+
+import WidgetKit
+import SwiftUI
+import Intents
+import CoreData
+
+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: 5)
+ return SimpleEntry(date: date, configuration: ConfigurationIntent(), mood: moodEntry)
+ }
+
+ func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
+ if context.isPreview {
+
+ }
+ let date = Date()
+ let moodEntry = PersistenceController.shared.moodEntries(forStartDate: date, count: 5)
+
+ let entry = SimpleEntry(date: date, configuration: configuration, mood: moodEntry)
+ completion(entry)
+ }
+
+ func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) {
+ var entries: [SimpleEntry] = []
+
+ 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)
+ entries.append(entry)
+
+ let timeline = Timeline(entries: entries, policy: .after(Random.widgetUpdateTime))
+ completion(timeline)
+ }
+}
+
+struct SimpleEntry: TimelineEntry {
+ let date: Date
+ let configuration: ConfigurationIntent
+ let mood: [MoodEntry]
+ let showStats: Bool
+
+ init(date: Date, configuration: ConfigurationIntent, mood: [MoodEntry], showStats: Bool = false) {
+ self.date = date
+ self.configuration = configuration
+ self.mood = mood
+ 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:
+ LargeWidgetView(entry: entry)
+ @unknown default:
+ fatalError()
+ }
+ }
+ }
+}
+
+struct SmallWidgetView: View {
+ var entry: Provider.Entry
+
+ var body: some View {
+ VStack {
+ EntryCardCollectionView(moodEntries: Array([entry.mood.first!]))
+ .padding()
+ }
+ }
+}
+
+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 body: some View {
+ VStack {
+ Spacer()
+
+ HStack {
+ Text(firstGroup.first?.date ?? Date(), formatter: formatter)
+ .font(.system(.footnote))
+ Text(" - ")
+ .font(.system(.footnote))
+ Text(firstGroup.last?.date ?? Date(), formatter: formatter)
+ .font(.system(.footnote))
+ }
+ .frame(minWidth: 0, maxWidth: .infinity)
+ .multilineTextAlignment(.leading)
+
+ EntryCardCollectionView(moodEntries: firstGroup)
+ .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 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?.date ?? Date(), formatter: formatter)
+ .font(.system(.footnote))
+ Text(" - ")
+ .font(.system(.footnote))
+ Text(firstGroup.last?.date ?? 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?.date ?? Date(), formatter: formatter)
+ .font(.system(.footnote))
+ Text(" - ")
+ Text(lastGroup.last?.date ?? Date(), formatter: formatter)
+ .font(.system(.footnote))
+ }
+ .frame(minWidth: 0, maxWidth: .infinity)
+ .multilineTextAlignment(.leading)
+
+ EntryCardCollectionView(moodEntries: lastGroup)
+ .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 body: some View {
+ moodEntry.mood.icon.font(.system(size: 50))
+ }
+}
+
+@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, .systemLarge])
+ }
+}
+
+struct FeelsWidget_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ FeelsWidgetEntryView(entry: SimpleEntry(date: Date(),
+ configuration: ConfigurationIntent(),
+ mood: PersistenceController.shared.randomEntries(count: 1)))
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+ .environment(\.sizeCategory, .small)
+
+ FeelsWidgetEntryView(entry: SimpleEntry(date: Date(),
+ configuration: ConfigurationIntent(),
+ mood: PersistenceController.shared.randomEntries(count: 3)))
+ .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)
+ }
+ }
+}
diff --git a/FeelsWidget/Info.plist b/FeelsWidget/Info.plist
new file mode 100644
index 0000000..0f118fb
--- /dev/null
+++ b/FeelsWidget/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/FeelsWidgetExtension.entitlements b/FeelsWidgetExtension.entitlements
new file mode 100644
index 0000000..2ead4a3
--- /dev/null
+++ b/FeelsWidgetExtension.entitlements
@@ -0,0 +1,16 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.icloud-container-identifiers
+
+ iCloud.com.88oak.feels
+
+ com.apple.developer.icloud-services
+
+ CloudKit
+
+
+
diff --git a/FeelsWidgetExtensionDev.entitlements b/FeelsWidgetExtensionDev.entitlements
new file mode 100644
index 0000000..1662af0
--- /dev/null
+++ b/FeelsWidgetExtensionDev.entitlements
@@ -0,0 +1,16 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.icloud-container-identifiers
+
+ iCloud.com.88oak.feelsDev
+
+ com.apple.developer.icloud-services
+
+ CloudKit
+
+
+
diff --git a/Shared/ContentView.swift b/Shared/ContentView.swift
deleted file mode 100644
index 281b0c8..0000000
--- a/Shared/ContentView.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// ContentView.swift
-// Shared
-//
-// Created by Trey Tartt on 1/10/22.
-//
-
-import SwiftUI
-import CoreData
-
-struct ContentView: View {
- @Environment(\.managedObjectContext) private var viewContext
-
- @FetchRequest(
- sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
- animation: .default)
- private var items: FetchedResults-
-
- var body: some View {
- NavigationView {
- List {
- ForEach(items) { item in
- NavigationLink {
- Text("Item at \(item.timestamp!, formatter: itemFormatter)")
- } label: {
- Text(item.timestamp!, formatter: itemFormatter)
- }
- }
- .onDelete(perform: deleteItems)
- }
- .toolbar {
-#if os(iOS)
- ToolbarItem(placement: .navigationBarTrailing) {
- EditButton()
- }
-#endif
- ToolbarItem {
- Button(action: addItem) {
- Label("Add Item", systemImage: "plus")
- }
- }
- }
- Text("Select an item")
- }
- }
-
- private func addItem() {
- withAnimation {
- let newItem = Item(context: viewContext)
- newItem.timestamp = 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)")
- }
- }
- }
-
- private func deleteItems(offsets: IndexSet) {
- withAnimation {
- offsets.map { items[$0] }.forEach(viewContext.delete)
-
- 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)")
- }
- }
- }
-}
-
-private let itemFormatter: DateFormatter = {
- let formatter = DateFormatter()
- formatter.dateStyle = .short
- formatter.timeStyle = .medium
- return formatter
-}()
-
-struct ContentView_Previews: PreviewProvider {
- static var previews: some View {
- ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
- }
-}
diff --git a/Shared/Feels.xcdatamodeld/Shared.xcdatamodel/contents b/Shared/Feels.xcdatamodeld/Shared.xcdatamodel/contents
index e8d6ec8..432945a 100644
--- a/Shared/Feels.xcdatamodeld/Shared.xcdatamodel/contents
+++ b/Shared/Feels.xcdatamodeld/Shared.xcdatamodel/contents
@@ -1,9 +1,11 @@
-
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/Shared/FeelsApp.swift b/Shared/FeelsApp.swift
index 6d499dc..ccfe0d7 100644
--- a/Shared/FeelsApp.swift
+++ b/Shared/FeelsApp.swift
@@ -2,19 +2,67 @@
// FeelsApp.swift
// Shared
//
-// Created by Trey Tartt on 1/10/22.
+// Created by Trey Tartt on 1/5/22.
//
import SwiftUI
+import BackgroundTasks
@main
struct FeelsApp: App {
+ @Environment(\.scenePhase) private var scenePhase
let persistenceController = PersistenceController.shared
-
+
+ init() {
+// persistenceController.fillInMissingDates()
+
+ BGTaskScheduler.shared.cancelAllTaskRequests()
+ BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTask.updateDBMissingID, using: nil) { (task) in
+ BGTask.runFillInMissingDatesTask(task: task as! BGProcessingTask)
+ }
+ }
+
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
+ }.onChange(of: scenePhase) { phase in
+ if phase == .background {
+ BGTask.scheduleBackgroundProcessing()
+ print("background")
+ }
+ }
+ }
+}
+
+class BGTask {
+ static let updateDBMissingID = "com.88oak.dbUpdateMissing"
+
+ class func runFillInMissingDatesTask(task: BGProcessingTask) {
+ BGTask.scheduleBackgroundProcessing()
+
+ task.expirationHandler = {
+ task.setTaskCompleted(success: false)
+ }
+
+ PersistenceController.shared.fillInMissingDates()
+ task.setTaskCompleted(success: true)
+ }
+
+ @available(iOS 13.0, *)
+ class func scheduleBackgroundProcessing() {
+ let request = BGProcessingTaskRequest(identifier: BGTask.updateDBMissingID)
+ request.requiresNetworkConnectivity = false
+ request.requiresExternalPower = false
+
+ var runDate = Calendar.current.date(byAdding: .day, value: 1, to: Date())
+ runDate = Calendar.current.date(bySettingHour: 0, minute: 1, second: 0, of: runDate!)
+ request.earliestBeginDate = runDate
+
+ do {
+ try BGTaskScheduler.shared.submit(request)
+ } catch {
+ print("Could not schedule image fetch: (error)")
}
}
}
diff --git a/Shared/LocalNotification.swift b/Shared/LocalNotification.swift
new file mode 100644
index 0000000..c5a41b2
--- /dev/null
+++ b/Shared/LocalNotification.swift
@@ -0,0 +1,143 @@
+//
+// LocalNotification.swift
+// Feels
+//
+// Created by Trey Tartt on 1/8/22.
+//
+
+import Foundation
+import UserNotifications
+
+class LocalNotification {
+ public enum ActionType: String {
+ case horrible = "HORRIBLE_ACTION"
+ case bad = "BAD_ACTION"
+ case average = "AVERAGE_ACTION"
+ case good = "GOOD_ACTION"
+ case great = "GREAT_ACTION"
+ }
+ static let categoryName = "MOOD_UPDATE"
+
+ public class func testIfEnabled(completion: @escaping (Result) -> Void) {
+ UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
+ if success {
+ completion(.success(true))
+ } else if let error = error {
+ completion(.failure(error))
+ }
+ }
+ }
+
+ public class func scheduleReminder(atTime time: Date) {
+ let _ = LocalNotification.createNotificationCategory()
+
+ let notificationContent = UNMutableNotificationContent()
+ notificationContent.title = "How was your day?"
+ notificationContent.badge = NSNumber(value: 1)
+ notificationContent.sound = .default
+ notificationContent.categoryIdentifier = LocalNotification.categoryName
+
+ let calendar = Calendar.current
+ let time = calendar.dateComponents([.hour,.minute], from: time)
+
+ var datComp = DateComponents()
+ datComp.hour = time.hour
+ datComp.minute = time.minute
+
+ let trigger = UNCalendarNotificationTrigger(dateMatching: datComp, repeats: true)
+ let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: trigger)
+ UNUserNotificationCenter.current().add(request) { (error : Error?) in
+ if let theError = error {
+ print(theError.localizedDescription)
+ }
+ }
+ }
+
+ private class func createNotificationCategory() -> UNNotificationCategory {
+ let moodCategory =
+ UNNotificationCategory(identifier: LocalNotification.categoryName,
+ actions: [horribleAction, badAction, averageAction, goodAction, greatAction],
+ intentIdentifiers: [],
+ hiddenPreviewsBodyPlaceholder: "",
+ options: .customDismissAction)
+ // Register the notification type.
+ let notificationCenter = UNUserNotificationCenter.current()
+ notificationCenter.setNotificationCategories([moodCategory])
+
+ return moodCategory
+ }
+
+ private class var horribleAction: UNNotificationAction {
+ let acceptAction = UNNotificationAction(identifier: ActionType.horrible.rawValue,
+ title: "Horrible",
+ options: [])
+ return acceptAction
+ }
+
+ private class var badAction: UNNotificationAction {
+ let acceptAction = UNNotificationAction(identifier: ActionType.bad.rawValue,
+ title: "Bad",
+ options: [])
+ return acceptAction
+ }
+
+ private class var averageAction: UNNotificationAction {
+ let acceptAction = UNNotificationAction(identifier: ActionType.average.rawValue,
+ title: "Average",
+ options: [])
+ return acceptAction
+ }
+
+ private class var goodAction: UNNotificationAction {
+ let acceptAction = UNNotificationAction(identifier: ActionType.good.rawValue,
+ title: "Good",
+ options: [])
+ return acceptAction
+ }
+
+ private class var greatAction: UNNotificationAction {
+ let acceptAction = UNNotificationAction(identifier: ActionType.great.rawValue,
+ title: "Great",
+ options: [])
+ return acceptAction
+ }
+
+ public class func removeNotificaiton() {
+ UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
+ }
+}
+
+class NotificationDelegate: NSObject, ObservableObject, UNUserNotificationCenterDelegate {
+ @Published var notificationCounter = 0
+
+ override init() {
+ super.init()
+ UNUserNotificationCenter.current().delegate = self
+ }
+
+ func requestAuthorization() {
+
+ }
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+ completionHandler([.badge, .banner, .sound])
+ }
+
+ func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+ if let action = LocalNotification.ActionType(rawValue: response.actionIdentifier) {
+ switch action {
+ case .horrible:
+ PersistenceController.shared.add(mood: .horrible, forDate: Date())
+ case .bad:
+ PersistenceController.shared.add(mood: .bad, forDate: Date())
+ case .average:
+ PersistenceController.shared.add(mood: .average, forDate: Date())
+ case .good:
+ PersistenceController.shared.add(mood: .good, forDate: Date())
+ case .great:
+ PersistenceController.shared.add(mood: .great, forDate: Date())
+ }
+ }
+ completionHandler()
+ }
+}
diff --git a/Shared/Models/Mood.swift b/Shared/Models/Mood.swift
new file mode 100644
index 0000000..1c407d8
--- /dev/null
+++ b/Shared/Models/Mood.swift
@@ -0,0 +1,63 @@
+//
+// Mood.swift
+// Feels
+//
+// Created by Trey Tartt on 1/5/22.
+//
+
+import Foundation
+import SwiftUI
+
+enum Mood: Int {
+ case horrible
+ case bad
+ case average
+ case good
+ case great
+ case missing
+
+ var strValue: String {
+ switch self {
+ case .horrible:
+ return "Horrible"
+ case .bad:
+ return "Bad"
+ case .average:
+ return "Average"
+ case .good:
+ return "Good"
+ case .great:
+ return "Great"
+ case .missing:
+ return "Missing"
+ }
+ }
+
+ static var allValues: [Mood] {
+ return [Mood.horrible, Mood.bad, Mood.average, Mood.good, Mood.great]
+ }
+
+ var icon: Text {
+ switch self {
+
+ case .horrible:
+ return Text("😫")
+ case .bad:
+ return Text("🙁")
+ case .average:
+ return Text("😐")
+ case .good:
+ return Text("🙂")
+ case .great:
+ return Text("😆")
+ case .missing:
+ return Text("🚫")
+ }
+ }
+}
+
+extension Mood: Identifiable {
+ var id: Int {
+ rawValue
+ }
+}
diff --git a/Shared/Models/MoodEntryExtension.swift b/Shared/Models/MoodEntryExtension.swift
new file mode 100644
index 0000000..b22725a
--- /dev/null
+++ b/Shared/Models/MoodEntryExtension.swift
@@ -0,0 +1,19 @@
+//
+// MoodEntryExtension.swift
+// Feels
+//
+// Created by Trey Tartt on 1/5/22.
+//
+
+import Foundation
+
+
+extension MoodEntry {
+ var moodString: String {
+ return Mood.init(rawValue: Int(self.moodValue))?.strValue ?? "NA"
+ }
+
+ var mood: Mood {
+ return Mood.init(rawValue: Int(self.moodValue))!
+ }
+}
diff --git a/Shared/Mooood.xcdatamodeld/.xccurrentversion b/Shared/Mooood.xcdatamodeld/.xccurrentversion
new file mode 100644
index 0000000..775cb51
--- /dev/null
+++ b/Shared/Mooood.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+
+
+
+
+ _XCCurrentVersionName
+ Shared.xcdatamodel
+
+
diff --git a/Shared/Mooood.xcdatamodeld/Shared.xcdatamodel/contents b/Shared/Mooood.xcdatamodeld/Shared.xcdatamodel/contents
new file mode 100644
index 0000000..432945a
--- /dev/null
+++ b/Shared/Mooood.xcdatamodeld/Shared.xcdatamodel/contents
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Shared/Persistence.swift b/Shared/Persistence.swift
index 20ac4b0..70b8df5 100644
--- a/Shared/Persistence.swift
+++ b/Shared/Persistence.swift
@@ -2,20 +2,71 @@
// Persistence.swift
// Shared
//
-// Created by Trey Tartt on 1/10/22.
+// Created by Trey Tartt on 1/5/22.
//
import CoreData
struct PersistenceController {
- static let shared = PersistenceController()
+ static let shared = PersistenceController.persistenceController
+
+ private static var persistenceController: PersistenceController {
+#if targetEnvironment(simulator)
+ return PersistenceController(inMemory: false)
+#else
+ return PersistenceController(inMemory: false)
+#endif
- static var preview: PersistenceController = {
- let result = PersistenceController(inMemory: true)
- let viewContext = result.container.viewContext
- for _ in 0..<10 {
- let newItem = Item(context: viewContext)
+ }
+
+ public var viewContext: NSManagedObjectContext {
+ return PersistenceController.shared.container.viewContext
+ }
+
+ public func add(mood: Mood, forDate date: Date) {
+ let newItem = MoodEntry(context: viewContext)
+ newItem.timestamp = Date()
+ newItem.moodValue = Int16(mood.rawValue)
+ newItem.date = date
+
+ do {
+ try viewContext.save()
+ } catch {
+ let nsError = error as NSError
+ fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
+ }
+ }
+
+ public func moodEntries(forStartDate date: Date, count: Int) -> [MoodEntry] {
+ let fetchRequest = NSFetchRequest(entityName: "MoodEntry")
+
+ fetchRequest.fetchLimit = count
+ fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", 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
+ }
+ }
+
+ func populateTestData() {
+ for idx in 1..<25 {
+ let newItem = MoodEntry(context: viewContext)
newItem.timestamp = Date()
+ newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
+ newItem.date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
}
do {
try viewContext.save()
@@ -25,11 +76,69 @@ struct PersistenceController {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
- return result
- }()
-
+ }
+
+ func populateMemory() {
+ for idx in 1..<25 {
+ let newItem = MoodEntry(context: viewContext)
+ newItem.timestamp = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
+ newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
+ newItem.date = Calendar.current.date(byAdding: .day, value: -idx, to: 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)")
+ }
+ }
+
+ func fillInMissingDates() {
+ let fetchRequest = NSFetchRequest(entityName: "MoodEntry")
+ fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
+ let entries = try! viewContext.fetch(fetchRequest)
+
+ if let earliestDate = entries.last?.date {
+ let diffInDays = Calendar.current.dateComponents([.day], from: earliestDate, to: Date()).day
+
+ for idx in 1.. = NSFetchRequest(entityName: "MoodEntry")
+ let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
+
+ do {
+ try viewContext.executeAndMergeChanges(using: deleteRequest)
+ try viewContext.save()
+ } catch let error as NSError {
+ fatalError("Unresolved error \(error), \(error.userInfo)")
+ }
+ }
+
+ public func randomEntries(count: Int) -> [MoodEntry] {
+ var entries = [MoodEntry]()
+
+ for idx in 0..
+
+ var body: some View {
+ TabView {
+ mainView
+ .tabItem {
+ Label("Main", systemImage: "list.dash")
+ }
+
+ GraphView()
+ .tabItem {
+ Label("Graph", systemImage: "chart.line.uptrend.xyaxis")
+ }
+ }
+ }
+
+ private var settingsButtonView: some View {
+ HStack {
+ Spacer()
+ Button(action: {
+ showingSheet.toggle()
+ }, label: {
+ Image(systemName: "gear")
+ .foregroundColor(Color(UIColor.systemGray))
+ .font(.system(size: 20))
+ }).sheet(isPresented: $showingSheet) {
+ SettingsView()
+ }.padding(.trailing)
+ }
+ }
+
+ private var listView: some View {
+ List {
+ ForEach(items) { item in
+ HStack {
+ item.mood.icon
+ .font(.system(size: 50))
+ VStack {
+ Text("\(item.moodString)")
+ .font(.title)
+ .foregroundColor(Color(UIColor.systemGray))
+ .frame(maxWidth: .infinity, alignment: .leading)
+ Text(item.date!, style: .date)
+ .font(.body)
+ .foregroundColor(Color(UIColor.label))
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .padding(.leading)
+ }
+ }
+ .onDelete(perform: deleteItems)
+ }
+ }
+
+ private var mainView: some View {
+ VStack{
+ settingsButtonView
+ if shouldShowTodayInput() {
+ AddMoodHeaderView()
+ .frame(minHeight: 85, maxHeight: 180)
+ .frame(minWidth: 0, maxWidth: .infinity)
+ } else {
+ HeaderStatsView(entries: [
+ //x - position of a bar, y - height of a bar
+ BarChartDataEntry(x: 1, y: 1),
+ BarChartDataEntry(x: 2, y: 5),
+ BarChartDataEntry(x: 3, y: 2),
+ BarChartDataEntry(x: 4, y: 4),
+ BarChartDataEntry(x: 5, y: 1)
+ ])
+ .frame(minHeight: 85, maxHeight: 180)
+ }
+ listView
+ }
+ }
+
+ private func deleteItems(offsets: IndexSet) {
+ withAnimation {
+ offsets.map { items[$0] }.forEach(viewContext.delete)
+
+ 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)")
+ }
+ }
+ }
+
+ private func shouldShowTodayInput() -> Bool {
+ let fetchRequest = NSFetchRequest(entityName: "MoodEntry")
+
+ var calendar = Calendar.current
+ calendar.timeZone = NSTimeZone.local
+
+ // Get today's beginning & end
+ let dateFrom = calendar.startOfDay(for: Date()) // eg. 2016-10-10 00:00:00
+ let dateTo = calendar.date(byAdding: .day, value: 1, to: dateFrom)!
+ // Note: Times are printed in UTC. Depending on where you live it won't print 00:00:00 but it will work with UTC times which can be converted to local time
+
+ // Set predicate as date being today's date
+ let fromPredicate = NSPredicate(format: "%@ <= %K", dateFrom as NSDate, #keyPath(MoodEntry.date))
+ let toPredicate = NSPredicate(format: "%K < %@", #keyPath(MoodEntry.date), dateTo as NSDate)
+ let datePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
+ fetchRequest.predicate = datePredicate
+ let entries = try! self.viewContext.count(for: fetchRequest)
+
+ return entries == 0
+ }
+}
+
+private let itemFormatter: DateFormatter = {
+ let formatter = DateFormatter()
+ formatter.dateStyle = .short
+ formatter.timeStyle = .medium
+ return formatter
+}()
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView().environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
+ .onAppear(perform: {
+ PersistenceController.shared.populateMemory()
+ })
+
+ ContentView()
+ .preferredColorScheme(.dark)
+ .environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
+ }
+}
diff --git a/Shared/views/GraphView.swift b/Shared/views/GraphView.swift
new file mode 100644
index 0000000..894ef0e
--- /dev/null
+++ b/Shared/views/GraphView.swift
@@ -0,0 +1,16 @@
+//
+// GraphView.swift
+// Feels
+//
+// Created by Trey Tartt on 1/8/22.
+//
+
+import Foundation
+import SwiftUI
+import CoreData
+
+struct GraphView: View {
+ var body: some View {
+ Text("this is a graph")
+ }
+}
diff --git a/Shared/views/HeaderStatsView.swift b/Shared/views/HeaderStatsView.swift
new file mode 100644
index 0000000..e449b0f
--- /dev/null
+++ b/Shared/views/HeaderStatsView.swift
@@ -0,0 +1,82 @@
+//
+// HeaderStatsView.swift
+// Feels
+//
+// Created by Trey Tartt on 1/8/22.
+//
+
+import SwiftUI
+import Charts
+
+struct HeaderStatsView : UIViewRepresentable {
+ //Bar chart accepts data as array of BarChartDataEntry objects
+ var entries : [BarChartDataEntry]
+ // this func is required to conform to UIViewRepresentable protocol
+ func makeUIView(context: Context) -> BarChartView {
+ //crate new chart
+ let chart = BarChartView()
+ chart.drawGridBackgroundEnabled = false
+ chart.drawValueAboveBarEnabled = false
+
+ chart.xAxis.drawAxisLineEnabled = false
+ chart.xAxis.labelTextColor = .clear
+
+ chart.rightAxis.drawAxisLineEnabled = false
+ chart.rightAxis.labelTextColor = .clear
+
+ chart.leftAxis.drawAxisLineEnabled = false
+ chart.leftAxis.labelTextColor = .clear
+
+ chart.xAxis.drawGridLinesEnabled = false
+ chart.leftAxis.drawGridLinesEnabled = false
+ chart.leftAxis.axisLineColor = .clear
+ chart.rightAxis.axisLineColor = .clear
+
+ chart.legend.textColor = .clear
+ chart.legend.enabled = false
+
+ chart.drawBordersEnabled = false
+ chart.drawMarkers = false
+// chart.yAxis.drawGridLinesEnabled = false
+ chart.rightAxis.drawGridLinesEnabled = false
+ chart.borderColor = .clear
+ //it is convenient to form chart data in a separate func
+ chart.data = addData()
+ return chart
+ }
+
+ // this func is required to conform to UIViewRepresentable protocol
+ func updateUIView(_ uiView: BarChartView, context: Context) {
+ //when data changes chartd.data update is required
+ uiView.data = addData()
+ }
+
+ func addData() -> BarChartData{
+ let data = BarChartData()
+ //BarChartDataSet is an object that contains information about your data, styling and more
+ let dataSet = BarChartDataSet(entries: entries)
+ // change bars color to green
+ dataSet.colors = [NSUIColor.green]
+ //change data label
+ data.addDataSet(dataSet)
+ return data
+ }
+
+ typealias UIViewType = BarChartView
+
+}
+
+
+
+struct HeaderStatsView_Previews: PreviewProvider {
+ static var previews: some View {
+ HeaderStatsView(entries: [
+ //x - position of a bar, y - height of a bar
+ BarChartDataEntry(x: 1, y: 1),
+ BarChartDataEntry(x: 2, y: 4),
+ BarChartDataEntry(x: 3, y: 3),
+ BarChartDataEntry(x: 4, y: 2),
+ BarChartDataEntry(x: 5, y: 1)
+ ]).frame(minHeight: 85, maxHeight: 90)
+ }
+}
diff --git a/Shared/views/SettingsView.swift b/Shared/views/SettingsView.swift
new file mode 100644
index 0000000..cf23c91
--- /dev/null
+++ b/Shared/views/SettingsView.swift
@@ -0,0 +1,142 @@
+//
+// SettingsView.swift
+// Feels
+//
+// Created by Trey Tartt on 1/8/22.
+//
+
+import SwiftUI
+
+struct SettingsView: View {
+ @Environment(\.dismiss) var dismiss
+
+ @State private var currentDate = Date() {
+ didSet {
+ if self.showReminder {
+ LocalNotification.scheduleReminder(atTime: self.currentDate)
+ }
+ }
+ }
+
+ @State private var showReminder: Bool = false {
+ didSet {
+ if self.showReminder {
+ LocalNotification.testIfEnabled(completion: { result in
+ switch result{
+ case .success(_):
+ LocalNotification.scheduleReminder(atTime: self.currentDate)
+ case .failure(_):
+ // show error
+ break
+ }
+ })
+ } else {
+ LocalNotification.removeNotificaiton()
+ }
+ }
+ }
+
+ var body: some View { ZStack {
+ Color(UIColor.secondarySystemBackground)
+
+ VStack {
+ closeButtonView
+ .padding()
+ notificationCell
+ randomShitCell
+ addTestDataCell
+ clearDB
+ whyBackgroundMode
+ Spacer()
+ }
+ .padding()
+ }
+ }
+
+ private var closeButtonView: some View {
+ HStack{
+ Spacer()
+ Button(action: {
+ dismiss()
+ }, label: {
+ Text("Exit")
+ .font(.body)
+ .foregroundColor(Color(UIColor.systemBlue))
+ })
+ }
+ }
+
+ private var notificationCell: some View {
+ ZStack {
+ Color(UIColor.systemBackground)
+ VStack {
+ Toggle("Would you like to be reminded?", isOn: $showReminder)
+ .padding()
+ DatePicker("", selection: $currentDate, displayedComponents: .hourAndMinute)
+ .disabled(showReminder == false)
+ .padding()
+ }
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
+ }
+
+ private var randomShitCell: some View {
+ ZStack {
+ Color(UIColor.systemBackground)
+ VStack {
+ Text("random shit")
+ .padding()
+ }
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
+ }
+
+ private var addTestDataCell: some View {
+ ZStack {
+ Color(UIColor.systemBackground)
+ Button(action: {
+ PersistenceController.shared.populateTestData()
+ }, label: {
+ Text("Add test data")
+ })
+ .padding()
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
+ }
+
+ private var clearDB: some View {
+ ZStack {
+ Color(UIColor.systemBackground)
+ Button(action: {
+ PersistenceController.shared.clearDB()
+ }, label: {
+ Text("Clear DB")
+ })
+ .padding()
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
+ }
+
+ private var whyBackgroundMode: some View {
+ ZStack {
+ Color(UIColor.systemBackground)
+ Text("we do bg mode b/c we can")
+ .padding()
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
+ }
+}
+
+struct SettingsView_Previews: PreviewProvider {
+ static var previews: some View {
+ SettingsView()
+
+ SettingsView()
+ .preferredColorScheme(.dark)
+ }
+}