init commit - bring project over from Mood

This commit is contained in:
Trey t
2022-01-10 09:04:38 -06:00
parent ff5d9bea18
commit 58697bf965
30 changed files with 1840 additions and 121 deletions

16
Feels (iOS).entitlements Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oak.feels</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oak.feelsDev</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
</dict>
</plist>

11
Feels--iOS--Info.plist Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>processing</string>
<string>remote-notification</string>
</array>
</dict>
</plist>

View File

@@ -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 = "<group>"; };
1CD90AED278C7DDF001C4FEA /* FeelsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeelsApp.swift; sourceTree = "<group>"; };
1CD90AEE278C7DDF001C4FEA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
1CD90AEF278C7DDF001C4FEA /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
1CD90AF0278C7DE0001C4FEA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
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 = "<group>"; };
1CD90B14278C7DE0001C4FEA /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = "<group>"; };
1CD90B32278C7E38001C4FEA /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
1CD90B33278C7E38001C4FEA /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = "<group>"; };
1CD90B34278C7E38001C4FEA /* AddMoodHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddMoodHeaderView.swift; sourceTree = "<group>"; };
1CD90B35278C7E38001C4FEA /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
1CD90B36278C7E38001C4FEA /* HeaderStatsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderStatsView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
1CD90B4E278C7E7A001C4FEA /* FeelsWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = FeelsWidget.intentdefinition; sourceTree = "<group>"; };
1CD90B4F278C7E7A001C4FEA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1CD90B51278C7E7A001C4FEA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1CD90B5C278C7EAD001C4FEA /* Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Random.swift; sourceTree = "<group>"; };
1CD90B61278C7EBA001C4FEA /* Mood.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mood.swift; sourceTree = "<group>"; };
1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoodEntryExtension.swift; sourceTree = "<group>"; };
1CD90B69278C7F65001C4FEA /* Feels--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Feels--iOS--Info.plist"; sourceTree = "<group>"; };
1CD90B6A278C7F75001C4FEA /* Feels (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS).entitlements"; sourceTree = "<group>"; };
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 = "<group>"; };
1CD90B6F278C8000001C4FEA /* FeelsWidgetExtensionDev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = FeelsWidgetExtensionDev.entitlements; sourceTree = "<group>"; };
1CD90B70278C8000001C4FEA /* Feels (iOS)Dev.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Feels (iOS)Dev.entitlements"; sourceTree = "<group>"; };
1CD90B75278C8119001C4FEA /* LocalNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@@ -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 = "<group>";
@@ -149,6 +246,48 @@
path = "Tests macOS";
sourceTree = "<group>";
};
1CD90B31278C7E38001C4FEA /* views */ = {
isa = PBXGroup;
children = (
1CD90B32278C7E38001C4FEA /* SettingsView.swift */,
1CD90B33278C7E38001C4FEA /* GraphView.swift */,
1CD90B34278C7E38001C4FEA /* AddMoodHeaderView.swift */,
1CD90B35278C7E38001C4FEA /* ContentView.swift */,
1CD90B36278C7E38001C4FEA /* HeaderStatsView.swift */,
);
path = views;
sourceTree = "<group>";
};
1CD90B46278C7E7A001C4FEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1CD90B6B278C7F78001C4FEA /* CloudKit.framework */,
1CD90B47278C7E7A001C4FEA /* WidgetKit.framework */,
1CD90B49278C7E7A001C4FEA /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
1CD90B4B278C7E7A001C4FEA /* FeelsWidget */ = {
isa = PBXGroup;
children = (
1CD90B4C278C7E7A001C4FEA /* FeelsWidget.swift */,
1CD90B4E278C7E7A001C4FEA /* FeelsWidget.intentdefinition */,
1CD90B4F278C7E7A001C4FEA /* Assets.xcassets */,
1CD90B51278C7E7A001C4FEA /* Info.plist */,
);
path = FeelsWidget;
sourceTree = "<group>";
};
1CD90B60278C7EBA001C4FEA /* Models */ = {
isa = PBXGroup;
children = (
1CD90B61278C7EBA001C4FEA /* Mood.swift */,
1CD90B62278C7EBA001C4FEA /* MoodEntryExtension.swift */,
);
path = Models;
sourceTree = "<group>";
};
/* 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;

View File

@@ -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
}

View File

@@ -14,6 +14,53 @@
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>FeelsWidgetExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>PlaygroundChart (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>PlaygroundChart (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>5</integer>
</dict>
<key>PlaygroundChart (Playground) 3.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>6</integer>
</dict>
<key>PlaygroundChart (Playground) 4.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>7</integer>
</dict>
<key>PlaygroundChart (Playground) 5.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>8</integer>
</dict>
<key>PlaygroundChart (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>INEnums</key>
<array/>
<key>INIntentDefinitionModelVersion</key>
<string>1.2</string>
<key>INIntentDefinitionNamespace</key>
<string>88xZPY</string>
<key>INIntentDefinitionSystemVersion</key>
<string>20A294</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>12A6144</string>
<key>INIntentDefinitionToolsVersion</key>
<string>12.0</string>
<key>INIntents</key>
<array>
<dict>
<key>INIntentCategory</key>
<string>information</string>
<key>INIntentDescriptionID</key>
<string>tVvJ9c</string>
<key>INIntentEligibleForWidgets</key>
<true/>
<key>INIntentIneligibleForSuggestions</key>
<true/>
<key>INIntentName</key>
<string>Configuration</string>
<key>INIntentResponse</key>
<dict>
<key>INIntentResponseCodes</key>
<array>
<dict>
<key>INIntentResponseCodeName</key>
<string>success</string>
<key>INIntentResponseCodeSuccess</key>
<true/>
</dict>
<dict>
<key>INIntentResponseCodeName</key>
<string>failure</string>
</dict>
</array>
</dict>
<key>INIntentTitle</key>
<string>Configuration</string>
<key>INIntentTitleID</key>
<string>gpCwrM</string>
<key>INIntentType</key>
<string>Custom</string>
<key>INIntentVerb</key>
<string>View</string>
</dict>
</array>
<key>INTypes</key>
<array/>
</dict>
</plist>

View File

@@ -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<Entry>) -> ()) {
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)
}
}
}

11
FeelsWidget/Info.plist Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oak.feels</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oak.feelsDev</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
</dict>
</plist>

View File

@@ -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<Item>
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)
}
}

View File

@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="true" userDefinedModelVersionIdentifier="">
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="MoodEntry" representedClassName="MoodEntry" syncable="YES" codeGenerationType="class">
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="moodValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
<element name="MoodEntry" positionX="-63" positionY="-18" width="128" height="74"/>
</elements>
</model>

View File

@@ -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)")
}
}
}

View File

@@ -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<Bool, Error>) -> 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()
}
}

63
Shared/Models/Mood.swift Normal file
View File

@@ -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
}
}

View File

@@ -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))!
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Shared.xcdatamodel</string>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="MoodEntry" representedClassName="MoodEntry" syncable="YES" codeGenerationType="class">
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="moodValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="MoodEntry" positionX="-63" positionY="-18" width="128" height="74"/>
</elements>
</model>

View File

@@ -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<MoodEntry>(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<MoodEntry>(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..<diffInDays! {
let searchDay = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
if entries.filter({ Calendar.current.isDate($0.date!, inSameDayAs:searchDay!) }).isEmpty {
self.add(mood: .missing, forDate: searchDay!)
}
}
}
}
func clearDB() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = 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..<count {
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())
entries.append(newItem)
}
return entries
}
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Feels")
if inMemory {
@@ -40,17 +149,31 @@ struct PersistenceController {
if let error = error as NSError? {
// 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
extension NSManagedObjectContext {
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
batchDeleteRequest.resultType = .resultTypeObjectIDs
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}

20
Shared/Random.swift Normal file
View File

@@ -0,0 +1,20 @@
//
// Random.swift
// Feels
//
// Created by Trey Tartt on 1/9/22.
//
import Foundation
class Random {
static var widgetUpdateTime: 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()),
let tomorrowMorning = Calendar.current.date(byAdding: components, to: tomorrow) {
updateTime = tomorrowMorning
}
return updateTime
}
}

View File

@@ -0,0 +1,79 @@
//
// AddMoodHeaderView.swift
// Feels
//
// Created by Trey Tartt on 1/5/22.
//
import Foundation
import SwiftUI
import CoreData
struct AddMoodHeaderView: View {
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
ZStack {
Color(UIColor.secondarySystemBackground)
VStack {
Text("How are you feeling today?")
.font(.title)
.foregroundColor(Color(UIColor.label))
.padding()
HStack{
ForEach(Mood.allValues) { mood in
VStack {
Button(action: {
addItem(withMoodValue: mood.rawValue)
}, label: {
mood.icon
.font(.system(size: 50))
})
//Text(mood.strValue)
}.frame(minWidth: 0, maxWidth: .infinity)
}
}
.padding([.leading, .trailing, .bottom])
}
}
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
.frame(minHeight: 85, maxHeight: 140)
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
}
private func addItem(withMoodValue moodValue: Int) {
withAnimation {
let newItem = MoodEntry(context: viewContext)
newItem.timestamp = Date()
newItem.moodValue = Int16(moodValue)
newItem.date = 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)")
}
}
}
}
struct AddMoodHeaderView_Previews: PreviewProvider {
static var previews: some View {
Group {
AddMoodHeaderView().environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
AddMoodHeaderView().preferredColorScheme(.dark).environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
AddMoodHeaderView().environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
AddMoodHeaderView().preferredColorScheme(.dark).environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
}
}
}

View File

@@ -0,0 +1,152 @@
//
// ContentView.swift
// Shared
//
// Created by Trey Tartt on 1/5/22.
//
import SwiftUI
import CoreData
import Charts
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@State private var showingSheet = false
@State private var showTodayInput = true
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \MoodEntry.date, ascending: false)],
animation: .spring())
private var items: FetchedResults<MoodEntry>
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<MoodEntry>(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)
}
}

View File

@@ -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")
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}