semi rewrite

fixed adding events
new icon
add archive view
fixed sync between contentView & archiveview
new help view
new eventlistview
new addeventbutton
fixed widgets
This commit is contained in:
neon443
2025-04-29 12:50:10 +01:00
parent 76a797fe8f
commit 08675b921a
37 changed files with 1165 additions and 673 deletions

16
Config.xcconfig Normal file
View File

@@ -0,0 +1,16 @@
//
// Config.xcconfig
// NearFuture
//
// Created by neon443 on 29/04/2025.
//
// Configuration settings file format documentation can be found at:
// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project
TEAM_ID = 8JGND254B7
BUNDLE_ID = com.neon443.NearFuture
GROUP_ID = group.NearFuture
VERSION = 3.0.1
NAME = Near Future
BUILD_NUMBER = 1

BIN
Images/NearFutureIcon.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
Images/NearFutureIcon.pxd Normal file
View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

View File

Binary file not shown.

BIN
Images/appstore.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -16,6 +16,8 @@
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 */; };
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 */; }; A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; };
A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; }; A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; };
A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; }; A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; };
@@ -61,6 +63,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
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>"; };
@@ -71,6 +74,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>"; };
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>"; }; A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; };
A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; }; A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; };
A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; }; A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; };
@@ -128,6 +133,7 @@
A920C27B2D24011300E4F9B1 = { A920C27B2D24011300E4F9B1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A90FDE222DC0D4310012790C /* Config.xcconfig */,
A920C2862D24011400E4F9B1 /* NearFuture */, A920C2862D24011400E4F9B1 /* NearFuture */,
A979F6082D270AF00094C0B3 /* NearFutureWidgets */, A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
A980FC382D93FB2B006A778F /* NearFutureTests */, A980FC382D93FB2B006A778F /* NearFutureTests */,
@@ -149,16 +155,18 @@
A920C2862D24011400E4F9B1 /* NearFuture */ = { A920C2862D24011400E4F9B1 /* NearFuture */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A979F57E2D26B1300094C0B3 /* EditEventView.swift */, A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */, A920C28B2D24011400E4F9B1 /* Item.swift */,
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */, A920C2C02D2403CA00E4F9B1 /* ContentView.swift */,
A985104F2DB263F00013D5FF /* EventListView.swift */, A985104F2DB263F00013D5FF /* EventListView.swift */,
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */,
A979F57E2D26B1300094C0B3 /* EditEventView.swift */,
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */,
A93BC0932D2B18A3002E8BBD /* StatsView.swift */, A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */, A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */, A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A980FC302D920097006A778F /* Info.plist */, A980FC302D920097006A778F /* Info.plist */,
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
A920C28B2D24011400E4F9B1 /* Item.swift */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */, A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */, A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */,
A920C2902D24011A00E4F9B1 /* Preview Content */, A920C2902D24011A00E4F9B1 /* Preview Content */,
@@ -283,7 +291,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1620; LastSwiftUpdateCheck = 1620;
LastUpgradeCheck = 1620; LastUpgradeCheck = 1630;
TargetAttributes = { TargetAttributes = {
A920C2832D24011300E4F9B1 = { A920C2832D24011300E4F9B1 = {
CreatedOnToolsVersion = 15.4; CreatedOnToolsVersion = 15.4;
@@ -356,9 +364,11 @@
A98510502DB263F00013D5FF /* EventListView.swift in Sources */, A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */, A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */, A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */,
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */,
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */, A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */,
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */,
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */, A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */,
A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */, A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */,
); );
@@ -435,6 +445,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 8JGND254B7;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -462,6 +473,7 @@
); );
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
}; };
name = Debug; name = Debug;
}; };
@@ -501,6 +513,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 8JGND254B7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -516,25 +529,27 @@
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 6.0;
}; };
name = Release; name = Release;
}; };
A920C2AC2D24011B00E4F9B1 /* Debug */ = { A920C2AC2D24011B00E4F9B1 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements; CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25; CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\"";
DEVELOPMENT_TEAM = 85Q9QG6DN7; DEVELOPMENT_TEAM = "$(TEAM_ID)";
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFuture/Info.plist; INFOPLIST_FILE = NearFuture/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; INFOPLIST_KEY_CFBundleDisplayName = "$(NAME)";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
@@ -546,16 +561,16 @@
"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; IPHONEOS_DEPLOYMENT_TARGET = 16.4;
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;
MARKETING_VERSION = 2.0; MARKETING_VERSION = "$(VERSION)";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-Xlinker", "-Xlinker",
"-interposable", "-interposable",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO; REGISTER_APP_GROUPS = NO;
SDKROOT = auto; SDKROOT = auto;
@@ -571,20 +586,21 @@
}; };
A920C2AD2D24011B00E4F9B1 /* Release */ = { A920C2AD2D24011B00E4F9B1 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements; CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25; CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\"";
DEVELOPMENT_TEAM = 85Q9QG6DN7; DEVELOPMENT_TEAM = "$(TEAM_ID)";
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NearFuture/Info.plist; INFOPLIST_FILE = NearFuture/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; INFOPLIST_KEY_CFBundleDisplayName = "$(NAME)";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
@@ -596,13 +612,13 @@
"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; IPHONEOS_DEPLOYMENT_TARGET = 16.4;
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;
MARKETING_VERSION = 2.0; MARKETING_VERSION = "$(VERSION)";
OTHER_LDFLAGS = ""; OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture; PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = NO; REGISTER_APP_GROUPS = NO;
SDKROOT = auto; SDKROOT = auto;
@@ -618,13 +634,13 @@
}; };
A979F6162D270AF90094C0B3 /* Debug */ = { A979F6162D270AF90094C0B3 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
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 = 25; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 85Q9QG6DN7;
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;
@@ -635,12 +651,12 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 2.0; MARKETING_VERSION = 3.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"-Xlinker", "-Xlinker",
"-interposable", "-interposable",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture.NearFutureWidgets; PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -654,13 +670,13 @@
}; };
A979F6172D270AF90094C0B3 /* Release */ = { A979F6172D270AF90094C0B3 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
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 = 25; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 85Q9QG6DN7;
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;
@@ -671,8 +687,8 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 2.0; MARKETING_VERSION = 3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture.NearFutureWidgets; PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -687,11 +703,11 @@
}; };
A980FC3E2D93FB2B006A778F /* Debug */ = { A980FC3E2D93FB2B006A778F /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = P6PV2R9443;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16; IPHONEOS_DEPLOYMENT_TARGET = 16;
MACOSX_DEPLOYMENT_TARGET = 13; MACOSX_DEPLOYMENT_TARGET = 13;
@@ -713,11 +729,11 @@
}; };
A980FC3F2D93FB2B006A778F /* Release */ = { A980FC3F2D93FB2B006A778F /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = P6PV2R9443;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16; IPHONEOS_DEPLOYMENT_TARGET = 16;
MACOSX_DEPLOYMENT_TARGET = 13; MACOSX_DEPLOYMENT_TARGET = 13;

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A920C2832D24011300E4F9B1"
BuildableName = "NearFuture.app"
BlueprintName = "NearFuture"
ReferencedContainer = "container:NearFuture.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A920C2832D24011300E4F9B1"
BuildableName = "NearFuture.app"
BlueprintName = "NearFuture"
ReferencedContainer = "container:NearFuture.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A920C2832D24011300E4F9B1"
BuildableName = "NearFuture.app"
BlueprintName = "NearFuture"
ReferencedContainer = "container:NearFuture.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1620" LastUpgradeVersion = "1630"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1620" LastUpgradeVersion = "1630"
wasCreatedForAppExtension = "YES" wasCreatedForAppExtension = "YES"
version = "2.0"> version = "2.0">
<BuildAction <BuildAction

View File

@@ -4,6 +4,11 @@
<dict> <dict>
<key>SchemeUserState</key> <key>SchemeUserState</key>
<dict> <dict>
<key>NearFuture nodebug.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>NearFuture.xcscheme_^#shared#^_</key> <key>NearFuture.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>

View File

@@ -21,12 +21,13 @@ struct AddEventView: View {
@Binding var eventTime: Bool @Binding var eventTime: Bool
@Binding var eventRecurrence: Event.RecurrenceType @Binding var eventRecurrence: Event.RecurrenceType
@State var adding : Bool @State var adding: Bool
@State var showNeedsNameAlert: Bool = false
@State var isSymbolPickerPresented = false @State var isSymbolPickerPresented = false
@FocusState private var focusedField: Field? @FocusState private var focusedField: Field?
private enum Field { private enum Field {
case Name, Description case Name, Notes
} }
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@@ -60,9 +61,8 @@ struct AddEventView: View {
searchLabel: "Search...", searchLabel: "Search...",
autoDismiss: true) autoDismiss: true)
} }
ColorPicker("", selection: $eventColor, supportsOpacity: true) ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize() .fixedSize()
Divider()
ZStack { ZStack {
TextField("Event Name", text: $eventName) TextField("Event Name", text: $eventName)
.textFieldStyle(RoundedBorderTextFieldStyle()) .textFieldStyle(RoundedBorderTextFieldStyle())
@@ -71,7 +71,7 @@ struct AddEventView: View {
.focused($focusedField, equals: Field.Name) .focused($focusedField, equals: Field.Name)
.submitLabel(.next) .submitLabel(.next)
.onSubmit { .onSubmit {
focusedField = .Description focusedField = .Notes
} }
MagicClearButton(text: $eventName) MagicClearButton(text: $eventName)
} }
@@ -79,11 +79,11 @@ struct AddEventView: View {
// dscription // dscription
ZStack { ZStack {
TextField("Event Description", text: $eventNotes) TextField("Event Notes", text: $eventNotes)
.textFieldStyle(RoundedBorderTextFieldStyle()) .textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.trailing, eventNotes.isEmpty ? 0 : 30) .padding(.trailing, eventNotes.isEmpty ? 0 : 30)
.animation(.spring, value: eventNotes) .animation(.spring, value: eventNotes)
.focused($focusedField, equals: Field.Description) .focused($focusedField, equals: Field.Notes)
.submitLabel(.done) .submitLabel(.done)
.onSubmit { .onSubmit {
focusedField = nil focusedField = nil
@@ -130,50 +130,68 @@ struct AddEventView: View {
) )
) )
} }
// save button only show iff adding new event
if adding {
Button {
viewModel.addEvent(
newEvent: Event(
name: eventName,
complete: eventComplete,
completeDesc: eventCompleteDesc,
symbol: eventSymbol,
color: ColorCodable(eventColor),
notes: eventNotes,
date: eventDate,
time: eventTime,
recurrence: eventRecurrence
)
)
resetAddEventView()
} label: {
Text("Save Event")
.font(.headline)
.cornerRadius(10)
.buttonStyle(BorderedProminentButtonStyle())
}
.disabled(eventName.isEmpty)
if eventName.isEmpty {
HStack {
Image(systemName: "exclamationmark")
.foregroundStyle(.red)
Text("Give your event a name.")
}
}
}
} }
.scrollContentBackground(.hidden)
.navigationTitle("\(adding ? "Add Event" : "")") .navigationTitle("\(adding ? "Add Event" : "")")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarLeading) {
if adding { if adding {
Button() { Button() {
resetAddEventView()
dismiss() dismiss()
} label: { } label: {
Image(systemName: "xmark.circle.fill") Image(systemName: "xmark.circle.fill")
.symbolRenderingMode(.hierarchical) .symbolRenderingMode(.hierarchical)
.resizable()
.scaledToFit()
.frame(width: 30)
}
}
}
ToolbarItem(placement: .topBarTrailing) {
if adding {
Button {
viewModel.addEvent(
newEvent: Event(
name: eventName,
complete: eventComplete,
completeDesc: eventCompleteDesc,
symbol: eventSymbol,
color: ColorCodable(eventColor),
notes: eventNotes,
date: eventDate,
time: eventTime,
recurrence: eventRecurrence
)
)
resetAddEventView()
} label: {
Text("Save")
.font(.headline)
.cornerRadius(10)
.buttonStyle(BorderedProminentButtonStyle())
}
.disabled(eventName.isEmpty)
.onTapGesture {
if eventName.isEmpty {
showNeedsNameAlert.toggle()
}
}
.alert("Missing Name", isPresented: $showNeedsNameAlert) {
Button("OK", role: .cancel) {
showNeedsNameAlert.toggle()
focusedField = .Name
}
} message: {
Text("Give your Event a name before saving.")
}
if eventName.isEmpty {
HStack {
Image(systemName: "exclamationmark")
.foregroundStyle(.red)
Text("Give your event a name.")
}
} }
} }
} }
@@ -182,14 +200,18 @@ struct AddEventView: View {
} }
func resetAddEventView() { func resetAddEventView() {
//reset addeventView //reset addeventView
eventName = "" eventName = viewModel.template.name
eventSymbol = "star" eventComplete = viewModel.template.complete
eventCompleteDesc = viewModel.template.completeDesc
eventSymbol = viewModel.template.symbol
eventColor = randomColor() eventColor = randomColor()
eventNotes = "" eventNotes = viewModel.template.notes
eventDate = Date() eventDate = viewModel.template.date
eventRecurrence = .none eventTime = viewModel.template.time
eventRecurrence = viewModel.template.recurrence
dismiss() dismiss()
} }
} }
struct MagicClearButton: View { struct MagicClearButton: View {
@Binding var text: String @Binding var text: String
@@ -213,17 +235,24 @@ struct MagicClearButton: View {
} }
#Preview { #Preview {
AddEventView( let vm = dummyEventViewModel()
viewModel: EventViewModel(), Color.orange
eventName: .constant("Birthday"), .ignoresSafeArea(.all)
eventComplete: .constant(false), .sheet(isPresented: .constant(true)) {
eventCompleteDesc: .constant(""), AddEventView(
eventSymbol: .constant("star"), viewModel: vm,
eventColor: .constant(Color.red), eventName: .constant(vm.template.notes),
eventNotes: .constant("A very special day"), eventComplete: .constant(vm.template.complete),
eventDate: .constant(Date()), eventCompleteDesc: .constant(vm.template.completeDesc),
eventTime: .constant(true), eventSymbol: .constant(vm.template.symbol),
eventRecurrence: .constant(.monthly), eventColor: .constant(vm.template.color.color),
adding: true 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)
}
} }

