PulsingRecordButton.swift 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import SwiftUI
  2. // MARK: - PulsingRecordButton
  3. /// A minimalist, line-drawn record button suitable for a business productivity app.
  4. /// Completely avoids heavy neon glows, replacing them with thin outlines, a clean scale animation,
  5. /// and an outline mic icon.
  6. struct PulsingRecordButton: View {
  7. /// Closure triggered on tap.
  8. let action: () -> Void
  9. /// Controls the subtle pulse animation.
  10. @State private var isAnimating: Bool = false
  11. var body: some View {
  12. ZStack {
  13. // Precise outer thin outline (pulses slightly)
  14. Circle()
  15. .stroke(Color.primary.opacity(0.1), lineWidth: 1)
  16. .frame(width: 120, height: 120)
  17. .scaleEffect(isAnimating ? 1.08 : 0.98)
  18. .animation(
  19. .easeInOut(duration: 2.0).repeatForever(autoreverses: true),
  20. value: isAnimating
  21. )
  22. // Middle thin outline (expanding and fading pulse)
  23. Circle()
  24. .stroke(Color.primary.opacity(0.08), lineWidth: 1)
  25. .frame(width: 100, height: 100)
  26. .scaleEffect(isAnimating ? 1.25 : 1.0)
  27. .opacity(isAnimating ? 0.0 : 0.8)
  28. .animation(
  29. .easeOut(duration: 2.5).repeatForever(autoreverses: false),
  30. value: isAnimating
  31. )
  32. // Inner button body - pure line circle with translucent background
  33. Circle()
  34. .fill(Color.primary.opacity(0.03))
  35. .frame(width: 90, height: 90)
  36. .overlay(
  37. Circle()
  38. .stroke(Color.primary.opacity(0.2), lineWidth: 1)
  39. )
  40. // Inner core indicator - simple line microphone icon
  41. Image(systemName: "mic")
  42. .font(.system(size: 24, weight: .light))
  43. .foregroundColor(.primary)
  44. .scaleEffect(isAnimating ? 1.02 : 0.98)
  45. .animation(
  46. .easeInOut(duration: 2.0).repeatForever(autoreverses: true),
  47. value: isAnimating
  48. )
  49. }
  50. .frame(width: 120, height: 120)
  51. .contentShape(Circle())
  52. .onTapGesture {
  53. HapticManager.trigger(.recordStart)
  54. action()
  55. }
  56. .onAppear {
  57. isAnimating = true
  58. }
  59. }
  60. }
  61. // MARK: - Preview
  62. #Preview {
  63. ZStack {
  64. Color.spaceBlack.ignoresSafeArea()
  65. PulsingRecordButton {
  66. print("Record tapped")
  67. }
  68. }
  69. }