diff --git a/MacTorn-v1.2.zip b/MacTorn-v1.2.zip new file mode 100644 index 0000000..9d5a4ba Binary files /dev/null and b/MacTorn-v1.2.zip differ diff --git a/MacTorn-v1.0.zip b/MacTorn/Archive/MacTorn-v1.0.zip similarity index 100% rename from MacTorn-v1.0.zip rename to MacTorn/Archive/MacTorn-v1.0.zip diff --git a/MacTorn/MacTorn/Info.plist b/MacTorn/MacTorn/Info.plist index bc929d5..7e734d3 100644 --- a/MacTorn/MacTorn/Info.plist +++ b/MacTorn/MacTorn/Info.plist @@ -17,8 +17,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + 1.2 CFBundleVersion - 1 + 1.2 diff --git a/MacTorn/MacTorn/Models/TornModels.swift b/MacTorn/MacTorn/Models/TornModels.swift index f795376..335e02d 100644 --- a/MacTorn/MacTorn/Models/TornModels.swift +++ b/MacTorn/MacTorn/Models/TornModels.swift @@ -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.. current { + return true + } else if new < current { + return false + } + } + + return false + } +} + // MARK: - Error struct TornError: Codable { let code: Int diff --git a/MacTorn/MacTorn/ViewModels/AppState.swift b/MacTorn/MacTorn/ViewModels/AppState.swift index d6a5614..0e1ee54 100644 --- a/MacTorn/MacTorn/ViewModels/AppState.swift +++ b/MacTorn/MacTorn/ViewModels/AppState.swift @@ -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 diff --git a/MacTorn/MacTorn/Views/ContentView.swift b/MacTorn/MacTorn/Views/ContentView.swift index 0e7bbed..974cf31 100644 --- a/MacTorn/MacTorn/Views/ContentView.swift +++ b/MacTorn/MacTorn/Views/ContentView.swift @@ -71,6 +71,7 @@ struct ContentView: View { } .task { await NotificationManager.shared.requestPermission() + appState.checkForAppUpdates() } } diff --git a/MacTorn/MacTorn/Views/SettingsView.swift b/MacTorn/MacTorn/Views/SettingsView.swift index edf0a48..d6e75c1 100644 --- a/MacTorn/MacTorn/Views/SettingsView.swift +++ b/MacTorn/MacTorn/Views/SettingsView.swift @@ -114,14 +114,47 @@ struct SettingsView: View { .background(Color.purple.opacity(0.05)) .cornerRadius(8) - // GitHub - HStack(spacing: 4) { - Image(systemName: "chevron.left.forwardslash.chevron.right") - .font(.caption2) - .foregroundColor(.gray) - Link("View on GitHub", - destination: URL(string: "https://github.com/pawelorzech/MacTorn")!) - .font(.caption) + // 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) + .foregroundColor(.gray) + Link("View on GitHub", + 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() diff --git a/README.md b/README.md index 8858f83..ffb36b5 100644 --- a/README.md +++ b/README.md @@ -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) +

+ MacTorn Screenshot +

## 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