From ef29db6d829cf43dd9fae5faf391d3c82ccdd06f Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Fri, 22 Nov 2024 18:59:53 +0100 Subject: [PATCH 01/45] 1 --- .../Internal/Helpers/MTimerCallbacks.swift | 14 ++ .../Helpers/MTimerConfigurationManager.swift | 58 +++++ .../Internal/Helpers/MTimerContainer.swift | 22 ++ .../Internal/Helpers/MTimerStateManager.swift | 25 +++ .../Internal/Helpers/MTimerValidator.swift | 22 ++ Sources/Internal/MTimer.swift | 200 ++++++++++-------- .../Protocols/FactoryInitializable.swift | 20 ++ Sources/Public/MTimerID.swift | 12 ++ Sources/Public/MTimerStatus.swift | 8 + Sources/Public/Public+MTime.swift | 2 +- Sources/Public/Public+MTimer.swift | 65 +++--- Tests/MTimeTests.swift | 8 +- Tests/MTimerTests.swift | 95 ++++++--- 13 files changed, 387 insertions(+), 164 deletions(-) create mode 100644 Sources/Internal/Helpers/MTimerCallbacks.swift create mode 100644 Sources/Internal/Helpers/MTimerConfigurationManager.swift create mode 100644 Sources/Internal/Helpers/MTimerContainer.swift create mode 100644 Sources/Internal/Helpers/MTimerStateManager.swift create mode 100644 Sources/Internal/Helpers/MTimerValidator.swift create mode 100644 Sources/Internal/Protocols/FactoryInitializable.swift create mode 100644 Sources/Public/MTimerID.swift create mode 100644 Sources/Public/MTimerStatus.swift diff --git a/Sources/Internal/Helpers/MTimerCallbacks.swift b/Sources/Internal/Helpers/MTimerCallbacks.swift new file mode 100644 index 0000000..555e556 --- /dev/null +++ b/Sources/Internal/Helpers/MTimerCallbacks.swift @@ -0,0 +1,14 @@ +// +// MTimerCallbacks.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +import SwiftUI + +class MTimerCallbacks { + var onRunningTimeChange: ((MTime) -> ())? + var onTimerActivityChange: ((MTimerStatus) -> ())? + var onTimerProgressChange: ((Double) -> ())? +} diff --git a/Sources/Internal/Helpers/MTimerConfigurationManager.swift b/Sources/Internal/Helpers/MTimerConfigurationManager.swift new file mode 100644 index 0000000..f08a065 --- /dev/null +++ b/Sources/Internal/Helpers/MTimerConfigurationManager.swift @@ -0,0 +1,58 @@ +// +// MTimerConfigurationManager.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +import SwiftUI + +class MTimerConfigurationManager { + var initialTime: (start: TimeInterval, end: TimeInterval) = (0, 1) + var publisherTime: TimeInterval = 0 + var publisherTimeTolerance: TimeInterval = 0.4 + var runningTime: TimeInterval = 0 +} + +extension MTimerConfigurationManager { + func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { + initialTime = (startTime, endTime) + runningTime = startTime + } + func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval) { + publisherTime = time + publisherTimeTolerance = tolerance + } + func resetRunningTime() { + runningTime = initialTime.start + } + func skipRunningTime() { + runningTime = initialTime.end + } + func getPublisherTime() -> TimeInterval { + publisherTime == 0 ? max(initialTime.start, initialTime.end) : publisherTime + } + func calculateNewRunningTime(_ timeChange: Any?) { + let timeChange = timeChange as? TimeInterval ?? publisherTime + let newRunningTime = runningTime + timeChange * timeIncrementMultiplier + runningTime = timeIncrementMultiplier == -1 + ? max(newRunningTime, initialTime.end) + : min(newRunningTime, initialTime.end) + } + func calculateTimerProgress() -> Double { + let timerTotalTime = max(initialTime.start, initialTime.end) - min(initialTime.start, initialTime.end) + let timerRunningTime = abs(runningTime - initialTime.start) + return timerRunningTime / timerTotalTime + } + func reset() { + initialTime = (0, 1) + publisherTime = 0 + publisherTimeTolerance = 0.4 + runningTime = 0 + } +} + +extension MTimerConfigurationManager { + var canTimerBeStarted: Bool { runningTime != initialTime.end } + var timeIncrementMultiplier: Double { initialTime.start > initialTime.end ? -1 : 1 } +} diff --git a/Sources/Internal/Helpers/MTimerContainer.swift b/Sources/Internal/Helpers/MTimerContainer.swift new file mode 100644 index 0000000..1d103fb --- /dev/null +++ b/Sources/Internal/Helpers/MTimerContainer.swift @@ -0,0 +1,22 @@ +// +// MTimerContainer.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +@MainActor class MTimerContainer { + private var timers: [MTimer] = [] + static let shared = MTimerContainer() + + private init() { } +} + +extension MTimerContainer { + func getTimer(_ id: MTimerID) -> MTimer? { timers.first(where: { $0.id == id }) } + func register(_ timer: MTimer) { if getTimer(timer.id) == nil { timers.append(timer) }} +} + +extension MTimerContainer { + func resetAll() { timers.forEach { $0.reset() }} +} diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift new file mode 100644 index 0000000..491f4f8 --- /dev/null +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -0,0 +1,25 @@ +// +// MTimerStateManager.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +import SwiftUI + +class MTimerStateManager { + var internalTimer: Timer? + var backgroundTransitionDate: Date? = nil + + deinit { internalTimer?.invalidate() } +} + +extension MTimerStateManager { + func didEnterBackground() { + internalTimer?.invalidate() + backgroundTransitionDate = .init() + } + func willEnterForeground() { + backgroundTransitionDate = nil + } +} diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift new file mode 100644 index 0000000..cceb809 --- /dev/null +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -0,0 +1,22 @@ +// +// MTimerValidator.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +import Foundation + +class MTimerValidator { + func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { + if publisherTime < 0.001 { throw MTimer.Error.publisherTimeCannotBeLessThanOneMillisecond } + } + func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { + if startTime < 0 || endTime < 0 { throw MTimer.Error.timeCannotBeLessThanZero } + if startTime == endTime { throw MTimer.Error.startTimeCannotBeTheSameAsEndTime } + if status == .inProgress && state.backgroundTransitionDate == nil { throw MTimer.Error.timerIsAlreadyRunning } + } + func checkRequirementsForResumingTimer(_ callbacks: MTimerCallbacks) throws { + if callbacks.onRunningTimeChange == nil { throw MTimer.Error.cannotResumeNotInitialisedTimer } + } +} diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index ae43ad9..46d9479 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -1,166 +1,180 @@ // -// MTimer.swift of Timer +// MTimer.swift +// MijickTimer // -// Created by Tomasz Kurylik -// - Twitter: https://twitter.com/tkurylik -// - Mail: tomasz.kurylik@mijick.com -// - GitHub: https://github.com/FulcrumOne +// Created by Alina Petrovska on 11.11.2024. // -// Copyright ©2023 Mijick. Licensed under MIT License. - import SwiftUI -public final class MTimer { - static let shared: MTimer = .init() - - // Current State - var internalTimer: Timer? - var isTimerRunning: Bool = false - var runningTime: TimeInterval = 0 - var backgroundTransitionDate: Date? = nil - - // Configuration - var initialTime: (start: TimeInterval, end: TimeInterval) = (0, 1) - var publisherTime: TimeInterval = 0 - var publisherTimeTolerance: TimeInterval = 0.4 - var onRunningTimeChange: ((MTime) -> ())! - var onTimerActivityChange: ((Bool) -> ())? - var onTimerProgressChange: ((Double) -> ())? - - deinit { internalTimer?.invalidate() } +public final class MTimer: ObservableObject, FactoryInitializable { + private let state = MTimerStateManager() + private let configuration = MTimerConfigurationManager() + private let validator = MTimerValidator() + + let callbacks = MTimerCallbacks() + let id: MTimerID + + @Published public private(set) var timerTime: MTime = .init() + @Published public private(set) var timerState: MTimerStatus = .notStarted + @Published public private(set) var timerProgress: Double = 0 + + init(identifier: MTimerID) { self.id = identifier } } - // MARK: - Initialising Timer extension MTimer { - func checkRequirementsForInitialisingTimer(_ publisherTime: TimeInterval) throws { - if publisherTime < 0.001 { throw Error.publisherTimeCannotBeLessThanOneMillisecond } + func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { + try validator.checkRequirementsForInitializingTimer(publisherTime) } func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { - publisherTime = time - publisherTimeTolerance = tolerance - onRunningTimeChange = completion + configuration.assignInitialPublisherValues(time, tolerance) + callbacks.onRunningTimeChange = completion } } // MARK: - Starting Timer extension MTimer { func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval) throws { - if startTime < 0 || endTime < 0 { throw Error.timeCannotBeLessThanZero } - if startTime == endTime { throw Error.startTimeCannotBeTheSameAsEndTime } - - if isTimerRunning && backgroundTransitionDate == nil { throw Error.timerIsAlreadyRunning } + try validator.checkRequirementsForStartingTimer(startTime, endTime, state, timerState) } func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { - initialTime = (startTime, endTime) - runningTime = startTime + configuration.assignInitialStartValues(startTime, endTime) + resetRunningTime() + resetTimerPublishers() + } + func startTimer() { + handleTimer(status: .inProgress) } - func startTimer() { handleTimer(start: true) } } // MARK: - Resuming Timer extension MTimer { func checkRequirementsForResumingTimer() throws { - if onRunningTimeChange == nil { throw Error.cannotResumeNotInitialisedTimer } + try validator.checkRequirementsForResumingTimer(callbacks) } } -// MARK: - Stopping Timer +// MARK: - Timer State Control extension MTimer { - func stopTimer() { handleTimer(start: false) } + func pauseTimer() { handleTimer(status: .paused) } + func cancelTimer() { handleTimer(status: .cancelled) } + func finishTimer() { handleTimer(status: .finished) } } -// MARK: - Resetting Timer +// MARK: - Reset Timer extension MTimer { - func resetRunningTime() { runningTime = initialTime.start } + func resetTimer() { + configuration.reset() + updateInternalTimer(false) + timerState = .notStarted + updateObservers(false) + resetTimerPublishers() + publishTimerStatus() + } } +// MARK: - Running Time Updates +extension MTimer { + func resetRunningTime() { configuration.resetRunningTime() } + func skipRunningTime() { configuration.skipRunningTime() } +} // MARK: - Handling Timer private extension MTimer { - func handleTimer(start: Bool) { if !start || canTimerBeStarted { - isTimerRunning = start - updateInternalTimer(start) - updateObservers(start) + func handleTimer(status: MTimerStatus) { if status != .inProgress || configuration.canTimerBeStarted { + timerState = status + updateInternalTimer(isTimerRunning) + updateObservers(isTimerRunning) publishTimerStatus() }} } private extension MTimer { - func updateInternalTimer(_ start: Bool) { DispatchQueue.main.async { [self] in switch start { - case true: updateInternalTimerStart() - case false: updateInternalTimerStop() - }}} - func updateObservers(_ start: Bool) { switch start { - case true: addObservers() - case false: removeObservers() + func updateInternalTimer(_ start: Bool) { + switch start { + case true: updateInternalTimerStart() + case false: updateInternalTimerStop() }} + func updateObservers(_ start: Bool) { + switch start { + case true: addObservers() + case false: removeObservers() + } + } } private extension MTimer { func updateInternalTimerStart() { - internalTimer = .scheduledTimer(withTimeInterval: publisherTime, repeats: true, block: handleTimeChange) - internalTimer?.tolerance = publisherTimeTolerance + let publisherTime = configuration.getPublisherTime() + state.internalTimer = .scheduledTimer(timeInterval: publisherTime, + target: self, + selector: #selector(handleTimeChange), + userInfo: nil, + repeats: true) + state.internalTimer?.tolerance = configuration.publisherTimeTolerance updateInternalTimerStartAddToRunLoop() } - func updateInternalTimerStop() { internalTimer?.invalidate() } + func updateInternalTimerStop() { + state.internalTimer?.invalidate() + } } + private extension MTimer { /// **CONTEXT**: On macOS, when the mouse is down in a menu item or other tracking loop, the timer will not start. /// **DECISION**: Adding a timer the RunLoop seems to fix the issue issue. func updateInternalTimerStartAddToRunLoop() { #if os(macOS) - if let internalTimer { RunLoop.main.add(internalTimer, forMode: .common) } + guard let internalTimer = state.internalTimer else { return } + RunLoop.main.add(internalTimer, forMode: .common) #endif } } // MARK: - Handling Time Change private extension MTimer { - func handleTimeChange(_ timeChange: Any? = nil) { - runningTime = calculateNewRunningTime(timeChange as? TimeInterval ?? publisherTime) + @objc func handleTimeChange(_ timeChange: Any) { + configuration.calculateNewRunningTime(timeChange) stopTimerIfNecessary() publishRunningTimeChange() } } private extension MTimer { - func calculateNewRunningTime(_ timeChange: TimeInterval) -> TimeInterval { - let newRunningTime = runningTime + timeChange * timeIncrementMultiplier - return timeIncrementMultiplier == -1 ? max(newRunningTime, initialTime.end) : min(newRunningTime, initialTime.end) - } - func stopTimerIfNecessary() { if !canTimerBeStarted { - stopTimer() + func stopTimerIfNecessary() { if !configuration.canTimerBeStarted { + finishTimer() }} } // MARK: - Handling Background Mode private extension MTimer { func addObservers() { - NotificationCenter.addAppStateNotifications(self, onDidEnterBackground: #selector(didEnterBackgroundNotification), onWillEnterForeground: #selector(willEnterForegroundNotification)) + NotificationCenter + .addAppStateNotifications(self, + onDidEnterBackground: #selector(didEnterBackgroundNotification), + onWillEnterForeground: #selector(willEnterForegroundNotification)) } func removeObservers() { NotificationCenter.removeAppStateChangedNotifications(self) } } private extension MTimer { - @objc func didEnterBackgroundNotification() { - internalTimer?.invalidate() - backgroundTransitionDate = .init() - } @objc func willEnterForegroundNotification() { handleReturnFromBackgroundWhenTimerIsRunning() - backgroundTransitionDate = nil + state.willEnterForeground() + } + @objc func didEnterBackgroundNotification() { + state.didEnterBackground() } } private extension MTimer { - func handleReturnFromBackgroundWhenTimerIsRunning() { if let backgroundTransitionDate, isTimerRunning { + func handleReturnFromBackgroundWhenTimerIsRunning() { + guard let backgroundTransitionDate = state.backgroundTransitionDate, isTimerRunning else { return } let timeChange = Date().timeIntervalSince(backgroundTransitionDate) - + handleTimeChange(timeChange) resumeTimerAfterReturningFromBackground() - }} + } } private extension MTimer { - func resumeTimerAfterReturningFromBackground() { if canTimerBeStarted { + func resumeTimerAfterReturningFromBackground() { if configuration.canTimerBeStarted { updateInternalTimer(true) }} } @@ -171,26 +185,30 @@ private extension MTimer { publishTimerStatusChange() publishRunningTimeChange() } + func resetTimerPublishers() { + guard isNeededReset else { return } + timerState = .notStarted + timerProgress = 0 + timerTime = .init(timeInterval: configuration.initialTime.start) + } } + private extension MTimer { - func publishTimerStatusChange() { DispatchQueue.main.async { [self] in - onTimerActivityChange?(isTimerRunning) + func publishTimerStatusChange() { DispatchQueue.main.async(qos: .userInteractive) { [weak self] in + guard let self else { return } + callbacks.onTimerActivityChange?(timerState) }} - func publishRunningTimeChange() { DispatchQueue.main.async { [self] in - onRunningTimeChange?(.init(timeInterval: runningTime)) - onTimerProgressChange?(calculateTimerProgress()) + func publishRunningTimeChange() { DispatchQueue.main.async(qos: .userInteractive) { [weak self] in + guard let self else { return } + callbacks.onRunningTimeChange?(.init(timeInterval: configuration.runningTime)) + callbacks.onTimerProgressChange?(configuration.calculateTimerProgress()) + timerTime = .init(timeInterval: configuration.runningTime) + timerProgress = configuration.calculateTimerProgress() }} } -private extension MTimer { - func calculateTimerProgress() -> Double { - let timerTotalTime = max(initialTime.start, initialTime.end) - min(initialTime.start, initialTime.end) - let timerRunningTime = abs(runningTime - initialTime.start) - return timerRunningTime / timerTotalTime - } -} -// MARK: - Others +// MARK: - Helpers private extension MTimer { - var canTimerBeStarted: Bool { runningTime != initialTime.end } - var timeIncrementMultiplier: Double { initialTime.start > initialTime.end ? -1 : 1 } + var isTimerRunning: Bool { timerState == .inProgress } + var isNeededReset: Bool { timerState == .finished || timerState == .cancelled || timerState == .notStarted } } diff --git a/Sources/Internal/Protocols/FactoryInitializable.swift b/Sources/Internal/Protocols/FactoryInitializable.swift new file mode 100644 index 0000000..9bd68c1 --- /dev/null +++ b/Sources/Internal/Protocols/FactoryInitializable.swift @@ -0,0 +1,20 @@ +// +// FactoryInitializable.swift +// MijickTimer +// +// Created by Alina Petrovska on 15.11.2024. +// + +import SwiftUI + +@MainActor public protocol FactoryInitializable { + init(_ id: MTimerID) +} + +extension FactoryInitializable where Self: MTimer { + public init(_ id: MTimerID) { + let timer = MTimerContainer.shared.getTimer(id) ?? MTimer(identifier: id) + MTimerContainer.shared.register(timer) + self = timer as! Self + } +} diff --git a/Sources/Public/MTimerID.swift b/Sources/Public/MTimerID.swift new file mode 100644 index 0000000..1737292 --- /dev/null +++ b/Sources/Public/MTimerID.swift @@ -0,0 +1,12 @@ +// +// MTimerID.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +public struct MTimerID: Equatable { + public let rawValue: String + + public init(rawValue: String) { self.rawValue = rawValue } +} diff --git a/Sources/Public/MTimerStatus.swift b/Sources/Public/MTimerStatus.swift new file mode 100644 index 0000000..6ee6e0c --- /dev/null +++ b/Sources/Public/MTimerStatus.swift @@ -0,0 +1,8 @@ +// +// MTimerStatus.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +public enum MTimerStatus { case notStarted, inProgress, cancelled, paused, finished } diff --git a/Sources/Public/Public+MTime.swift b/Sources/Public/Public+MTime.swift index 17056ec..8f8bc27 100644 --- a/Sources/Public/Public+MTime.swift +++ b/Sources/Public/Public+MTime.swift @@ -23,7 +23,7 @@ extension MTime { self.init(timeInterval: timeInterval) } public init(timeInterval: TimeInterval) { - let millisecondsInt = Int(timeInterval * 1000) + let millisecondsInt = timeInterval == .infinity ? Int(Self.max.toTimeInterval() * 1000) : Int(timeInterval * 1000) let hoursDiv = 1000 * 60 * 60 let minutesDiv = 1000 * 60 diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 8afea43..1f9715d 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -11,24 +11,8 @@ import SwiftUI -// MARK: - Creating New Instance Of Timer -extension MTimer { - /// Allows to create multiple instances of a timer. - public static func createNewInstance() -> MTimer { .init() } -} - // MARK: - Initialising Timer extension MTimer { - /// Prepares the timer to start. - /// WARNING: Use the start() method to start the timer. - public static func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { - try shared.publish(every: time, tolerance: tolerance, completion) - } - /// Prepares the timer to start. - /// WARNING: Use the start() method to start the timer. - public static func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { - try shared.publish(every: time, tolerance: tolerance) { currentTime.wrappedValue = $0 } - } /// Prepares the timer to start. /// WARNING: Use the start() method to start the timer. public func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { @@ -37,7 +21,7 @@ extension MTimer { /// Prepares the timer to start. /// WARNING: Use the start() method to start the timer. public func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { - try checkRequirementsForInitialisingTimer(time) + try checkRequirementsForInitializingTimer(time) assignInitialPublisherValues(time, tolerance, completion) return self } @@ -63,22 +47,14 @@ extension MTimer { // MARK: - Stopping Timer extension MTimer { - /// Stops the timer. - public static func stop() { - shared.stop() - } - /// Stops the timer. - public func stop() { - stopTimer() + /// Pause the timer. + public func pause() { + pauseTimer() } } // MARK: - Resuming Timer extension MTimer { - /// Resumes the stopped timer. - public static func resume() throws { - try shared.resume() - } /// Resumes the stopped timer. public func resume() throws { try checkRequirementsForResumingTimer() @@ -86,29 +62,42 @@ extension MTimer { } } -// MARK: - Resetting Timer +// MARK: - Aborting Timer extension MTimer { /// Stops the timer and resets its current time to the initial value. - public static func reset() { - shared.reset() + public func cancel() { + resetRunningTime() + cancelTimer() } - /// Stops the timer and resets its current time to the initial value. +} + +// MARK: - Aborting Timer +extension MTimer { + /// Stops the timer and resets all timer states to default public func reset() { - resetRunningTime() - stopTimer() + resetTimer() + } +} + +// MARK: - Skip Timer +extension MTimer { + /// Stops the timer and skips it's condition to the final state. + public func skip() { + skipRunningTime() + finishTimer() } } // MARK: - Publishing Timer Activity Status extension MTimer { /// Publishes the timer activity changes. - public func onTimerActivityChange(_ action: @escaping (_ isRunning: Bool) -> ()) -> MTimer { - onTimerActivityChange = action + public func onTimerActivityChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { + callbacks.onTimerActivityChange = action return self } /// Publishes the timer activity changes. public func bindTimerStatus(isTimerRunning: Binding) -> MTimer { - onTimerActivityChange { isTimerRunning.wrappedValue = $0 } + onTimerActivityChange { isTimerRunning.wrappedValue = $0 == .inProgress } } } @@ -116,7 +105,7 @@ extension MTimer { extension MTimer { /// Publishes the timer progress changes. public func onTimerProgressChange(_ action: @escaping (_ progress: Double) -> ()) -> MTimer { - onTimerProgressChange = action + callbacks.onTimerProgressChange = action return self } /// Publishes the timer progress changes. diff --git a/Tests/MTimeTests.swift b/Tests/MTimeTests.swift index 1640fa9..cf1673d 100644 --- a/Tests/MTimeTests.swift +++ b/Tests/MTimeTests.swift @@ -17,7 +17,7 @@ final class MTimeTests: XCTestCase {} // MARK: - Initialisation from TimeInterval extension MTimeTests { func testTimeInitialisesCorrectly_1second() { - let time = MTime(1) + let time = MTime(timeInterval: 1) XCTAssertEqual(time.hours, 0) XCTAssertEqual(time.minutes, 0) @@ -25,7 +25,7 @@ extension MTimeTests { XCTAssertEqual(time.milliseconds, 0) } func testTimeInitialisesCorrectly_59seconds120milliseconds() { - let time = MTime(59.12) + let time = MTime(timeInterval: 59.12) XCTAssertEqual(time.hours, 0) XCTAssertEqual(time.minutes, 0) @@ -33,7 +33,7 @@ extension MTimeTests { XCTAssertEqual(time.milliseconds, 120) } func testTimeInitialisesCorrectly_21minutes37seconds() { - let time = MTime(1297) + let time = MTime(timeInterval: 1297) XCTAssertEqual(time.hours, 0) XCTAssertEqual(time.minutes, 21) @@ -41,7 +41,7 @@ extension MTimeTests { XCTAssertEqual(time.milliseconds, 0) } func testTimeInitialisesCorrectly_1hour39minutes17seconds140milliseconds() { - let time = MTime(5957.14) + let time = MTime(timeInterval: 5957.14) XCTAssertEqual(time.hours, 1) XCTAssertEqual(time.minutes, 39) diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index 7910fef..ad5b360 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -12,10 +12,12 @@ import XCTest @testable import MijickTimer -final class MTimerTests: XCTestCase { +@MainActor final class MTimerTests: XCTestCase { var currentTime: TimeInterval = 0 - override func setUp() { MTimer.stop() } + override func setUp() async throws { + MTimerContainer.shared.resetAll() + } } // MARK: - Basics @@ -23,55 +25,71 @@ extension MTimerTests { func testTimerStarts() { try! defaultTimer.start() wait(for: defaultWaitingTime) - + XCTAssertGreaterThan(currentTime, 0) + XCTAssertEqual(.inProgress, timer.timerState) } func testTimerIsCancellable() { try! defaultTimer.start() wait(for: defaultWaitingTime) - MTimer.stop() + timer.cancel() wait(for: defaultWaitingTime) let timeAfterStop = currentTime wait(for: defaultWaitingTime) XCTAssertEqual(timeAfterStop, currentTime) + XCTAssertEqual(.cancelled, timer.timerState) } func testTimerIsResetable() { let startTime: TimeInterval = 3 - try! defaultTimer.start(from: startTime) wait(for: defaultWaitingTime) - + XCTAssertNotEqual(currentTime, startTime) - + + wait(for: defaultWaitingTime) + timer.reset() + wait(for: defaultWaitingTime) + + XCTAssertEqual(0, currentTime) + XCTAssertEqual(0, timer.timerProgress) + XCTAssertEqual(.notStarted, timer.timerState) + } + func testTimerIsSkippable() { + let endTime: TimeInterval = 3 + + try! defaultTimer.start(to: endTime) wait(for: defaultWaitingTime) - MTimer.reset() + timer.skip() wait(for: defaultWaitingTime) - XCTAssertEqual(startTime, currentTime) + XCTAssertEqual(endTime, currentTime) + XCTAssertEqual(1, timer.timerProgress) + XCTAssertEqual(.finished, timer.timerState) } func testTimerCanBeResumed() { try! defaultTimer.start() wait(for: defaultWaitingTime) - MTimer.stop() + timer.pause() let timeAfterStop = currentTime wait(for: defaultWaitingTime) - try! MTimer.resume() + try! timer.resume() wait(for: defaultWaitingTime) XCTAssertNotEqual(timeAfterStop, currentTime) + XCTAssertEqual(.inProgress, timer.timerState) } } // MARK: - Additional Basics extension MTimerTests { func testTimerShouldPublishAccurateValuesWithZeroTolerance() { - try! MTimer - .publish(every: 0.1, tolerance: 0) { self.currentTime = $0.toTimeInterval() } + try! timer + .publish(every: 0.1, tolerance: 0.0) { self.currentTime = $0.toTimeInterval() } .start() wait(for: 0.6) @@ -80,8 +98,10 @@ extension MTimerTests { func testTimerShouldPublishInaccurateValuesWithNonZeroTolerance() { try! defaultTimer.start() wait(for: 1) - - XCTAssertNotEqual(currentTime, 1) + + // usually returns 1.0000000000000002 that is equal to 1.0 + // OLD test XCTAssertNotEqual(currentTime, 1) + XCTAssertEqual(currentTime, 1) } func testTimerCanRunBackwards() { try! defaultTimer.start(from: 3, to: 1) @@ -90,14 +110,14 @@ extension MTimerTests { XCTAssertLessThan(currentTime, 3) } func testTimerPublishesStatuses() { - var statuses: [Bool: Bool] = [true: false, false: false] + var statuses: [MTimerStatus: Bool] = [.inProgress: false, .cancelled: false] try! defaultTimer .onTimerActivityChange { statuses[$0] = true } .start() wait(for: defaultWaitingTime) - MTimer.stop() + timer.cancel() wait(for: defaultWaitingTime) XCTAssertTrue(statuses.values.filter { !$0 }.isEmpty) @@ -137,7 +157,7 @@ extension MTimerTests { func testTimerCanHaveMultipleInstances() { var newTime: TimeInterval = 0 - let newTimer = MTimer.createNewInstance() + let newTimer = MTimer(.multipleInstancesTimer) try! newTimer .publish(every: 0.3) { newTime = $0.toTimeInterval() } .start(from: 10, to: 100) @@ -150,20 +170,20 @@ extension MTimerTests { XCTAssertNotEqual(newTime, currentTime) } func testNewInstanceTimerCanBeStopped() { - let newTimer = MTimer.createNewInstance() + let newTimer = MTimer(.stoppableTimer) try! newTimer - .publish(every: 0.1) { self.currentTime = $0.toTimeInterval() } + .publish(every: 0.1) { print($0); self.currentTime = $0.toTimeInterval() } .start() wait(for: defaultWaitingTime) - newTimer.stop() + newTimer.cancel() wait(for: defaultWaitingTime) let timeAfterStop = currentTime wait(for: defaultWaitingTime) - XCTAssertGreaterThan(currentTime, 0) + XCTAssertEqual(currentTime, 0) XCTAssertEqual(timeAfterStop, currentTime) } } @@ -173,7 +193,7 @@ extension MTimerTests { func testTimerProgressCountsCorrectly_From0To10() { var progress: Double = 0 - try! MTimer + try! timer .publish(every: 0.5, tolerance: 0) { self.currentTime = $0.toTimeInterval() } .onTimerProgressChange { progress = $0 } .start(from: 0, to: 10) @@ -184,7 +204,7 @@ extension MTimerTests { func testTimerProgressCountsCorrectly_From10To29() { var progress: Double = 0 - try! MTimer + try! timer .publish(every: 0.5, tolerance: 0) { self.currentTime = $0.toTimeInterval() } .onTimerProgressChange { progress = $0 } .start(from: 10, to: 29) @@ -195,7 +215,7 @@ extension MTimerTests { func testTimerProgressCountsCorrectly_From31To100() { var progress: Double = 0 - try! MTimer + try! timer .publish(every: 0.5, tolerance: 0) { self.currentTime = $0.toTimeInterval() } .onTimerProgressChange { progress = $0 } .start(from: 31, to: 100) @@ -206,7 +226,7 @@ extension MTimerTests { func testTimerProgressCountsCorrectly_From100To0() { var progress: Double = 0 - try! MTimer + try! timer .publish(every: 0.5, tolerance: 0) { self.currentTime = $0.toTimeInterval() } .onTimerProgressChange { progress = $0 } .start(from: 100, to: 0) @@ -217,20 +237,28 @@ extension MTimerTests { func testTimerProgressCountsCorrectly_From31To14() { var progress: Double = 0 - try! MTimer + try! timer .publish(every: 0.25, tolerance: 0) { self.currentTime = $0.toTimeInterval() } .onTimerProgressChange { progress = $0 } .start(from: 31, to: 14) wait(for: 1) XCTAssertEqual(progress, 1/17) + XCTAssertEqual(timer.timerProgress, 1/17) + } + func timerShouldPublishStatusUpdateAtTheEndIfPublishersNotSetUpped() { + let timer = MTimer(.timerWithoutPublishers) + try! timer.start(to: 1) + wait(for: 1) + + XCTAssertEqual(1.0, timer.timerTime.toTimeInterval()) } } // MARK: - Errors extension MTimerTests { func testTimerCannotBeInitialised_PublishTimeIsTooLess() { - XCTAssertThrowsError(try MTimer.publish(every: 0.0001, { _ in })) { error in + XCTAssertThrowsError(try timer.publish(every: 0.0001, { _ in })) { error in let error = error as! MTimer.Error XCTAssertEqual(error, .publisherTimeCannotBeLessThanOneMillisecond) } @@ -254,7 +282,7 @@ extension MTimerTests { } } func testCannotResumeTimer_WhenTimerIsNotInitialised() { - XCTAssertThrowsError(try MTimer.resume()) { error in + XCTAssertThrowsError(try timer.resume()) { error in let error = error as! MTimer.Error XCTAssertEqual(error, .cannotResumeNotInitialisedTimer) } @@ -284,5 +312,12 @@ private extension MTimerTests { } private extension MTimerTests { var defaultWaitingTime: TimeInterval { 0.15 } - var defaultTimer: MTimer { try! .publish(every: 0.05, tolerance: 0.5) { self.currentTime = $0.toTimeInterval() } } + var defaultTimer: MTimer { try! timer.publish(every: 0.05, tolerance: 0.5) { self.currentTime = $0.toTimeInterval() } } + var timer: MTimer { .init(.testTimer) } +} +fileprivate extension MTimerID { + @MainActor static let testTimer: MTimerID = .init(rawValue: "Test timer") + @MainActor static let timerWithoutPublishers: MTimerID = .init(rawValue: "Timer Without Publishers") + @MainActor static let stoppableTimer: MTimerID = .init(rawValue: "Stoppable Timer") + @MainActor static let multipleInstancesTimer: MTimerID = .init(rawValue: "Multiple Instances") } From b68abb66bf527c4701d237dc638c778c8f819496 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Fri, 22 Nov 2024 19:05:12 +0100 Subject: [PATCH 02/45] 1 --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -------- Package.swift | 5 +++-- 2 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Package.swift b/Package.swift index 85206b0..d71f7cb 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -15,5 +15,6 @@ let package = Package( targets: [ .target(name: "MijickTimer", dependencies: [], path: "Sources"), .testTarget(name: "MijickTimerTests", dependencies: ["MijickTimer"], path: "Tests") - ] + ], + swiftLanguageModes: [.version("6"), .v5] ) From 89f44a815cdcfe0e26d1acbcfa24fd888050433c Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 01:49:08 +0100 Subject: [PATCH 03/45] 1 --- Sources/Public/MTimerStatus.swift | 8 +++++- Sources/Public/Public+MTimer.swift | 46 +++++++++++++++--------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/Sources/Public/MTimerStatus.swift b/Sources/Public/MTimerStatus.swift index 6ee6e0c..d487fa3 100644 --- a/Sources/Public/MTimerStatus.swift +++ b/Sources/Public/MTimerStatus.swift @@ -5,4 +5,10 @@ // Created by Alina Petrovska on 11.11.2024. // -public enum MTimerStatus { case notStarted, inProgress, cancelled, paused, finished } +public enum MTimerStatus { + case notStarted + case inProgress + case cancelled + case paused + case finished +} diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 1f9715d..882ccd6 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -12,15 +12,15 @@ import SwiftUI // MARK: - Initialising Timer -extension MTimer { +public extension MTimer { /// Prepares the timer to start. /// WARNING: Use the start() method to start the timer. - public func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { + func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { try publish(every: time, tolerance: tolerance) { currentTime.wrappedValue = $0 } } /// Prepares the timer to start. /// WARNING: Use the start() method to start the timer. - public func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { + func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { try checkRequirementsForInitializingTimer(time) assignInitialPublisherValues(time, tolerance, completion) return self @@ -28,88 +28,88 @@ extension MTimer { } // MARK: - Starting Timer -extension MTimer { +public extension MTimer { /// Starts the timer using the specified initial values. Can be run backwards - use any "to" value that is greater than "from". - public func start(from startTime: MTime = .zero, to endTime: MTime = .max) throws { + func start(from startTime: MTime = .zero, to endTime: MTime = .max) throws { try start(from: startTime.toTimeInterval(), to: endTime.toTimeInterval()) } /// Starts the timer using the specified initial values. Can be run backwards - use any "to" value that is greater than "from". - public func start(from startTime: TimeInterval = 0, to endTime: TimeInterval = .infinity) throws { + func start(from startTime: TimeInterval = 0, to endTime: TimeInterval = .infinity) throws { try checkRequirementsForStartingTimer(startTime, endTime) assignInitialStartValues(startTime, endTime) startTimer() } /// Starts the timer. - public func start() throws { + func start() throws { try start(from: .zero, to: .infinity) } } // MARK: - Stopping Timer -extension MTimer { +public extension MTimer { /// Pause the timer. - public func pause() { + func pause() { pauseTimer() } } // MARK: - Resuming Timer -extension MTimer { +public extension MTimer { /// Resumes the stopped timer. - public func resume() throws { + func resume() throws { try checkRequirementsForResumingTimer() startTimer() } } // MARK: - Aborting Timer -extension MTimer { +public extension MTimer { /// Stops the timer and resets its current time to the initial value. - public func cancel() { + func cancel() { resetRunningTime() cancelTimer() } } // MARK: - Aborting Timer -extension MTimer { +public extension MTimer { /// Stops the timer and resets all timer states to default - public func reset() { + func reset() { resetTimer() } } // MARK: - Skip Timer -extension MTimer { +public extension MTimer { /// Stops the timer and skips it's condition to the final state. - public func skip() { + func skip() { skipRunningTime() finishTimer() } } // MARK: - Publishing Timer Activity Status -extension MTimer { +public extension MTimer { /// Publishes the timer activity changes. - public func onTimerActivityChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { + func onTimerActivityChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { callbacks.onTimerActivityChange = action return self } /// Publishes the timer activity changes. - public func bindTimerStatus(isTimerRunning: Binding) -> MTimer { + func bindTimerStatus(isTimerRunning: Binding) -> MTimer { onTimerActivityChange { isTimerRunning.wrappedValue = $0 == .inProgress } } } // MARK: - Publishing Timer Progress -extension MTimer { +public extension MTimer { /// Publishes the timer progress changes. - public func onTimerProgressChange(_ action: @escaping (_ progress: Double) -> ()) -> MTimer { + func onTimerProgressChange(_ action: @escaping (_ progress: Double) -> ()) -> MTimer { callbacks.onTimerProgressChange = action return self } /// Publishes the timer progress changes. - public func bindTimerProgress(progress: Binding) -> MTimer { + func bindTimerProgress(progress: Binding) -> MTimer { onTimerProgressChange { progress.wrappedValue = $0 } } } From 305a70e01d8e7018405710efefc92755d2a36553 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:16:33 +0100 Subject: [PATCH 04/45] 1 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d71f7cb..74a0714 100644 --- a/Package.swift +++ b/Package.swift @@ -16,5 +16,5 @@ let package = Package( .target(name: "MijickTimer", dependencies: [], path: "Sources"), .testTarget(name: "MijickTimerTests", dependencies: ["MijickTimer"], path: "Tests") ], - swiftLanguageModes: [.version("6"), .v5] + swiftLanguageModes: [.v6] ) From cddf0db6dc958ebd29c6759e119fb99275d11485 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:19:40 +0100 Subject: [PATCH 05/45] Renames --- Sources/Internal/Helpers/MTimerCallbacks.swift | 2 +- Sources/Internal/MTimer.swift | 16 ++++++++-------- Sources/Public/Public+MTimer.swift | 2 +- Tests/MTimerTests.swift | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerCallbacks.swift b/Sources/Internal/Helpers/MTimerCallbacks.swift index 555e556..83c6ff2 100644 --- a/Sources/Internal/Helpers/MTimerCallbacks.swift +++ b/Sources/Internal/Helpers/MTimerCallbacks.swift @@ -9,6 +9,6 @@ import SwiftUI class MTimerCallbacks { var onRunningTimeChange: ((MTime) -> ())? - var onTimerActivityChange: ((MTimerStatus) -> ())? + var onTimerStatusChange: ((MTimerStatus) -> ())? var onTimerProgressChange: ((Double) -> ())? } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 46d9479..2974cc4 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -16,7 +16,7 @@ public final class MTimer: ObservableObject, FactoryInitializable { let id: MTimerID @Published public private(set) var timerTime: MTime = .init() - @Published public private(set) var timerState: MTimerStatus = .notStarted + @Published public private(set) var timerStatus: MTimerStatus = .notStarted // Status not state @Published public private(set) var timerProgress: Double = 0 init(identifier: MTimerID) { self.id = identifier } @@ -36,7 +36,7 @@ extension MTimer { // MARK: - Starting Timer extension MTimer { func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval) throws { - try validator.checkRequirementsForStartingTimer(startTime, endTime, state, timerState) + try validator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) } func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { configuration.assignInitialStartValues(startTime, endTime) @@ -67,7 +67,7 @@ extension MTimer { func resetTimer() { configuration.reset() updateInternalTimer(false) - timerState = .notStarted + timerStatus = .notStarted updateObservers(false) resetTimerPublishers() publishTimerStatus() @@ -83,7 +83,7 @@ extension MTimer { // MARK: - Handling Timer private extension MTimer { func handleTimer(status: MTimerStatus) { if status != .inProgress || configuration.canTimerBeStarted { - timerState = status + timerStatus = status updateInternalTimer(isTimerRunning) updateObservers(isTimerRunning) publishTimerStatus() @@ -187,7 +187,7 @@ private extension MTimer { } func resetTimerPublishers() { guard isNeededReset else { return } - timerState = .notStarted + timerStatus = .notStarted timerProgress = 0 timerTime = .init(timeInterval: configuration.initialTime.start) } @@ -196,7 +196,7 @@ private extension MTimer { private extension MTimer { func publishTimerStatusChange() { DispatchQueue.main.async(qos: .userInteractive) { [weak self] in guard let self else { return } - callbacks.onTimerActivityChange?(timerState) + callbacks.onTimerStatusChange?(timerStatus) }} func publishRunningTimeChange() { DispatchQueue.main.async(qos: .userInteractive) { [weak self] in guard let self else { return } @@ -209,6 +209,6 @@ private extension MTimer { // MARK: - Helpers private extension MTimer { - var isTimerRunning: Bool { timerState == .inProgress } - var isNeededReset: Bool { timerState == .finished || timerState == .cancelled || timerState == .notStarted } + var isTimerRunning: Bool { timerStatus == .inProgress } + var isNeededReset: Bool { timerStatus == .finished || timerStatus == .cancelled || timerStatus == .notStarted } } diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 882ccd6..4e451c4 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -92,7 +92,7 @@ public extension MTimer { public extension MTimer { /// Publishes the timer activity changes. func onTimerActivityChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { - callbacks.onTimerActivityChange = action + callbacks.onTimerStatusChange = action return self } /// Publishes the timer activity changes. diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index ad5b360..90e49e9 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -27,7 +27,7 @@ extension MTimerTests { wait(for: defaultWaitingTime) XCTAssertGreaterThan(currentTime, 0) - XCTAssertEqual(.inProgress, timer.timerState) + XCTAssertEqual(.inProgress, timer.timerStatus) } func testTimerIsCancellable() { try! defaultTimer.start() @@ -40,7 +40,7 @@ extension MTimerTests { wait(for: defaultWaitingTime) XCTAssertEqual(timeAfterStop, currentTime) - XCTAssertEqual(.cancelled, timer.timerState) + XCTAssertEqual(.cancelled, timer.timerStatus) } func testTimerIsResetable() { let startTime: TimeInterval = 3 @@ -55,7 +55,7 @@ extension MTimerTests { XCTAssertEqual(0, currentTime) XCTAssertEqual(0, timer.timerProgress) - XCTAssertEqual(.notStarted, timer.timerState) + XCTAssertEqual(.notStarted, timer.timerStatus) } func testTimerIsSkippable() { let endTime: TimeInterval = 3 @@ -67,7 +67,7 @@ extension MTimerTests { XCTAssertEqual(endTime, currentTime) XCTAssertEqual(1, timer.timerProgress) - XCTAssertEqual(.finished, timer.timerState) + XCTAssertEqual(.finished, timer.timerStatus) } func testTimerCanBeResumed() { try! defaultTimer.start() @@ -81,7 +81,7 @@ extension MTimerTests { wait(for: defaultWaitingTime) XCTAssertNotEqual(timeAfterStop, currentTime) - XCTAssertEqual(.inProgress, timer.timerState) + XCTAssertEqual(.inProgress, timer.timerStatus) } } From 4fe63eb919e272ecd73e021359e95e9e39699b4f Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:31:45 +0100 Subject: [PATCH 06/45] 1 --- .../Helpers/MTimerConfigurationManager.swift | 48 +++++++++---------- Sources/Internal/MTimer.swift | 16 +++---- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerConfigurationManager.swift b/Sources/Internal/Helpers/MTimerConfigurationManager.swift index f08a065..578b188 100644 --- a/Sources/Internal/Helpers/MTimerConfigurationManager.swift +++ b/Sources/Internal/Helpers/MTimerConfigurationManager.swift @@ -8,51 +8,51 @@ import SwiftUI class MTimerConfigurationManager { - var initialTime: (start: TimeInterval, end: TimeInterval) = (0, 1) - var publisherTime: TimeInterval = 0 - var publisherTimeTolerance: TimeInterval = 0.4 - var runningTime: TimeInterval = 0 + private(set) var time: (start: TimeInterval, end: TimeInterval) = (0, 1) + private(set) var publisherTime: TimeInterval = 0 + private(set) var publisherTimeTolerance: TimeInterval = 0.4 + private(set) var currentTime: TimeInterval = 0 } extension MTimerConfigurationManager { - func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { - initialTime = (startTime, endTime) - runningTime = startTime + func setInitialTime(startTime: TimeInterval, endTime: TimeInterval) { + time = (startTime, endTime) + currentTime = startTime } - func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval) { + func setInitialPublisher(time: TimeInterval, tolerance: TimeInterval) { publisherTime = time publisherTimeTolerance = tolerance } - func resetRunningTime() { - runningTime = initialTime.start + func setCurrentTimeToStart() { + currentTime = time.start } - func skipRunningTime() { - runningTime = initialTime.end + func setCurrentTimeToEnd() { + currentTime = time.end } func getPublisherTime() -> TimeInterval { - publisherTime == 0 ? max(initialTime.start, initialTime.end) : publisherTime + publisherTime == 0 ? max(time.start, time.end) : publisherTime } - func calculateNewRunningTime(_ timeChange: Any?) { + func calculateNewCurrentTime(_ timeChange: Any?) { let timeChange = timeChange as? TimeInterval ?? publisherTime - let newRunningTime = runningTime + timeChange * timeIncrementMultiplier - runningTime = timeIncrementMultiplier == -1 - ? max(newRunningTime, initialTime.end) - : min(newRunningTime, initialTime.end) + let newCurrentTime = currentTime + timeChange * timeIncrementMultiplier + currentTime = timeIncrementMultiplier == -1 + ? max(newCurrentTime, time.end) + : min(newCurrentTime, time.end) } func calculateTimerProgress() -> Double { - let timerTotalTime = max(initialTime.start, initialTime.end) - min(initialTime.start, initialTime.end) - let timerRunningTime = abs(runningTime - initialTime.start) + let timerTotalTime = max(time.start, time.end) - min(time.start, time.end) + let timerRunningTime = abs(currentTime - time.start) return timerRunningTime / timerTotalTime } func reset() { - initialTime = (0, 1) + time = (0, 1) publisherTime = 0 publisherTimeTolerance = 0.4 - runningTime = 0 + currentTime = 0 } } extension MTimerConfigurationManager { - var canTimerBeStarted: Bool { runningTime != initialTime.end } - var timeIncrementMultiplier: Double { initialTime.start > initialTime.end ? -1 : 1 } + var canTimerBeStarted: Bool { currentTime != time.end } + var timeIncrementMultiplier: Double { time.start > time.end ? -1 : 1 } } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 2974cc4..cf9fdaa 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -28,7 +28,7 @@ extension MTimer { try validator.checkRequirementsForInitializingTimer(publisherTime) } func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { - configuration.assignInitialPublisherValues(time, tolerance) + configuration.setInitialPublisher(time: time, tolerance: tolerance) callbacks.onRunningTimeChange = completion } } @@ -39,7 +39,7 @@ extension MTimer { try validator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) } func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { - configuration.assignInitialStartValues(startTime, endTime) + configuration.setInitialTime(startTime: startTime, endTime: endTime) resetRunningTime() resetTimerPublishers() } @@ -76,8 +76,8 @@ extension MTimer { // MARK: - Running Time Updates extension MTimer { - func resetRunningTime() { configuration.resetRunningTime() } - func skipRunningTime() { configuration.skipRunningTime() } + func resetRunningTime() { configuration.setCurrentTimeToStart() } + func skipRunningTime() { configuration.setCurrentTimeToEnd() } } // MARK: - Handling Timer @@ -132,7 +132,7 @@ private extension MTimer { // MARK: - Handling Time Change private extension MTimer { @objc func handleTimeChange(_ timeChange: Any) { - configuration.calculateNewRunningTime(timeChange) + configuration.calculateNewCurrentTime(timeChange) stopTimerIfNecessary() publishRunningTimeChange() } @@ -189,7 +189,7 @@ private extension MTimer { guard isNeededReset else { return } timerStatus = .notStarted timerProgress = 0 - timerTime = .init(timeInterval: configuration.initialTime.start) + timerTime = .init(timeInterval: configuration.time.start) } } @@ -200,9 +200,9 @@ private extension MTimer { }} func publishRunningTimeChange() { DispatchQueue.main.async(qos: .userInteractive) { [weak self] in guard let self else { return } - callbacks.onRunningTimeChange?(.init(timeInterval: configuration.runningTime)) + callbacks.onRunningTimeChange?(.init(timeInterval: configuration.currentTime)) callbacks.onTimerProgressChange?(configuration.calculateTimerProgress()) - timerTime = .init(timeInterval: configuration.runningTime) + timerTime = .init(timeInterval: configuration.currentTime) timerProgress = configuration.calculateTimerProgress() }} } From d8c9224b7c698df5a49a2ca2cbe7aa47b829c653 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:33:53 +0100 Subject: [PATCH 07/45] 1 --- .../Helpers/MTimerConfigurationManager.swift | 23 +++++++++++-------- Sources/Internal/MTimer.swift | 6 ++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerConfigurationManager.swift b/Sources/Internal/Helpers/MTimerConfigurationManager.swift index 578b188..a66d7b9 100644 --- a/Sources/Internal/Helpers/MTimerConfigurationManager.swift +++ b/Sources/Internal/Helpers/MTimerConfigurationManager.swift @@ -14,6 +14,7 @@ class MTimerConfigurationManager { private(set) var currentTime: TimeInterval = 0 } +// MARK: Setters extension MTimerConfigurationManager { func setInitialTime(startTime: TimeInterval, endTime: TimeInterval) { time = (startTime, endTime) @@ -29,21 +30,13 @@ extension MTimerConfigurationManager { func setCurrentTimeToEnd() { currentTime = time.end } - func getPublisherTime() -> TimeInterval { - publisherTime == 0 ? max(time.start, time.end) : publisherTime - } - func calculateNewCurrentTime(_ timeChange: Any?) { + func setNewCurrentTime(_ timeChange: Any?) { let timeChange = timeChange as? TimeInterval ?? publisherTime let newCurrentTime = currentTime + timeChange * timeIncrementMultiplier currentTime = timeIncrementMultiplier == -1 ? max(newCurrentTime, time.end) : min(newCurrentTime, time.end) } - func calculateTimerProgress() -> Double { - let timerTotalTime = max(time.start, time.end) - min(time.start, time.end) - let timerRunningTime = abs(currentTime - time.start) - return timerRunningTime / timerTotalTime - } func reset() { time = (0, 1) publisherTime = 0 @@ -52,6 +45,18 @@ extension MTimerConfigurationManager { } } +// MARK: Getters +extension MTimerConfigurationManager { + func getPublisherTime() -> TimeInterval { + publisherTime == 0 ? max(time.start, time.end) : publisherTime + } + func getTimerProgress() -> Double { + let timerTotalTime = max(time.start, time.end) - min(time.start, time.end) + let timerRunningTime = abs(currentTime - time.start) + return timerRunningTime / timerTotalTime + } +} + extension MTimerConfigurationManager { var canTimerBeStarted: Bool { currentTime != time.end } var timeIncrementMultiplier: Double { time.start > time.end ? -1 : 1 } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index cf9fdaa..c06d513 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -132,7 +132,7 @@ private extension MTimer { // MARK: - Handling Time Change private extension MTimer { @objc func handleTimeChange(_ timeChange: Any) { - configuration.calculateNewCurrentTime(timeChange) + configuration.setNewCurrentTime(timeChange) stopTimerIfNecessary() publishRunningTimeChange() } @@ -201,9 +201,9 @@ private extension MTimer { func publishRunningTimeChange() { DispatchQueue.main.async(qos: .userInteractive) { [weak self] in guard let self else { return } callbacks.onRunningTimeChange?(.init(timeInterval: configuration.currentTime)) - callbacks.onTimerProgressChange?(configuration.calculateTimerProgress()) + callbacks.onTimerProgressChange?(configuration.getTimerProgress()) timerTime = .init(timeInterval: configuration.currentTime) - timerProgress = configuration.calculateTimerProgress() + timerProgress = configuration.getTimerProgress() }} } From eddd62a1f348810ccd85984083b58c14a21da57a Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:36:49 +0100 Subject: [PATCH 08/45] 1 --- .../Helpers/MTimerConfigurationManager.swift | 29 ++++++++++--------- Sources/Internal/MTimer.swift | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerConfigurationManager.swift b/Sources/Internal/Helpers/MTimerConfigurationManager.swift index a66d7b9..3c3c1a6 100644 --- a/Sources/Internal/Helpers/MTimerConfigurationManager.swift +++ b/Sources/Internal/Helpers/MTimerConfigurationManager.swift @@ -14,13 +14,25 @@ class MTimerConfigurationManager { private(set) var currentTime: TimeInterval = 0 } +// MARK: Getters +extension MTimerConfigurationManager { + func getPublisherTime() -> TimeInterval { + publisherTime == 0 ? max(time.start, time.end) : publisherTime + } + func getTimerProgress() -> Double { + let timerTotalTime = max(time.start, time.end) - min(time.start, time.end) + let timerRunningTime = abs(currentTime - time.start) + return timerRunningTime / timerTotalTime + } +} + // MARK: Setters extension MTimerConfigurationManager { func setInitialTime(startTime: TimeInterval, endTime: TimeInterval) { time = (startTime, endTime) currentTime = startTime } - func setInitialPublisher(time: TimeInterval, tolerance: TimeInterval) { + func setPublishers(time: TimeInterval, tolerance: TimeInterval) { publisherTime = time publisherTimeTolerance = tolerance } @@ -44,20 +56,11 @@ extension MTimerConfigurationManager { currentTime = 0 } } - -// MARK: Getters -extension MTimerConfigurationManager { - func getPublisherTime() -> TimeInterval { - publisherTime == 0 ? max(time.start, time.end) : publisherTime - } - func getTimerProgress() -> Double { - let timerTotalTime = max(time.start, time.end) - min(time.start, time.end) - let timerRunningTime = abs(currentTime - time.start) - return timerRunningTime / timerTotalTime - } +private extension MTimerConfigurationManager { + var timeIncrementMultiplier: Double { time.start > time.end ? -1 : 1 } } +// MARK: Helpers extension MTimerConfigurationManager { var canTimerBeStarted: Bool { currentTime != time.end } - var timeIncrementMultiplier: Double { time.start > time.end ? -1 : 1 } } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index c06d513..cffb786 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -28,7 +28,7 @@ extension MTimer { try validator.checkRequirementsForInitializingTimer(publisherTime) } func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { - configuration.setInitialPublisher(time: time, tolerance: tolerance) + configuration.setPublishers(time: time, tolerance: tolerance) callbacks.onRunningTimeChange = completion } } From 11e712925f5ff88acef0a5bb700f498bb50b5d22 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:39:18 +0100 Subject: [PATCH 09/45] 1 --- Sources/Internal/Helpers/MTimerContainer.swift | 11 ++++------- Sources/Internal/Protocols/FactoryInitializable.swift | 4 ++-- Tests/MTimerTests.swift | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerContainer.swift b/Sources/Internal/Helpers/MTimerContainer.swift index 1d103fb..fb08c36 100644 --- a/Sources/Internal/Helpers/MTimerContainer.swift +++ b/Sources/Internal/Helpers/MTimerContainer.swift @@ -6,17 +6,14 @@ // @MainActor class MTimerContainer { - private var timers: [MTimer] = [] - static let shared = MTimerContainer() - - private init() { } + private static var timers: [MTimer] = [] } extension MTimerContainer { - func getTimer(_ id: MTimerID) -> MTimer? { timers.first(where: { $0.id == id }) } - func register(_ timer: MTimer) { if getTimer(timer.id) == nil { timers.append(timer) }} + static func getTimer(_ id: MTimerID) -> MTimer? { timers.first(where: { $0.id == id }) } + static func register(_ timer: MTimer) { if getTimer(timer.id) == nil { timers.append(timer) }} } extension MTimerContainer { - func resetAll() { timers.forEach { $0.reset() }} + static func resetAll() { timers.forEach { $0.reset() }} } diff --git a/Sources/Internal/Protocols/FactoryInitializable.swift b/Sources/Internal/Protocols/FactoryInitializable.swift index 9bd68c1..6ada06f 100644 --- a/Sources/Internal/Protocols/FactoryInitializable.swift +++ b/Sources/Internal/Protocols/FactoryInitializable.swift @@ -13,8 +13,8 @@ import SwiftUI extension FactoryInitializable where Self: MTimer { public init(_ id: MTimerID) { - let timer = MTimerContainer.shared.getTimer(id) ?? MTimer(identifier: id) - MTimerContainer.shared.register(timer) + let timer = MTimerContainer.getTimer(id) ?? MTimer(identifier: id) + MTimerContainer.register(timer) self = timer as! Self } } diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index 90e49e9..ebc4542 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -16,7 +16,7 @@ import XCTest var currentTime: TimeInterval = 0 override func setUp() async throws { - MTimerContainer.shared.resetAll() + MTimerContainer.resetAll() } } From 3b613c14fcb51cb3511458b5b7a7ec088db1dcf0 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:42:27 +0100 Subject: [PATCH 10/45] 1 --- Sources/Internal/Helpers/MTimerContainer.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerContainer.swift b/Sources/Internal/Helpers/MTimerContainer.swift index fb08c36..398e4e8 100644 --- a/Sources/Internal/Helpers/MTimerContainer.swift +++ b/Sources/Internal/Helpers/MTimerContainer.swift @@ -10,8 +10,16 @@ } extension MTimerContainer { - static func getTimer(_ id: MTimerID) -> MTimer? { timers.first(where: { $0.id == id }) } - static func register(_ timer: MTimer) { if getTimer(timer.id) == nil { timers.append(timer) }} + static func getTimer(_ id: MTimerID) -> MTimer? { + timers.first(where: { $0.id == id }) + } +} + +extension MTimerContainer { + static func register(_ timer: MTimer) { + guard getTimer(timer.id) == nil else { return } + timers.append(timer) + } } extension MTimerContainer { From dfe6f38b389ec2cd71b82d9bfde7b75ade6026e1 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:51:20 +0100 Subject: [PATCH 11/45] 1 --- Sources/Internal/Helpers/MTimerStateManager.swift | 7 +++++++ Sources/Internal/MTimer.swift | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift index 491f4f8..fa28d44 100644 --- a/Sources/Internal/Helpers/MTimerStateManager.swift +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -15,6 +15,13 @@ class MTimerStateManager { } extension MTimerStateManager { + func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { + internalTimer = .scheduledTimer(timeInterval: timeInterval, + target: target, + selector: selector, + userInfo: nil, + repeats: true) + } func didEnterBackground() { internalTimer?.invalidate() backgroundTransitionDate = .init() diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index cffb786..f335561 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -105,11 +105,7 @@ private extension MTimer { private extension MTimer { func updateInternalTimerStart() { let publisherTime = configuration.getPublisherTime() - state.internalTimer = .scheduledTimer(timeInterval: publisherTime, - target: self, - selector: #selector(handleTimeChange), - userInfo: nil, - repeats: true) + state.runTimer(self, publisherTime, #selector(handleTimeChange)) state.internalTimer?.tolerance = configuration.publisherTimeTolerance updateInternalTimerStartAddToRunLoop() } From d08a1b8856efe2796453fe751e702b17cb3fb04e Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:55:22 +0100 Subject: [PATCH 12/45] 1 --- Sources/Internal/Helpers/MTimerStateManager.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift index fa28d44..4dc612a 100644 --- a/Sources/Internal/Helpers/MTimerStateManager.swift +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -16,12 +16,17 @@ class MTimerStateManager { extension MTimerStateManager { func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { - internalTimer = .scheduledTimer(timeInterval: timeInterval, - target: target, - selector: selector, - userInfo: nil, - repeats: true) + internalTimer = .scheduledTimer( + timeInterval: timeInterval, + target: target, + selector: selector, + userInfo: nil, + repeats: true + ) } +} + +extension MTimerStateManager { func didEnterBackground() { internalTimer?.invalidate() backgroundTransitionDate = .init() From 63992a877295d74eca5c365bb807414042e1933d Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:57:23 +0100 Subject: [PATCH 13/45] 1 --- Sources/Internal/Helpers/MTimerStateManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift index 4dc612a..12c9ade 100644 --- a/Sources/Internal/Helpers/MTimerStateManager.swift +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -15,7 +15,7 @@ class MTimerStateManager { } extension MTimerStateManager { - func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { + func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { // TODO: create separate func to hadnle it internalTimer = .scheduledTimer( timeInterval: timeInterval, target: target, From b3b90016f4165922bd0474f02dfca3ce48480153 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 13:59:15 +0100 Subject: [PATCH 14/45] 1 --- Sources/Internal/Helpers/MTimerStateManager.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift index 12c9ade..00225ae 100644 --- a/Sources/Internal/Helpers/MTimerStateManager.swift +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -10,12 +10,10 @@ import SwiftUI class MTimerStateManager { var internalTimer: Timer? var backgroundTransitionDate: Date? = nil - - deinit { internalTimer?.invalidate() } } extension MTimerStateManager { - func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { // TODO: create separate func to hadnle it + func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { // TODO: create separate func to handle it internalTimer = .scheduledTimer( timeInterval: timeInterval, target: target, @@ -26,6 +24,7 @@ extension MTimerStateManager { } } +// MARK: App State Handle extension MTimerStateManager { func didEnterBackground() { internalTimer?.invalidate() From 6b5f0d405a8a3e023b87370e1b7df3354c1aca94 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:10:16 +0100 Subject: [PATCH 15/45] 1 --- Sources/Internal/Helpers/MTimerValidator.swift | 10 +++++----- Sources/Public/Public+MTimer.swift | 1 + ...c+MTimer.Error.swift => Public+MTimerError.swift} | 6 +++--- Tests/MTimerTests.swift | 12 ++++++------ 4 files changed, 15 insertions(+), 14 deletions(-) rename Sources/Public/{Public+MTimer.Error.swift => Public+MTimerError.swift} (82%) diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index cceb809..15d88f4 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -9,14 +9,14 @@ import Foundation class MTimerValidator { func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { - if publisherTime < 0.001 { throw MTimer.Error.publisherTimeCannotBeLessThanOneMillisecond } + if publisherTime < 0.001 { throw MTimerError.publisherTimeCannotBeLessThanOneMillisecond } } func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { - if startTime < 0 || endTime < 0 { throw MTimer.Error.timeCannotBeLessThanZero } - if startTime == endTime { throw MTimer.Error.startTimeCannotBeTheSameAsEndTime } - if status == .inProgress && state.backgroundTransitionDate == nil { throw MTimer.Error.timerIsAlreadyRunning } + if startTime < 0 || endTime < 0 { throw MTimerError.timeCannotBeLessThanZero } + if startTime == endTime { throw MTimerError.startTimeCannotBeTheSameAsEndTime } + if status == .inProgress && state.backgroundTransitionDate == nil { throw MTimerError.timerIsAlreadyRunning } } func checkRequirementsForResumingTimer(_ callbacks: MTimerCallbacks) throws { - if callbacks.onRunningTimeChange == nil { throw MTimer.Error.cannotResumeNotInitialisedTimer } + if callbacks.onRunningTimeChange == nil { throw MTimerError.cannotResumeNotInitialisedTimer } } } diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 4e451c4..a53d08b 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -21,6 +21,7 @@ public extension MTimer { /// Prepares the timer to start. /// WARNING: Use the start() method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { + // TODO: add and test assert instead of throwing error try checkRequirementsForInitializingTimer(time) assignInitialPublisherValues(time, tolerance, completion) return self diff --git a/Sources/Public/Public+MTimer.Error.swift b/Sources/Public/Public+MTimerError.swift similarity index 82% rename from Sources/Public/Public+MTimer.Error.swift rename to Sources/Public/Public+MTimerError.swift index 751e6b2..90c6cee 100644 --- a/Sources/Public/Public+MTimer.Error.swift +++ b/Sources/Public/Public+MTimerError.swift @@ -1,5 +1,5 @@ // -// Public+MTimer.Error.swift of Timer +// Public+MTimerError.swift of Timer // // Created by Tomasz Kurylik // - Twitter: https://twitter.com/tkurylik @@ -11,9 +11,9 @@ import Foundation -extension MTimer { public enum Error: Swift.Error { +public enum MTimerError: Error { case publisherTimeCannotBeLessThanOneMillisecond case startTimeCannotBeTheSameAsEndTime, timeCannotBeLessThanZero case cannotResumeNotInitialisedTimer case timerIsAlreadyRunning -}} +} diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index ebc4542..2204fbd 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -259,31 +259,31 @@ extension MTimerTests { extension MTimerTests { func testTimerCannotBeInitialised_PublishTimeIsTooLess() { XCTAssertThrowsError(try timer.publish(every: 0.0001, { _ in })) { error in - let error = error as! MTimer.Error + let error = error as! MTimerError XCTAssertEqual(error, .publisherTimeCannotBeLessThanOneMillisecond) } } func testTimerDoesNotStart_StartTimeEqualsEndTime() { XCTAssertThrowsError(try defaultTimer.start(from: 0, to: 0)) { error in - let error = error as! MTimer.Error + let error = error as! MTimerError XCTAssertEqual(error, .startTimeCannotBeTheSameAsEndTime) } } func testTimerDoesNotStart_StartTimeIsLessThanZero() { XCTAssertThrowsError(try defaultTimer.start(from: -10, to: 5)) { error in - let error = error as! MTimer.Error + let error = error as! MTimerError XCTAssertEqual(error, .timeCannotBeLessThanZero) } } func testTimerDoesNotStart_EndTimeIsLessThanZero() { XCTAssertThrowsError(try defaultTimer.start(from: 10, to: -15)) { error in - let error = error as! MTimer.Error + let error = error as! MTimerError XCTAssertEqual(error, .timeCannotBeLessThanZero) } } func testCannotResumeTimer_WhenTimerIsNotInitialised() { XCTAssertThrowsError(try timer.resume()) { error in - let error = error as! MTimer.Error + let error = error as! MTimerError XCTAssertEqual(error, .cannotResumeNotInitialisedTimer) } } @@ -291,7 +291,7 @@ extension MTimerTests { try! defaultTimer.start() XCTAssertThrowsError(try defaultTimer.start()) { error in - let error = error as! MTimer.Error + let error = error as! MTimerError XCTAssertEqual(error, .timerIsAlreadyRunning) } } From 552a0f8230b8709b3ed827a0f232907446eb261c Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:24:09 +0100 Subject: [PATCH 16/45] 1 --- Sources/Internal/Helpers/MTimerContainer.swift | 14 +++++++------- .../Internal/Protocols/FactoryInitializable.swift | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerContainer.swift b/Sources/Internal/Helpers/MTimerContainer.swift index 398e4e8..0700726 100644 --- a/Sources/Internal/Helpers/MTimerContainer.swift +++ b/Sources/Internal/Helpers/MTimerContainer.swift @@ -10,15 +10,15 @@ } extension MTimerContainer { - static func getTimer(_ id: MTimerID) -> MTimer? { - timers.first(where: { $0.id == id }) + static func register(_ timer: MTimer) -> MTimer { + if let timer = getTimer(timer.id) { return timer } + timers.append(timer) + return timer } } - -extension MTimerContainer { - static func register(_ timer: MTimer) { - guard getTimer(timer.id) == nil else { return } - timers.append(timer) +private extension MTimerContainer { + static func getTimer(_ id: MTimerID) -> MTimer? { + timers.first(where: { $0.id == id }) } } diff --git a/Sources/Internal/Protocols/FactoryInitializable.swift b/Sources/Internal/Protocols/FactoryInitializable.swift index 6ada06f..2e7a52e 100644 --- a/Sources/Internal/Protocols/FactoryInitializable.swift +++ b/Sources/Internal/Protocols/FactoryInitializable.swift @@ -13,8 +13,8 @@ import SwiftUI extension FactoryInitializable where Self: MTimer { public init(_ id: MTimerID) { - let timer = MTimerContainer.getTimer(id) ?? MTimer(identifier: id) - MTimerContainer.register(timer) - self = timer as! Self + let timer = MTimer(identifier: id) + let registeredTimer = MTimerContainer.register(timer) + self = registeredTimer as! Self } } From 7a67e980a6bbbef5242af72bd6733f45581ccfc3 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:26:17 +0100 Subject: [PATCH 17/45] 1 --- Sources/Public/MTimerID.swift | 2 +- Tests/MTimerTests.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Public/MTimerID.swift b/Sources/Public/MTimerID.swift index 1737292..6a23c63 100644 --- a/Sources/Public/MTimerID.swift +++ b/Sources/Public/MTimerID.swift @@ -5,7 +5,7 @@ // Created by Alina Petrovska on 11.11.2024. // -public struct MTimerID: Equatable { +public struct MTimerID: Equatable, Sendable { public let rawValue: String public init(rawValue: String) { self.rawValue = rawValue } diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index 2204fbd..bc222d9 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -316,8 +316,8 @@ private extension MTimerTests { var timer: MTimer { .init(.testTimer) } } fileprivate extension MTimerID { - @MainActor static let testTimer: MTimerID = .init(rawValue: "Test timer") - @MainActor static let timerWithoutPublishers: MTimerID = .init(rawValue: "Timer Without Publishers") - @MainActor static let stoppableTimer: MTimerID = .init(rawValue: "Stoppable Timer") - @MainActor static let multipleInstancesTimer: MTimerID = .init(rawValue: "Multiple Instances") + static let testTimer: MTimerID = .init(rawValue: "Test timer") + static let timerWithoutPublishers: MTimerID = .init(rawValue: "Timer Without Publishers") + static let stoppableTimer: MTimerID = .init(rawValue: "Stoppable Timer") + static let multipleInstancesTimer: MTimerID = .init(rawValue: "Multiple Instances") } From 82e33e642275497260ceab4e0af6834bba274b24 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:35:27 +0100 Subject: [PATCH 18/45] 1 --- Sources/Public/Public+MTime.swift | 1 + Tests/MTimerTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Public/Public+MTime.swift b/Sources/Public/Public+MTime.swift index 8f8bc27..3239589 100644 --- a/Sources/Public/Public+MTime.swift +++ b/Sources/Public/Public+MTime.swift @@ -23,6 +23,7 @@ extension MTime { self.init(timeInterval: timeInterval) } public init(timeInterval: TimeInterval) { + // TODO: Refactor let millisecondsInt = timeInterval == .infinity ? Int(Self.max.toTimeInterval() * 1000) : Int(timeInterval * 1000) let hoursDiv = 1000 * 60 * 60 diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index bc222d9..ce2fc54 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -98,7 +98,7 @@ extension MTimerTests { func testTimerShouldPublishInaccurateValuesWithNonZeroTolerance() { try! defaultTimer.start() wait(for: 1) - + // TODO: TIMER run not from main actor / therad. How much takes resources // usually returns 1.0000000000000002 that is equal to 1.0 // OLD test XCTAssertNotEqual(currentTime, 1) XCTAssertEqual(currentTime, 1) @@ -312,7 +312,7 @@ private extension MTimerTests { } private extension MTimerTests { var defaultWaitingTime: TimeInterval { 0.15 } - var defaultTimer: MTimer { try! timer.publish(every: 0.05, tolerance: 0.5) { self.currentTime = $0.toTimeInterval() } } + var defaultTimer: MTimer { try! timer.publish(every: 0.05, tolerance: 20) { self.currentTime = $0.toTimeInterval() } } var timer: MTimer { .init(.testTimer) } } fileprivate extension MTimerID { From fcd8bd7078e7ef4af026d9e618d20c69b324c777 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:38:25 +0100 Subject: [PATCH 19/45] 1 --- Sources/Internal/MTimer.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index f335561..b10cd60 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -8,16 +8,16 @@ import SwiftUI public final class MTimer: ObservableObject, FactoryInitializable { - private let state = MTimerStateManager() - private let configuration = MTimerConfigurationManager() - private let validator = MTimerValidator() + @Published public private(set) var timerTime: MTime = .init() + @Published public private(set) var timerStatus: MTimerStatus = .notStarted + @Published public private(set) var timerProgress: Double = 0 let callbacks = MTimerCallbacks() let id: MTimerID - @Published public private(set) var timerTime: MTime = .init() - @Published public private(set) var timerStatus: MTimerStatus = .notStarted // Status not state - @Published public private(set) var timerProgress: Double = 0 + private let state = MTimerStateManager() + private let configuration = MTimerConfigurationManager() + private let validator = MTimerValidator() init(identifier: MTimerID) { self.id = identifier } } From 7b0d42635c1cdda45c5f09355d08803d5f940641 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:40:09 +0100 Subject: [PATCH 20/45] 1 --- Sources/Internal/Helpers/MTimerValidator.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index 15d88f4..554e2fe 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -8,15 +8,15 @@ import Foundation class MTimerValidator { - func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { + static func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { if publisherTime < 0.001 { throw MTimerError.publisherTimeCannotBeLessThanOneMillisecond } } - func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { + static func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { if startTime < 0 || endTime < 0 { throw MTimerError.timeCannotBeLessThanZero } if startTime == endTime { throw MTimerError.startTimeCannotBeTheSameAsEndTime } if status == .inProgress && state.backgroundTransitionDate == nil { throw MTimerError.timerIsAlreadyRunning } } - func checkRequirementsForResumingTimer(_ callbacks: MTimerCallbacks) throws { + static func checkRequirementsForResumingTimer(_ callbacks: MTimerCallbacks) throws { if callbacks.onRunningTimeChange == nil { throw MTimerError.cannotResumeNotInitialisedTimer } } } From af9afadfbd478ebc1937b11ec535f86c0bbcd4e6 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:40:13 +0100 Subject: [PATCH 21/45] 1 --- Sources/Internal/MTimer.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index b10cd60..dda95aa 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -17,7 +17,6 @@ public final class MTimer: ObservableObject, FactoryInitializable { private let state = MTimerStateManager() private let configuration = MTimerConfigurationManager() - private let validator = MTimerValidator() init(identifier: MTimerID) { self.id = identifier } } @@ -25,7 +24,7 @@ public final class MTimer: ObservableObject, FactoryInitializable { // MARK: - Initialising Timer extension MTimer { func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { - try validator.checkRequirementsForInitializingTimer(publisherTime) + try MTimerValidator.checkRequirementsForInitializingTimer(publisherTime) } func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { configuration.setPublishers(time: time, tolerance: tolerance) @@ -36,7 +35,7 @@ extension MTimer { // MARK: - Starting Timer extension MTimer { func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval) throws { - try validator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) + try MTimerValidator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) } func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { configuration.setInitialTime(startTime: startTime, endTime: endTime) @@ -51,7 +50,7 @@ extension MTimer { // MARK: - Resuming Timer extension MTimer { func checkRequirementsForResumingTimer() throws { - try validator.checkRequirementsForResumingTimer(callbacks) + try MTimerValidator.checkRequirementsForResumingTimer(callbacks) } } From 383c2e9d699ec976367e1a4fb243ed21e61b4cc9 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sat, 23 Nov 2024 14:46:18 +0100 Subject: [PATCH 22/45] 1 --- Sources/Internal/MTimer.swift | 22 ++++------------------ Sources/Public/Public+MTimer.swift | 8 ++++---- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index dda95aa..68c11a2 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -12,21 +12,17 @@ public final class MTimer: ObservableObject, FactoryInitializable { @Published public private(set) var timerStatus: MTimerStatus = .notStarted @Published public private(set) var timerProgress: Double = 0 - let callbacks = MTimerCallbacks() let id: MTimerID - - private let state = MTimerStateManager() - private let configuration = MTimerConfigurationManager() + let callbacks = MTimerCallbacks() + let state = MTimerStateManager() + let configuration = MTimerConfigurationManager() init(identifier: MTimerID) { self.id = identifier } } // MARK: - Initialising Timer extension MTimer { - func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { - try MTimerValidator.checkRequirementsForInitializingTimer(publisherTime) - } - func assignInitialPublisherValues(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { + func setupPublishers(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { configuration.setPublishers(time: time, tolerance: tolerance) callbacks.onRunningTimeChange = completion } @@ -34,9 +30,6 @@ extension MTimer { // MARK: - Starting Timer extension MTimer { - func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval) throws { - try MTimerValidator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) - } func assignInitialStartValues(_ startTime: TimeInterval, _ endTime: TimeInterval) { configuration.setInitialTime(startTime: startTime, endTime: endTime) resetRunningTime() @@ -47,13 +40,6 @@ extension MTimer { } } -// MARK: - Resuming Timer -extension MTimer { - func checkRequirementsForResumingTimer() throws { - try MTimerValidator.checkRequirementsForResumingTimer(callbacks) - } -} - // MARK: - Timer State Control extension MTimer { func pauseTimer() { handleTimer(status: .paused) } diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index a53d08b..404d70b 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -22,8 +22,8 @@ public extension MTimer { /// WARNING: Use the start() method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { // TODO: add and test assert instead of throwing error - try checkRequirementsForInitializingTimer(time) - assignInitialPublisherValues(time, tolerance, completion) + try MTimerValidator.checkRequirementsForInitializingTimer(time) + setupPublishers(time, tolerance, completion) return self } } @@ -36,7 +36,7 @@ public extension MTimer { } /// Starts the timer using the specified initial values. Can be run backwards - use any "to" value that is greater than "from". func start(from startTime: TimeInterval = 0, to endTime: TimeInterval = .infinity) throws { - try checkRequirementsForStartingTimer(startTime, endTime) + try MTimerValidator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) assignInitialStartValues(startTime, endTime) startTimer() } @@ -58,7 +58,7 @@ public extension MTimer { public extension MTimer { /// Resumes the stopped timer. func resume() throws { - try checkRequirementsForResumingTimer() + try MTimerValidator.checkRequirementsForResumingTimer(callbacks) startTimer() } } From 5a6624d496baa5991ff7ad9759ef6b2ced65750f Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Sun, 24 Nov 2024 21:38:35 +0100 Subject: [PATCH 23/45] 1 --- .../Helpers/MTimerConfigurationManager.swift | 2 +- .../Internal/Helpers/MTimerStateManager.swift | 31 +++++++++++++++++-- .../Internal/Helpers/MTimerValidator.swift | 2 +- Sources/Internal/MTimer.swift | 24 ++------------ Sources/Public/Public+MTime.swift | 6 ++-- Sources/Public/Public+MTimer.swift | 8 ++--- Tests/MTimerTests.swift | 8 ++--- 7 files changed, 44 insertions(+), 37 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerConfigurationManager.swift b/Sources/Internal/Helpers/MTimerConfigurationManager.swift index 3c3c1a6..64704f0 100644 --- a/Sources/Internal/Helpers/MTimerConfigurationManager.swift +++ b/Sources/Internal/Helpers/MTimerConfigurationManager.swift @@ -43,7 +43,7 @@ extension MTimerConfigurationManager { currentTime = time.end } func setNewCurrentTime(_ timeChange: Any?) { - let timeChange = timeChange as? TimeInterval ?? publisherTime + let timeChange = timeChange as? TimeInterval ?? getPublisherTime() let newCurrentTime = currentTime + timeChange * timeIncrementMultiplier currentTime = timeIncrementMultiplier == -1 ? max(newCurrentTime, time.end) diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift index 00225ae..c7e0eac 100644 --- a/Sources/Internal/Helpers/MTimerStateManager.swift +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -8,20 +8,45 @@ import SwiftUI class MTimerStateManager { - var internalTimer: Timer? + private var internalTimer: Timer? var backgroundTransitionDate: Date? = nil } +// MARK: Run Timer extension MTimerStateManager { - func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ selector: Selector) { // TODO: create separate func to handle it + func runTimer(_ configuration: MTimerConfigurationManager, _ target: Any, _ completion: Selector) { + stopTimer() + runTimer(target, configuration.getPublisherTime(), completion) + setTolerance(configuration.publisherTimeTolerance) + updateInternalTimerStartAddToRunLoop() + } +} +private extension MTimerStateManager { + func runTimer(_ target: Any, _ timeInterval: TimeInterval, _ completion: Selector) { internalTimer = .scheduledTimer( timeInterval: timeInterval, target: target, - selector: selector, + selector: completion, userInfo: nil, repeats: true ) } + func setTolerance(_ value: TimeInterval) { + internalTimer?.tolerance = value + } + func updateInternalTimerStartAddToRunLoop() { + #if os(macOS) + guard let internalTimer = internalTimer else { return } + RunLoop.main.add(internalTimer, forMode: .common) + #endif + } +} + +// MARK: Stop Timer +extension MTimerStateManager { + func stopTimer() { + internalTimer?.invalidate() + } } // MARK: App State Handle diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index 554e2fe..096d1a5 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -9,7 +9,7 @@ import Foundation class MTimerValidator { static func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { - if publisherTime < 0.001 { throw MTimerError.publisherTimeCannotBeLessThanOneMillisecond } + if publisherTime < 0 { throw MTimerError.publisherTimeCannotBeLessThanOneMillisecond } } static func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { if startTime < 0 || endTime < 0 { throw MTimerError.timeCannotBeLessThanZero } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 68c11a2..08aba96 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -12,7 +12,7 @@ public final class MTimer: ObservableObject, FactoryInitializable { @Published public private(set) var timerStatus: MTimerStatus = .notStarted @Published public private(set) var timerProgress: Double = 0 - let id: MTimerID + public let id: MTimerID let callbacks = MTimerCallbacks() let state = MTimerStateManager() let configuration = MTimerConfigurationManager() @@ -88,26 +88,8 @@ private extension MTimer { } } private extension MTimer { - func updateInternalTimerStart() { - let publisherTime = configuration.getPublisherTime() - state.runTimer(self, publisherTime, #selector(handleTimeChange)) - state.internalTimer?.tolerance = configuration.publisherTimeTolerance - updateInternalTimerStartAddToRunLoop() - } - func updateInternalTimerStop() { - state.internalTimer?.invalidate() - } -} - -private extension MTimer { - /// **CONTEXT**: On macOS, when the mouse is down in a menu item or other tracking loop, the timer will not start. - /// **DECISION**: Adding a timer the RunLoop seems to fix the issue issue. - func updateInternalTimerStartAddToRunLoop() { - #if os(macOS) - guard let internalTimer = state.internalTimer else { return } - RunLoop.main.add(internalTimer, forMode: .common) - #endif - } + func updateInternalTimerStart() { state.runTimer(configuration, self, #selector(handleTimeChange)) } + func updateInternalTimerStop() { state.stopTimer() } } // MARK: - Handling Time Change diff --git a/Sources/Public/Public+MTime.swift b/Sources/Public/Public+MTime.swift index 3239589..d89b219 100644 --- a/Sources/Public/Public+MTime.swift +++ b/Sources/Public/Public+MTime.swift @@ -23,8 +23,7 @@ extension MTime { self.init(timeInterval: timeInterval) } public init(timeInterval: TimeInterval) { - // TODO: Refactor - let millisecondsInt = timeInterval == .infinity ? Int(Self.max.toTimeInterval() * 1000) : Int(timeInterval * 1000) + let millisecondsInt = timeInterval == .infinity ? Self.maxMilliseconds : Int(timeInterval * 1000) let hoursDiv = 1000 * 60 * 60 let minutesDiv = 1000 * 60 @@ -39,6 +38,9 @@ extension MTime { public static var zero: MTime { .init() } public static var max: MTime { .init(hours: 60 * 60 * 24 * 365 * 100) } } +private extension MTime { + static var maxMilliseconds: Int { Int(max.toTimeInterval() * 1000) } +} // MARK: - Converting to TimeInterval extension MTime { diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 404d70b..c019d7a 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -18,10 +18,10 @@ public extension MTimer { func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { try publish(every: time, tolerance: tolerance) { currentTime.wrappedValue = $0 } } + /// Prepares the timer to start. /// WARNING: Use the start() method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { - // TODO: add and test assert instead of throwing error try MTimerValidator.checkRequirementsForInitializingTimer(time) setupPublishers(time, tolerance, completion) return self @@ -91,14 +91,14 @@ public extension MTimer { // MARK: - Publishing Timer Activity Status public extension MTimer { - /// Publishes the timer activity changes. - func onTimerActivityChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { + /// Publishes the timer status changes. + func onTimerStatusChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { callbacks.onTimerStatusChange = action return self } /// Publishes the timer activity changes. func bindTimerStatus(isTimerRunning: Binding) -> MTimer { - onTimerActivityChange { isTimerRunning.wrappedValue = $0 == .inProgress } + onTimerStatusChange { isTimerRunning.wrappedValue = $0 == .inProgress } } } diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index ce2fc54..ec4abf7 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -98,9 +98,7 @@ extension MTimerTests { func testTimerShouldPublishInaccurateValuesWithNonZeroTolerance() { try! defaultTimer.start() wait(for: 1) - // TODO: TIMER run not from main actor / therad. How much takes resources - // usually returns 1.0000000000000002 that is equal to 1.0 - // OLD test XCTAssertNotEqual(currentTime, 1) + XCTAssertEqual(currentTime, 1) } func testTimerCanRunBackwards() { @@ -113,7 +111,7 @@ extension MTimerTests { var statuses: [MTimerStatus: Bool] = [.inProgress: false, .cancelled: false] try! defaultTimer - .onTimerActivityChange { statuses[$0] = true } + .onTimerStatusChange { statuses[$0] = true } .start() wait(for: defaultWaitingTime) @@ -258,7 +256,7 @@ extension MTimerTests { // MARK: - Errors extension MTimerTests { func testTimerCannotBeInitialised_PublishTimeIsTooLess() { - XCTAssertThrowsError(try timer.publish(every: 0.0001, { _ in })) { error in + XCTAssertThrowsError(try timer.publish(every: -1, { _ in })) { error in let error = error as! MTimerError XCTAssertEqual(error, .publisherTimeCannotBeLessThanOneMillisecond) } From 0eaa34ffb52bcfa6e6455eefcf9a2a6af413bf22 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Mon, 25 Nov 2024 19:05:51 +0100 Subject: [PATCH 24/45] 1 --- .../Internal/Helpers/MTimerValidator.swift | 2 +- Sources/Internal/MTimer.swift | 11 ++++++ .../Public+MTimerError.swift | 2 +- .../Enumerations/Public+MTimerStatus.swift | 39 +++++++++++++++++++ Sources/Public/MTimerStatus.swift | 14 ------- .../Public+MTimerID.swift} | 3 +- Sources/Public/Public+MTime.swift | 5 ++- Sources/Public/Public+MTimer.swift | 39 ++++++++++++++----- Tests/MTimerTests.swift | 2 +- 9 files changed, 89 insertions(+), 28 deletions(-) rename Sources/Public/{ => Enumerations}/Public+MTimerError.swift (89%) create mode 100644 Sources/Public/Enumerations/Public+MTimerStatus.swift delete mode 100644 Sources/Public/MTimerStatus.swift rename Sources/Public/{MTimerID.swift => Models/Public+MTimerID.swift} (67%) diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index 096d1a5..bb93fa8 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -9,7 +9,7 @@ import Foundation class MTimerValidator { static func checkRequirementsForInitializingTimer(_ publisherTime: TimeInterval) throws { - if publisherTime < 0 { throw MTimerError.publisherTimeCannotBeLessThanOneMillisecond } + if publisherTime < 0 { throw MTimerError.publisherTimeCannotBeLessThanZero } } static func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { if startTime < 0 || endTime < 0 { throw MTimerError.timeCannotBeLessThanZero } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 08aba96..54fa487 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -8,11 +8,22 @@ import SwiftUI public final class MTimer: ObservableObject, FactoryInitializable { + /// Timer time updates publisher. + /// - important: The frequency for updating this property can be configured with function ``MTimer/publish(every:tolerance:currentTime:)`` + /// - NOTE: By default, updates are triggered each time the timer status is marked as **finished** @Published public private(set) var timerTime: MTime = .init() + + /// Timer status updates publisher. @Published public private(set) var timerStatus: MTimerStatus = .notStarted + + /// Timer progress publisher. + /// - important: The frequency for updating this property can be configured with function ``MTimer/publish(every:tolerance:currentTime:)`` + /// - NOTE: By default, updates are triggered each time the timer status is marked as **finished** @Published public private(set) var timerProgress: Double = 0 + /// Unique id that enables an access to the registered timer from any location. public let id: MTimerID + let callbacks = MTimerCallbacks() let state = MTimerStateManager() let configuration = MTimerConfigurationManager() diff --git a/Sources/Public/Public+MTimerError.swift b/Sources/Public/Enumerations/Public+MTimerError.swift similarity index 89% rename from Sources/Public/Public+MTimerError.swift rename to Sources/Public/Enumerations/Public+MTimerError.swift index 90c6cee..bbf6b3c 100644 --- a/Sources/Public/Public+MTimerError.swift +++ b/Sources/Public/Enumerations/Public+MTimerError.swift @@ -12,7 +12,7 @@ import Foundation public enum MTimerError: Error { - case publisherTimeCannotBeLessThanOneMillisecond + case publisherTimeCannotBeLessThanZero case startTimeCannotBeTheSameAsEndTime, timeCannotBeLessThanZero case cannotResumeNotInitialisedTimer case timerIsAlreadyRunning diff --git a/Sources/Public/Enumerations/Public+MTimerStatus.swift b/Sources/Public/Enumerations/Public+MTimerStatus.swift new file mode 100644 index 0000000..0fd2325 --- /dev/null +++ b/Sources/Public/Enumerations/Public+MTimerStatus.swift @@ -0,0 +1,39 @@ +// +// Public+MTimerStatus.swift +// MijickTimer +// +// Created by Alina Petrovska on 11.11.2024. +// + +public enum MTimerStatus { + /// Initial timer state + /// ## Triggered by methods + /// - ``MTimer/reset()`` + case notStarted + + /// Timer in progress + /// + /// ## Triggered by methods + /// - ``MTimer/start()`` + /// - ``MTimer/start(from:to:)-1mvp1`` + /// - ``MTimer/resume()`` + case inProgress + + /// Timer was stopped/cancelled + /// + /// ## Triggered by methods + /// - ``MTimer/cancel()`` + case cancelled + + /// Timer is in a pause + /// + /// ## Triggered by methods + /// - ``MTimer/pause()`` + case paused + + /// Timer was finished by running out of time or by calling function + /// + /// ## Triggered by methods + /// - ``MTimer/skip()`` + case finished +} diff --git a/Sources/Public/MTimerStatus.swift b/Sources/Public/MTimerStatus.swift deleted file mode 100644 index d487fa3..0000000 --- a/Sources/Public/MTimerStatus.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// MTimerStatus.swift -// MijickTimer -// -// Created by Alina Petrovska on 11.11.2024. -// - -public enum MTimerStatus { - case notStarted - case inProgress - case cancelled - case paused - case finished -} diff --git a/Sources/Public/MTimerID.swift b/Sources/Public/Models/Public+MTimerID.swift similarity index 67% rename from Sources/Public/MTimerID.swift rename to Sources/Public/Models/Public+MTimerID.swift index 6a23c63..2e582ad 100644 --- a/Sources/Public/MTimerID.swift +++ b/Sources/Public/Models/Public+MTimerID.swift @@ -1,10 +1,11 @@ // -// MTimerID.swift +// Public+MTimerID.swift // MijickTimer // // Created by Alina Petrovska on 11.11.2024. // +/// Unique id that enables an access to the registered timer from any location. public struct MTimerID: Equatable, Sendable { public let rawValue: String diff --git a/Sources/Public/Public+MTime.swift b/Sources/Public/Public+MTime.swift index d89b219..6b58623 100644 --- a/Sources/Public/Public+MTime.swift +++ b/Sources/Public/Public+MTime.swift @@ -44,6 +44,7 @@ private extension MTime { // MARK: - Converting to TimeInterval extension MTime { + /// Converts MTime values to TimeInterval public func toTimeInterval() -> TimeInterval { let hoursAsTimeInterval = 60 * 60 * TimeInterval(hours) let minutesAsTimeInterval = 60 * TimeInterval(minutes) @@ -56,7 +57,9 @@ extension MTime { // MARK: - Converting To String extension MTime { - /// Converts the object to a string representation. Output can be customised by modifying the formatter block. + /// Converts the object to a string representation. Output can be customized by modifying the formatter block. + /// - Parameters: + /// - formatter: A formatter that creates string representations of quantities of time public func toString(_ formatter: (DateComponentsFormatter) -> DateComponentsFormatter = { $0 }) -> String { formatter(defaultTimeFormatter).string(from: toTimeInterval()) ?? "" } diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index c019d7a..8b4d188 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -13,14 +13,26 @@ import SwiftUI // MARK: - Initialising Timer public extension MTimer { - /// Prepares the timer to start. - /// WARNING: Use the start() method to start the timer. + /// Configure the interval at which the timer's status will be published. + /// + /// - Parameters: + /// - time: the interval of publishing timer state + /// - tolerance: The amount of time after the scheduled fire date that the timer may fire. + /// - currentTime: Binding value that will be updated every **time** interval + /// + /// - WARNING: Use the ``start()`` method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { try publish(every: time, tolerance: tolerance) { currentTime.wrappedValue = $0 } } - /// Prepares the timer to start. - /// WARNING: Use the start() method to start the timer. + /// Configure the interval at which the timer's status will be published. + /// + /// - Parameters: + /// - time: the interval of publishing timer state + /// - tolerance: The amount of time after the scheduled fire date that the timer may fire. + /// - completion: Completion block that will be executed every **time** interval + /// + /// - WARNING: Use the ``start()`` method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { try MTimerValidator.checkRequirementsForInitializingTimer(time) setupPublishers(time, tolerance, completion) @@ -30,17 +42,22 @@ public extension MTimer { // MARK: - Starting Timer public extension MTimer { - /// Starts the timer using the specified initial values. Can be run backwards - use any "to" value that is greater than "from". + /// Starts the timer using the specified initial values. + /// + /// - Note: Can be run backwards - use any **to** value that is greater than **from**. func start(from startTime: MTime = .zero, to endTime: MTime = .max) throws { try start(from: startTime.toTimeInterval(), to: endTime.toTimeInterval()) } - /// Starts the timer using the specified initial values. Can be run backwards - use any "to" value that is greater than "from". + + /// Starts the timer using the specified initial values. + /// + /// - Note: Can be run backwards - use any **to** value that is greater than **from**. func start(from startTime: TimeInterval = 0, to endTime: TimeInterval = .infinity) throws { try MTimerValidator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) assignInitialStartValues(startTime, endTime) startTimer() } - /// Starts the timer. + /// Starts infinity timer func start() throws { try start(from: .zero, to: .infinity) } @@ -56,7 +73,7 @@ public extension MTimer { // MARK: - Resuming Timer public extension MTimer { - /// Resumes the stopped timer. + /// Resumes the paused timer. func resume() throws { try MTimerValidator.checkRequirementsForResumingTimer(callbacks) startTimer() @@ -82,7 +99,7 @@ public extension MTimer { // MARK: - Skip Timer public extension MTimer { - /// Stops the timer and skips it's condition to the final state. + /// Stops the timer and it's condition to the final state. func skip() { skipRunningTime() finishTimer() @@ -92,11 +109,13 @@ public extension MTimer { // MARK: - Publishing Timer Activity Status public extension MTimer { /// Publishes the timer status changes. + /// - Note: To configure the interval at which the timer's status will be published use the method ``publish(every:tolerance:currentTime:)`` func onTimerStatusChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { callbacks.onTimerStatusChange = action return self } /// Publishes the timer activity changes. + /// - Note: To configure the interval at which the timer's status will be published use the method ``publish(every:tolerance:currentTime:)`` func bindTimerStatus(isTimerRunning: Binding) -> MTimer { onTimerStatusChange { isTimerRunning.wrappedValue = $0 == .inProgress } } @@ -105,11 +124,13 @@ public extension MTimer { // MARK: - Publishing Timer Progress public extension MTimer { /// Publishes the timer progress changes. + /// - Note: To configure the interval at which the timer's progress will be published use the method ``publish(every:tolerance:currentTime:)`` func onTimerProgressChange(_ action: @escaping (_ progress: Double) -> ()) -> MTimer { callbacks.onTimerProgressChange = action return self } /// Publishes the timer progress changes. + /// - Note: To configure the interval at which the timer's progress will be published use the method ``publish(every:tolerance:currentTime:)`` func bindTimerProgress(progress: Binding) -> MTimer { onTimerProgressChange { progress.wrappedValue = $0 } } diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index ec4abf7..5f6a938 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -258,7 +258,7 @@ extension MTimerTests { func testTimerCannotBeInitialised_PublishTimeIsTooLess() { XCTAssertThrowsError(try timer.publish(every: -1, { _ in })) { error in let error = error as! MTimerError - XCTAssertEqual(error, .publisherTimeCannotBeLessThanOneMillisecond) + XCTAssertEqual(error, .publisherTimeCannotBeLessThanZero) } } func testTimerDoesNotStart_StartTimeEqualsEndTime() { From f564d37d02cc43a81fc5de9c02529c19757c6689 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Mon, 25 Nov 2024 19:19:36 +0100 Subject: [PATCH 25/45] 1 --- Sources/Internal/MTimer.swift | 5 +++-- Sources/Internal/Protocols/FactoryInitializable.swift | 5 ++--- Sources/Public/Public+MTimer.swift | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 54fa487..894305e 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -8,12 +8,12 @@ import SwiftUI public final class MTimer: ObservableObject, FactoryInitializable { - /// Timer time updates publisher. + /// Timer time publisher. /// - important: The frequency for updating this property can be configured with function ``MTimer/publish(every:tolerance:currentTime:)`` /// - NOTE: By default, updates are triggered each time the timer status is marked as **finished** @Published public private(set) var timerTime: MTime = .init() - /// Timer status updates publisher. + /// Timer status publisher. @Published public private(set) var timerStatus: MTimerStatus = .notStarted /// Timer progress publisher. @@ -36,6 +36,7 @@ extension MTimer { func setupPublishers(_ time: TimeInterval, _ tolerance: TimeInterval, _ completion: @escaping (MTime) -> ()) { configuration.setPublishers(time: time, tolerance: tolerance) callbacks.onRunningTimeChange = completion + resetTimerPublishers() } } diff --git a/Sources/Internal/Protocols/FactoryInitializable.swift b/Sources/Internal/Protocols/FactoryInitializable.swift index 2e7a52e..de384c0 100644 --- a/Sources/Internal/Protocols/FactoryInitializable.swift +++ b/Sources/Internal/Protocols/FactoryInitializable.swift @@ -7,11 +7,10 @@ import SwiftUI -@MainActor public protocol FactoryInitializable { - init(_ id: MTimerID) -} +@MainActor public protocol FactoryInitializable { } extension FactoryInitializable where Self: MTimer { + /// Registers or returns registered Timer public init(_ id: MTimerID) { let timer = MTimer(identifier: id) let registeredTimer = MTimerContainer.register(timer) diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 8b4d188..a83b3fe 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -20,7 +20,7 @@ public extension MTimer { /// - tolerance: The amount of time after the scheduled fire date that the timer may fire. /// - currentTime: Binding value that will be updated every **time** interval /// - /// - WARNING: Use the ``start()`` method to start the timer. + /// - WARNING: Use the ``start()`` or ``start(from:to:)-1mvp1`` methods to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { try publish(every: time, tolerance: tolerance) { currentTime.wrappedValue = $0 } } @@ -32,7 +32,7 @@ public extension MTimer { /// - tolerance: The amount of time after the scheduled fire date that the timer may fire. /// - completion: Completion block that will be executed every **time** interval /// - /// - WARNING: Use the ``start()`` method to start the timer. + /// - WARNING: Use the ``start()`` or ``start(from:to:)-1mvp1`` method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { try MTimerValidator.checkRequirementsForInitializingTimer(time) setupPublishers(time, tolerance, completion) @@ -110,14 +110,14 @@ public extension MTimer { public extension MTimer { /// Publishes the timer status changes. /// - Note: To configure the interval at which the timer's status will be published use the method ``publish(every:tolerance:currentTime:)`` - func onTimerStatusChange(_ action: @escaping (_ isRunning: MTimerStatus) -> ()) -> MTimer { + func onTimerStatusChange(_ action: @escaping (_ timerStatus: MTimerStatus) -> ()) -> MTimer { callbacks.onTimerStatusChange = action return self } /// Publishes the timer activity changes. /// - Note: To configure the interval at which the timer's status will be published use the method ``publish(every:tolerance:currentTime:)`` - func bindTimerStatus(isTimerRunning: Binding) -> MTimer { - onTimerStatusChange { isTimerRunning.wrappedValue = $0 == .inProgress } + func bindTimerStatus(timerStatus: Binding) -> MTimer { + onTimerStatusChange { timerStatus.wrappedValue = $0 } } } From 4b11347ccda6b95912f38c69858addea4cf76be1 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Mon, 25 Nov 2024 19:28:28 +0100 Subject: [PATCH 26/45] 1 --- Sources/Public/Public+MTimer.swift | 43 +++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index a83b3fe..31d05f0 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -42,22 +42,51 @@ public extension MTimer { // MARK: - Starting Timer public extension MTimer { - /// Starts the timer using the specified initial values. - /// - /// - Note: Can be run backwards - use any **to** value that is greater than **from**. + /** + Starts the timer using the specified initial values. + + - Note: Can be run backwards - use any **to** value that is greater than **from**. + + ### Up going timer + ```swift + MTimer(.exampleId) + .start(from: .zero, to: .init(seconds: 10)) + ``` + + ### Down going timer + ```swift + MTimer(.exampleId) + .start(from: .init(seconds: 10), to: .zero) + ``` + */ func start(from startTime: MTime = .zero, to endTime: MTime = .max) throws { try start(from: startTime.toTimeInterval(), to: endTime.toTimeInterval()) } - /// Starts the timer using the specified initial values. - /// - /// - Note: Can be run backwards - use any **to** value that is greater than **from**. + /** + Starts the timer using the specified initial values. + + - Note: Can be run backwards - use any **to** value that is greater than **from**. + + ### Up going timer + ```swift + MTimer(.exampleId) + .start(from: .zero, to: 10) + ``` + + ### Down going timer + ```swift + MTimer(.exampleId) + .start(from: 10, to: .zero) + ``` + */ func start(from startTime: TimeInterval = 0, to endTime: TimeInterval = .infinity) throws { try MTimerValidator.checkRequirementsForStartingTimer(startTime, endTime, state, timerStatus) assignInitialStartValues(startTime, endTime) startTimer() } - /// Starts infinity timer + + /// Starts up going infinity timer func start() throws { try start(from: .zero, to: .infinity) } From cb58f85d83c6a233b3ff4d77c15fadc086385811 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Thu, 28 Nov 2024 11:20:25 +0100 Subject: [PATCH 27/45] 1 --- Sources/Internal/Helpers/MTimerValidator.swift | 4 ++++ Sources/Public/Enumerations/Public+MTimerError.swift | 2 +- Sources/Public/Public+MTimer.swift | 11 ++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index bb93fa8..3f698fc 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -19,4 +19,8 @@ class MTimerValidator { static func checkRequirementsForResumingTimer(_ callbacks: MTimerCallbacks) throws { if callbacks.onRunningTimeChange == nil { throw MTimerError.cannotResumeNotInitialisedTimer } } + static func isCanBeSkipped(_ timerStatus: MTimerStatus) throws { + if timerStatus == .inProgress || timerStatus == .paused { return } + throw MTimerError.timerIsNotStarted + } } diff --git a/Sources/Public/Enumerations/Public+MTimerError.swift b/Sources/Public/Enumerations/Public+MTimerError.swift index bbf6b3c..c1da998 100644 --- a/Sources/Public/Enumerations/Public+MTimerError.swift +++ b/Sources/Public/Enumerations/Public+MTimerError.swift @@ -15,5 +15,5 @@ public enum MTimerError: Error { case publisherTimeCannotBeLessThanZero case startTimeCannotBeTheSameAsEndTime, timeCannotBeLessThanZero case cannotResumeNotInitialisedTimer - case timerIsAlreadyRunning + case timerIsAlreadyRunning, timerIsNotStarted } diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 31d05f0..6713abe 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -33,7 +33,7 @@ public extension MTimer { /// - completion: Completion block that will be executed every **time** interval /// /// - WARNING: Use the ``start()`` or ``start(from:to:)-1mvp1`` method to start the timer. - func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> ()) throws -> MTimer { + func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> () = { _ in }) throws -> MTimer { try MTimerValidator.checkRequirementsForInitializingTimer(time) setupPublishers(time, tolerance, completion) return self @@ -50,13 +50,13 @@ public extension MTimer { ### Up going timer ```swift MTimer(.exampleId) - .start(from: .zero, to: .init(seconds: 10)) + .start(from: .zero, to: MTime(seconds: 10)) ``` ### Down going timer ```swift MTimer(.exampleId) - .start(from: .init(seconds: 10), to: .zero) + .start(from: MTime(seconds: 10), to: .zero) ``` */ func start(from startTime: MTime = .zero, to endTime: MTime = .max) throws { @@ -128,8 +128,9 @@ public extension MTimer { // MARK: - Skip Timer public extension MTimer { - /// Stops the timer and it's condition to the final state. - func skip() { + /// Stops the timer and updates its status to the final state + func skip() throws { + try MTimerValidator.isCanBeSkipped(timerStatus) skipRunningTime() finishTimer() } From e4580d22498628e960ed800e727c745ac17a9c92 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Tue, 3 Dec 2024 12:17:09 +0100 Subject: [PATCH 28/45] Adding some constraints on status change --- Sources/Internal/MTimer.swift | 4 ++-- Sources/Public/Enumerations/Public+MTimerStatus.swift | 8 ++++++++ Sources/Public/Public+MTimer.swift | 3 +++ Tests/MTimerTests.swift | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 894305e..dcf4e3a 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -184,6 +184,6 @@ private extension MTimer { // MARK: - Helpers private extension MTimer { - var isTimerRunning: Bool { timerStatus == .inProgress } - var isNeededReset: Bool { timerStatus == .finished || timerStatus == .cancelled || timerStatus == .notStarted } + var isTimerRunning: Bool { timerStatus.isTimerRunning } + var isNeededReset: Bool { timerStatus.isNeededReset } } diff --git a/Sources/Public/Enumerations/Public+MTimerStatus.swift b/Sources/Public/Enumerations/Public+MTimerStatus.swift index 0fd2325..3f7c737 100644 --- a/Sources/Public/Enumerations/Public+MTimerStatus.swift +++ b/Sources/Public/Enumerations/Public+MTimerStatus.swift @@ -37,3 +37,11 @@ public enum MTimerStatus { /// - ``MTimer/skip()`` case finished } + + +extension MTimerStatus { + var isTimerRunning: Bool { self == .inProgress } + var isNeededReset: Bool { self == .notStarted || self == .finished || self == .cancelled } + var isSkippable: Bool { self == .inProgress || self == .paused } + var isCancellable: Bool { self == .inProgress || self == .paused } +} diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 6713abe..9f0104f 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -96,6 +96,7 @@ public extension MTimer { public extension MTimer { /// Pause the timer. func pause() { + guard timerStatus == .inProgress else { return } pauseTimer() } } @@ -113,6 +114,7 @@ public extension MTimer { public extension MTimer { /// Stops the timer and resets its current time to the initial value. func cancel() { + guard timerStatus.isCancellable else { return } resetRunningTime() cancelTimer() } @@ -130,6 +132,7 @@ public extension MTimer { public extension MTimer { /// Stops the timer and updates its status to the final state func skip() throws { + guard timerStatus.isSkippable else { return } try MTimerValidator.isCanBeSkipped(timerStatus) skipRunningTime() finishTimer() diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index 5f6a938..a16e04a 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -62,7 +62,7 @@ extension MTimerTests { try! defaultTimer.start(to: endTime) wait(for: defaultWaitingTime) - timer.skip() + try! timer.skip() wait(for: defaultWaitingTime) XCTAssertEqual(endTime, currentTime) From d426078f5a3f59084ae833f46cda879c22c18049 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Wed, 4 Dec 2024 14:44:57 +0100 Subject: [PATCH 29/45] 1 --- Sources/Internal/Helpers/MTimerValidator.swift | 4 ++-- Sources/Internal/MTimer.swift | 6 +++--- .../Enumerations/Public+MTimerStatus.swift | 16 ++++------------ Sources/Public/Public+MTimer.swift | 3 +-- Tests/MTimerTests.swift | 8 ++++---- 5 files changed, 14 insertions(+), 23 deletions(-) diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index 3f698fc..7887e20 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -14,13 +14,13 @@ class MTimerValidator { static func checkRequirementsForStartingTimer(_ startTime: TimeInterval, _ endTime: TimeInterval, _ state: MTimerStateManager, _ status: MTimerStatus) throws { if startTime < 0 || endTime < 0 { throw MTimerError.timeCannotBeLessThanZero } if startTime == endTime { throw MTimerError.startTimeCannotBeTheSameAsEndTime } - if status == .inProgress && state.backgroundTransitionDate == nil { throw MTimerError.timerIsAlreadyRunning } + if status == .running && state.backgroundTransitionDate == nil { throw MTimerError.timerIsAlreadyRunning } } static func checkRequirementsForResumingTimer(_ callbacks: MTimerCallbacks) throws { if callbacks.onRunningTimeChange == nil { throw MTimerError.cannotResumeNotInitialisedTimer } } static func isCanBeSkipped(_ timerStatus: MTimerStatus) throws { - if timerStatus == .inProgress || timerStatus == .paused { return } + if timerStatus == .running || timerStatus == .paused { return } throw MTimerError.timerIsNotStarted } } diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index dcf4e3a..1cd13d2 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -48,14 +48,14 @@ extension MTimer { resetTimerPublishers() } func startTimer() { - handleTimer(status: .inProgress) + handleTimer(status: .running) } } // MARK: - Timer State Control extension MTimer { func pauseTimer() { handleTimer(status: .paused) } - func cancelTimer() { handleTimer(status: .cancelled) } + func cancelTimer() { handleTimer(status: .notStarted) } func finishTimer() { handleTimer(status: .finished) } } @@ -79,7 +79,7 @@ extension MTimer { // MARK: - Handling Timer private extension MTimer { - func handleTimer(status: MTimerStatus) { if status != .inProgress || configuration.canTimerBeStarted { + func handleTimer(status: MTimerStatus) { if status != .running || configuration.canTimerBeStarted { timerStatus = status updateInternalTimer(isTimerRunning) updateObservers(isTimerRunning) diff --git a/Sources/Public/Enumerations/Public+MTimerStatus.swift b/Sources/Public/Enumerations/Public+MTimerStatus.swift index 3f7c737..0f1b4c1 100644 --- a/Sources/Public/Enumerations/Public+MTimerStatus.swift +++ b/Sources/Public/Enumerations/Public+MTimerStatus.swift @@ -17,13 +17,7 @@ public enum MTimerStatus { /// - ``MTimer/start()`` /// - ``MTimer/start(from:to:)-1mvp1`` /// - ``MTimer/resume()`` - case inProgress - - /// Timer was stopped/cancelled - /// - /// ## Triggered by methods - /// - ``MTimer/cancel()`` - case cancelled + case running /// Timer is in a pause /// @@ -38,10 +32,8 @@ public enum MTimerStatus { case finished } - extension MTimerStatus { - var isTimerRunning: Bool { self == .inProgress } - var isNeededReset: Bool { self == .notStarted || self == .finished || self == .cancelled } - var isSkippable: Bool { self == .inProgress || self == .paused } - var isCancellable: Bool { self == .inProgress || self == .paused } + var isTimerRunning: Bool { self == .running } + var isNeededReset: Bool { self == .notStarted || self == .finished } + var isSkippable: Bool { self == .running || self == .paused } } diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 9f0104f..525a3e4 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -96,7 +96,7 @@ public extension MTimer { public extension MTimer { /// Pause the timer. func pause() { - guard timerStatus == .inProgress else { return } + guard timerStatus == .running else { return } pauseTimer() } } @@ -114,7 +114,6 @@ public extension MTimer { public extension MTimer { /// Stops the timer and resets its current time to the initial value. func cancel() { - guard timerStatus.isCancellable else { return } resetRunningTime() cancelTimer() } diff --git a/Tests/MTimerTests.swift b/Tests/MTimerTests.swift index a16e04a..00f5bef 100644 --- a/Tests/MTimerTests.swift +++ b/Tests/MTimerTests.swift @@ -27,7 +27,7 @@ extension MTimerTests { wait(for: defaultWaitingTime) XCTAssertGreaterThan(currentTime, 0) - XCTAssertEqual(.inProgress, timer.timerStatus) + XCTAssertEqual(.running, timer.timerStatus) } func testTimerIsCancellable() { try! defaultTimer.start() @@ -40,7 +40,7 @@ extension MTimerTests { wait(for: defaultWaitingTime) XCTAssertEqual(timeAfterStop, currentTime) - XCTAssertEqual(.cancelled, timer.timerStatus) + XCTAssertEqual(.notStarted, timer.timerStatus) } func testTimerIsResetable() { let startTime: TimeInterval = 3 @@ -81,7 +81,7 @@ extension MTimerTests { wait(for: defaultWaitingTime) XCTAssertNotEqual(timeAfterStop, currentTime) - XCTAssertEqual(.inProgress, timer.timerStatus) + XCTAssertEqual(.running, timer.timerStatus) } } @@ -108,7 +108,7 @@ extension MTimerTests { XCTAssertLessThan(currentTime, 3) } func testTimerPublishesStatuses() { - var statuses: [MTimerStatus: Bool] = [.inProgress: false, .cancelled: false] + var statuses: [MTimerStatus: Bool] = [.running: false, .notStarted: false] try! defaultTimer .onTimerStatusChange { statuses[$0] = true } From 2a224d88c1027992370ae10b1a22de8e573e7756 Mon Sep 17 00:00:00 2001 From: Alina P Date: Tue, 10 Dec 2024 18:01:41 +0100 Subject: [PATCH 30/45] README Updated --- README.md | 352 +++++++++++++++++++++++++++++------------------------- 1 file changed, 186 insertions(+), 166 deletions(-) diff --git a/README.md b/README.md index 73f49ad..f9c9bee 100644 --- a/README.md +++ b/README.md @@ -1,208 +1,228 @@ -
- +

- - - - Timer Logo - + + // ADD PUCTURE HERE +

-

- Modern API for Timer -

-

- Easy to use yet powerful Timer library. Keep your code clean + +

+

Modern API for Timer

+

Easy to use yet powerful Timer library. Keep your code clean

+

- Try demo we prepared - | - Roadmap + Try demo we prepared | - Propose a new feature + Framework documentation


+ +

- SwiftUI logo - Platforms: iOS, iPadOS, macOS, tvOS - Current Version - License: MIT + Labels

-

- Made in Kraków - - Follow us on X - - - Let's work together - - - Stargazers - -

+
+

Timer Examples


-Timer is a free and open-source library dedicated for Swift that makes the process of handling timers easier and much cleaner. -* **Improves code quality.** Start timer using the `publish().start()` method. Stop the timer with `stop()`. Simple as never. -* **Run your timer in both directions.** Our Timer can operate in both modes (increasing or decreasing). -* **Supports background mode.** Don't worry about the timer when the app goes into the background. We handled it! -* **And much more.** Our library allows you to convert the current time to a string or to display the timer progress in no time. - -
- -# Getting Started -### ✋ Requirements - -| **Platforms** | **Minimum Swift Version** | -|:----------|:----------| -| iOS 13+ | 5.0 | -| iPadOS 13+ | 5.0 | -| macOS 10.15+ | 5.0 | - -### ⏳ Installation - -#### [Swift package manager][spm] -Swift package manager is a tool for automating the distribution of Swift code and is integrated into the Swift compiler. - -Once you have your Swift package set up, adding Timer as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. - -```Swift -dependencies: [ - .package(url: "https://github.com/Mijick/Timer", branch(“main”)) -] -``` - -#### [Cocoapods][cocoapods] -Cocoapods is a dependency manager for Swift and Objective-C Cocoa projects that helps to scale them elegantly. - -Installation steps: -- Install CocoaPods 1.10.0 (or later) -- [Generate CocoaPods][generate_cocoapods] for your project -```Swift - pod init -``` -- Add CocoaPods dependency into your `Podfile` -```Swift - pod 'MijickTimer' -``` -- Install dependency and generate `.xcworkspace` file -```Swift - pod install -``` -- Use new XCode project file `.xcworkspace` -
+ +