View File

@@ -0,0 +1,66 @@
//
// ArchiveView.swift
// NearFuture
//
// Created by neon443 on 25/04/2025.
//
import SwiftUI
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 {
backgroundGradient
if viewModel.events.filter({$0.complete}).isEmpty {
HelpView(showAddEvent: $showAddEvent)
} else {
ScrollView {
ForEach(viewModel.events.filter({$0.complete})) { event in
EventListView(viewModel: viewModel, event: event)
}
.padding(.horizontal)
.id(hey)
.onReceive(viewModel.objectWillChange) {
hey = UUID()
}
}
}
}
.scrollContentBackground(.hidden)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
AddEventButton(showingAddEventView: $showAddEvent)
}
}
}
.sheet(isPresented: $showAddEvent) {
AddEventView(
viewModel: viewModel,
eventName: $viewModel.editableTemplate.name,
eventComplete: $viewModel.editableTemplate.complete,
eventCompleteDesc: $viewModel.editableTemplate.completeDesc,
eventSymbol: $viewModel.editableTemplate.symbol,
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)
}
}
}
#Preview {
ArchiveView(viewModel: dummyEventViewModel())
}

View File

@@ -6,7 +6,7 @@
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "0.000", "blue" : "0.000",
"green" : "0.375", "green" : "0.376",
"red" : "1.000" "red" : "1.000"
} }
}, },

View File

@@ -1,7 +1,31 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "telescope-pointed-at-the-milky-way-galaxy-royalty-free-image-93204189-1533148569-2899733190.jpg", "filename" : "NearFutureIcon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "NearFutureIconDark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "NearFutureIconTint.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.800",
"blue" : "0.576",
"green" : "0.557",
"red" : "0.557"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.300",
"blue" : "0.576",
"green" : "0.557",
"red" : "0.557"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"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,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -11,9 +11,16 @@ import SwiftData
enum Field { enum Field {
case Search case Search
} }
enum Tab {
case Home
case Archive
case Statistics
case Settings
case Gradient
}
struct ContentView: View { struct ContentView: View {
@StateObject private var viewModel = EventViewModel() @StateObject var viewModel: EventViewModel
@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 = ""
@@ -23,6 +30,7 @@ struct ContentView: View {
@State private var eventDate = Date() @State private var eventDate = Date()
@State private var eventTime = false @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 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] {
@@ -36,31 +44,6 @@ struct ContentView: View {
} }
} }
@Environment(\.colorScheme) var appearance
private var backgroundGradient: LinearGradient {
switch appearance {
case .light:
return LinearGradient(
gradient: Gradient(colors: [.gray.opacity(0.2), .white]),
startPoint: .top,
endPoint: .bottom
)
case .dark:
return LinearGradient(
gradient: Gradient(colors: [.gray.opacity(0.2), .black]),
startPoint: .top,
endPoint: .bottom)
@unknown default:
//red bg gradient for uknown appearance
return LinearGradient(
gradient: Gradient(colors: [.red, .black]),
startPoint: .bottom,
endPoint: .top
)
}
}
@State var showSettings: Bool = false
var noEvents: Bool { var noEvents: Bool {
if viewModel.events.count == 0 { if viewModel.events.count == 0 {
return true return true
@@ -70,40 +53,43 @@ struct ContentView: View {
} }
@FocusState private var focusedField: Field? @FocusState private var focusedField: Field?
@FocusState private var focusedTab: Tab?
var body: some View { var body: some View {
TabView { TabView {
NavigationStack { NavigationStack {
ZStack { ZStack {
backgroundGradient backgroundGradient
.ignoresSafeArea(.all)
VStack { VStack {
ZStack { ZStack {
TextField( SearchBar(searchInput: $searchInput)
"\(Image(systemName: "magnifyingglass")) Search", .focused($focusedField, equals: Field.Search)
text: $searchInput .onSubmit {
) focusedField = nil
.padding(.trailing, searchInput.isEmpty ? 0 : 30) }
.animation(.spring, value: searchInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.focused($focusedField, equals: Field.Search)
.onSubmit {
focusedField = nil
}
.submitLabel(.done)
MagicClearButton(text: $searchInput) MagicClearButton(text: $searchInput)
.onTapGesture {
focusedField = nil
}
} }
.padding(.horizontal) .padding(.horizontal)
List { if filteredEvents.isEmpty && !searchInput.isEmpty {
ForEach(filteredEvents) { event in HelpView(searchInput: $searchInput, focusedField: focusedField)
EventListView(viewModel: viewModel, event: event) } else {
} ScrollView {
.onDelete(perform: viewModel.removeEvent) ForEach(filteredEvents) { event in
if !searchInput.isEmpty { EventListView(viewModel: viewModel, event: event)
SearchHelp( }
searchInput: $searchInput, .onDelete(perform: viewModel.removeEvent)
focusedField: _focusedField .id(hey)
) .onReceive(viewModel.objectWillChange) {
hey = UUID()
}
.padding(.horizontal)
if /*!searchInput.isEmpty && */filteredEvents.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField)
}
Spacer()
} }
} }
} }
@@ -123,16 +109,12 @@ struct ContentView: View {
eventRecurrence: $eventRecurrence, eventRecurrence: $eventRecurrence,
adding: true //adding event adding: true //adding event
) )
.presentationDragIndicator(.visible)
.presentationBackground(.ultraThinMaterial)
} }
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button() { AddEventButton(showingAddEventView: $showingAddEventView)
showingAddEventView.toggle()
} label: {
Image(systemName: "plus.circle")
.resizable()
.scaledToFit()
}
} }
} }
} }
@@ -140,44 +122,76 @@ struct ContentView: View {
.tabItem { .tabItem {
Label("Home", systemImage: "house") Label("Home", systemImage: "house")
} }
.focused($focusedTab, equals: Tab.Home)
ArchiveView(viewModel: viewModel)
.tabItem() {
Label("Archive", systemImage: "tray.full")
}
.focused($focusedTab, equals: Tab.Archive)
StatsView(viewModel: viewModel) StatsView(viewModel: viewModel)
.tabItem { .tabItem {
Label("Statistics", systemImage: "chart.pie") Label("Statistics", systemImage: "chart.pie")
} }
.focused($focusedTab, equals: Tab.Statistics)
SettingsView(viewModel: viewModel) SettingsView(viewModel: viewModel)
.tabItem { .tabItem {
Label("Settings", systemImage: "gear") Label("Settings", systemImage: "gear")
} }
} .focused($focusedTab, equals: Tab.Settings)
}
}
struct SearchHelp: View {
@Binding var searchInput: String
@FocusState var focusedField: Field?
var body: some View {
HStack {
Image(systemName: "questionmark.square.dashed")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.padding(.trailing)
Text("Can't find what you're looking for?")
}
Text("Tip: The Search bar searches event names and notes")
Button() {
searchInput = ""
focusedField = nil
} label: {
HStack {
Image(systemName: "xmark")
Text("Clear Filters")
}
.foregroundStyle(Color.accentColor)
} }
} }
} }
#Preview { #Preview {
ContentView() 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)
}
}
}
}
extension View {
var appearance: ColorScheme {
return UITraitCollection.current.userInterfaceStyle == .dark ? .dark : .light
}
var backgroundGradient: some View {
return LinearGradient(
gradient: Gradient(colors: [.bgTop, .two]),
startPoint: .top,
endPoint: .bottom
)
.ignoresSafeArea(.all)
}
} }

