Redesign achievements progress bar with milestone circles
Replaces the simple progress bar with a custom milestone bar showing individual circles for each achievement, filled when unlocked with a checkmark indicator. Fixes #27 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -705,18 +705,18 @@ struct AchievementsView: View {
|
||||
private var headerCard: some View {
|
||||
let total = max(achievementStatuses.count, 1)
|
||||
let unlockedCount = unlockedAchievements.count
|
||||
let progress = Double(unlockedCount) / Double(total)
|
||||
|
||||
return VStack(alignment: .leading, spacing: 8) {
|
||||
Text(String(localized: "achievements_progress_title"))
|
||||
.font(.headline)
|
||||
ProgressView(value: progress)
|
||||
.tint(.appSecondary)
|
||||
|
||||
AchievementMilestoneBar(statuses: achievementStatuses)
|
||||
|
||||
Text(
|
||||
String(
|
||||
format: NSLocalizedString("achievements_unlocked_count", comment: ""),
|
||||
unlockedCount,
|
||||
achievementStatuses.count
|
||||
total
|
||||
)
|
||||
)
|
||||
.font(.subheadline)
|
||||
@@ -833,6 +833,61 @@ private extension MonthlyCheckInView {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Achievement Milestone Bar
|
||||
|
||||
struct AchievementMilestoneBar: View {
|
||||
let statuses: [MonthlyCheckInAchievementStatus]
|
||||
|
||||
var body: some View {
|
||||
let total = max(statuses.count, 1)
|
||||
let unlockedCount = statuses.filter(\.isUnlocked).count
|
||||
let progress = Double(unlockedCount) / Double(total)
|
||||
|
||||
GeometryReader { geo in
|
||||
let barWidth = geo.size.width
|
||||
let barHeight: CGFloat = 6
|
||||
|
||||
ZStack(alignment: .leading) {
|
||||
// Background track
|
||||
Capsule()
|
||||
.fill(Color.gray.opacity(0.2))
|
||||
.frame(height: barHeight)
|
||||
|
||||
// Filled track
|
||||
Capsule()
|
||||
.fill(Color.appSecondary)
|
||||
.frame(width: barWidth * progress, height: barHeight)
|
||||
|
||||
// Milestone circles
|
||||
ForEach(Array(statuses.enumerated()), id: \.element.id) { index, status in
|
||||
let position = total == 1
|
||||
? barWidth / 2
|
||||
: barWidth * Double(index) / Double(total - 1)
|
||||
|
||||
Circle()
|
||||
.fill(status.isUnlocked ? Color.appSecondary : Color.gray.opacity(0.3))
|
||||
.frame(width: 14, height: 14)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(Color(.systemBackground), lineWidth: 2)
|
||||
)
|
||||
.overlay(
|
||||
Group {
|
||||
if status.isUnlocked {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 7, weight: .bold))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
)
|
||||
.position(x: position, y: geo.size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 20)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationStack {
|
||||
MonthlyCheckInView()
|
||||
|
||||
Reference in New Issue
Block a user