Skip to content
This repository has been archived by the owner on Jan 23, 2024. It is now read-only.

Commit

Permalink
minor adjustments, show max_resin in statusbar
Browse files Browse the repository at this point in the history
  • Loading branch information
spencerwooo committed Apr 30, 2022
1 parent 2b3d844 commit 7c80e25
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 55 deletions.
29 changes: 14 additions & 15 deletions PaimonMenuBar/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<MenuExtrasView>!

Expand All @@ -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()
Expand Down Expand Up @@ -89,7 +88,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.menu = menu

updateStatusBar()
}
}
69 changes: 33 additions & 36 deletions PaimonMenuBar/GameRecordViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 */
Expand All @@ -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 {
Expand All @@ -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<Void, Never>?

/**
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
Expand All @@ -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
Expand All @@ -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 **/
Expand All @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions PaimonMenuBar/MenuExtrasView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
2 changes: 1 addition & 1 deletion PaimonMenuBar/Networking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 7c80e25

Please sign in to comment.