View File

@@ -12,38 +12,7 @@ struct EditEventView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@Binding var event: Event @Binding var event: Event
@State private var eventName: String fileprivate func saveEdits() {
@State private var eventComplete: Bool
@State private var eventCompleteDesc: String
@State private var eventSymbol: String
@State private var eventColor: Color
@State private var eventNotes: String
@State private var eventDate: Date
@State private var eventTime: Bool
@State private var eventRecurrence: Event.RecurrenceType
init(viewModel: EventViewModel, event: Binding<Event>) {
self.viewModel = viewModel
_event = event
_eventName = State(initialValue: event.wrappedValue.name)
_eventComplete = State(initialValue: event.wrappedValue.complete)
_eventCompleteDesc = State(initialValue: event.wrappedValue.completeDesc)
_eventSymbol = State(initialValue: event.wrappedValue.symbol)
_eventColor = State(initialValue: event.wrappedValue.color.color)
_eventNotes = State(initialValue: event.wrappedValue.notes)
_eventDate = State(initialValue: event.wrappedValue.date)
_eventTime = State(initialValue: event.wrappedValue.time)
_eventRecurrence = State(initialValue: event.wrappedValue.recurrence)
}
fileprivate func saveEdits() {
event.name = eventName
event.symbol = eventSymbol
event.color = ColorCodable(eventColor)
event.notes = eventNotes
event.date = eventDate
event.recurrence = eventRecurrence
//if there is an event in vM.events with the id of the event we r editing, //if there is an event in vM.events with the id of the event we r editing,
//firstindex - loops through the arr and finds first element where that events id matches editing event's id //firstindex - loops through the arr and finds first element where that events id matches editing event's id
if let index = viewModel.events.firstIndex(where: { xEvent in if let index = viewModel.events.firstIndex(where: { xEvent in
@@ -59,15 +28,15 @@ struct EditEventView: View {
var body: some View { var body: some View {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel,
eventName: $eventName, eventName: $event.name,
eventComplete: $eventComplete, eventComplete: $event.complete,
eventCompleteDesc: $eventCompleteDesc, eventCompleteDesc: $event.completeDesc,
eventSymbol: $eventSymbol, eventSymbol: $event.symbol,
eventColor: $eventColor, eventColor: $event.color.colorBind,
eventNotes: $eventNotes, eventNotes: $event.notes,
eventDate: $eventDate, eventDate: $event.date,
eventTime: $eventTime, eventTime: $event.time,
eventRecurrence: $eventRecurrence, eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event adding: false //bc we editing existing event
) )
.navigationTitle("Edit Event") .navigationTitle("Edit Event")
@@ -77,18 +46,18 @@ struct EditEventView: View {
saveEdits() saveEdits()
} label: { } label: {
Text("Done") Text("Done")
.bold()
} }
.disabled(eventName == "") .disabled(event.name == "")
} }
} }
} }
} }
#Preview { #Preview {
let vm = dummyEventViewModel()
EditEventView( EditEventView(
viewModel: EventViewModel(), viewModel: vm,
event: .constant( event: .constant(vm.example)
EventViewModel().example
)
) )
} }

