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
This commit is contained in:
neon443
2025-05-04 12:31:01 +01:00
parent c97d37711c
commit ffbd17fad8
9 changed files with 191 additions and 150 deletions

View File

@@ -12,6 +12,6 @@ TEAM_ID = 8JGND254B7
BUNDLE_ID = com.neon443.NearFuture BUNDLE_ID = com.neon443.NearFuture
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
GROUP_ID = group.NearFuture GROUP_ID = group.NearFuture
VERSION = 3.1.1 VERSION = 3.2.1
NAME = Near Future NAME = Near Future
BUILD_NUMBER = 5 BUILD_NUMBER = 5

View File

@@ -94,8 +94,10 @@ struct AddEventView: View {
// date picker // date picker
HStack { HStack {
Spacer()
DatePicker("", selection: $eventDate, displayedComponents: .date) DatePicker("", selection: $eventDate, displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle()) .datePickerStyle(WheelDatePickerStyle())
Spacer()
Button() { Button() {
eventDate = Date() eventDate = Date()
} label: { } label: {

View File

@@ -36,6 +36,14 @@ struct ArchiveView: View {
AddEventButton(showingAddEventView: $showAddEvent) AddEventButton(showingAddEventView: $showAddEvent)
} }
} }
.navigationTitle("Archive")
.apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
.sheet(isPresented: $showAddEvent) { .sheet(isPresented: $showAddEvent) {
AddEventView( AddEventView(

View File

@@ -34,7 +34,7 @@ struct ContentView: View {
@State private var searchInput: String = "" @State private var searchInput: String = ""
var filteredEvents: [Event] { var filteredEvents: [Event] {
if searchInput.isEmpty { if searchInput.isEmpty {
return viewModel.events return viewModel.events.filter() {!$0.complete}
} else { } else {
return viewModel.events.filter { return viewModel.events.filter {
$0.name.localizedCaseInsensitiveContains(searchInput) || $0.name.localizedCaseInsensitiveContains(searchInput) ||
@@ -86,14 +86,23 @@ struct ContentView: View {
} }
.padding(.horizontal) .padding(.horizontal)
if /*!searchInput.isEmpty && */filteredEvents.isEmpty { if /*!searchInput.isEmpty && */filteredEvents.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField) HelpView(
searchInput: $searchInput,
focusedField: focusedField
)
} }
Spacer() Spacer()
} }
} }
} }
.navigationTitle("Near Future") .navigationTitle("Near Future")
.navigationBarTitleDisplayMode(.inline) .apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $showingAddEventView) { .sheet(isPresented: $showingAddEventView) {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel,
@@ -193,3 +202,7 @@ extension View {
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
} }
} }
extension View {
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}

View File

@@ -44,11 +44,13 @@ struct EventListView: View {
.font(.headline) .font(.headline)
.foregroundStyle(.one) .foregroundStyle(.one)
.strikethrough(event.complete) .strikethrough(event.complete)
.multilineTextAlignment(.leading)
} }
if !event.notes.isEmpty { if !event.notes.isEmpty {
Text(event.notes) Text(event.notes)
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.one.opacity(0.8)) .foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
} }
Text( Text(
event.date.formatted( event.date.formatted(

View File

@@ -76,30 +76,24 @@ struct ColorCodable: Codable, Equatable {
func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let calendar = Calendar.current let calendar = Calendar.current
let now = Date() let startOfDayNow = calendar.startOfDay(for: Date())
let startOfDayEvent = calendar.startOfDay(for: eventDate)
let isToday = calendar.isDate(now, inSameDayAs: eventDate) let components = calendar.dateComponents([.day], from: startOfDayNow, to: startOfDayEvent)
let components = calendar.dateComponents([.second, .day], from: now, to: eventDate) guard let days = components.day else { return ("N/A", "N/A") }
guard days != 0 else { return ("Today", "Today") }
guard !isToday else { return ("Today", "Today") } if days < 0 {
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 {
//past //past
days = Int(floor(secs/86400)) return (
long = "\(-days) day\(plu(days)) ago" "\(-days) day\(plu(days)) ago",
short = "\(days)d" "\(days)d"
)
} else { } else {
//future //future
days = Int(ceil(secs/86400)) return (
long = "\(days) day\(plu(days))" "\(days) day\(plu(days))",
short = "\(days)d" "\(days)d"
)
} }
return (long, short)
} }
class EventViewModel: ObservableObject { class EventViewModel: ObservableObject {

View File

@@ -108,7 +108,13 @@ struct SettingsView: View {
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Settings") .navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline) .apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

View File

@@ -58,7 +58,13 @@ struct StatsView: View {
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Statistics") .navigationTitle("Statistics")
.navigationBarTitleDisplayMode(.inline) .apply {
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

View File

@@ -37,145 +37,155 @@ struct iCloudSettingsView: View {
ZStack { ZStack {
backgroundGradient backgroundGradient
List { List {
HStack { Section {
Spacer() HStack {
VStack { Spacer()
ZStack { VStack {
Image(systemName: "icloud") ZStack {
.resizable() Image(systemName: "icloud")
.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")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30, height: 40) .frame(width: 75, height: 55)
.symbolRenderingMode(.multicolor)
Text("\(viewModel.icloudEventCount)")
.font(.title2)
.monospaced()
.bold()
} }
.buttonStyle(BorderedButtonStyle()) Text("iCloud")
.alert("Warning", isPresented: $showPushAlert) { HStack {
Button("OK", role: .destructive) { Button(role: .destructive) {
viewModel.replaceiCloudWithLocalData() showPushAlert.toggle()
} label: {
Image(systemName: "arrow.up")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPushAlert) {
Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
}
Button() {
viewModel.sync() viewModel.sync()
updateStatus() updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
.foregroundStyle(Color.accentColor)
}
.buttonStyle(BorderedButtonStyle())
Button(role: .destructive) {
showPullAlert.toggle()
} label: {
Image(systemName: "arrow.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPullAlert) {
Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored locally with Events stored in iCloud.")
} }
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored in iCloud with Events stored locally.")
} }
ZStack {
Button() { Image(systemName: device.sf)
viewModel.sync()
updateStatus()
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30, height: 40) .frame(width: 75, height: 75)
.foregroundStyle(Color.accentColor) .symbolRenderingMode(.monochrome)
} Text("\(viewModel.localEventCount)")
.buttonStyle(BorderedButtonStyle()) .font(.title2)
.monospaced()
Button(role: .destructive) { .bold()
showPullAlert.toggle()
} label: {
Image(systemName: "arrow.down")
.resizable()
.scaledToFit()
.frame(width: 30, height: 40)
}
.buttonStyle(BorderedButtonStyle())
.alert("Warning", isPresented: $showPullAlert) {
Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData()
viewModel.sync()
updateStatus()
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will replace Events stored locally with Events stored in iCloud.")
} }
Text(device.label)
} }
ZStack { Spacer()
Image(systemName: device.sf)
.resizable()
.scaledToFit()
.frame(width: 75, height: 75)
.symbolRenderingMode(.monochrome)
Text("\(viewModel.localEventCount)")
.font(.title2)
.monospaced()
.bold()
}
Text(device.label)
} }
Spacer() .listRowSeparator(.hidden)
} .onAppear {
.listRowSeparator(.hidden) viewModel.sync()
.onAppear { updateStatus()
viewModel.sync() }
updateStatus()
} HStack {
Circle()
HStack { .frame(width: 20, height: 20)
Circle() .foregroundStyle(hasUbiquitous ? .green : .red)
.frame(width: 20, height: 20) Text("iCloud")
.foregroundStyle(hasUbiquitous ? .green : .red) Spacer()
Text("iCloud") Text("\(hasUbiquitous ? "" : "Not ")Working")
Spacer() .bold()
Text("\(hasUbiquitous ? "" : "Not ")Working") }
.bold()
} HStack {
Circle()
HStack { .frame(width: 20, height: 20)
Circle() .foregroundStyle(lastSyncWasSuccessful ? .green : .red)
.frame(width: 20, height: 20) Text("Sync Status")
.foregroundStyle(lastSyncWasSuccessful ? .green : .red) 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") Text("Sync Status")
Spacer() } footer: {
Text("\(viewModel.syncStatus)") Text("Pull to sync\nOr use the arrows to force push/pull")
.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()
} }
} }
.refreshable {
viewModel.sync()
updateStatus()
}
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("iCloud") .navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)