diff --git a/Config.xcconfig b/Config.xcconfig new file mode 100644 index 0000000..a330a83 --- /dev/null +++ b/Config.xcconfig @@ -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 diff --git a/Images/NearFutureIcon.png b/Images/NearFutureIcon.png new file mode 100644 index 0000000..2684ae9 Binary files /dev/null and b/Images/NearFutureIcon.png differ diff --git a/Images/NearFutureIcon.pxd b/Images/NearFutureIcon.pxd new file mode 100644 index 0000000..8318185 Binary files /dev/null and b/Images/NearFutureIcon.pxd differ diff --git a/Images/NearFutureIconDark.png b/Images/NearFutureIconDark.png new file mode 100644 index 0000000..78e92c3 Binary files /dev/null and b/Images/NearFutureIconDark.png differ diff --git a/Images/NearFutureIconDark.pxd b/Images/NearFutureIconDark.pxd new file mode 100644 index 0000000..0337169 Binary files /dev/null and b/Images/NearFutureIconDark.pxd differ diff --git a/Images/NearFutureIconTint.png b/Images/NearFutureIconTint.png new file mode 100644 index 0000000..935ff7b Binary files /dev/null and b/Images/NearFutureIconTint.png differ diff --git a/Images/NearFutureIconTint.pxd b/Images/NearFutureIconTint.pxd new file mode 100644 index 0000000..d769db8 Binary files /dev/null and b/Images/NearFutureIconTint.pxd differ diff --git a/Images/appstore.png b/Images/appstore.png new file mode 100644 index 0000000..2beabe0 Binary files /dev/null and b/Images/appstore.png differ diff --git a/NearFuture.xcodeproj/project.pbxproj b/NearFuture.xcodeproj/project.pbxproj index 8933b5c..ad08da6 100644 --- a/NearFuture.xcodeproj/project.pbxproj +++ b/NearFuture.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; }; A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; }; A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; }; + A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; }; + A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; }; A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; }; A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; }; A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; }; @@ -61,6 +63,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 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 = ""; }; A920C28B2D24011400E4F9B1 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; @@ -71,6 +74,8 @@ 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 = ""; }; A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; + A977CC912DBBB48000DED8C0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = ""; }; + A977CC992DBD74FE00DED8C0 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.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 = ""; }; @@ -128,6 +133,7 @@ A920C27B2D24011300E4F9B1 = { isa = PBXGroup; children = ( + A90FDE222DC0D4310012790C /* Config.xcconfig */, A920C2862D24011400E4F9B1 /* NearFuture */, A979F6082D270AF00094C0B3 /* NearFutureWidgets */, A980FC382D93FB2B006A778F /* NearFutureTests */, @@ -149,16 +155,18 @@ A920C2862D24011400E4F9B1 /* NearFuture */ = { isa = PBXGroup; children = ( - A979F57E2D26B1300094C0B3 /* EditEventView.swift */, - A920C2B72D2401A300E4F9B1 /* AddEventView.swift */, + A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, + A920C28B2D24011400E4F9B1 /* Item.swift */, A920C2C02D2403CA00E4F9B1 /* ContentView.swift */, A985104F2DB263F00013D5FF /* EventListView.swift */, + A920C2B72D2401A300E4F9B1 /* AddEventView.swift */, + A979F57E2D26B1300094C0B3 /* EditEventView.swift */, + A977CC912DBBB48000DED8C0 /* ArchiveView.swift */, A93BC0932D2B18A3002E8BBD /* StatsView.swift */, + A977CC992DBD74FE00DED8C0 /* HelpView.swift */, A920C2B42D2401A100E4F9B1 /* SettingsView.swift */, A985104D2DB256430013D5FF /* iCloudSettingsView.swift */, A980FC302D920097006A778F /* Info.plist */, - A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, - A920C28B2D24011400E4F9B1 /* Item.swift */, A920C28D2D24011A00E4F9B1 /* Assets.xcassets */, A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */, A920C2902D24011A00E4F9B1 /* Preview Content */, @@ -283,7 +291,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1620; - LastUpgradeCheck = 1620; + LastUpgradeCheck = 1630; TargetAttributes = { A920C2832D24011300E4F9B1 = { CreatedOnToolsVersion = 15.4; @@ -356,9 +364,11 @@ A98510502DB263F00013D5FF /* EventListView.swift in Sources */, A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */, A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */, + A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */, A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */, + A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */, A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */, A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */, ); @@ -435,6 +445,7 @@ COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 8JGND254B7; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -462,6 +473,7 @@ ); SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -501,6 +513,7 @@ COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 8JGND254B7; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -516,25 +529,27 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 6.0; }; name = Release; }; A920C2AC2D24011B00E4F9B1 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; buildSettings = { 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 = 25; + CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; - DEVELOPMENT_TEAM = 85Q9QG6DN7; + DEVELOPMENT_TEAM = "$(TEAM_ID)"; ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = NearFuture/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; + INFOPLIST_KEY_CFBundleDisplayName = "$(NAME)"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -546,16 +561,16 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13; - MARKETING_VERSION = 2.0; + MARKETING_VERSION = "$(VERSION)"; OTHER_LDFLAGS = ( "-Xlinker", "-interposable", ); - PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = NO; SDKROOT = auto; @@ -571,20 +586,21 @@ }; A920C2AD2D24011B00E4F9B1 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; buildSettings = { 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 = 25; + CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; - DEVELOPMENT_TEAM = 85Q9QG6DN7; + DEVELOPMENT_TEAM = "$(TEAM_ID)"; ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = NearFuture/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; + INFOPLIST_KEY_CFBundleDisplayName = "$(NAME)"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -596,13 +612,13 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13; - MARKETING_VERSION = 2.0; + MARKETING_VERSION = "$(VERSION)"; OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = NO; SDKROOT = auto; @@ -618,13 +634,13 @@ }; A979F6162D270AF90094C0B3 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; buildSettings = { 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 = 25; - DEVELOPMENT_TEAM = 85Q9QG6DN7; + CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = NearFutureWidgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; @@ -635,12 +651,12 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.0; + MARKETING_VERSION = 3.0; OTHER_LDFLAGS = ( "-Xlinker", "-interposable", ); - PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture.NearFutureWidgets; + PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -654,13 +670,13 @@ }; A979F6172D270AF90094C0B3 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; buildSettings = { 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 = 25; - DEVELOPMENT_TEAM = 85Q9QG6DN7; + CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = NearFutureWidgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; @@ -671,8 +687,8 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.0; - PRODUCT_BUNDLE_IDENTIFIER = com.vape.NearFuture.NearFutureWidgets; + MARKETING_VERSION = 3.0; + PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture.widgets; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -687,11 +703,11 @@ }; A980FC3E2D93FB2B006A778F /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = P6PV2R9443; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16; MACOSX_DEPLOYMENT_TARGET = 13; @@ -713,11 +729,11 @@ }; A980FC3F2D93FB2B006A778F /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = P6PV2R9443; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16; MACOSX_DEPLOYMENT_TARGET = 13; diff --git a/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture nodebug.xcscheme b/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture nodebug.xcscheme new file mode 100644 index 0000000..c8ab148 --- /dev/null +++ b/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture nodebug.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme b/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme index 1dfa8b7..e4d5232 100644 --- a/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme +++ b/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme @@ -1,6 +1,6 @@ SchemeUserState + NearFuture nodebug.xcscheme_^#shared#^_ + + orderHint + 2 + NearFuture.xcscheme_^#shared#^_ orderHint diff --git a/NearFuture/AddEventView.swift b/NearFuture/AddEventView.swift index 54a2e72..c28fdea 100644 --- a/NearFuture/AddEventView.swift +++ b/NearFuture/AddEventView.swift @@ -21,12 +21,13 @@ struct AddEventView: View { @Binding var eventTime: Bool @Binding var eventRecurrence: Event.RecurrenceType - @State var adding : Bool + @State var adding: Bool + @State var showNeedsNameAlert: Bool = false @State var isSymbolPickerPresented = false @FocusState private var focusedField: Field? private enum Field { - case Name, Description + case Name, Notes } @Environment(\.dismiss) var dismiss @@ -60,9 +61,8 @@ struct AddEventView: View { searchLabel: "Search...", autoDismiss: true) } - ColorPicker("", selection: $eventColor, supportsOpacity: true) + ColorPicker("", selection: $eventColor, supportsOpacity: false) .fixedSize() - Divider() ZStack { TextField("Event Name", text: $eventName) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -71,7 +71,7 @@ struct AddEventView: View { .focused($focusedField, equals: Field.Name) .submitLabel(.next) .onSubmit { - focusedField = .Description + focusedField = .Notes } MagicClearButton(text: $eventName) } @@ -79,11 +79,11 @@ struct AddEventView: View { // dscription ZStack { - TextField("Event Description", text: $eventNotes) + TextField("Event Notes", text: $eventNotes) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.trailing, eventNotes.isEmpty ? 0 : 30) .animation(.spring, value: eventNotes) - .focused($focusedField, equals: Field.Description) + .focused($focusedField, equals: Field.Notes) .submitLabel(.done) .onSubmit { 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" : "")") .navigationBarTitleDisplayMode(.inline) .toolbar { - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem(placement: .topBarLeading) { if adding { Button() { + resetAddEventView() dismiss() } label: { Image(systemName: "xmark.circle.fill") .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() { //reset addeventView - eventName = "" - eventSymbol = "star" + eventName = viewModel.template.name + eventComplete = viewModel.template.complete + eventCompleteDesc = viewModel.template.completeDesc + eventSymbol = viewModel.template.symbol eventColor = randomColor() - eventNotes = "" - eventDate = Date() - eventRecurrence = .none + eventNotes = viewModel.template.notes + eventDate = viewModel.template.date + eventTime = viewModel.template.time + eventRecurrence = viewModel.template.recurrence dismiss() } + } struct MagicClearButton: View { @Binding var text: String @@ -213,17 +235,24 @@ struct MagicClearButton: View { } #Preview { - AddEventView( - viewModel: EventViewModel(), - eventName: .constant("Birthday"), - eventComplete: .constant(false), - eventCompleteDesc: .constant(""), - eventSymbol: .constant("star"), - eventColor: .constant(Color.red), - eventNotes: .constant("A very special day"), - eventDate: .constant(Date()), - eventTime: .constant(true), - eventRecurrence: .constant(.monthly), - adding: true - ) + let vm = dummyEventViewModel() + Color.orange + .ignoresSafeArea(.all) + .sheet(isPresented: .constant(true)) { + AddEventView( + viewModel: vm, + eventName: .constant(vm.template.notes), + eventComplete: .constant(vm.template.complete), + eventCompleteDesc: .constant(vm.template.completeDesc), + eventSymbol: .constant(vm.template.symbol), + eventColor: .constant(vm.template.color.color), + eventNotes: .constant(vm.template.notes), + eventDate: .constant(vm.template.date), + eventTime: .constant(vm.template.time), + eventRecurrence: .constant(vm.template.recurrence), + adding: true + ) + .presentationDragIndicator(.visible) + .presentationBackground(.ultraThinMaterial) + } } diff --git a/NearFuture/ArchiveView.swift b/NearFuture/ArchiveView.swift new file mode 100644 index 0000000..923fcdd --- /dev/null +++ b/NearFuture/ArchiveView.swift @@ -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()) +} + + diff --git a/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json b/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json index 762fbd2..aff6f36 100644 --- a/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/NearFuture/Assets.xcassets/AccentColor.colorset/Contents.json @@ -6,7 +6,7 @@ "components" : { "alpha" : "1.000", "blue" : "0.000", - "green" : "0.375", + "green" : "0.376", "red" : "1.000" } }, diff --git a/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json b/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json index ec5da97..1e9333b 100644 --- a/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/NearFuture/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,7 +1,31 @@ { "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", "platform" : "ios", "size" : "1024x1024" diff --git a/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png b/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png new file mode 100644 index 0000000..2684ae9 Binary files /dev/null and b/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png differ diff --git a/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIconDark.png b/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIconDark.png new file mode 100644 index 0000000..78e92c3 Binary files /dev/null and b/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIconDark.png differ diff --git a/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIconTint.png b/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIconTint.png new file mode 100644 index 0000000..935ff7b Binary files /dev/null and b/NearFuture/Assets.xcassets/AppIcon.appiconset/NearFutureIconTint.png differ 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 deleted file mode 100644 index e179716..0000000 Binary files a/NearFuture/Assets.xcassets/AppIcon.appiconset/telescope-pointed-at-the-milky-way-galaxy-royalty-free-image-93204189-1533148569-2899733190.jpg and /dev/null differ diff --git a/NearFuture/Assets.xcassets/bgTop.colorset/Contents.json b/NearFuture/Assets.xcassets/bgTop.colorset/Contents.json new file mode 100644 index 0000000..871974d --- /dev/null +++ b/NearFuture/Assets.xcassets/bgTop.colorset/Contents.json @@ -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 + } +} diff --git a/NearFuture/Assets.xcassets/one.colorset/Contents.json b/NearFuture/Assets.xcassets/one.colorset/Contents.json new file mode 100644 index 0000000..d890719 --- /dev/null +++ b/NearFuture/Assets.xcassets/one.colorset/Contents.json @@ -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 + } +} diff --git a/NearFuture/Assets.xcassets/two.colorset/Contents.json b/NearFuture/Assets.xcassets/two.colorset/Contents.json new file mode 100644 index 0000000..0425637 --- /dev/null +++ b/NearFuture/Assets.xcassets/two.colorset/Contents.json @@ -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 + } +} diff --git a/NearFuture/ContentView.swift b/NearFuture/ContentView.swift index 613c2a9..d918166 100644 --- a/NearFuture/ContentView.swift +++ b/NearFuture/ContentView.swift @@ -11,9 +11,16 @@ import SwiftData enum Field { case Search } +enum Tab { + case Home + case Archive + case Statistics + case Settings + case Gradient +} struct ContentView: View { - @StateObject private var viewModel = EventViewModel() + @StateObject var viewModel: EventViewModel @State private var eventName = "" @State private var eventComplete = false @State private var eventCompleteDesc = "" @@ -23,6 +30,7 @@ struct ContentView: View { @State private var eventDate = Date() @State private var eventTime = false @State private var eventRecurrence: Event.RecurrenceType = .none + @State var hey: UUID = UUID() @State private var showingAddEventView = false @State private var searchInput: String = "" var filteredEvents: [Event] { @@ -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 { if viewModel.events.count == 0 { return true @@ -70,40 +53,43 @@ struct ContentView: View { } @FocusState private var focusedField: Field? + @FocusState private var focusedTab: Tab? var body: some View { TabView { NavigationStack { ZStack { backgroundGradient - .ignoresSafeArea(.all) VStack { ZStack { - TextField( - "\(Image(systemName: "magnifyingglass")) Search", - text: $searchInput - ) - .padding(.trailing, searchInput.isEmpty ? 0 : 30) - .animation(.spring, value: searchInput) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .focused($focusedField, equals: Field.Search) - .onSubmit { - focusedField = nil - } - .submitLabel(.done) + SearchBar(searchInput: $searchInput) + .focused($focusedField, equals: Field.Search) + .onSubmit { + focusedField = nil + } MagicClearButton(text: $searchInput) + .onTapGesture { + focusedField = nil + } } .padding(.horizontal) - List { - ForEach(filteredEvents) { event in - EventListView(viewModel: viewModel, event: event) - } - .onDelete(perform: viewModel.removeEvent) - if !searchInput.isEmpty { - SearchHelp( - searchInput: $searchInput, - focusedField: _focusedField - ) + if filteredEvents.isEmpty && !searchInput.isEmpty { + HelpView(searchInput: $searchInput, focusedField: focusedField) + } else { + ScrollView { + ForEach(filteredEvents) { event in + EventListView(viewModel: viewModel, event: event) + } + .onDelete(perform: viewModel.removeEvent) + .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, adding: true //adding event ) + .presentationDragIndicator(.visible) + .presentationBackground(.ultraThinMaterial) } .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button() { - showingAddEventView.toggle() - } label: { - Image(systemName: "plus.circle") - .resizable() - .scaledToFit() - } + AddEventButton(showingAddEventView: $showingAddEventView) } } } @@ -140,44 +122,76 @@ struct ContentView: View { .tabItem { 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) .tabItem { Label("Statistics", systemImage: "chart.pie") } + .focused($focusedTab, equals: Tab.Statistics) SettingsView(viewModel: viewModel) .tabItem { Label("Settings", systemImage: "gear") } - } - } -} - -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) + .focused($focusedTab, equals: Tab.Settings) } } } #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) + } } diff --git a/NearFuture/EditEventView.swift b/NearFuture/EditEventView.swift index 81d4a32..3263c90 100644 --- a/NearFuture/EditEventView.swift +++ b/NearFuture/EditEventView.swift @@ -12,38 +12,7 @@ struct EditEventView: View { @ObservedObject var viewModel: EventViewModel @Binding var event: Event - @State private var eventName: String - @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) { - 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 - + fileprivate func saveEdits() { //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 @@ -59,15 +28,15 @@ struct EditEventView: View { var body: some View { AddEventView( viewModel: viewModel, - eventName: $eventName, - eventComplete: $eventComplete, - eventCompleteDesc: $eventCompleteDesc, - eventSymbol: $eventSymbol, - eventColor: $eventColor, - eventNotes: $eventNotes, - eventDate: $eventDate, - eventTime: $eventTime, - eventRecurrence: $eventRecurrence, + eventName: $event.name, + eventComplete: $event.complete, + eventCompleteDesc: $event.completeDesc, + eventSymbol: $event.symbol, + eventColor: $event.color.colorBind, + eventNotes: $event.notes, + eventDate: $event.date, + eventTime: $event.time, + eventRecurrence: $event.recurrence, adding: false //bc we editing existing event ) .navigationTitle("Edit Event") @@ -77,18 +46,18 @@ struct EditEventView: View { saveEdits() } label: { Text("Done") + .bold() } - .disabled(eventName == "") + .disabled(event.name == "") } } } } #Preview { + let vm = dummyEventViewModel() EditEventView( - viewModel: EventViewModel(), - event: .constant( - EventViewModel().example - ) + viewModel: vm, + event: .constant(vm.example) ) } diff --git a/NearFuture/EventListView.swift b/NearFuture/EventListView.swift index 8ed9e8e..74b830e 100644 --- a/NearFuture/EventListView.swift +++ b/NearFuture/EventListView.swift @@ -19,118 +19,149 @@ struct EventListView: View { event: $event ) } label: { - HStack { - RoundedRectangle(cornerRadius: 5) - .frame(width: 5) - .foregroundStyle( - 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) + ZStack { + HStack { + RoundedRectangle(cornerRadius: 5) + .frame(width: 7) .foregroundStyle( event.color.color.opacity( event.complete ? 0.5 : 1 ) ) - .animation(.spring, value: event.complete) - } - Button() { - withAnimation(.spring) { - event.complete.toggle() + VStack(alignment: .leading) { + HStack { + Image(systemName: event.symbol) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .shadow(radius: 5) + .foregroundStyle( + .one.opacity( + event.complete ? 0.5 : 1 + ) + ) + Text("\(event.name)") + .font(.headline) + .foregroundStyle(.one) + .strikethrough(event.complete) + } + 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 currEvent.id == event.id } if let eventToModify = eventToModify { - viewModel.events[eventToModify] = event + viewModel.events.remove(at: eventToModify) viewModel.saveEvents() - viewModel.loadEvents() } } label: { - if event.complete { - ZStack { - Circle() - .foregroundStyle(.green) - Image(systemName: "checkmark") - .resizable() - .foregroundStyle(.white) - .scaledToFit() - .frame(width: 15) - } - } else { - Image(systemName: "circle") - .resizable() - .scaledToFit() - .foregroundStyle(event.color.color) - } + Label("Delete", systemImage: "trash") } - .buttonStyle(.borderless) - .frame(maxWidth: 25, maxHeight: 25) - .animation(.spring, value: event.complete) } } } } #Preview("EventListView") { - EventListView( - viewModel: EventViewModel(), - event: - EventViewModel().example - ) + let vm = dummyEventViewModel() + ZStack { + Color.black + VStack { + ForEach(0..<50) { _ in + Rectangle() + .foregroundStyle(randomColor().opacity(0.5)) + .padding(-10) + } + .ignoresSafeArea(.all) + .blur(radius: 5) + } + VStack { + ForEach(vm.events) { event in + EventListView( + viewModel: vm, + event: event + ) + } + } + .padding(.horizontal, 10) + } } diff --git a/NearFuture/HelpView.swift b/NearFuture/HelpView.swift new file mode 100644 index 0000000..3ad621d --- /dev/null +++ b/NearFuture/HelpView.swift @@ -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, focusedField: Field?) { + _searchInput = searchInput + self.helpType = .Search + _showAddEvent = .constant(false) + } + + /// initialises an Archive HelpView + /// + init(showAddEvent: Binding) { + _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)) +} diff --git a/NearFuture/Item.swift b/NearFuture/Item.swift index f0dcac9..d9d3e6b 100644 --- a/NearFuture/Item.swift +++ b/NearFuture/Item.swift @@ -40,57 +40,38 @@ struct ColorCodable: Codable { var red: Double var green: 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 { - Color(red: red, green: green, blue: blue, opacity: alpha) + Color(red: red, green: green, blue: blue) } var colorBind: Color { get { return Color( red: red, green: green, - blue: blue, - opacity: alpha + blue: blue ) } set { let cc = ColorCodable(newValue) - red = cc.red - green = cc.green - blue = cc.blue - alpha = cc.alpha + self.red = cc.red + self.green = cc.green + self.blue = cc.blue } } init(_ color: 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) 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) { + init(red: Double, green: Double, blue: Double) { self.red = red self.green = green self.blue = blue - self.alpha = alpha } } @@ -120,7 +101,18 @@ class EventViewModel: ObservableObject { @Published var events: [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: "", complete: false, completeDesc: "", @@ -148,12 +140,14 @@ class EventViewModel: ObservableObject { @Published var localEventCount: Int = 0 @Published var syncStatus: String = "Not Synced" - init() { - loadEvents() + init(load: Bool = true) { + if load { + loadEvents() + } } //appgroup or regular userdefaults - let appGroupUserDefaults = UserDefaults(suiteName: "group.com.neon443.NearFuture") ?? UserDefaults.standard + let appGroupUserDefaults = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard //icloud store let icloudStore = NSUbiquitousKeyValueStore.default @@ -191,6 +185,7 @@ class EventViewModel: ObservableObject { updateSyncStatus() loadEvents() 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 { let dateString = date.formatted(date: .long, time: .omitted) let recurrenceDescription: String @@ -385,6 +388,5 @@ func randomColor() -> Color { let r = Double.random(in: 0...1) let g = 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, opacity: a) + return Color(red: r, green: g, blue: b) } diff --git a/NearFuture/NearFuture.entitlements b/NearFuture/NearFuture.entitlements index 0c67376..82eaccb 100644 --- a/NearFuture/NearFuture.entitlements +++ b/NearFuture/NearFuture.entitlements @@ -1,5 +1,14 @@ - + + com.apple.developer.icloud-container-identifiers + + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)$(CFBundleIdentifier) + com.apple.security.application-groups + + group.NearFuture + + diff --git a/NearFuture/NearFutureApp.swift b/NearFuture/NearFutureApp.swift index a346f8d..35e6105 100644 --- a/NearFuture/NearFutureApp.swift +++ b/NearFuture/NearFutureApp.swift @@ -25,7 +25,7 @@ struct NearFutureApp: App { var body: some Scene { WindowGroup { - ContentView() + ContentView(viewModel: EventViewModel()) } // .modelContainer(sharedModelContainer) } diff --git a/NearFuture/SettingsView.swift b/NearFuture/SettingsView.swift index 99b7054..884c608 100644 --- a/NearFuture/SettingsView.swift +++ b/NearFuture/SettingsView.swift @@ -41,109 +41,111 @@ struct SettingsView: View { var body: some View { NavigationStack { - 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() - } - - NavigationLink() { - NavigationStack() { - Button() { - UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")" - print(viewModel.exportEvents() as Any) - } label: { - Text("copy") + ZStack { + backgroundGradient + 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) } - Text("\(viewModel.exportEvents() ?? "")") } - } label: { - 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) + .onAppear { + viewModel.sync() + updateStatus() + } + + NavigationLink() { + NavigationStack() { Button() { - viewModel.importEvents(importStr) + UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")" + print(viewModel.exportEvents() as Any) } label: { - Text("import events") + Text("copy") } - .buttonStyle(BorderedProminentButtonStyle()) - Button() { - if let pb = UIPasteboard.general.string { - print(pb) + Text("\(viewModel.exportEvents() ?? "")") + } + } label: { + 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") - Text("Import events") - } - - Section("Tip") { - Text("Near Future has Widgets!") - } - - Section("Danger Zone") { - Button("Delete local data", role: .destructive) { - viewModel.dangerClearLocalData() + + Section("Tip") { + Text("Near Future has Widgets!") } - 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) { - viewModel.dangerClearLocalData() - viewModel.dangerCleariCloudData() - } - } - Section("Debug") { - Button("Reset UserDefaults", role: .destructive) { - viewModel.dangerResetLocalData() - } - Button("Reset iCloud", role: .destructive) { - viewModel.dangerResetiCloud() + Section("Debug") { + 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 { - SettingsView( - viewModel: EventViewModel() - ) + SettingsView(viewModel: dummyEventViewModel()) } func test() -> Void { diff --git a/NearFuture/StatsView.swift b/NearFuture/StatsView.swift index bd61ecd..b95627c 100644 --- a/NearFuture/StatsView.swift +++ b/NearFuture/StatsView.swift @@ -13,55 +13,59 @@ struct StatsView: View { var body: some View { NavigationStack { - List { - Section(header: Text("Upcoming Events")) { - let upcomingEvents = viewModel.events.filter { $0.date > Date() } - Text("\(upcomingEvents.count) upcoming event\(upcomingEvents.count == 1 ? "" : "s")") - .font(.headline) - .foregroundStyle(Color.accentColor) - 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))") + ZStack { + backgroundGradient + List { + Section(header: Text("Upcoming Events")) { + let upcomingEvents = viewModel.events.filter { $0.date > Date() } + Text("\(upcomingEvents.count) upcoming event\(upcomingEvents.count == 1 ? "" : "s")") + .font(.headline) + .foregroundStyle(Color.accentColor) + let pastEvents = viewModel.events.filter { $0.date < Date() } + Text("\(pastEvents.count) past event\(pastEvents.count == 1 ? "" : "s")") + .foregroundStyle(.gray) } - } - - 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")" - } + 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 + 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 { StatsView( - viewModel: EventViewModel() + viewModel: dummyEventViewModel() ) } diff --git a/NearFuture/iCloudSettingsView.swift b/NearFuture/iCloudSettingsView.swift index 44f8f4c..03a0b72 100644 --- a/NearFuture/iCloudSettingsView.swift +++ b/NearFuture/iCloudSettingsView.swift @@ -34,154 +34,158 @@ struct iCloudSettingsView: View { var updateStatus: () -> Void var body: some View { - List { - HStack { - Spacer() - VStack { - ZStack { - Image(systemName: "icloud") - .resizable() - .scaledToFit() - .frame(width: 75, height: 55) - .symbolRenderingMode(.multicolor) - Text("\(viewModel.icloudEventCount)") - .font(.title2) - .monospaced() - .bold() - } - Text("iCloud") - HStack { - Button(role: .destructive) { - showPushAlert.toggle() - } label: { - Image(systemName: "arrow.up") + ZStack { + backgroundGradient + List { + HStack { + Spacer() + VStack { + ZStack { + Image(systemName: "icloud") .resizable() .scaledToFit() - .frame(width: 30, height: 40) + .frame(width: 75, height: 55) + .symbolRenderingMode(.multicolor) + Text("\(viewModel.icloudEventCount)") + .font(.title2) + .monospaced() + .bold() } - .buttonStyle(BorderedButtonStyle()) - .alert("Warning", isPresented: $showPushAlert) { - Button("OK", role: .destructive) { - viewModel.replaceiCloudWithLocalData() + Text("iCloud") + HStack { + Button(role: .destructive) { + showPushAlert.toggle() + } label: { + Image(systemName: "arrow.up") + .resizable() + .scaledToFit() + .frame(width: 30, height: 40) + } + .buttonStyle(BorderedButtonStyle()) + .alert("Warning", isPresented: $showPushAlert) { + Button("OK", role: .destructive) { + viewModel.replaceiCloudWithLocalData() + viewModel.sync() + updateStatus() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This will replace Events stored in iCloud with Events stored locally.") + } + + Button() { viewModel.sync() updateStatus() + } label: { + Image(systemName: "arrow.triangle.2.circlepath") + .resizable() + .scaledToFit() + .frame(width: 30, height: 40) + .foregroundStyle(Color.accentColor) + } + .buttonStyle(BorderedButtonStyle()) + + Button(role: .destructive) { + showPullAlert.toggle() + } label: { + Image(systemName: "arrow.down") + .resizable() + .scaledToFit() + .frame(width: 30, height: 40) + } + .buttonStyle(BorderedButtonStyle()) + .alert("Warning", isPresented: $showPullAlert) { + Button("OK", role: .destructive) { + viewModel.replaceLocalWithiCloudData() + viewModel.sync() + updateStatus() + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This will replace Events stored locally with Events stored in iCloud.") } - 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") + ZStack { + Image(systemName: device.sf) .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.") + .frame(width: 75, height: 75) + .symbolRenderingMode(.monochrome) + Text("\(viewModel.localEventCount)") + .font(.title2) + .monospaced() + .bold() } + Text(device.label) } - ZStack { - Image(systemName: device.sf) - .resizable() - .scaledToFit() - .frame(width: 75, height: 75) - .symbolRenderingMode(.monochrome) - Text("\(viewModel.localEventCount)") - .font(.title2) - .monospaced() - .bold() - } - Text(device.label) + Spacer() + } + .listRowSeparator(.hidden) + .onAppear { + viewModel.sync() + updateStatus() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(hasUbiquitous ? .green : .red) + Text("iCloud") + Spacer() + Text("\(hasUbiquitous ? "" : "Not ")Working") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(lastSyncWasSuccessful ? .green : .red) + Text("Sync Status") + Spacer() + Text("\(viewModel.syncStatus)") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(lastSyncWasNormalAgo ? .green : .red) + Text("Last Sync") + Spacer() + Text("\(viewModel.lastSync?.formatted() ?? "Never")") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(localCountEqualToiCloud ? .green : .red) + Text("Local Events") + Spacer() + Text("\(viewModel.localEventCount)") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(icloudCountEqualToLocal ? .green : .red) + Text("Events in iCloud") + Spacer() + Text("\(viewModel.icloudEventCount)") + .bold() } - 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") { iCloudSettingsView( - viewModel: EventViewModel(), + viewModel: dummyEventViewModel(), hasUbiquitous: .constant(true), lastSyncWasSuccessful: .constant(true), lastSyncWasNormalAgo: .constant(true), diff --git a/NearFutureWidgets/NearFutureWidgets.swift b/NearFutureWidgets/NearFutureWidgets.swift index 63959d4..5008ba7 100644 --- a/NearFutureWidgets/NearFutureWidgets.swift +++ b/NearFutureWidgets/NearFutureWidgets.swift @@ -76,7 +76,7 @@ struct EventWidgetView: View { var body: some View { let isLarge = widgetFamily == .systemLarge - let events = entry.events + let events = entry.events.filter(){!$0.complete} ZStack { bgGradient .padding(.top, 4) @@ -90,51 +90,49 @@ struct EventWidgetView: View { .padding(.top, -12) ForEach(events.prefix(showedEventsNum), id: \.id) { event in - if !event.complete { - 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)") - .foregroundStyle(.white) - .font(.footnote) - .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) - } + 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.isEmpty ? " " : event.name)") + .foregroundStyle(.white) + .font(.footnote) + .padding(.leading, -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) + 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() + + //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() @@ -152,38 +150,31 @@ struct EventWidgetView: View { } struct Widget_Previews: PreviewProvider { - static var events = [ - EventViewModel().example, - EventViewModel().example, - EventViewModel().example, - EventViewModel().example - ] + static var events = dummyEventViewModel().events static var previews: some View { - Group { - EventWidgetView( - entry: EventWidgetEntry( - date: Date(), - events: events - ) + EventWidgetView( + entry: EventWidgetEntry( + date: Date(), + events: events ) - .previewContext(WidgetPreviewContext(family: .systemLarge)) - .previewDisplayName("Large") - 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: .systemMedium)) + .previewDisplayName("Medium") + EventWidgetView( + entry: EventWidgetEntry( + date: Date(), + events: events ) - .previewContext(WidgetPreviewContext(family: .systemSmall)) - .previewDisplayName("Small") - } + ) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .previewDisplayName("Small") } } diff --git a/NearFutureWidgets/NearFutureWidgetsExtension.entitlements b/NearFutureWidgets/NearFutureWidgetsExtension.entitlements index ee95ab7..7bf1b8e 100644 --- a/NearFutureWidgets/NearFutureWidgetsExtension.entitlements +++ b/NearFutureWidgets/NearFutureWidgetsExtension.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.NearFuture + com.apple.security.network.client diff --git a/README.md b/README.md index 6cb9912..c37ce03 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # 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 - [x] Add and delete events @@ -19,6 +20,7 @@ Near Future is a SwiftUI App to help people to track upcoming events - Holidays, - [ ] Archive - [ ] Collaboration - [ ] Autocomplete tasks +- [ ] Settings ## Features - **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 ## Contributing -Contributions are welcome! just follow these: +Contributions are welcome! Just follow these steps: 1. Follow [#Compiling] to get a copy on your machine 2. Optionally create a feature branch for your additions 3. Open a pull request ## Used Tools/Frameworks - Swift & SwiftUI by Apple -- **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI]. +- **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI]. \ No newline at end of file