View File

@@ -19,118 +19,149 @@ struct EventListView: View {
event: $event event: $event
) )
} label: { } label: {
HStack { ZStack {
RoundedRectangle(cornerRadius: 5) HStack {
.frame(width: 5) RoundedRectangle(cornerRadius: 5)
.foregroundStyle( .frame(width: 7)
event.color.color.opacity(
event.complete ? 0.5 : 1
)
)
.padding(.leading, -10)
.padding(.vertical, 5)
.animation(.spring, value: event.complete)
VStack(alignment: .leading) {
HStack {
Image(systemName: event.symbol)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
)
.animation(.spring, value: event.complete)
Text("\(event.name)")
.font(.headline)
.strikethrough(event.complete)
.animation(.spring, value: event.complete)
}
if !event.notes.isEmpty {
Text(event.notes)
.font(.subheadline)
.foregroundColor(.gray)
}
Text(
event.date.formatted(
date: .long,
time: event.time ? .standard : .omitted
)
)
.font(.subheadline)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
)
.animation(.spring, value: event.complete)
if event.recurrence != .none {
Text("Recurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.primary.opacity(
event.complete ? 0.5 : 1
)
)
.animation(.spring, value: event.complete)
}
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date, short: false))")
.font(.subheadline)
.foregroundStyle( .foregroundStyle(
event.color.color.opacity( event.color.color.opacity(
event.complete ? 0.5 : 1 event.complete ? 0.5 : 1
) )
) )
.animation(.spring, value: event.complete) VStack(alignment: .leading) {
} HStack {
Button() { Image(systemName: event.symbol)
withAnimation(.spring) { .resizable()
event.complete.toggle() .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)
}
if !event.notes.isEmpty {
Text(event.notes)
.font(.subheadline)
.foregroundStyle(.one.opacity(0.8))
}
Text(
event.date.formatted(
date: .long,
time: event.time ? .shortened : .omitted
)
)
.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, short: false))")
.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 let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id currEvent.id == event.id
} }
if let eventToModify = eventToModify { if let eventToModify = eventToModify {
viewModel.events[eventToModify] = event viewModel.events.remove(at: eventToModify)
viewModel.saveEvents() viewModel.saveEvents()
viewModel.loadEvents()
} }
} label: { } label: {
if event.complete { Label("Delete", systemImage: "trash")
ZStack {
Circle()
.foregroundStyle(.green)
Image(systemName: "checkmark")
.resizable()
.foregroundStyle(.white)
.scaledToFit()
.frame(width: 15)
}
} else {
Image(systemName: "circle")
.resizable()
.scaledToFit()
.foregroundStyle(event.color.color)
}
} }
.buttonStyle(.borderless)
.frame(maxWidth: 25, maxHeight: 25)
.animation(.spring, value: event.complete)
} }
} }
} }
} }
#Preview("EventListView") { #Preview("EventListView") {
EventListView( let vm = dummyEventViewModel()
viewModel: EventViewModel(), ZStack {
event: Color.black
EventViewModel().example 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)
}
} }

112
NearFuture/HelpView.swift Normal file
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(.accent)
.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

@@ -40,57 +40,38 @@ struct ColorCodable: Codable {
var red: Double var red: Double
var green: Double var green: Double
var blue: Double var blue: Double
var alpha: Double
//for the brainrot kids: alpha is the opacity/transparency of the color,
//alpha == 0 completely transparent
//alpha == 1 completely opaque
// var color: Color {
// get {
// Color(red: red, green: green, blue: blue, opacity: alpha)
// }
// set {
// self.red = newValue.resolve(in: red)
// self.green = newValue.resolve(in: green)
// self.blue = newValue.resolve(in: blue)
// self.alpha = newValue.resolve(in: alpha)
// }
// }
var color: Color { var color: Color {
Color(red: red, green: green, blue: blue, opacity: alpha) Color(red: red, green: green, blue: blue)
} }
var colorBind: Color { var colorBind: Color {
get { get {
return Color( return Color(
red: red, red: red,
green: green, green: green,
blue: blue, blue: blue
opacity: alpha
) )
} set { } set {
let cc = ColorCodable(newValue) let cc = ColorCodable(newValue)
red = cc.red self.red = cc.red
green = cc.green self.green = cc.green
blue = cc.blue self.blue = cc.blue
alpha = cc.alpha
} }
} }
init(_ color: Color) { init(_ color: Color) {
let uiColor = UIColor(color) let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r) self.red = Double(r)
self.green = Double(g) self.green = Double(g)
self.blue = Double(b) self.blue = Double(b)
self.alpha = Double(a)
} }
init(red: Double, green: Double, blue: Double, alpha: Double = 1.0) { init(red: Double, green: Double, blue: Double) {
self.red = red self.red = red
self.green = green self.green = green
self.blue = blue self.blue = blue
self.alpha = alpha
} }
} }
@@ -120,7 +101,18 @@ class EventViewModel: ObservableObject {
@Published var events: [Event] = [] @Published var events: [Event] = []
@Published var icloudData: [Event] = [] @Published var icloudData: [Event] = []
@Published var template: Event = Event( public let template: Event = Event(
name: "",
complete: false,
completeDesc: "",
symbol: "star",
color: ColorCodable(randomColor()),
notes: "",
date: Date(),
time: false,
recurrence: .none
)
@Published var editableTemplate: Event = Event(
name: "", name: "",
complete: false, complete: false,
completeDesc: "", completeDesc: "",
@@ -148,12 +140,14 @@ class EventViewModel: ObservableObject {
@Published var localEventCount: Int = 0 @Published var localEventCount: Int = 0
@Published var syncStatus: String = "Not Synced" @Published var syncStatus: String = "Not Synced"
init() { init(load: Bool = true) {
loadEvents() if load {
loadEvents()
}
} }
//appgroup or regular userdefaults //appgroup or regular userdefaults
let appGroupUserDefaults = UserDefaults(suiteName: "group.com.neon443.NearFuture") ?? UserDefaults.standard let appGroupUserDefaults = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard
//icloud store //icloud store
let icloudStore = NSUbiquitousKeyValueStore.default let icloudStore = NSUbiquitousKeyValueStore.default
@@ -191,6 +185,7 @@ class EventViewModel: ObservableObject {
updateSyncStatus() updateSyncStatus()
loadEvents() loadEvents()
WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events
objectWillChange.send()
} }
} }
@@ -349,6 +344,14 @@ class EventViewModel: ObservableObject {
} }
} }
class dummyEventViewModel: EventViewModel {
override init(load: Bool = false) {
super.init(load: false)
self.events = [self.example, self.template, self.example, self.template]
self.events[0].complete.toggle()
}
}
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
@@ -385,6 +388,5 @@ func randomColor() -> Color {
let r = Double.random(in: 0...1) let r = Double.random(in: 0...1)
let g = Double.random(in: 0...1) let g = Double.random(in: 0...1)
let b = Double.random(in: 0...1) let b = Double.random(in: 0...1)
let a = Double.random(in: 0...1) return Color(red: r, green: g, blue: b)
return Color(red: r, green: g, blue: b, opacity: a)
} }

