Skip to content

Commit

Permalink
Add swiftui arc graphic
Browse files Browse the repository at this point in the history
  • Loading branch information
eonist committed Oct 31, 2024
1 parent 02ce022 commit fff52bd
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 9 deletions.
18 changes: 9 additions & 9 deletions Sources/TwoFa/util/GraphProgressView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*/
#if os(iOS)
import UIKit // Import the UIKit module for iOS
public typealias Color = UIColor // Define the `Color` typealias as `UIColor` for iOS
public typealias OSColor = UIColor // Define the `Color` typealias as `UIColor` for iOS
public typealias OSView = UIView // Define the `OSView` typealias as `UIView` for iOS
#elseif os(macOS)
import Cocoa // Import the Cocoa module for macOS
public typealias Color = NSColor // Define the `Color` typealias as `NSColor` for macOS
public typealias OSColor = NSColor // Define the `Color` typealias as `NSColor` for macOS
public typealias OSView = NSView // Define the `OSView` typealias as `NSView` for macOS
#endif
/**
Expand Down Expand Up @@ -79,7 +79,7 @@ public final class GraphProgressView: OSView {
* - Note: This property is used to set the stroke color for the background of the graph.
* - Warning: This property is not documented yet.
*/
public var backgroundStrokeColor: Color = .darkGray {
public var backgroundStrokeColor: OSColor = .darkGray {
didSet { // Property observer that redraws the graph when the stroke color changes
drawGraphic() // Redraw the graph when the stroke color changes
}
Expand Down Expand Up @@ -112,9 +112,9 @@ extension GraphProgressView {
* when the progress is nearing completion.
*/
public typealias TintColors = (
idle: Color, // The color for the idle state of the graph
start: Color, // The color for the start state of the graph
end: Color // The color for the end state of the graph
idle: OSColor, // The color for the idle state of the graph
start: OSColor, // The color for the start state of the graph
end: OSColor // The color for the end state of the graph
)
}
/**
Expand Down Expand Up @@ -150,7 +150,7 @@ extension GraphProgressView {
* color.
* - Remark: This method returns the current color of the graph based on the progress and threshold values.
*/
public var color: Color { // Style
public var color: OSColor { // Style
if progress < threshold {
// Return the start color if the progress is less than the threshold
return tintColors.start
Expand Down Expand Up @@ -207,7 +207,7 @@ extension GraphProgressView {
* - lineColor: color of line
* - progress: 0-1
*/
fileprivate func createArcShape(lineColor: Color, progress: CGFloat) -> CAShapeLayer {
fileprivate func createArcShape(lineColor: OSColor, progress: CGFloat) -> CAShapeLayer {
let center: CGPoint = .init(x: self.bounds.midX, y: self.bounds.midY) // Get the center point of the view
let radius: CGFloat = max(self.bounds.size.height / 1.0, self.bounds.size.width / 1.0) - lineWeight // Calculate the radius of the arc shape
let path: CGMutablePath = .init() // Create a new mutable path
Expand All @@ -222,7 +222,7 @@ extension GraphProgressView {
clockwise: false // A Boolean value indicating whether the arc should be drawn clockwise or counterclockwise
) // Add an arc to the path with the specified center, radius, start angle, end angle, and direction
let layer: CAShapeLayer = .init() // Create a new shape layer
layer.fillColor = Color.clear.cgColor // Set the fill color of the shape layer to clear
layer.fillColor = OSColor.clear.cgColor // Set the fill color of the shape layer to clear
layer.strokeColor = lineColor.cgColor // Set the stroke color of the shape layer to the specified line color
layer.lineWidth = lineWeight // Set the line width of the shape layer to the specified line weight
layer.lineCap = .round // Set the line cap of the shape layer to round
Expand Down
39 changes: 39 additions & 0 deletions Sources/TwoFa/util/GraphView/GraphView+Components.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftUI
//import HybridColor
/**
* Components
*/
extension GraphView {
/**
* Foreground
* - Description: The foreground component of the GraphView, which is a partial circle that represents the current progress. It is styled with the color based on the current progress and threshold values, and has a line weight that determines its thickness. The circle is trimmed according to the progress value, creating the progress indicator effect.
*/
internal var foreground: some View {
Circle() // Draw the background circle
.trim(from: 0.0, to: progress) // Trim the circle to draw the full circle
.stroke( // Applies a stroke to the circle
color,// Sets the color of the background stroke
style: StrokeStyle( // Initializes a new StrokeStyle
lineWidth: lineWeight, // Sets the width of the line
lineCap: .round, // Sets the cap style of the line to round
lineJoin: .round // Sets the join style of the line to round
)
) // Set the stroke color and style for the background circle
}
/**
* Background
* - Description: The background component of the GraphView, which is a full circle that serves as the backdrop for the progress indicator. It is styled with the backgroundStrokeColor and has the same line weight as the foreground progress circle but remains static, indicating the full extent of the progress track.
*/
internal var background: some View {
Circle() // Draw the progress circle
.trim(from: 0.0, to: 1.0) // Trims the circle to draw the progress
.stroke( // Applies a stroke to the circle
backgroundStrokeColor, // Sets the color of the progress circle
style: StrokeStyle( // Initializes a new StrokeStyle
lineWidth: lineWeight, // Sets the width of the line
lineCap: .round, // Sets the cap style of the line to round
lineJoin: .round // Sets the join style of the line to round
)
) // Set the stroke color and style for the progress circle
}
}
12 changes: 12 additions & 0 deletions Sources/TwoFa/util/GraphView/GraphView+Const.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SwiftUI

extension GraphView {
/**
* Container for the colors used in the arc stroke
*/
public typealias TintColors = (
idle: Color, // Represents the color when the progress is idle
start: Color, // Represents the color at the start of the progress
end: Color // Represents the color at the end of the progress
)
}
17 changes: 17 additions & 0 deletions Sources/TwoFa/util/GraphView/GraphView+Content.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftUI
/**
* Content
*/
extension GraphView {
/**
* Body
* - Description: The main view of the GraphView which consists of a ZStack that layers the background and foreground components to create a circular progress indicator. The entire ZStack is rotated by -90 degrees to ensure the progress starts from the top.
*/
public var body: some View {
ZStack {
background
foreground
}
.rotationEffect(.degrees(-90)) // Rotates the view 90 degrees to align the progress circle with the x-axis
}
}
24 changes: 24 additions & 0 deletions Sources/TwoFa/util/GraphView/GraphView+Getter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftUI
//import HybridColor
/**
* Getter
*/
extension GraphView {
/**
* Get current color
* - Description: This computed property returns the appropriate color for the progress bar based on the current progress and threshold values. If the progress is less than the threshold, it returns the start color. If the progress is greater than 1 minus the threshold, it returns the end color. Otherwise, it returns the idle color.
*/
internal var color: Color { // Style
if progress < threshold {
// Return the start color if the progress is less than the threshold
return tintColors.start
}
else if progress > 1 - threshold {
// Return the end color if the progress is greater than 1 minus the threshold
return tintColors.end
} else {
// Return the idle color if the progress is between the threshold and 1 minus the threshold
return tintColors.idle
}
}
}
33 changes: 33 additions & 0 deletions Sources/TwoFa/util/GraphView/GraphView+Preview.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import SwiftUI
//import HybridColor
/**
* Preview (dark / light mode)
* - Fixme: ⚠️️ Add support for preview container
* - Fixme: ⚠️️ Use light gray for light mode and dark gray for darkmode?
*/
#Preview(traits: .fixedLayout(width: 200, height: 200)) {
struct DebugContainer: View {
var body: some View {
// Creates an instance of GraphView for preview purposes
GraphView(
progress: .constant(0.18), // Represents the current progress as a fraction of the total, where 0.18 indicates 18% completion.
threshold: .constant(0.2), // Sets the threshold value at 20%, used to trigger certain actions when exceeded.
tintColors: .constant((.blue, .green, .red)), // Defines the colors used for tinting elements in the graph, in this case blue, green, and red.
// ⚠️️ Use .init(light: lightGray.opacity(0.5), dark: darkGray.opacity(0.5))
backgroundStrokeColor: .constant(.gray), // Specifies the color of the background stroke of the graph as gray.
lineWeight: .constant(4) // Determines the thickness of the line in the graph, set to 4 points.
)
.frame(width: 42, height: 42) // Sets the frame of the GraphView to 42x42 pixels.
.padding() // Adds padding around the GraphView to separate it from adjacent UI elements.
}
}
// return PreviewContainer {
return DebugContainer()
.frame(maxWidth: .infinity)
// .background(Color.black/*Color.whiteOrBlack*/)
// }
.environment(\.colorScheme, .dark) // dark / light
#if os(macOS)
.frame(width: 200, height: 200)
#endif
}
54 changes: 54 additions & 0 deletions Sources/TwoFa/util/GraphView/GraphView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import SwiftUI
//import HybridColor
/**
* This SwiftUI GraphProgressView struct creates a circular progress bar similar to the UIKit GraphProgressView class. The @State property wrapper is used to create mutable state for the progress, threshold, tintColors, backgroundStrokeColor, and lineWeight properties. The body property returns a ZStack that contains two Circle views that represent the background and foreground of the progress bar. The trim(from:to:) modifier is used to create the progress effect, and the stroke(_:style:) modifier is used to set the stroke color and style of the circles. The rotationEffect(_:) modifier is used to rotate the ZStack so that the progress starts from the top. The color computed property returns the appropriate color based on the current progress.
* - Description: A view that shows a circular progress bar
* - Note: Used by `OTPArc` and `AuditView`
* - Note: Works for iOS and macOS
* - Fixme: ⚠️️ Animate graph in onApear, maybe later?
* - Fixme: ⚠️️ Transfer more of the comments from legacy 👈
* - Fixme: ⚠️️ Move to TwoFA scope?
*/
public struct GraphView: View {
/**
* Progress from 0-1
* - Description: The current progress of the graph, represented as a value from 0.0 (no progress) to 1.0 (complete progress).
*/
@Binding internal var progress: CGFloat
/**
* When should the time threshold indication be turned on (green / red)
* - Description: The threshold value at which the progress bar changes color to indicate a warning or critical state. For example, if the threshold is set to 0.5, the progress bar will change color when the progress reaches or exceeds 50%.
*/
@Binding internal var threshold: CGFloat // - Important: ⚠️️ notice that idle comes before start etc, use labels if needed
/**
* Set tint colors - Foreground color
* - Description: The colors used to tint the progress bar, which can change based on the current progress in relation to the threshold. The tuple contains colors for normal progress, warning progress, and critical progress states.
*/
@Binding internal var tintColors: TintColors
/**
* The stroke color for the background of the graph
* - Description: The color used for the background stroke of the progress bar. This color remains constant regardless of the progress or threshold values.
*/
@Binding internal var backgroundStrokeColor: Color
/**
* Line-weight - Filled or stroked
* - Description: The line weight determines the thickness of the progress bar. A higher value will result in a thicker bar, while a lower value will result in a thinner bar.
*/
@Binding internal var lineWeight: CGFloat
/**
* - Description: A placeholder for additional documentation or description of functionality that may be added in the future.
* - Parameters:
* - progress: The progress value from 0 to 1 that determines the amount of the circle that is filled.
* - threshold: The threshold value that determines when the progress bar color changes to indicate a warning or critical state.
* - tintColors: A tuple of three colors that represent the tint colors for the progress bar at different stages (e.g., normal, warning, critical).
* - backgroundStrokeColor: The color of the background stroke of the progress bar.
* - lineWeight: The width of the line that makes up the progress bar.
*/
public init(progress: Binding<CGFloat> = .constant(0), threshold: Binding<CGFloat> = .constant(0.2), tintColors: Binding<TintColors> = .constant((Color.blue, Color.green, Color.red)), backgroundStrokeColor: Binding<Color> = .constant(Color.gray), lineWeight: Binding<CGFloat> = .constant(4)) {
self._progress = progress
self._threshold = threshold
self._tintColors = tintColors
self._backgroundStrokeColor = backgroundStrokeColor
self._lineWeight = lineWeight
}
}

0 comments on commit fff52bd

Please sign in to comment.