diff --git a/NearFuture.xcodeproj/project.pbxproj b/NearFuture.xcodeproj/project.pbxproj index 36f44a1..8d55a3b 100644 --- a/NearFuture.xcodeproj/project.pbxproj +++ b/NearFuture.xcodeproj/project.pbxproj @@ -11,32 +11,45 @@ A920C28C2D24011400E4F9B1 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; }; A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; }; A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; }; - A920C29C2D24011A00E4F9B1 /* NearFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C29B2D24011A00E4F9B1 /* NearFutureTests.swift */; }; - A920C2A62D24011B00E4F9B1 /* NearFutureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2A52D24011B00E4F9B1 /* NearFutureUITests.swift */; }; - A920C2A82D24011B00E4F9B1 /* NearFutureUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2A72D24011B00E4F9B1 /* NearFutureUITestsLaunchTests.swift */; }; A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2B42D2401A100E4F9B1 /* SettingsView.swift */; }; A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2B72D2401A300E4F9B1 /* AddEventView.swift */; }; A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; }; A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; }; + A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; }; + A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; }; + A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; }; + A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; }; + A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; }; + A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; }; + A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + A979F6182D2714310094C0B3 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; }; + A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - A920C2982D24011A00E4F9B1 /* PBXContainerItemProxy */ = { + A979F6122D270AF90094C0B3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A920C27C2D24011300E4F9B1 /* Project object */; proxyType = 1; - remoteGlobalIDString = A920C2832D24011300E4F9B1; - remoteInfo = NearFuture; - }; - A920C2A22D24011B00E4F9B1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = A920C27C2D24011300E4F9B1 /* Project object */; - proxyType = 1; - remoteGlobalIDString = A920C2832D24011300E4F9B1; - remoteInfo = NearFuture; + remoteGlobalIDString = A979F6012D270AF00094C0B3; + remoteInfo = NearFutureWidgetsExtension; }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + A979F59C2D27006D0094C0B3 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 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 = ""; }; @@ -44,14 +57,25 @@ A920C28D2D24011A00E4F9B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFuture.entitlements; sourceTree = ""; }; A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - A920C2972D24011A00E4F9B1 /* NearFutureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NearFutureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A920C29B2D24011A00E4F9B1 /* NearFutureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureTests.swift; sourceTree = ""; }; - A920C2A12D24011B00E4F9B1 /* NearFutureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NearFutureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A920C2A52D24011B00E4F9B1 /* NearFutureUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureUITests.swift; sourceTree = ""; }; - A920C2A72D24011B00E4F9B1 /* NearFutureUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureUITestsLaunchTests.swift; sourceTree = ""; }; A920C2B42D2401A100E4F9B1 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = ""; }; A920C2C02D2403CA00E4F9B1 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = ""; }; + A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = ""; }; + A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = ""; }; + A979F58F2D2700680094C0B3 /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = ""; }; + A979F5912D2700680094C0B3 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = ""; }; + A979F5932D27006D0094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A979F5952D27006D0094C0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + A979F6042D270AF00094C0B3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + A979F6062D270AF00094C0B3 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = ""; }; + A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = ""; }; + A979F60F2D270AF80094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + A979F6112D270AF90094C0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFutureWidgetsExtension.entitlements; sourceTree = ""; }; + A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,17 +87,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - A920C2942D24011A00E4F9B1 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A920C29E2D24011B00E4F9B1 /* Frameworks */ = { + A979F5FF2D270AF00094C0B3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */, + A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,8 +103,8 @@ isa = PBXGroup; children = ( A920C2862D24011400E4F9B1 /* NearFuture */, - A920C29A2D24011A00E4F9B1 /* NearFutureTests */, - A920C2A42D24011B00E4F9B1 /* NearFutureUITests */, + A979F6082D270AF00094C0B3 /* NearFutureWidgets */, + A979F6032D270AF00094C0B3 /* Frameworks */, A920C2852D24011400E4F9B1 /* Products */, ); sourceTree = ""; @@ -94,8 +113,7 @@ isa = PBXGroup; children = ( A920C2842D24011400E4F9B1 /* NearFuture.app */, - A920C2972D24011A00E4F9B1 /* NearFutureTests.xctest */, - A920C2A12D24011B00E4F9B1 /* NearFutureUITests.xctest */, + A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -103,6 +121,7 @@ A920C2862D24011400E4F9B1 /* NearFuture */ = { isa = PBXGroup; children = ( + A979F57E2D26B1300094C0B3 /* EditEventView.swift */, A920C2B72D2401A300E4F9B1 /* AddEventView.swift */, A920C2C02D2403CA00E4F9B1 /* ContentView.swift */, A920C2B42D2401A100E4F9B1 /* SettingsView.swift */, @@ -118,26 +137,45 @@ A920C2902D24011A00E4F9B1 /* Preview Content */ = { isa = PBXGroup; children = ( + A979F58A2D2700680094C0B3 /* NearFutureWidgets */, A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; - A920C29A2D24011A00E4F9B1 /* NearFutureTests */ = { + A979F58A2D2700680094C0B3 /* NearFutureWidgets */ = { isa = PBXGroup; children = ( - A920C29B2D24011A00E4F9B1 /* NearFutureTests.swift */, + A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */, + A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */, + A979F58F2D2700680094C0B3 /* NearFutureWidgets.swift */, + A979F5912D2700680094C0B3 /* AppIntent.swift */, + A979F5932D27006D0094C0B3 /* Assets.xcassets */, + A979F5952D27006D0094C0B3 /* Info.plist */, ); - path = NearFutureTests; + path = NearFutureWidgets; sourceTree = ""; }; - A920C2A42D24011B00E4F9B1 /* NearFutureUITests */ = { + A979F6032D270AF00094C0B3 /* Frameworks */ = { isa = PBXGroup; children = ( - A920C2A52D24011B00E4F9B1 /* NearFutureUITests.swift */, - A920C2A72D24011B00E4F9B1 /* NearFutureUITestsLaunchTests.swift */, + A979F6042D270AF00094C0B3 /* WidgetKit.framework */, + A979F6062D270AF00094C0B3 /* SwiftUI.framework */, ); - path = NearFutureUITests; + name = Frameworks; + sourceTree = ""; + }; + A979F6082D270AF00094C0B3 /* NearFutureWidgets */ = { + isa = PBXGroup; + children = ( + A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */, + A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */, + A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */, + A979F60F2D270AF80094C0B3 /* Assets.xcassets */, + A979F6112D270AF90094C0B3 /* Info.plist */, + A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */, + ); + path = NearFutureWidgets; sourceTree = ""; }; /* End PBXGroup section */ @@ -150,10 +188,12 @@ A920C2802D24011300E4F9B1 /* Sources */, A920C2812D24011300E4F9B1 /* Frameworks */, A920C2822D24011300E4F9B1 /* Resources */, + A979F59C2D27006D0094C0B3 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + A979F6132D270AF90094C0B3 /* PBXTargetDependency */, ); name = NearFuture; packageProductDependencies = ( @@ -163,41 +203,22 @@ productReference = A920C2842D24011400E4F9B1 /* NearFuture.app */; productType = "com.apple.product-type.application"; }; - A920C2962D24011A00E4F9B1 /* NearFutureTests */ = { + A979F6012D270AF00094C0B3 /* NearFutureWidgetsExtension */ = { isa = PBXNativeTarget; - buildConfigurationList = A920C2AE2D24011B00E4F9B1 /* Build configuration list for PBXNativeTarget "NearFutureTests" */; + buildConfigurationList = A979F6152D270AF90094C0B3 /* Build configuration list for PBXNativeTarget "NearFutureWidgetsExtension" */; buildPhases = ( - A920C2932D24011A00E4F9B1 /* Sources */, - A920C2942D24011A00E4F9B1 /* Frameworks */, - A920C2952D24011A00E4F9B1 /* Resources */, + A979F5FE2D270AF00094C0B3 /* Sources */, + A979F5FF2D270AF00094C0B3 /* Frameworks */, + A979F6002D270AF00094C0B3 /* Resources */, ); buildRules = ( ); dependencies = ( - A920C2992D24011A00E4F9B1 /* PBXTargetDependency */, ); - name = NearFutureTests; - productName = NearFutureTests; - productReference = A920C2972D24011A00E4F9B1 /* NearFutureTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - A920C2A02D24011B00E4F9B1 /* NearFutureUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = A920C2B12D24011B00E4F9B1 /* Build configuration list for PBXNativeTarget "NearFutureUITests" */; - buildPhases = ( - A920C29D2D24011B00E4F9B1 /* Sources */, - A920C29E2D24011B00E4F9B1 /* Frameworks */, - A920C29F2D24011B00E4F9B1 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - A920C2A32D24011B00E4F9B1 /* PBXTargetDependency */, - ); - name = NearFutureUITests; - productName = NearFutureUITests; - productReference = A920C2A12D24011B00E4F9B1 /* NearFutureUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; + name = NearFutureWidgetsExtension; + productName = NearFutureWidgetsExtension; + productReference = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; + productType = "com.apple.product-type.app-extension"; }; /* End PBXNativeTarget section */ @@ -212,13 +233,8 @@ A920C2832D24011300E4F9B1 = { CreatedOnToolsVersion = 15.4; }; - A920C2962D24011A00E4F9B1 = { + A979F6012D270AF00094C0B3 = { CreatedOnToolsVersion = 15.4; - TestTargetID = A920C2832D24011300E4F9B1; - }; - A920C2A02D24011B00E4F9B1 = { - CreatedOnToolsVersion = 15.4; - TestTargetID = A920C2832D24011300E4F9B1; }; }; }; @@ -239,8 +255,7 @@ projectRoot = ""; targets = ( A920C2832D24011300E4F9B1 /* NearFuture */, - A920C2962D24011A00E4F9B1 /* NearFutureTests */, - A920C2A02D24011B00E4F9B1 /* NearFutureUITests */, + A979F6012D270AF00094C0B3 /* NearFutureWidgetsExtension */, ); }; /* End PBXProject section */ @@ -255,17 +270,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - A920C2952D24011A00E4F9B1 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A920C29F2D24011B00E4F9B1 /* Resources */ = { + A979F6002D270AF00094C0B3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -281,38 +290,28 @@ A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */, A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, + A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - A920C2932D24011A00E4F9B1 /* Sources */ = { + A979F5FE2D270AF00094C0B3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A920C29C2D24011A00E4F9B1 /* NearFutureTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A920C29D2D24011B00E4F9B1 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A920C2A62D24011B00E4F9B1 /* NearFutureUITests.swift in Sources */, - A920C2A82D24011B00E4F9B1 /* NearFutureUITestsLaunchTests.swift in Sources */, + A979F6182D2714310094C0B3 /* Item.swift in Sources */, + A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */, + A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */, + A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - A920C2992D24011A00E4F9B1 /* PBXTargetDependency */ = { + A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = A920C2832D24011300E4F9B1 /* NearFuture */; - targetProxy = A920C2982D24011A00E4F9B1 /* PBXContainerItemProxy */; - }; - A920C2A32D24011B00E4F9B1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = A920C2832D24011300E4F9B1 /* NearFuture */; - targetProxy = A920C2A22D24011B00E4F9B1 /* PBXContainerItemProxy */; + target = A979F6012D270AF00094C0B3 /* NearFutureWidgetsExtension */; + targetProxy = A979F6122D270AF90094C0B3 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -434,16 +433,18 @@ A920C2AC2D24011B00E4F9B1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; DEVELOPMENT_TEAM = P6PV2R9443; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -454,11 +455,11 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; + IPHONEOS_DEPLOYMENT_TARGET = 15; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0; + MACOSX_DEPLOYMENT_TARGET = 13; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFuture; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -472,16 +473,18 @@ A920C2AD2D24011B00E4F9B1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; DEVELOPMENT_TEAM = P6PV2R9443; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; @@ -492,11 +495,11 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; + IPHONEOS_DEPLOYMENT_TARGET = 15; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0; + MACOSX_DEPLOYMENT_TARGET = 13; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFuture; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -507,93 +510,64 @@ }; name = Release; }; - A920C2AF2D24011B00E4F9B1 /* Debug */ = { + A979F6162D270AF90094C0B3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = P6PV2R9443; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; - MACOSX_DEPLOYMENT_TARGET = 14.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFutureTests; + INFOPLIST_FILE = NearFutureWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFuture.NearFutureWidgets; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; - SWIFT_EMIT_LOC_STRINGS = NO; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NearFuture.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NearFuture"; }; name = Debug; }; - A920C2B02D24011B00E4F9B1 /* Release */ = { + A979F6172D270AF90094C0B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = NearFutureWidgets/NearFutureWidgetsExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = P6PV2R9443; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; - MACOSX_DEPLOYMENT_TARGET = 14.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFutureTests; + INFOPLIST_FILE = NearFutureWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 2.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFuture.NearFutureWidgets; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; - SWIFT_EMIT_LOC_STRINGS = NO; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NearFuture.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NearFuture"; - }; - name = Release; - }; - A920C2B22D24011B00E4F9B1 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = P6PV2R9443; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; - MACOSX_DEPLOYMENT_TARGET = 14.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFutureUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = NearFuture; - }; - name = Debug; - }; - A920C2B32D24011B00E4F9B1 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = P6PV2R9443; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; - MACOSX_DEPLOYMENT_TARGET = 14.5; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFutureUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = NearFuture; + VALIDATE_PRODUCT = YES; }; name = Release; }; @@ -618,20 +592,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - A920C2AE2D24011B00E4F9B1 /* Build configuration list for PBXNativeTarget "NearFutureTests" */ = { + A979F6152D270AF90094C0B3 /* Build configuration list for PBXNativeTarget "NearFutureWidgetsExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( - A920C2AF2D24011B00E4F9B1 /* Debug */, - A920C2B02D24011B00E4F9B1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - A920C2B12D24011B00E4F9B1 /* Build configuration list for PBXNativeTarget "NearFutureUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A920C2B22D24011B00E4F9B1 /* Debug */, - A920C2B32D24011B00E4F9B1 /* Release */, + A979F6162D270AF90094C0B3 /* Debug */, + A979F6172D270AF90094C0B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/NearFuture/AddEventView.swift b/NearFuture/AddEventView.swift index ac9c0f1..f095596 100644 --- a/NearFuture/AddEventView.swift +++ b/NearFuture/AddEventView.swift @@ -10,19 +10,33 @@ import SFSymbolsPicker struct AddEventView: View { @ObservedObject var viewModel: EventViewModel + @Binding var eventName: String @Binding var eventSymbol: String @Binding var eventColor: Color @Binding var eventDescription: String @Binding var eventDate: Date @Binding var eventRecurrence: Event.RecurrenceType - @Binding var isPresented: Bool + + @State var adding : Bool @State var isSymbolPickerPresented = false + @FocusState private var focusedField: Field? + private enum Field { + case Name, Description + } + + @Environment(\.dismiss) var dismiss + var body: some View { - NavigationStack { + NavigationView { Form { - Section(header: Text("Event Details").font(.headline).foregroundColor(.blue)) { + Section( + header: + Text("Event Details") + .font(.headline) + .foregroundColor(.accentColor) + ) { // name & symbol HStack(spacing: 5) { Button() { @@ -32,6 +46,7 @@ struct AddEventView: View { .resizable() .scaledToFit() .frame(width: 25, height: 25) + .foregroundStyle(eventColor) } // .frame(width: 30) .buttonStyle(.bordered) @@ -50,6 +65,10 @@ struct AddEventView: View { .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.trailing, eventName.isEmpty ? 0 : 30) .animation(.spring, value: eventName) + .focused($focusedField, equals: Field.Name) + .onSubmit { + focusedField = .Description + } MagicClearButton(text: $eventName) } } @@ -60,12 +79,16 @@ struct AddEventView: View { .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.trailing, eventDescription.isEmpty ? 0 : 30) .animation(.spring, value: eventDescription) + .focused($focusedField, equals: Field.Description) + .onSubmit { + focusedField = nil + } MagicClearButton(text: $eventDescription) } // date picker - DatePicker("Event Date", selection: $eventDate, in: Date()..., displayedComponents: .date) + DatePicker("Event Date", selection: $eventDate, displayedComponents: .date) .datePickerStyle(WheelDatePickerStyle()) // re-ocurrence Picker @@ -83,64 +106,67 @@ struct AddEventView: View { ) } - // save button - Button { - viewModel.addEvent( - name: eventName, - symbol: eventSymbol, - color: ColorCodable(eventColor), - description: eventDescription, - date: eventDate, - recurrence: eventRecurrence - ) - //reset addeventView - eventName = "" - eventSymbol = "star" - eventColor = [ - Color.red, - Color.orange, - Color.yellow, - Color.green, - Color.blue, - Color.indigo, - Color.purple - ].randomElement() ?? Color.red - eventDescription = "" - eventDate = Date() - eventRecurrence = .none - isPresented = false - } label: { - Text("Save Event") - .font(.headline) - .cornerRadius(10) - .shadow(radius: 10) - .buttonStyle(BorderedProminentButtonStyle()) - } - .disabled(eventName.isEmpty || eventDescription.isEmpty) - if eventName.isEmpty && eventDescription.isEmpty { - Text("Give your event a name and description.") - } else if eventName.isEmpty { - Text("Give your event a name.") - } else if eventDescription.isEmpty { - Text("Give your event a description.") + // save button only show iff adding new event + if adding { + Button { + viewModel.addEvent( + name: eventName, + symbol: eventSymbol, + color: ColorCodable(eventColor), + description: eventDescription, + date: eventDate, + recurrence: eventRecurrence + ) + resetAddEventView() + } label: { + Text("Save Event") + .font(.headline) + .cornerRadius(10) + .shadow(radius: 10) + .buttonStyle(BorderedProminentButtonStyle()) + } + .disabled(eventName.isEmpty) + if eventName.isEmpty { + Text("Give your event a name.") + + } } } - .navigationTitle("Add Event") + .navigationTitle("\(adding ? "Add Event" : "")") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button() { - isPresented.toggle() - } label: { - Image(systemName: "xmark.circle.fill") - .symbolRenderingMode(.hierarchical) + if adding { + Button() { + dismiss() + } label: { + Image(systemName: "xmark.circle.fill") + .symbolRenderingMode(.hierarchical) + } } } } } } + func resetAddEventView() { + //reset addeventView + eventName = "" + eventSymbol = "star" + eventColor = [ + Color.red, + Color.orange, + Color.yellow, + Color.green, + Color.blue, + Color.indigo, + Color.purple + ].randomElement() ?? Color.red + eventDescription = "" + eventDate = Date() + eventRecurrence = .none + dismiss() + } } - struct MagicClearButton: View { @Binding var text: String var body: some View { @@ -176,7 +202,8 @@ struct AddEvent_Preview: PreviewProvider { eventDescription: .constant("A very special day"), eventDate: $date, eventRecurrence: .constant(.monthly), - isPresented: .constant(true) + adding: true ) } } + diff --git a/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json b/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..762fbd2 100644 --- a/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.375", + "red" : "1.000" + } + }, "idiom" : "universal" } ], diff --git a/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json b/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json index 532cd72..ec5da97 100644 --- a/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "telescope-pointed-at-the-milky-way-galaxy-royalty-free-image-93204189-1533148569-2899733190.jpg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/NearFuture/Assets.xcassets/AppIcon.appiconset/telescope-pointed-at-the-milky-way-galaxy-royalty-free-image-93204189-1533148569-2899733190.jpg b/NearFuture/Assets.xcassets/AppIcon.appiconset/telescope-pointed-at-the-milky-way-galaxy-royalty-free-image-93204189-1533148569-2899733190.jpg new file mode 100644 index 0000000..e179716 Binary files /dev/null and b/NearFuture/Assets.xcassets/AppIcon.appiconset/telescope-pointed-at-the-milky-way-galaxy-royalty-free-image-93204189-1533148569-2899733190.jpg differ diff --git a/NearFuture/ContentView.swift b/NearFuture/ContentView.swift index ef32081..684df33 100644 --- a/NearFuture/ContentView.swift +++ b/NearFuture/ContentView.swift @@ -94,6 +94,7 @@ struct ContentView: View { } } } + @Environment(\.colorScheme) var appearance private var backgroundGradient: LinearGradient { switch appearance { @@ -119,6 +120,10 @@ struct ContentView: View { } @State var showSettings: Bool = false + @FocusState private var focusedField: Field? + private enum Field { + case Search + } var body: some View { NavigationView { ZStack { @@ -133,24 +138,39 @@ struct ContentView: View { .padding(.trailing, searchInput.isEmpty ? 0 : 30) .animation(.spring, value: searchInput) .textFieldStyle(RoundedBorderTextFieldStyle()) + .focused($focusedField, equals: Field.Search) + .onSubmit { + focusedField = nil + } MagicClearButton(text: $searchInput) } .padding(.horizontal) List { ForEach(filteredEvents) { event in - var eventBackgroundGradient: LinearGradient { - return LinearGradient( - colors: [ - event.color.color, - Color.black - ], - startPoint: .leading, - endPoint: .trailing - ) - } - EventListView(event: event) + EventListView(viewModel: viewModel, event: event) } .onDelete(perform: viewModel.removeEvent) + if !searchInput.isEmpty { + 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 descriptions") + Button() { + searchInput = "" + focusedField = nil + } label: { + HStack { + Image(systemName: "xmark") + Text("Clear Filters") + } + .foregroundStyle(Color.accentColor) + } + } } } .navigationTitle("Near Future") @@ -164,14 +184,13 @@ struct ContentView: View { eventDescription: $eventDescription, eventDate: $eventDate, eventRecurrence: $eventRecurrence, - isPresented: $showingAddEventView + adding: true //adding event ) } .sheet( isPresented: $showSettings) { SettingsView( - viewModel: viewModel, - showSettings: $showSettings + viewModel: viewModel ) } .toolbar { @@ -193,52 +212,61 @@ struct ContentView: View { } } } - } } } struct EventListView: View { + @StateObject var viewModel: EventViewModel @State var event: Event var body: some View { -// var testColor = Color.red -// var codableColor = ColorCodable(testColor) -// Text("\(codableColor.red), \(codableColor.green), \(codableColor.blue), \(codableColor.alpha)") - ZStack { + NavigationLink() { + EditEventView( + viewModel: viewModel, + event: $event + ) + } label: { HStack { RoundedRectangle(cornerRadius: 5) .frame(width: 5) .foregroundStyle(event.color.color) - .padding(.leading, -5) + .padding(.leading, -10) + .padding(.vertical, 5) VStack(alignment: .leading) { HStack { - Text("\(Image(systemName: event.symbol)) \(event.name)") + Image(systemName: event.symbol) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundStyle(event.color.color) + Text("\(event.name)") .font(.headline) } - Text(event.description) - .font(.subheadline) - .foregroundColor(.gray) - if event.recurrence != .none { - Text("Recurring: \(event.recurrence.rawValue.capitalized)") + if !event.description.isEmpty { + Text(event.description) .font(.subheadline) - .foregroundColor(.blue) + .foregroundColor(.gray) } Text(event.date.formatted(date: .long, time: .omitted)) .font(.subheadline) - .foregroundColor(.blue) + .foregroundColor(event.color.color) + if event.recurrence != .none { + Text("Recurring: \(event.recurrence.rawValue.capitalized)") + .font(.subheadline) + } } Spacer() - Text("\(daysUntilEvent(event.date))") + Text("\(daysUntilEvent(event.date, short: false))") .font(.subheadline) - .foregroundColor(.gray) + .foregroundColor(event.color.color) } - .padding(.vertical, 8) } } } + #Preview { ContentView() } diff --git a/NearFuture/EditEventView.swift b/NearFuture/EditEventView.swift new file mode 100644 index 0000000..b89db96 --- /dev/null +++ b/NearFuture/EditEventView.swift @@ -0,0 +1,89 @@ +// +// EditEventView.swift +// NearFuture +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import SwiftUI + +struct EditEventView: View { + @Environment(\.dismiss) var dismiss + @ObservedObject var viewModel: EventViewModel + @Binding var event: Event + + @State private var eventName: String + @State private var eventSymbol: String + @State private var eventColor: Color + @State private var eventDescription: String + @State private var eventDate: Date + @State private var eventRecurrence: Event.RecurrenceType + + init(viewModel: EventViewModel, event: Binding) { + self.viewModel = viewModel + _event = event + _eventName = State(initialValue: event.wrappedValue.name) + _eventSymbol = State(initialValue: event.wrappedValue.symbol) + _eventColor = State(initialValue: event.wrappedValue.color.color) + _eventDescription = State(initialValue: event.wrappedValue.description) + _eventDate = State(initialValue: event.wrappedValue.date) + _eventRecurrence = State(initialValue: event.wrappedValue.recurrence) + } + + var body: some View { +// NavigationView { + AddEventView( + viewModel: viewModel, + eventName: $eventName, + eventSymbol: $eventSymbol, + eventColor: $eventColor, + eventDescription: $eventDescription, + eventDate: $eventDate, + eventRecurrence: $eventRecurrence, + adding: false //bc we editing existing event + ) + .navigationTitle("Edit Event") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button() { + event.name = eventName + event.symbol = eventSymbol + event.color = ColorCodable(eventColor) + event.description = eventDescription + event.date = eventDate + event.recurrence = eventRecurrence + + //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 + if let index = viewModel.events.firstIndex(where: { xEvent in + xEvent.id == event.id + }) { + viewModel.events[index] = event + } + viewModel.saveEvents() + + dismiss() + } label: { + Text("Done") + } + } + } +// } + } +} + +#Preview { + EditEventView( + viewModel: EventViewModel(), + event: .constant( + Event( + name: "Birthday", + symbol: "gear", + color: ColorCodable(.red), + description: "an event", + date: Date(), + recurrence: .yearly + ) + ) + ) +} diff --git a/NearFuture/Item.swift b/NearFuture/Item.swift index 2fc9a0a..95fe0fd 100644 --- a/NearFuture/Item.swift +++ b/NearFuture/Item.swift @@ -8,189 +8,272 @@ import Foundation import SwiftData import SwiftUI +import WidgetKit -@Model -final class Item { - var timestamp: Date - - init(timestamp: Date) { - self.timestamp = timestamp - } -} +//@Model +//final class Item { +// var timestamp: Date +// +// init(timestamp: Date) { +// self.timestamp = timestamp +// } +//} struct Event: Identifiable, Codable { - var id = UUID() - var name: String - var symbol: String - var color: ColorCodable - var description: String - var date: Date - var recurrence: RecurrenceType - - enum RecurrenceType: String, Codable, CaseIterable { - case none, daily, weekly, monthly, yearly - } + var id = UUID() + var name: String + var symbol: String + var color: ColorCodable + var description: String + var date: Date + var recurrence: RecurrenceType + + enum RecurrenceType: String, Codable, CaseIterable { + case none, daily, weekly, monthly, yearly + } } struct ColorCodable: Codable { - var red: Double - var green: Double - var blue: Double - var alpha: Double - //for the brainrotted: alpha is the opacity/transparency of the color, - //alpha == 0 completely transparent - //alpha == 1 completely opaque - - var color: Color { - Color(red: red, green: green, blue: blue, opacity: alpha) - } - - init(_ color: Color) { - let uiColor = UIColor(color) - var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 - uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) - - self.red = Double(r) - self.green = Double(g) - self.blue = Double(b) - self.alpha = Double(a) - - } - init(red: Double, green: Double, blue: Double, alpha: Double = 1.0) { - self.red = red - self.green = green - self.blue = blue - self.alpha = alpha - } + var red: Double + var green: Double + var blue: Double + var alpha: Double + //for the brainrotted: alpha is the opacity/transparency of the color, + //alpha == 0 completely transparent + //alpha == 1 completely opaque + + var color: Color { + Color(red: red, green: green, blue: blue, opacity: alpha) + } + + init(_ color: Color) { + let uiColor = UIColor(color) + var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) + + self.red = Double(r) + self.green = Double(g) + self.blue = Double(b) + self.alpha = Double(a) + } + init(red: Double, green: Double, blue: Double, alpha: Double = 1.0) { + self.red = red + self.green = green + self.blue = blue + self.alpha = alpha + } } -func daysUntilEvent(_ eventDate: Date) -> String { - let calendar = Calendar.current - let currentDate = Date() - let components = calendar.dateComponents([.day], from: currentDate, to: eventDate) - guard let days = components.day else { return "N/A" } - guard days >= 0 else { - return "\(days) days ago" - } - guard days != 0 else { - return "Today" - } - return "\(days) days" +func daysUntilEvent(_ eventDate: Date, short: Bool) -> String { + let calendar = Calendar.current + let currentDate = Date() + let components = calendar.dateComponents([.day], from: currentDate, to: eventDate) + guard let days = components.day else { return "N/A" } + guard days >= 0 else { + if short { + return "\(days)d" + } else { + return "\(-days) day\(-days == 1 ? "" : "s") ago" + } + } + guard days != 0 else { + return "Today" + } + if short { + return "\(days)d" + } else { + return "\(days) day\(days == 1 ? "" : "s")" + } } class EventViewModel: ObservableObject { - @Published var events: [Event] = [] - @Published var icloudData: [Event] = [] - - init() { - loadEvents() - } - - //icloud - let icloudStore = NSUbiquitousKeyValueStore.default - - func loadEvents() { - //load icloud 1st - if let icData = icloudStore.data(forKey: "events") { - let decoder = JSONDecoder() - if let decodedIcEvents = try? decoder.decode([Event].self, from: icData) { - self.icloudData = decodedIcEvents - self.events = decodedIcEvents - } - } - - if events.isEmpty, let savedData = UserDefaults.standard.data(forKey: "events") { - let decoder = JSONDecoder() - if let decodedEvents = try? decoder.decode([Event].self, from: savedData) { - self.events = decodedEvents - } - } - } - - func saveEvents() { - let encoder = JSONEncoder() - if let encoded = try? encoder.encode(events) { - UserDefaults.standard.set(encoded, forKey: "events") - -// do { - icloudStore.set(encoded, forKey: "events") - icloudStore.synchronize() -// } catch { -// print("Error saving to iCloud: \(error)") -// } - - if icloudStore.data(forKey: "events") != nil { - print(icloudStore.dictionaryRepresentation) - } - } - } - - func addEvent( - name: String, - symbol: String, - color: ColorCodable, - description: String, - date: Date, - recurrence: Event.RecurrenceType - ) { - let newEvent = Event( - name: name, - symbol: symbol, - color: color, - description: description, - date: date, - recurrence: recurrence - ) - events.append(newEvent) - saveEvents() //sync with icloud - } - - func removeEvent(at index: IndexSet) { - events.remove(atOffsets: index) - saveEvents() //sync local and icl - } - - //MARK: Danger Zone - func dangerClearLocalData() { - UserDefaults.standard.removeObject(forKey: "events") - } - func dangerCleariCloudData() { - let icloud = NSUbiquitousKeyValueStore() - icloud.removeObject(forKey: "events") - icloud.synchronize() - } - func dangerResetLocalData() { - let userDFDict = UserDefaults.standard.dictionaryRepresentation() - for key in userDFDict.keys { - UserDefaults.standard.removeObject(forKey: key) - } - } - func dangerResetiCloud() { - let icloud = NSUbiquitousKeyValueStore() - let icloudDict = NSUbiquitousKeyValueStore().dictionaryRepresentation - for key in icloudDict.keys { - icloud.removeObject(forKey: key) - } - icloud.synchronize() - } + @Published var events: [Event] = [] + @Published var icloudData: [Event] = [] + + @Published var lastSync: Date? = nil + @Published var icloudEventCount: Int = 0 + @Published var localEventCount: Int = 0 + @Published var syncStatus: String = "Not Synced" + + init() { + loadEvents() + } + + //appgroup or regular userdefaults + let appGroupUserDefaults = UserDefaults(suiteName: "group.com.neon443.NearFuture") ?? UserDefaults.standard + + //icloud store + let icloudStore = NSUbiquitousKeyValueStore.default + + // load from icloud or local + func loadEvents() { + //load icloud 1st + if let icData = icloudStore.data(forKey: "events") { + let decoder = JSONDecoder() + if let decodedIcEvents = try? decoder.decode([Event].self, from: icData) { + self.icloudData = decodedIcEvents + self.events = decodedIcEvents + } + } + + if events.isEmpty, let savedData = appGroupUserDefaults.data(forKey: "events") { + let decoder = JSONDecoder() + if let decodedEvents = try? decoder.decode([Event].self, from: savedData) { + self.events = decodedEvents + } + } + updateSyncStatus() + } + + // save to local and icloud + func saveEvents() { + let encoder = JSONEncoder() + if let encoded = try? encoder.encode(events) { + appGroupUserDefaults.set(encoded, forKey: "events") + + //sync + icloudStore.set(encoded, forKey: "events") + icloudStore.synchronize() + + updateSyncStatus() + loadEvents() + WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events + } + } + + private func updateSyncStatus() { + lastSync = Date() + icloudEventCount = icloudData.count + localEventCount = events.count + + if icloudEventCount == localEventCount { + syncStatus = "Successful" + } else { + syncStatus = "Pending" + } + } + + func addEvent( + name: String, + symbol: String, + color: ColorCodable, + description: String, + date: Date, + recurrence: Event.RecurrenceType + ) { + let newEvent = Event( + name: name, + symbol: symbol, + color: color, + description: description, + date: date, + recurrence: recurrence + ) + events.append(newEvent) + saveEvents() //sync with icloud + } + + func removeEvent(at index: IndexSet) { + events.remove(atOffsets: index) + saveEvents() //sync local and icl + } + + func hasUbiquitousKeyValueStore() -> Bool { + let icloud = NSUbiquitousKeyValueStore.default + + let key = "com.neon443.NearFuture.testkey" + let value = "testValue" + + icloud.set(value, forKey: key) + icloud.synchronize() + + if let retrievedVal = icloud.string(forKey: key) { + print("has UbiquitousKeyValueStore: retrieved \(retrievedVal)") + icloud.removeObject(forKey: key) + icloud.synchronize() + return true + } else { + print("!has UbiquitousKeyValueStore") + icloud.removeObject(forKey: key) + icloud.synchronize() + return false + } + } + + func sync() { + NSUbiquitousKeyValueStore.default.synchronize() + loadEvents() + } + + func replaceLocalWithiCloudData() { + icloudStore.synchronize() + self.events = self.icloudData + saveEvents() + } + + func replaceiCloudWithLocalData() { + icloudStore.synchronize() + self.icloudData = self.events + saveEvents() + } + + //MARK: Danger Zone + func dangerClearLocalData() { + UserDefaults.standard.removeObject(forKey: "events") + appGroupUserDefaults.removeObject(forKey: "events") + events.removeAll() + updateSyncStatus() + } + + func dangerCleariCloudData() { + icloudStore.removeObject(forKey: "events") + icloudStore.synchronize() + icloudData.removeAll() + updateSyncStatus() + } + + func dangerResetLocalData() { + let userDFDict = UserDefaults.standard.dictionaryRepresentation() + for key in userDFDict.keys { + UserDefaults.standard.removeObject(forKey: key) + } + + let appGUSDDict = appGroupUserDefaults.dictionaryRepresentation() + for key in appGUSDDict.keys { + appGroupUserDefaults.removeObject(forKey: key) + } + + events.removeAll() + updateSyncStatus() + } + + func dangerResetiCloud() { + let icloudDict = icloudStore.dictionaryRepresentation + for key in icloudDict.keys { + icloudStore.removeObject(forKey: key) + } + icloudStore.synchronize() + icloudData.removeAll() + updateSyncStatus() + } } func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String { - let dateString = date.formatted(date: .long, time: .omitted) - let recurrenceDescription: String - - switch recurrence { - case .none: - recurrenceDescription = "Occurs once on" - case .daily: - recurrenceDescription = "Repeats every day from" - case .weekly: - recurrenceDescription = "Repeats every week from" - case .monthly: - recurrenceDescription = "Repeats every month from" - case .yearly: - recurrenceDescription = "Repeats every year from" - } - - return "\(recurrenceDescription) \(dateString)" + let dateString = date.formatted(date: .long, time: .omitted) + let recurrenceDescription: String + + switch recurrence { + case .none: + recurrenceDescription = "Occurs once on" + case .daily: + recurrenceDescription = "Repeats every day from" + case .weekly: + recurrenceDescription = "Repeats every week from" + case .monthly: + recurrenceDescription = "Repeats every month from" + case .yearly: + recurrenceDescription = "Repeats every year from" + } + + return "\(recurrenceDescription) \(dateString)" } diff --git a/NearFuture/NearFuture.entitlements b/NearFuture/NearFuture.entitlements index ad259ee..df5ecbf 100644 --- a/NearFuture/NearFuture.entitlements +++ b/NearFuture/NearFuture.entitlements @@ -6,9 +6,9 @@ com.apple.developer.ubiquity-kvstore-identifier $(TeamIdentifierPrefix)$(CFBundleIdentifier) - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - + com.apple.security.application-groups + + group.com.neon443.NearFuture + diff --git a/NearFuture/NearFutureApp.swift b/NearFuture/NearFutureApp.swift index 2542a42..a346f8d 100644 --- a/NearFuture/NearFutureApp.swift +++ b/NearFuture/NearFutureApp.swift @@ -10,23 +10,23 @@ import SwiftData @main struct NearFutureApp: App { - var sharedModelContainer: ModelContainer = { - let schema = Schema([ - Item.self, - ]) - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - - do { - return try ModelContainer(for: schema, configurations: [modelConfiguration]) - } catch { - fatalError("Could not create ModelContainer: \(error)") - } - }() +// var sharedModelContainer: ModelContainer = { +// let schema = Schema([ +// Item.self, +// ]) +// let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) +// +// do { +// return try ModelContainer(for: schema, configurations: [modelConfiguration]) +// } catch { +// fatalError("Could not create ModelContainer: \(error)") +// } +// }() var body: some Scene { WindowGroup { ContentView() } - .modelContainer(sharedModelContainer) +// .modelContainer(sharedModelContainer) } } diff --git a/NearFuture/Preview Content/NearFutureWidgets/AppIntent.swift b/NearFuture/Preview Content/NearFutureWidgets/AppIntent.swift new file mode 100644 index 0000000..c48db3f --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/AppIntent.swift @@ -0,0 +1,18 @@ +// +// AppIntent.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import WidgetKit +import AppIntents + +struct ConfigurationAppIntent: WidgetConfigurationIntent { + static var title: LocalizedStringResource = "Configuration" + static var description = IntentDescription("This is an example widget.") + + // An example configurable parameter. + @Parameter(title: "Favorite Emoji", default: "πŸ˜ƒ") + var favoriteEmoji: String +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Info.plist b/NearFuture/Preview Content/NearFutureWidgets/Info.plist new file mode 100644 index 0000000..0f118fb --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgets.swift b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgets.swift new file mode 100644 index 0000000..1bc2e8c --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgets.swift @@ -0,0 +1,84 @@ +// +// NearFutureWidgets.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import WidgetKit +import SwiftUI + +struct Provider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date(), configuration: ConfigurationAppIntent()) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { + SimpleEntry(date: Date(), configuration: configuration) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + var entries: [SimpleEntry] = [] + + // Generate a timeline consisting of five entries an hour apart, starting from the current date. + let currentDate = Date() + for hourOffset in 0 ..< 5 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + let entry = SimpleEntry(date: entryDate, configuration: configuration) + entries.append(entry) + } + + return Timeline(entries: entries, policy: .atEnd) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let configuration: ConfigurationAppIntent +} + +struct NearFutureWidgetsEntryView : View { + var entry: Provider.Entry + + var body: some View { + VStack { + Text("Time:") + Text(entry.date, style: .time) + + Text("Favorite Emoji:") + Text(entry.configuration.favoriteEmoji) + } + } +} + +struct NearFutureWidgets: Widget { + let kind: String = "NearFutureWidgets" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in + NearFutureWidgetsEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } + } +} + +extension ConfigurationAppIntent { + fileprivate static var smiley: ConfigurationAppIntent { + let intent = ConfigurationAppIntent() + intent.favoriteEmoji = "πŸ˜€" + return intent + } + + fileprivate static var starEyes: ConfigurationAppIntent { + let intent = ConfigurationAppIntent() + intent.favoriteEmoji = "🀩" + return intent + } +} + +#Preview(as: .systemSmall) { + NearFutureWidgets() +} timeline: { + SimpleEntry(date: .now, configuration: .smiley) + SimpleEntry(date: .now, configuration: .starEyes) +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsBundle.swift b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsBundle.swift new file mode 100644 index 0000000..53d8f79 --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsBundle.swift @@ -0,0 +1,17 @@ +// +// NearFutureWidgetsBundle.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import WidgetKit +import SwiftUI + +@main +struct NearFutureWidgetsBundle: WidgetBundle { + var body: some Widget { + NearFutureWidgets() + NearFutureWidgetsLiveActivity() + } +} diff --git a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift new file mode 100644 index 0000000..21c5d09 --- /dev/null +++ b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift @@ -0,0 +1,80 @@ +// +// NearFutureWidgetsLiveActivity.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import ActivityKit +import WidgetKit +import SwiftUI + +struct NearFutureWidgetsAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + // Dynamic stateful properties about your activity go here! + var emoji: String + } + + // Fixed non-changing properties about your activity go here! + var name: String +} + +struct NearFutureWidgetsLiveActivity: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in + // Lock screen/banner UI goes here + VStack { + Text("Hello \(context.state.emoji)") + } + .activityBackgroundTint(Color.cyan) + .activitySystemActionForegroundColor(Color.black) + + } dynamicIsland: { context in + DynamicIsland { + // Expanded UI goes here. Compose the expanded UI through + // various regions, like leading/trailing/center/bottom + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom \(context.state.emoji)") + // more content + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T \(context.state.emoji)") + } minimal: { + Text(context.state.emoji) + } + .widgetURL(URL(string: "http://www.apple.com")) + .keylineTint(Color.red) + } + } +} + +extension NearFutureWidgetsAttributes { + fileprivate static var preview: NearFutureWidgetsAttributes { + NearFutureWidgetsAttributes(name: "World") + } +} + +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 +} diff --git a/NearFuture/SettingsView.swift b/NearFuture/SettingsView.swift index 001d763..7e0b3ed 100644 --- a/NearFuture/SettingsView.swift +++ b/NearFuture/SettingsView.swift @@ -9,11 +9,64 @@ import SwiftUI struct SettingsView: View { @State var viewModel: EventViewModel - @Binding var showSettings: Bool + @Environment(\.dismiss) var dismiss + + @State private var hasUbiquitous: Bool = false + @State private var lastSyncWasSuccessful: Bool = false + @State private var lastSyncWasNormalAgo: Bool = false + @State private var localCountEqualToiCloud: Bool = false + @State private var icloudCountEqualToLocal: Bool = false + + func updateStatus() { + let vm = viewModel + hasUbiquitous = vm.hasUbiquitousKeyValueStore() + lastSyncWasSuccessful = vm.syncStatus.contains("Success") + lastSyncWasNormalAgo = vm.lastSync?.timeIntervalSinceNow.isNormal ?? false + localCountEqualToiCloud = vm.localEventCount == vm.icloudEventCount + icloudCountEqualToLocal = vm.icloudEventCount == vm.localEventCount + } + + var iCloudStatusColor: Color { + let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal + let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal + + if allTrue { + return .green + } else if someTrue { + return .orange + } else { + return .red + } + } var body: some View { - NavigationStack { + NavigationView { List { + NavigationLink() { + iCloudSettingsView( + viewModel: viewModel, + hasUbiquitous: $hasUbiquitous, + lastSyncWasSuccessful: $lastSyncWasSuccessful, + lastSyncWasNormalAgo: $lastSyncWasNormalAgo, + localCountEqualToiCloud: $localCountEqualToiCloud, + icloudCountEqualToLocal: $icloudCountEqualToLocal, + updateStatus: updateStatus + ) + } label: { + HStack { + Image(systemName: "icloud.fill") + Text("iCloud") + Spacer() + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(iCloudStatusColor) + } + } + .onAppear { + viewModel.sync() + updateStatus() + } + Section("Danger Zone") { Button("Delete local data", role: .destructive) { viewModel.dangerClearLocalData() @@ -40,7 +93,7 @@ struct SettingsView: View { .toolbar { ToolbarItem(placement: .topBarTrailing) { Button() { - showSettings.toggle() + dismiss() } label: { Image(systemName: "xmark.circle.fill") .symbolRenderingMode(.hierarchical) @@ -51,9 +104,171 @@ struct SettingsView: View { } } +struct iCloudSettingsView: View { + @State var viewModel: EventViewModel + @State var showPushAlert: Bool = false + @State var showPullAlert: Bool = false + + @Binding var hasUbiquitous: Bool + @Binding var lastSyncWasSuccessful: Bool + @Binding var lastSyncWasNormalAgo: Bool + @Binding var localCountEqualToiCloud: Bool + @Binding var icloudCountEqualToLocal: Bool + + var updateStatus: () -> Void + + var body: some View { + List { + HStack { + Spacer() + VStack { + ZStack { + Image(systemName: "icloud") + .resizable() + .scaledToFit() + .frame(width: 75, height: 75) + .symbolRenderingMode(.multicolor) + Text("\(viewModel.icloudEventCount)") + .font(.title2) + } + HStack { + Button(role: .destructive) { + showPushAlert.toggle() + } label: { + Image(systemName: "arrow.up") + .resizable() + .scaledToFit() + .frame(width: 30, height: 40) + } + .buttonStyle(BorderedButtonStyle()) + .alert("Warning", isPresented: $showPushAlert) { + Button("OK", role: .destructive) { + viewModel.replaceiCloudWithLocalData() + viewModel.sync() + updateStatus() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This will replace Events stored in iCloud with Events stored locally.") + } + + Button() { + viewModel.sync() + updateStatus() + } label: { + Image(systemName: "arrow.triangle.2.circlepath") + .resizable() + .scaledToFit() + .frame(width: 30, height: 40) + .foregroundStyle(Color.accentColor) + } + .buttonStyle(BorderedButtonStyle()) + + Button(role: .destructive) { + showPullAlert.toggle() + } label: { + Image(systemName: "arrow.down") + .resizable() + .scaledToFit() + .frame(width: 30, height: 40) + } + .buttonStyle(BorderedButtonStyle()) + .alert("Warning", isPresented: $showPullAlert) { + Button("OK", role: .destructive) { + viewModel.replaceLocalWithiCloudData() + viewModel.sync() + updateStatus() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This will replace Events stored locally with Events stored in iCloud.") + } + } + ZStack { + Image(systemName: "iphone.gen3") + .resizable() + .scaledToFit() + .frame(width: 75, height: 75) + .symbolRenderingMode(.monochrome) + Text("\(viewModel.localEventCount)") + .font(.headline) + } + } + Spacer() + } + .onAppear { + viewModel.sync() + updateStatus() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(hasUbiquitous ? .green : .red) + Text("iCloud Key Value Store:") + Text("\(hasUbiquitous ? "" : "Not ")Working") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(lastSyncWasSuccessful ? .green : .red) + Text("Sync Status:") + Text("\(viewModel.syncStatus)") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(lastSyncWasNormalAgo ? .green : .red) + Text("Last Sync:") + Text("\(viewModel.lastSync?.formatted() ?? "Never")") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(localCountEqualToiCloud ? .green : .red) + Text("\(viewModel.localEventCount)") + .bold() + Text("Local Events") + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(icloudCountEqualToLocal ? .green : .red) + Text("\(viewModel.icloudEventCount)") + .bold() + Text("Events in iCloud") + } + } + .navigationTitle("iCloud") + .navigationBarTitleDisplayMode(.inline) + } +} + #Preview { SettingsView( - viewModel: EventViewModel(), - showSettings: .constant(true) + viewModel: EventViewModel() ) } + +#Preview("iCloudSettingsView") { + iCloudSettingsView( + viewModel: EventViewModel(), + hasUbiquitous: .constant(true), + lastSyncWasSuccessful: .constant(true), + lastSyncWasNormalAgo: .constant(true), + localCountEqualToiCloud: .constant(true), + icloudCountEqualToLocal: .constant(true), + updateStatus: test + ) +} + +func test() -> Void { + +} diff --git a/NearFutureTests/NearFutureTests.swift b/NearFutureTests/NearFutureTests.swift deleted file mode 100644 index d210086..0000000 --- a/NearFutureTests/NearFutureTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// NearFutureTests.swift -// NearFutureTests -// -// Created by Nihaal Sharma on 31/12/2024. -// - -import XCTest - -final class NearFutureTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/NearFutureUITests/NearFutureUITests.swift b/NearFutureUITests/NearFutureUITests.swift deleted file mode 100644 index 2c2e0b0..0000000 --- a/NearFutureUITests/NearFutureUITests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// NearFutureUITests.swift -// NearFutureUITests -// -// Created by Nihaal Sharma on 31/12/2024. -// - -import XCTest - -final class NearFutureUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/NearFutureUITests/NearFutureUITestsLaunchTests.swift b/NearFutureUITests/NearFutureUITestsLaunchTests.swift deleted file mode 100644 index 717973f..0000000 --- a/NearFutureUITests/NearFutureUITestsLaunchTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// NearFutureUITestsLaunchTests.swift -// NearFutureUITests -// -// Created by Nihaal Sharma on 31/12/2024. -// - -import XCTest - -final class NearFutureUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} diff --git a/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json b/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json b/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFutureWidgets/Assets.xcassets/Contents.json b/NearFutureWidgets/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/NearFutureWidgets/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json b/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..78d3a40 --- /dev/null +++ b/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.135", + "green" : "0.135", + "red" : "0.135" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NearFutureWidgets/Info.plist b/NearFutureWidgets/Info.plist new file mode 100644 index 0000000..0f118fb --- /dev/null +++ b/NearFutureWidgets/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/NearFutureWidgets/NearFutureWidgets.swift b/NearFutureWidgets/NearFutureWidgets.swift new file mode 100644 index 0000000..cefacc8 --- /dev/null +++ b/NearFutureWidgets/NearFutureWidgets.swift @@ -0,0 +1,208 @@ +// +// NearFutureWidgets.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import WidgetKit +import SwiftUI + +// Timeline Entry for Widget +struct EventWidgetEntry: TimelineEntry { + let date: Date + let events: [Event] +} + +// Timeline Provider to handle widget data +struct EventWidgetProvider: TimelineProvider { + func placeholder(in context: Context) -> EventWidgetEntry { + EventWidgetEntry(date: Date(), events: []) + } + + func getSnapshot(in context: Context, completion: @escaping (EventWidgetEntry) -> ()) { + let entry = EventWidgetEntry(date: Date(), events: getEvents()) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + let events = getEvents() + let currentDate = Date() + + // Timeline entry for the current date + let entry = EventWidgetEntry(date: currentDate, events: events) + + // Set timeline to refresh every 15 minutes + let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)! + let timeline = Timeline(entries: [entry], policy: .after(nextUpdateDate)) + + completion(timeline) + } + + private func getEvents() -> [Event] { + let viewModel = EventViewModel() + viewModel.loadEvents() + return viewModel.events + } +} + +// Event Widget View +struct EventWidgetView: View { + var entry: EventWidgetEntry + @Environment(\.widgetFamily) var widgetFamily + var showedEvents: Int { + switch widgetFamily { + case .systemSmall: + return 3 + case .systemMedium: + return 3 + case .systemLarge: + return 6 + default: + return 3 + } + } + + var body: some View { + let isLarge = widgetFamily == .systemLarge + let events = entry.events + VStack { + Text("Upcoming Events") + .font(.subheadline) + .padding(.top, -12) +// .padding(.bottom, -5) + + ForEach(events.prefix(showedEvents), id: \.id) { event in + HStack { + RoundedRectangle(cornerRadius: 5) + .frame(width: 5) + .frame(maxHeight: isLarge ? 50 : 30) + .foregroundStyle(event.color.color) + .padding(.leading, -18) + .padding(.vertical, 2) + VStack(alignment: .leading) { + HStack { + Image(systemName: event.symbol) + .resizable() + .scaledToFit() + .frame(width: 15, height: 15) + .foregroundStyle(event.color.color) + Text("\(event.name)") + .font(.footnote) + .padding(.leading, -5) + } + + if isLarge { + if !event.description.isEmpty { + Text(event.description) + .font(.caption2) + .foregroundColor(.gray) + .padding(.top, -5) + } + 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)") + .font(.caption2) + .padding(.top, -5) + } + } + .padding(.leading, -15) + + Spacer() + + Text(daysUntilEvent(event.date, short: !isLarge)) + .font(.caption) + .foregroundColor(event.color.color) + .padding(.trailing, -12) + } + } + Spacer() + if showedEvents < events.count { + let xMoreEvents = events.count - showedEvents + Text("+\(xMoreEvents) more event\(xMoreEvents == 1 ? "" : "s")") + .font(.caption2) + .foregroundStyle(.gray) + .padding(.top, -5) + .padding(.bottom, -15) + } + } + .containerBackground(Color.widgetBackground, for: .widget) + } +} + +struct Widget_Previews: PreviewProvider { + static var events = [ + Event( + name: "Event Name", + symbol: "gear", + color: ColorCodable(.blue), + description: "Event description", + date: Date.distantFuture, + recurrence: .yearly + ), + Event( + name: "A Day", + symbol: "star", + color: ColorCodable(.orange), + description: "description", + date: Date(), + recurrence: .daily + ), + Event( + name: "A Day", + symbol: "star", + color: ColorCodable(.orange), + description: "description", + date: Date(), + recurrence: .daily + ), + Event( + name: "A Day", + symbol: "star", + color: ColorCodable(.orange), + description: "description", + date: Date(), + recurrence: .daily + ), + Event( + name: "A Day", + symbol: "star", + color: ColorCodable(.orange), + description: "description", + date: Date(), + recurrence: .daily + ) + ] + static var previews: some View { + Group { + EventWidgetView( + entry: EventWidgetEntry( + date: Date(), + events: events + ) + ) + .previewContext(WidgetPreviewContext(family: .systemLarge)) + .previewDisplayName("Large") + EventWidgetView( + entry: EventWidgetEntry( + date: Date(), + events: events + ) + ) + .previewContext(WidgetPreviewContext(family: .systemMedium)) + .previewDisplayName("Medium") + EventWidgetView( + entry: EventWidgetEntry( + date: Date(), + events: events + ) + ) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .previewDisplayName("Small") + } + } +} diff --git a/NearFutureWidgets/NearFutureWidgetsBundle.swift b/NearFutureWidgets/NearFutureWidgetsBundle.swift new file mode 100644 index 0000000..654f90b --- /dev/null +++ b/NearFutureWidgets/NearFutureWidgetsBundle.swift @@ -0,0 +1,31 @@ +// +// NearFutureWidgetsBundle.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import WidgetKit +import SwiftUI + +//@main +//struct NearFutureWidgetsBundle: WidgetBundle { +// var body: some Widget { +// NearFutureWidgets() +// NearFutureWidgetsLiveActivity() +// } +//} + +@main +struct NearFutureWidget: Widget { + let kind: String = "NearFutureWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: EventWidgetProvider()) { entry in + EventWidgetView(entry: entry) + } + .configurationDisplayName("Upcoming Events Widget") + .description("Displays your upcoming events.") + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + } +} diff --git a/NearFutureWidgets/NearFutureWidgetsExtension.entitlements b/NearFutureWidgets/NearFutureWidgetsExtension.entitlements new file mode 100644 index 0000000..9fcd64e --- /dev/null +++ b/NearFutureWidgets/NearFutureWidgetsExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.neon443.NearFuture + + + diff --git a/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift b/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift new file mode 100644 index 0000000..21c5d09 --- /dev/null +++ b/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift @@ -0,0 +1,80 @@ +// +// NearFutureWidgetsLiveActivity.swift +// NearFutureWidgets +// +// Created by Nihaal Sharma on 02/01/2025. +// + +import ActivityKit +import WidgetKit +import SwiftUI + +struct NearFutureWidgetsAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + // Dynamic stateful properties about your activity go here! + var emoji: String + } + + // Fixed non-changing properties about your activity go here! + var name: String +} + +struct NearFutureWidgetsLiveActivity: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in + // Lock screen/banner UI goes here + VStack { + Text("Hello \(context.state.emoji)") + } + .activityBackgroundTint(Color.cyan) + .activitySystemActionForegroundColor(Color.black) + + } dynamicIsland: { context in + DynamicIsland { + // Expanded UI goes here. Compose the expanded UI through + // various regions, like leading/trailing/center/bottom + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom \(context.state.emoji)") + // more content + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T \(context.state.emoji)") + } minimal: { + Text(context.state.emoji) + } + .widgetURL(URL(string: "http://www.apple.com")) + .keylineTint(Color.red) + } + } +} + +extension NearFutureWidgetsAttributes { + fileprivate static var preview: NearFutureWidgetsAttributes { + NearFutureWidgetsAttributes(name: "World") + } +} + +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 +} diff --git a/README.md b/README.md index 729cbb6..8133a37 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Near Future is a SwiftUI App to help people to track upcoming events - Holidays, - [x] Descriptions - [x] Icons - [x] Event colors -- [ ] Recurrence +- [x] Recurrence - [x] Search - [ ] Notifications - [ ] Apple Watch App -- [ ] Home Screen Widgets +- [x] Home Screen Widgets - [ ] Lock Screen Widgets ## Features