View File

@@ -1,5 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict/> <dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.NearFuture</string>
</array>
</dict>
</plist> </plist>

View File

@@ -25,7 +25,7 @@ struct NearFutureApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView(viewModel: EventViewModel())
} }
// .modelContainer(sharedModelContainer) // .modelContainer(sharedModelContainer)
} }

View File

@@ -41,109 +41,111 @@ struct SettingsView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { ZStack {
NavigationLink() { backgroundGradient
iCloudSettingsView( List {
viewModel: viewModel, NavigationLink() {
hasUbiquitous: $hasUbiquitous, iCloudSettingsView(
lastSyncWasSuccessful: $lastSyncWasSuccessful, viewModel: viewModel,
lastSyncWasNormalAgo: $lastSyncWasNormalAgo, hasUbiquitous: $hasUbiquitous,
localCountEqualToiCloud: $localCountEqualToiCloud, lastSyncWasSuccessful: $lastSyncWasSuccessful,
icloudCountEqualToLocal: $icloudCountEqualToLocal, lastSyncWasNormalAgo: $lastSyncWasNormalAgo,
updateStatus: updateStatus localCountEqualToiCloud: $localCountEqualToiCloud,
) icloudCountEqualToLocal: $icloudCountEqualToLocal,
} label: { updateStatus: updateStatus
HStack { )
Image(systemName: "icloud.fill") } label: {
Text("iCloud") HStack {
Spacer() Image(systemName: "icloud.fill")
Circle() Text("iCloud")
.frame(width: 20, height: 20) Spacer()
.foregroundStyle(iCloudStatusColor) Circle()
} .frame(width: 20, height: 20)
} .foregroundStyle(iCloudStatusColor)
.onAppear {
viewModel.sync()
updateStatus()
}
NavigationLink() {
NavigationStack() {
Button() {
UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: {
Text("copy")
} }
Text("\(viewModel.exportEvents() ?? "")")
} }
} label: { .onAppear {
Image(systemName: "list.bullet.rectangle") viewModel.sync()
Text("Export events") updateStatus()
} }
NavigationLink() {
NavigationStack() { NavigationLink() {
VStack { NavigationStack() {
TextEditor(text: $importStr)
.foregroundStyle(.foreground, .gray)
.background(.gray)
.frame(width: 200, height: 400)
.shadow(radius: 5)
Button() { Button() {
viewModel.importEvents(importStr) UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: { } label: {
Text("import events") Text("copy")
} }
.buttonStyle(BorderedProminentButtonStyle()) Text("\(viewModel.exportEvents() ?? "")")
Button() { }
if let pb = UIPasteboard.general.string { } label: {
print(pb) Image(systemName: "list.bullet.rectangle")
Text("Export events")
}
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")
} }
} label: {
Text("print pb")
} }
} }
} label: {
Image(systemName: "square.and.arrow.down")
Text("Import events")
} }
} label: {
Image(systemName: "square.and.arrow.down") Section("Tip") {
Text("Import events") Text("Near Future has Widgets!")
}
Section("Tip") {
Text("Near Future has Widgets!")
}
Section("Danger Zone") {
Button("Delete local data", role: .destructive) {
viewModel.dangerClearLocalData()
} }
Button("Delete iCloud data", role: .destructive) {
viewModel.dangerCleariCloudData() Section("Danger Zone") {
Button("Delete local data", role: .destructive) {
viewModel.dangerClearLocalData()
}
Button("Delete iCloud data", role: .destructive) {
viewModel.dangerCleariCloudData()
}
Button("Delete all data", role: .destructive) {
viewModel.dangerClearLocalData()
viewModel.dangerCleariCloudData()
}
} }
Button("Delete all data", role: .destructive) { Section("Debug") {
viewModel.dangerClearLocalData() Button("Reset UserDefaults", role: .destructive) {
viewModel.dangerCleariCloudData() viewModel.dangerResetLocalData()
} }
} Button("Reset iCloud", role: .destructive) {
Section("Debug") { viewModel.dangerResetiCloud()
Button("Reset UserDefaults", role: .destructive) { }
viewModel.dangerResetLocalData()
}
Button("Reset iCloud", role: .destructive) {
viewModel.dangerResetiCloud()
} }
} }
.scrollContentBackground(.hidden)
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
} }
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
} }
} }
} }
#Preview { #Preview {
SettingsView( SettingsView(viewModel: dummyEventViewModel())
viewModel: EventViewModel()
)
} }
func test() -> Void { func test() -> Void {

View File

@@ -13,55 +13,59 @@ struct StatsView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { ZStack {
Section(header: Text("Upcoming Events")) { backgroundGradient
let upcomingEvents = viewModel.events.filter { $0.date > Date() } List {
Text("\(upcomingEvents.count) upcoming event\(upcomingEvents.count == 1 ? "" : "s")") Section(header: Text("Upcoming Events")) {
.font(.headline) let upcomingEvents = viewModel.events.filter { $0.date > Date() }
.foregroundStyle(Color.accentColor) Text("\(upcomingEvents.count) upcoming event\(upcomingEvents.count == 1 ? "" : "s")")
let pastEvents = viewModel.events.filter { $0.date < Date() } .font(.headline)
Text("\(pastEvents.count) past event\(pastEvents.count == 1 ? "" : "s")") .foregroundStyle(Color.accentColor)
.foregroundStyle(.gray) let pastEvents = viewModel.events.filter { $0.date < Date() }
} Text("\(pastEvents.count) past event\(pastEvents.count == 1 ? "" : "s")")
.foregroundStyle(.gray)
Section("Events by Month") {
let eventsByMonth = Dictionary(grouping: viewModel.events, by: { $0.date })
ForEach(eventsByMonth.keys.sorted(), id: \.self) { month in
let count = eventsByMonth[month]?.count ?? 0
Text("\(count) - \(month.formatted(date: .long, time: .omitted))")
} }
}
Section("Event Count") {
let eventCount = viewModel.events.count
Text("\(eventCount) event\(eventCount == 1 ? "" : "s")")
.font(.headline)
.foregroundStyle(Color.accentColor)
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in Section("Events by Month") {
let count = viewModel.events.filter { $0.recurrence == recurrence }.count let eventsByMonth = Dictionary(grouping: viewModel.events, by: { $0.date })
let recurrenceStr = recurrence.rawValue.capitalized ForEach(eventsByMonth.keys.sorted(), id: \.self) { month in
var description: String { let count = eventsByMonth[month]?.count ?? 0
if recurrenceStr == "None" { Text("\(count) - \(month.formatted(date: .long, time: .omitted))")
return "One-Time event\(count == 1 ? "" : "s")" }
} else { }
return "\(recurrenceStr) event\(count == 1 ? "" : "s")"
} Section("Event Count") {
let eventCount = viewModel.events.count
Text("\(eventCount) event\(eventCount == 1 ? "" : "s")")
.font(.headline)
.foregroundStyle(Color.accentColor)
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
let count = viewModel.events.filter { $0.recurrence == recurrence }.count
let recurrenceStr = recurrence.rawValue.capitalized
var description: String {
if recurrenceStr == "None" {
return "One-Time event\(count == 1 ? "" : "s")"
} else {
return "\(recurrenceStr) event\(count == 1 ? "" : "s")"
}
}
Text("\(count) \(description)")
.font(.subheadline)
.foregroundStyle(Color.secondary)
} }
Text("\(count) \(description)")
.font(.subheadline)
.foregroundStyle(Color.secondary)
} }
} }
.scrollContentBackground(.hidden)
.navigationTitle("Statistics")
.navigationBarTitleDisplayMode(.inline)
} }
.navigationTitle("Statistics")
.navigationBarTitleDisplayMode(.inline)
} }
} }
} }
#Preview { #Preview {
StatsView( StatsView(
viewModel: EventViewModel() viewModel: dummyEventViewModel()
) )
} }

