mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 14:56:15 +00:00
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:
@@ -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: {
|
||||||
|
|
||||||
|
|||||||
@@ -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 */,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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].
|
||||||
|
|||||||
47
Shared/Model/AccentIcon.swift
Normal file
47
Shared/Model/AccentIcon.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user