import Commanding import SwiftUI import WidgetKit /// The style of the action buttons of the recording Live Activity: it renders a button's label as a fixed-size icon on a circle /// colored by the ``ActionType`` given at initialization. /// /// The style is a regular ``ButtonStyle`` — not a ``PrimitiveButtonStyle`` — so that SwiftUI and WidgetKit keep handling the /// button's tap and fire its intent from the Live Activity; a primitive style would have to invoke the trigger itself, which arbitrary /// gestures cannot do inside a widget. struct ActionButtonStyle: ButtonStyle { // MARK: Properties /// The type of the action behind the styled button, deciding the color of the circular background. private let action: ActionType // MARK: Initializers /// Creates a style for a button behind the given type of action. /// /// - Parameter action: The type of the action behind the styled button. fileprivate init( _ action: ActionType ) { self.action = action } // MARK: Methods /// Builds the styled button: its label as a fixed-size, padded icon on a circle colored by the type of the action. /// /// - Parameter configuration: The properties of the button to style. /// - Returns: The styled button. func makeBody( configuration: Configuration ) -> some View { configuration.label .scaledToFit() .frame( width: Constant.Size.icon.width, height: Constant.Size.icon.height ) .foregroundStyle(.primary) .padding(Constant.Padding.icon) .background { Circle() .foregroundStyle(backgroundColor) } } } // MARK: - Helpers private extension ActionButtonStyle { /// The color of the circular background, matching the type of the action behind the button. var backgroundColor: Color { switch action { case .confirmative: .green case .custom(let color): color case .default: .yellow case .destructive: .red } } } // MARK: - Enumerations extension ActionButtonStyle { /// The types of action a styled button can stand behind, each coloring the button's background differently. enum ActionType { /// An action that confirms or submits, colored green. case confirmative /// An action colored by the given custom color. case custom(_ color: Color) /// A regular action, colored yellow. case `default` /// An action that throws something away, colored red. case destructive } } // MARK: - Constants /// The constant values used across the action button style. private enum Constant { /// The padding constants. enum Padding { /// The padding around the button's icon. static let icon: CGFloat = 16 } /// The size constants. enum Size { /// The size of the button's icon. static let icon: CGSize = .init( width: 24, height: 24 ) } } // MARK: - Styles extension ButtonStyle where Self == ActionButtonStyle { /// An action button style for a button behind the given type of action. /// /// - Parameter actionType: The type of the action behind the styled button. /// - Returns: The action button style colored for the given type. static func action( type actionType: ActionButtonStyle.ActionType ) -> ActionButtonStyle { .init(actionType) } }