22 Commits

Author SHA1 Message Date
neon443
5f52b423db changes to pending notif checker
add migration, if events dont have notifs
fix runtime error about bg threads by using await MainActor.run
add cancelallnotifs
bump version
2025-05-09 14:09:06 +01:00
neon443
7af96834d6 ok now notifs seem doen
neew event -> notif
edited event- cancel old, new notif
completed events- cancel notif
moved notif granting to settings struct
add: getNotifs, checkPendingNotifs, checkNotif, scheduleEventNotif, getDateComponents, cancelNotif
fix scheduleNotif
migration next
2025-05-07 10:33:35 +01:00
neon443
17ef4f1478 mroe reorg 2025-05-06 22:49:35 +01:00
neon443
785dda0e2b reorg -- long overdue lol 2025-05-06 21:49:21 +01:00
neon443
5f5702d958 ssdlkfjaslkf 2025-05-06 21:05:28 +01:00
neon443
b88bc11b36 yayy notifs!!
made the about app icon better
2025-05-06 21:03:53 +01:00
neon443
f8f5cdde26 symbolpicker presentationdetents 2025-05-06 18:32:59 +01:00
neon443
169d51e6b9 improved app icon getting - now gets the actual app icon 2025-05-06 15:37:10 +01:00
neon443
afd6d23838 bump version 2025-05-06 10:09:16 +01:00
neon443
5877f3b525 fixed no color selected when having no settings
fix helpview always orange
2025-05-06 10:05:01 +01:00
neon443
4157520ff4 accent color picking
added about
added saveSettings()
backported to 16.0
new title bar
2025-05-06 09:37:03 +01:00
neon443
f03ea73813 basic settings done!!!
now can set accent color
2025-05-05 09:57:58 +01:00
neon443
dafd2ae0e8 Merge branch 'feat-accent' 2025-05-05 08:58:10 +01:00
neon443
1bbacd0caa added onappear reset to importVie 2025-05-05 08:57:45 +01:00
Nihaal Sharma
29be23efc2 Update README.md 2025-05-04 12:49:49 +01:00
neon443
ffbd17fad8 rerewrote daysUntillEvent, now acc can count days
inlineLarge titlebar, backwards compatible view extension
competed events go to archive
fix long event name and notes alignemnts
can refresh icloud settings to sync
2025-05-04 12:31:01 +01:00
neon443
c97d37711c Fix import/Export events
rewrote the daysTillEventt()
2025-05-03 10:36:17 +01:00
neon443
80b193f449 ajdshfjkas 2025-05-02 10:03:43 +01:00
neon443
48d086602e fix the negative time when using the long daystill 2025-05-02 09:50:26 +01:00
neon443
3450e8bf1c cleanup 2025-05-01 21:23:14 +01:00
neon443
a08642bcb5 fix day count being wrong, minor updates 2025-05-01 21:05:31 +01:00
neon443
8ea1eda8f4 add xcconfig vars to widget appex 2025-04-29 21:29:21 +01:00
53 changed files with 1463 additions and 549 deletions

View File

@@ -10,7 +10,8 @@
TEAM_ID = 8JGND254B7 TEAM_ID = 8JGND254B7
BUNDLE_ID = com.neon443.NearFuture BUNDLE_ID = com.neon443.NearFuture
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
GROUP_ID = group.NearFuture GROUP_ID = group.NearFuture
VERSION = 3.0.1 VERSION = 4.0.0
NAME = Near Future NAME = Near Future
BUILD_NUMBER = 1 BUILD_NUMBER = 0

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 KiB

View File

Binary file not shown.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 70; objectVersion = 56;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -11,14 +11,22 @@
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; }; A920C28C2D24011400E4F9B1 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; };
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; }; A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; };
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; }; A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; };
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2B42D2401A100E4F9B1 /* SettingsView.swift */; };
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2B72D2401A300E4F9B1 /* AddEventView.swift */; };
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; }; A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; };
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; }; A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; };
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; }; A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; };
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; }; A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; }; A949F84D2DCAABE00064DCA0 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83D2DCAABE00064DCA0 /* EditEventView.swift */; };
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; }; A949F84E2DCAABE00064DCA0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83F2DCAABE00064DCA0 /* ContentView.swift */; };
A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8402DCAABE00064DCA0 /* EventListView.swift */; };
A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8412DCAABE00064DCA0 /* HelpView.swift */; };
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8432DCAABE00064DCA0 /* ExportView.swift */; };
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8442DCAABE00064DCA0 /* iCloudSettingsView.swift */; };
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8452DCAABE00064DCA0 /* ImportView.swift */; };
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; };
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; };
A949F8562DCAABE00064DCA0 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8432DCAABE00064DCA0 /* ExportView.swift */; };
A949F8592DCAAD670064DCA0 /* NearFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8572DCAAD670064DCA0 /* NearFutureTests.swift */; };
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; };
A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; }; A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; };
A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; }; A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; };
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; }; A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; };
@@ -26,8 +34,6 @@
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; }; A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; };
A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
A979F6182D2714310094C0B3 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; }; A979F6182D2714310094C0B3 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; };
A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985104D2DB256430013D5FF /* iCloudSettingsView.swift */; };
A98510502DB263F00013D5FF /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985104F2DB263F00013D5FF /* EventListView.swift */; };
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; }; A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -70,13 +76,23 @@
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; A920C28D2D24011A00E4F9B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFuture.entitlements; sourceTree = "<group>"; }; A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFuture.entitlements; sourceTree = "<group>"; };
A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; A949F82E2DCAAA640064DCA0 /* NearFutureIcon.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIcon.pxd; sourceTree = "<group>"; };
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = "<group>"; }; A949F82F2DCAAA640064DCA0 /* NearFutureIconDark.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIconDark.pxd; sourceTree = "<group>"; };
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; A949F8302DCAAA640064DCA0 /* NearFutureIconTint.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIconTint.pxd; sourceTree = "<group>"; };
A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; }; A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NearFutureIcon.png; path = Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png; sourceTree = "<group>"; };
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; }; A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; };
A977CC992DBD74FE00DED8C0 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = "<group>"; }; A949F83C2DCAABE00064DCA0 /* AddEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = "<group>"; };
A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; }; A949F83D2DCAABE00064DCA0 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; };
A949F83F2DCAABE00064DCA0 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
A949F8402DCAABE00064DCA0 /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = "<group>"; };
A949F8412DCAABE00064DCA0 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = "<group>"; };
A949F8432DCAABE00064DCA0 /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = "<group>"; };
A949F8442DCAABE00064DCA0 /* iCloudSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iCloudSettingsView.swift; sourceTree = "<group>"; };
A949F8452DCAABE00064DCA0 /* ImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportView.swift; sourceTree = "<group>"; };
A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A949F8572DCAAD670064DCA0 /* NearFutureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureTests.swift; sourceTree = "<group>"; };
A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; }; A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; };
A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; }; A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; };
A979F58F2D2700680094C0B3 /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; }; A979F58F2D2700680094C0B3 /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
@@ -92,16 +108,10 @@
A979F6112D270AF90094C0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; A979F6112D270AF90094C0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A980FC302D920097006A778F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; A980FC302D920097006A778F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A980FC372D93FB2B006A778F /* NearFutureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NearFutureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A980FC372D93FB2B006A778F /* NearFutureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NearFutureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iCloudSettingsView.swift; sourceTree = "<group>"; };
A985104F2DB263F00013D5FF /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = "<group>"; };
A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFutureWidgetsExtension.entitlements; sourceTree = "<group>"; }; A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFutureWidgetsExtension.entitlements; sourceTree = "<group>"; };
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; }; A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
A980FC382D93FB2B006A778F /* NearFutureTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = NearFutureTests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
A920C2812D24011300E4F9B1 /* Frameworks */ = { A920C2812D24011300E4F9B1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
@@ -134,9 +144,10 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A90FDE222DC0D4310012790C /* Config.xcconfig */, A90FDE222DC0D4310012790C /* Config.xcconfig */,
A949F8002DCAA0340064DCA0 /* Resources */,
A920C2862D24011400E4F9B1 /* NearFuture */, A920C2862D24011400E4F9B1 /* NearFuture */,
A979F6082D270AF00094C0B3 /* NearFutureWidgets */, A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
A980FC382D93FB2B006A778F /* NearFutureTests */, A949F8582DCAAD670064DCA0 /* NearFutureTests */,
A979F6032D270AF00094C0B3 /* Frameworks */, A979F6032D270AF00094C0B3 /* Frameworks */,
A920C2852D24011400E4F9B1 /* Products */, A920C2852D24011400E4F9B1 /* Products */,
); );
@@ -157,17 +168,8 @@
children = ( children = (
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
A920C28B2D24011400E4F9B1 /* Item.swift */, A920C28B2D24011400E4F9B1 /* Item.swift */,
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */, A949F84A2DCAABE00064DCA0 /* Views */,
A985104F2DB263F00013D5FF /* EventListView.swift */,
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */,
A979F57E2D26B1300094C0B3 /* EditEventView.swift */,
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */,
A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A980FC302D920097006A778F /* Info.plist */, A980FC302D920097006A778F /* Info.plist */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */, A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */,
A920C2902D24011A00E4F9B1 /* Preview Content */, A920C2902D24011A00E4F9B1 /* Preview Content */,
); );
@@ -183,6 +185,93 @@
path = "Preview Content"; path = "Preview Content";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A949F8002DCAA0340064DCA0 /* Resources */ = {
isa = PBXGroup;
children = (
A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
A949F82E2DCAAA640064DCA0 /* NearFutureIcon.pxd */,
A949F82F2DCAAA640064DCA0 /* NearFutureIconDark.pxd */,
A949F8302DCAAA640064DCA0 /* NearFutureIconTint.pxd */,
);
path = Resources;
sourceTree = "<group>";
};
A949F83B2DCAABE00064DCA0 /* Archive */ = {
isa = PBXGroup;
children = (
A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */,
);
path = Archive;
sourceTree = "<group>";
};
A949F83E2DCAABE00064DCA0 /* Events */ = {
isa = PBXGroup;
children = (
A949F83C2DCAABE00064DCA0 /* AddEventView.swift */,
A949F83D2DCAABE00064DCA0 /* EditEventView.swift */,
);
path = Events;
sourceTree = "<group>";
};
A949F8422DCAABE00064DCA0 /* Home */ = {
isa = PBXGroup;
children = (
A949F83F2DCAABE00064DCA0 /* ContentView.swift */,
A949F8402DCAABE00064DCA0 /* EventListView.swift */,
A949F8412DCAABE00064DCA0 /* HelpView.swift */,
);
path = Home;
sourceTree = "<group>";
};
A949F8472DCAABE00064DCA0 /* Settings */ = {
isa = PBXGroup;
children = (
A949F8432DCAABE00064DCA0 /* ExportView.swift */,
A949F8442DCAABE00064DCA0 /* iCloudSettingsView.swift */,
A949F8452DCAABE00064DCA0 /* ImportView.swift */,
A949F8462DCAABE00064DCA0 /* SettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
};
A949F8492DCAABE00064DCA0 /* Stats */ = {
isa = PBXGroup;
children = (
A949F8482DCAABE00064DCA0 /* StatsView.swift */,
);
path = Stats;
sourceTree = "<group>";
};
A949F84A2DCAABE00064DCA0 /* Views */ = {
isa = PBXGroup;
children = (
A949F83B2DCAABE00064DCA0 /* Archive */,
A949F83E2DCAABE00064DCA0 /* Events */,
A949F8422DCAABE00064DCA0 /* Home */,
A949F8472DCAABE00064DCA0 /* Settings */,
A949F8492DCAABE00064DCA0 /* Stats */,
A949F85E2DCABB420064DCA0 /* Misc */,
);
path = Views;
sourceTree = "<group>";
};
A949F8582DCAAD670064DCA0 /* NearFutureTests */ = {
isa = PBXGroup;
children = (
A949F8572DCAAD670064DCA0 /* NearFutureTests.swift */,
);
path = NearFutureTests;
sourceTree = "<group>";
};
A949F85E2DCABB420064DCA0 /* Misc */ = {
isa = PBXGroup;
children = (
A949F85D2DCABB420064DCA0 /* Buttons.swift */,
);
path = Misc;
sourceTree = "<group>";
};
A979F58A2D2700680094C0B3 /* NearFutureWidgets */ = { A979F58A2D2700680094C0B3 /* NearFutureWidgets */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -273,9 +362,6 @@
dependencies = ( dependencies = (
A980FC3C2D93FB2B006A778F /* PBXTargetDependency */, A980FC3C2D93FB2B006A778F /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = (
A980FC382D93FB2B006A778F /* NearFutureTests */,
);
name = NearFutureTests; name = NearFutureTests;
packageProductDependencies = ( packageProductDependencies = (
); );
@@ -334,6 +420,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */, A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */,
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */, A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -360,17 +447,20 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */,
A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */,
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */,
A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */,
A949F84D2DCAABE00064DCA0 /* EditEventView.swift in Sources */,
A949F84E2DCAABE00064DCA0 /* ContentView.swift in Sources */,
A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */,
A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */,
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */,
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */,
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */,
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */,
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */,
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */,
A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -389,6 +479,8 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A949F8592DCAAD670064DCA0 /* NearFutureTests.swift in Sources */,
A949F8562DCAABE00064DCA0 /* ExportView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -561,7 +653,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.4; IPHONEOS_DEPLOYMENT_TARGET = 16;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13; MACOSX_DEPLOYMENT_TARGET = 13;
@@ -572,10 +664,10 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO; REGISTER_APP_GROUPS = YES;
SDKROOT = auto; SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -612,7 +704,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.4; IPHONEOS_DEPLOYMENT_TARGET = 16;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13; MACOSX_DEPLOYMENT_TARGET = 13;
@@ -620,10 +712,10 @@
OTHER_LDFLAGS = ""; OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO; REGISTER_APP_GROUPS = YES;
SDKROOT = auto; SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -640,7 +732,8 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEVELOPMENT_TEAM = "$(TEAM_ID)";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFutureWidgets/Info.plist; INFOPLIST_FILE = NearFutureWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets;
@@ -651,20 +744,23 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 3.0; MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = "$(VERSION)";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-Xlinker", "-Xlinker",
"-interposable", "-interposable",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_WIDGETS)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VERSION = "$(VERSION)";
}; };
name = Debug; name = Debug;
}; };
@@ -676,7 +772,8 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements; CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEVELOPMENT_TEAM = "$(TEAM_ID)";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFutureWidgets/Info.plist; INFOPLIST_FILE = NearFutureWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets;
@@ -687,17 +784,20 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 3.0; MACOSX_DEPLOYMENT_TARGET = 14;
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets; MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_WIDGETS)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
VERSION = "$(VERSION)";
}; };
name = Release; name = Release;
}; };

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

View File

@@ -44,16 +44,18 @@ struct EventListView: View {
.font(.headline) .font(.headline)
.foregroundStyle(.one) .foregroundStyle(.one)
.strikethrough(event.complete) .strikethrough(event.complete)
.multilineTextAlignment(.leading)
} }
if !event.notes.isEmpty { if !event.notes.isEmpty {
Text(event.notes) Text(event.notes)
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.one.opacity(0.8)) .foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
} }
Text( Text(
event.date.formatted( event.date.formatted(
date: .long, date: .long,
time: event.time ? .shortened : .omitted time: .shortened
) )
) )
.font(.subheadline) .font(.subheadline)
@@ -71,7 +73,7 @@ struct EventListView: View {
} }
Spacer() Spacer()
VStack { VStack {
Text("\(daysUntilEvent(event.date, short: false))") Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.one) .foregroundStyle(.one)
} }

View File

@@ -9,6 +9,7 @@ import Foundation
import SwiftData import SwiftData
import SwiftUI import SwiftUI
import WidgetKit import WidgetKit
import UserNotifications
//@Model //@Model
//final class Item { //final class Item {
@@ -28,7 +29,6 @@ struct Event: Identifiable, Codable {
var color: ColorCodable var color: ColorCodable
var notes: String var notes: String
var date: Date var date: Date
var time: Bool
var recurrence: RecurrenceType var recurrence: RecurrenceType
enum RecurrenceType: String, Codable, CaseIterable { enum RecurrenceType: String, Codable, CaseIterable {
@@ -36,7 +36,29 @@ struct Event: Identifiable, Codable {
} }
} }
struct ColorCodable: Codable { struct ColorCodable: Codable, Equatable {
init(_ color: Color) {
let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
init(uiColor: UIColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
var red: Double var red: Double
var green: Double var green: Double
var blue: Double var blue: Double
@@ -58,42 +80,88 @@ struct ColorCodable: Codable {
self.blue = cc.blue self.blue = cc.blue
} }
} }
init(_ color: Color) {
let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
} }
func daysUntilEvent(_ eventDate: Date, short: Bool, sepLines: Bool = false) -> String { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let calendar = Calendar.current let calendar = Calendar.current
let currentDate = Date() let startOfDayNow = calendar.startOfDay(for: Date())
let components = calendar.dateComponents([.day], from: currentDate, to: eventDate) let startOfDayEvent = calendar.startOfDay(for: eventDate)
guard let days = components.day else { return "N/A" } let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent)
guard days >= 0 else { guard let days = components.day else { return ("N/A", "N/A") }
if short { guard days != 0 else { return ("Today", "Today") }
return "\(days)d" if days < 0 {
//past
return (
"\(-days) day\(plu(days)) ago",
"\(days)d"
)
} else { } else {
return "\(-days)\(sepLines ? "\n" : " ")day\(-days == 1 ? "" : "s") ago" //future
return (
"\(days) day\(plu(days))",
"\(days)d"
)
} }
} }
guard days != 0 else {
return "Today" struct Settings: Codable, Equatable {
var showCompletedInHome: Bool
var tint: ColorCodable
}
class SettingsViewModel: ObservableObject {
@Published var settings: Settings = Settings(
showCompletedInHome: false,
tint: ColorCodable(uiColor: UIColor(named: "AccentColor")!)
)
@Published var notifsGranted: Bool = false
@Published var accentChoices: [Color] = [
Color(UIColor(named: "uiColors/red")!),
Color(UIColor(named: "uiColors/orange")!),
Color(UIColor(named: "uiColors/yellow")!),
Color(UIColor(named: "uiColors/green")!),
Color(UIColor(named: "uiColors/blue")!),
Color(UIColor(named: "uiColors/indigo")!),
Color(UIColor(named: "uiColors/basic")!)
]
init(load: Bool = true) {
if load {
loadSettings()
Task {
let requestResult = await requestNotifs()
await MainActor.run {
self.notifsGranted = requestResult
}
}
}
}
let appGroupSettingsStore = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard
let icSettStore = NSUbiquitousKeyValueStore.default
func loadSettings() {
let decoder = JSONDecoder()
if let icSettings = icSettStore.data(forKey: "settings") {
if let decodedSetts = try? decoder.decode(Settings.self, from: icSettings) {
self.settings = decodedSetts
}
} else if let savedData = appGroupSettingsStore.data(forKey: "settings") {
if let decodedSetts = try? decoder.decode(Settings.self, from: savedData) {
self.settings = decodedSetts
}
}
}
func saveSettings() {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(settings) {
appGroupSettingsStore.set(encoded, forKey: "settings")
icSettStore.set(encoded, forKey: "settings")
icSettStore.synchronize()
loadSettings()
} }
if short {
return "\(days)d"
} else {
return "\(days)\(sepLines ? "\n" : " ")day\(days == 1 ? "" : "s")"
} }
} }
@@ -109,20 +177,9 @@ class EventViewModel: ObservableObject {
color: ColorCodable(randomColor()), color: ColorCodable(randomColor()),
notes: "", notes: "",
date: Date(), date: Date(),
time: false,
recurrence: .none
)
@Published var editableTemplate: Event = Event(
name: "",
complete: false,
completeDesc: "",
symbol: "star",
color: ColorCodable(randomColor()),
notes: "",
date: Date(),
time: false,
recurrence: .none recurrence: .none
) )
@Published var editableTemplate: Event
@Published var example: Event = Event( @Published var example: Event = Event(
name: "event", name: "event",
complete: false, complete: false,
@@ -131,7 +188,6 @@ class EventViewModel: ObservableObject {
color: ColorCodable(.orange), color: ColorCodable(.orange),
notes: "lksdjfakdflkasjlkjl", notes: "lksdjfakdflkasjlkjl",
date: Date(), date: Date(),
time: true,
recurrence: .daily recurrence: .daily
) )
@@ -141,6 +197,7 @@ class EventViewModel: ObservableObject {
@Published var syncStatus: String = "Not Synced" @Published var syncStatus: String = "Not Synced"
init(load: Bool = true) { init(load: Bool = true) {
self.editableTemplate = template
if load { if load {
loadEvents() loadEvents()
} }
@@ -172,6 +229,43 @@ class EventViewModel: ObservableObject {
updateSyncStatus() updateSyncStatus()
} }
func getNotifs() async -> [UNNotificationRequest] {
return await UNUserNotificationCenter.current().pendingNotificationRequests()
}
func checkPendingNotifs(_ pending: [UNNotificationRequest]) {
var eventUUIDs = events.map({$0.id.uuidString})
for req in pending {
//match the notif to an event
if let index = events.firstIndex(where: {$0.id.uuidString == req.identifier}) {
if let remove = eventUUIDs.firstIndex(where: {$0 == req.identifier}) {
eventUUIDs.remove(at: remove)
}
let components = getDateComponents(events[index].date)
//check the notif matches event details
if req.content.title == events[index].name,
req.content.subtitle == events[index].notes,
req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) {
//if it does, make sure the notif delets if u complete the veent
if events[index].complete {
cancelNotif(req.identifier)
}
} else {
cancelNotif(req.identifier)
scheduleEventNotif(events[index])
}
} else {
//cancel if the event is deleted
cancelNotif(req.identifier)
}
}
for uuid in eventUUIDs {
if let event = events.first(where: {$0.id.uuidString == uuid}) {
scheduleEventNotif(event)
}
}
}
// save to local and icloud // save to local and icloud
func saveEvents() { func saveEvents() {
let encoder = JSONEncoder() let encoder = JSONEncoder()
@@ -184,6 +278,9 @@ class EventViewModel: ObservableObject {
updateSyncStatus() updateSyncStatus()
loadEvents() loadEvents()
Task {
await checkPendingNotifs(getNotifs())
}
WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events
objectWillChange.send() objectWillChange.send()
} }
@@ -203,6 +300,7 @@ class EventViewModel: ObservableObject {
func addEvent(newEvent: Event) { func addEvent(newEvent: Event) {
events.append(newEvent) events.append(newEvent)
scheduleEventNotif(newEvent)
saveEvents() //sync with icloud saveEvents() //sync with icloud
} }
@@ -249,57 +347,25 @@ class EventViewModel: ObservableObject {
saveEvents() saveEvents()
} }
func exportEvents() -> String? { func exportEvents() -> String {
let encoder = JSONEncoder() let encoder = JSONEncoder()
if let json = try? encoder.encode(self.events) {
// Custom date encoding strategy to handle date formatting return "\(json.base64EncodedString())"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
encoder.dateEncodingStrategy = .formatted(dateFormatter)
do {
// Encode the events array to JSON data
let encodedData = try encoder.encode(events)
// Convert the JSON data to a string
if let jsonString = String(data: encodedData, encoding: .utf8) {
return jsonString
} else {
print("Failed to convert encoded data to string")
return nil
}
} catch {
print("Failed to encode events: \(error.localizedDescription)")
return nil
} }
return ""
} }
func importEvents(_ imp: String) { func importEvents(_ imported: String) throws {
guard let impData = imp.data(using: .utf8) else { guard let data = Data(base64Encoded: imported) else {
print("Failed to convert string to data") throw importError.invalidB64
return
} }
// Create a JSONDecoder
let decoder = JSONDecoder() let decoder = JSONDecoder()
// Add a custom date formatter for decoding the date string
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" // Adjust this to the date format you're using
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do { do {
// Attempt to decode the events from the provided data let decoded = try decoder.decode([Event].self, from: data)
let decoded = try decoder.decode([Event].self, from: impData)
print("Successfully decoded events: \(decoded)")
// Save and reload after importing events
self.events = decoded self.events = decoded
saveEvents() saveEvents()
loadEvents()
} catch { } catch {
// Print error if decoding fails throw error
print("Failed to decode events: \(error.localizedDescription)")
} }
} }
@@ -308,6 +374,7 @@ class EventViewModel: ObservableObject {
UserDefaults.standard.removeObject(forKey: "events") UserDefaults.standard.removeObject(forKey: "events")
appGroupUserDefaults.removeObject(forKey: "events") appGroupUserDefaults.removeObject(forKey: "events")
events.removeAll() events.removeAll()
cancelAllNotifs()
updateSyncStatus() updateSyncStatus()
} }
@@ -315,6 +382,7 @@ class EventViewModel: ObservableObject {
icloudStore.removeObject(forKey: "events") icloudStore.removeObject(forKey: "events")
icloudStore.synchronize() icloudStore.synchronize()
icloudData.removeAll() icloudData.removeAll()
cancelAllNotifs()
updateSyncStatus() updateSyncStatus()
} }
@@ -330,6 +398,7 @@ class EventViewModel: ObservableObject {
} }
events.removeAll() events.removeAll()
cancelAllNotifs()
updateSyncStatus() updateSyncStatus()
} }
@@ -340,6 +409,7 @@ class EventViewModel: ObservableObject {
} }
icloudStore.synchronize() icloudStore.synchronize()
icloudData.removeAll() icloudData.removeAll()
cancelAllNotifs()
updateSyncStatus() updateSyncStatus()
} }
} }
@@ -352,6 +422,12 @@ class dummyEventViewModel: EventViewModel {
} }
} }
class dummySettingsViewModel: SettingsViewModel {
override init(load: Bool = false) {
super.init(load: false)
}
}
func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String { func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String {
let dateString = date.formatted(date: .long, time: .omitted) let dateString = date.formatted(date: .long, time: .omitted)
let recurrenceDescription: String let recurrenceDescription: String
@@ -390,3 +466,53 @@ func randomColor() -> Color {
let b = Double.random(in: 0...1) let b = Double.random(in: 0...1)
return Color(red: r, green: g, blue: b) return Color(red: r, green: g, blue: b)
} }
func plu(_ inp: Int) -> String {
var input = inp
if inp < 0 { input.negate() }
return "\(input == 1 ? "" : "s")"
}
public enum importError: Error {
case invalidB64
}
func requestNotifs() async -> Bool {
let result = try? await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .carPlay, .sound])
return result ?? false
}
func scheduleNotif(title: String, sub: String, date: Date, id: String = UUID().uuidString) {
let content = UNMutableNotificationContent()
content.title = title
content.subtitle = sub
content.sound = .default
let identifier = id
let dateComponents = getDateComponents(date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
func scheduleEventNotif(_ event: Event) {
scheduleNotif(
title: event.name,
sub: event.notes,
date: event.date,
id: event.id.uuidString
)
}
func getDateComponents(_ date: Date) -> DateComponents {
return Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: date)
}
func cancelNotif(_ id: String) {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [id])
}
func cancelAllNotifs() {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
}

View File

@@ -74,7 +74,7 @@ struct HelpView: View {
var body: some View { var body: some View {
List { List {
ZStack { ZStack {
Color(.accent) Color(.tintColor)
.opacity(0.4) .opacity(0.4)
.padding(.horizontal, -15) .padding(.horizontal, -15)
.blur(radius: 5) .blur(radius: 5)

View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key> <key>com.apple.developer.icloud-container-identifiers</key>
<array/> <array/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key> <key>com.apple.developer.ubiquity-kvstore-identifier</key>

View File

@@ -22,10 +22,14 @@ struct NearFutureApp: App {
// fatalError("Could not create ModelContainer: \(error)") // fatalError("Could not create ModelContainer: \(error)")
// } // }
// }() // }()
@StateObject var settingsModel: SettingsViewModel = SettingsViewModel()
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView(viewModel: EventViewModel()) ContentView(
viewModel: EventViewModel(),
settingsModel: settingsModel
)
.tint(settingsModel.settings.tint.color)
} }
// .modelContainer(sharedModelContainer) // .modelContainer(sharedModelContainer)
} }

View File

@@ -11,9 +11,6 @@ struct ArchiveView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@State var showAddEvent: Bool = false @State var showAddEvent: Bool = false
@State var hey: UUID = UUID() @State var hey: UUID = UUID()
init(viewModel: EventViewModel) {
self.viewModel = viewModel
}
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ZStack { ZStack {
@@ -39,6 +36,14 @@ struct ArchiveView: View {
AddEventButton(showingAddEventView: $showAddEvent) AddEventButton(showingAddEventView: $showAddEvent)
} }
} }
.navigationTitle("Archive")
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
.sheet(isPresented: $showAddEvent) { .sheet(isPresented: $showAddEvent) {
AddEventView( AddEventView(
@@ -50,11 +55,15 @@ struct ArchiveView: View {
eventColor: $viewModel.editableTemplate.color.colorBind, eventColor: $viewModel.editableTemplate.color.colorBind,
eventNotes: $viewModel.editableTemplate.notes, eventNotes: $viewModel.editableTemplate.notes,
eventDate: $viewModel.editableTemplate.date, eventDate: $viewModel.editableTemplate.date,
eventTime: $viewModel.editableTemplate.time,
eventRecurrence: $viewModel.editableTemplate.recurrence, eventRecurrence: $viewModel.editableTemplate.recurrence,
adding: true adding: true
) )
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
} }
} }
} }

View File

@@ -18,7 +18,6 @@ struct AddEventView: View {
@Binding var eventColor: Color @Binding var eventColor: Color
@Binding var eventNotes: String @Binding var eventNotes: String
@Binding var eventDate: Date @Binding var eventDate: Date
@Binding var eventTime: Bool
@Binding var eventRecurrence: Event.RecurrenceType @Binding var eventRecurrence: Event.RecurrenceType
@State var adding: Bool @State var adding: Bool
@@ -60,6 +59,12 @@ struct AddEventView: View {
title: "Choose a Symbol", title: "Choose a Symbol",
searchLabel: "Search...", searchLabel: "Search...",
autoDismiss: true) autoDismiss: true)
.presentationDetents([.medium])
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
} }
ColorPicker("", selection: $eventColor, supportsOpacity: false) ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize() .fixedSize()
@@ -94,8 +99,10 @@ struct AddEventView: View {
// date picker // date picker
HStack { HStack {
Spacer()
DatePicker("", selection: $eventDate, displayedComponents: .date) DatePicker("", selection: $eventDate, displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle()) .datePickerStyle(WheelDatePickerStyle())
Spacer()
Button() { Button() {
eventDate = Date() eventDate = Date()
} label: { } label: {
@@ -107,14 +114,11 @@ struct AddEventView: View {
.frame(width: 20) .frame(width: 20)
} }
Toggle("Schedule a Time", isOn: $eventTime)
if eventTime {
DatePicker( DatePicker(
"", "",
selection: $eventDate, selection: $eventDate,
displayedComponents: .hourAndMinute displayedComponents: .hourAndMinute
) )
}
// re-ocurrence Picker // re-ocurrence Picker
Picker("Recurrence", selection: $eventRecurrence) { Picker("Recurrence", selection: $eventRecurrence) {
@@ -161,7 +165,6 @@ struct AddEventView: View {
color: ColorCodable(eventColor), color: ColorCodable(eventColor),
notes: eventNotes, notes: eventNotes,
date: eventDate, date: eventDate,
time: eventTime,
recurrence: eventRecurrence recurrence: eventRecurrence
) )
) )
@@ -207,32 +210,11 @@ struct AddEventView: View {
eventColor = randomColor() eventColor = randomColor()
eventNotes = viewModel.template.notes eventNotes = viewModel.template.notes
eventDate = viewModel.template.date eventDate = viewModel.template.date
eventTime = viewModel.template.time
eventRecurrence = viewModel.template.recurrence eventRecurrence = viewModel.template.recurrence
dismiss() dismiss()
} }
} }
struct MagicClearButton: View {
@Binding var text: String
var body: some View {
HStack {
Spacer()
Button {
text = ""
} label: {
Image(systemName: "xmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: text.isEmpty ? 0 : 25)
.symbolRenderingMode(.hierarchical)
.padding(.trailing, -5)
.animation(.spring, value: text.isEmpty)
}
.buttonStyle(.borderless)
}
}
}
#Preview { #Preview {
let vm = dummyEventViewModel() let vm = dummyEventViewModel()
@@ -248,11 +230,14 @@ struct MagicClearButton: View {
eventColor: .constant(vm.template.color.color), eventColor: .constant(vm.template.color.color),
eventNotes: .constant(vm.template.notes), eventNotes: .constant(vm.template.notes),
eventDate: .constant(vm.template.date), eventDate: .constant(vm.template.date),
eventTime: .constant(vm.template.time),
eventRecurrence: .constant(vm.template.recurrence), eventRecurrence: .constant(vm.template.recurrence),
adding: true adding: true
) )
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.presentationBackground(.ultraThinMaterial) .apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
} }
} }

View File

@@ -35,7 +35,6 @@ struct EditEventView: View {
eventColor: $event.color.colorBind, eventColor: $event.color.colorBind,
eventNotes: $event.notes, eventNotes: $event.notes,
eventDate: $event.date, eventDate: $event.date,
eventTime: $event.time,
eventRecurrence: $event.recurrence, eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event adding: false //bc we editing existing event
) )

View File

@@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import UserNotifications
import SwiftData import SwiftData
enum Field { enum Field {
@@ -21,6 +22,7 @@ enum Tab {
struct ContentView: View { struct ContentView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel
@State private var eventName = "" @State private var eventName = ""
@State private var eventComplete = false @State private var eventComplete = false
@State private var eventCompleteDesc = "" @State private var eventCompleteDesc = ""
@@ -28,14 +30,13 @@ struct ContentView: View {
@State private var eventColor: Color = randomColor() @State private var eventColor: Color = randomColor()
@State private var eventNotes = "" @State private var eventNotes = ""
@State private var eventDate = Date() @State private var eventDate = Date()
@State private var eventTime = false
@State private var eventRecurrence: Event.RecurrenceType = .none @State private var eventRecurrence: Event.RecurrenceType = .none
@State var hey: UUID = UUID() @State var hey: UUID = UUID()
@State private var showingAddEventView = false @State private var showingAddEventView = false
@State private var searchInput: String = "" @State private var searchInput: String = ""
var filteredEvents: [Event] { var filteredEvents: [Event] {
if searchInput.isEmpty { if searchInput.isEmpty {
return viewModel.events return viewModel.events.filter() {!$0.complete}
} else { } else {
return viewModel.events.filter { return viewModel.events.filter {
$0.name.localizedCaseInsensitiveContains(searchInput) || $0.name.localizedCaseInsensitiveContains(searchInput) ||
@@ -62,7 +63,14 @@ struct ContentView: View {
backgroundGradient backgroundGradient
VStack { VStack {
ZStack { ZStack {
SearchBar(searchInput: $searchInput) TextField(
"\(Image(systemName: "magnifyingglass")) Search",
text: $searchInput
)
.padding(.trailing, searchInput.isEmpty ? 0 : 30)
.animation(.spring, value: searchInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.submitLabel(.done)
.focused($focusedField, equals: Field.Search) .focused($focusedField, equals: Field.Search)
.onSubmit { .onSubmit {
focusedField = nil focusedField = nil
@@ -73,6 +81,7 @@ struct ContentView: View {
} }
} }
.padding(.horizontal) .padding(.horizontal)
if filteredEvents.isEmpty && !searchInput.isEmpty { if filteredEvents.isEmpty && !searchInput.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField) HelpView(searchInput: $searchInput, focusedField: focusedField)
} else { } else {
@@ -87,14 +96,23 @@ struct ContentView: View {
} }
.padding(.horizontal) .padding(.horizontal)
if /*!searchInput.isEmpty && */filteredEvents.isEmpty { if /*!searchInput.isEmpty && */filteredEvents.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField) HelpView(
searchInput: $searchInput,
focusedField: focusedField
)
} }
Spacer() Spacer()
} }
} }
} }
.navigationTitle("Near Future") .navigationTitle("Near Future")
.navigationBarTitleDisplayMode(.inline) .apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $showingAddEventView) { .sheet(isPresented: $showingAddEventView) {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel,
@@ -105,12 +123,15 @@ struct ContentView: View {
eventColor: $eventColor, eventColor: $eventColor,
eventNotes: $eventNotes, eventNotes: $eventNotes,
eventDate: $eventDate, eventDate: $eventDate,
eventTime: $eventTime,
eventRecurrence: $eventRecurrence, eventRecurrence: $eventRecurrence,
adding: true //adding event adding: true //adding event
) )
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.presentationBackground(.ultraThinMaterial) .apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
} }
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
@@ -133,7 +154,7 @@ struct ContentView: View {
Label("Statistics", systemImage: "chart.pie") Label("Statistics", systemImage: "chart.pie")
} }
.focused($focusedTab, equals: Tab.Statistics) .focused($focusedTab, equals: Tab.Statistics)
SettingsView(viewModel: viewModel) SettingsView(viewModel: viewModel, settingsModel: settingsModel)
.tabItem { .tabItem {
Label("Settings", systemImage: "gear") Label("Settings", systemImage: "gear")
} }
@@ -143,43 +164,10 @@ struct ContentView: View {
} }
#Preview { #Preview {
ContentView(viewModel: dummyEventViewModel()) ContentView(
} viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
struct SearchBar: View {
@Binding var searchInput: String
var body: some View {
TextField(
"\(Image(systemName: "magnifyingglass")) Search",
text: $searchInput
) )
.padding(.trailing, searchInput.isEmpty ? 0 : 30)
.animation(.spring, value: searchInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.submitLabel(.done)
}
}
struct AddEventButton: View {
@Binding var showingAddEventView: Bool
var body: some View {
Button() {
showingAddEventView.toggle()
} label: {
ZStack {
Circle()
.frame(width: 33)
.foregroundStyle(.one)
Image(systemName: "plus")
.resizable()
.scaledToFit()
.frame(width: 15)
.bold()
.foregroundStyle(.two)
}
}
}
} }
extension View { extension View {
@@ -195,3 +183,7 @@ extension View {
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
} }
} }
extension View {
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}

View File

@@ -0,0 +1,169 @@
//
// EventListView.swift
// NearFuture
//
// Created by neon443 on 18/04/2025.
//
import SwiftUI
import SwiftData
struct EventListView: View {
@ObservedObject var viewModel: EventViewModel
@State var event: Event
var body: some View {
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: $event
)
} label: {
ZStack {
HStack {
RoundedRectangle(cornerRadius: 5)
.frame(width: 7)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
)
VStack(alignment: .leading) {
HStack {
Image(systemName: event.symbol)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.shadow(radius: 5)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
Text("\(event.name)")
.font(.headline)
.foregroundStyle(.one)
.strikethrough(event.complete)
.multilineTextAlignment(.leading)
}
if !event.notes.isEmpty {
Text(event.notes)
.font(.subheadline)
.foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
}
Text(
event.date.formatted(
date: .long,
time: .shortened
)
)
.font(.subheadline)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
if event.recurrence != .none {
Text("Occurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.one.opacity(event.complete ? 0.5 : 1))
}
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline)
.foregroundStyle(.one)
}
Button() {
withAnimation {
event.complete.toggle()
}
let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events[eventToModify] = event
viewModel.saveEvents()
}
} label: {
if event.complete {
ZStack {
Circle()
.foregroundStyle(.green)
Image(systemName: "checkmark")
.resizable()
.foregroundStyle(.white)
.scaledToFit()
.bold()
.frame(width: 15)
}
} else {
Image(systemName: "circle")
.resizable()
.scaledToFit()
.foregroundStyle(event.color.color)
}
}
.buttonStyle(.borderless)
.frame(maxWidth: 25, maxHeight: 25)
.shadow(radius: 5)
.padding(.trailing, 5)
}
.padding(.vertical, 5)
.background(.ultraThinMaterial)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(
.one.opacity(appearance == .dark ? 0.5 : 1),
lineWidth: 1
)
)
.clipShape(
RoundedRectangle(cornerRadius: 10)
)
.fixedSize(horizontal: false, vertical: true)
}
.contextMenu() {
Button(role: .destructive) {
let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events.remove(at: eventToModify)
viewModel.saveEvents()
}
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
}
#Preview("EventListView") {
let vm = dummyEventViewModel()
ZStack {
Color.black
VStack {
ForEach(0..<50) { _ in
Rectangle()
.foregroundStyle(randomColor().opacity(0.5))
.padding(-10)
}
.ignoresSafeArea(.all)
.blur(radius: 5)
}
VStack {
ForEach(vm.events) { event in
EventListView(
viewModel: vm,
event: event
)
}
}
.padding(.horizontal, 10)
}
}

View File

@@ -0,0 +1,112 @@
//
// ArchiveHelp.swift
// NearFuture
//
// Created by neon443 on 26/04/2025.
//
import SwiftUI
enum HelpType {
case Search
case Archive
}
struct HelpView: View {
/// initialises a Search HelpView
///
init(searchInput: Binding<String>, focusedField: Field?) {
_searchInput = searchInput
self.helpType = .Search
_showAddEvent = .constant(false)
}
/// initialises an Archive HelpView
///
init(showAddEvent: Binding<Bool>) {
_showAddEvent = showAddEvent
self.helpType = .Archive
_searchInput = .constant("")
self.focusedField = nil
}
@Binding var searchInput: String
@FocusState var focusedField: Field?
@Binding var showAddEvent: Bool
var helpType: HelpType
var details: (
symbol: String,
title: String,
body: String,
buttonAction: () -> (),
buttonSymbol: String,
buttonText: String
) {
switch helpType {
case .Search:
return (
symbol: "questionmark.app.dashed",
title: "Looking for something?",
body: "Tip: The Search bar searches event names and notes.",
buttonAction: {
searchInput = ""
focusedField = nil
},
buttonSymbol: "xmark",
buttonText: "Clear Filters"
)
case .Archive:
return (
symbol: "eyes",
title: "Nothing to see here...",
body: "The Archive contains events that have been marked as complete.",
buttonAction: {
showAddEvent.toggle()
},
buttonSymbol: "plus",
buttonText: "Create an event"
)
}
}
var body: some View {
List {
ZStack {
Color(.tintColor)
.opacity(0.4)
.padding(.horizontal, -15)
.blur(radius: 5)
HStack {
Image(systemName: details.symbol)
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.padding(.trailing)
Text(details.title)
.bold()
.font(.title2)
}
}
.listRowSeparator(.hidden)
Text(details.body)
Button() {
details.buttonAction()
} label: {
HStack {
Image(systemName: details.buttonSymbol)
.bold()
Text(details.buttonText)
}
.foregroundStyle(Color.accentColor)
}
}
.scrollContentBackground(.hidden)
}
}
#Preview {
HelpView(searchInput: .constant(""), focusedField: nil)
HelpView(showAddEvent: .constant(false))
}

View File

@@ -0,0 +1,58 @@
//
// MagicClearButton.swift
// NearFuture
//
// Created by neon443 on 06/05/2025.
//
import SwiftUI
struct MagicClearButton: View {
@Binding var text: String
var body: some View {
HStack {
Spacer()
Button {
text = ""
} label: {
Image(systemName: "xmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: text.isEmpty ? 0 : 25)
.symbolRenderingMode(.hierarchical)
.padding(.trailing, -5)
.animation(.spring, value: text.isEmpty)
}
.buttonStyle(.borderless)
}
}
}
struct AddEventButton: View {
@Binding var showingAddEventView: Bool
var body: some View {
Button() {
showingAddEventView.toggle()
} label: {
ZStack {
Circle()
.frame(width: 33)
.foregroundStyle(.one)
Image(systemName: "plus")
.resizable()
.scaledToFit()
.frame(width: 15)
.bold()
.foregroundStyle(.two)
}
}
}
}
#Preview {
MagicClearButton(text: .constant("s"))
}
#Preview {
AddEventButton(showingAddEventView: .constant(false))
}

View File

@@ -0,0 +1,27 @@
//
// ExportView.swift
// NearFuture
//
// Created by neon443 on 02/05/2025.
//
import SwiftUI
struct ExportView: View {
@ObservedObject var viewModel: EventViewModel
var body: some View {
List {
Button() {
UIPasteboard.general.string = viewModel.exportEvents()
} label: {
Label("Copy Events", systemImage: "document.on.clipboard")
}
Text(viewModel.exportEvents())
.textSelection(.enabled)
}
}
}
#Preview {
ExportView(viewModel: dummyEventViewModel())
}

View File

@@ -0,0 +1,67 @@
//
// ImportView.swift
// NearFuture
//
// Created by neon443 on 02/05/2025.
//
import SwiftUI
struct ImportView: View {
@ObservedObject var viewModel: EventViewModel
@Binding var importStr: String
@State private var image: String = "clock.fill"
@State private var text: String = "Ready..."
@State private var fgColor: Color = .yellow
var body: some View {
List {
Section("Status") {
Label(text, systemImage: image)
.contentTransition(.numericText())
.foregroundStyle(fgColor)
}
TextField("", text: $importStr)
Button() {
do throws {
try viewModel.importEvents(importStr)
withAnimation {
image = "checkmark.circle.fill"
text = "Complete"
fgColor = .green
}
} catch importError.invalidB64 {
withAnimation {
image = "xmark.app.fill"
text = "Invalid base64 input."
fgColor = .red
}
} catch {
withAnimation {
image = "xmark.app.fill"
text = error.localizedDescription
fgColor = .red
}
}
} label: {
Label("Import", systemImage: "tray.and.arrow.down.fill")
}
.disabled(importStr.isEmpty)
.onAppear() {
importStr = ""
image = "clock.fill"
text = "Ready..."
fgColor = .yellow
}
}
}
}
#Preview {
ImportView(
viewModel: dummyEventViewModel(),
importStr: .constant("kljadfskljafdlkj;==")
)
}

View File

@@ -8,7 +8,8 @@
import SwiftUI import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@State var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel
@State private var hasUbiquitous: Bool = false @State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false @State private var lastSyncWasSuccessful: Bool = false
@@ -44,6 +45,47 @@ struct SettingsView: View {
ZStack { ZStack {
backgroundGradient backgroundGradient
List { List {
ScrollView(.horizontal) {
HStack {
ForEach(settingsModel.accentChoices, id: \.self) { color in
ZStack {
Button() {
settingsModel.settings.tint.colorBind = color
settingsModel.saveSettings()
} label: {
Circle()
.foregroundStyle(color)
.frame(width: 30)
}
if ColorCodable(color) == settingsModel.settings.tint {
let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint
Circle()
.foregroundStyle(needContrast ? .two : .one)
.frame(width: 10)
}
}
}
}
}
NavigationLink() {
List {
if !settingsModel.notifsGranted {
Button("Request Notifications") {
Task {
settingsModel.notifsGranted = await requestNotifs()
}
}
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
} else {
Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future")
.foregroundStyle(.green)
}
}
} label: {
Image(systemName: "bell.badge.fill")
Text("Notifications")
}
NavigationLink() { NavigationLink() {
iCloudSettingsView( iCloudSettingsView(
viewModel: viewModel, viewModel: viewModel,
@@ -68,47 +110,17 @@ struct SettingsView: View {
viewModel.sync() viewModel.sync()
updateStatus() updateStatus()
} }
NavigationLink() { NavigationLink() {
NavigationStack() { ImportView(viewModel: viewModel, importStr: $importStr)
Button() {
UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: { } label: {
Text("copy") Label("Import Events", systemImage: "tray.and.arrow.down.fill")
} .foregroundStyle(.one)
Text("\(viewModel.exportEvents() ?? "")")
}
} label: {
Image(systemName: "list.bullet.rectangle")
Text("Export events")
} }
NavigationLink() { NavigationLink() {
NavigationStack() { ExportView(viewModel: viewModel)
VStack {
TextEditor(text: $importStr)
.foregroundStyle(.foreground, .gray)
.background(.gray)
.frame(width: 200, height: 400)
.shadow(radius: 5)
Button() {
viewModel.importEvents(importStr)
} label: { } label: {
Text("import events") Label("Export Events", systemImage: "square.and.arrow.up")
} .foregroundStyle(.one)
.buttonStyle(BorderedProminentButtonStyle())
Button() {
if let pb = UIPasteboard.general.string {
print(pb)
}
} label: {
Text("print pb")
}
}
}
} label: {
Image(systemName: "square.and.arrow.down")
Text("Import events")
} }
Section("Tip") { Section("Tip") {
@@ -135,19 +147,54 @@ struct SettingsView: View {
viewModel.dangerResetiCloud() viewModel.dangerResetiCloud()
} }
} }
Section("About") {
VStack {
Image(uiImage: #imageLiteral(resourceName: "NearFutureIcon.png"))
.resizable()
.scaledToFit()
.frame(width: 100)
.clipShape(RoundedRectangle(cornerRadius: 25))
Text("Near Future")
.bold()
.monospaced()
.font(.title)
.frame(maxWidth: .infinity)
Text("Version " + getVersion() + " (\(getBuildID()))")
.frame(maxWidth: .infinity)
}
}
}
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Settings") .navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline) .apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
} }
} }
} }
} }
#Preview { #Preview {
SettingsView(viewModel: dummyEventViewModel()) SettingsView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
} }
func test() -> Void { func getVersion() -> String {
guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] else {
fatalError("no bundle id wtf lol")
}
return "\(version)"
}
func getBuildID() -> String {
guard let build = Bundle.main.infoDictionary?["CFBundleVersion"] else {
fatalError("wtf did u do w the build number")
}
return "\(build)"
} }

View File

@@ -0,0 +1,206 @@
//
// iCloudSettingsView.swift
// NearFuture
//
// Created by neon443 on 18/04/2025.
//
import SwiftUI
struct iCloudSettingsView: View {
@State var viewModel: EventViewModel
@State var showPushAlert: Bool = false
@State var showPullAlert: Bool = false
@Binding var hasUbiquitous: Bool
@Binding var lastSyncWasSuccessful: Bool
@Binding var lastSyncWasNormalAgo: Bool
@Binding var localCountEqualToiCloud: Bool
@Binding var icloudCountEqualToLocal: Bool
let asi = ProcessInfo().isiOSAppOnMac
let model = UIDevice().model
var device: (sf: String, label: String) {
if asi {
return (sf: "laptopcomputer", label: "Computer")
} else if model == "iPhone" {
return (sf: model.lowercased(), label: model)
} else if model == "iPad" {
return (sf: model.lowercased(), label: model)
}
return (sf: "iphone", label: "iPhone")
}
var updateStatus: () -> Void
var body: some View {
ZStack {
backgroundGradient
List {
Section {
HStack {
Spacer()
VStack {
ZStack {
Image(systemName: "icloud")
.resizable()
.scaledToFit()
.frame(width: 75, height: 55)
.symbolRenderingMode(.multicolor)
Text("\(viewModel.icloudEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text("iCloud")
HStack {
Button(role: .destructive) {
showPushAlert.toggle()
} label: {
Image(systemName: "arrow.up")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPushAlert) {
Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
}
Button() {
viewModel.sync()
updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
.foregroundStyle(Color.accentColor)
}
.buttonStyle(BorderedButtonStyle())
Button(role: .destructive) {
showPullAlert.toggle()
} label: {
Image(systemName: "arrow.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPullAlert) {
Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored locally with Events stored in iCloud.")
}
}
ZStack {
Image(systemName: device.sf)
.resizable()
.scaledToFit()
.frame(width: 75, height: 75)
.symbolRenderingMode(.monochrome)
Text("\(viewModel.localEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text(device.label)
}
Spacer()
}
.listRowSeparator(.hidden)
.onAppear {
viewModel.sync()
updateStatus()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red)
Text("iCloud")
Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red)
Text("Sync Status")
Spacer()
Text("\(viewModel.syncStatus)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync")
Spacer()
Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red)
Text("Local Events")
Spacer()
Text("\(viewModel.localEventCount)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud")
Spacer()
Text("\(viewModel.icloudEventCount)")
.bold()
}
} header: {
Text("Sync Status")
} footer: {
Text("Pull to sync\nOr use the arrows to force push/pull")
}
}
.refreshable {
viewModel.sync()
updateStatus()
}
.scrollContentBackground(.hidden)
.navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
}
}
}
#Preview("iCloudSettingsView") {
iCloudSettingsView(
viewModel: dummyEventViewModel(),
hasUbiquitous: .constant(true),
lastSyncWasSuccessful: .constant(true),
lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true),
updateStatus: {}
)
}

View File