+ + + Join us on Discord + + + + Follow us on LinkedIn + + + + See our other frameworks + + + + Read us on Medium + + + + Buy us a coffee + +

-# Usage + + +# ☀️ Why MijickTimer? +MijickTimer library it’s a Swift-based library that offers powerful and flexible timer features for iOS and macOS apps. Allows to create both countdown and count-up timers with enhanced state management and observation options. + +# Features + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ⏳ + + Countdown Timer (Down-Going) +
+ ⏱️ + + Count-Up Timer (Elapsed Time) +
+ ⏸️ + + Pause Timer +
+ ▶️ + + Resume Timer +
+ ⏭️ + + Skip Timer +
+ ⏮️ + + Cancel Timer +
+ ⚡ + + Reactive programming friendly +
+ + +

+

Count-Up Timer

+

Track elapsed time seamlessly with a count-up timer. Ideal for productivity, logging, or workout apps.

+

Take a look at the implementation details here.

+

-### 1. Initialise the timer -Call the `publish()` method that has three parameters: -* **time** - The number of seconds between firings of the timer. -* **tolerance** - The number of seconds after the update date that the timer may fire. -* **currentTime** - The current timer time. ```Swift - try! MTimer.publish(every: 1, currentTime: $currentTime) +@MainActor class ViewModel: ObservableObject { + @Published var time: TimeInterval = 0 + + func startTimer() { + try? MTimer(.id) +             .publish(every: 1, onTimerCallback) +             .start(from: 0, to: 10) + } + func onTimerCallback(_ time: MTime) { +        self.time = time.toTimeInterval() +    } +} ``` -### 2. Start the timer -Start the timer using the `start()` method. You can customise the start and end time using the parameters of this method. -```Swift - try! MTimer - .publish(every: 1, currentTime: $currentTime) - .start(from: .init(minutes: 21, seconds: 37), to: .zero) -``` +

