From 7c80e257bd8d379c13901b3bccf6328464f65258 Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Sat, 30 Apr 2022 14:44:51 +0800 Subject: [PATCH] minor adjustments, show max_resin in statusbar --- PaimonMenuBar/AppDelegate.swift | 29 +++++------ PaimonMenuBar/GameRecordViewModel.swift | 69 ++++++++++++------------- PaimonMenuBar/MenuExtrasView.swift | 6 +-- PaimonMenuBar/Networking.swift | 2 +- 4 files changed, 51 insertions(+), 55 deletions(-) diff --git a/PaimonMenuBar/AppDelegate.swift b/PaimonMenuBar/AppDelegate.swift index 8c0a723..48ffa55 100644 --- a/PaimonMenuBar/AppDelegate.swift +++ b/PaimonMenuBar/AppDelegate.swift @@ -10,33 +10,32 @@ import Foundation import SwiftUI final class AppDelegate: NSObject, NSApplicationDelegate { - - static private(set) var shared: AppDelegate! - + private(set) static var shared: AppDelegate! + /** Must be called in the main thread to avoid race condition. */ func updateStatusBar() { assert(Thread.isMainThread) - + guard let button = statusItem.button else { return } - + button.imagePosition = NSControl.ImagePosition.imageLeading - button.image = NSImage(named:NSImage.Name("FragileResin")) - button.image?.isTemplate = true - button.image?.size.width = 19 - button.image?.size.height = 19 - + button.image = NSImage(named: NSImage.Name("FragileResin")) + button.image?.isTemplate = true // This sets the resin icon in the statusbar as monochrome + button.image?.size.width = 14 + button.image?.size.height = 14 + let gameRecord = GameRecordViewModel.shared.gameRecord if gameRecord.retcode == nil { button.title = "" // Cookie Not configured } else { - button.title = String(gameRecord.data.current_resin) + button.title = "\(gameRecord.data.current_resin)/\(gameRecord.data.max_resin)" } - + let currentExpeditionNum = gameRecord.data.current_expedition_num // 271 = 299 (ViewHeight with Padding) - 28 menuItemMain.frame = NSRect(x: 0, y: 0, width: 280, height: 271 + currentExpeditionNum * 28) } - + private var statusItem: NSStatusItem! private var menuItemMain: NSHostingView! @@ -49,7 +48,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_: Notification) { AppDelegate.shared = self - + // Update game record on initial launch print("App is started") GameRecordViewModel.shared.tryUpdateGameRecord() @@ -89,7 +88,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem.menu = menu - + updateStatusBar() } } diff --git a/PaimonMenuBar/GameRecordViewModel.swift b/PaimonMenuBar/GameRecordViewModel.swift index 58bf250..86abf62 100644 --- a/PaimonMenuBar/GameRecordViewModel.swift +++ b/PaimonMenuBar/GameRecordViewModel.swift @@ -6,12 +6,12 @@ // import Foundation -import SwiftUI import Network +import SwiftUI private let emptyGameRecord = GameRecord( - retcode: nil, // Indicate that this is a mock record - + retcode: nil, // Indicate that this is a mock record + message: "OK", data: GameData( current_resin: 0, max_resin: 160, resin_recovery_time: "0", finished_task_num: 0, @@ -32,7 +32,7 @@ private let emptyGameRecord = GameRecord( class GameRecordViewModel: ObservableObject { /** Singleton GameRecordVM across the application **/ static let shared = GameRecordViewModel() - + private var initialized = false /** The cached game record in userdefaults */ @@ -41,15 +41,14 @@ class GameRecordViewModel: ObservableObject { onGameRecordChanged() } } - - /** The record update interval set in userdefaults */ - // Note: Resin restores every 8 minutes + + /** The record update interval set in userdefaults, resin restores every 8 minutes */ @Published var recordUpdateInterval: Double = 60 * 8 { didSet { onRecordUpdateIntervalChanged() } } - + func updateGameRecordNow() async -> GameRecord? { if let data = await getGameRecord() { DispatchQueue.main.async { @@ -60,26 +59,26 @@ class GameRecordViewModel: ObservableObject { return nil } } - - private var lastUpdateAt: DispatchTime = DispatchTime.init(uptimeNanoseconds: 0) + + private var lastUpdateAt: DispatchTime = .init(uptimeNanoseconds: 0) private var updateTask: Task? - + /** Unlike updateGameRecordNow, this is throttle-protected so that not each call will cause an update. Also it will return immediately, schedules an update in the background. - + Must be called in the main thread to avoid race condition. **/ func tryUpdateGameRecord() { assert(Thread.isMainThread) - + guard updateTask == nil else { // If there is an on-flying request, skip. print("Fetch skipped, there is on-flying request") return } let now = DispatchTime.now() - if now.uptimeNanoseconds - lastUpdateAt.uptimeNanoseconds < 60*UInt64(1e9) { + if now.uptimeNanoseconds - lastUpdateAt.uptimeNanoseconds < 60 * UInt64(1e9) { // If last request is started within 1 minute, skip. print("Fetch skipped, a fetch was performed recently") return @@ -94,17 +93,17 @@ class GameRecordViewModel: ObservableObject { /** Must be called in the main thread to avoid race condition. */ func clearGameRecord() { assert(Thread.isMainThread) - + gameRecord = emptyGameRecord } - + // MARK: - Self-Update the record when network is actve - + private let networkActivityMon = NWPathMonitor() - + private func startNetworkActivityUpdater() { assert(Thread.isMainThread) - + networkActivityMon.pathUpdateHandler = { [weak self] path in if path.status != .satisfied { return @@ -114,25 +113,23 @@ class GameRecordViewModel: ObservableObject { } networkActivityMon.start(queue: DispatchQueue.main) } - - // MARK: - - + // MARK: - Self-Update the record according to the interval - + private var updateTimer: Timer? - + private func resetUpdateTimer() { assert(Thread.isMainThread) - + if updateTimer != nil { updateTimer?.invalidate() } - updateTimer = Timer.scheduledTimer(withTimeInterval: recordUpdateInterval, repeats: true) { timer in + updateTimer = Timer.scheduledTimer(withTimeInterval: recordUpdateInterval, repeats: true) { _ in print("Scheduled update is triggered") self.tryUpdateGameRecord() } } - + // MARK: - /** Key to access userdefaults **/ @@ -146,35 +143,35 @@ class GameRecordViewModel: ObservableObject { { gameRecord = decodedGameRecord } - + if let interval = UserDefaults.standard.object(forKey: recordKeySelfUpdateInterval) as? Double { recordUpdateInterval = interval } - + initialized = true - + startNetworkActivityUpdater() resetUpdateTimer() } private func onGameRecordChanged() { assert(Thread.isMainThread) - + guard initialized else { return } // Ignore any value change when init is not finished - + print("GameRecord is updated\n", gameRecord) if let encodedGameRecord = try? JSONEncoder().encode(gameRecord) { UserDefaults.standard.set(encodedGameRecord, forKey: recordKeyGameRecord) } - + AppDelegate.shared.updateStatusBar() } - + private func onRecordUpdateIntervalChanged() { assert(Thread.isMainThread) - + guard initialized else { return } // Ignore any value change when init is not finished - + print("SelfUpdateInterval is changed to", recordUpdateInterval) UserDefaults.standard.set(recordUpdateInterval, forKey: recordKeySelfUpdateInterval) resetUpdateTimer() diff --git a/PaimonMenuBar/MenuExtrasView.swift b/PaimonMenuBar/MenuExtrasView.swift index 150dde8..64d4ef7 100644 --- a/PaimonMenuBar/MenuExtrasView.swift +++ b/PaimonMenuBar/MenuExtrasView.swift @@ -121,21 +121,21 @@ struct ExpeditionView: View { let currentExpeditionNum: Int var body: some View { - VStack(spacing: 8) { + VStack(spacing: 8) { HStack { Text("Expeditions \(currentExpeditionNum)/\(maxExpeditionNum)") .font(.subheadline) .opacity(0.6) Spacer() } - + ForEach(expeditions, id: \.self) { expedition in ExpeditionItemView( status: expedition.status, avatar: expedition.avatar_side_icon, remainedTime: expedition.remained_time ) } - + Divider() } } diff --git a/PaimonMenuBar/Networking.swift b/PaimonMenuBar/Networking.swift index 91c9711..90fa150 100644 --- a/PaimonMenuBar/Networking.swift +++ b/PaimonMenuBar/Networking.swift @@ -45,7 +45,7 @@ func getGameRecord() async -> GameRecord? { let server: String = UserDefaults.standard.string(forKey: "server"), let cookie: String = UserDefaults.standard.string(forKey: "cookie") else { return nil } - + print("Fetching game record data...", uid, server) let api = isCnServer(server: server) ? apiCn : apiGlobal