@@ -58,7 +58,13 @@ struct StatsView: View {
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Statistics") .navigationTitle("Statistics")
.navigationBarTitleDisplayMode(.inline) .apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

View File

@@ -1,196 +0,0 @@
//
// iCloudSettingsView.swift
// NearFuture
//
// Created by neon443 on 18/04/2025.
//
import SwiftUI
struct iCloudSettingsView: View {
@State var viewModel: EventViewModel
@State var showPushAlert: Bool = false
@State var showPullAlert: Bool = false
@Binding var hasUbiquitous: Bool
@Binding var lastSyncWasSuccessful: Bool
@Binding var lastSyncWasNormalAgo: Bool
@Binding var localCountEqualToiCloud: Bool
@Binding var icloudCountEqualToLocal: Bool
let asi = ProcessInfo().isiOSAppOnMac
let model = UIDevice().model
var device: (sf: String, label: String) {
if asi {
return (sf: "laptopcomputer", label: "Computer")
} else if model == "iPhone" {
return (sf: model.lowercased(), label: model)
} else if model == "iPad" {
return (sf: model.lowercased(), label: model)
}
return (sf: "iphone", label: "iPhone")
}
var updateStatus: () -> Void
var body: some View {
ZStack {
backgroundGradient
List {
HStack {
Spacer()
VStack {
ZStack {
Image(systemName: "icloud")
.resizable()
.scaledToFit()
.frame(width: 75, height: 55)
.symbolRenderingMode(.multicolor)
Text("\(viewModel.icloudEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text("iCloud")
HStack {
Button(role: .destructive) {
showPushAlert.toggle()
} label: {
Image(systemName: "arrow.up")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPushAlert) {
Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
}
Button() {
viewModel.sync()
updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
.foregroundStyle(Color.accentColor)
}
.buttonStyle(BorderedButtonStyle())
Button(role: .destructive) {
showPullAlert.toggle()
} label: {
Image(systemName: "arrow.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPullAlert) {
Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored locally with Events stored in iCloud.")
}
}
ZStack {
Image(systemName: device.sf)
.resizable()
.scaledToFit()
.frame(width: 75, height: 75)
.symbolRenderingMode(.monochrome)
Text("\(viewModel.localEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text(device.label)
}
Spacer()
}
.listRowSeparator(.hidden)
.onAppear {
viewModel.sync()
updateStatus()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red)
Text("iCloud")
Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red)
Text("Sync Status")
Spacer()
Text("\(viewModel.syncStatus)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync")
Spacer()
Text("\(viewModel.lastSync?.formatted() ?? "Never")")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red)
Text("Local Events")
Spacer()
Text("\(viewModel.localEventCount)")
.bold()
}
HStack {
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud")
Spacer()
Text("\(viewModel.icloudEventCount)")
.bold()
}
}
.scrollContentBackground(.hidden)
.navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
}
}
}
#Preview("iCloudSettingsView") {
iCloudSettingsView(
viewModel: dummyEventViewModel(),
hasUbiquitous: .constant(true),
lastSyncWasSuccessful: .constant(true),
lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true),
updateStatus: test
)
}

View File

@@ -128,11 +128,19 @@ struct EventWidgetView: View {
Spacer() Spacer()
//short days till if not large widget //short days till if not large widget
Text(daysUntilEvent(event.date, short: !isLarge, sepLines: true)) if isLarge {
Text(daysUntilEvent(event.date).long)
.font(.caption) .font(.caption)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.foregroundColor(event.color.color) .foregroundColor(event.color.color)
.padding(.trailing, -12) .padding(.trailing, -12)
} else {
Text(daysUntilEvent(event.date).short)
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
}
} }
} }
Spacer() Spacer()

View File

@@ -5,76 +5,76 @@
// Created by Nihaal Sharma on 02/01/2025. // Created by Nihaal Sharma on 02/01/2025.
// //
import ActivityKit //import ActivityKit
import WidgetKit import WidgetKit
import SwiftUI import SwiftUI
struct NearFutureWidgetsAttributes: ActivityAttributes { //struct NearFutureWidgetsAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable { // public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here! // // Dynamic stateful properties about your activity go here!
var emoji: String // var emoji: String
} // }
//
// // Fixed non-changing properties about your activity go here!
// var name: String
//}
// Fixed non-changing properties about your activity go here! //struct NearFutureWidgetsLiveActivity: Widget {
var name: String // var body: some WidgetConfiguration {
} // ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in
// // Lock screen/banner UI goes here
// VStack {
// Text("Hello \(context.state.emoji)")
// }
// .activityBackgroundTint(Color.cyan)
// .activitySystemActionForegroundColor(Color.black)
//
// } dynamicIsland: { context in
// DynamicIsland {
// // Expanded UI goes here. Compose the expanded UI through
// // various regions, like leading/trailing/center/bottom
// DynamicIslandExpandedRegion(.leading) {
// Text("Leading")
// }
// DynamicIslandExpandedRegion(.trailing) {
// Text("Trailing")
// }
// DynamicIslandExpandedRegion(.bottom) {
// Text("Bottom \(context.state.emoji)")
// // more content
// }
// } compactLeading: {
// Text("L")
// } compactTrailing: {
// Text("T \(context.state.emoji)")
// } minimal: {
// Text(context.state.emoji)
// }
// .widgetURL(URL(string: "http://www.apple.com"))
// .keylineTint(Color.red)
// }
// }
//}
//
//extension NearFutureWidgetsAttributes {
// fileprivate static var preview: NearFutureWidgetsAttributes {
// NearFutureWidgetsAttributes(name: "World")
// }
//}
struct NearFutureWidgetsLiveActivity: Widget { //extension NearFutureWidgetsAttributes.ContentState {
var body: some WidgetConfiguration { // fileprivate static var smiley: NearFutureWidgetsAttributes.ContentState {
ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in // NearFutureWidgetsAttributes.ContentState(emoji: "😀")
// Lock screen/banner UI goes here // }
VStack { //
Text("Hello \(context.state.emoji)") // fileprivate static var starEyes: NearFutureWidgetsAttributes.ContentState {
} // NearFutureWidgetsAttributes.ContentState(emoji: "🤩")
.activityBackgroundTint(Color.cyan) // }
.activitySystemActionForegroundColor(Color.black) //}
//
} dynamicIsland: { context in //#Preview("Notification", as: .content, using: NearFutureWidgetsAttributes.preview) {
DynamicIsland { // NearFutureWidgetsLiveActivity()
// Expanded UI goes here. Compose the expanded UI through //} contentStates: {
// various regions, like leading/trailing/center/bottom // NearFutureWidgetsAttributes.ContentState.smiley
DynamicIslandExpandedRegion(.leading) { // NearFutureWidgetsAttributes.ContentState.starEyes
Text("Leading") //}
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom \(context.state.emoji)")
// more content
}
} compactLeading: {
Text("L")
} compactTrailing: {
Text("T \(context.state.emoji)")
} minimal: {
Text(context.state.emoji)
}
.widgetURL(URL(string: "http://www.apple.com"))
.keylineTint(Color.red)
}
}
}
extension NearFutureWidgetsAttributes {
fileprivate static var preview: NearFutureWidgetsAttributes {
NearFutureWidgetsAttributes(name: "World")
}
}
extension NearFutureWidgetsAttributes.ContentState {
fileprivate static var smiley: NearFutureWidgetsAttributes.ContentState {
NearFutureWidgetsAttributes.ContentState(emoji: "😀")
}
fileprivate static var starEyes: NearFutureWidgetsAttributes.ContentState {
NearFutureWidgetsAttributes.ContentState(emoji: "🤩")
}
}
#Preview("Notification", as: .content, using: NearFutureWidgetsAttributes.preview) {
NearFutureWidgetsLiveActivity()
} contentStates: {
NearFutureWidgetsAttributes.ContentState.smiley
NearFutureWidgetsAttributes.ContentState.starEyes
}

View File

@@ -1,5 +1,6 @@
# NearFuture # NearFuture
[![AppStore Link](https://github.com/neon443/NearFuture/blob/main/NearFuture/Images/appstore.png?raw=true)](https://apps.apple.com/us/app/near-future-event-tracker/id6744963429)
[App Store](https://apps.apple.com/us/app/near-future-event-tracker/id6744963429)
**Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries. **Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

View File

@@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.961",
"green" : "0.510",
"red" : "0.227"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.216",
"green" : "0.737",
"red" : "0.337"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.871",
"green" : "0.329",
"red" : "0.345"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "extended-srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.376",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.349",
"green" : "0.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.242",
"green" : "0.197",
"red" : "0.910"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.239",
"green" : "0.690",
"red" : "0.933"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

Binary file not shown.

View File

Binary file not shown.