diff --git a/PaimonMenuBar.xcodeproj/project.pbxproj b/PaimonMenuBar.xcodeproj/project.pbxproj index 0e3abe4..181e0ed 100644 --- a/PaimonMenuBar.xcodeproj/project.pbxproj +++ b/PaimonMenuBar.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 76085E6427FC23EA00960915 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 76085E6327FC23EA00960915 /* Sparkle */; }; 7621675327F2FC080023F8B2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7621675527F2FC080023F8B2 /* Localizable.strings */; }; 7686474127EF082400BCC350 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7686474027EF082400BCC350 /* Bundle.swift */; }; - 76C2009027EE124B0026D6CC /* MenuBarResinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C2008F27EE124B0026D6CC /* MenuBarResinView.swift */; }; 76C290F027EAFFB000A30C9F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C290EF27EAFFB000A30C9F /* AppDelegate.swift */; }; 76CCDDDE27EAD1C4009CFC64 /* PaimonMenuBarApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76CCDDDD27EAD1C4009CFC64 /* PaimonMenuBarApp.swift */; }; 76CCDDE027EAD1C4009CFC64 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76CCDDDF27EAD1C4009CFC64 /* SettingsView.swift */; }; @@ -19,7 +18,6 @@ 76CCDDE527EAD1C5009CFC64 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 76CCDDE427EAD1C5009CFC64 /* Preview Assets.xcassets */; }; 76D73BBF27EC650500CCDEA6 /* GameRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76D73BBE27EC650500CCDEA6 /* GameRecord.swift */; }; 76D73BC127EC67D300CCDEA6 /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76D73BC027EC67D300CCDEA6 /* Networking.swift */; }; - 76DD33FE27EF5CA400F0A563 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76DD33FD27EF5CA400F0A563 /* NetworkMonitor.swift */; }; 76E429A927EDDE000032313C /* GameRecordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E429A827EDDE000032313C /* GameRecordViewModel.swift */; }; 76E986B627EDD5FC004ECC6C /* MenuExtrasView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E986B527EDD5FC004ECC6C /* MenuExtrasView.swift */; }; 76F9AE6D27F570D90051CDC8 /* UpdaterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F9AE6C27F570D90051CDC8 /* UpdaterViewModel.swift */; }; @@ -30,7 +28,6 @@ 7621675627F2FC0B0023F8B2 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7686474027EF082400BCC350 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 76B3F03127F2B76100833555 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 76C2008F27EE124B0026D6CC /* MenuBarResinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarResinView.swift; sourceTree = ""; }; 76C290EF27EAFFB000A30C9F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 76CCDDDD27EAD1C4009CFC64 /* PaimonMenuBarApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaimonMenuBarApp.swift; sourceTree = ""; }; 76CCDDDF27EAD1C4009CFC64 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; @@ -39,7 +36,6 @@ 76CCDDE627EAD1C5009CFC64 /* PaimonMenuBar.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PaimonMenuBar.entitlements; sourceTree = ""; }; 76D73BBE27EC650500CCDEA6 /* GameRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameRecord.swift; sourceTree = ""; }; 76D73BC027EC67D300CCDEA6 /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = ""; }; - 76DD33FD27EF5CA400F0A563 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; 76E429A827EDDE000032313C /* GameRecordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameRecordViewModel.swift; sourceTree = ""; }; 76E986B527EDD5FC004ECC6C /* MenuExtrasView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuExtrasView.swift; sourceTree = ""; }; 76F9AE6B27F570640051CDC8 /* PaimonMenuBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PaimonMenuBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -82,8 +78,6 @@ 76CCDDE627EAD1C5009CFC64 /* PaimonMenuBar.entitlements */, 76CCDDE327EAD1C5009CFC64 /* Preview Content */, 76C290EF27EAFFB000A30C9F /* AppDelegate.swift */, - 76C2008F27EE124B0026D6CC /* MenuBarResinView.swift */, - 76DD33FD27EF5CA400F0A563 /* NetworkMonitor.swift */, 76D73BBE27EC650500CCDEA6 /* GameRecord.swift */, 76D73BC027EC67D300CCDEA6 /* Networking.swift */, ); @@ -207,9 +201,7 @@ 76D73BC127EC67D300CCDEA6 /* Networking.swift in Sources */, 76F9AE6D27F570D90051CDC8 /* UpdaterViewModel.swift in Sources */, 76CCDDE027EAD1C4009CFC64 /* SettingsView.swift in Sources */, - 76DD33FE27EF5CA400F0A563 /* NetworkMonitor.swift in Sources */, 76CCDDDE27EAD1C4009CFC64 /* PaimonMenuBarApp.swift in Sources */, - 76C2009027EE124B0026D6CC /* MenuBarResinView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -278,7 +270,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 12.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -332,7 +324,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 12.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; diff --git a/PaimonMenuBar/AppDelegate.swift b/PaimonMenuBar/AppDelegate.swift index d908525..48ffa55 100644 --- a/PaimonMenuBar/AppDelegate.swift +++ b/PaimonMenuBar/AppDelegate.swift @@ -10,12 +10,34 @@ import Foundation import SwiftUI final class AppDelegate: NSObject, NSApplicationDelegate { - private var statusItem: NSStatusItem! + 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 // 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 = "\(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 lazy var contentView: NSView? = { - let view = (statusItem.value(forKey: "window") as? NSWindow)?.contentView - return view - }() + private var statusItem: NSStatusItem! + private var menuItemMain: NSHostingView! @objc private func openSettingsView() { NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) @@ -25,10 +47,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } func applicationDidFinishLaunching(_: Notification) { + AppDelegate.shared = self + // Update game record on initial launch - Task { - await GameRecordViewModel.shared.updateGameRecord() - } + print("App is started") + GameRecordViewModel.shared.tryUpdateGameRecord() // Close main APP window on initial launch NSApp.setActivationPolicy(.accessory) @@ -36,8 +59,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { window.close() } - setupStatusItem() - setupMenus() + setupStatusBar() } func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { @@ -46,30 +68,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return false } - private func setupStatusItem() { - statusItem = NSStatusBar.system.statusItem(withLength: 100) - - let hostingView = NSHostingView(rootView: MenuBarResinView()) - hostingView.translatesAutoresizingMaskIntoConstraints = false - guard let contentView = contentView else { return } - contentView.addSubview(hostingView) - - NSLayoutConstraint.activate([ - hostingView.topAnchor.constraint(equalTo: contentView.topAnchor), - hostingView.rightAnchor.constraint(equalTo: contentView.rightAnchor), - hostingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - hostingView.leftAnchor.constraint(equalTo: contentView.leftAnchor), - ]) - } - - private func setupMenus() { + private func setupStatusBar() { let menu = NSMenu() // Main menu area, render view as NSHostingView + menuItemMain = NSHostingView(rootView: MenuExtrasView()) let menuItem = NSMenuItem() - GameRecordViewModel.shared.hostingView = NSHostingView(rootView: AnyView(MenuExtrasView())) - GameRecordViewModel.shared.hostingView?.frame = NSRect(x: 0, y: 0, width: 280, height: 425) - menuItem.view = GameRecordViewModel.shared.hostingView + menuItem.view = menuItemMain menu.addItem(menuItem) // Submenu, preferences, and quit APP @@ -81,6 +86,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { .addItem(NSMenuItem(title: String(localized: "Quit"), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem.menu = menu + + updateStatusBar() } } diff --git a/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@1.png b/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@1.png index 8afabb7..4263b2a 100644 Binary files a/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@1.png and b/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@1.png differ diff --git a/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@2.png b/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@2.png index c2d3922..e65938a 100644 Binary files a/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@2.png and b/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@2.png differ diff --git a/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@3.png b/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@3.png index 810495c..9ccee6f 100644 Binary files a/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@3.png and b/PaimonMenuBar/Assets.xcassets/FragileResin.imageset/fragile_resin@3.png differ diff --git a/PaimonMenuBar/GameRecord.swift b/PaimonMenuBar/GameRecord.swift index 56c200b..9b86bbe 100644 --- a/PaimonMenuBar/GameRecord.swift +++ b/PaimonMenuBar/GameRecord.swift @@ -8,7 +8,10 @@ import Foundation struct GameRecord: Codable { - var retcode: Int + /** + We specifically use nil to mark that this GameRecord is valid. The server will always present this field in the response. + */ + var retcode: Int? var message: String var data: GameData diff --git a/PaimonMenuBar/GameRecordViewModel.swift b/PaimonMenuBar/GameRecordViewModel.swift index eb9b463..86abf62 100644 --- a/PaimonMenuBar/GameRecordViewModel.swift +++ b/PaimonMenuBar/GameRecordViewModel.swift @@ -6,14 +6,17 @@ // import Foundation +import Network import SwiftUI -let initGameRecord = GameRecord( - retcode: 0, message: "OK", +private let emptyGameRecord = GameRecord( + 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, total_task_num: 4, is_extra_task_reward_received: false, remain_resin_discount_num: 0, - resin_discount_num_limit: 3, current_expedition_num: 0, max_expedition_num: 5, + resin_discount_num_limit: 3, current_expedition_num: 1, max_expedition_num: 5, expeditions: [Expeditions(status: "Finished", avatar_side_icon: "", remained_time: "0")], current_home_coin: 0, max_home_coin: 2400, home_coin_recovery_time: "0", calendar_url: "", transformer: Transformer( @@ -23,39 +26,33 @@ let initGameRecord = GameRecord( ) ) +/** + This ViewModel also drives itself to update continuously. + **/ class GameRecordViewModel: ObservableObject { - // Shared GameRecordVM across the application + /** Singleton GameRecordVM across the application **/ static let shared = GameRecordViewModel() - @Published var hostingView: NSHostingView? - @Published var gameRecord: GameRecord = initGameRecord { + private var initialized = false + + /** The cached game record in userdefaults */ + @Published private(set) var gameRecord: GameRecord = emptyGameRecord { didSet { - // Save game record to userdefaults on change - saveGameRecord() + onGameRecordChanged() } } - // Game record key saved in userdefaults - let gameRecordKey = "game_record" - - init() { - // Try to load game record from user defaults - if let data = UserDefaults.standard.data(forKey: gameRecordKey), - let decodedGameRecord = try? JSONDecoder().decode(GameRecord.self, from: data) - { - gameRecord = decodedGameRecord + /** The record update interval set in userdefaults, resin restores every 8 minutes */ + @Published var recordUpdateInterval: Double = 60 * 8 { + didSet { + onRecordUpdateIntervalChanged() } } - func updateGameRecord() async -> GameRecord? { - print("Fetching data...") + func updateGameRecordNow() async -> GameRecord? { if let data = await getGameRecord() { DispatchQueue.main.async { self.gameRecord = data - // Update hostingView frame height on gameRecord change - let currentExpeditionNum = data.data.current_expedition_num - print(currentExpeditionNum) - self.hostingView?.frame = NSRect(x: 0, y: 0, width: 280, height: 265 + currentExpeditionNum * 32) } return data } else { @@ -63,13 +60,120 @@ class GameRecordViewModel: ObservableObject { } } + 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 last request is started within 1 minute, skip. + print("Fetch skipped, a fetch was performed recently") + return + } + lastUpdateAt = now + updateTask = Task { + _ = await updateGameRecordNow() + updateTask = nil + } + } + + /** Must be called in the main thread to avoid race condition. */ func clearGameRecord() { - gameRecord = initGameRecord + assert(Thread.isMainThread) + + gameRecord = emptyGameRecord } - func saveGameRecord() { + // 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 + } + print("Network is active") + self?.tryUpdateGameRecord() + } + networkActivityMon.start(queue: DispatchQueue.main) + } + + // 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) { _ in + print("Scheduled update is triggered") + self.tryUpdateGameRecord() + } + } + + // MARK: - + + /** Key to access userdefaults **/ + private let recordKeyGameRecord = "game_record" + private let recordKeySelfUpdateInterval = "update_interval" + + init() { + // Try to load game record from user defaults + if let data = UserDefaults.standard.data(forKey: recordKeyGameRecord), + let decodedGameRecord = try? JSONDecoder().decode(GameRecord.self, from: data) + { + 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: gameRecordKey) + 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/MenuBarResinView.swift b/PaimonMenuBar/MenuBarResinView.swift deleted file mode 100644 index bc01218..0000000 --- a/PaimonMenuBar/MenuBarResinView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// MenuBarResinView.swift -// PaimonMenuBar -// -// Created by Spencer Woo on 2022/3/25. -// - -import Combine -import SwiftUI - -struct MenuBarResinView: View { - @StateObject var gameRecordVM = GameRecordViewModel.shared - @ObservedObject var monitor = NetworkMonitor() - @AppStorage("update_interval") private var updateInterval: Double = 60 * 8 // Resin restores every 8 minutes - - // Init timer before view appears - @State var timer = Timer.publish(every: 60, tolerance: 10, on: .main, in: .common).autoconnect() - - var body: some View { - HStack(spacing: 4) { - Image("FragileResin") - .resizable() - .frame(width: 19, height: 19) - Text("\(gameRecordVM.gameRecord.data.current_resin)/\(gameRecordVM.gameRecord.data.max_resin)") - .font(.system(.body, design: .monospaced).bold()) - } - .onAppear { - // Update timer based on saved updateInterval after view loads - self.timer = Timer.publish(every: updateInterval, tolerance: 10, on: .main, in: .common).autoconnect() - } - .onReceive(timer) { _ in - Task { - await gameRecordVM.updateGameRecord() - } - } - .onChange(of: updateInterval) { interval in - // Update timer when updateInterval changes in settings - timer = Timer.publish(every: interval, tolerance: 10, on: .main, in: .common).autoconnect() - } - .onChange(of: monitor.isConnected) { connected in - Task { - // Update game record when application reconnects to the internet - if connected { - let _ = await gameRecordVM.updateGameRecord() - } - } - } - } -} diff --git a/PaimonMenuBar/MenuExtrasView.swift b/PaimonMenuBar/MenuExtrasView.swift index f274292..64d4ef7 100644 --- a/PaimonMenuBar/MenuExtrasView.swift +++ b/PaimonMenuBar/MenuExtrasView.swift @@ -71,8 +71,8 @@ struct MenuExtrasView: View { ParametricTransformerView(transformer: gameRecordVM.gameRecord.data.transformer) } - .padding([.horizontal, .top]) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .padding([.horizontal]) + .padding([.vertical], 8) } } @@ -121,23 +121,23 @@ struct ExpeditionView: View { let currentExpeditionNum: Int var body: some View { - HStack { - Text("Expeditions \(currentExpeditionNum)/\(maxExpeditionNum)") - .font(.subheadline) - .opacity(0.6) - Spacer() - } + VStack(spacing: 8) { + HStack { + Text("Expeditions \(currentExpeditionNum)/\(maxExpeditionNum)") + .font(.subheadline) + .opacity(0.6) + Spacer() + } - VStack { ForEach(expeditions, id: \.self) { expedition in ExpeditionItemView( status: expedition.status, avatar: expedition.avatar_side_icon, remainedTime: expedition.remained_time ) } - } - Divider() + Divider() + } } } diff --git a/PaimonMenuBar/NetworkMonitor.swift b/PaimonMenuBar/NetworkMonitor.swift deleted file mode 100644 index 8d575fd..0000000 --- a/PaimonMenuBar/NetworkMonitor.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// NetworkMonitor.swift -// PaimonMenuBar -// -// Created by Spencer Woo on 2022/3/26. -// - -import Foundation -import Network - -final class NetworkMonitor: ObservableObject { - let monitor = NWPathMonitor() - let queue = DispatchQueue(label: "Monitor") - - @Published var isConnected = true - - init() { - monitor.pathUpdateHandler = { [weak self] path in - DispatchQueue.main.async { - self?.isConnected = path.status == .satisfied ? true : false - } - } - monitor.start(queue: queue) - } -} diff --git a/PaimonMenuBar/Networking.swift b/PaimonMenuBar/Networking.swift index aef2944..90fa150 100644 --- a/PaimonMenuBar/Networking.swift +++ b/PaimonMenuBar/Networking.swift @@ -46,6 +46,8 @@ func getGameRecord() async -> GameRecord? { 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 guard let url = URL(string: "\(api)?role_id=\(uid)&server=\(server)") else { return nil } diff --git a/PaimonMenuBar/PaimonMenuBar.entitlements b/PaimonMenuBar/PaimonMenuBar.entitlements index 82d7078..95c1bcf 100644 --- a/PaimonMenuBar/PaimonMenuBar.entitlements +++ b/PaimonMenuBar/PaimonMenuBar.entitlements @@ -8,8 +8,6 @@ com.apple.security.network.client - com.apple.security.network.server - com.apple.security.temporary-exception.mach-lookup.global-name $(PRODUCT_BUNDLE_IDENTIFIER)-spks diff --git a/PaimonMenuBar/SettingsView.swift b/PaimonMenuBar/SettingsView.swift index 9a799ca..bc2fb2c 100644 --- a/PaimonMenuBar/SettingsView.swift +++ b/PaimonMenuBar/SettingsView.swift @@ -20,7 +20,7 @@ struct CheckForUpdatesView: View { } struct PreferenceSettingsView: View { - @AppStorage("update_interval") private var updateInterval: Double = 60 * 8 // Resin restores every 6 minutes + @StateObject var gameRecordVM = GameRecordViewModel.shared @StateObject var updaterViewModel = UpdaterViewModel.shared @@ -39,14 +39,14 @@ struct PreferenceSettingsView: View { Text("Current version: \(Bundle.main.appVersion ?? "") (\(Bundle.main.buildNumber ?? ""))") .font(.caption).opacity(0.6) - Slider(value: $updateInterval, in: 60 ... 16 * 60, step: 60, label: { + Slider(value: $gameRecordVM.recordUpdateInterval, in: 60 ... 16 * 60, step: 60, label: { Text("Update interval:") }) { editing in isEditing = editing } .frame(width: 400) - Text("Paimon fetches data every \(updateInterval, specifier: "%.0f") seconds*") + Text("Paimon fetches data every \(gameRecordVM.recordUpdateInterval, specifier: "%.0f") seconds*") .font(.caption).opacity(0.6) } @@ -114,7 +114,7 @@ struct ConfigurationSettingsView: View { Button { Task { isLoading = true - if let _ = await GameRecordViewModel.shared.updateGameRecord() { + if let _ = await GameRecordViewModel.shared.updateGameRecordNow() { self.alertText = String(localized: "👌 It's working!") self.alertMessage = String(localized: "Your config is valid.") self.showConfigValidAlert.toggle()