mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 14:56:15 +00:00
135 lines
2.9 KiB
Swift
135 lines
2.9 KiB
Swift
//
|
|
// CompleteEventButton.swift
|
|
// NearFuture
|
|
//
|
|
// Created by neon443 on 15/06/2025.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct CompleteEventButton: View {
|
|
@ObservedObject var viewModel: EventViewModel
|
|
@Binding var event: Event
|
|
|
|
@State var timer: Timer?
|
|
@State var largeTick: Bool = false
|
|
@State var completeInProgress: Bool = false
|
|
@State var completeStartTime: Date = .now
|
|
@State var progress: Double = 0
|
|
private let completeDuration: TimeInterval = 3.0
|
|
|
|
var isMac: Bool {
|
|
#if canImport(AppKit)
|
|
return true
|
|
#else
|
|
return false
|
|
#endif
|
|
}
|
|
|
|
func startCompleting() {
|
|
#if canImport(UIKit)
|
|
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
|
#endif
|
|
withAnimation { completeInProgress = true }
|
|
completeStartTime = .now
|
|
progress = 0
|
|
|
|
timer = Timer(timeInterval: 0.02, repeats: true) { timer in
|
|
guard completeInProgress else { return }
|
|
guard timer.isValid else { return }
|
|
let elapsed = Date().timeIntervalSince(completeStartTime)
|
|
progress = min(1, elapsed)
|
|
#if canImport(UIKit)
|
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
#endif
|
|
|
|
if progress >= 1 {
|
|
withAnimation { completeInProgress = false }
|
|
viewModel.completeEvent(&event)
|
|
#if canImport(UIKit)
|
|
DispatchQueue.main.asyncAfter(deadline: .now()+0.02) {
|
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
|
}
|
|
#endif
|
|
timer.invalidate()
|
|
progress = 0
|
|
}
|
|
}
|
|
RunLoop.main.add(timer!, forMode: .common)
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
if completeInProgress {
|
|
ZStack {
|
|
CircularProgressView(progress: $progress)
|
|
Image(systemName: "xmark")
|
|
.bold()
|
|
}
|
|
.onTapGesture {
|
|
withAnimation { completeInProgress = false }
|
|
}
|
|
} 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: isMac ? 20 : 30)
|
|
.shadow(color: .one.opacity(0.2), radius: 2.5)
|
|
.padding(.trailing, isMac ? 15 : 5)
|
|
.transition(.scale)
|
|
.animation(.spring, value: completeInProgress)
|
|
.animation(
|
|
.spring(response: 0.2, dampingFraction: 0.75, blendDuration: 2),
|
|
value: largeTick
|
|
)
|
|
}
|
|
}
|
|
|
|
struct CircularProgressView: View {
|
|
@Binding var progress: Double
|
|
var body: some View {
|
|
ZStack {
|
|
Circle()
|
|
.stroke(
|
|
.two,
|
|
lineWidth: 5
|
|
)
|
|
Circle()
|
|
.trim(from: 0, to: progress)
|
|
.stroke(
|
|
.one,
|
|
lineWidth: 5
|
|
// style: StrokeStyle(
|
|
// lineWidth: 5,
|
|
// lineCap: .round
|
|
// )
|
|
)
|
|
.rotationEffect(.degrees(-90))
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
CompleteEventButton(
|
|
viewModel: dummyEventViewModel(),
|
|
event: .constant(dummyEventViewModel().example)
|
|
)
|
|
.scaleEffect(5)
|
|
}
|
|
|
|
#Preview {
|
|
CircularProgressView(progress: .constant(0.5))
|
|
}
|