refactor(design): remove gamification — replace emoji icons with progress ring

WorkoutTodayBanner: progress ring replaces emoji cell as the lead
visual. Card background is now surface/border, not blue-tinted.

StreakBannerView: strip gradient, dot trail, and "Don't break the
chain". Replace with clean number + 7-tick week bar.

ProgressRingCard: "Keep going 🔥" / "Workout complete! 🎉" removed;
replaced with "N sets remaining" / "All sets done".

Sample programs: 🔥 Shoulders & Arms -> 🔄, 💥 Full Body -> 🏋️.
Emoji picker: 🔥 replaced with 🫀 (heart/cardio) as last option.
This commit is contained in:
kutesir
2026-05-27 23:41:15 +03:00
parent 1b29fb996f
commit 36191f616f
3 changed files with 72 additions and 66 deletions

View File

@@ -527,7 +527,7 @@ class WorkoutViewModel: ObservableObject {
]) ])
// Wednesday: Shoulders, Triceps & Biceps // Wednesday: Shoulders, Triceps & Biceps
let wed = WorkoutProgram(name: "Shoulders & Arms", emoji: "🔥", colorIndex: 2, sections: [ let wed = WorkoutProgram(name: "Shoulders & Arms", emoji: "🔄", colorIndex: 2, sections: [
warmUp, warmUp,
WorkoutSection(name: "Shoulders", exercises: [ WorkoutSection(name: "Shoulders", exercises: [
Exercise(name: "Reverse Fly", sfSymbol: "dumbbell.fill", colorIndex: 2, sets: s(3)), Exercise(name: "Reverse Fly", sfSymbol: "dumbbell.fill", colorIndex: 2, sets: s(3)),
@@ -580,7 +580,7 @@ class WorkoutViewModel: ObservableObject {
]) ])
// Saturday: Full Body // Saturday: Full Body
let sat = WorkoutProgram(name: "Full Body", emoji: "💥", colorIndex: 1, sections: [ let sat = WorkoutProgram(name: "Full Body", emoji: "🏋️", colorIndex: 1, sections: [
warmUp, warmUp,
WorkoutSection(name: "Upper Body", exercises: [ WorkoutSection(name: "Upper Body", exercises: [
Exercise(name: "Incline Bench Press",sfSymbol: "figure.strengthtraining.traditional", colorIndex: 0, sets: s(3)), Exercise(name: "Incline Bench Press",sfSymbol: "figure.strengthtraining.traditional", colorIndex: 0, sets: s(3)),

View File

@@ -307,62 +307,61 @@ struct WorkoutTodayBanner: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
// Header row taps open session // Main row progress ring leads
HStack(spacing: 6) { HStack(spacing: 13) {
Image(systemName: isDone ? "checkmark.seal.fill" : "dumbbell.fill") // Progress ring (or idle ring when not active)
.font(.system(size: 10)) ZStack {
Text(isDone ? "WORKOUT COMPLETE" : "TODAY'S WORKOUT") Circle()
.font(AppFonts.mono(9, weight: .bold)) .stroke(AppColors.borderHi(cs), lineWidth: 3)
Spacer() .frame(width: 44, height: 44)
Text(setsText).font(AppFonts.mono(9)).foregroundColor(AppColors.text3(cs)) if isActiveProgram && vm.totalSets > 0 {
Image(systemName: "chevron.right") Circle()
.font(.system(size: 9, weight: .semibold)) .trim(from: 0, to: CGFloat(min(vm.progress, 1)))
.foregroundColor(accent.opacity(0.5)) .stroke(accent, style: StrokeStyle(lineWidth: 3, lineCap: .round))
} .frame(width: 44, height: 44)
.foregroundColor(accent) .rotationEffect(.degrees(-90))
.padding(.horizontal, 13).padding(.top, 10).padding(.bottom, 8) .animation(KisaniSpring.entrance, value: vm.progress)
.contentShape(Rectangle()) }
.onTapGesture { onTap() } if isDone {
Image(systemName: "checkmark")
.font(.system(size: 11, weight: .semibold))
.foregroundColor(accent)
} else if isActiveProgram && vm.totalSets > 0 {
Text("\(Int(vm.progress * 100))%")
.font(AppFonts.mono(9, weight: .bold))
.foregroundColor(accent)
} else {
Image(systemName: "dumbbell")
.font(.system(size: 13, weight: .regular))
.foregroundColor(AppColors.text3(cs))
}
}
Divider().background(accent.opacity(0.15)) VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 5) {
// Program identity row taps open session Text(isDone ? "DONE" : "TODAY")
HStack(spacing: 10) { .font(AppFonts.mono(8, weight: .bold))
Text(program.emoji).font(.system(size: 18)) .foregroundColor(accent)
.frame(width: 36, height: 36) Text("·")
.background(isDone ? AppColors.greenSoft : AppColors.blueSoft) .font(AppFonts.mono(8))
.clipShape(RoundedRectangle(cornerRadius: 9)) .foregroundColor(AppColors.text3(cs))
Text(setsText)
VStack(alignment: .leading, spacing: 2) { .font(AppFonts.mono(8))
.foregroundColor(AppColors.text3(cs))
}
Text(program.name) Text(program.name)
.font(AppFonts.sans(13, weight: .semibold)) .font(AppFonts.sans(14, weight: .semibold))
.foregroundColor(AppColors.text(cs)) .foregroundColor(AppColors.text(cs))
Text("\(program.sections.count) section\(program.sections.count == 1 ? "" : "s") · \(program.totalExercises) exercise\(program.totalExercises == 1 ? "" : "s")") Text("\(program.sections.count) section\(program.sections.count == 1 ? "" : "s") · \(program.totalExercises) exercise\(program.totalExercises == 1 ? "" : "s")")
.font(AppFonts.mono(9.5)) .font(AppFonts.mono(9))
.foregroundColor(AppColors.text3(cs)) .foregroundColor(AppColors.text3(cs))
} }
Spacer() Spacer()
Image(systemName: "chevron.right")
if isActiveProgram && vm.totalSets > 0 { .font(.system(size: 10, weight: .semibold))
ZStack { .foregroundColor(AppColors.text3(cs))
Circle().stroke(AppColors.borderHi(cs), lineWidth: 2.5).frame(width: 34, height: 34)
Circle().trim(from: 0, to: CGFloat(vm.progress))
.stroke(accent, style: StrokeStyle(lineWidth: 2.5, lineCap: .round))
.frame(width: 34, height: 34).rotationEffect(.degrees(-90))
.animation(KisaniSpring.entrance, value: vm.progress)
if isDone {
Image(systemName: "checkmark")
.font(.system(size: 10, weight: .bold))
.foregroundColor(AppColors.green)
} else {
Text("\(Int(vm.progress * 100))%")
.font(AppFonts.mono(8.5, weight: .bold))
.foregroundColor(accent)
}
}
}
} }
.padding(.horizontal, 13).padding(.vertical, 10) .padding(.horizontal, 13).padding(.vertical, 12)
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture { onTap() } .onTapGesture { onTap() }
@@ -408,9 +407,9 @@ struct WorkoutTodayBanner: View {
} }
} }
} }
.background(accent.opacity(0.07)) .background(AppColors.surface(cs))
.clipShape(RoundedRectangle(cornerRadius: AppRadius.large)) .clipShape(RoundedRectangle(cornerRadius: AppRadius.large))
.overlay(RoundedRectangle(cornerRadius: AppRadius.large).stroke(accent.opacity(0.2), lineWidth: 1)) .overlay(RoundedRectangle(cornerRadius: AppRadius.large).stroke(AppColors.border(cs), lineWidth: 1))
.animation(KisaniSpring.entrance, value: isDone) .animation(KisaniSpring.entrance, value: isDone)
} }
} }

View File

@@ -374,7 +374,7 @@ struct ProgressRingCard: View {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text("\(done) / \(total) sets done") Text("\(done) / \(total) sets done")
.font(AppFonts.sans(12.5, weight: .semibold)).foregroundColor(AppColors.text(cs)) .font(AppFonts.sans(12.5, weight: .semibold)).foregroundColor(AppColors.text(cs))
Text(total == 0 ? "Add exercises to get started" : done == total && total > 0 ? "Workout complete! 🎉" : "Keep going 🔥") Text(total == 0 ? "Add exercises to get started" : done == total && total > 0 ? "All sets done" : "\(total - done) sets remaining")
.font(AppFonts.sans(10)).foregroundColor(AppColors.text3(cs)) .font(AppFonts.sans(10)).foregroundColor(AppColors.text3(cs))
} }
Spacer() Spacer()
@@ -387,24 +387,31 @@ struct ProgressRingCard: View {
struct StreakBannerView: View { struct StreakBannerView: View {
@Environment(\.colorScheme) private var cs @Environment(\.colorScheme) private var cs
let streak: Int let streak: Int
var body: some View { var body: some View {
HStack(spacing: 10) { HStack(spacing: 0) {
Text("\(streak)").font(AppFonts.mono(26, weight: .bold)).foregroundColor(AppColors.accent) VStack(alignment: .leading, spacing: 2) {
VStack(alignment: .leading, spacing: 1) { Text("\(streak)")
Text("Day Streak").font(AppFonts.sans(12, weight: .semibold)).foregroundColor(AppColors.text(cs)) .font(AppFonts.mono(28, weight: .bold))
Text("Don't break the chain").font(AppFonts.sans(10)).foregroundColor(AppColors.text3(cs)) .foregroundColor(AppColors.text(cs))
Text("day streak")
.font(AppFonts.sans(11))
.foregroundColor(AppColors.text3(cs))
} }
Spacer() Spacer()
HStack(spacing: 4) { // 7-day tick bar current week progress
HStack(spacing: 3) {
ForEach(0..<7, id: \.self) { i in ForEach(0..<7, id: \.self) { i in
Circle().fill(i < streak ? AppColors.accent : AppColors.borderHi(cs)).frame(width: 7, height: 7) let filled = streak > 0 && i < (streak % 7 == 0 ? 7 : streak % 7)
RoundedRectangle(cornerRadius: 1.5)
.fill(filled ? AppColors.accent : AppColors.borderHi(cs))
.frame(width: 18, height: 3)
} }
} }
} }
.padding(11) .padding(.horizontal, 14)
.background(LinearGradient(colors: [AppColors.accent.opacity(0.12), AppColors.yellow.opacity(0.08)], startPoint: .topLeading, endPoint: .bottomTrailing)) .padding(.vertical, 13)
.clipShape(RoundedRectangle(cornerRadius: AppRadius.large)) .cardStyle()
.overlay(RoundedRectangle(cornerRadius: AppRadius.large).stroke(AppColors.accent.opacity(0.18), lineWidth: 1))
} }
} }
@@ -1116,7 +1123,7 @@ struct RenameProgramSheet: View {
@State private var name: String @State private var name: String
@State private var selectedEmoji: String @State private var selectedEmoji: String
@FocusState private var nameFocused: Bool @FocusState private var nameFocused: Bool
private let emojis = ["💪", "🦵", "🔄", "🏃", "🤸", "🏋️", "🧘", "🚴", "🥊", "🎯", "⚡️", "🔥"] private let emojis = ["💪", "🦵", "🔄", "🏃", "🤸", "🏋️", "🧘", "🚴", "🥊", "🎯", "⚡️", "🫀"]
init(program: WorkoutProgram) { init(program: WorkoutProgram) {
self.program = program self.program = program
@@ -1190,7 +1197,7 @@ struct AddProgramSheet: View {
@State private var name = "" @State private var name = ""
@State private var selectedEmoji = "💪" @State private var selectedEmoji = "💪"
@FocusState private var nameFocused: Bool @FocusState private var nameFocused: Bool
private let emojis = ["💪", "🦵", "🔄", "🏃", "🤸", "🏋️", "🧘", "🚴", "🥊", "🎯", "⚡️", "🔥"] private let emojis = ["💪", "🦵", "🔄", "🏃", "🤸", "🏋️", "🧘", "🚴", "🥊", "🎯", "⚡️", "🫀"]
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {