moved icloud ui functions into viewModel

- idk why it was in the view
added settings for mac
fix addeventview
export view pasteboard for mac
move accent icon to new file
Archive view reversed - new to old
This commit is contained in:
neon443
2025-06-13 17:15:23 +01:00
parent ee2e05c523
commit 3f21074091
11 changed files with 227 additions and 193 deletions

View File

@@ -32,6 +32,15 @@ struct ContentView: View {
Image(systemName: "tray.full") Image(systemName: "tray.full")
Text("Archive") Text("Archive")
} }
NavigationLink {
SettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} label: {
Image(systemName: "gear")
Text("Settings")
}
} }
} detail: { } detail: {

View File

@@ -44,6 +44,13 @@
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; }; A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; };
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; }; A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; };
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; }; A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; };
A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8442DCAABE00064DCA0 /* iCloudSettingsView.swift */; };
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9EE32DFC775300ED655F /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; };
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8452DCAABE00064DCA0 /* ImportView.swift */; };
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8432DCAABE00064DCA0 /* ExportView.swift */; };
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; }; A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; };
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; }; A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; };
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; }; A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; };
@@ -125,6 +132,7 @@
A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; }; A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; }; A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
A95E9ED72DFC742B00ED655F /* AccentIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentIcon.swift; sourceTree = "<group>"; };
A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; }; A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; };
A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; }; A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; };
@@ -170,6 +178,7 @@
children = ( children = (
A920C28B2D24011400E4F9B1 /* Events.swift */, A920C28B2D24011400E4F9B1 /* Events.swift */,
A90D49602DDE626300781124 /* Settings.swift */, A90D49602DDE626300781124 /* Settings.swift */,
A95E9ED72DFC742B00ED655F /* AccentIcon.swift */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -507,9 +516,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A95E9EE32DFC775300ED655F /* SettingsView.swift in Sources */,
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */, A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */,
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
A98C20CC2DE730740008D61C /* EditEventView.swift in Sources */, A98C20CC2DE730740008D61C /* EditEventView.swift in Sources */,
A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */, A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */,
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */,
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */, A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */, A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */, A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
@@ -519,6 +532,7 @@
A90D49422DDE114100781124 /* Events.swift in Sources */, A90D49422DDE114100781124 /* Events.swift in Sources */,
A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */, A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */,
A90D49622DDE626300781124 /* Settings.swift in Sources */, A90D49622DDE626300781124 /* Settings.swift in Sources */,
A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -540,6 +554,7 @@
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */, A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */,
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */, A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */,
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */, A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */, A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */, A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */, A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
@@ -555,6 +570,7 @@
files = ( files = (
A979F6182D2714310094C0B3 /* Events.swift in Sources */, A979F6182D2714310094C0B3 /* Events.swift in Sources */,
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */, A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */,
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */, A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */,
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */, A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */,
A90D49632DDE626300781124 /* Settings.swift in Sources */, A90D49632DDE626300781124 /* Settings.swift in Sources */,

View File

@@ -11,7 +11,8 @@ struct ArchiveView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@State var showAddEvent: Bool = false @State var showAddEvent: Bool = false
var filteredEvents: [Event] { var filteredEvents: [Event] {
return viewModel.events.filter() {$0.complete} let filteredEvents = viewModel.events.filter({$0.complete})
return filteredEvents.reversed()
} }
var body: some View { var body: some View {
NavigationStack { NavigationStack {

View File

@@ -31,90 +31,92 @@ struct AddEventView: View {
if !adding { if !adding {
backgroundGradient backgroundGradient
} }
List { NavigationStack {
Section( List {
header: Section(
Text("Event Details") header:
.font(.headline) Text("Event Details")
.foregroundColor(.accentColor) .font(.headline)
) { .foregroundColor(.accentColor)
// name & symbol ) {
HStack(spacing: 5) { // name & symbol
Button() { HStack(spacing: 5) {
isSymbolPickerPresented.toggle() Button() {
} label: { isSymbolPickerPresented.toggle()
Image(systemName: event.symbol) } label: {
.resizable() Image(systemName: event.symbol)
.scaledToFit() .resizable()
.frame(width: 20, height: 20) .scaledToFit()
.foregroundStyle(event.color.color) .frame(width: 20, height: 20)
} .foregroundStyle(event.color.color)
.frame(width: 20)
.buttonStyle(.borderless)
.sheet(isPresented: $isSymbolPickerPresented) {
SymbolsPicker(
selection: $event.symbol,
title: "Choose a Symbol",
searchLabel: "Search...",
autoDismiss: true)
.presentationDetents([.medium])
}
}
// dscription
ZStack {
TextField("Event Notes", text: $event.notes)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.trailing, event.notes.isEmpty ? 0 : 30)
.animation(.spring, value: event.notes)
.focused($focusedField, equals: Field.Notes)
.submitLabel(.done)
.onSubmit {
focusedField = nil
} }
// MagicClearButton(text: $eventNotes) .frame(width: 20)
} .buttonStyle(.borderless)
.sheet(isPresented: $isSymbolPickerPresented) {
SymbolsPicker(
// date picker selection: $event.symbol,
HStack { title: "Choose a Symbol",
Spacer() searchLabel: "Search...",
DatePicker("", selection: $event.date, displayedComponents: .date) autoDismiss: true)
.datePickerStyle(.wheel) .presentationDetents([.medium])
Spacer() }
Button() { TextField("Event Name", text: $event.name)
event.date = Date() .textFieldStyle(.roundedBorder)
} label: {
Image(systemName: "arrow.uturn.left")
.resizable()
.scaledToFit()
} }
.buttonStyle(BorderlessButtonStyle())
.frame(width: 20) // dscription
} ZStack {
TextField("Event Notes", text: $event.notes)
DatePicker( .textFieldStyle(RoundedBorderTextFieldStyle())
"", .padding(.trailing, event.notes.isEmpty ? 0 : 30)
selection: $event.date, .animation(.spring, value: event.notes)
displayedComponents: .hourAndMinute .focused($focusedField, equals: Field.Notes)
) .submitLabel(.done)
.datePickerStyle(.wheel) .onSubmit {
focusedField = nil
// re-ocurrence Picker }
Picker("Recurrence", selection: $event.recurrence) { // MagicClearButton(text: $eventNotes)
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
Text(recurrence.rawValue.capitalized)
} }
}
.pickerStyle(SegmentedPickerStyle())
Text( // date picker
describeOccurrence( HStack {
date: event.date, Spacer()
recurrence: event.recurrence DatePicker("", selection: $event.date, displayedComponents: .date)
.datePickerStyle(.wheel)
Spacer()
Button() {
event.date = Date()
} label: {
Image(systemName: "arrow.uturn.left")
.resizable()
.scaledToFit()
}
.buttonStyle(BorderlessButtonStyle())
.frame(width: 20)
}
DatePicker(
"",
selection: $event.date,
displayedComponents: .hourAndMinute
) )
)
// re-ocurrence Picker
Picker("Recurrence", selection: $event.recurrence) {
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
Text(recurrence.rawValue.capitalized)
}
}
.pickerStyle(SegmentedPickerStyle())
Text(
describeOccurrence(
date: event.date,
recurrence: event.recurrence
)
)
}
} }
.scrollContentBackground(.hidden)
.navigationTitle("\(adding ? "Add Event" : "")") .navigationTitle("\(adding ? "Add Event" : "")")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
@@ -128,10 +130,11 @@ struct AddEventView: View {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30) .frame(width: 30)
.tint(.one)
} }
} }
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem() {
if adding { if adding {
Button { Button {
viewModel.addEvent( viewModel.addEvent(
@@ -165,18 +168,12 @@ struct AddEventView: View {
} message: { } message: {
Text("Give your Event a name before saving.") Text("Give your Event a name before saving.")
} }
if event.name.isEmpty {
HStack {
Image(systemName: "exclamationmark")
.foregroundStyle(.red)
Text("Give your event a name.")
}
}
} }
} }
} }
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.scrollContentBackground(.hidden)
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
} }
} }

