mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
locked the freak in for this one -
can cancel events being completed!!!!! added a cancel to symbolpicker and made it dip after choosing extracted completevent into viewmodel eventlistview is pretty full rn -- need to cleana added color picker ot addeventview wtf why wasnt it there
This commit is contained in:
@@ -49,6 +49,7 @@ struct NearFutureApp: App {
|
||||
)
|
||||
)
|
||||
}
|
||||
.windowIdealSize(.fitToContent)
|
||||
.restorationBehavior(.disabled)
|
||||
|
||||
Window("About Near Future", id: "about") {
|
||||
|
||||
@@ -14,8 +14,31 @@ struct EventListView: View {
|
||||
@State var largeTick: Bool = false
|
||||
@State var hovering: Bool = false
|
||||
|
||||
@State var completeInProgress: Bool = false
|
||||
@State var completeStartTime: Date = .now
|
||||
@State var progress: Double = 0
|
||||
@State var timer: Timer?
|
||||
private let completeDuration: TimeInterval = 3.0
|
||||
@Environment(\.openWindow) var openWindow
|
||||
|
||||
func startCompleting() {
|
||||
NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .now)
|
||||
completeInProgress = true
|
||||
progress = 0
|
||||
completeStartTime = .now
|
||||
|
||||
timer = Timer(timeInterval: 0.05, repeats: true) { timer in
|
||||
let elapsed = Date().timeIntervalSince(completeStartTime)
|
||||
progress = min(elapsed, 1.0)
|
||||
|
||||
if progress >= 1.0 {
|
||||
timer.invalidate()
|
||||
viewModel.completeEvent(&event)
|
||||
completeInProgress = false
|
||||
}
|
||||
}
|
||||
RunLoop.main.add(timer!, forMode: .common)
|
||||
}
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.opacity(hovering ? 0.5 : 0.0)
|
||||
@@ -75,33 +98,20 @@ struct EventListView: View {
|
||||
.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()
|
||||
}
|
||||
startCompleting()
|
||||
} label: {
|
||||
if event.complete {
|
||||
if completeInProgress {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundStyle(.green)
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.scaledToFit()
|
||||
ProgressView(value: progress)
|
||||
.progressViewStyle(.circular)
|
||||
Image(systemName: "xmark")
|
||||
.bold()
|
||||
.frame(width: 15)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(event.color.color)
|
||||
Image(systemName: event.complete ? "checkmark.circle.fill" : "circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(event.complete ? .green : event.color.color)
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
.onHover() { hovering in
|
||||
@@ -110,7 +120,10 @@ struct EventListView: View {
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.scaleEffect(largeTick ? 1.5 : 1)
|
||||
.scaleEffect(
|
||||
completeInProgress ? 1 :
|
||||
largeTick ? 1.5 : 1
|
||||
)
|
||||
.frame(maxWidth: 20)
|
||||
.shadow(radius: 5)
|
||||
.padding(.trailing, 15)
|
||||
|
||||
@@ -70,10 +70,10 @@
|
||||
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; };
|
||||
A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
A979F6182D2714310094C0B3 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; };
|
||||
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CA2DE730740008D61C /* EventListViewMac.swift */; };
|
||||
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; };
|
||||
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; };
|
||||
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; };
|
||||
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8402DCAABE00064DCA0 /* EventListView.swift */; };
|
||||
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -531,7 +531,6 @@
|
||||
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
|
||||
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
|
||||
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
|
||||
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */,
|
||||
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
|
||||
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
|
||||
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
|
||||
@@ -539,6 +538,7 @@
|
||||
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */,
|
||||
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */,
|
||||
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */,
|
||||
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */,
|
||||
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
|
||||
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
|
||||
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
|
||||
|
||||
@@ -25,6 +25,16 @@ struct AddEventView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var isMac: Bool {
|
||||
if #available(iOS 1, *) {
|
||||
return false
|
||||
} else if #available(macOS 10, *) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if !adding {
|
||||
@@ -75,6 +85,7 @@ struct AddEventView: View {
|
||||
}
|
||||
}
|
||||
|
||||
ColorPicker("Event Color", selection: $event.color.colorBind)
|
||||
|
||||
// date picker
|
||||
HStack {
|
||||
@@ -165,8 +176,9 @@ struct AddEventView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Editing \(event.name) - Ne")
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.scrollContentBackground(isMac ? .automatic : .hidden)
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,165 @@ struct EventListView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@State var event: Event
|
||||
|
||||
@State var completeInProgress: Bool = false
|
||||
@State var completeStartTime: Date = .now
|
||||
@State var progress: Double = 0
|
||||
@State var timer: Timer?
|
||||
private let completeDuration: TimeInterval = 3.0
|
||||
@Environment(\.openWindow) var openWindow
|
||||
|
||||
@State var largeTick: Bool = false
|
||||
@State var hovering: Bool = false
|
||||
|
||||
func startCompleting() {
|
||||
#if canImport(UIKit)
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
#endif
|
||||
completeInProgress = true
|
||||
progress = 0
|
||||
completeStartTime = .now
|
||||
|
||||
timer = Timer(timeInterval: 0.01, repeats: true) { timer in
|
||||
guard timer.isValid else { return }
|
||||
guard completeInProgress else { return }
|
||||
let elapsed = Date().timeIntervalSince(completeStartTime)
|
||||
progress = min(elapsed, 1.0)
|
||||
|
||||
if progress >= 1.0 {
|
||||
timer.invalidate()
|
||||
viewModel.completeEvent(&event)
|
||||
#if canImport(UIKit)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
#endif
|
||||
completeInProgress = false
|
||||
}
|
||||
}
|
||||
RunLoop.main.add(timer!, forMode: .common)
|
||||
}
|
||||
|
||||
#if canImport(AppKit)
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.opacity(hovering ? 0.5 : 0.0)
|
||||
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)")
|
||||
.bold()
|
||||
.foregroundStyle(.one)
|
||||
.strikethrough(event.complete)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
if !event.notes.isEmpty {
|
||||
Text(event.notes)
|
||||
.foregroundStyle(.one.opacity(0.8))
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
Text(
|
||||
event.date.formatted(
|
||||
date: .long,
|
||||
time: .shortened
|
||||
)
|
||||
)
|
||||
.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)")
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
|
||||
}
|
||||
|
||||
Group {
|
||||
if completeInProgress {
|
||||
ZStack {
|
||||
ProgressView(value: progress)
|
||||
.progressViewStyle(.circular)
|
||||
Image(systemName: "xmark")
|
||||
.bold()
|
||||
}
|
||||
.onTapGesture {
|
||||
timer?.invalidate()
|
||||
completeInProgress = false
|
||||
progress = 0
|
||||
}
|
||||
} else {
|
||||
Image(systemName: event.complete ? "checkmark.circle.fill" : "circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(event.complete ? .green : event.color.color)
|
||||
.bold()
|
||||
.onTapGesture {
|
||||
startCompleting()
|
||||
}
|
||||
.onHover() { hovering in
|
||||
withAnimation {
|
||||
largeTick.toggle()
|
||||
}
|
||||
}
|
||||
.scaleEffect(largeTick ? 1.5 : 1)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 20)
|
||||
.shadow(radius: 5)
|
||||
.padding(.trailing, 15)
|
||||
.animation(
|
||||
.spring(response: 0.2, dampingFraction: 0.75, blendDuration: 2),
|
||||
value: largeTick
|
||||
)
|
||||
}
|
||||
.transition(.opacity)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.onHover { isHovering in
|
||||
withAnimation {
|
||||
hovering.toggle()
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
openWindow(value: event.id)
|
||||
}
|
||||
.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")
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
var body: some View {
|
||||
NavigationLink() {
|
||||
EditEventView(
|
||||
@@ -79,33 +238,25 @@ struct EventListView: View {
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
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()
|
||||
}
|
||||
startCompleting()
|
||||
} label: {
|
||||
if event.complete {
|
||||
if completeInProgress {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundStyle(.green)
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.scaledToFit()
|
||||
ProgressView(value: progress)
|
||||
.progressViewStyle(.circular)
|
||||
Image(systemName: "xmark")
|
||||
.bold()
|
||||
.frame(width: 15)
|
||||
}
|
||||
.onTapGesture {
|
||||
timer?.invalidate()
|
||||
completeInProgress = false
|
||||
progress = 0
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(event.color.color)
|
||||
Image(systemName: event.complete ? "checkmark.circle.fill" : "circle")
|
||||
.resizable().scaledToFit()
|
||||
.foregroundStyle(event.complete ? .green : event.color.color)
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
@@ -143,6 +294,7 @@ struct EventListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#Preview("EventListView") {
|
||||
|
||||
@@ -222,6 +222,17 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
|
||||
saveEvents() //sync with icloud
|
||||
}
|
||||
|
||||
func completeEvent(_ event: inout Event) {
|
||||
withAnimation { event.complete.toggle() }
|
||||
let eventToModify = self.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
self.events[eventToModify] = event
|
||||
self.saveEvents()
|
||||
}
|
||||
}
|
||||
|
||||
func removeEvent(at index: IndexSet) {
|
||||
events.remove(atOffsets: index)
|
||||
saveEvents() //sync local and icl
|
||||
|
||||
@@ -14,6 +14,7 @@ struct SymbolsPicker: View {
|
||||
@FocusState var searchfocuesd: Bool
|
||||
|
||||
@State var searchInput: String = ""
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var symbols: [String] {
|
||||
return symbolsLoader.getSymbols(searchInput)
|
||||
@@ -45,6 +46,8 @@ struct SymbolsPicker: View {
|
||||
ForEach(symbols, id: \.self) { symbol in
|
||||
Button() {
|
||||
selection = symbol
|
||||
searchInput = ""
|
||||
dismiss()
|
||||
} label: {
|
||||
VStack {
|
||||
Image(systemName: symbol)
|
||||
@@ -62,7 +65,15 @@ struct SymbolsPicker: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchInput)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
searchInput = ""
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user