View File

@@ -34,154 +34,158 @@ struct iCloudSettingsView: View {
var updateStatus: () -> Void var updateStatus: () -> Void
var body: some View { var body: some View {
List { ZStack {
HStack { backgroundGradient
Spacer() List {
VStack { HStack {
ZStack { Spacer()
Image(systemName: "icloud") VStack {
.resizable() ZStack {
.scaledToFit() Image(systemName: "icloud")
.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() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30, height: 40) .frame(width: 75, height: 55)
.symbolRenderingMode(.multicolor)
Text("\(viewModel.icloudEventCount)")
.font(.title2)
.monospaced()
.bold()
} }
.buttonStyle(BorderedButtonStyle()) Text("iCloud")
.alert("Warning", isPresented: $showPushAlert) { HStack {
Button("OK", role: .destructive) { Button(role: .destructive) {
viewModel.replaceiCloudWithLocalData() 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() viewModel.sync()
updateStatus() 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.")
} }
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
} }
ZStack {
Button() { Image(systemName: device.sf)
viewModel.sync()
updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30, height: 40) .frame(width: 75, height: 75)
.foregroundStyle(Color.accentColor) .symbolRenderingMode(.monochrome)
} Text("\(viewModel.localEventCount)")
.buttonStyle(BorderedButtonStyle()) .font(.title2)
.monospaced()
Button(role: .destructive) { .bold()
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.")
} }
Text(device.label)
} }
ZStack { Spacer()
Image(systemName: device.sf) }
.resizable() .listRowSeparator(.hidden)
.scaledToFit() .onAppear {
.frame(width: 75, height: 75) viewModel.sync()
.symbolRenderingMode(.monochrome) updateStatus()
Text("\(viewModel.localEventCount)") }
.font(.title2)
.monospaced() HStack {
.bold() Circle()
} .frame(width: 20, height: 20)
Text(device.label) .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()
} }
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)
} }
.navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
} }
} }
#Preview("iCloudSettingsView") { #Preview("iCloudSettingsView") {
iCloudSettingsView( iCloudSettingsView(
viewModel: EventViewModel(), viewModel: dummyEventViewModel(),
hasUbiquitous: .constant(true), hasUbiquitous: .constant(true),
lastSyncWasSuccessful: .constant(true), lastSyncWasSuccessful: .constant(true),
lastSyncWasNormalAgo: .constant(true), lastSyncWasNormalAgo: .constant(true),

View File

@@ -76,7 +76,7 @@ struct EventWidgetView: View {
var body: some View { var body: some View {
let isLarge = widgetFamily == .systemLarge let isLarge = widgetFamily == .systemLarge
let events = entry.events let events = entry.events.filter(){!$0.complete}
ZStack { ZStack {
bgGradient bgGradient
.padding(.top, 4) .padding(.top, 4)
@@ -90,51 +90,49 @@ struct EventWidgetView: View {
.padding(.top, -12) .padding(.top, -12)
ForEach(events.prefix(showedEventsNum), id: \.id) { event in ForEach(events.prefix(showedEventsNum), id: \.id) { event in
if !event.complete { HStack {
HStack { RoundedRectangle(cornerRadius: 5)
RoundedRectangle(cornerRadius: 5) .frame(width: 5)
.frame(width: 5) .frame(maxHeight: isLarge ? 50 : 30)
.frame(maxHeight: isLarge ? 50 : 30) .foregroundStyle(event.color.color)
.foregroundStyle(event.color.color) .padding(.leading, -18)
.padding(.leading, -18) .padding(.vertical, 2)
.padding(.vertical, 2) VStack(alignment: .leading) {
VStack(alignment: .leading) { HStack {
HStack { Image(systemName: event.symbol)
Image(systemName: event.symbol) .resizable()
.resizable() .scaledToFit()
.scaledToFit() .frame(width: 15, height: 15)
.frame(width: 15, height: 15) .foregroundStyle(event.color.color)
.foregroundStyle(event.color.color) Text("\(event.name.isEmpty ? " " : event.name)")
Text("\(event.name)") .foregroundStyle(.white)
.foregroundStyle(.white) .font(.footnote)
.font(.footnote) .padding(.leading, -5)
.padding(.leading, -5)
}
if isLarge {
Text(event.date.formatted(date: .long, time: .omitted))
.font(.caption2)
.foregroundColor(event.color.color)
.padding(.top, -5)
}
if event.recurrence != .none {
Text("\(event.recurrence.rawValue.capitalized)")
.foregroundStyle(.white)
.font(.caption2)
.padding(.top, -5)
}
} }
.padding(.leading, -15)
Spacer() if isLarge {
Text(event.date.formatted(date: .long, time: .omitted))
//short days till if not large widget .font(.caption2)
Text(daysUntilEvent(event.date, short: !isLarge, sepLines: true)) .foregroundColor(event.color.color)
.font(.caption) .padding(.top, -5)
.multilineTextAlignment(.trailing) }
.foregroundColor(event.color.color) if event.recurrence != .none {
.padding(.trailing, -12) Text("\(event.recurrence.rawValue.capitalized)")
.foregroundStyle(.white)
.font(.caption2)
.padding(.top, -5)
}
} }
.padding(.leading, -15)
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)
} }
} }
Spacer() Spacer()
@@ -152,38 +150,31 @@ struct EventWidgetView: View {
} }
struct Widget_Previews: PreviewProvider { struct Widget_Previews: PreviewProvider {
static var events = [ static var events = dummyEventViewModel().events
EventViewModel().example,
EventViewModel().example,
EventViewModel().example,
EventViewModel().example
]
static var previews: some View { static var previews: some View {
Group { EventWidgetView(
EventWidgetView( entry: EventWidgetEntry(
entry: EventWidgetEntry( date: Date(),
date: Date(), events: events
events: events
)
) )
.previewContext(WidgetPreviewContext(family: .systemLarge)) )
.previewDisplayName("Large") .previewContext(WidgetPreviewContext(family: .systemLarge))
EventWidgetView( .previewDisplayName("Large")
entry: EventWidgetEntry( EventWidgetView(
date: Date(), entry: EventWidgetEntry(
events: events date: Date(),
) events: events
) )
.previewContext(WidgetPreviewContext(family: .systemMedium)) )
.previewDisplayName("Medium") .previewContext(WidgetPreviewContext(family: .systemMedium))
EventWidgetView( .previewDisplayName("Medium")
entry: EventWidgetEntry( EventWidgetView(
date: Date(), entry: EventWidgetEntry(
events: events date: Date(),
) events: events
) )
.previewContext(WidgetPreviewContext(family: .systemSmall)) )
.previewDisplayName("Small") .previewContext(WidgetPreviewContext(family: .systemSmall))
} .previewDisplayName("Small")
} }
} }

View File

@@ -4,6 +4,10 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.NearFuture</string>
</array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
</dict> </dict>

View File

@@ -1,6 +1,7 @@
# 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)
Near Future is a SwiftUI App to help people to 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.
## Roadmap ## Roadmap
- [x] Add and delete events - [x] Add and delete events
@@ -19,6 +20,7 @@ Near Future is a SwiftUI App to help people to track upcoming events - Holidays,
- [ ] Archive - [ ] Archive
- [ ] Collaboration - [ ] Collaboration
- [ ] Autocomplete tasks - [ ] Autocomplete tasks
- [ ] Settings
## Features ## Features
- **Event Creation**: Create events with a name, description, date, recurrence, and an icon - **Event Creation**: Create events with a name, description, date, recurrence, and an icon
@@ -43,11 +45,11 @@ Near Future is a SwiftUI App to help people to track upcoming events - Holidays,
4. Hit `Command + R` to Build and Run the Project 4. Hit `Command + R` to Build and Run the Project
## Contributing ## Contributing
Contributions are welcome! just follow these: Contributions are welcome! Just follow these steps:
1. Follow [#Compiling] to get a copy on your machine 1. Follow [#Compiling] to get a copy on your machine
2. Optionally create a feature branch for your additions 2. Optionally create a feature branch for your additions
3. Open a pull request 3. Open a pull request
## Used Tools/Frameworks ## Used Tools/Frameworks
- Swift & SwiftUI by Apple - Swift & SwiftUI by Apple
- **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI]. - **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI].