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:
@@ -445,8 +445,8 @@ struct WorkoutSessionSheet: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, 20).padding(.top, 20).padding(.bottom, 14)
|
.padding(.horizontal, 20).padding(.top, 20).padding(.bottom, 14)
|
||||||
|
|
||||||
// ── Progress Bar ──
|
// ── Dot Grid Progress ──
|
||||||
VStack(spacing: 6) {
|
VStack(spacing: 8) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("PROGRESS")
|
Text("PROGRESS")
|
||||||
.font(AppFonts.mono(8.5, weight: .bold))
|
.font(AppFonts.mono(8.5, weight: .bold))
|
||||||
@@ -454,37 +454,24 @@ struct WorkoutSessionSheet: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Text(vm.totalSets > 0 ? "\(Int(vm.progress * 100))%" : "0%")
|
Text(vm.totalSets > 0 ? "\(Int(vm.progress * 100))%" : "0%")
|
||||||
.font(AppFonts.mono(10, weight: .bold))
|
.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
|
DotGridProgress(progress: vm.totalSets > 0 ? vm.progress : 0, cs: cs)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20).padding(.bottom, 14)
|
.padding(.horizontal, 20).padding(.bottom, 14)
|
||||||
|
|
||||||
// ── Completion Banner ──
|
// ── Completion Banner ──
|
||||||
if vm.progress >= 1 && vm.totalSets > 0 {
|
if vm.progress >= 1 && vm.totalSets > 0 {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: "checkmark.seal.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.font(.system(size: 15)).foregroundColor(AppColors.green)
|
.font(.system(size: 14)).foregroundColor(AppColors.green)
|
||||||
Text("Workout complete! Great work 🎉")
|
Text("All sets done")
|
||||||
.font(AppFonts.sans(12.5, weight: .semibold))
|
.font(AppFonts.mono(11, weight: .bold))
|
||||||
.foregroundColor(AppColors.green)
|
.foregroundColor(AppColors.green)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20).padding(.vertical, 11)
|
.padding(.horizontal, 20).padding(.vertical, 10)
|
||||||
.background(AppColors.green.opacity(0.09))
|
.background(AppColors.green.opacity(0.08))
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider().background(AppColors.border(cs))
|
Divider().background(AppColors.border(cs))
|
||||||
@@ -1088,3 +1075,51 @@ struct AddTaskSheet: View {
|
|||||||
dismiss()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user