From ffbd17fad8006e1f4c77e9008b5ec936b937a79c Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Sun, 4 May 2025 12:31:01 +0100 Subject: [PATCH] rerewrote daysUntillEvent, now acc can count days inlineLarge titlebar, backwards compatible view extension competed events go to archive fix long event name and notes alignemnts can refresh icloud settings to sync --- Config.xcconfig | 2 +- NearFuture/AddEventView.swift | 2 + NearFuture/ArchiveView.swift | 8 + NearFuture/ContentView.swift | 19 +- NearFuture/EventListView.swift | 2 + NearFuture/Item.swift | 34 ++-- NearFuture/SettingsView.swift | 8 +- NearFuture/StatsView.swift | 8 +- NearFuture/iCloudSettingsView.swift | 258 +++++++++++++++------------- 9 files changed, 191 insertions(+), 150 deletions(-) diff --git a/Config.xcconfig b/Config.xcconfig index b5617b1..2135b30 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -12,6 +12,6 @@ TEAM_ID = 8JGND254B7 BUNDLE_ID = com.neon443.NearFuture BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets GROUP_ID = group.NearFuture -VERSION = 3.1.1 +VERSION = 3.2.1 NAME = Near Future BUILD_NUMBER = 5 diff --git a/NearFuture/AddEventView.swift b/NearFuture/AddEventView.swift index bb7dae8..d2aa17d 100644 --- a/NearFuture/AddEventView.swift +++ b/NearFuture/AddEventView.swift @@ -94,8 +94,10 @@ struct AddEventView: View { // date picker HStack { + Spacer() DatePicker("", selection: $eventDate, displayedComponents: .date) .datePickerStyle(WheelDatePickerStyle()) + Spacer() Button() { eventDate = Date() } label: { diff --git a/NearFuture/ArchiveView.swift b/NearFuture/ArchiveView.swift index 6a30b3f..ca062f9 100644 --- a/NearFuture/ArchiveView.swift +++ b/NearFuture/ArchiveView.swift @@ -36,6 +36,14 @@ struct ArchiveView: View { AddEventButton(showingAddEventView: $showAddEvent) } } + .navigationTitle("Archive") + .apply { + if #available(iOS 17, *) { + $0.toolbarTitleDisplayMode(.inlineLarge) + } else { + $0.navigationBarTitleDisplayMode(.inline) + } + } } .sheet(isPresented: $showAddEvent) { AddEventView( diff --git a/NearFuture/ContentView.swift b/NearFuture/ContentView.swift index 1c1fb93..15f5cc4 100644 --- a/NearFuture/ContentView.swift +++ b/NearFuture/ContentView.swift @@ -34,7 +34,7 @@ struct ContentView: View { @State private var searchInput: String = "" var filteredEvents: [Event] { if searchInput.isEmpty { - return viewModel.events + return viewModel.events.filter() {!$0.complete} } else { return viewModel.events.filter { $0.name.localizedCaseInsensitiveContains(searchInput) || @@ -86,14 +86,23 @@ struct ContentView: View { } .padding(.horizontal) if /*!searchInput.isEmpty && */filteredEvents.isEmpty { - HelpView(searchInput: $searchInput, focusedField: focusedField) + HelpView( + searchInput: $searchInput, + focusedField: focusedField + ) } Spacer() } } } .navigationTitle("Near Future") - .navigationBarTitleDisplayMode(.inline) + .apply { + if #available(iOS 17, *) { + $0.toolbarTitleDisplayMode(.inlineLarge) + } else { + $0.navigationBarTitleDisplayMode(.inline) + } + } .sheet(isPresented: $showingAddEventView) { AddEventView( viewModel: viewModel, @@ -193,3 +202,7 @@ extension View { .ignoresSafeArea(.all) } } + +extension View { + func apply(@ViewBuilder _ block: (Self) -> V) -> V { block(self) } +} diff --git a/NearFuture/EventListView.swift b/NearFuture/EventListView.swift index 2083efa..49d576c 100644 --- a/NearFuture/EventListView.swift +++ b/NearFuture/EventListView.swift @@ -44,11 +44,13 @@ struct EventListView: View { .font(.headline) .foregroundStyle(.one) .strikethrough(event.complete) + .multilineTextAlignment(.leading) } if !event.notes.isEmpty { Text(event.notes) .font(.subheadline) .foregroundStyle(.one.opacity(0.8)) + .multilineTextAlignment(.leading) } Text( event.date.formatted( diff --git a/NearFuture/Item.swift b/NearFuture/Item.swift index 9412792..757a227 100644 --- a/NearFuture/Item.swift +++ b/NearFuture/Item.swift @@ -76,30 +76,24 @@ struct ColorCodable: Codable, Equatable { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) { let calendar = Calendar.current - let now = Date() - - let isToday = calendar.isDate(now, inSameDayAs: eventDate) - let components = calendar.dateComponents([.second, .day], from: now, to: eventDate) - - guard !isToday else { return ("Today", "Today") } - let secsComponents = eventDate.timeIntervalSinceNow - guard let daysCompontents = components.day else { return ("N/A", "N/A") } - let secs = Double(secsComponents) - var days = 0 - var long = "" - var short = "" - if secs < 0 { + let startOfDayNow = calendar.startOfDay(for: Date()) + let startOfDayEvent = calendar.startOfDay(for: eventDate) + let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent) + guard let days = components.day else { return ("N/A", "N/A") } + guard days != 0 else { return ("Today", "Today") } + if days < 0 { //past - days = Int(floor(secs/86400)) - long = "\(-days) day\(plu(days)) ago" - short = "\(days)d" + return ( + "\(-days) day\(plu(days)) ago", + "\(days)d" + ) } else { //future - days = Int(ceil(secs/86400)) - long = "\(days) day\(plu(days))" - short = "\(days)d" + return ( + "\(days) day\(plu(days))", + "\(days)d" + ) } - return (long, short) } class EventViewModel: ObservableObject { diff --git a/NearFuture/SettingsView.swift b/NearFuture/SettingsView.swift index 348d647..896ff72 100644 --- a/NearFuture/SettingsView.swift +++ b/NearFuture/SettingsView.swift @@ -108,7 +108,13 @@ struct SettingsView: View { } .scrollContentBackground(.hidden) .navigationTitle("Settings") - .navigationBarTitleDisplayMode(.inline) + .apply { + if #available(iOS 17, *) { + $0.toolbarTitleDisplayMode(.inlineLarge) + } else { + $0.navigationBarTitleDisplayMode(.inline) + } + } } } } diff --git a/NearFuture/StatsView.swift b/NearFuture/StatsView.swift index b95627c..fce5cc3 100644 --- a/NearFuture/StatsView.swift +++ b/NearFuture/StatsView.swift @@ -58,7 +58,13 @@ struct StatsView: View { } .scrollContentBackground(.hidden) .navigationTitle("Statistics") - .navigationBarTitleDisplayMode(.inline) + .apply { + if #available(iOS 17, *) { + $0.toolbarTitleDisplayMode(.inlineLarge) + } else { + $0.navigationBarTitleDisplayMode(.inline) + } + } } } } diff --git a/NearFuture/iCloudSettingsView.swift b/NearFuture/iCloudSettingsView.swift index 4b924cd..4f016a3 100644 --- a/NearFuture/iCloudSettingsView.swift +++ b/NearFuture/iCloudSettingsView.swift @@ -37,145 +37,155 @@ struct iCloudSettingsView: View { ZStack { backgroundGradient List { - HStack { - Spacer() - VStack { - ZStack { - Image(systemName: "icloud") - .resizable() - .scaledToFit() - .frame(width: 75, height: 55) - .symbolRenderingMode(.multicolor) - Text("\(viewModel.icloudEventCount)") - .font(.title2) - .monospaced() - .bold() - } - Text("iCloud") - HStack { - Button(role: .destructive) { - showPushAlert.toggle() - } label: { - Image(systemName: "arrow.up") + Section { + 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() } - 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) + .listRowSeparator(.hidden) + .onAppear { + viewModel.sync() + updateStatus() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(hasUbiquitous ? .green : .red) + Text("iCloud") + Spacer() + Text("\(hasUbiquitous ? "" : "Not ")Working") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(lastSyncWasSuccessful ? .green : .red) + Text("Sync Status") + Spacer() + Text("\(viewModel.syncStatus)") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(lastSyncWasNormalAgo ? .green : .red) + Text("Last Sync") + Spacer() + Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(localCountEqualToiCloud ? .green : .red) + Text("Local Events") + Spacer() + Text("\(viewModel.localEventCount)") + .bold() + } + + HStack { + Circle() + .frame(width: 20, height: 20) + .foregroundStyle(icloudCountEqualToLocal ? .green : .red) + Text("Events in iCloud") + Spacer() + Text("\(viewModel.icloudEventCount)") + .bold() + } + } header: { Text("Sync Status") - 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() + } footer: { + Text("Pull to sync\nOr use the arrows to force push/pull") } } + .refreshable { + viewModel.sync() + updateStatus() + } .scrollContentBackground(.hidden) .navigationTitle("iCloud") .navigationBarTitleDisplayMode(.inline)