learning_ai_clock/ios/ChronoMind/Views/Gamification/BadgeGridView.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)
}
}
}