Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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,76 @@
import ComponentsKit
import SwiftUI
import UIKit

struct CountdownPreview: View {
@State private var model = CountdownVM()
@State private var selectedLocale = Locale(identifier: "en")

enum BaseStyle: String, CaseIterable {
case plain, light
}

@State private var selectedBaseStyle: BaseStyle = .light
@State private var selectedUnitsPosition: CountdownStyle.UnitsPosition = .bottom

var body: some View {
VStack {
PreviewWrapper(title: "SwiftUI") {
SUCountdown(model: self.model)
}
Form {
ComponentOptionalColorPicker(selection: self.$model.color)
FontPicker(selection: self.$model.font)
SizePicker(selection: self.$model.size)
Picker("Units Position", selection: $selectedUnitsPosition) {
Text("None").tag(CountdownStyle.UnitsPosition.none)
Text("Bottom").tag(CountdownStyle.UnitsPosition.bottom)
Text("Trailing").tag(CountdownStyle.UnitsPosition.trailing)
}
.onChange(of: self.selectedUnitsPosition) { _ in
self.updateModelStyle()
}
Picker("Style", selection: $selectedBaseStyle) {
Text("Plain").tag(BaseStyle.plain)
Text("Light").tag(BaseStyle.light)
}
.onChange(of: self.selectedBaseStyle) { _ in
self.updateModelStyle()
}

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"))
}

DatePicker("Until Date", selection: $model.until, in: Date()..., displayedComponents: [.date, .hourAndMinute])
.datePickerStyle(.compact)
}
}
.onAppear {
self.updateModelStyle()
}
}

private func updateModelStyle() {
switch self.selectedBaseStyle {
case .plain:
self.model.style = .plain(self.selectedUnitsPosition)
case .light:
self.model.style = .light(self.selectedUnitsPosition)
}
}
}

#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
126 changes: 126 additions & 0 deletions Sources/ComponentsKit/Countdown/Localization/UnitsLocalization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import Foundation

// MARK: - UnitsLocalization

public struct UnitsLocalization: Equatable {
public struct UnitItemLocalization: Equatable {
public let short: String
public let long: String

public init(short: String, long: String) {
self.short = short
self.long = long
}
}

// MARK: - Properties

public let seconds: UnitItemLocalization
public let minutes: UnitItemLocalization
public let hours: UnitItemLocalization
public let days: UnitItemLocalization

// MARK: - Initializer

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 {
public 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")
)
]

public static let defaultLocalization: UnitsLocalization = {
return defaultLocalizations[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")
)
}()
}
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()
}
}
}
20 changes: 20 additions & 0 deletions Sources/ComponentsKit/Countdown/Models/CountdownStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

/// Defines the visual styles for the countdown component.
public enum CountdownStyle: Equatable {
public enum UnitsPosition: Equatable {
case none
case bottom
case trailing
}

case plain(UnitsPosition)
case light(UnitsPosition)

public var unitsPosition: UnitsPosition {
switch self {
case .plain(let position), .light(let position):
return position
}
}
}
Loading
Loading