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
BUNDLE_ID = com.neon443.NearFuture
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
GROUP_ID = group.NearFuture
VERSION = 3.0.1
VERSION = 4.0.0
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;
classes = {
};
objectVersion = 70;
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@@ -11,14 +11,22 @@
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; };
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* 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 */; };
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; };
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; };
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; };
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; };
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; };
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; };
A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; };
A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
A949F84D2DCAABE00064DCA0 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83D2DCAABE00064DCA0 /* 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 */; };
A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; };
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 */; };
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 */; };
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 */; };
/* End PBXBuildFile section */
@@ -70,13 +76,23 @@
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>"; };
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>"; };
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = "<group>"; };
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A977CC912DBBB48000DED8C0 /* 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>"; };
A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; };
A949F82E2DCAAA640064DCA0 /* NearFutureIcon.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIcon.pxd; sourceTree = "<group>"; };
A949F82F2DCAAA640064DCA0 /* NearFutureIconDark.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIconDark.pxd; sourceTree = "<group>"; };
A949F8302DCAAA640064DCA0 /* NearFutureIconTint.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIconTint.pxd; sourceTree = "<group>"; };
A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NearFutureIcon.png; path = Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png; sourceTree = "<group>"; };
A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; };
A949F83C2DCAABE00064DCA0 /* AddEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEventView.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>"; };
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>"; };
@@ -92,16 +108,10 @@
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>"; };
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>"; };
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
A980FC382D93FB2B006A778F /* NearFutureTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = NearFutureTests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
A920C2812D24011300E4F9B1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -134,9 +144,10 @@
isa = PBXGroup;
children = (
A90FDE222DC0D4310012790C /* Config.xcconfig */,
A949F8002DCAA0340064DCA0 /* Resources */,
A920C2862D24011400E4F9B1 /* NearFuture */,
A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
A980FC382D93FB2B006A778F /* NearFutureTests */,
A949F8582DCAAD670064DCA0 /* NearFutureTests */,
A979F6032D270AF00094C0B3 /* Frameworks */,
A920C2852D24011400E4F9B1 /* Products */,
);
@@ -157,17 +168,8 @@
children = (
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
A920C28B2D24011400E4F9B1 /* Item.swift */,
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */,
A985104F2DB263F00013D5FF /* EventListView.swift */,
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */,
A979F57E2D26B1300094C0B3 /* EditEventView.swift */,
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */,
A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A949F84A2DCAABE00064DCA0 /* Views */,
A980FC302D920097006A778F /* Info.plist */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */,
A920C2902D24011A00E4F9B1 /* Preview Content */,
);
@@ -183,6 +185,93 @@
path = "Preview Content";
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 */ = {
isa = PBXGroup;
children = (
@@ -273,9 +362,6 @@
dependencies = (
A980FC3C2D93FB2B006A778F /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
A980FC382D93FB2B006A778F /* NearFutureTests */,
);
name = NearFutureTests;
packageProductDependencies = (
);
@@ -334,6 +420,7 @@
buildActionMask = 2147483647;
files = (
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */,
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -360,17 +447,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
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 */,
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 */,
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */,
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */,
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */,
A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -389,6 +479,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A949F8592DCAAD670064DCA0 /* NearFutureTests.swift in Sources */,
A949F8562DCAABE00064DCA0 /* ExportView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -561,7 +653,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown 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[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13;
@@ -572,10 +664,10 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO;
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -612,7 +704,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown 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[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13;
@@ -620,10 +712,10 @@
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO;
REGISTER_APP_GROUPS = YES;
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -640,7 +732,8 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEVELOPMENT_TEAM = "$(TEAM_ID)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFutureWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets;
@@ -651,20 +744,23 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0;
MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = "$(VERSION)";
OTHER_LDFLAGS = (
"-Xlinker",
"-interposable",
);
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets;
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_WIDGETS)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSION = "$(VERSION)";
};
name = Debug;
};
@@ -676,7 +772,8 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEVELOPMENT_TEAM = "$(TEAM_ID)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFutureWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets;
@@ -687,17 +784,20 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets;
MACOSX_DEPLOYMENT_TARGET = 14;
MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_WIDGETS)";
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSION = "$(VERSION)";
};
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)
.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: event.time ? .shortened : .omitted
time: .shortened
)
)
.font(.subheadline)
@@ -71,7 +73,7 @@ struct EventListView: View {
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date, short: false))")
Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline)
.foregroundStyle(.one)
}

View File

@@ -9,6 +9,7 @@ import Foundation
import SwiftData
import SwiftUI
import WidgetKit
import UserNotifications
//@Model
//final class Item {
@@ -28,7 +29,6 @@ struct Event: Identifiable, Codable {
var color: ColorCodable
var notes: String
var date: Date
var time: Bool
var recurrence: RecurrenceType
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 green: Double
var blue: Double
@@ -58,42 +80,88 @@ struct ColorCodable: Codable {
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) -> (long: String, short: String) {
let calendar = Calendar.current
let startOfDayNow = calendar.startOfDay(for: Date())
let startOfDayEvent = calendar.startOfDay(for: eventDate)
let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent)
guard let days = components.day else { return ("N/A", "N/A") }
guard days != 0 else { return ("Today", "Today") }
if days < 0 {
//past
return (
"\(-days) day\(plu(days)) ago",
"\(days)d"
)
} else {
//future
return (
"\(days) day\(plu(days))",
"\(days)d"
)
}
}
func daysUntilEvent(_ eventDate: Date, short: Bool, sepLines: Bool = false) -> String {
let calendar = Calendar.current
let currentDate = Date()
let components = calendar.dateComponents([.day], from: currentDate, to: eventDate)
guard let days = components.day else { return "N/A" }
guard days >= 0 else {
if short {
return "\(days)d"
} else {
return "\(-days)\(sepLines ? "\n" : " ")day\(-days == 1 ? "" : "s") ago"
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
}
}
}
}
guard days != 0 else {
return "Today"
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
}
}
}
if short {
return "\(days)d"
} else {
return "\(days)\(sepLines ? "\n" : " ")day\(days == 1 ? "" : "s")"
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()
}
}
}
@@ -109,20 +177,9 @@ class EventViewModel: ObservableObject {
color: ColorCodable(randomColor()),
notes: "",
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
)
@Published var editableTemplate: Event
@Published var example: Event = Event(
name: "event",
complete: false,
@@ -131,7 +188,6 @@ class EventViewModel: ObservableObject {
color: ColorCodable(.orange),
notes: "lksdjfakdflkasjlkjl",
date: Date(),
time: true,
recurrence: .daily
)
@@ -141,6 +197,7 @@ class EventViewModel: ObservableObject {
@Published var syncStatus: String = "Not Synced"
init(load: Bool = true) {
self.editableTemplate = template
if load {
loadEvents()
}
@@ -172,6 +229,43 @@ class EventViewModel: ObservableObject {
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
func saveEvents() {
let encoder = JSONEncoder()
@@ -184,6 +278,9 @@ class EventViewModel: ObservableObject {
updateSyncStatus()
loadEvents()
Task {
await checkPendingNotifs(getNotifs())
}
WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events
objectWillChange.send()
}
@@ -203,6 +300,7 @@ class EventViewModel: ObservableObject {
func addEvent(newEvent: Event) {
events.append(newEvent)
scheduleEventNotif(newEvent)
saveEvents() //sync with icloud
}
@@ -249,57 +347,25 @@ class EventViewModel: ObservableObject {
saveEvents()
}
func exportEvents() -> String? {
func exportEvents() -> String {
let encoder = JSONEncoder()
// Custom date encoding strategy to handle date formatting
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
if let json = try? encoder.encode(self.events) {
return "\(json.base64EncodedString())"
}
return ""
}
func importEvents(_ imp: String) {
guard let impData = imp.data(using: .utf8) else {
print("Failed to convert string to data")
return
func importEvents(_ imported: String) throws {
guard let data = Data(base64Encoded: imported) else {
throw importError.invalidB64
}
// Create a 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 {
// Attempt to decode the events from the provided data
let decoded = try decoder.decode([Event].self, from: impData)
print("Successfully decoded events: \(decoded)")
// Save and reload after importing events
let decoded = try decoder.decode([Event].self, from: data)
self.events = decoded
saveEvents()
loadEvents()
} catch {
// Print error if decoding fails
print("Failed to decode events: \(error.localizedDescription)")
throw error
}
}
@@ -308,6 +374,7 @@ class EventViewModel: ObservableObject {
UserDefaults.standard.removeObject(forKey: "events")
appGroupUserDefaults.removeObject(forKey: "events")
events.removeAll()
cancelAllNotifs()
updateSyncStatus()
}
@@ -315,6 +382,7 @@ class EventViewModel: ObservableObject {
icloudStore.removeObject(forKey: "events")
icloudStore.synchronize()
icloudData.removeAll()
cancelAllNotifs()
updateSyncStatus()
}
@@ -330,6 +398,7 @@ class EventViewModel: ObservableObject {
}
events.removeAll()
cancelAllNotifs()
updateSyncStatus()
}
@@ -340,6 +409,7 @@ class EventViewModel: ObservableObject {
}
icloudStore.synchronize()
icloudData.removeAll()
cancelAllNotifs()
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 {
let dateString = date.formatted(date: .long, time: .omitted)
let recurrenceDescription: String
@@ -390,3 +466,53 @@ func randomColor() -> Color {
let b = Double.random(in: 0...1)
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 {
List {
ZStack {
Color(.accent)
Color(.tintColor)
.opacity(0.4)
.padding(.horizontal, -15)
.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">
<plist version="1.0">
<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>
<array/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>

View File

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

View File

@@ -11,9 +11,6 @@ struct ArchiveView: View {
@ObservedObject var viewModel: EventViewModel
@State var showAddEvent: Bool = false
@State var hey: UUID = UUID()
init(viewModel: EventViewModel) {
self.viewModel = viewModel
}
var body: some View {
NavigationStack {
ZStack {
@@ -39,6 +36,14 @@ struct ArchiveView: View {
AddEventButton(showingAddEventView: $showAddEvent)
}
}
.navigationTitle("Archive")
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
}
.sheet(isPresented: $showAddEvent) {
AddEventView(
@@ -50,11 +55,15 @@ struct ArchiveView: View {
eventColor: $viewModel.editableTemplate.color.colorBind,
eventNotes: $viewModel.editableTemplate.notes,
eventDate: $viewModel.editableTemplate.date,
eventTime: $viewModel.editableTemplate.time,
eventRecurrence: $viewModel.editableTemplate.recurrence,
adding: true
)
.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 eventNotes: String
@Binding var eventDate: Date
@Binding var eventTime: Bool
@Binding var eventRecurrence: Event.RecurrenceType
@State var adding: Bool
@@ -60,6 +59,12 @@ struct AddEventView: View {
title: "Choose a Symbol",
searchLabel: "Search...",
autoDismiss: true)
.presentationDetents([.medium])
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize()
@@ -94,8 +99,10 @@ struct AddEventView: View {
// date picker
HStack {
Spacer()
DatePicker("", selection: $eventDate, displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle())
Spacer()
Button() {
eventDate = Date()
} label: {
@@ -107,14 +114,11 @@ struct AddEventView: View {
.frame(width: 20)
}
Toggle("Schedule a Time", isOn: $eventTime)
if eventTime {
DatePicker(
"",
selection: $eventDate,
displayedComponents: .hourAndMinute
)
}
// re-ocurrence Picker
Picker("Recurrence", selection: $eventRecurrence) {
@@ -161,7 +165,6 @@ struct AddEventView: View {
color: ColorCodable(eventColor),
notes: eventNotes,
date: eventDate,
time: eventTime,
recurrence: eventRecurrence
)
)
@@ -207,32 +210,11 @@ struct AddEventView: View {
eventColor = randomColor()
eventNotes = viewModel.template.notes
eventDate = viewModel.template.date
eventTime = viewModel.template.time
eventRecurrence = viewModel.template.recurrence
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 {
let vm = dummyEventViewModel()
@@ -248,11 +230,14 @@ struct MagicClearButton: View {
eventColor: .constant(vm.template.color.color),
eventNotes: .constant(vm.template.notes),
eventDate: .constant(vm.template.date),
eventTime: .constant(vm.template.time),
eventRecurrence: .constant(vm.template.recurrence),
adding: true
)
.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,
eventNotes: $event.notes,
eventDate: $event.date,
eventTime: $event.time,
eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event
)

View File

@@ -6,6 +6,7 @@
//
import SwiftUI
import UserNotifications
import SwiftData
enum Field {
@@ -21,6 +22,7 @@ enum Tab {
struct ContentView: View {
@StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel
@State private var eventName = ""
@State private var eventComplete = false
@State private var eventCompleteDesc = ""
@@ -28,14 +30,13 @@ struct ContentView: View {
@State private var eventColor: Color = randomColor()
@State private var eventNotes = ""
@State private var eventDate = Date()
@State private var eventTime = false
@State private var eventRecurrence: Event.RecurrenceType = .none
@State var hey: UUID = UUID()
@State private var showingAddEventView = false
@State private var searchInput: String = ""
var filteredEvents: [Event] {
if searchInput.isEmpty {
return viewModel.events
return viewModel.events.filter() {!$0.complete}
} else {
return viewModel.events.filter {
$0.name.localizedCaseInsensitiveContains(searchInput) ||
@@ -62,17 +63,25 @@ struct ContentView: View {
backgroundGradient
VStack {
ZStack {
SearchBar(searchInput: $searchInput)
.focused($focusedField, equals: Field.Search)
.onSubmit {
focusedField = nil
}
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)
.onSubmit {
focusedField = nil
}
MagicClearButton(text: $searchInput)
.onTapGesture {
focusedField = nil
}
}
.padding(.horizontal)
if filteredEvents.isEmpty && !searchInput.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField)
} else {
@@ -87,14 +96,23 @@ struct ContentView: View {
}
.padding(.horizontal)
if /*!searchInput.isEmpty && */filteredEvents.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField)
HelpView(
searchInput: $searchInput,
focusedField: focusedField
)
}
Spacer()
}
}
}
.navigationTitle("Near Future")
.navigationBarTitleDisplayMode(.inline)
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $showingAddEventView) {
AddEventView(
viewModel: viewModel,
@@ -105,12 +123,15 @@ struct ContentView: View {
eventColor: $eventColor,
eventNotes: $eventNotes,
eventDate: $eventDate,
eventTime: $eventTime,
eventRecurrence: $eventRecurrence,
adding: true //adding event
)
.presentationDragIndicator(.visible)
.presentationBackground(.ultraThinMaterial)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
@@ -133,7 +154,7 @@ struct ContentView: View {
Label("Statistics", systemImage: "chart.pie")
}
.focused($focusedTab, equals: Tab.Statistics)
SettingsView(viewModel: viewModel)
SettingsView(viewModel: viewModel, settingsModel: settingsModel)
.tabItem {
Label("Settings", systemImage: "gear")
}
@@ -143,43 +164,10 @@ struct ContentView: View {
}
#Preview {
ContentView(viewModel: dummyEventViewModel())
}
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)
}
}
}
ContentView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
}
extension View {
@@ -195,3 +183,7 @@ extension View {
.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
struct SettingsView: View {
@State var viewModel: EventViewModel
@ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel
@State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false
@@ -44,6 +45,47 @@ struct SettingsView: View {
ZStack {
backgroundGradient
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() {
iCloudSettingsView(
viewModel: viewModel,
@@ -68,47 +110,17 @@ struct SettingsView: View {
viewModel.sync()
updateStatus()
}
NavigationLink() {
NavigationStack() {
Button() {
UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: {
Text("copy")
}
Text("\(viewModel.exportEvents() ?? "")")
}
ImportView(viewModel: viewModel, importStr: $importStr)
} label: {
Image(systemName: "list.bullet.rectangle")
Text("Export events")
Label("Import Events", systemImage: "tray.and.arrow.down.fill")
.foregroundStyle(.one)
}
NavigationLink() {
NavigationStack() {
VStack {
TextEditor(text: $importStr)
.foregroundStyle(.foreground, .gray)
.background(.gray)
.frame(width: 200, height: 400)
.shadow(radius: 5)
Button() {
viewModel.importEvents(importStr)
} label: {
Text("import events")
}
.buttonStyle(BorderedProminentButtonStyle())
Button() {
if let pb = UIPasteboard.general.string {
print(pb)
}
} label: {
Text("print pb")
}
}
}
ExportView(viewModel: viewModel)
} label: {
Image(systemName: "square.and.arrow.down")
Text("Import events")
Label("Export Events", systemImage: "square.and.arrow.up")
.foregroundStyle(.one)
}
Section("Tip") {
@@ -135,19 +147,54 @@ struct SettingsView: View {
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)
.navigationTitle("Settings")
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
.scrollContentBackground(.hidden)
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
#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)
.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()
//short days till if not large widget
Text(daysUntilEvent(event.date, short: !isLarge, sepLines: true))
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
if isLarge {
Text(daysUntilEvent(event.date).long)
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
} else {
Text(daysUntilEvent(event.date).short)
.font(.caption)
.multilineTextAlignment(.trailing)
.foregroundColor(event.color.color)
.padding(.trailing, -12)
}
}
}
Spacer()

View File

@@ -5,76 +5,76 @@
// Created by Nihaal Sharma on 02/01/2025.
//
import ActivityKit
//import ActivityKit
import WidgetKit
import SwiftUI
struct NearFutureWidgetsAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
var emoji: String
}
//struct NearFutureWidgetsAttributes: ActivityAttributes {
// public struct ContentState: Codable, Hashable {
// // Dynamic stateful properties about your activity go here!
// var emoji: String
// }
//
// // Fixed non-changing properties about your activity go here!
// var name: String
//}
// Fixed non-changing properties about your activity go here!
var name: String
}
//struct NearFutureWidgetsLiveActivity: Widget {
// 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 {
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")
}
}
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
}
//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
[![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.

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.