Created
September 26, 2021 13:58
-
-
Save ABashkirova/b0ab13b121145b7d70c7aab33bb3fbd4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import PlaygroundSupport | |
struct WidgetWithCustomTimeView: View { | |
@State var hour: DateComponents = Date.currentHour | |
@State var minute: DateComponents = Date.currentMinutes | |
@State var timeColonState: ColonState = .paused | |
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() | |
var backgroundColor = Color(red: 0.961, green: 0.957, blue: 0.949) | |
var body: some View { | |
VStack(alignment: .center, spacing: 8) { | |
widget | |
Button(timeColonState == .paused ? "Start" : "Stop", action: { | |
switch timeColonState { | |
case .paused: | |
timeColonState = timeColonState.start() | |
case .started: | |
timeColonState = timeColonState.pause() | |
} | |
}) | |
} | |
.padding(15) | |
.background(.black) | |
.onReceive(timer) { _ in | |
self.updateTime() | |
self.updateColonColorIfNeeded() | |
} | |
} | |
// MARK: Display logic | |
private func updateColonColorIfNeeded() { | |
if timeColonState != .paused { | |
withAnimation(Animation.easeOut(duration: 1).delay(1) | |
) { | |
timeColonState = timeColonState.toggle() | |
} | |
} | |
} | |
private func updateTime() { | |
hour = Date.currentHour | |
minute = Date.currentMinutes | |
} | |
private func dotColor(_ dot: ColonState.FilledDot) -> Color { | |
return timeColonState == .started(dot) ? .red : .black | |
} | |
private func formatter(_ allowedUnit: NSCalendar.Unit) -> DateComponentsFormatter { | |
let formatter = DateComponentsFormatter() | |
formatter.allowedUnits = [allowedUnit] | |
return formatter | |
} | |
// MARK: - Subviews | |
// MARK: Widget | |
private var widget: some View { | |
ZStack { | |
widgetBody | |
widgetContent | |
.padding(15) | |
} | |
.frame(width: 150, height: 150, alignment: .center) | |
} | |
private var widgetBody: some View { | |
RoundedRectangle(cornerRadius: 10) | |
.foregroundColor(backgroundColor) | |
} | |
private var widgetContent: some View { | |
VStack(alignment: .leading) { | |
time | |
searchBar | |
} | |
} | |
// MARK: Time | |
private var time: some View { | |
HStack(alignment: .bottom, spacing: 0) { | |
Text(formatter(.hour).string(from: hour) ?? "") | |
.labelStyle(WidgetLabelStyle(size: 30)) | |
colonView | |
Text(formatter(.minute).string(from: minute) ?? "") | |
.labelStyle(WidgetLabelStyle(size: 30)) | |
} | |
} | |
private var dotView: some View { | |
Text(".").labelStyle(WidgetLabelStyle(size: 30)) | |
} | |
private var colonView: some View { | |
VStack(alignment: .trailing, spacing: -14) { | |
dotView.foregroundColor(dotColor(.top)) | |
dotView.foregroundColor(dotColor(.bottom)) | |
} | |
} | |
// MARK: Search | |
private var searchBar: some View { | |
ZStack(alignment: .leading) { | |
searchBarContainer | |
searchIcon.padding(.leading, 15) | |
} | |
} | |
private var searchBarContainer: some View { | |
RoundedRectangle(cornerRadius: 20) | |
.frame(height: 40) | |
.foregroundColor(.white) | |
} | |
private var searchIcon: some View { | |
Text("Y") | |
.labelStyle(WidgetLabelStyle(size: 24)) | |
.foregroundColor(.red) | |
} | |
} | |
struct WidgetLabelStyle: LabelStyle { | |
private let size: CGFloat | |
init(size: CGFloat) { | |
self.size = size | |
} | |
func makeBody(configuration: Configuration) -> some View { | |
Label(configuration) | |
.font(.system(size: size, weight: .light)) | |
} | |
} | |
// MARK: Color state | |
enum ColonState: Equatable { | |
case paused | |
case started(FilledDot) | |
enum FilledDot: Equatable { | |
case top | |
case bottom | |
func toggle() -> FilledDot { | |
switch self { | |
case .bottom: return .top | |
case .top: return .bottom | |
} | |
} | |
} | |
func pause() -> ColonState { | |
return .paused | |
} | |
func start() -> ColonState { | |
return .started(.top) | |
} | |
func toggle() -> ColonState { | |
switch self { | |
case .paused: | |
return self.start() | |
case .started(let dot): | |
return .started(dot.toggle()) | |
} | |
} | |
} | |
// MARK: Date + utils | |
extension Date { | |
static var currentHour: DateComponents { | |
Date().component(.hour) | |
} | |
static var currentMinutes: DateComponents { | |
Date().component(.minute) | |
} | |
func component(_ component: Calendar.Component) -> DateComponents { | |
return Calendar.current.dateComponents([component], from: self) | |
} | |
} | |
PlaygroundPage.current.setLiveView(WidgetWithCustomTimeView()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment