Skip to content

Commit

Permalink
[LOOP-4884] Use LoopCircleView for LoopStateView / SwiftUI Interop
Browse files Browse the repository at this point in the history
  • Loading branch information
Camji55 authored Jul 25, 2024
2 parents 030035b + a601a2f commit ebe4d78
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 106 deletions.
1 change: 1 addition & 0 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
28 changes: 7 additions & 21 deletions LoopUI/Views/LoopCompletionHUDView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public final class LoopCompletionHUDView: BaseHUDView {

private(set) var freshness = LoopCompletionFreshness.stale {
didSet {
updateTintColor()
loopStateView.freshness = freshness
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down
166 changes: 81 additions & 85 deletions LoopUI/Views/LoopStateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<WrappedLoopCircleView> {
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)
])
}
}

0 comments on commit ebe4d78

Please sign in to comment.