88 lines
3.1 KiB
Swift
88 lines
3.1 KiB
Swift
// ── Badge Grid View ───────────────────────────────────────────
|
|
// Shows all badges with earned/locked states
|
|
|
|
import SwiftUI
|
|
|
|
struct BadgeGridView: View {
|
|
@ObservedObject var gamification = GamificationStore.shared
|
|
|
|
private var badges: [Badge] {
|
|
BadgeDefinitions.earnedBadges(for: gamification.streak.currentStreak)
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: CMSpacing.md) {
|
|
HStack {
|
|
Text("BADGES")
|
|
.font(CMFonts.body(size: 11, weight: .bold))
|
|
.foregroundStyle(CMColors.textMuted)
|
|
.tracking(1.5)
|
|
Spacer()
|
|
let earned = badges.filter(\.isEarned).count
|
|
Text("\(earned)/\(badges.count)")
|
|
.font(CMFonts.mono(size: 12))
|
|
.foregroundStyle(CMColors.textMuted)
|
|
}
|
|
|
|
LazyVGrid(columns: [
|
|
GridItem(.flexible(), spacing: CMSpacing.md),
|
|
GridItem(.flexible(), spacing: CMSpacing.md),
|
|
GridItem(.flexible(), spacing: CMSpacing.md),
|
|
GridItem(.flexible(), spacing: CMSpacing.md),
|
|
], spacing: CMSpacing.md) {
|
|
ForEach(badges) { badge in
|
|
BadgeCell(badge: badge)
|
|
}
|
|
}
|
|
}
|
|
.padding(CMSpacing.lg)
|
|
.background(CMColors.surface)
|
|
.clipShape(RoundedRectangle(cornerRadius: CMRadius.lg))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: CMRadius.lg)
|
|
.stroke(CMColors.border, lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Badge Cell
|
|
|
|
struct BadgeCell: View {
|
|
let badge: Badge
|
|
|
|
var body: some View {
|
|
VStack(spacing: CMSpacing.xs) {
|
|
ZStack {
|
|
Circle()
|
|
.fill(badge.isEarned ? tierColor(badge.tier).opacity(0.2) : CMColors.border.opacity(0.3))
|
|
.frame(width: 48, height: 48)
|
|
|
|
Image(systemName: badge.icon)
|
|
.font(.system(size: 20))
|
|
.foregroundStyle(badge.isEarned ? tierColor(badge.tier) : CMColors.textMuted.opacity(0.4))
|
|
}
|
|
|
|
Text(badge.title)
|
|
.font(CMFonts.body(size: 9, weight: .medium))
|
|
.foregroundStyle(badge.isEarned ? CMColors.text : CMColors.textMuted)
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(0.8)
|
|
|
|
Text("\(badge.requirement)d")
|
|
.font(CMFonts.mono(size: 8))
|
|
.foregroundStyle(CMColors.textMuted)
|
|
}
|
|
.opacity(badge.isEarned ? 1.0 : 0.5)
|
|
}
|
|
|
|
private func tierColor(_ tier: BadgeTier) -> Color {
|
|
switch tier {
|
|
case .bronze: return Color(red: 0.8, green: 0.5, blue: 0.2)
|
|
case .silver: return Color(red: 0.75, green: 0.75, blue: 0.8)
|
|
case .gold: return Color(red: 1.0, green: 0.84, blue: 0.0)
|
|
case .platinum: return Color(red: 0.7, green: 0.85, blue: 0.95)
|
|
case .diamond: return Color(red: 0.6, green: 0.8, blue: 1.0)
|
|
}
|
|
}
|
|
}
|