mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
declared types on all views, trying to make a nice mac app
This commit is contained in:
24
MacNearFuture/MacNearFutureApp.swift
Normal file
24
MacNearFuture/MacNearFutureApp.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// MacNearFutureApp.swift
|
||||||
|
// MacNearFuture
|
||||||
|
//
|
||||||
|
// Created by neon443 on 21/05/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct NearFutureApp: App {
|
||||||
|
@StateObject var settingsModel: SettingsViewModel = SettingsViewModel()
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView(
|
||||||
|
viewModel: EventViewModel(),
|
||||||
|
settingsModel: settingsModel
|
||||||
|
)
|
||||||
|
.tint(settingsModel.settings.tint.color)
|
||||||
|
}
|
||||||
|
.windowIdealSize(.fitToContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,15 +12,11 @@ struct ContentView: View {
|
|||||||
@ObservedObject var settingsModel: SettingsViewModel
|
@ObservedObject var settingsModel: SettingsViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
ScrollView {
|
||||||
Image(systemName: "globe")
|
ForEach(viewModel.events) { event in
|
||||||
.imageScale(.large)
|
EventListView(viewModel: viewModel, event: event)
|
||||||
.foregroundStyle(.tint)
|
}
|
||||||
Text("Hello, world!")
|
}
|
||||||
Text(getVersion())
|
|
||||||
.foregroundStyle(Color("uiColors/bloo"))
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
61
MacNearFuture/Views/EditEventView.swift
Normal file
61
MacNearFuture/Views/EditEventView.swift
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// EditEventView.swift
|
||||||
|
// NearFuture
|
||||||
|
//
|
||||||
|
// Created by neon443 on 21/05/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct EditEventView: View {
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
@ObservedObject var viewModel: EventViewModel
|
||||||
|
@Binding var event: Event
|
||||||
|
|
||||||
|
fileprivate func saveEdits() {
|
||||||
|
//if there is an event in vM.events with the id of the event we r editing,
|
||||||
|
//firstindex - loops through the arr and finds first element where that events id matches editing event's id
|
||||||
|
if let index = viewModel.events.firstIndex(where: { xEvent in
|
||||||
|
xEvent.id == event.id
|
||||||
|
}) {
|
||||||
|
viewModel.events[index] = event
|
||||||
|
}
|
||||||
|
viewModel.saveEvents()
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
AddEventView(
|
||||||
|
viewModel: viewModel,
|
||||||
|
eventName: $event.name,
|
||||||
|
eventComplete: $event.complete,
|
||||||
|
eventCompleteDesc: $event.completeDesc,
|
||||||
|
eventSymbol: $event.symbol,
|
||||||
|
eventColor: $event.color.colorBind,
|
||||||
|
eventNotes: $event.notes,
|
||||||
|
eventDate: $event.date,
|
||||||
|
eventRecurrence: $event.recurrence,
|
||||||
|
adding: false //bc we editing existing event
|
||||||
|
)
|
||||||
|
.navigationTitle("Edit Event")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
Button() {
|
||||||
|
saveEdits()
|
||||||
|
} label: {
|
||||||
|
Text("Done")
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
.disabled(event.name == "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
EditEventView(
|
||||||
|
viewModel: dummyEventViewModel(),
|
||||||
|
event: .constant(dummyEventViewModel().template)
|
||||||
|
)
|
||||||
|
}
|
||||||
176
MacNearFuture/Views/EventListView.swift
Normal file
176
MacNearFuture/Views/EventListView.swift
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//
|
||||||
|
// EventListView.swift
|
||||||
|
// MacNearFuture
|
||||||
|
//
|
||||||
|
// Created by neon443 on 21/05/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct EventListView: View {
|
||||||
|
@ObservedObject var viewModel: EventViewModel
|
||||||
|
@State var event: Event
|
||||||
|
@State private var sheetpresented: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
ZStack {
|
||||||
|
HStack {
|
||||||
|
RoundedRectangle(cornerRadius: 5)
|
||||||
|
.frame(width: 7)
|
||||||
|
.foregroundStyle(
|
||||||
|
event.color.color.opacity(
|
||||||
|
event.complete ? 0.5 : 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: event.symbol)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.shadow(radius: 5)
|
||||||
|
.foregroundStyle(
|
||||||
|
.one.opacity(
|
||||||
|
event.complete ? 0.5 : 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Text("\(event.name)")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.one)
|
||||||
|
.strikethrough(event.complete)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
if !event.notes.isEmpty {
|
||||||
|
Text(event.notes)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.one.opacity(0.8))
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
event.date.formatted(
|
||||||
|
date: .long,
|
||||||
|
time: .shortened
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(
|
||||||
|
.one.opacity(
|
||||||
|
event.complete ? 0.5 : 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if event.recurrence != .none {
|
||||||
|
Text("Occurs \(event.recurrence.rawValue)")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(
|
||||||
|
.one.opacity(event.complete ? 0.5 : 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text("\(daysUntilEvent(event.date).long)")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||||
|
}
|
||||||
|
Button() {
|
||||||
|
withAnimation {
|
||||||
|
event.complete.toggle()
|
||||||
|
}
|
||||||
|
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||||
|
currEvent.id == event.id
|
||||||
|
}
|
||||||
|
if let eventToModify = eventToModify {
|
||||||
|
viewModel.events[eventToModify] = event
|
||||||
|
viewModel.saveEvents()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if event.complete {
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.foregroundStyle(.green)
|
||||||
|
Image(systemName: "checkmark")
|
||||||
|
.resizable()
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.scaledToFit()
|
||||||
|
.bold()
|
||||||
|
.frame(width: 15)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Image(systemName: "circle")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.foregroundStyle(event.color.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.frame(maxWidth: 25, maxHeight: 25)
|
||||||
|
.shadow(radius: 5)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
.apply {
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
$0.sensoryFeedback(.success, trigger: event.complete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transition(.opacity)
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
.background(.ultraThinMaterial)
|
||||||
|
.clipShape(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
}
|
||||||
|
.contextMenu() {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||||
|
currEvent.id == event.id
|
||||||
|
}
|
||||||
|
if let eventToModify = eventToModify {
|
||||||
|
viewModel.events.remove(at: eventToModify)
|
||||||
|
viewModel.saveEvents()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $sheetpresented) {
|
||||||
|
EditEventView(
|
||||||
|
viewModel: viewModel,
|
||||||
|
event: $event
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("EventListView") {
|
||||||
|
let vm = dummyEventViewModel()
|
||||||
|
ZStack {
|
||||||
|
Color.black
|
||||||
|
VStack {
|
||||||
|
ForEach(0..<50) { _ in
|
||||||
|
Rectangle()
|
||||||
|
.foregroundStyle(randomColor().opacity(0.5))
|
||||||
|
.padding(-10)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea(.all)
|
||||||
|
.blur(radius: 5)
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
ForEach(vm.events) { event in
|
||||||
|
EventListView(
|
||||||
|
viewModel: vm,
|
||||||
|
event: event
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
EventListView(
|
||||||
|
viewModel: dummyEventViewModel(),
|
||||||
|
event: dummyEventViewModel().template
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,11 +10,20 @@
|
|||||||
A90D49382DDE0FAF00781124 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49332DDE0FAF00781124 /* ContentView.swift */; };
|
A90D49382DDE0FAF00781124 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49332DDE0FAF00781124 /* ContentView.swift */; };
|
||||||
A90D493D2DDE10B200781124 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; };
|
A90D493D2DDE10B200781124 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; };
|
||||||
A90D493E2DDE10CF00781124 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; };
|
A90D493E2DDE10CF00781124 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; };
|
||||||
A90D49402DDE111400781124 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; };
|
|
||||||
A90D49422DDE114100781124 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Event.swift */; };
|
A90D49422DDE114100781124 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Event.swift */; };
|
||||||
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
|
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
|
||||||
A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
|
A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
|
||||||
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
|
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
|
||||||
|
A90D494B2DDE2C2900781124 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
|
||||||
|
A90D494F2DDE2C8500781124 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D494E2DDE2C8500781124 /* EditEventView.swift */; };
|
||||||
|
A90D49502DDE2C8500781124 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D494E2DDE2C8500781124 /* EditEventView.swift */; };
|
||||||
|
A90D49522DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; };
|
||||||
|
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; };
|
||||||
|
A90D49562DDE2D5800781124 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A90D49552DDE2D5800781124 /* SFSymbolsPicker */; };
|
||||||
|
A90D49582DDE2DBD00781124 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49572DDE2DBD00781124 /* EventListView.swift */; };
|
||||||
|
A90D49592DDE2DBD00781124 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49572DDE2DBD00781124 /* EventListView.swift */; };
|
||||||
|
A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; };
|
||||||
|
A90D495C2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; };
|
||||||
A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; };
|
A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; };
|
||||||
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; };
|
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; };
|
||||||
A914FA4F2DD276D200856265 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4E2DD276D200856265 /* AboutView.swift */; };
|
A914FA4F2DD276D200856265 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4E2DD276D200856265 /* AboutView.swift */; };
|
||||||
@@ -70,10 +79,14 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
A90D491F2DDE08E400781124 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
A90D491F2DDE08E400781124 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
A90D49262DDE0FA400781124 /* MacNearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MacNearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
A90D49262DDE0FA400781124 /* Near Future.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Near Future.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
A90D49332DDE0FAF00781124 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
A90D49332DDE0FAF00781124 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
A90D49342DDE0FAF00781124 /* MacNearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MacNearFuture.entitlements; sourceTree = "<group>"; };
|
A90D49342DDE0FAF00781124 /* MacNearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MacNearFuture.entitlements; sourceTree = "<group>"; };
|
||||||
A90D49432DDE1C1100781124 /* Tints.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Tints.xcassets; sourceTree = "<group>"; };
|
A90D49432DDE1C1100781124 /* Tints.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Tints.xcassets; sourceTree = "<group>"; };
|
||||||
|
A90D494E2DDE2C8500781124 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EditEventView.swift; path = MacNearFuture/Views/EditEventView.swift; sourceTree = SOURCE_ROOT; };
|
||||||
|
A90D49512DDE2D0000781124 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||||
|
A90D49572DDE2DBD00781124 /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = "<group>"; };
|
||||||
|
A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MacNearFutureApp.swift; path = MacNearFuture/MacNearFutureApp.swift; sourceTree = SOURCE_ROOT; };
|
||||||
A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||||
A914FA4C2DD2768900856265 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewView.swift; path = NearFuture/Views/Settings/WhatsNewView.swift; sourceTree = SOURCE_ROOT; };
|
A914FA4C2DD2768900856265 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewView.swift; path = NearFuture/Views/Settings/WhatsNewView.swift; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -111,6 +124,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
A90D49562DDE2D5800781124 /* SFSymbolsPicker in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -143,6 +157,7 @@
|
|||||||
A90D49362DDE0FAF00781124 /* MacNearFuture */ = {
|
A90D49362DDE0FAF00781124 /* MacNearFuture */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */,
|
||||||
A90D493F2DDE10EC00781124 /* Views */,
|
A90D493F2DDE10EC00781124 /* Views */,
|
||||||
A90D49342DDE0FAF00781124 /* MacNearFuture.entitlements */,
|
A90D49342DDE0FAF00781124 /* MacNearFuture.entitlements */,
|
||||||
);
|
);
|
||||||
@@ -153,6 +168,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A90D49332DDE0FAF00781124 /* ContentView.swift */,
|
A90D49332DDE0FAF00781124 /* ContentView.swift */,
|
||||||
|
A90D49572DDE2DBD00781124 /* EventListView.swift */,
|
||||||
|
A90D494E2DDE2C8500781124 /* EditEventView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -161,11 +178,19 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
|
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
|
||||||
|
A90D49512DDE2D0000781124 /* Extensions.swift */,
|
||||||
A90D49202DDE0A3B00781124 /* Model */,
|
A90D49202DDE0A3B00781124 /* Model */,
|
||||||
);
|
);
|
||||||
path = Shared;
|
path = Shared;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A90D49542DDE2D5800781124 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A920C27B2D24011300E4F9B1 = {
|
A920C27B2D24011300E4F9B1 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -176,6 +201,7 @@
|
|||||||
A90D49362DDE0FAF00781124 /* MacNearFuture */,
|
A90D49362DDE0FAF00781124 /* MacNearFuture */,
|
||||||
A949F8002DCAA0340064DCA0 /* Resources */,
|
A949F8002DCAA0340064DCA0 /* Resources */,
|
||||||
A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
|
A979F6082D270AF00094C0B3 /* NearFutureWidgets */,
|
||||||
|
A90D49542DDE2D5800781124 /* Frameworks */,
|
||||||
A920C2852D24011400E4F9B1 /* Products */,
|
A920C2852D24011400E4F9B1 /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -185,7 +211,7 @@
|
|||||||
children = (
|
children = (
|
||||||
A920C2842D24011400E4F9B1 /* NearFuture.app */,
|
A920C2842D24011400E4F9B1 /* NearFuture.app */,
|
||||||
A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */,
|
A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */,
|
||||||
A90D49262DDE0FA400781124 /* MacNearFuture.app */,
|
A90D49262DDE0FA400781124 /* Near Future.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -240,9 +266,9 @@
|
|||||||
A949F8422DCAABE00064DCA0 /* Home */ = {
|
A949F8422DCAABE00064DCA0 /* Home */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A914FA4A2DD26C0F00856265 /* HomeView.swift */,
|
||||||
A949F8402DCAABE00064DCA0 /* EventListView.swift */,
|
A949F8402DCAABE00064DCA0 /* EventListView.swift */,
|
||||||
A949F8412DCAABE00064DCA0 /* HelpView.swift */,
|
A949F8412DCAABE00064DCA0 /* HelpView.swift */,
|
||||||
A914FA4A2DD26C0F00856265 /* HomeView.swift */,
|
|
||||||
);
|
);
|
||||||
path = Home;
|
path = Home;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -315,12 +341,14 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
A90D494D2DDE2C6000781124 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = MacNearFuture;
|
name = MacNearFuture;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
A90D49552DDE2D5800781124 /* SFSymbolsPicker */,
|
||||||
);
|
);
|
||||||
productName = MacNearFuture;
|
productName = MacNearFuture;
|
||||||
productReference = A90D49262DDE0FA400781124 /* MacNearFuture.app */;
|
productReference = A90D49262DDE0FA400781124 /* Near Future.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
A920C2832D24011300E4F9B1 /* NearFuture */ = {
|
A920C2832D24011300E4F9B1 /* NearFuture */ = {
|
||||||
@@ -444,9 +472,13 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A90D49402DDE111400781124 /* NearFutureApp.swift in Sources */,
|
A90D494B2DDE2C2900781124 /* AddEventView.swift in Sources */,
|
||||||
|
A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */,
|
||||||
|
A90D49522DDE2D0000781124 /* Extensions.swift in Sources */,
|
||||||
A90D49422DDE114100781124 /* Event.swift in Sources */,
|
A90D49422DDE114100781124 /* Event.swift in Sources */,
|
||||||
A90D49382DDE0FAF00781124 /* ContentView.swift in Sources */,
|
A90D49382DDE0FAF00781124 /* ContentView.swift in Sources */,
|
||||||
|
A90D494F2DDE2C8500781124 /* EditEventView.swift in Sources */,
|
||||||
|
A90D49582DDE2DBD00781124 /* EventListView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -459,14 +491,18 @@
|
|||||||
A914FA4F2DD276D200856265 /* AboutView.swift in Sources */,
|
A914FA4F2DD276D200856265 /* AboutView.swift in Sources */,
|
||||||
A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */,
|
A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */,
|
||||||
A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */,
|
A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */,
|
||||||
|
A90D49502DDE2C8500781124 /* EditEventView.swift in Sources */,
|
||||||
A949F84D2DCAABE00064DCA0 /* EditEventView.swift in Sources */,
|
A949F84D2DCAABE00064DCA0 /* EditEventView.swift in Sources */,
|
||||||
A949F84E2DCAABE00064DCA0 /* ContentView.swift in Sources */,
|
A949F84E2DCAABE00064DCA0 /* ContentView.swift in Sources */,
|
||||||
|
A90D495C2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */,
|
||||||
A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */,
|
A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */,
|
||||||
A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */,
|
A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */,
|
||||||
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 */,
|
||||||
|
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
|
||||||
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
|
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
|
||||||
|
A90D49592DDE2DBD00781124 /* EventListView.swift in Sources */,
|
||||||
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
|
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
|
||||||
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
|
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
|
||||||
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */,
|
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */,
|
||||||
@@ -488,6 +524,10 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
A90D494D2DDE2C6000781124 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
productRef = A90D494C2DDE2C6000781124 /* SFSymbolsPicker */;
|
||||||
|
};
|
||||||
A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = {
|
A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
platformFilter = ios;
|
platformFilter = ios;
|
||||||
@@ -520,7 +560,7 @@
|
|||||||
MACOSX_DEPLOYMENT_TARGET = 15.5;
|
MACOSX_DEPLOYMENT_TARGET = 15.5;
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture;
|
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "Near Future";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -551,7 +591,7 @@
|
|||||||
MACOSX_DEPLOYMENT_TARGET = 15.5;
|
MACOSX_DEPLOYMENT_TARGET = 15.5;
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture;
|
PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "Near Future";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -920,6 +960,16 @@
|
|||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
A90D494C2DDE2C6000781124 /* SFSymbolsPicker */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
|
||||||
|
productName = SFSymbolsPicker;
|
||||||
|
};
|
||||||
|
A90D49552DDE2D5800781124 /* SFSymbolsPicker */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
|
||||||
|
productName = SFSymbolsPicker;
|
||||||
|
};
|
||||||
A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */ = {
|
A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
|
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
|
||||||
|
|||||||
@@ -64,33 +64,3 @@ struct ContentView: View {
|
|||||||
settingsModel: dummySettingsViewModel()
|
settingsModel: dummySettingsViewModel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
|
||||||
var backgroundGradient: some View {
|
|
||||||
return LinearGradient(
|
|
||||||
gradient: Gradient(colors: [.bgTop, .two]),
|
|
||||||
startPoint: .top,
|
|
||||||
endPoint: .bottom
|
|
||||||
)
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
}
|
|
||||||
|
|
||||||
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AnyTransition {
|
|
||||||
static var moveAndFade: AnyTransition {
|
|
||||||
.asymmetric(
|
|
||||||
insertion: .move(edge: .leading),
|
|
||||||
removal: .move(edge: .trailing)
|
|
||||||
)
|
|
||||||
.combined(with: .opacity)
|
|
||||||
}
|
|
||||||
static var moveAndFadeReversed: AnyTransition {
|
|
||||||
.asymmetric(
|
|
||||||
insertion: .move(edge: .trailing),
|
|
||||||
removal: .move(edge: .leading)
|
|
||||||
)
|
|
||||||
.combined(with: .opacity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ struct AddEventView: View {
|
|||||||
|
|
||||||
@State var adding: Bool
|
@State var adding: Bool
|
||||||
@State var showNeedsNameAlert: Bool = false
|
@State var showNeedsNameAlert: Bool = false
|
||||||
@State var isSymbolPickerPresented = false
|
@State var isSymbolPickerPresented: Bool = false
|
||||||
|
|
||||||
@State private var bye: Bool = false
|
@State private var bye: Bool = false
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ struct AddEventView: View {
|
|||||||
.onSubmit {
|
.onSubmit {
|
||||||
focusedField = .Notes
|
focusedField = .Notes
|
||||||
}
|
}
|
||||||
MagicClearButton(text: $eventName)
|
// MagicClearButton(text: $eventName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ struct AddEventView: View {
|
|||||||
.onSubmit {
|
.onSubmit {
|
||||||
focusedField = nil
|
focusedField = nil
|
||||||
}
|
}
|
||||||
MagicClearButton(text: $eventNotes)
|
// MagicClearButton(text: $eventNotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ struct AddEventView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
DatePicker("", selection: $eventDate, displayedComponents: .date)
|
DatePicker("", selection: $eventDate, displayedComponents: .date)
|
||||||
.datePickerStyle(WheelDatePickerStyle())
|
// .datePickerStyle(datepickersty)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button() {
|
Button() {
|
||||||
eventDate = Date()
|
eventDate = Date()
|
||||||
@@ -143,9 +143,9 @@ struct AddEventView: View {
|
|||||||
}
|
}
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.navigationTitle("\(adding ? "Add Event" : "")")
|
.navigationTitle("\(adding ? "Add Event" : "")")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
// .navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(/*placement: .topBarLeading*/) {
|
||||||
if adding {
|
if adding {
|
||||||
Button() {
|
Button() {
|
||||||
resetAddEventView()
|
resetAddEventView()
|
||||||
@@ -159,7 +159,7 @@ struct AddEventView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem/*(placement: .topBarTrailing)*/ {
|
||||||
if adding {
|
if adding {
|
||||||
Button {
|
Button {
|
||||||
viewModel.addEvent(
|
viewModel.addEvent(
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import SwiftUI;import AppIntents
|
|||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
@ObservedObject var viewModel: EventViewModel
|
@ObservedObject var viewModel: EventViewModel
|
||||||
@ObservedObject var settingsModel: SettingsViewModel
|
@ObservedObject var settingsModel: SettingsViewModel
|
||||||
@State private var eventName = ""
|
@State private var eventName: String = ""
|
||||||
@State private var eventComplete = false
|
@State private var eventComplete: Bool = false
|
||||||
@State private var eventCompleteDesc = ""
|
@State private var eventCompleteDesc: String = ""
|
||||||
@State private var eventSymbol = "star"
|
@State private var eventSymbol: String = "star"
|
||||||
@State private var eventColor: Color = randomColor()
|
@State private var eventColor: Color = randomColor()
|
||||||
@State private var eventNotes = ""
|
@State private var eventNotes: String = ""
|
||||||
@State private var eventDate = Date()
|
@State private var eventDate = Date()
|
||||||
@State private var eventRecurrence: Event.RecurrenceType = .none
|
@State private var eventRecurrence: Event.RecurrenceType = .none
|
||||||
@State private var showingAddEventView = false
|
@State private var showingAddEventView: Bool = false
|
||||||
@State private var searchInput: String = ""
|
@State private var searchInput: String = ""
|
||||||
@Environment(\.colorScheme) var appearance
|
@Environment(\.colorScheme) var appearance
|
||||||
var darkMode: Bool {
|
var darkMode: Bool {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct WhatsNewView: View {
|
|||||||
var title: String
|
var title: String
|
||||||
var subtitle: String
|
var subtitle: String
|
||||||
}
|
}
|
||||||
@State var bye = false
|
@State var bye: Bool = false
|
||||||
var whatsNewChunks: [WhatsNewChunk] {
|
var whatsNewChunks: [WhatsNewChunk] {
|
||||||
return [
|
return [
|
||||||
WhatsNewChunk(
|
WhatsNewChunk(
|
||||||
|
|||||||
39
Shared/Extensions.swift
Normal file
39
Shared/Extensions.swift
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// Extension.swift
|
||||||
|
// NearFuture
|
||||||
|
//
|
||||||
|
// Created by neon443 on 21/05/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
var backgroundGradient: some View {
|
||||||
|
return LinearGradient(
|
||||||
|
gradient: Gradient(colors: [.bgTop, .two]),
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
.ignoresSafeArea(.all)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyTransition {
|
||||||
|
static var moveAndFade: AnyTransition {
|
||||||
|
.asymmetric(
|
||||||
|
insertion: .move(edge: .leading),
|
||||||
|
removal: .move(edge: .trailing)
|
||||||
|
)
|
||||||
|
.combined(with: .opacity)
|
||||||
|
}
|
||||||
|
static var moveAndFadeReversed: AnyTransition {
|
||||||
|
.asymmetric(
|
||||||
|
insertion: .move(edge: .trailing),
|
||||||
|
removal: .move(edge: .leading)
|
||||||
|
)
|
||||||
|
.combined(with: .opacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,18 +10,6 @@ import SwiftData
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct NearFutureApp: App {
|
struct NearFutureApp: App {
|
||||||
// var sharedModelContainer: ModelContainer = {
|
|
||||||
// let schema = Schema([
|
|
||||||
// Item.self,
|
|
||||||
// ])
|
|
||||||
// let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
|
|
||||||
//
|
|
||||||
// do {
|
|
||||||
// return try ModelContainer(for: schema, configurations: [modelConfiguration])
|
|
||||||
// } catch {
|
|
||||||
// fatalError("Could not create ModelContainer: \(error)")
|
|
||||||
// }
|
|
||||||
// }()
|
|
||||||
@StateObject var settingsModel: SettingsViewModel = SettingsViewModel()
|
@StateObject var settingsModel: SettingsViewModel = SettingsViewModel()
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
@@ -31,6 +19,5 @@ struct NearFutureApp: App {
|
|||||||
)
|
)
|
||||||
.tint(settingsModel.settings.tint.color)
|
.tint(settingsModel.settings.tint.color)
|
||||||
}
|
}
|
||||||
// .modelContainer(sharedModelContainer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user