diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 76ebeb740..ed5a727d8 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -982,6 +982,7 @@ final class StatusTableViewController: LoopChartsTableViewController { case .hud: let cell = tableView.dequeueReusableCell(withIdentifier: HUDViewTableViewCell.className, for: indexPath) as! HUDViewTableViewCell hudView = cell.hudView + cell.hudView.loopCompletionHUD.loopStatusColors = .loopStatus return cell case .charts: diff --git a/LoopUI/Views/LoopCompletionHUDView.swift b/LoopUI/Views/LoopCompletionHUDView.swift index 4794fda54..ac2ec3b72 100644 --- a/LoopUI/Views/LoopCompletionHUDView.swift +++ b/LoopUI/Views/LoopCompletionHUDView.swift @@ -20,7 +20,7 @@ public final class LoopCompletionHUDView: BaseHUDView { private(set) var freshness = LoopCompletionFreshness.stale { didSet { - updateTintColor() + loopStateView.freshness = freshness } } @@ -30,6 +30,12 @@ public final class LoopCompletionHUDView: BaseHUDView { updateDisplay(nil) } + public var loopStatusColors: StateColorPalette = StateColorPalette(unknown: .black, normal: .black, warning: .black, error: .black) { + didSet { + loopStateView.loopStatusColors = loopStatusColors + } + } + public var loopIconClosed = false { didSet { loopStateView.open = !loopIconClosed @@ -65,26 +71,6 @@ public final class LoopCompletionHUDView: BaseHUDView { } } - override public func stateColorsDidUpdate() { - super.stateColorsDidUpdate() - updateTintColor() - } - - private var _tintColor: UIColor? { - switch freshness { - case .fresh: - return stateColors?.normal - case .aging: - return stateColors?.warning - case .stale: - return stateColors?.error - } - } - - private func updateTintColor() { - self.tintColor = _tintColor - } - private func initTimer(_ startDate: Date) { let updateInterval = TimeInterval(minutes: 1) diff --git a/LoopUI/Views/LoopStateView.swift b/LoopUI/Views/LoopStateView.swift index 95991048b..508d9a53b 100644 --- a/LoopUI/Views/LoopStateView.swift +++ b/LoopUI/Views/LoopStateView.swift @@ -6,113 +6,109 @@ // Copyright © 2016 Nathan Racklyeft. All rights reserved. // +import LoopKit +import LoopKitUI +import SwiftUI import UIKit -final class LoopStateView: UIView { - var firstDataUpdate = true +class WrappedLoopStateViewModel: ObservableObject { + @Published var loopStatusColors: StateColorPalette + @Published var closedLoop: Bool + @Published var freshness: LoopCompletionFreshness + @Published var animating: Bool - override func tintColorDidChange() { - super.tintColorDidChange() - - updateTintColor() + init( + loopStatusColors: StateColorPalette = StateColorPalette(unknown: .black, normal: .black, warning: .black, error: .black), + closedLoop: Bool = true, + freshness: LoopCompletionFreshness = .stale, + animating: Bool = false + ) { + self.loopStatusColors = loopStatusColors + self.closedLoop = closedLoop + self.freshness = freshness + self.animating = animating } +} - private func updateTintColor() { - shapeLayer.strokeColor = tintColor.cgColor +struct WrappedLoopCircleView: View { + + @ObservedObject var viewModel: WrappedLoopStateViewModel + + var body: some View { + LoopCircleView(closedLoop: viewModel.closedLoop, freshness: viewModel.freshness, animating: viewModel.animating) + .environment(\.loopStatusColorPalette, viewModel.loopStatusColors) } +} - var open = false { - didSet { - if open != oldValue { - if open, animated { - animated = false - } - shapeLayer.path = drawPath() - } - } +class LoopCircleHostingController: UIHostingController { + init(viewModel: WrappedLoopStateViewModel) { + super.init( + rootView: WrappedLoopCircleView( + viewModel: viewModel + ) + ) } - - override class var layerClass : AnyClass { - return CAShapeLayer.self + + required init?(coder aDecoder: NSCoder) { + fatalError() } +} - private var shapeLayer: CAShapeLayer { - return layer as! CAShapeLayer - } +final class LoopStateView: UIView { + override init(frame: CGRect) { super.init(frame: frame) - - shapeLayer.lineWidth = 8 - shapeLayer.fillColor = UIColor.clear.cgColor - updateTintColor() - - shapeLayer.path = drawPath() + + setupViews() } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - - shapeLayer.lineWidth = 8 - shapeLayer.fillColor = UIColor.clear.cgColor - updateTintColor() - - shapeLayer.path = drawPath() + + required init?(coder: NSCoder) { + super.init(coder: coder) + + setupViews() } - - override func layoutSubviews() { - super.layoutSubviews() - - shapeLayer.path = drawPath() + + var loopStatusColors: StateColorPalette = StateColorPalette(unknown: .black, normal: .black, warning: .black, error: .black) { + didSet { + viewModel.loopStatusColors = loopStatusColors + } } - private func drawPath(lineWidth: CGFloat? = nil) -> CGPath { - let center = CGPoint(x: bounds.midX, y: bounds.midY) - let lineWidth = lineWidth ?? shapeLayer.lineWidth - let radius = min(bounds.width / 2, bounds.height / 2) - lineWidth / 2 - - let startAngle = open ? -CGFloat.pi / 4 : 0 - let endAngle = open ? 5 * CGFloat.pi / 4 : 2 * CGFloat.pi - - let path = UIBezierPath( - arcCenter: center, - radius: radius, - startAngle: startAngle, - endAngle: endAngle, - clockwise: true - ) - - return path.cgPath + var freshness: LoopCompletionFreshness = .stale { + didSet { + viewModel.freshness = freshness + } + } + + var open = false { + didSet { + viewModel.closedLoop = !open + } } - - private static let AnimationKey = "com.loudnate.Naterade.breatheAnimation" var animated: Bool = false { didSet { - if animated != oldValue { - if animated, !open { - let path = CABasicAnimation(keyPath: "path") - path.fromValue = shapeLayer.path ?? drawPath() - path.toValue = drawPath(lineWidth: 16) - - let width = CABasicAnimation(keyPath: "lineWidth") - width.fromValue = shapeLayer.lineWidth - width.toValue = 10 - - let group = CAAnimationGroup() - group.animations = [path, width] - group.duration = firstDataUpdate ? 0 : 1 - group.repeatCount = HUGE - group.autoreverses = true - group.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) - - shapeLayer.add(group, forKey: type(of: self).AnimationKey) - } else { - shapeLayer.removeAnimation(forKey: type(of: self).AnimationKey) - } - } - firstDataUpdate = false + viewModel.animating = animated } } + + private let viewModel = WrappedLoopStateViewModel() + + private func setupViews() { + let hostingController = LoopCircleHostingController(viewModel: viewModel) + + hostingController.view.backgroundColor = .clear + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + + addSubview(hostingController.view) + + NSLayoutConstraint.activate([ + hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor), + hostingController.view.topAnchor.constraint(equalTo: topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } }