From 5b743c14be55a3be2806632de2762d691ec7ffbb Mon Sep 17 00:00:00 2001 From: Tim Xie Date: Thu, 28 Nov 2024 00:51:50 -0800 Subject: [PATCH] clean confetti, add haptic --- CubeTime.xcodeproj/project.pbxproj | 40 +-------- .../StopwatchManager/StopwatchManager.swift | 6 ++ .../ConfettiSwiftUI.swift => Confetti.swift} | 86 ++++++++++--------- .../Timer/Confetti/Shapes/RoundedCross.swift | 34 -------- .../Timer/Confetti/Shapes/SlimRectangle.swift | 27 ------ CubeTime/Timer/Confetti/Shapes/Triangle.swift | 27 ------ .../Timer/Confetti/View+ConfettiCannon.swift | 83 ------------------ 7 files changed, 55 insertions(+), 248 deletions(-) rename CubeTime/Timer/{Confetti/ConfettiSwiftUI.swift => Confetti.swift} (84%) delete mode 100644 CubeTime/Timer/Confetti/Shapes/RoundedCross.swift delete mode 100644 CubeTime/Timer/Confetti/Shapes/SlimRectangle.swift delete mode 100644 CubeTime/Timer/Confetti/Shapes/Triangle.swift delete mode 100644 CubeTime/Timer/Confetti/View+ConfettiCannon.swift diff --git a/CubeTime.xcodeproj/project.pbxproj b/CubeTime.xcodeproj/project.pbxproj index 4128d2e..a1fbf8b 100644 --- a/CubeTime.xcodeproj/project.pbxproj +++ b/CubeTime.xcodeproj/project.pbxproj @@ -29,11 +29,7 @@ 238FB42029B8175C008EC1D4 /* GradientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238FB41F29B8175C008EC1D4 /* GradientManager.swift */; }; 239560A629C3056E00735035 /* SplashPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 239560A529C3056E00735035 /* SplashPad.storyboard */; }; 2397131F2791032300268DFA /* AveragePhases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2397131E2791032300268DFA /* AveragePhases.swift */; }; - 239AC6982CF853340061DD1D /* Triangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC6952CF853340061DD1D /* Triangle.swift */; }; - 239AC6992CF853340061DD1D /* SlimRectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC6942CF853340061DD1D /* SlimRectangle.swift */; }; - 239AC69A2CF853340061DD1D /* ConfettiSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC6922CF853340061DD1D /* ConfettiSwiftUI.swift */; }; - 239AC69B2CF853340061DD1D /* RoundedCross.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC6932CF853340061DD1D /* RoundedCross.swift */; }; - 239AC69C2CF853340061DD1D /* View+ConfettiCannon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC6972CF853340061DD1D /* View+ConfettiCannon.swift */; }; + 239AC69A2CF853340061DD1D /* Confetti.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC6922CF853340061DD1D /* Confetti.swift */; }; 239AC6A02CF85EF50061DD1D /* TimeCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239AC69F2CF85EE80061DD1D /* TimeCardView.swift */; }; 23A1CDBD29399FE000F0895D /* SwiftfulLoadingIndicators in Frameworks */ = {isa = PBXBuildFile; productRef = 23A1CDBC29399FE000F0895D /* SwiftfulLoadingIndicators */; }; 23AAF99629B0219200C5C1FF /* TimeTrendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AAF99529B0219200C5C1FF /* TimeTrendViewController.swift */; }; @@ -155,11 +151,7 @@ 238FB41F29B8175C008EC1D4 /* GradientManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientManager.swift; sourceTree = ""; }; 239560A529C3056E00735035 /* SplashPad.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SplashPad.storyboard; sourceTree = ""; }; 2397131E2791032300268DFA /* AveragePhases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AveragePhases.swift; sourceTree = ""; }; - 239AC6922CF853340061DD1D /* ConfettiSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfettiSwiftUI.swift; sourceTree = ""; }; - 239AC6932CF853340061DD1D /* RoundedCross.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedCross.swift; sourceTree = ""; }; - 239AC6942CF853340061DD1D /* SlimRectangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlimRectangle.swift; sourceTree = ""; }; - 239AC6952CF853340061DD1D /* Triangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Triangle.swift; sourceTree = ""; }; - 239AC6972CF853340061DD1D /* View+ConfettiCannon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ConfettiCannon.swift"; sourceTree = ""; }; + 239AC6922CF853340061DD1D /* Confetti.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Confetti.swift; sourceTree = ""; }; 239AC69F2CF85EE80061DD1D /* TimeCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeCardView.swift; sourceTree = ""; }; 23AAF99529B0219200C5C1FF /* TimeTrendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTrendViewController.swift; sourceTree = ""; }; 23AAF99729B072F900C5C1FF /* StatsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsDetailView.swift; sourceTree = ""; }; @@ -291,26 +283,6 @@ path = Helper; sourceTree = ""; }; - 239AC6912CF853270061DD1D /* Confetti */ = { - isa = PBXGroup; - children = ( - 239AC6922CF853340061DD1D /* ConfettiSwiftUI.swift */, - 239AC6962CF853340061DD1D /* Shapes */, - 239AC6972CF853340061DD1D /* View+ConfettiCannon.swift */, - ); - path = Confetti; - sourceTree = ""; - }; - 239AC6962CF853340061DD1D /* Shapes */ = { - isa = PBXGroup; - children = ( - 239AC6932CF853340061DD1D /* RoundedCross.swift */, - 239AC6942CF853340061DD1D /* SlimRectangle.swift */, - 239AC6952CF853340061DD1D /* Triangle.swift */, - ); - path = Shapes; - sourceTree = ""; - }; 239AC69D2CF85EBC0061DD1D /* TimeListViewInner */ = { isa = PBXGroup; children = ( @@ -323,7 +295,6 @@ 23A73F5C276B0AA100153748 /* Timer */ = { isa = PBXGroup; children = ( - 239AC6912CF853270061DD1D /* Confetti */, 23DE3BDC274DDBE200254D29 /* TimerView.swift */, 23B7AC3929BAB2E300B5C9B4 /* TimerTools.swift */, 2326435F28C4927600F6322E /* TimerHeader.swift */, @@ -331,6 +302,7 @@ 7D3D3153278D3CA400BBDF55 /* TimerTouchView.swift */, AF6EAC1F29ADDA1C00171F73 /* TimerMenu.swift */, 7D7E7C62279A69F0004C9A4E /* ScrambleView.swift */, + 239AC6922CF853340061DD1D /* Confetti.swift */, ); path = Timer; sourceTree = ""; @@ -746,11 +718,7 @@ 23B7AC3C29BB049F00B5C9B4 /* CalculatorTool.swift in Sources */, 23CB6D0E29AC3DFB00C60EF8 /* TimeDetailView.swift in Sources */, 231B2E2E29BDECF9007E5DC2 /* HelperButtons.swift in Sources */, - 239AC6982CF853340061DD1D /* Triangle.swift in Sources */, - 239AC6992CF853340061DD1D /* SlimRectangle.swift in Sources */, - 239AC69A2CF853340061DD1D /* ConfettiSwiftUI.swift in Sources */, - 239AC69B2CF853340061DD1D /* RoundedCross.swift in Sources */, - 239AC69C2CF853340061DD1D /* View+ConfettiCannon.swift in Sources */, + 239AC69A2CF853340061DD1D /* Confetti.swift in Sources */, 23DE3BDB274DDBE200254D29 /* CubeTimeApp.swift in Sources */, 23D7722429B1E3540044AFBA /* NewSessionView.swift in Sources */, 7D7DDF2327955AFF00362DE9 /* PenButton.swift in Sources */, diff --git a/CubeTime/StopwatchManager/StopwatchManager.swift b/CubeTime/StopwatchManager/StopwatchManager.swift index b0c8abe..d9d056b 100644 --- a/CubeTime/StopwatchManager/StopwatchManager.swift +++ b/CubeTime/StopwatchManager/StopwatchManager.swift @@ -403,6 +403,12 @@ class StopwatchManager: ObservableObject { if let best = self.bestSingle { if secondsElapsed == best.timeIncPen { self.confetti += 1 + + if let loc = self.confettiLocation, #available(iOS 17.5, *) { + UINotificationFeedbackGenerator().notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success, at: loc) + } else { + UINotificationFeedbackGenerator().notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success) + } } } } diff --git a/CubeTime/Timer/Confetti/ConfettiSwiftUI.swift b/CubeTime/Timer/Confetti.swift similarity index 84% rename from CubeTime/Timer/Confetti/ConfettiSwiftUI.swift rename to CubeTime/Timer/Confetti.swift index 0a27ef7..bbb59b3 100644 --- a/CubeTime/Timer/Confetti/ConfettiSwiftUI.swift +++ b/CubeTime/Timer/Confetti.swift @@ -3,35 +3,18 @@ // Confetti // // Created by Simon Bachmann on 24.11.20. +// Created by Abdullah Alhaider on 24/03/2022. // import SwiftUI -public enum ConfettiType:CaseIterable, Hashable { - - public enum Shape { - case circle - case triangle - case square - case slimRectangle - case roundedCross - } - - case shape(Shape) +public enum ConfettiType: Hashable { case text(String) case sfSymbol(symbolName: String) case image(String) - public var view:AnyView{ + public var view: AnyView { switch self { - case .shape(.square): - return AnyView(Rectangle()) - case .shape(.triangle): - return AnyView(Triangle()) - case .shape(.slimRectangle): - return AnyView(SlimRectangle()) - case .shape(.roundedCross): - return AnyView(RoundedCross()) case let .text(text): return AnyView(Text(text)) case .sfSymbol(let symbolName): @@ -42,10 +25,6 @@ public enum ConfettiType:CaseIterable, Hashable { return AnyView(Circle()) } } - - public static var allCases: [ConfettiType] { - return [.shape(.circle), .shape(.triangle), .shape(.square), .shape(.slimRectangle), .shape(.roundedCross)] - } } @available(iOS 14.0, macOS 11.0, watchOS 7, tvOS 14.0, *) @@ -58,23 +37,9 @@ public struct ConfettiCannon: View { @State var firstAppear = false @State var error = "" - /// renders configurable confetti animaiton - /// - Parameters: - /// - counter: on any change of this variable the animation is run - /// - num: amount of confettis - /// - colors: list of colors that is applied to the default shapes - /// - confettiSize: size that confettis and emojis are scaled to - /// - rainHeight: vertical distance that confettis pass - /// - fadesOut: reduce opacity towards the end of the animation - /// - opacity: maximum opacity that is reached during the animation - /// - openingAngle: boundary that defines the opening angle in degrees - /// - closingAngle: boundary that defines the closing angle in degrees - /// - radius: explosion radius - /// - repetitions: number of repetitions of the explosion - /// - repetitionInterval: duration between the repetitions public init(counter:Binding, num:Int = 20, - confettis:[ConfettiType] = ConfettiType.allCases, + confettis:[ConfettiType], colors:[Color] = [.blue, .red, .green, .yellow, .pink, .purple, .orange], confettiSize:CGFloat = 10.0, rainHeight: CGFloat = 600.0, @@ -92,8 +57,6 @@ public struct ConfettiCannon: View { for confetti in confettis{ for color in colors{ switch confetti { - case .shape(_): - shapes.append(AnyView(confetti.view.foregroundColor(color).frame(width: confettiSize, height: confettiSize, alignment: .center))) case .image(_): shapes.append(AnyView(confetti.view.frame(maxWidth:confettiSize, maxHeight: confettiSize))) default: @@ -319,3 +282,44 @@ class ConfettiConfig: ObservableObject { return CGFloat(closingAngle.degrees) * 180 / .pi } } + + +public extension View { + @ViewBuilder func confettiCannon( + counter: Binding, + num: Int = 20, + confettis: [ConfettiType], + colors: [Color] = [.blue, .red, .green, .yellow, .pink, .purple, .orange], + confettiSize: CGFloat = 10.0, + rainHeight: CGFloat = 600.0, + fadesOut: Bool = true, + opacity: Double = 1.0, + openingAngle: Angle = .degrees(60), + closingAngle: Angle = .degrees(120), + radius: CGFloat = 300, + repetitions: Int = 0, + repetitionInterval: Double = 1.0, + position: CGPoint = .zero + ) -> some View { + ZStack { + self + + ConfettiCannon( + counter: counter, + num: num, + confettis: confettis, + colors: colors, + confettiSize: confettiSize, + rainHeight: rainHeight, + fadesOut: fadesOut, + opacity: opacity, + openingAngle: openingAngle, + closingAngle: closingAngle, + radius: radius, + repetitions: repetitions, + repetitionInterval: repetitionInterval + ) + .position(position) + } + } +} diff --git a/CubeTime/Timer/Confetti/Shapes/RoundedCross.swift b/CubeTime/Timer/Confetti/Shapes/RoundedCross.swift deleted file mode 100644 index a4efe7c..0000000 --- a/CubeTime/Timer/Confetti/Shapes/RoundedCross.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// RoundedCross.swift -// Confetti -// -// Created by Simon Bachmann on 04.12.20. -// - -import SwiftUI - -public struct RoundedCross: Shape { - public func path(in rect: CGRect) -> Path { - var path = Path() - - path.move(to: CGPoint(x: rect.minX, y: rect.maxY/3)) - path.addQuadCurve(to: CGPoint(x: rect.maxX/3, y: rect.minY), control: CGPoint(x: rect.maxX/3, y: rect.maxY/3)) - path.addLine(to: CGPoint(x: 2*rect.maxX/3, y: rect.minY)) - - path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.maxY/3), control: CGPoint(x: 2*rect.maxX/3, y: rect.maxY/3)) - path.addLine(to: CGPoint(x: rect.maxX, y: 2*rect.maxY/3)) - - path.addQuadCurve(to: CGPoint(x: 2*rect.maxX/3, y: rect.maxY), control: CGPoint(x: 2*rect.maxX/3, y: 2*rect.maxY/3)) - path.addLine(to: CGPoint(x: rect.maxX/3, y: rect.maxY)) - - path.addQuadCurve(to: CGPoint(x: 2*rect.minX/3, y: 2*rect.maxY/3), control: CGPoint(x: rect.maxX/3, y: 2*rect.maxY/3)) - - return path - } -} - -struct RoundedCross_Previews: PreviewProvider { - static var previews: some View { - RoundedCross() - } -} diff --git a/CubeTime/Timer/Confetti/Shapes/SlimRectangle.swift b/CubeTime/Timer/Confetti/Shapes/SlimRectangle.swift deleted file mode 100644 index b442305..0000000 --- a/CubeTime/Timer/Confetti/Shapes/SlimRectangle.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SlimRectangle.swift -// Confetti -// -// Created by Simon Bachmann on 04.12.20. -// - -import SwiftUI - -public struct SlimRectangle: Shape { - public func path(in rect: CGRect) -> Path { - var path = Path() - - path.move(to: CGPoint(x: rect.minX, y: 4*rect.maxY/5)) - path.addLine(to: CGPoint(x: rect.maxX, y: 4*rect.maxY/5)) - path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) - - return path - } -} - -struct SlimRectangle_Previews: PreviewProvider { - static var previews: some View { - SlimRectangle() - } -} diff --git a/CubeTime/Timer/Confetti/Shapes/Triangle.swift b/CubeTime/Timer/Confetti/Shapes/Triangle.swift deleted file mode 100644 index f9ad065..0000000 --- a/CubeTime/Timer/Confetti/Shapes/Triangle.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Triangle.swift -// Confetti -// -// Created by Simon Bachmann on 04.12.20. -// - -import SwiftUI - -public struct Triangle: Shape { - public func path(in rect: CGRect) -> Path { - var path = Path() - - path.move(to: CGPoint(x: rect.midX, y: rect.minY)) - path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.midX, y: rect.minY)) - - return path - } -} - -struct Triangle_Previews: PreviewProvider { - static var previews: some View { - Triangle() - } -} diff --git a/CubeTime/Timer/Confetti/View+ConfettiCannon.swift b/CubeTime/Timer/Confetti/View+ConfettiCannon.swift deleted file mode 100644 index 9cb609e..0000000 --- a/CubeTime/Timer/Confetti/View+ConfettiCannon.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// View+ConfettiCannon.swift -// -// -// Created by Abdullah Alhaider on 24/03/2022. -// - -import SwiftUI - -public extension View { - - /// renders configurable confetti animaiton - /// - /// - Usage: - /// - /// ``` - /// import SwiftUI - /// - /// struct ContentView: View { - /// - /// @State private var counter: Int = 0 - /// - /// var body: some View { - /// Button("Wow") { - /// counter += 1 - /// } - /// .confettiCannon(counter: $counter) - /// } - /// } - /// ``` - /// - /// - Parameters: - /// - counter: on any change of this variable the animation is run - /// - num: amount of confettis - /// - colors: list of colors that is applied to the default shapes - /// - confettiSize: size that confettis and emojis are scaled to - /// - rainHeight: vertical distance that confettis pass - /// - fadesOut: reduce opacity towards the end of the animation - /// - opacity: maximum opacity that is reached during the animation - /// - openingAngle: boundary that defines the opening angle in degrees - /// - closingAngle: boundary that defines the closing angle in degrees - /// - radius: explosion radius - /// - repetitions: number of repetitions of the explosion - /// - repetitionInterval: duration between the repetitions - /// - @ViewBuilder func confettiCannon( - counter: Binding, - num: Int = 20, - confettis: [ConfettiType] = ConfettiType.allCases, - colors: [Color] = [.blue, .red, .green, .yellow, .pink, .purple, .orange], - confettiSize: CGFloat = 10.0, - rainHeight: CGFloat = 600.0, - fadesOut: Bool = true, - opacity: Double = 1.0, - openingAngle: Angle = .degrees(60), - closingAngle: Angle = .degrees(120), - radius: CGFloat = 300, - repetitions: Int = 0, - repetitionInterval: Double = 1.0, - position: CGPoint = .zero - ) -> some View { - ZStack { - self - - ConfettiCannon( - counter: counter, - num: num, - confettis: confettis, - colors: colors, - confettiSize: confettiSize, - rainHeight: rainHeight, - fadesOut: fadesOut, - opacity: opacity, - openingAngle: openingAngle, - closingAngle: closingAngle, - radius: radius, - repetitions: repetitions, - repetitionInterval: repetitionInterval - ) - .position(position) - } - } -}