View File

@@ -12,7 +12,11 @@ struct ExportView: View {
var body: some View { var body: some View {
List { List {
Button() { Button() {
#if canImport(UIKit)
UIPasteboard.general.string = viewModel.exportEvents() UIPasteboard.general.string = viewModel.exportEvents()
#else
NSPasteboard.general.setString(viewModel.exportEvents(), forType: .string)
#endif
} label: { } label: {
Label("Copy Events", systemImage: "document.on.clipboard") Label("Copy Events", systemImage: "document.on.clipboard")
} }

View File

@@ -10,37 +10,11 @@ import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel @ObservedObject var settingsModel: SettingsViewModel
@State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false
@State private var lastSyncWasNormalAgo: Bool = false
@State private var localCountEqualToiCloud: Bool = false
@State private var icloudCountEqualToLocal: Bool = false
@State private var importStr: String = "" @State private var importStr: String = ""
func updateStatus() {
let vm = viewModel
hasUbiquitous = vm.hasUbiquitousKeyValueStore()
lastSyncWasSuccessful = vm.syncStatus.contains("Success")
lastSyncWasNormalAgo = vm.lastSync?.timeIntervalSinceNow.isNormal ?? false
localCountEqualToiCloud = vm.localEventCount == vm.icloudEventCount
icloudCountEqualToLocal = vm.icloudEventCount == vm.localEventCount
}
var iCloudStatusColor: Color {
let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal
let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal
if allTrue {
return .green
} else if someTrue {
return .orange
} else {
return .red
}
}
func changeIcon(to: String) { func changeIcon(to: String) {
#if canImport(UIKit)
guard UIApplication.shared.supportsAlternateIcons else { guard UIApplication.shared.supportsAlternateIcons else {
print("doesnt tsupport alternate icons") print("doesnt tsupport alternate icons")
return return
@@ -54,6 +28,7 @@ struct SettingsView: View {
UIApplication.shared.setAlternateIconName(to) { error in UIApplication.shared.setAlternateIconName(to) { error in
print(error as Any) print(error as Any)
} }
#endif
} }
var body: some View { var body: some View {
@@ -64,7 +39,11 @@ struct SettingsView: View {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack { HStack {
ForEach(settingsModel.accentChoices, id: \.self) { choice in ForEach(settingsModel.accentChoices, id: \.self) { choice in
#if canImport(UIKit)
let color = Color(uiColor: UIColor(named: "uiColors/\(choice)")!) let color = Color(uiColor: UIColor(named: "uiColors/\(choice)")!)
#else
let color = Color(nsColor: NSColor(named: "uiColors/\(choice)")!)
#endif
ZStack { ZStack {
Button() { Button() {
settingsModel.changeTint(to: choice) settingsModel.changeTint(to: choice)
@@ -94,6 +73,8 @@ struct SettingsView: View {
NavigationLink() { NavigationLink() {
List { List {
if !settingsModel.notifsGranted { if !settingsModel.notifsGranted {
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
Button("Request Notifications") { Button("Request Notifications") {
Task.detached { Task.detached {
let requestNotifsResult = await requestNotifs() let requestNotifsResult = await requestNotifs()
@@ -102,8 +83,6 @@ struct SettingsView: View {
} }
} }
} }
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
} else { } else {
Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future") Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future")
.foregroundStyle(.green) .foregroundStyle(.green)
@@ -116,13 +95,7 @@ struct SettingsView: View {
NavigationLink() { NavigationLink() {
iCloudSettingsView( iCloudSettingsView(
viewModel: viewModel, viewModel: viewModel,
settingsModel: settingsModel, settingsModel: settingsModel
hasUbiquitous: $hasUbiquitous,
lastSyncWasSuccessful: $lastSyncWasSuccessful,
lastSyncWasNormalAgo: $lastSyncWasNormalAgo,
localCountEqualToiCloud: $localCountEqualToiCloud,
icloudCountEqualToLocal: $icloudCountEqualToLocal,
updateStatus: updateStatus
) )
} label: { } label: {
HStack { HStack {
@@ -131,12 +104,12 @@ struct SettingsView: View {
Spacer() Spacer()
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(iCloudStatusColor) .foregroundStyle(viewModel.iCloudStatusColor)
} }
} }
.onAppear { .onAppear {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
NavigationLink() { NavigationLink() {
ImportView(viewModel: viewModel, importStr: $importStr) ImportView(viewModel: viewModel, importStr: $importStr)
@@ -183,11 +156,13 @@ struct SettingsView: View {
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Settings") .navigationTitle("Settings")
.apply { .apply {
#if canImport(UIKit)
if #available(iOS 17, *) { if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge) $0.toolbarTitleDisplayMode(.inlineLarge)
} else { } else {
$0.navigationBarTitleDisplayMode(.inline) $0.navigationBarTitleDisplayMode(.inline)
} }
#endif
} }
} }
} }

View File

@@ -13,13 +13,13 @@ struct iCloudSettingsView: View {
@State var showPushAlert: Bool = false @State var showPushAlert: Bool = false
@State var showPullAlert: Bool = false @State var showPullAlert: Bool = false
@Binding var hasUbiquitous: Bool // @Binding var hasUbiquitous: Bool
@Binding var lastSyncWasSuccessful: Bool // @Binding var lastSyncWasSuccessful: Bool
@Binding var lastSyncWasNormalAgo: Bool // @Binding var lastSyncWasNormalAgo: Bool
@Binding var localCountEqualToiCloud: Bool // @Binding var localCountEqualToiCloud: Bool
@Binding var icloudCountEqualToLocal: Bool // @Binding var icloudCountEqualToLocal: Bool
//
var updateStatus: () -> Void // var updateStatus: () -> Void
var body: some View { var body: some View {
ZStack { ZStack {
@@ -55,7 +55,7 @@ struct iCloudSettingsView: View {
Button("OK", role: .destructive) { Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData() viewModel.replaceiCloudWithLocalData()
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
@@ -64,7 +64,7 @@ struct iCloudSettingsView: View {
Button() { Button() {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} label: { } label: {
Image(systemName: "arrow.triangle.2.circlepath") Image(systemName: "arrow.triangle.2.circlepath")
.resizable() .resizable()
@@ -87,7 +87,7 @@ struct iCloudSettingsView: View {
Button("OK", role: .destructive) { Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData() viewModel.replaceLocalWithiCloudData()
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
@@ -112,23 +112,23 @@ struct iCloudSettingsView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.onAppear { .onAppear {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red) .foregroundStyle(viewModel.hasUbiquitous ? .green : .red)
Text("iCloud") Text("iCloud")
Spacer() Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working") Text("\(viewModel.hasUbiquitous ? "" : "Not ")Working")
.bold() .bold()
} }
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red) .foregroundStyle(viewModel.lastSyncWasSuccessful ? .green : .red)
Text("Sync Status") Text("Sync Status")
Spacer() Spacer()
Text("\(viewModel.syncStatus)") Text("\(viewModel.syncStatus)")
@@ -138,7 +138,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red) .foregroundStyle(viewModel.lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync") Text("Last Sync")
Spacer() Spacer()
Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")") Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")")
@@ -148,7 +148,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red) .foregroundStyle(viewModel.localCountEqualToiCloud ? .green : .red)
Text("Local Events") Text("Local Events")
Spacer() Spacer()
Text("\(viewModel.localEventCount)") Text("\(viewModel.localEventCount)")
@@ -158,7 +158,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red) .foregroundStyle(viewModel.icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud") Text("Events in iCloud")
Spacer() Spacer()
Text("\(viewModel.icloudEventCount)") Text("\(viewModel.icloudEventCount)")
@@ -172,11 +172,10 @@ struct iCloudSettingsView: View {
} }
.refreshable { .refreshable {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("iCloud") .navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
} }
} }
} }
@@ -184,12 +183,6 @@ struct iCloudSettingsView: View {
#Preview("iCloudSettingsView") { #Preview("iCloudSettingsView") {
iCloudSettingsView( iCloudSettingsView(
viewModel: dummyEventViewModel(), viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel(), settingsModel: dummySettingsViewModel()
hasUbiquitous: .constant(true),
lastSyncWasSuccessful: .constant(true),
lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true),
updateStatus: {}
) )
} }

View File

@@ -11,17 +11,18 @@
- [x] Event colors - [x] Event colors
- [x] Recurrence - [x] Recurrence
- [x] Search - [x] Search
- [ ] Notifications - [x] Notifications
- [ ] Mac App
- [ ] Apple Watch App - [ ] Apple Watch App
- [x] Home Screen Widgets - [x] Home Screen Widgets
- [ ] Lock Screen Widgets - [ ] Lock Screen Widgets
- [ ] Later Box - [ ] Later Box
- [ ] Sort by - [ ] Sort by
- [ ] Reorder Events - [ ] Reorder Events
- [ ] Archive - [x] Archive
- [ ] Collaboration - [ ] Collaboration
- [ ] Autocomplete tasks - [ ] Autocomplete tasks
- [ ] Settings - [x] Settings
## Features ## Features
- **Event Creation**: Create events with a name, description, date, recurrence, and an icon - **Event Creation**: Create events with a name, description, date, recurrence, and an icon
@@ -53,4 +54,4 @@ Contributions are welcome! Just follow these steps:
## Used Tools/Frameworks ## Used Tools/Frameworks
- Swift & SwiftUI by Apple - Swift & SwiftUI by Apple
- **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI]. - **SFSymbolsPicker** by [alessiorubicini/SFSymbolsPickerForSwiftUI].

View File

@@ -0,0 +1,47 @@
//
// AccentIcon.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif
class AccentIcon {
#if canImport(UIKit)
var icon: UIImage
#elseif canImport(AppKit)
var icon: NSImage
#endif
var color: Color
var name: String
init(_ colorName: String) {
#if canImport(UIKit)
self.icon = UIImage(named: "AppIcon")!
self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: "AppIcon")
self.color = Color(nsColor: NSColor(named: "uiColors/\(colorName)")!)
#endif
self.name = colorName
if colorName != "orange" {
setSelfIcon(to: colorName)
}
}
func setSelfIcon(to name: String) {
#if canImport(UIKit)
self.icon = UIImage(named: name)!
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: name)
#endif
}
}

View File

@@ -11,10 +11,8 @@ import SwiftUI
import WidgetKit import WidgetKit
import UserNotifications import UserNotifications
import AppIntents import AppIntents
import AudioToolbox
#if canImport(AppKit) #if canImport(AppKit)
import AppKit import AppKit
import IOKit
#endif #endif
//@Model //@Model
@@ -164,6 +162,25 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
@Published var localEventCount: Int = 0 @Published var localEventCount: Int = 0
@Published var syncStatus: String = "Not Synced" @Published var syncStatus: String = "Not Synced"
@Published var hasUbiquitous: Bool = false
@Published var lastSyncWasSuccessful: Bool = false
@Published var lastSyncWasNormalAgo: Bool = false
@Published var localCountEqualToiCloud: Bool = false
@Published var icloudCountEqualToLocal: Bool = false
var iCloudStatusColor: Color {
let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal
let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal
if allTrue {
return .green
} else if someTrue {
return .orange
} else {
return .red
}
}
init(load: Bool = true) { init(load: Bool = true) {
self.editableTemplate = template self.editableTemplate = template
if load { if load {
@@ -343,6 +360,14 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
} }
} }
func updateiCStatus() {
hasUbiquitous = hasUbiquitousKeyValueStore()
lastSyncWasSuccessful = syncStatus.contains("Success")
lastSyncWasNormalAgo = lastSync?.timeIntervalSinceNow.isNormal ?? false
localCountEqualToiCloud = localEventCount == icloudEventCount
icloudCountEqualToLocal = icloudEventCount == localEventCount
}
//MARK: Danger Zone //MARK: Danger Zone
func dangerClearLocalData() { func dangerClearLocalData() {
UserDefaults.standard.removeObject(forKey: "events") UserDefaults.standard.removeObject(forKey: "events")

View File

@@ -18,40 +18,6 @@ struct NFSettings: Codable, Equatable {
var prevAppVersion: String = getVersion()+getBuildID() var prevAppVersion: String = getVersion()+getBuildID()
} }
class AccentIcon {
#if canImport(UIKit)
var icon: UIImage
#elseif canImport(AppKit)
var icon: NSImage
#endif
var color: Color
var name: String
init(_ colorName: String) {
#if canImport(UIKit)
self.icon = UIImage(named: "AppIcon")!
self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: "AppIcon")
self.color = Color(nsColor: NSColor(named: "uiColors/\(colorName)")!)
#endif
self.name = colorName
if colorName != "orange" {
setSelfIcon(to: colorName)
}
}
func setSelfIcon(to name: String) {
#if canImport(UIKit)
self.icon = UIImage(named: name)!
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: name)
#endif
}
}
class SettingsViewModel: ObservableObject { class SettingsViewModel: ObservableObject {
@Published var settings: NFSettings = NFSettings() @Published var settings: NFSettings = NFSettings()