15 Commits

Author SHA1 Message Date
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
37 changed files with 807 additions and 371 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 = 3.3.1
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.

View File

@@ -7,6 +7,8 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
A9111B1B2DCA549600D4F793 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */; };
A9111B1C2DCA549600D4F793 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */; };
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; }; A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; };
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 */; };
@@ -16,6 +18,9 @@
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 */; }; A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; };
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; }; A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; };
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26B2DC551310028F8A2 /* ImportView.swift */; };
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26F2DC552EB0028F8A2 /* ExportView.swift */; };
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26F2DC552EB0028F8A2 /* ExportView.swift */; };
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; }; A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; };
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; }; A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; };
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; }; A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; };
@@ -64,6 +69,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; }; A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NearFutureIcon.png; path = NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png; sourceTree = SOURCE_ROOT; };
A920C2842D24011400E4F9B1 /* NearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; }; A920C2842D24011400E4F9B1 /* NearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; };
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureApp.swift; sourceTree = "<group>"; }; A920C2872D24011400E4F9B1 /* NearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureApp.swift; sourceTree = "<group>"; };
A920C28B2D24011400E4F9B1 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; }; A920C28B2D24011400E4F9B1 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
@@ -74,6 +80,8 @@
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.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>"; }; 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>"; }; A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A973B26B2DC551310028F8A2 /* ImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportView.swift; sourceTree = "<group>"; };
A973B26F2DC552EB0028F8A2 /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = "<group>"; };
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.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>"; }; 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>"; }; A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; };
@@ -130,10 +138,19 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
A9111B192DCA548800D4F793 /* Resources */ = {
isa = PBXGroup;
children = (
A9111B1A2DCA549600D4F793 /* NearFutureIcon.png */,
);
path = Resources;
sourceTree = "<group>";
};
A920C27B2D24011300E4F9B1 = { A920C27B2D24011300E4F9B1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A90FDE222DC0D4310012790C /* Config.xcconfig */, A90FDE222DC0D4310012790C /* Config.xcconfig */,
A9111B192DCA548800D4F793 /* Resources */,
A920C2862D24011400E4F9B1 /* NearFuture */, A920C2862D24011400E4F9B1 /* NearFuture */,
A979F6082D270AF00094C0B3 /* NearFutureWidgets */, A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
A980FC382D93FB2B006A778F /* NearFutureTests */, A980FC382D93FB2B006A778F /* NearFutureTests */,
@@ -165,6 +182,8 @@
A93BC0932D2B18A3002E8BBD /* StatsView.swift */, A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */, A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */, A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A973B26B2DC551310028F8A2 /* ImportView.swift */,
A973B26F2DC552EB0028F8A2 /* ExportView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */, A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A980FC302D920097006A778F /* Info.plist */, A980FC302D920097006A778F /* Info.plist */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */, A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
@@ -334,6 +353,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */, A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
A9111B1C2DCA549600D4F793 /* NearFutureIcon.png in Resources */,
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */, A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -343,6 +363,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */, A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */,
A9111B1B2DCA549600D4F793 /* NearFutureIcon.png in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -361,8 +382,10 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */, A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */,
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */,
A98510502DB263F00013D5FF /* EventListView.swift in Sources */, A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */, A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */,
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */, A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */, A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */,
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
@@ -389,6 +412,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -561,7 +585,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 +596,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 +636,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 +644,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 +664,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 +676,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 +704,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 +716,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

@@ -104,7 +104,6 @@
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2"> launchAutomaticallySubstyle = "2">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">

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,11 @@ struct AddEventView: View {
title: "Choose a Symbol", title: "Choose a Symbol",
searchLabel: "Search...", searchLabel: "Search...",
autoDismiss: true) autoDismiss: true)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
} }
ColorPicker("", selection: $eventColor, supportsOpacity: false) ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize() .fixedSize()
@@ -94,8 +98,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 +113,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 +164,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,7 +209,6 @@ 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()
} }
@@ -248,11 +249,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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 KiB

After

Width:  |  Height:  |  Size: 729 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

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

@@ -21,6 +21,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 +29,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) ||
@@ -87,14 +87,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 +114,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 +145,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,7 +155,10 @@ struct ContentView: View {
} }
#Preview { #Preview {
ContentView(viewModel: dummyEventViewModel()) ContentView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
} }
struct SearchBar: View { struct SearchBar: View {
@@ -195,3 +210,7 @@ extension View {
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
} }
} }
extension View {
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}

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

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

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

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

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

@@ -28,7 +28,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 +35,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 +79,81 @@ struct ColorCodable: Codable {
self.blue = cc.blue self.blue = cc.blue
} }
} }
}
init(_ color: Color) { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let uiColor = UIColor(color) let calendar = Calendar.current
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0 let startOfDayNow = calendar.startOfDay(for: Date())
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) let startOfDayEvent = calendar.startOfDay(for: eventDate)
let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent)
self.red = Double(r) guard let days = components.day else { return ("N/A", "N/A") }
self.green = Double(g) guard days != 0 else { return ("Today", "Today") }
self.blue = Double(b) if days < 0 {
} //past
init(red: Double, green: Double, blue: Double) { return (
self.red = red "\(-days) day\(plu(days)) ago",
self.green = green "\(days)d"
self.blue = blue )
} else {
//future
return (
"\(days) day\(plu(days))",
"\(days)d"
)
} }
} }
func daysUntilEvent(_ eventDate: Date, short: Bool, sepLines: Bool = false) -> String { struct Settings: Codable, Equatable {
let calendar = Calendar.current var showCompletedInHome: Bool
let currentDate = Date() var tint: ColorCodable
let components = calendar.dateComponents([.day], from: currentDate, to: eventDate) }
guard let days = components.day else { return "N/A" }
guard days >= 0 else { class SettingsViewModel: ObservableObject {
if short { @Published var settings: Settings = Settings(
return "\(days)d" showCompletedInHome: false,
} else { tint: ColorCodable(uiColor: UIColor(named: "AccentColor")!)
return "\(-days)\(sepLines ? "\n" : " ")day\(-days == 1 ? "" : "s") ago" )
@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()
} }
} }
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
}
}
}
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 +169,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 +180,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 +189,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()
} }
@@ -249,57 +298,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)")
} }
} }
@@ -352,6 +369,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 +413,13 @@ 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
}

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

@@ -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,28 @@ 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() { NavigationLink() {
iCloudSettingsView( iCloudSettingsView(
viewModel: viewModel, viewModel: viewModel,
@@ -68,47 +91,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 +128,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

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

@@ -37,6 +37,7 @@ struct iCloudSettingsView: View {
ZStack { ZStack {
backgroundGradient backgroundGradient
List { List {
Section {
HStack { HStack {
Spacer() Spacer()
VStack { VStack {
@@ -152,7 +153,7 @@ struct iCloudSettingsView: View {
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red) .foregroundStyle(lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync") Text("Last Sync")
Spacer() Spacer()
Text("\(viewModel.lastSync?.formatted() ?? "Never")") Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")")
.bold() .bold()
} }
@@ -175,6 +176,15 @@ struct iCloudSettingsView: View {
Text("\(viewModel.icloudEventCount)") Text("\(viewModel.icloudEventCount)")
.bold() .bold()
} }
} header: {
Text("Sync Status")
} footer: {
Text("Pull to sync\nOr use the arrows to force push/pull")
}
}
.refreshable {
viewModel.sync()
updateStatus()
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("iCloud") .navigationTitle("iCloud")
@@ -191,6 +201,6 @@ struct iCloudSettingsView: View {
lastSyncWasNormalAgo: .constant(true), lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true), localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true), icloudCountEqualToLocal: .constant(true),
updateStatus: test updateStatus: {}
) )
} }

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.