Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4a181e0
SUCountdown
VislovIvan Dec 8, 2024
77a2135
Localization
VislovIvan Dec 9, 2024
4af06c6
Some fix
VislovIvan Dec 9, 2024
cc435ef
Docs and Localization fix
VislovIvan Dec 9, 2024
4f18c59
pr fix
VislovIvan Dec 10, 2024
01592e5
Fix localization
VislovIvan Dec 11, 2024
1e3b5a2
refactor SUCountdown
VislovIvan Dec 11, 2024
8009cae
removed associated values
VislovIvan Dec 12, 2024
99038f8
localization fix
VislovIvan Dec 12, 2024
70caad0
removed unused code
VislovIvan Dec 12, 2024
532ed5f
minor fix and docs
VislovIvan Dec 12, 2024
be87586
CountdownHelpers
VislovIvan Dec 12, 2024
52f547b
layout refactor
VislovIvan Dec 12, 2024
29eecdf
calculateMaxWidth
VislovIvan Dec 15, 2024
df990f6
calculateWidth
VislovIvan Dec 16, 2024
a8b6a93
bug fix
VislovIvan Dec 16, 2024
a05fe2c
fix countdown foreground color and width + refactor
mikhailChelbaev Dec 17, 2024
7fb95c7
refactor: move a method for calculating time width to the model
mikhailChelbaev Dec 17, 2024
ece0bc5
Merge branch 'dev' into SUCountdown
mikhailChelbaev Dec 17, 2024
04c51b6
add spacing param to the model and rename `unitsPosition` to `unitsSt…
mikhailChelbaev Dec 17, 2024
399ad68
UKCountdown
VislovIvan Dec 19, 2024
c6fa757
swiftlint fix
VislovIvan Dec 19, 2024
5796de3
improve implementation of UKCountdown
mikhailChelbaev Dec 19, 2024
623615e
merge with origin UKCountdown
mikhailChelbaev Dec 19, 2024
440c477
pin stack view to superview edges
mikhailChelbaev Dec 19, 2024
885febe
docs and func shouldUpdateHeight
VislovIvan Dec 20, 2024
2690486
add docs for `UnitsLocalization`
mikhailChelbaev Dec 20, 2024
78baf60
update constraints in `UKCountdown`
mikhailChelbaev Dec 20, 2024
26b1ec1
Merge pull request #35 from componentskit/UKCountdown
mikhailChelbaev Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ComponentsKit
import SwiftUI
import UIKit

struct CountdownPreview: View {
@State private var model = CountdownVM()

var body: some View {
VStack {
PreviewWrapper(title: "UIKit") {
UKCountdown(model: self.model)
.preview
}
PreviewWrapper(title: "SwiftUI") {
SUCountdown(model: self.model)
}
Form {
ComponentOptionalColorPicker(selection: self.$model.color)
FontPicker(selection: self.$model.font)
Picker("Locale", selection: self.$model.locale) {
Text("Current").tag(Locale.current)
Text("EN").tag(Locale(identifier: "en"))
Text("ES").tag(Locale(identifier: "es"))
Text("FR").tag(Locale(identifier: "fr"))
Text("DE").tag(Locale(identifier: "de"))
Text("ZH").tag(Locale(identifier: "zh"))
Text("JA").tag(Locale(identifier: "ja"))
Text("RU").tag(Locale(identifier: "ru"))
Text("AR").tag(Locale(identifier: "ar"))
Text("HI").tag(Locale(identifier: "hi"))
Text("PT").tag(Locale(identifier: "pt"))
}
SizePicker(selection: self.$model.size)
Picker("Style", selection: self.$model.style) {
Text("Plain").tag(CountdownVM.Style.plain)
Text("Light").tag(CountdownVM.Style.light)
}
Picker("Units Style", selection: self.$model.unitsStyle) {
Text("None").tag(CountdownVM.UnitsStyle.hidden)
Text("Bottom").tag(CountdownVM.UnitsStyle.bottom)
Text("Trailing").tag(CountdownVM.UnitsStyle.trailing)
}
DatePicker("Until Date", selection: self.$model.until, in: Date()..., displayedComponents: [.date, .hourAndMinute])
.datePickerStyle(.compact)
}
}
}
}

#Preview {
CountdownPreview()
}
3 changes: 3 additions & 0 deletions Examples/DemosApp/DemosApp/Core/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ struct App: View {
NavigationLinkWithTitle("Checkbox") {
CheckboxPreview()
}
NavigationLinkWithTitle("Countdown") {
CountdownPreview()
}
NavigationLinkWithTitle("Divider") {
DividerPreview()
}
Expand Down
15 changes: 15 additions & 0 deletions Sources/ComponentsKit/Countdown/Helpers/CountdownHelpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

enum CountdownHelpers {
enum Unit {
case days
case hours
case minutes
case seconds
}

enum UnitLength {
case short
case long
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import UIKit

struct CountdownWidthCalculator {
private static let label = UILabel()

private init() {}

static func preferredWidth(
for attributedText: NSAttributedString,
model: CountdownVM
) -> CGFloat {
self.style(label, with: model)
label.attributedText = attributedText

let targetSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: model.height)
let estimatedSize = label.sizeThatFits(targetSize)

return estimatedSize.width
}

private static func style(_ label: UILabel, with model: CountdownVM) {
label.numberOfLines = 0
}
}
154 changes: 154 additions & 0 deletions Sources/ComponentsKit/Countdown/Localization/UnitsLocalization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import Foundation

// MARK: - UnitsLocalization

/// A structure that provides localized representations of time units (seconds, minutes, hours, days).
public struct UnitsLocalization: Equatable {
/// A structure that represents the localized short and long forms of a single time unit.
public struct UnitItemLocalization: Equatable {
/// The short-form representation of the time unit (e.g., "s" for seconds).
public let short: String
/// The long-form representation of the time unit (e.g., "Seconds").
public let long: String

/// Initializes a new `UnitItemLocalization` with specified short and long forms.
///
/// - Parameters:
/// - short: The short-form representation of the time unit.
/// - long: The long-form representation of the time unit.
public init(short: String, long: String) {
self.short = short
self.long = long
}
}

// MARK: - Properties

/// The localized representation for seconds.
public let seconds: UnitItemLocalization

/// The localized representation for minutes.
public let minutes: UnitItemLocalization

/// The localized representation for hours.
public let hours: UnitItemLocalization

/// The localized representation for days.
public let days: UnitItemLocalization

// MARK: - Initialization

/// Initializes a new `UnitsLocalization` with localized representations for all time units.
///
/// - Parameters:
/// - seconds: The localization for seconds.
/// - minutes: The localization for minutes.
/// - hours: The localization for hours.
/// - days: The localization for days.
public init(
seconds: UnitItemLocalization,
minutes: UnitItemLocalization,
hours: UnitItemLocalization,
days: UnitItemLocalization
) {
self.seconds = seconds
self.minutes = minutes
self.hours = hours
self.days = days
}
}

// MARK: - Localizations

extension UnitsLocalization {
static let defaultLocalizations: [Locale: UnitsLocalization] = [
// English (en)
Locale(identifier: "en"): UnitsLocalization(
seconds: .init(short: "s", long: "Seconds"),
minutes: .init(short: "m", long: "Minutes"),
hours: .init(short: "h", long: "Hours"),
days: .init(short: "d", long: "Days")
),

// Spanish (es)
Locale(identifier: "es"): UnitsLocalization(
seconds: .init(short: "s", long: "Segundos"),
minutes: .init(short: "m", long: "Minutos"),
hours: .init(short: "h", long: "Horas"),
days: .init(short: "d", long: "Días")
),

// French (fr)
Locale(identifier: "fr"): UnitsLocalization(
seconds: .init(short: "s", long: "Secondes"),
minutes: .init(short: "m", long: "Minutes"),
hours: .init(short: "h", long: "Heures"),
days: .init(short: "j", long: "Jours")
),

// German (de)
Locale(identifier: "de"): UnitsLocalization(
seconds: .init(short: "s", long: "Sekunden"),
minutes: .init(short: "m", long: "Minuten"),
hours: .init(short: "h", long: "Stunden"),
days: .init(short: "t", long: "Tage")
),

// Chinese (zh)
Locale(identifier: "zh"): UnitsLocalization(
seconds: .init(short: "秒", long: "秒"),
minutes: .init(short: "分", long: "分钟"),
hours: .init(short: "时", long: "小时"),
days: .init(short: "天", long: "天")
),

// Japanese (ja)
Locale(identifier: "ja"): UnitsLocalization(
seconds: .init(short: "秒", long: "秒"),
minutes: .init(short: "分", long: "分"),
hours: .init(short: "時", long: "時間"),
days: .init(short: "日", long: "日")
),

// Russian (ru)
Locale(identifier: "ru"): UnitsLocalization(
seconds: .init(short: "с", long: "Секунд"),
minutes: .init(short: "м", long: "Минут"),
hours: .init(short: "ч", long: "Часов"),
days: .init(short: "д", long: "Дней")
),

// Arabic (ar)
Locale(identifier: "ar"): UnitsLocalization(
seconds: .init(short: "ث", long: "ثوانٍ"),
minutes: .init(short: "د", long: "دقائق"),
hours: .init(short: "س", long: "ساعات"),
days: .init(short: "ي", long: "أيام")
),

// Hindi (hi)
Locale(identifier: "hi"): UnitsLocalization(
seconds: .init(short: "से", long: "सेकंड"),
minutes: .init(short: "मि", long: "मिनट"),
hours: .init(short: "घं", long: "घंटे"),
days: .init(short: "दि", long: "दिन")
),

// Portuguese (pt)
Locale(identifier: "pt"): UnitsLocalization(
seconds: .init(short: "s", long: "Segundos"),
minutes: .init(short: "m", long: "Minutos"),
hours: .init(short: "h", long: "Horas"),
days: .init(short: "d", long: "Dias")
)
]

static var localizationFallback: UnitsLocalization {
return UnitsLocalization(
seconds: .init(short: "s", long: "Seconds"),
minutes: .init(short: "m", long: "Minutes"),
hours: .init(short: "h", long: "Hours"),
days: .init(short: "d", long: "Days")
)
}
}
50 changes: 50 additions & 0 deletions Sources/ComponentsKit/Countdown/Manager/CountdownManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import SwiftUI

class CountdownManager: ObservableObject {
// MARK: - Published Properties

@Published var days: Int = 0
@Published var hours: Int = 0
@Published var minutes: Int = 0
@Published var seconds: Int = 0

// MARK: - Properties

private var timer: Timer?
private var until: Date?

// MARK: - Methods

func start(until: Date) {
self.until = until
self.updateUnitValues()
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.updateUnitValues()
}
}

func stop() {
self.timer?.invalidate()
self.timer = nil
}

private func updateUnitValues() {
guard let until = self.until else { return }

let now = Date()
let calendar = Calendar.current
let components = calendar.dateComponents(
[.day, .hour, .minute, .second],
from: now,
to: until
)
self.days = max(0, components.day ?? 0)
self.hours = max(0, components.hour ?? 0)
self.minutes = max(0, components.minute ?? 0)
self.seconds = max(0, components.second ?? 0)

if now >= until {
self.stop()
}
}
}
16 changes: 16 additions & 0 deletions Sources/ComponentsKit/Countdown/Models/CountdownStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

extension CountdownVM {
/// Defines the visual styles for the countdown component.
public enum Style: Equatable {
case plain
case light
}

/// Defines the units style for the countdown component.
public enum UnitsStyle: Equatable {
case hidden
case bottom
case trailing
}
}
Loading
Loading