+

Countdown Timer

+

Easily create countdown timers to track remaining time. Perfect for games, events, or task timers.

+

Take a look at the implementation details here.

+

-### 3. *(Optional)* Observe TimerStatus and TimerProgress -You can observe changes in both values by calling either of the methods ```Swift - try! MTimer - .publish(every: 1, currentTime: $currentTime) - .bindTimerStatus(isTimerRunning: $isTimerRunning) - .bindTimerProgress(progress: $timerProgress) - .start(from: .init(minutes: 21, seconds: 37), to: .zero) +func startTimer() { + try? MTimer(.id) + .start(from: 10, to: 0) +} ``` -### 4. Stop the timer -Timer can be stopped with `stop()` method. -```Swift - MTimer.stop() -``` +

+

Control Timer state

+

Pause timers and resume them later without losing progress, skip and cancel.

+

Take a look at the implementation details here.

+

+

-### 5. Additional timer controls -- Once stopped, the timer can be resumed - simply use the `resume()` method. ```Swift - try! MTimer.resume() -``` -- To stop and reset the timer to its initial values, use the `reset()` method. -```Swift - MTimer.reset() -``` +struct ContentView: View { + @ObservedObject var timer = MTimer(.id) -### 6. Displaying the current time as String -You can convert the current MTime to String by calling the `toString()` method. Use the `formatter` parameter to customise the output. -```Swift - currentTime.toString { - $0.unitsStyle = .full - $0.allowedUnits = [.hour, .minute] - return $0 + var body: some View { + (...) } +     + func pause() { timer.pause() } + func resume() throws { try timer.resume() } + func stop() { timer.cancel() } + func skip() throws { try timer.skip() } +} ``` -### 7. Creating more timer instances -Create a new instance of the timer and assign it to a new variable. Use the above functions directly with it -```Swift - let newTimer = MTimer.createNewInstance() - - try! newTimer - .publish(every: 1, currentTime: $currentTime) - .start() - - newTimer.stop() -``` - -
- -# Try our demo -See for yourself how does it work by cloning [project][Demo] we created - -# License -Timer is released under the MIT license. See [LICENSE][License] for details. - +

State Observation Made Easy

+

+

Monitor timer states with a variety of different approaches.

+

Take a look at the implementation details here.

+

+Code Example 6 + +# ✅ Why Choose This Timer Library? +**Multiple Apple Platform Support:** +* Works on iPhone, iPad. Requires iOS 13.0+ . +* Works on Mac. Requires macOS 10.15+. + +**Built for Swift 6:** +* Modern, efficient, and designed for performance. + +**All-in-One Timer Solution:** +* Handles countdowns, count-ups, pausing, resuming, and state management seamlessly. + +**Versatile Observation:** +* Choose callbacks, bindings, or Combine for the implementation that works best for you. + +**It's just a cool library 😎**

- -# Our other open source SwiftUI libraries -[PopupView] - The most powerful popup library that allows you to present any popup -
-[NavigationView] - Easier and cleaner way of navigating through your app -
-[CalendarView] - Create your own calendar object in no time -
-[GridView] - Lay out your data with no effort -
-[CameraView] - The most powerful CameraController. Designed for SwiftUI + +# 🔧 Installation +Follow the [installation guide](https://github.com/Mijick/Timer/wiki/Installation) to integrate the Timer library into your project. + +# 🚀 How to use it? +Visit the framework's [documentation](https://github.com/Mijick/Timer/wiki) to learn how to integrate your project with **MijickTimer**.
+See for yourself how does it work by cloning [project](https://github.com/Mijick/Timer-Demo) we created + +# 🍀 Community +Join the welcoming community of developers on [Discord](https://link.mijick.com/discord). + +# 💜 Sponsor our work +Support our work by [becoming a backer](https://link.mijick.com/buymeacoffee). -[MIT]: https://en.wikipedia.org/wiki/MIT_License -[SPM]: https://www.swift.org/package-manager -[cocoapods]: https://cocoapods.org/ -[generate_cocoapods]: https://github.com/square/cocoapods-generate - -[Demo]: https://github.com/Mijick/Timer-Demo -[License]: https://github.com/Mijick/Timer/blob/main/LICENSE - -[PopupView]: https://github.com/Mijick/PopupView -[NavigationView]: https://github.com/Mijick/NavigationView -[CalendarView]: https://github.com/Mijick/CalendarView -[GridView]: https://github.com/Mijick/GridView -[CameraView]: https://github.com/Mijick/CameraView From 369af69a525d8b28445eb34c2d5cd1c9a864936d Mon Sep 17 00:00:00 2001 From: Alina P Date: Tue, 10 Dec 2024 19:56:37 +0100 Subject: [PATCH 31/45] Update LICENSE --- LICENSE | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 201 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index ec86490..7aaaeb7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2024 Mijick - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright ©2023 Mijick + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From e0ab6402d1889cc2e268b966fbbbb6c166060d94 Mon Sep 17 00:00:00 2001 From: Alina P Date: Tue, 10 Dec 2024 19:58:42 +0100 Subject: [PATCH 32/45] Update LICENSE From 062e3c4d52d37c46a8d28d4664f3190ed1761d0b Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Tue, 10 Dec 2024 20:03:59 +0100 Subject: [PATCH 33/45] 1 --- .github/CODEOWNERS | 1 + .github/CODE_OF_CONDUCT.md | 128 ++++++++++++++++++ .github/CONTRIBUTING.md | 1 + .github/ISSUE_TEMPLATE/config.yml | 5 + .../\360\237\232\200-feature-request.md" | 17 +++ .../\360\237\246\237-bug-report.md" | 41 ++++++ 6 files changed, 193 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 ".github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" create mode 100644 ".github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..02e39f6 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @FulcrumOne diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..916f081 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +team@mijick.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..684e27f --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +Coming soon... diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d06b778 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: ❓ Help and Support Discord Channel + url: https://discord.com/invite/dT5V7nm5SC + about: Please ask and answer questions here diff --git "a/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" "b/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" new file mode 100644 index 0000000..08dc366 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" @@ -0,0 +1,17 @@ +--- +name: "\U0001F680 Feature Request" +about: If you have a feature request +title: "[FREQ]" +labels: 'state: inactive, type: feature' +assignees: FulcrumOne + +--- + +## Context +What are you trying to do and how would you want to do it differently? Is it something you currently you cannot do? Is this related to an issue/problem? + +## Alternatives +Can you achieve the same result doing it in an alternative way? Is the alternative considerable? + +## If the feature request is approved, would you be willing to submit a PR? +Yes / No _(Help can be provided if you need assistance submitting a PR)_ diff --git "a/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" "b/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" new file mode 100644 index 0000000..f6783f4 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" @@ -0,0 +1,41 @@ +--- +name: "\U0001F99F Bug Report" +about: If something isn't working +title: "[BUG]" +labels: 'state: inactive, type: bug' +assignees: FulcrumOne, jay-jay-lama + +--- + +## Prerequisites +- [ ] I checked the [documentation](https://github.com/Mijick/Popups/wiki) and found no answer +- [ ] I checked to make sure that this issue has not already been filed + +## Expected Behavior +Please describe the behavior you are expecting + +## Current Behavior +What is the current behavior? + +## Steps to Reproduce +Please provide detailed steps for reproducing the issue. +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +## Code Sample +If you can, please include a code sample that we can use to debug the bug. + +## Screenshots +If applicable, add screenshots to help explain your problem. + +## Context +Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. + +| Name | Version | +| ------| ---------| +| SDK | e.g. 3.0.0 | +| Xcode | e.g. 14.0 | +| Operating System | e.g. iOS 18.0 | +| Device | e.g. iPhone 14 Pro | From bcbaa5d5c9bcc6a8d06e8491c1eaeebb853bb074 Mon Sep 17 00:00:00 2001 From: Alina P Date: Tue, 10 Dec 2024 20:10:01 +0100 Subject: [PATCH 34/45] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f9c9bee..0514e41 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,12 @@ See for yourself how does it work by cloning [project](https://github.com/Mijick # 🍀 Community Join the welcoming community of developers on [Discord](https://link.mijick.com/discord). + +# 🌼 Contribute +To contribute a feature or idea to **MijickTimer**, create an [issue](https://github.com/Mijick/Timer/issues/new?assignees=FulcrumOne&labels=state%3A+inactive%2C+type%3A+feature&projects=&template=🚀-feature-request.md&title=%5BFREQ%5D) explaining your idea or bring it up on [Discord](https://discord.com/invite/dT5V7nm5SC).
+If you find a bug, please create an [issue](https://github.com/Mijick/Timer/issues/new?assignees=FulcrumOne%2C+jay-jay-lama&labels=state%3A+inactive%2C+type%3A+bug&projects=&template=🦟-bug-report.md&title=%5BBUG%5D).
+If you would like to contribute, please refer to the [Contribution Guidelines](https://link.mijick.com/contribution-guidelines). + # 💜 Sponsor our work Support our work by [becoming a backer](https://link.mijick.com/buymeacoffee). From eb3378ea243c931557b9fad97596feb1d7f21a08 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Wed, 11 Dec 2024 17:30:32 +0100 Subject: [PATCH 35/45] Added visionOS support --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 74a0714..b434338 100644 --- a/Package.swift +++ b/Package.swift @@ -7,7 +7,8 @@ let package = Package( name: "MijickTimer", platforms: [ .iOS(.v13), - .macOS(.v10_15) + .macOS(.v10_15), + .visionOS(.v1) ], products: [ .library(name: "MijickTimer", targets: ["MijickTimer"]), From 3039bffeb8ac7e63c97e98ca8ebd61ecd7670b7c Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Wed, 11 Dec 2024 17:51:45 +0100 Subject: [PATCH 36/45] 1 --- .../Enumerations/Public+MTimerStatus.swift | 8 ++-- Sources/Public/Public+MTimer.swift | 46 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Sources/Public/Enumerations/Public+MTimerStatus.swift b/Sources/Public/Enumerations/Public+MTimerStatus.swift index 0f1b4c1..963f37e 100644 --- a/Sources/Public/Enumerations/Public+MTimerStatus.swift +++ b/Sources/Public/Enumerations/Public+MTimerStatus.swift @@ -6,12 +6,12 @@ // public enum MTimerStatus { - /// Initial timer state + /// Initial timer status /// ## Triggered by methods /// - ``MTimer/reset()`` case notStarted - /// Timer in progress + /// Timer is in a progress /// /// ## Triggered by methods /// - ``MTimer/start()`` @@ -19,13 +19,13 @@ public enum MTimerStatus { /// - ``MTimer/resume()`` case running - /// Timer is in a pause + /// Timer is in a paused state /// /// ## Triggered by methods /// - ``MTimer/pause()`` case paused - /// Timer was finished by running out of time or by calling function + /// The timer was terminated by running out of time or calling the function /// /// ## Triggered by methods /// - ``MTimer/skip()`` diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index 525a3e4..d90a1e2 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -13,24 +13,24 @@ import SwiftUI // MARK: - Initialising Timer public extension MTimer { - /// Configure the interval at which the timer's status will be published. + /// Configure the interval for publishing the timer status. /// /// - Parameters: - /// - time: the interval of publishing timer state + /// - time: timer status publishing interval /// - tolerance: The amount of time after the scheduled fire date that the timer may fire. - /// - currentTime: Binding value that will be updated every **time** interval + /// - currentTime: A binding value that will be updated every **time** interval. /// /// - WARNING: Use the ``start()`` or ``start(from:to:)-1mvp1`` methods to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, currentTime: Binding) throws -> MTimer { try publish(every: time, tolerance: tolerance) { currentTime.wrappedValue = $0 } } - /// Configure the interval at which the timer's status will be published. + /// Configure the interval for publishing the timer status. /// /// - Parameters: - /// - time: the interval of publishing timer state + /// - time: timer status publishing interval /// - tolerance: The amount of time after the scheduled fire date that the timer may fire. - /// - completion: Completion block that will be executed every **time** interval + /// - completion: A completion block that will be executed every **time** interval /// /// - WARNING: Use the ``start()`` or ``start(from:to:)-1mvp1`` method to start the timer. func publish(every time: TimeInterval, tolerance: TimeInterval = 0.4, _ completion: @escaping (_ currentTime: MTime) -> () = { _ in }) throws -> MTimer { @@ -45,15 +45,15 @@ public extension MTimer { /** Starts the timer using the specified initial values. - - Note: Can be run backwards - use any **to** value that is greater than **from**. + - Note: The timer can be run backwards - use any value **to** that is greater than **from**. - ### Up going timer + ### Up-going timer ```swift MTimer(.exampleId) .start(from: .zero, to: MTime(seconds: 10)) ``` - ### Down going timer + ### Down-going timer ```swift MTimer(.exampleId) .start(from: MTime(seconds: 10), to: .zero) @@ -66,15 +66,15 @@ public extension MTimer { /** Starts the timer using the specified initial values. - - Note: Can be run backwards - use any **to** value that is greater than **from**. + - Note: The timer can be run backwards - use any value **to** that is greater than **from**. - ### Up going timer + ### Up-going timer ```swift MTimer(.exampleId) .start(from: .zero, to: 10) ``` - ### Down going timer + ### Down-going timer ```swift MTimer(.exampleId) .start(from: 10, to: .zero) @@ -86,7 +86,7 @@ public extension MTimer { startTimer() } - /// Starts up going infinity timer + /// Starts the up-going infinity timer func start() throws { try start(from: .zero, to: .infinity) } @@ -121,7 +121,7 @@ public extension MTimer { // MARK: - Aborting Timer public extension MTimer { - /// Stops the timer and resets all timer states to default + /// Stops the timer and resets all timer states to default values. func reset() { resetTimer() } @@ -129,7 +129,7 @@ public extension MTimer { // MARK: - Skip Timer public extension MTimer { - /// Stops the timer and updates its status to the final state + /// Stops the timer and updates its status to the final state. func skip() throws { guard timerStatus.isSkippable else { return } try MTimerValidator.isCanBeSkipped(timerStatus) @@ -140,14 +140,14 @@ public extension MTimer { // MARK: - Publishing Timer Activity Status public extension MTimer { - /// Publishes the timer status changes. - /// - Note: To configure the interval at which the timer's status will be published use the method ``publish(every:tolerance:currentTime:)`` + /// Publishes timer status changes. + /// - Note: To configure the interval at which the state of the timer will be published, use method ``publish(every:tolerance:currentTime:)`` func onTimerStatusChange(_ action: @escaping (_ timerStatus: MTimerStatus) -> ()) -> MTimer { callbacks.onTimerStatusChange = action return self } - /// Publishes the timer activity changes. - /// - Note: To configure the interval at which the timer's status will be published use the method ``publish(every:tolerance:currentTime:)`` + /// Publishes timer status changes. + /// - Note: To configure the interval at which the state of the timer will be published, use method ``publish(every:tolerance:currentTime:)`` func bindTimerStatus(timerStatus: Binding) -> MTimer { onTimerStatusChange { timerStatus.wrappedValue = $0 } } @@ -155,14 +155,14 @@ public extension MTimer { // MARK: - Publishing Timer Progress public extension MTimer { - /// Publishes the timer progress changes. - /// - Note: To configure the interval at which the timer's progress will be published use the method ``publish(every:tolerance:currentTime:)`` + /// Publishes timer progress changes. + /// - Note: To configure the interval at which the timer's progress will be published, use method ``publish(every:tolerance:currentTime:)`` func onTimerProgressChange(_ action: @escaping (_ progress: Double) -> ()) -> MTimer { callbacks.onTimerProgressChange = action return self } - /// Publishes the timer progress changes. - /// - Note: To configure the interval at which the timer's progress will be published use the method ``publish(every:tolerance:currentTime:)`` + /// Publishes timer progress changes. + /// - Note: To configure the interval at which the timer's progress will be published, use method ``publish(every:tolerance:currentTime:)`` func bindTimerProgress(progress: Binding) -> MTimer { onTimerProgressChange { progress.wrappedValue = $0 } } From 4fcd6ede7102f1ecb22cb36e32cf4ac1ac08b282 Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Wed, 11 Dec 2024 19:27:51 +0100 Subject: [PATCH 37/45] 1 --- MijickTimer.podspec | 9 +++++---- .../Internal/Extensions/NotificationCenter++.swift | 12 ++++++------ Sources/Internal/Helpers/MTimerCallbacks.swift | 5 ++++- .../Helpers/MTimerConfigurationManager.swift | 5 ++++- Sources/Internal/Helpers/MTimerContainer.swift | 5 ++++- Sources/Internal/Helpers/MTimerStateManager.swift | 5 ++++- Sources/Internal/Helpers/MTimerValidator.swift | 5 ++++- Sources/Internal/MTime.swift | 4 ++-- Sources/Internal/MTimer.swift | 5 ++++- .../Internal/Protocols/FactoryInitializable.swift | 5 ++++- Sources/Public/Enumerations/Public+MTimerError.swift | 4 ++-- .../Public/Enumerations/Public+MTimerStatus.swift | 5 ++++- Sources/Public/Models/Public+MTimerID.swift | 5 ++++- Sources/Public/Public+MTime.swift | 2 +- Sources/Public/Public+MTimer.swift | 10 +++++----- 15 files changed, 57 insertions(+), 29 deletions(-) diff --git a/MijickTimer.podspec b/MijickTimer.podspec index 439a687..f1fe449 100644 --- a/MijickTimer.podspec +++ b/MijickTimer.podspec @@ -1,18 +1,19 @@ Pod::Spec.new do |s| s.name = 'MijickTimer' s.summary = 'Modern API for Timer' - s.description = 'MijickTimer is a free, open-source library for the Swift language that makes the process of managing timers much easier and clearer.' + s.description = 'Swift library for timers, supporting countdown, count-up, pause, resume, and state management for iOS, macOS and visionOS.' - s.version = '1.0.2' + s.version = '2.0.0' s.ios.deployment_target = '13.0' s.osx.deployment_target = '10.15' - s.swift_version = '5.0' + s.visionos.deployment_target = '1.0' + s.swift_version = '6.0' s.source_files = 'Sources/**/*' s.frameworks = 'SwiftUI', 'Foundation', 'Combine' s.homepage = 'https://github.com/Mijick/Timer.git' s.license = { :type => 'MIT', :file => 'LICENSE' } - s.author = { 'Mijick' => 'team@mijick.com' } + s.author = { 'Alina P. from Mijick' => 'alina.petrovska@mijick.com' } s.source = { :git => 'https://github.com/Mijick/Timer.git', :tag => s.version.to_s } end diff --git a/Sources/Internal/Extensions/NotificationCenter++.swift b/Sources/Internal/Extensions/NotificationCenter++.swift index 0fc4c6f..974bb78 100644 --- a/Sources/Internal/Extensions/NotificationCenter++.swift +++ b/Sources/Internal/Extensions/NotificationCenter++.swift @@ -1,12 +1,12 @@ // -// NotificationCenter++.swift of Timer +// NotificationCenter++.swift +// MijickTimer // -// Created by Tomasz Kurylik -// - Twitter: https://twitter.com/tkurylik -// - Mail: tomasz.kurylik@mijick.com -// - GitHub: https://github.com/FulcrumOne +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // -// Copyright ©2023 Mijick. Licensed under MIT License. +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI diff --git a/Sources/Internal/Helpers/MTimerCallbacks.swift b/Sources/Internal/Helpers/MTimerCallbacks.swift index 83c6ff2..70a741e 100644 --- a/Sources/Internal/Helpers/MTimerCallbacks.swift +++ b/Sources/Internal/Helpers/MTimerCallbacks.swift @@ -2,8 +2,11 @@ // MTimerCallbacks.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI diff --git a/Sources/Internal/Helpers/MTimerConfigurationManager.swift b/Sources/Internal/Helpers/MTimerConfigurationManager.swift index 64704f0..449524b 100644 --- a/Sources/Internal/Helpers/MTimerConfigurationManager.swift +++ b/Sources/Internal/Helpers/MTimerConfigurationManager.swift @@ -2,8 +2,11 @@ // MTimerConfigurationManager.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI diff --git a/Sources/Internal/Helpers/MTimerContainer.swift b/Sources/Internal/Helpers/MTimerContainer.swift index 0700726..c25fcab 100644 --- a/Sources/Internal/Helpers/MTimerContainer.swift +++ b/Sources/Internal/Helpers/MTimerContainer.swift @@ -2,8 +2,11 @@ // MTimerContainer.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. @MainActor class MTimerContainer { private static var timers: [MTimer] = [] diff --git a/Sources/Internal/Helpers/MTimerStateManager.swift b/Sources/Internal/Helpers/MTimerStateManager.swift index c7e0eac..fbfb135 100644 --- a/Sources/Internal/Helpers/MTimerStateManager.swift +++ b/Sources/Internal/Helpers/MTimerStateManager.swift @@ -2,8 +2,11 @@ // MTimerStateManager.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI diff --git a/Sources/Internal/Helpers/MTimerValidator.swift b/Sources/Internal/Helpers/MTimerValidator.swift index 7887e20..697fbdd 100644 --- a/Sources/Internal/Helpers/MTimerValidator.swift +++ b/Sources/Internal/Helpers/MTimerValidator.swift @@ -2,8 +2,11 @@ // MTimerValidator.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. import Foundation diff --git a/Sources/Internal/MTime.swift b/Sources/Internal/MTime.swift index 2feb36f..6548956 100644 --- a/Sources/Internal/MTime.swift +++ b/Sources/Internal/MTime.swift @@ -1,12 +1,12 @@ // -// MTime.swift of Timer +// MTime.swift // // Created by Tomasz Kurylik // - Twitter: https://twitter.com/tkurylik // - Mail: tomasz.kurylik@mijick.com // - GitHub: https://github.com/FulcrumOne // -// Copyright ©2023 Mijick. Licensed under MIT License. +// Copyright ©2023 Mijick. All rights reserved. import Foundation diff --git a/Sources/Internal/MTimer.swift b/Sources/Internal/MTimer.swift index 1cd13d2..3b9efdc 100644 --- a/Sources/Internal/MTimer.swift +++ b/Sources/Internal/MTimer.swift @@ -2,8 +2,11 @@ // MTimer.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI diff --git a/Sources/Internal/Protocols/FactoryInitializable.swift b/Sources/Internal/Protocols/FactoryInitializable.swift index de384c0..78a1220 100644 --- a/Sources/Internal/Protocols/FactoryInitializable.swift +++ b/Sources/Internal/Protocols/FactoryInitializable.swift @@ -2,8 +2,11 @@ // FactoryInitializable.swift // MijickTimer // -// Created by Alina Petrovska on 15.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI diff --git a/Sources/Public/Enumerations/Public+MTimerError.swift b/Sources/Public/Enumerations/Public+MTimerError.swift index c1da998..72357a9 100644 --- a/Sources/Public/Enumerations/Public+MTimerError.swift +++ b/Sources/Public/Enumerations/Public+MTimerError.swift @@ -1,12 +1,12 @@ // -// Public+MTimerError.swift of Timer +// Public+MTimerError.swift // // Created by Tomasz Kurylik // - Twitter: https://twitter.com/tkurylik // - Mail: tomasz.kurylik@mijick.com // - GitHub: https://github.com/FulcrumOne // -// Copyright ©2023 Mijick. Licensed under MIT License. +// Copyright ©2023 Mijick. All rights reserved. import Foundation diff --git a/Sources/Public/Enumerations/Public+MTimerStatus.swift b/Sources/Public/Enumerations/Public+MTimerStatus.swift index 963f37e..956012b 100644 --- a/Sources/Public/Enumerations/Public+MTimerStatus.swift +++ b/Sources/Public/Enumerations/Public+MTimerStatus.swift @@ -2,8 +2,11 @@ // Public+MTimerStatus.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. public enum MTimerStatus { /// Initial timer status diff --git a/Sources/Public/Models/Public+MTimerID.swift b/Sources/Public/Models/Public+MTimerID.swift index 2e582ad..b48820e 100644 --- a/Sources/Public/Models/Public+MTimerID.swift +++ b/Sources/Public/Models/Public+MTimerID.swift @@ -2,8 +2,11 @@ // Public+MTimerID.swift // MijickTimer // -// Created by Alina Petrovska on 11.11.2024. +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // +// Copyright ©2024 Mijick. All rights reserved. /// Unique id that enables an access to the registered timer from any location. public struct MTimerID: Equatable, Sendable { diff --git a/Sources/Public/Public+MTime.swift b/Sources/Public/Public+MTime.swift index 6b58623..4f1d203 100644 --- a/Sources/Public/Public+MTime.swift +++ b/Sources/Public/Public+MTime.swift @@ -6,7 +6,7 @@ // - Mail: tomasz.kurylik@mijick.com // - GitHub: https://github.com/FulcrumOne // -// Copyright ©2023 Mijick. Licensed under MIT License. +// Copyright ©2023 Mijick. All rights reserved. import Foundation diff --git a/Sources/Public/Public+MTimer.swift b/Sources/Public/Public+MTimer.swift index d90a1e2..a7e661e 100644 --- a/Sources/Public/Public+MTimer.swift +++ b/Sources/Public/Public+MTimer.swift @@ -1,12 +1,12 @@ // // Public+MTimer.swift of Timer +// MijickTimer // -// Created by Tomasz Kurylik -// - Twitter: https://twitter.com/tkurylik -// - Mail: tomasz.kurylik@mijick.com -// - GitHub: https://github.com/FulcrumOne +// Created by Alina Petrovska +// - Mail: alina.petrovska@mijick.com +// - GitHub: https://github.com/Mijick // -// Copyright ©2023 Mijick. Licensed under MIT License. +// Copyright ©2024 Mijick. All rights reserved. import SwiftUI From 53ce14e86663c6ccab8f8aeca175388cc81ec03e Mon Sep 17 00:00:00 2001 From: Alina P Date: Wed, 11 Dec 2024 20:24:28 +0100 Subject: [PATCH 38/45] Update README.md --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0514e41..992bcb1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- // ADD PUCTURE HERE + MijickTimer Hero

@@ -60,13 +60,7 @@

- - -# ☀️ Why MijickTimer? -MijickTimer library it’s a Swift-based library that offers powerful and flexible timer features for iOS and macOS apps. Allows to create both countdown and count-up timers with enhanced state management and observation options. - # Features - @@ -129,7 +123,14 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib
+ +# ☀️ Why MijickTimer? +MijickTimer library it’s a Swift-based library that offers powerful and flexible timer features for iOS and macOS apps. Allows to create both countdown and count-up timers with enhanced state management and observation options. + + + +

Count-Up Timer

Track elapsed time seamlessly with a count-up timer. Ideal for productivity, logging, or workout apps.

@@ -194,21 +195,25 @@ struct ContentView: View { Code Example 6 # ✅ Why Choose This Timer Library? -**Multiple Apple Platform Support:** -* Works on iPhone, iPad. Requires iOS 13.0+ . -* Works on Mac. Requires macOS 10.15+. +

Multiple Apple Platform Support:

+ +* iPhone, iPad. Requires iOS 13.0+ . +* Mac. Requires macOS 10.15+. +* Apple Vision Pro. Requires visionOS 1.0+. -**Built for Swift 6:** +

Built for Swift 6:

+ * Modern, efficient, and designed for performance. -**All-in-One Timer Solution:** +

All-in-One Timer Solution:

+ * Handles countdowns, count-ups, pausing, resuming, and state management seamlessly. -**Versatile Observation:** +

Versatile Observation:

+ * Choose callbacks, bindings, or Combine for the implementation that works best for you. -**It's just a cool library 😎** -

+

It's just a cool library 😎

# 🔧 Installation From 4ad854e96acf45bf556eee4e36d67d721ed0e04e Mon Sep 17 00:00:00 2001 From: Alina P Date: Thu, 12 Dec 2024 22:45:07 +0100 Subject: [PATCH 39/45] Update README.md --- README.md | 62 ++++++++++++++----------------------------------------- 1 file changed, 15 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 992bcb1..ae5db64 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,11 @@

- Try demo we prepared + Try demo we prepared | - Framework documentation + Framework documentation + | + Roadmap


@@ -125,10 +127,10 @@ -# ☀️ Why MijickTimer? +# ☀️ What Is MijickTimer? MijickTimer library it’s a Swift-based library that offers powerful and flexible timer features for iOS and macOS apps. Allows to create both countdown and count-up timers with enhanced state management and observation options. - +## 💡 Feature Insights

@@ -136,34 +138,14 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib

Track elapsed time seamlessly with a count-up timer. Ideal for productivity, logging, or workout apps.

Take a look at the implementation details here.

- -```Swift -@MainActor class ViewModel: ObservableObject { - @Published var time: TimeInterval = 0 - - func startTimer() { - try? MTimer(.id) -             .publish(every: 1, onTimerCallback) -             .start(from: 0, to: 10) - } - func onTimerCallback(_ time: MTime) { -        self.time = time.toTimeInterval() -    } -} -``` +A demonstration of the code used to compare the implementation of the native iOS timer framework with the custom MijickTimer.

Countdown Timer

Easily create countdown timers to track remaining time. Perfect for games, events, or task timers.

Take a look at the implementation details here.

- -```Swift -func startTimer() { - try? MTimer(.id) - .start(from: 10, to: 0) -} -``` +An illustration of how to create a countdown timer using only a few lines of code with the MijickTimer library.

Control Timer state

@@ -171,30 +153,16 @@ func startTimer() {

Take a look at the implementation details here.

+Demonstrates code for controlling the Timer state via the MijickTimer library: stop, pause, resume, skip, and cancel or stop the Timer. -```Swift -struct ContentView: View { - @ObservedObject var timer = MTimer(.id) - - var body: some View { - (...) - } -     - func pause() { timer.pause() } - func resume() throws { try timer.resume() } - func stop() { timer.cancel() } - func skip() throws { try timer.skip() } -} -``` - -

State Observation Made Easy

+

Observe Timer State

-

Monitor timer states with a variety of different approaches.

+

Monitor timer states with a variety of different approaches: binding, callbacks, combine, state value updates

Take a look at the implementation details here.

-Code Example 6 +The code illustrates various methods for monitoring the current timer state, including binding, callbacks, combining, and state observation. -# ✅ Why Choose This Timer Library? +# ✅ Why MijickTimer?

Multiple Apple Platform Support:

* iPhone, iPad. Requires iOS 13.0+ . @@ -220,8 +188,8 @@ struct ContentView: View { Follow the [installation guide](https://github.com/Mijick/Timer/wiki/Installation) to integrate the Timer library into your project. # 🚀 How to use it? -Visit the framework's [documentation](https://github.com/Mijick/Timer/wiki) to learn how to integrate your project with **MijickTimer**.
-See for yourself how does it work by cloning [project](https://github.com/Mijick/Timer-Demo) we created +Visit the framework's [documentation](https://link.mijick.com/timer-wiki) to learn how to integrate your project with **MijickTimer**.
+See for yourself how does it work by cloning [project](https://link.mijick.com/timer-demo) we created # 🍀 Community From 9d819c29a141de38ceec5d47d1f80fa9dfc97a48 Mon Sep 17 00:00:00 2001 From: Alina P Date: Thu, 12 Dec 2024 23:06:05 +0100 Subject: [PATCH 40/45] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae5db64..97c9815 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,9 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib

The code illustrates various methods for monitoring the current timer state, including binding, callbacks, combining, and state observation. + + + # ✅ Why MijickTimer?

Multiple Apple Platform Support:

@@ -180,7 +183,8 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib

Versatile Observation:

* Choose callbacks, bindings, or Combine for the implementation that works best for you. - +* Provides the ability to access the state of a specific timer from any part of the code base. +

It's just a cool library 😎

From 0dc8adfffe641cd7c6572c7ef8eb6e01d94a8401 Mon Sep 17 00:00:00 2001 From: Alina P Date: Thu, 12 Dec 2024 23:11:39 +0100 Subject: [PATCH 41/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97c9815..1fce99e 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib

Easily create countdown timers to track remaining time. Perfect for games, events, or task timers.

Take a look at the implementation details here.

-An illustration of how to create a countdown timer using only a few lines of code with the MijickTimer library. +An illustration of how to create a countdown timer using only a few lines of code with the MijickTimer library.

Control Timer state

From 86264cbfe21dd4425140e91d7a4db901d4a02b6a Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Thu, 12 Dec 2024 23:19:22 +0100 Subject: [PATCH 42/45] Issue templates updated --- ".github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" | 3 ++- ".github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git "a/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" "b/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" index 08dc366..37c27b9 100644 --- "a/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" +++ "b/.github/ISSUE_TEMPLATE/\360\237\232\200-feature-request.md" @@ -2,7 +2,8 @@ name: "\U0001F680 Feature Request" about: If you have a feature request title: "[FREQ]" -labels: 'state: inactive, type: feature' +labels: 'feature' +projects: "Mijick/17" assignees: FulcrumOne --- diff --git "a/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" "b/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" index f6783f4..0ccee61 100644 --- "a/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" +++ "b/.github/ISSUE_TEMPLATE/\360\237\246\237-bug-report.md" @@ -2,13 +2,14 @@ name: "\U0001F99F Bug Report" about: If something isn't working title: "[BUG]" -labels: 'state: inactive, type: bug' +labels: 'bug' +projects: "Mijick/17" assignees: FulcrumOne, jay-jay-lama --- ## Prerequisites -- [ ] I checked the [documentation](https://github.com/Mijick/Popups/wiki) and found no answer +- [ ] I checked the [documentation](https://github.com/Mijick/Timer/wiki) and found no answer - [ ] I checked to make sure that this issue has not already been filed ## Expected Behavior From c76296ac6cf06379dd5721dba84145a927d7ce73 Mon Sep 17 00:00:00 2001 From: Alina P Date: Thu, 12 Dec 2024 23:42:47 +0100 Subject: [PATCH 43/45] Update README.md --- README.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1fce99e..8f88f7f 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@

-

Modern API for Timer

-

Easy to use yet powerful Timer library. Keep your code clean

+

Timers made simple

+

Easy to use yet powerful Timer library. Keep your code clean.

@@ -62,7 +62,7 @@

-# Features +# ✨ Features @@ -128,28 +128,28 @@ # ☀️ What Is MijickTimer? -MijickTimer library it’s a Swift-based library that offers powerful and flexible timer features for iOS and macOS apps. Allows to create both countdown and count-up timers with enhanced state management and observation options. +MijickTimer library is Swift-based library that offers powerful and flexible timer features for iOS and macOS and visionOS apps. It allows to create both countdown and count-up timers with enhanced state management and observation options. -## 💡 Feature Insights +# 💡 Feature Insights

Count-Up Timer

-

Track elapsed time seamlessly with a count-up timer. Ideal for productivity, logging, or workout apps.

+

Track elapsed time seamlessly with a count-up timer. Ideal for productivity, logging or workout apps.

Take a look at the implementation details here.

A demonstration of the code used to compare the implementation of the native iOS timer framework with the custom MijickTimer.

Countdown Timer

-

Easily create countdown timers to track remaining time. Perfect for games, events, or task timers.

+

Easily create countdown timers to track remaining time. Perfect for games, events or task apps.

Take a look at the implementation details here.

An illustration of how to create a countdown timer using only a few lines of code with the MijickTimer library.

Control Timer state

-

Pause timers and resume them later without losing progress, skip and cancel.

+

Pause timers and resume them later without losing progress. Also allows to skip and cancel the progress.

Take a look at the implementation details here.

@@ -157,10 +157,10 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib

Observe Timer State

-

Monitor timer states with a variety of different approaches: binding, callbacks, combine, state value updates

+

Monitor timer state with a variety of different approaches: binding, callbacks, combine, state value updates.

Take a look at the implementation details here.

-The code illustrates various methods for monitoring the current timer state, including binding, callbacks, combining, and state observation. +The code illustrates various methods for monitoring the current timer state, including binding, callbacks, combining, and state observation @@ -178,19 +178,16 @@ MijickTimer library it’s a Swift-based library that offers powerful and flexib

All-in-One Timer Solution:

-* Handles countdowns, count-ups, pausing, resuming, and state management seamlessly. +* Handles countdowns, count-ups, pausing, resuming and state management seamlessly.

Versatile Observation:

-* Choose callbacks, bindings, or Combine for the implementation that works best for you. +* Choose callbacks, bindings or Combine for the implementation that works best for you. * Provides the ability to access the state of a specific timer from any part of the code base.

It's just a cool library 😎

-# 🔧 Installation -Follow the [installation guide](https://github.com/Mijick/Timer/wiki/Installation) to integrate the Timer library into your project. - # 🚀 How to use it? Visit the framework's [documentation](https://link.mijick.com/timer-wiki) to learn how to integrate your project with **MijickTimer**.
See for yourself how does it work by cloning [project](https://link.mijick.com/timer-demo) we created From d612e6128f644f3ec8e2fee8ceee7c106985deb5 Mon Sep 17 00:00:00 2001 From: Alina P Date: Thu, 12 Dec 2024 23:43:21 +0100 Subject: [PATCH 44/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f88f7f..c89cac5 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ MijickTimer library is Swift-based library that offers powerful and flexible tim

Control Timer state

-

Pause timers and resume them later without losing progress. Also allows to skip and cancel the progress.

+

Pause timers and resume them later without losing progress. It also allows to skip and cancel the progress.

Take a look at the implementation details here.

From 1d4ab0d0263abbf03734d0d4cccaa8a0324ef5ec Mon Sep 17 00:00:00 2001 From: jay-jay-lama Date: Thu, 12 Dec 2024 23:46:44 +0100 Subject: [PATCH 45/45] Pod update --- MijickTimer.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MijickTimer.podspec b/MijickTimer.podspec index f1fe449..f4c23d4 100644 --- a/MijickTimer.podspec +++ b/MijickTimer.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'MijickTimer' s.summary = 'Modern API for Timer' - s.description = 'Swift library for timers, supporting countdown, count-up, pause, resume, and state management for iOS, macOS and visionOS.' + s.description = 'Timers made simple: The Ultimate Swift Framework for Modern Apps on iOS, macOS, and visionOS.' s.version = '2.0.0' s.ios.deployment_target = '13.0'