Add automatic update checker and UI integration

Introduces an UpdateManager to check for new releases on GitHub and integrates update notifications into the app's state and SettingsView. Updates Info.plist to version 1.2, adds MacTorn-v1.2.zip, archives v1.0 zip, and revises README to document the new update checker and other improvements.
This commit is contained in:
Paweł Orzech 2026-01-17 21:17:57 +00:00
parent e75131aa67
commit 10a441e557
No known key found for this signature in database
8 changed files with 144 additions and 23 deletions

BIN
MacTorn-v1.2.zip Normal file

Binary file not shown.

View file

@ -17,8 +17,8 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.2</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>1.2</string>
</dict>
</plist>

View file

@ -440,6 +440,75 @@ struct WatchlistItem: Codable, Identifiable {
}
}
// MARK: - Update Manager
struct GitHubRelease: Codable {
let tagName: String
let htmlUrl: String
let body: String
enum CodingKeys: String, CodingKey {
case tagName = "tag_name"
case htmlUrl = "html_url"
case body
}
}
class UpdateManager {
static let shared = UpdateManager()
// Configure your repository here
private let githubOwner = "pawelorzech"
private let githubRepo = "MacTorn"
func checkForUpdates(currentVersion: String) async -> GitHubRelease? {
let urlString = "https://api.github.com/repos/\(githubOwner)/\(githubRepo)/releases/latest"
guard let url = URL(string: urlString) else { return nil }
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
return nil
}
let release = try JSONDecoder().decode(GitHubRelease.self, from: data)
// Compare versions
let versionString = release.tagName.replacingOccurrences(of: "v", with: "")
if isVersion(versionString, greaterThan: currentVersion) {
return release
}
} catch {
print("Update check failed: \(error)")
}
return nil
}
private func isVersion(_ newVersion: String, greaterThan currentVersion: String) -> Bool {
let newComponents = newVersion.split(separator: ".").compactMap { Int($0) }
let currentComponents = currentVersion.split(separator: ".").compactMap { Int($0) }
let maxLength = max(newComponents.count, currentComponents.count)
for i in 0..<maxLength {
let new = i < newComponents.count ? newComponents[i] : 0
let current = i < currentComponents.count ? currentComponents[i] : 0
if new > current {
return true
} else if new < current {
return false
}
}
return false
}
}
// MARK: - Error
struct TornError: Codable {
let code: Int

View file

@ -23,9 +23,13 @@ class AppState: ObservableObject {
@Published var propertiesData: [PropertyInfo]?
@Published var watchlistItems: [WatchlistItem] = []
// MARK: - Update State
@Published var updateAvailable: GitHubRelease?
// MARK: - Managers
let launchAtLogin = LaunchAtLoginManager()
let shortcutsManager = ShortcutsManager()
let updateManager = UpdateManager.shared
// MARK: - State Comparison
private var previousBars: Bars?
@ -458,6 +462,19 @@ class AppState: ObservableObject {
}
}
}
// MARK: - Updates
func checkForAppUpdates() {
guard let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { return }
Task {
if let release = await updateManager.checkForUpdates(currentVersion: currentVersion) {
await MainActor.run {
self.updateAvailable = release
}
}
}
}
}
// MARK: - Errors

View file

@ -71,6 +71,7 @@ struct ContentView: View {
}
.task {
await NotificationManager.shared.requestPermission()
appState.checkForAppUpdates()
}
}

View file

@ -114,7 +114,32 @@ struct SettingsView: View {
.background(Color.purple.opacity(0.05))
.cornerRadius(8)
// GitHub
// Update Section
if let update = appState.updateAvailable {
VStack(spacing: 8) {
HStack {
Image(systemName: "arrow.triangle.2.circlepath")
.foregroundColor(.green)
Text("New version available: \(update.tagName)")
.font(.caption.bold())
}
Button("Download Update") {
if let url = URL(string: update.htmlUrl) {
NSWorkspace.shared.open(url)
}
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
.padding(10)
.frame(maxWidth: .infinity)
.background(Color.green.opacity(0.1))
.cornerRadius(8)
}
// GitHub & Version
VStack(spacing: 4) {
HStack(spacing: 4) {
Image(systemName: "chevron.left.forwardslash.chevron.right")
.font(.caption2)
@ -123,6 +148,14 @@ struct SettingsView: View {
destination: URL(string: "https://github.com/pawelorzech/MacTorn")!)
.font(.caption)
}
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
Text("v\(version)")
.font(.caption2)
.foregroundColor(.secondary)
.opacity(0.5)
}
}
}
.padding()
.frame(width: 320)

View file

@ -6,7 +6,9 @@ A native macOS menu bar app for monitoring your **Torn** game status.
![Swift](https://img.shields.io/badge/Swift-5.0-orange)
![License](https://img.shields.io/badge/License-MIT-green)
![MacTorn Screenshot](app.png)
<p align="center">
<img src="app.png" alt="MacTorn Screenshot" width="600">
</p>
## Features
@ -35,18 +37,17 @@ A native macOS menu bar app for monitoring your **Torn** game status.
- Armory quick-use buttons
### 📈 Watchlist Tab
- Track item prices
- Track item prices (Latest API v2 support)
- Displays lowest market price AND quantity (e.g., `$4.2M x12`)
- Price change indicators
- Add/remove items from watchlist
### 🏠 Properties Tab
- Property info and vault contents
- Upkeep status and countdown
### ⚙️ General
- 🔔 Smart notifications for bars, cooldowns, landing, chain
- 🕒 Configurable refresh intervals (15s/30s/60s/2m)
- 🚀 Launch at Login
- **🔄 Update Checker**: Automatically notifies you when a new version is available on GitHub.
- **🔔 Smart Notifications**: Alerts for bar thresholds, cooldown ready, landing, chain expiring.
- **🕒 Configurable Refresh**: Intervals (15s/30s/60s/2m).
- **🚀 Launch at Login**: Start seamlessly with macOS.
- **⚡️ Optimized Startup**: Non-blocking data fetching for instant UI responsiveness.
## Installation
@ -55,12 +56,12 @@ A native macOS menu bar app for monitoring your **Torn** game status.
3. Open MacTorn from Applications
4. Enter your [Torn API Key](https://www.torn.com/preferences.php#tab=api)
> **Note**: If you download an unsigned build, macOS Gatekeeper will block it. Right-click the app and select "Open", or go to System Settings → Privacy & Security → Open Anyway.
> **Note**: If you download an unsigned build, macOS Gatekeeper may block it. Right-click the app and select "Open", or go to System Settings → Privacy & Security → Open Anyway.
## Requirements
- macOS 13.0 (Ventura) or later
- Torn API Key with access to: basic, bars, cooldowns, travel, profile, events, messages
- Torn API Key with access to: basic, bars, cooldowns, travel, profile, events, messages, market
## Configuration
@ -70,8 +71,8 @@ Choose polling frequency: 15s, 30s, 60s, or 120s
### Notifications
MacTorn sends notifications for bar thresholds, cooldown ready, landing, chain expiring, and release. Notification defaults are stored locally.
### Quick Links
8 preset shortcuts to common Torn pages (fully editable)
### Updates
The app checks for updates automatically on startup. If a new version is available, you'll see a notification in the **Settings** tab.
## Building from Source