feat: replace workout progress bar with dot-grid indicator

7 rows of dots fill left-to-right, top-to-bottom as sets complete.
Filled dots: AppColors.green solid. Empty: green at 12% opacity.
Also: strip emoji from completion banner, tighten copy.
This commit is contained in:
kutesir
2026-05-28 18:55:09 +03:00
parent e06f15c585
commit 05a579e2f7

View File

@@ -445,8 +445,8 @@ struct WorkoutSessionSheet: View {
}
.padding(.horizontal, 20).padding(.top, 20).padding(.bottom, 14)
// Progress Bar
VStack(spacing: 6) {
// Dot Grid Progress
VStack(spacing: 8) {
HStack {
Text("PROGRESS")
.font(AppFonts.mono(8.5, weight: .bold))
@@ -454,37 +454,24 @@ struct WorkoutSessionSheet: View {
Spacer()
Text(vm.totalSets > 0 ? "\(Int(vm.progress * 100))%" : "0%")
.font(AppFonts.mono(10, weight: .bold))
.foregroundColor(vm.progress >= 1 ? AppColors.green : AppColors.accent)
.foregroundColor(vm.progress >= 1 ? AppColors.green : AppColors.text2(cs))
}
GeometryReader { geo in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 5)
.fill(AppColors.borderHi(cs))
.frame(height: 8)
if vm.totalSets > 0 {
RoundedRectangle(cornerRadius: 5)
.fill(vm.progress >= 1 ? AppColors.green : AppColors.accent)
.frame(width: geo.size.width * CGFloat(min(vm.progress, 1)), height: 8)
.animation(KisaniSpring.entrance, value: vm.progress)
}
}
}
.frame(height: 8)
DotGridProgress(progress: vm.totalSets > 0 ? vm.progress : 0, cs: cs)
}
.padding(.horizontal, 20).padding(.bottom, 14)
// Completion Banner
if vm.progress >= 1 && vm.totalSets > 0 {
HStack(spacing: 8) {
Image(systemName: "checkmark.seal.fill")
.font(.system(size: 15)).foregroundColor(AppColors.green)
Text("Workout complete! Great work 🎉")
.font(AppFonts.sans(12.5, weight: .semibold))
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 14)).foregroundColor(AppColors.green)
Text("All sets done")
.font(AppFonts.mono(11, weight: .bold))
.foregroundColor(AppColors.green)
Spacer()
}
.padding(.horizontal, 20).padding(.vertical, 11)
.background(AppColors.green.opacity(0.09))
.padding(.horizontal, 20).padding(.vertical, 10)
.background(AppColors.green.opacity(0.08))
}
Divider().background(AppColors.border(cs))
@@ -1088,3 +1075,51 @@ struct AddTaskSheet: View {
dismiss()
}
}
// MARK: - Dot Grid Progress
struct DotGridProgress: View {
let progress: Double
let cs: ColorScheme
private let dotSize: CGFloat = 7.5
private let gap: CGFloat = 2.5
private let rows = 7
var body: some View {
GeometryReader { geo in
let cols = max(1, Int((geo.size.width + gap) / (dotSize + gap)))
let total = cols * rows
let filled = Int(Double(total) * min(max(progress, 0), 1))
VStack(alignment: .leading, spacing: gap) {
ForEach(0..<rows, id: \.self) { row in
DotRow(cols: cols, rowStart: row * cols, filled: filled,
dotSize: dotSize, gap: gap, cs: cs)
}
}
.animation(KisaniSpring.entrance, value: filled)
}
.frame(height: CGFloat(rows) * dotSize + CGFloat(rows - 1) * gap)
}
}
private struct DotRow: View {
let cols: Int
let rowStart: Int
let filled: Int
let dotSize: CGFloat
let gap: CGFloat
let cs: ColorScheme
var body: some View {
HStack(spacing: gap) {
ForEach(0..<cols, id: \.self) { col in
let isFilled = (rowStart + col) < filled
Circle()
.fill(isFilled ? AppColors.green : AppColors.green.opacity(0.12))
.frame(width: dotSize, height: dotSize)
}
}
}
}