feat: show animated installation progress bar in menu bar during update
This commit is contained in:
parent
715b5b26f2
commit
7710a300fc
2 changed files with 70 additions and 5 deletions
|
|
@ -56,6 +56,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
private func updateStatusItem() {
|
private func updateStatusItem() {
|
||||||
guard let button = statusItem?.button else { return }
|
guard let button = statusItem?.button else { return }
|
||||||
|
|
||||||
|
if updater.isUpdating {
|
||||||
|
button.image = NSImage(systemSymbolName: "arrow.triangle.2.circlepath", accessibilityDescription: "Updating")
|
||||||
|
let text = updater.progressText.isEmpty ? "Обновление" : updater.progressText
|
||||||
|
button.title = " \(updater.progressVisual) \(text)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let active = tracker.activeHeadphone
|
let active = tracker.activeHeadphone
|
||||||
let isConn = active?.isConnected == true
|
let isConn = active?.isConnected == true
|
||||||
let percent = active?.primaryBatteryPercent
|
let percent = active?.primaryBatteryPercent
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,64 @@ public final class UpdaterService: ObservableObject {
|
||||||
|
|
||||||
@Published public var isUpdating: Bool = false
|
@Published public var isUpdating: Bool = false
|
||||||
@Published public var updateStatusMessage: String = ""
|
@Published public var updateStatusMessage: String = ""
|
||||||
|
@Published public var progressText: String = ""
|
||||||
|
@Published public var progressVisual: String = "▰▱▱▱▱"
|
||||||
|
|
||||||
|
private var animationTimer: Timer?
|
||||||
|
private var animIndex = 0
|
||||||
|
private let animFrames = ["▰▱▱▱▱", "▰▰▱▱▱", "▰▰▰▱▱", "▰▰▰▰▱", "▰▰▰▰▰", "▱▱▱▱▱"]
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
private func startAnimation(initialStage: String) {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.progressText = initialStage
|
||||||
|
self.progressVisual = self.animFrames[0]
|
||||||
|
self.animationTimer?.invalidate()
|
||||||
|
self.animIndex = 0
|
||||||
|
|
||||||
|
self.animationTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in
|
||||||
|
guard let self = self, self.isUpdating else { return }
|
||||||
|
self.animIndex = (self.animIndex + 1) % self.animFrames.count
|
||||||
|
self.progressVisual = self.animFrames[self.animIndex]
|
||||||
|
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
||||||
|
}
|
||||||
|
RunLoop.main.add(self.animationTimer!, forMode: .common)
|
||||||
|
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateStage(stage: String, visual: String? = nil) {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.progressText = stage
|
||||||
|
if let v = visual { self.progressVisual = v }
|
||||||
|
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopAnimation() {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.animationTimer?.invalidate()
|
||||||
|
self?.animationTimer = nil
|
||||||
|
self?.isUpdating = false
|
||||||
|
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func checkForUpdatesAndApply() {
|
public func checkForUpdatesAndApply() {
|
||||||
guard !isUpdating else { return }
|
guard !isUpdating else { return }
|
||||||
|
|
||||||
isUpdating = true
|
isUpdating = true
|
||||||
updateStatusMessage = "Проверка обновлений на GitHub..."
|
updateStatusMessage = "Проверка обновлений на GitHub..."
|
||||||
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
startAnimation(initialStage: "Проверка")
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||||
let projectDir = "/Users/autli/Documents/blts_tracker"
|
let projectDir = "/Users/autli/Documents/blts_tracker"
|
||||||
|
|
||||||
|
self?.updateStage(stage: "Связь с GitHub", visual: "▰▰▱▱▱")
|
||||||
|
|
||||||
let updateScript = """
|
let updateScript = """
|
||||||
cd "\(projectDir)"
|
cd "\(projectDir)"
|
||||||
git -c http.timeout=8 fetch origin main 2>&1
|
git -c http.timeout=8 fetch origin main 2>&1
|
||||||
|
|
@ -66,16 +112,29 @@ public final class UpdaterService: ObservableObject {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try process.run()
|
try process.run()
|
||||||
|
|
||||||
|
// Advance visual stages during execution
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||||
|
if self?.isUpdating == true {
|
||||||
|
self?.updateStage(stage: "Загрузка", visual: "▰▰▰▱▱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + 2.5) {
|
||||||
|
if self?.isUpdating == true {
|
||||||
|
self?.updateStage(stage: "Сборка", visual: "▰▰▰▰▱")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
process.waitUntilExit()
|
process.waitUntilExit()
|
||||||
|
|
||||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
let output = String(data: data, encoding: .utf8) ?? ""
|
let output = String(data: data, encoding: .utf8) ?? ""
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self?.isUpdating = false
|
self?.stopAnimation()
|
||||||
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
|
||||||
|
|
||||||
if output.contains("SUCCESS") {
|
if output.contains("SUCCESS") {
|
||||||
|
self?.updateStage(stage: "Готово", visual: "▰▰▰▰▰")
|
||||||
self?.sendNotification(
|
self?.sendNotification(
|
||||||
title: "🎉 Обновление установлено",
|
title: "🎉 Обновление установлено",
|
||||||
body: "BLTS Tracker успешно обновлён с GitHub! Перезапуск..."
|
body: "BLTS Tracker успешно обновлён с GitHub! Перезапуск..."
|
||||||
|
|
@ -107,8 +166,7 @@ public final class UpdaterService: ObservableObject {
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self?.isUpdating = false
|
self?.stopAnimation()
|
||||||
NotificationCenter.default.post(name: UpdaterService.didUpdateStateNotification, object: self)
|
|
||||||
self?.sendNotification(
|
self?.sendNotification(
|
||||||
title: "⚠️ Ошибка",
|
title: "⚠️ Ошибка",
|
||||||
body: "Не удалось выполнить проверку: \(error.localizedDescription)"
|
body: "Не удалось выполнить проверку: \(error.localizedDescription)"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue