Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/PokeTokenBar/Core/UsageStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ final class UsageStore {
}

/// 표시용 한도 % 변환 — remaining 모드면 100−사용률(0 하한: 사용률이 100 을 넘어도 음수 금지).
/// 순수 판정을 분리해 테스트한다. 숫자 *표시* 전용 — 색·게이지·알림 판정에는 쓰지 않는다.
/// 숫자와 게이지 채움에 함께 사용한다. 경고색·알림 판정은 원래 사용률을 유지한다.
nonisolated static func displayPercent(_ utilization: Double, mode: LimitDisplayMode) -> Double {
mode == .remaining ? max(0, 100 - utilization) : utilization
}
Expand Down
32 changes: 19 additions & 13 deletions Sources/PokeTokenBar/UI/PopoverView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,7 @@ struct PopoverView: View {
.foregroundStyle(.tertiary)
}
}
ProgressView(value: min(utilization, 100), total: 100)
.tint(limitColor(utilization))
.controlSize(.small)
LimitProgressBar(usedPercent: utilization, tint: limitColor(utilization))
}
}

Expand Down Expand Up @@ -521,7 +519,7 @@ struct PopoverView: View {


/// 한도 % 표시 문자열 — remaining 모드면 남은 %에 자기설명 접미사("남음/left/残り").
/// 게이지 채움·경고색은 사용률 원값 기준 유지 — 숫자 텍스트만 모드를 따른다.
/// 게이지 채움도 같은 표시 모드를 따르며, 경고색은 실제 사용률로 판단한다.
private func limitPercentText(_ utilization: Double) -> String {
let text = TokenFormatter.percent(store.limitDisplayPercent(utilization))
return store.limitDisplayMode == .remaining ? l.percentRemaining(text) : text
Expand Down Expand Up @@ -566,9 +564,7 @@ struct PopoverView: View {
.foregroundStyle(.tertiary)
}
}
ProgressView(value: min(utilization, 100), total: 100)
.tint(limitColor(utilization))
.controlSize(.small)
LimitProgressBar(usedPercent: utilization, tint: limitColor(utilization))
}
}
}
Expand Down Expand Up @@ -737,9 +733,7 @@ struct PopoverView: View {
.foregroundStyle(.tertiary)
}
}
ProgressView(value: min(utilization, 100), total: 100)
.tint(limitColor(utilization))
.controlSize(.small)
LimitProgressBar(usedPercent: utilization, tint: limitColor(utilization))
}
}
}
Expand All @@ -765,9 +759,7 @@ struct PopoverView: View {
.font(.caption)
.foregroundStyle(.tertiary)
}
ProgressView(value: min(utilization, 100), total: 100)
.tint(limitColor(utilization))
.controlSize(.small)
LimitProgressBar(usedPercent: utilization, tint: limitColor(utilization))
}
}
}
Expand Down Expand Up @@ -1081,3 +1073,17 @@ struct ProviderTabBar: View {
.scrollBounceBehavior(.basedOnSize, axes: .horizontal)
}
}

/// Text and fill describe the same quantity; warning colors still represent actual usage.
@MainActor
struct LimitProgressBar: View {
let usedPercent: Double
let tint: Color
@Environment(UsageStore.self) private var store

var body: some View {
ProgressView(value: min(100, max(0, store.limitDisplayPercent(usedPercent))), total: 100)
.tint(tint)
.controlSize(.small)
}
}
38 changes: 38 additions & 0 deletions Tests/PokeTokenBarTests/LimitProgressRenderingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import AppKit
import SwiftUI
import XCTest
@testable import PokeTokenBar

@MainActor
final class LimitProgressRenderingTests: XCTestCase {
func testRemainingModeReversesRenderedFillAndClampsExhaustedQuota() throws {
let suite = "LimitProgressRendering-\(UUID().uuidString)"
let defaults = try XCTUnwrap(UserDefaults(suiteName: suite))
defer { defaults.removePersistentDomain(forName: suite) }
let store = UsageStore(providers: [], autoRefresh: false, defaults: defaults)

func renderedPercent(_ used: Double) throws -> Double {
let view = LimitProgressBar(usedPercent: used, tint: .blue)
.environment(store).frame(width: 240, height: 20)
let host = NSHostingController(rootView: view)
host.view.frame = NSRect(x: 0, y: 0, width: 240, height: 20)
host.view.layoutSubtreeIfNeeded()
func descendants(_ view: NSView) -> [NSView] {
[view] + view.subviews.flatMap(descendants)
}
let views = descendants(host.view)
let bar = try XCTUnwrap(views.compactMap { $0 as? NSProgressIndicator }.first,
views.map { String(describing: type(of: $0)) }.joined(separator: ","))
return bar.doubleValue / bar.maxValue * 100
}

store.limitDisplayMode = .used
XCTAssertEqual(try renderedPercent(25), 25, accuracy: 0.01)
store.limitDisplayMode = .remaining
XCTAssertEqual(try renderedPercent(25), 75, accuracy: 0.01)
XCTAssertEqual(try renderedPercent(125), 0, accuracy: 0.01)
XCTAssertEqual(try renderedPercent(0), 100, accuracy: 0.01)
store.limitDisplayMode = .used
XCTAssertEqual(try renderedPercent(25), 25, accuracy: 0.01)
}
}