-
Notifications
You must be signed in to change notification settings - Fork 11
SUCountdown #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
SUCountdown #32
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
4a181e0
SUCountdown
VislovIvan 77a2135
Localization
VislovIvan 4af06c6
Some fix
VislovIvan cc435ef
Docs and Localization fix
VislovIvan 4f18c59
pr fix
VislovIvan 01592e5
Fix localization
VislovIvan 1e3b5a2
refactor SUCountdown
VislovIvan 8009cae
removed associated values
VislovIvan 99038f8
localization fix
VislovIvan 70caad0
removed unused code
VislovIvan 532ed5f
minor fix and docs
VislovIvan be87586
CountdownHelpers
VislovIvan 52f547b
layout refactor
VislovIvan 29eecdf
calculateMaxWidth
VislovIvan df990f6
calculateWidth
VislovIvan a8b6a93
bug fix
VislovIvan a05fe2c
fix countdown foreground color and width + refactor
mikhailChelbaev 7fb95c7
refactor: move a method for calculating time width to the model
mikhailChelbaev ece0bc5
Merge branch 'dev' into SUCountdown
mikhailChelbaev 04c51b6
add spacing param to the model and rename `unitsPosition` to `unitsSt…
mikhailChelbaev 399ad68
UKCountdown
VislovIvan c6fa757
swiftlint fix
VislovIvan 5796de3
improve implementation of UKCountdown
mikhailChelbaev 623615e
merge with origin UKCountdown
mikhailChelbaev 440c477
pin stack view to superview edges
mikhailChelbaev 885febe
docs and func shouldUpdateHeight
VislovIvan 2690486
add docs for `UnitsLocalization`
mikhailChelbaev 78baf60
update constraints in `UKCountdown`
mikhailChelbaev 26b1ec1
Merge pull request #35 from componentskit/UKCountdown
mikhailChelbaev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
68 changes: 68 additions & 0 deletions
68
Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CountdownPreview.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import ComponentsKit | ||
| import SwiftUI | ||
| import UIKit | ||
|
|
||
| struct CountdownPreview: View { | ||
| @State private var model = CountdownVM( | ||
| until: Date().addingTimeInterval(3600), | ||
| localization: defaultLocalizations | ||
| ) | ||
| @State private var tempUntil = Date() | ||
| @State private var selectedLocale = Locale(identifier: "en") | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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: $model.unitsPosition) { | ||
| Text("None").tag(UnitsPosition.none) | ||
| Text("Bottom").tag(UnitsPosition.bottom) | ||
| Text("Trailing").tag(UnitsPosition.trailing) | ||
| } | ||
| .onChange(of: model.unitsPosition) { newValue in | ||
| if newValue != .bottom { | ||
| model.style = .plain | ||
| } | ||
| } | ||
| Picker("Style", selection: $model.style) { | ||
| Text("Plain").tag(CountdownStyle.plain) | ||
| Text("Light").tag(CountdownStyle.light) | ||
| } | ||
| .onChange(of: model.style) { newValue in | ||
| if newValue == .light && model.unitsPosition != .bottom { | ||
| model.unitsPosition = .bottom | ||
| } | ||
| } | ||
| Picker("Localization", selection: $selectedLocale) { | ||
| ForEach(defaultLocalizations.keys.sorted(by: { $0.identifier < $1.identifier }), id: \.self) { locale in | ||
| Text(locale.localizedString(forIdentifier: locale.identifier) ?? locale.identifier) | ||
| .tag(locale) | ||
| } | ||
| } | ||
| .onChange(of: selectedLocale) { newLocale in | ||
| model.locale = newLocale | ||
| } | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| DatePicker("Select Date and Time", selection: $tempUntil, in: Date()..., displayedComponents: [.date, .hourAndMinute]) | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .datePickerStyle(.compact) | ||
| } | ||
| } | ||
| .toolbar { | ||
| ToolbarItem(placement: .navigationBarTrailing) { | ||
| if tempUntil > Date() { | ||
| Button("Update Timer") { | ||
| model.until = tempUntil | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #Preview { | ||
| CountdownPreview() | ||
| } | ||
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
113 changes: 113 additions & 0 deletions
113
Sources/ComponentsKit/Countdown/Localization/UnitsLocalization.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| 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 | ||
|
|
||
| public let defaultLocalizations: [Locale: UnitsLocalization] = [ | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Spanish (es) | ||
| 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") | ||
| ), | ||
|
|
||
| // French (fr) | ||
| 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") | ||
| ), | ||
|
|
||
| // German (de) | ||
| 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") | ||
| ), | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Chinese (zh) | ||
| 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") | ||
| ), | ||
|
|
||
| // Japanese (ja) | ||
| Locale(identifier: "zh"): UnitsLocalization( | ||
| seconds: .init(short: "秒", long: "秒"), | ||
| minutes: .init(short: "分", long: "分钟"), | ||
| hours: .init(short: "时", long: "小时"), | ||
| days: .init(short: "天", long: "天") | ||
| ), | ||
|
|
||
| // Russian (ru) | ||
| Locale(identifier: "ja"): UnitsLocalization( | ||
| seconds: .init(short: "秒", long: "秒"), | ||
| minutes: .init(short: "分", long: "分"), | ||
| hours: .init(short: "時", long: "時間"), | ||
| days: .init(short: "日", long: "日") | ||
| ), | ||
|
|
||
| // Arabic (ar) | ||
| Locale(identifier: "ru"): UnitsLocalization( | ||
| seconds: .init(short: "с", long: "Секунд"), | ||
| minutes: .init(short: "м", long: "Минут"), | ||
| hours: .init(short: "ч", long: "Часов"), | ||
| days: .init(short: "д", long: "Дней") | ||
| ), | ||
|
|
||
| // Hindi (hi) | ||
| Locale(identifier: "ar"): UnitsLocalization( | ||
| seconds: .init(short: "ث", long: "ثوانٍ"), | ||
| minutes: .init(short: "د", long: "دقائق"), | ||
| hours: .init(short: "س", long: "ساعات"), | ||
| days: .init(short: "ي", long: "أيام") | ||
| ), | ||
| 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") | ||
| ) | ||
| ] | ||
50 changes: 50 additions & 0 deletions
50
Sources/ComponentsKit/Countdown/Manager/CountdownManager.swift
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
| 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 | ||
| updateTime() | ||
| timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in | ||
| self.updateTime() | ||
| } | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| func stop() { | ||
| timer?.invalidate() | ||
| timer = nil | ||
| } | ||
|
|
||
| private func updateTime() { | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| guard let until = until else { return } | ||
|
|
||
| let now = Date() | ||
| let calendar = Calendar.current | ||
| let components = calendar.dateComponents( | ||
| [.day, .hour, .minute, .second], | ||
| from: now, | ||
| to: until | ||
| ) | ||
| days = max(0, components.day ?? 0) | ||
| hours = max(0, components.hour ?? 0) | ||
| minutes = max(0, components.minute ?? 0) | ||
| seconds = max(0, components.second ?? 0) | ||
|
|
||
| if now >= until { | ||
| stop() | ||
| } | ||
| } | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import Foundation | ||
|
|
||
| /// Defines the visual styles for the countdown component. | ||
| public enum CountdownStyle { | ||
| case plain | ||
| case light | ||
| } |
117 changes: 117 additions & 0 deletions
117
Sources/ComponentsKit/Countdown/Models/CountdownVM.swift
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import SwiftUI | ||
|
|
||
| /// Defines the possible positions for units in the countdown. | ||
| public enum UnitsPosition { | ||
| case none | ||
| case bottom | ||
| case trailing | ||
| } | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// A model that defines the appearance properties for a countdown component. | ||
| public struct CountdownVM: ComponentVM { | ||
| /// The font used for displaying the countdown numbers and units. | ||
| public var font: UniversalFont? | ||
|
|
||
| /// The color of the countdown. | ||
| public var color: ComponentColor? | ||
|
|
||
| /// The predefined size of the countdown. | ||
| /// | ||
| /// Defaults to `.medium`. | ||
| public var size: ComponentSize = .medium | ||
|
|
||
| /// The position of the units relative to the countdown numbers. | ||
| /// | ||
| /// - Default value: `.bottom` | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public var unitsPosition: UnitsPosition = .bottom | ||
|
|
||
| /// The visual style of the countdown component. | ||
| /// | ||
| /// - Default value: `.plain` | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public var style: CountdownStyle = .light | ||
|
|
||
| /// The target date until which the countdown runs. | ||
| /// | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public var until: Date = Date().addingTimeInterval(3600) | ||
|
|
||
| /// The locale used for formatting the countdown. | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public var locale: Locale = .current | ||
|
|
||
| /// Localization. | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public var localization: [Locale: UnitsLocalization] = [:] | ||
|
|
||
| /// Initializes a new instance of `CountdownVM` with default values. | ||
| public init() {} | ||
|
|
||
| /// Initializes a new instance of `CountdownVM` with specified parameters. | ||
| /// | ||
| /// - Parameters: | ||
| /// - until: The target date until which the countdown runs. | ||
| /// - locale: The locale used for formatting the countdown. Defaults to `.current`. | ||
| /// - localization: Localization. | ||
| public init(until: Date, locale: Locale = .current, localization: [Locale: UnitsLocalization] = [:]) { | ||
| self.until = until | ||
| self.locale = locale | ||
| self.localization = localization | ||
| } | ||
| } | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // MARK: - Shared Helpers | ||
|
|
||
| extension CountdownVM { | ||
| var preferredFont: UniversalFont { | ||
| if let font = self.font { | ||
| return font | ||
| } | ||
|
|
||
| switch self.size { | ||
| case .small: | ||
| return UniversalFont.Component.small | ||
| case .medium: | ||
| return UniversalFont.Component.medium | ||
| case .large: | ||
| return UniversalFont.Component.large | ||
| } | ||
| } | ||
| var unitFontSize: CGFloat { | ||
| switch self.size { | ||
| case .small: | ||
| return 6 | ||
| case .medium: | ||
| return 8 | ||
| case .large: | ||
| return 10 | ||
| } | ||
| } | ||
VislovIvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var backgroundColor: UniversalColor { | ||
| if let color { | ||
| return color.main.withOpacity(0.15) | ||
| } else { | ||
| return .init( | ||
| light: .rgba(r: 244, g: 244, b: 245, a: 1.0), | ||
| dark: .rgba(r: 39, g: 39, b: 42, a: 1.0) | ||
| ) | ||
| } | ||
| } | ||
| var foregroundColor: UniversalColor { | ||
| let foregroundColor = self.color?.main ?? .init( | ||
| light: .rgba(r: 0, g: 0, b: 0, a: 1.0), | ||
| dark: .rgba(r: 255, g: 255, b: 255, a: 1.0) | ||
| ) | ||
| return foregroundColor | ||
| } | ||
| var height: CGFloat { | ||
| return switch self.size { | ||
| case .small: 45 | ||
| case .medium: 55 | ||
| case .large: 60 | ||
| } | ||
| } | ||
| var horizontalPadding: CGFloat { | ||
| return switch self.size { | ||
| case .small: 8 | ||
| case .medium: 12 | ||
| case .large: 16 | ||
| } | ||
| } | ||
VislovIvan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.