caiobzen/Reactions

View on GitHub
Sources/Reactions/Reactions.swift

Summary

Maintainability
A
0 mins
Test Coverage
import SwiftUI
import UIKit

public struct Reactions: View {
    let reactions: [Reaction]
    let reactionSize: CGFloat
    let onReactionSelected: (Reaction) -> Void
    
    public init(_ reactions: [Reaction], reactionSize: CGFloat = 50, onReactionSelected: @escaping (Reaction) -> Void) {
        self.reactions = reactions
        self.reactionSize = reactionSize
        self.onReactionSelected = onReactionSelected
    }
    
    @State private var isDragging = false {
        didSet {
            if isDragging != oldValue {
                UIImpactFeedbackGenerator().impactOccurred()
            }
        }
    }
    
    @State private var selectedReaction: Reaction? = nil {
        didSet {
            if selectedReaction != oldValue {
                UIImpactFeedbackGenerator().impactOccurred()
            }
        }
    }
    
    private var defaultReaction: Reaction {
        selectedReaction ?? reactions.first!
    }
    
    public var body: some View {
        ZStack(alignment: .leading) {
            HStack {
                ForEach(reactions, id: \.name) {
                    ReactionView(reaction: $0, isSelected: self.selectedReaction == $0, iconSize: self.reactionSize)
                }
            }
            .opacity(isDragging ? 1 : .zero)
            .frame(width: reactionSize, height: reactionSize)
            .offset(y: -reactionSize)
            
            ReactionSelectionButton(reaction: defaultReaction, size: reactionSize, onChanged: { value in
                withAnimation { self.isDragging = true }
                self.selectedReaction = self.getSelectedReaction(for: value.translation)
            }, onEnded: {
                withAnimation { self.isDragging = false }
                if let reaction = self.selectedReaction {
                    self.onReactionSelected(reaction)
                }
            })
        }
    }
}

extension Reactions {
    func getSelectedReaction(for translation: CGSize) -> Reaction? {
        let iconWidth = reactionSize
        let halfWidth = (iconWidth * CGFloat(reactions.count)) / 2
        let widthTranslation = translation.width + halfWidth
        let isYInRange = translation.height < (reactionSize * -0.5) && translation.height > (reactionSize * -1.5)
        
        if isYInRange {
            var iconIndex = Int(widthTranslation / iconWidth)
            if iconIndex < .zero { iconIndex = .zero }
            if iconIndex >= reactions.count { iconIndex = reactions.count - 1 }
            return reactions[iconIndex]
        }
        return nil
    }
}