mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user