import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit import AppBundle enum LegacyCallControllerButtonType { case mute case end case accept case speaker case bluetooth case switchCamera } private let buttonSize = CGSize(width: 75.0, height: 75.0) private func generateEmptyButtonImage(icon: UIImage?, strokeColor: UIColor?, fillColor: UIColor, knockout: Bool = false, angle: CGFloat = 0.0) -> UIImage? { return generateImage(buttonSize, contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setBlendMode(.copy) if let strokeColor = strokeColor { context.setFillColor(strokeColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) context.setFillColor(fillColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: 1.5, y: 1.5), size: CGSize(width: size.width - 3.0, height: size.height - 3.0))) } else { context.setFillColor(fillColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) } if let icon = icon { if !angle.isZero { context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.rotate(by: angle) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) } let imageSize = icon.size let imageRect = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.width - imageSize.height) / 2.0)), size: imageSize) if knockout { context.setBlendMode(.copy) context.clip(to: imageRect, mask: icon.cgImage!) context.setFillColor(UIColor.clear.cgColor) context.fill(imageRect) } else { context.setBlendMode(.normal) context.draw(generateTintedImage(image: icon, color: .white)!.cgImage!, in: imageRect) } } }) } private func generateFilledButtonImage(color: UIColor, icon: UIImage?, angle: CGFloat = 0.0) -> UIImage? { return generateImage(buttonSize, contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setBlendMode(.normal) context.setFillColor(color.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) if let icon = icon { if !angle.isZero { context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.rotate(by: angle) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) } context.draw(icon.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - icon.size.width) / 2.0), y: floor((size.height - icon.size.height) / 2.0)), size: icon.size)) } }) } private let emptyStroke = UIColor(white: 1.0, alpha: 0.8) private let emptyHighlightedFill = UIColor(white: 1.0, alpha: 0.3) private let invertedFill = UIColor(white: 1.0, alpha: 1.0) private let labelFont = Font.regular(14.5) final class LegacyCallControllerButtonNode: HighlightTrackingButtonNode { private var type: LegacyCallControllerButtonType private var regularImage: UIImage? private var highlightedImage: UIImage? private var filledImage: UIImage? private let backgroundNode: ASImageNode private let labelNode: ASTextNode? init(type: LegacyCallControllerButtonType, label: String?) { self.type = type self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.displayWithoutProcessing = false self.backgroundNode.displaysAsynchronously = false if let label = label { let labelNode = ASTextNode() labelNode.attributedText = NSAttributedString(string: label, font: labelFont, textColor: .white) self.labelNode = labelNode } else { self.labelNode = nil } var regularImage: UIImage? var highlightedImage: UIImage? var filledImage: UIImage? switch type { case .mute: regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallMuteButton"), strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallMuteButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallMuteButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) case .accept: regularImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) case .end: regularImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton")) highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton")) case .speaker: regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallSpeakerButton"), strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallSpeakerButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallSpeakerButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) case .bluetooth: regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) case .switchCamera: let patternImage = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white) regularImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: patternImage, strokeColor: nil, fillColor: invertedFill, knockout: true) } self.regularImage = regularImage self.highlightedImage = highlightedImage self.filledImage = filledImage super.init() self.addSubnode(self.backgroundNode) if let labelNode = self.labelNode { self.addSubnode(labelNode) } self.backgroundNode.image = regularImage self.currentImage = regularImage self.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { strongSelf.internalHighlighted = highlighted strongSelf.updateState(highlighted: highlighted, selected: strongSelf.isSelected) } } } private var internalHighlighted = false override var isSelected: Bool { didSet { self.updateState(highlighted: self.internalHighlighted, selected: self.isSelected) } } private var currentImage: UIImage? private func updateState(highlighted: Bool, selected: Bool) { let image: UIImage? if selected { image = self.filledImage } else if highlighted { image = self.highlightedImage } else { image = self.regularImage } if self.currentImage !== image { let currentContents = self.backgroundNode.layer.contents self.backgroundNode.layer.removeAnimation(forKey: "contents") if let currentContents = currentContents, let image = image { self.backgroundNode.image = image self.backgroundNode.layer.animate(from: currentContents as AnyObject, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: image === self.currentImage || image === self.filledImage ? 0.25 : 0.15) } else { self.backgroundNode.image = image } self.currentImage = image } } func updateType(_ type: LegacyCallControllerButtonType) { if self.type == type { return } self.type = type var regularImage: UIImage? var highlightedImage: UIImage? var filledImage: UIImage? switch type { case .mute: regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallMuteButton"), strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallMuteButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallMuteButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) case .accept: regularImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) case .end: regularImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton")) highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/LegacyCallPhoneButton")) case .speaker: regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallSpeakerButton"), strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallSpeakerButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/LegacyCallSpeakerButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) case .bluetooth: regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) case .switchCamera: let patternImage = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white) regularImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: .clear) highlightedImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: emptyHighlightedFill) filledImage = generateEmptyButtonImage(icon: patternImage, strokeColor: nil, fillColor: invertedFill, knockout: true) } self.regularImage = regularImage self.highlightedImage = highlightedImage self.filledImage = filledImage self.updateState(highlighted: self.isHighlighted, selected: self.isSelected) } func animateRollTransition() { self.backgroundNode.layer.animate(from: 0.0 as NSNumber, to: (-CGFloat.pi * 5 / 4) as NSNumber, keyPath: "transform.rotation.z", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3, removeOnCompletion: false) self.labelNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) } override func layout() { super.layout() let size = self.bounds.size self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)) if let labelNode = self.labelNode { let labelSize = labelNode.measure(CGSize(width: 200.0, height: 100.0)) labelNode.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: 81.0), size: labelSize) } } }