Created
July 22, 2020 04:12
-
-
Save rr-codes/8e18298dd7d9cb95aeae8d50a1b9503d 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
| // | |
| // CardView2.swift | |
| // Hourglass | |
| // | |
| // Created by Richard Robinson on 2020-07-21. | |
| // | |
| import Foundation | |
| import SwiftUI | |
| // MARK: Example | |
| let example: some View = CardView2Factory.create( | |
| colors: (.red, .blue), | |
| name: "My Birthday", | |
| range: .init(start: Date(), duration: 0), | |
| configuration: (size: .small, style: .inverted) | |
| ) | |
| // MARK: Factory | |
| class CardView2Factory { | |
| enum Size { | |
| case small, medium | |
| } | |
| enum Style { | |
| case plain, inverted | |
| } | |
| @ViewBuilder static func create( | |
| colors: (Color, Color), | |
| name: String, | |
| range: DateInterval, | |
| configuration: (size: Size, style: Style) | |
| ) -> some View { | |
| switch configuration { | |
| case (.small, .plain): | |
| SmallPlainCardView2(colors: colors, name: name, range: range) | |
| case (.small, .inverted): | |
| SmallInvertedCardView2(colors: colors, name: name, range: range) | |
| case (.medium, .plain): | |
| MediumPlainCardView2(colors: colors, name: name, range: range) | |
| case (.medium, .inverted): | |
| MediumInvertedCardView2(colors: colors, name: name, range: range) | |
| } | |
| } | |
| } | |
| // MARK: Main Protocol | |
| protocol CardView2: View { | |
| associatedtype Accent: ShapeStyle & View | |
| associatedtype Background: ShapeStyle | |
| var colors: (Color, Color) { get } | |
| var name: String { get } | |
| var range: DateInterval { get } | |
| var headerTextColor: Color { get } | |
| var accent: Accent { get } | |
| var background: Background { get } | |
| } | |
| // MARK: Size Protocols | |
| extension CardView2 { | |
| fileprivate var dateFormatter: DateFormatter { | |
| let df = DateFormatter() | |
| df.doesRelativeDateFormatting = true | |
| df.dateStyle = .medium | |
| df.timeStyle = .none | |
| return df | |
| } | |
| fileprivate var dateComponentsFormatter: DateComponentsFormatter { | |
| let dcf = DateComponentsFormatter() | |
| dcf.allowedUnits = [.day, .hour, .minute, .second] | |
| dcf.unitsStyle = .short | |
| dcf.maximumUnitCount = 2 | |
| return dcf | |
| } | |
| fileprivate var mainText: Text { | |
| #if WIDGET | |
| let text = Text(range.end, style: .relative) | |
| #else | |
| let text = Text( | |
| dateComponentsFormatter.string(from: range.end.timeIntervalSinceNow)! | |
| ) | |
| #endif | |
| return range.progress < 1.0 ? text : Text("Complete!") | |
| } | |
| fileprivate var shape: some Shape { | |
| #if WIDGET | |
| return Rectangle() | |
| #else | |
| return RoundedRectangle(cornerRadius: 23.0, style: .continuous) | |
| #endif | |
| } | |
| fileprivate var text: some View { | |
| VStack { | |
| Text(name) | |
| .font(.headline) | |
| .fontWeight(.bold) | |
| .frame(maxWidth: .infinity, alignment: .leading) | |
| .foregroundColor(headerTextColor) | |
| .padding(.bottom, 2) | |
| Text(dateFormatter.string(from: range.end)) | |
| .font(.subheadline) | |
| .fontWeight(.medium) | |
| .foregroundColor(headerTextColor.opacity(0.75)) | |
| .frame(maxWidth: .infinity, alignment: .leading) | |
| } | |
| } | |
| } | |
| protocol SmallCardView2: CardView2 { | |
| } | |
| extension SmallCardView2 { | |
| var body: some View { | |
| shape.fill(background) | |
| .overlay( | |
| VStack { | |
| self.text | |
| Spacer() | |
| mainText.font( | |
| Font.system(.title3, design: .rounded) | |
| .monospacedDigit() | |
| .weight(.bold) | |
| ) | |
| .tracking(-0.1) | |
| .foreground(accent) | |
| .padding(.bottom, 11.0) | |
| Spacer() | |
| ProgressView2(value: range.progress, fill: accent) | |
| } | |
| .padding(.bottom, 24) | |
| .padding(.top, 22) | |
| .padding(.horizontal) | |
| ) | |
| } | |
| } | |
| protocol MediumCardView2: CardView2 { | |
| } | |
| extension MediumCardView2 { | |
| var body: some View { | |
| shape.fill(background) | |
| .overlay( | |
| HStack(alignment: VerticalAlignment.center) { | |
| VStack { | |
| self.text | |
| Spacer() | |
| mainText.font( | |
| Font.system(.title, design: .rounded) | |
| .monospacedDigit() | |
| .weight(.bold) | |
| ) | |
| .tracking(-0.1) | |
| .foreground(accent) | |
| .frame(maxWidth: .infinity, alignment: .leading) | |
| .padding(.bottom, 5.0) | |
| } | |
| RingView(progress: 0.5, diameter: 85.0, colors: colors, textColor: headerTextColor) | |
| .padding(.trailing, 10) | |
| .padding(.bottom, 8) | |
| } | |
| .padding(.top, 34) | |
| .padding(.bottom, 32) | |
| .padding(.horizontal, 27) | |
| ) | |
| } | |
| } | |
| // MARK: Style Protocols | |
| protocol PlainCardView2: CardView2 { | |
| } | |
| extension PlainCardView2 { | |
| var headerTextColor: Color { | |
| return .black | |
| } | |
| var accent: LinearGradient { | |
| return LinearGradient( | |
| gradient: .init(colors: [colors.0, colors.1]), | |
| startPoint: .topTrailing, | |
| endPoint: .bottomLeading | |
| ) | |
| } | |
| var background: Color { | |
| return .white | |
| } | |
| } | |
| protocol InvertedCardView2: CardView2 { | |
| } | |
| extension InvertedCardView2 { | |
| var headerTextColor: Color { | |
| return .white | |
| } | |
| var accent: Color { | |
| return .white | |
| } | |
| var background: LinearGradient { | |
| LinearGradient( | |
| gradient: .init(colors: [colors.0, colors.1]), | |
| startPoint: .topTrailing, | |
| endPoint: .bottomLeading | |
| ) | |
| } | |
| } | |
| // MARK: Implementation | |
| struct SmallPlainCardView2: SmallCardView2, PlainCardView2 { | |
| let colors: (Color, Color) | |
| let name: String | |
| let range: DateInterval | |
| } | |
| struct SmallInvertedCardView2: SmallCardView2, InvertedCardView2 { | |
| let colors: (Color, Color) | |
| let name: String | |
| let range: DateInterval | |
| } | |
| struct MediumPlainCardView2: MediumCardView2, PlainCardView2 { | |
| let colors: (Color, Color) | |
| let name: String | |
| let range: DateInterval | |
| } | |
| struct MediumInvertedCardView2: MediumCardView2, InvertedCardView2 { | |
| let colors: (Color, Color) | |
| let name: String | |
| let range: DateInterval | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment