mirror of
https://github.com/pawelorzech/MacTorn.git
synced 2026-01-29 19:54:27 +00:00
fix: Improve travel timer accuracy with direct timestamp usage
- Use API timestamp directly for travel countdown calculations - Add fallback to timeLeft for backward compatibility - Add comprehensive test coverage for remainingSeconds method - Bump version to 1.4.5 - Add CHANGELOG.md
This commit is contained in:
parent
a55be3c6be
commit
e4c8f6927b
4 changed files with 175 additions and 6 deletions
58
CHANGELOG.md
Normal file
58
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to MacTorn will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.4.5] - 2025-01-25
|
||||
|
||||
### Fixed
|
||||
- Improved travel timer accuracy by using API timestamp directly instead of calculating from fetch time offset
|
||||
- Travel countdown now stays synchronized regardless of network delays or fetch timing
|
||||
|
||||
### Added
|
||||
- Comprehensive test coverage for travel timer calculations
|
||||
|
||||
## [1.4.4] - Previous Release
|
||||
|
||||
### Fixed
|
||||
- Resolve Swift concurrency errors by extracting MainActor functions
|
||||
- Fix watchlist item mutation to update via copy
|
||||
|
||||
### Added
|
||||
- Universal Binary support for Intel and Apple Silicon Macs
|
||||
- Improved accessibility support
|
||||
- Display cooldown labels as text instead of icons
|
||||
|
||||
## [1.4.3] - Earlier Release
|
||||
|
||||
### Added
|
||||
- GitHub wiki documentation
|
||||
- Migrated wiki to GitHub Wiki feature
|
||||
|
||||
## [1.4.2] - Earlier Release
|
||||
|
||||
### Changed
|
||||
- Various bug fixes and improvements
|
||||
|
||||
## [1.4.1] - Earlier Release
|
||||
|
||||
### Changed
|
||||
- Various bug fixes and improvements
|
||||
|
||||
## [1.4] - Initial Public Release
|
||||
|
||||
### Added
|
||||
- Native macOS menu bar app for Torn game monitoring
|
||||
- Status tab with live bars, cooldowns, and travel monitoring
|
||||
- Travel tab with live countdown timer in menu bar
|
||||
- Money tab with cash, vault, points display
|
||||
- Attacks tab with battle stats and recent attacks
|
||||
- Faction tab with chain status
|
||||
- Watchlist tab for item price tracking
|
||||
- Smart notifications for various game events
|
||||
- Configurable refresh intervals
|
||||
- Launch at login support
|
||||
- Light and dark mode support
|
||||
- Accessibility support with Reduce Transparency
|
||||
|
|
@ -639,7 +639,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mactorn.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
|
@ -666,7 +666,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mactorn.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
|
@ -684,7 +684,7 @@
|
|||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mactorn.MacTornTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
|
|
@ -702,7 +702,7 @@
|
|||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mactorn.MacTornTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
|
|
@ -720,7 +720,7 @@
|
|||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mactorn.MacTornUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
|
|
@ -737,7 +737,7 @@
|
|||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 1.4.4;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mactorn.MacTornUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
|
|
|
|||
|
|
@ -115,6 +115,13 @@ struct Travel: Codable, Equatable {
|
|||
|
||||
/// Calculate remaining seconds based on fetch time (for live countdown)
|
||||
func remainingSeconds(from fetchTime: Date) -> Int {
|
||||
// Primary: Use timestamp directly if available (more accurate)
|
||||
if let timestamp = timestamp, timestamp > 0 {
|
||||
let now = Int(Date().timeIntervalSince1970)
|
||||
return max(0, timestamp - now)
|
||||
}
|
||||
|
||||
// Fallback: Use timeLeft with fetchTime offset (backward compatibility)
|
||||
guard let timeLeft = timeLeft, timeLeft > 0 else { return 0 }
|
||||
let elapsed = Int(Date().timeIntervalSince(fetchTime))
|
||||
return max(0, timeLeft - elapsed)
|
||||
|
|
|
|||
|
|
@ -132,4 +132,108 @@ final class TravelTests: XCTestCase {
|
|||
let travel = try decode(Travel.self, from: json)
|
||||
XCTAssertFalse(travel.isTraveling)
|
||||
}
|
||||
|
||||
// MARK: - remainingSeconds Tests
|
||||
|
||||
func testRemainingSeconds_usesTimestampDirectly() throws {
|
||||
// Set arrival time 60 seconds in the future
|
||||
let futureTimestamp = Int(Date().timeIntervalSince1970) + 60
|
||||
let json: [String: Any] = [
|
||||
"destination": "Japan",
|
||||
"timestamp": futureTimestamp,
|
||||
"departed": futureTimestamp - 1000,
|
||||
"time_left": 60
|
||||
]
|
||||
let travel = try decode(Travel.self, from: json)
|
||||
|
||||
// Even with a stale fetchTime, should use timestamp directly
|
||||
let staleFetchTime = Date().addingTimeInterval(-300) // 5 minutes ago
|
||||
let remaining = travel.remainingSeconds(from: staleFetchTime)
|
||||
|
||||
// Should be approximately 60 seconds (allow 1-2 seconds tolerance for test execution)
|
||||
XCTAssertGreaterThanOrEqual(remaining, 58)
|
||||
XCTAssertLessThanOrEqual(remaining, 62)
|
||||
}
|
||||
|
||||
func testRemainingSeconds_fallsBackToTimeLeftWhenTimestampNil() throws {
|
||||
let json: [String: Any] = [
|
||||
"destination": "Japan",
|
||||
"time_left": 120
|
||||
]
|
||||
let travel = try decode(Travel.self, from: json)
|
||||
|
||||
let fetchTime = Date()
|
||||
let remaining = travel.remainingSeconds(from: fetchTime)
|
||||
|
||||
// Should use timeLeft since timestamp is nil
|
||||
XCTAssertGreaterThanOrEqual(remaining, 118)
|
||||
XCTAssertLessThanOrEqual(remaining, 120)
|
||||
}
|
||||
|
||||
func testRemainingSeconds_fallsBackToTimeLeftWhenTimestampZero() throws {
|
||||
let json: [String: Any] = [
|
||||
"destination": "Japan",
|
||||
"timestamp": 0,
|
||||
"time_left": 90
|
||||
]
|
||||
let travel = try decode(Travel.self, from: json)
|
||||
|
||||
let fetchTime = Date()
|
||||
let remaining = travel.remainingSeconds(from: fetchTime)
|
||||
|
||||
// Should use timeLeft since timestamp is 0
|
||||
XCTAssertGreaterThanOrEqual(remaining, 88)
|
||||
XCTAssertLessThanOrEqual(remaining, 90)
|
||||
}
|
||||
|
||||
func testRemainingSeconds_returnsZeroWhenArrivalPassed() throws {
|
||||
// Set arrival time in the past
|
||||
let pastTimestamp = Int(Date().timeIntervalSince1970) - 60
|
||||
let json: [String: Any] = [
|
||||
"destination": "Japan",
|
||||
"timestamp": pastTimestamp,
|
||||
"departed": pastTimestamp - 1000,
|
||||
"time_left": 0
|
||||
]
|
||||
let travel = try decode(Travel.self, from: json)
|
||||
|
||||
let remaining = travel.remainingSeconds(from: Date())
|
||||
XCTAssertEqual(remaining, 0)
|
||||
}
|
||||
|
||||
func testRemainingSeconds_consistentRegardlessOfFetchTime() throws {
|
||||
// Set arrival time 120 seconds in the future
|
||||
let futureTimestamp = Int(Date().timeIntervalSince1970) + 120
|
||||
let json: [String: Any] = [
|
||||
"destination": "Japan",
|
||||
"timestamp": futureTimestamp,
|
||||
"departed": futureTimestamp - 1000,
|
||||
"time_left": 120
|
||||
]
|
||||
let travel = try decode(Travel.self, from: json)
|
||||
|
||||
// Test with different fetchTimes - result should be the same
|
||||
let recentFetchTime = Date()
|
||||
let staleFetchTime = Date().addingTimeInterval(-60)
|
||||
let veryOldFetchTime = Date().addingTimeInterval(-600)
|
||||
|
||||
let remaining1 = travel.remainingSeconds(from: recentFetchTime)
|
||||
let remaining2 = travel.remainingSeconds(from: staleFetchTime)
|
||||
let remaining3 = travel.remainingSeconds(from: veryOldFetchTime)
|
||||
|
||||
// All should return approximately the same value (within 1 second tolerance)
|
||||
XCTAssertEqual(remaining1, remaining2, accuracy: 1)
|
||||
XCTAssertEqual(remaining2, remaining3, accuracy: 1)
|
||||
}
|
||||
|
||||
func testRemainingSeconds_zeroWhenNotTraveling() throws {
|
||||
let json: [String: Any] = [
|
||||
"destination": "Torn",
|
||||
"time_left": 0
|
||||
]
|
||||
let travel = try decode(Travel.self, from: json)
|
||||
|
||||
let remaining = travel.remainingSeconds(from: Date())
|
||||
XCTAssertEqual(remaining, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue