From 36191f616f932f65d098b4575746b3fe91ee938c Mon Sep 17 00:00:00 2001 From: kutesir Date: Wed, 27 May 2026 23:41:15 +0300 Subject: [PATCH] =?UTF-8?q?refactor(design):=20remove=20gamification=20?= =?UTF-8?q?=E2=80=94=20replace=20emoji=20icons=20with=20progress=20ring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- KisaniCal/Models/ExerciseModels.swift | 4 +- KisaniCal/Views/TodayView.swift | 99 +++++++++++++-------------- KisaniCal/Views/WorkoutView.swift | 35 ++++++---- 3 files changed, 72 insertions(+), 66 deletions(-) diff --git a/KisaniCal/Models/ExerciseModels.swift b/KisaniCal/Models/ExerciseModels.swift index 6d1eadd..f600299 100644 --- a/KisaniCal/Models/ExerciseModels.swift +++ b/KisaniCal/Models/ExerciseModels.swift @@ -527,7 +527,7 @@ class WorkoutViewModel: ObservableObject { ]) // โ”€โ”€ 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, WorkoutSection(name: "Shoulders", exercises: [ Exercise(name: "Reverse Fly", sfSymbol: "dumbbell.fill", colorIndex: 2, sets: s(3)), @@ -580,7 +580,7 @@ class WorkoutViewModel: ObservableObject { ]) // โ”€โ”€ Saturday: Full Body โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - let sat = WorkoutProgram(name: "Full Body", emoji: "๐Ÿ’ฅ", colorIndex: 1, sections: [ + let sat = WorkoutProgram(name: "Full Body", emoji: "๐Ÿ‹๏ธ", colorIndex: 1, sections: [ warmUp, WorkoutSection(name: "Upper Body", exercises: [ Exercise(name: "Incline Bench Press",sfSymbol: "figure.strengthtraining.traditional", colorIndex: 0, sets: s(3)), diff --git a/KisaniCal/Views/TodayView.swift b/KisaniCal/Views/TodayView.swift index d2e3e4f..de8c965 100644 --- a/KisaniCal/Views/TodayView.swift +++ b/KisaniCal/Views/TodayView.swift @@ -307,62 +307,61 @@ struct WorkoutTodayBanner: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - // โ”€โ”€ Header row โ€” taps open session โ”€โ”€ - HStack(spacing: 6) { - Image(systemName: isDone ? "checkmark.seal.fill" : "dumbbell.fill") - .font(.system(size: 10)) - Text(isDone ? "WORKOUT COMPLETE" : "TODAY'S WORKOUT") - .font(AppFonts.mono(9, weight: .bold)) - Spacer() - Text(setsText).font(AppFonts.mono(9)).foregroundColor(AppColors.text3(cs)) - Image(systemName: "chevron.right") - .font(.system(size: 9, weight: .semibold)) - .foregroundColor(accent.opacity(0.5)) - } - .foregroundColor(accent) - .padding(.horizontal, 13).padding(.top, 10).padding(.bottom, 8) - .contentShape(Rectangle()) - .onTapGesture { onTap() } + // โ”€โ”€ Main row โ€” progress ring leads โ”€โ”€ + HStack(spacing: 13) { + // Progress ring (or idle ring when not active) + ZStack { + Circle() + .stroke(AppColors.borderHi(cs), lineWidth: 3) + .frame(width: 44, height: 44) + if isActiveProgram && vm.totalSets > 0 { + Circle() + .trim(from: 0, to: CGFloat(min(vm.progress, 1))) + .stroke(accent, style: StrokeStyle(lineWidth: 3, lineCap: .round)) + .frame(width: 44, height: 44) + .rotationEffect(.degrees(-90)) + .animation(KisaniSpring.entrance, value: vm.progress) + } + 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)) - - // โ”€โ”€ Program identity row โ€” taps open session โ”€โ”€ - HStack(spacing: 10) { - Text(program.emoji).font(.system(size: 18)) - .frame(width: 36, height: 36) - .background(isDone ? AppColors.greenSoft : AppColors.blueSoft) - .clipShape(RoundedRectangle(cornerRadius: 9)) - - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: 3) { + HStack(spacing: 5) { + Text(isDone ? "DONE" : "TODAY") + .font(AppFonts.mono(8, weight: .bold)) + .foregroundColor(accent) + Text("ยท") + .font(AppFonts.mono(8)) + .foregroundColor(AppColors.text3(cs)) + Text(setsText) + .font(AppFonts.mono(8)) + .foregroundColor(AppColors.text3(cs)) + } Text(program.name) - .font(AppFonts.sans(13, weight: .semibold)) + .font(AppFonts.sans(14, weight: .semibold)) .foregroundColor(AppColors.text(cs)) 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)) } Spacer() - - if isActiveProgram && vm.totalSets > 0 { - ZStack { - 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) - } - } - } + Image(systemName: "chevron.right") + .font(.system(size: 10, weight: .semibold)) + .foregroundColor(AppColors.text3(cs)) } - .padding(.horizontal, 13).padding(.vertical, 10) + .padding(.horizontal, 13).padding(.vertical, 12) .contentShape(Rectangle()) .onTapGesture { onTap() } @@ -408,9 +407,9 @@ struct WorkoutTodayBanner: View { } } } - .background(accent.opacity(0.07)) + .background(AppColors.surface(cs)) .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) } } diff --git a/KisaniCal/Views/WorkoutView.swift b/KisaniCal/Views/WorkoutView.swift index 8dbc9b3..aeb9033 100644 --- a/KisaniCal/Views/WorkoutView.swift +++ b/KisaniCal/Views/WorkoutView.swift @@ -374,7 +374,7 @@ struct ProgressRingCard: View { VStack(alignment: .leading, spacing: 2) { Text("\(done) / \(total) sets done") .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)) } Spacer() @@ -387,24 +387,31 @@ struct ProgressRingCard: View { struct StreakBannerView: View { @Environment(\.colorScheme) private var cs let streak: Int + var body: some View { - HStack(spacing: 10) { - Text("\(streak)").font(AppFonts.mono(26, weight: .bold)).foregroundColor(AppColors.accent) - VStack(alignment: .leading, spacing: 1) { - Text("Day Streak").font(AppFonts.sans(12, weight: .semibold)).foregroundColor(AppColors.text(cs)) - Text("Don't break the chain").font(AppFonts.sans(10)).foregroundColor(AppColors.text3(cs)) + HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 2) { + Text("\(streak)") + .font(AppFonts.mono(28, weight: .bold)) + .foregroundColor(AppColors.text(cs)) + Text("day streak") + .font(AppFonts.sans(11)) + .foregroundColor(AppColors.text3(cs)) } Spacer() - HStack(spacing: 4) { + // 7-day tick bar โ€” current week progress + HStack(spacing: 3) { 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) - .background(LinearGradient(colors: [AppColors.accent.opacity(0.12), AppColors.yellow.opacity(0.08)], startPoint: .topLeading, endPoint: .bottomTrailing)) - .clipShape(RoundedRectangle(cornerRadius: AppRadius.large)) - .overlay(RoundedRectangle(cornerRadius: AppRadius.large).stroke(AppColors.accent.opacity(0.18), lineWidth: 1)) + .padding(.horizontal, 14) + .padding(.vertical, 13) + .cardStyle() } } @@ -1116,7 +1123,7 @@ struct RenameProgramSheet: View { @State private var name: String @State private var selectedEmoji: String @FocusState private var nameFocused: Bool - private let emojis = ["๐Ÿ’ช", "๐Ÿฆต", "๐Ÿ”„", "๐Ÿƒ", "๐Ÿคธ", "๐Ÿ‹๏ธ", "๐Ÿง˜", "๐Ÿšด", "๐ŸฅŠ", "๐ŸŽฏ", "โšก๏ธ", "๐Ÿ”ฅ"] + private let emojis = ["๐Ÿ’ช", "๐Ÿฆต", "๐Ÿ”„", "๐Ÿƒ", "๐Ÿคธ", "๐Ÿ‹๏ธ", "๐Ÿง˜", "๐Ÿšด", "๐ŸฅŠ", "๐ŸŽฏ", "โšก๏ธ", "๐Ÿซ€"] init(program: WorkoutProgram) { self.program = program @@ -1190,7 +1197,7 @@ struct AddProgramSheet: View { @State private var name = "" @State private var selectedEmoji = "๐Ÿ’ช" @FocusState private var nameFocused: Bool - private let emojis = ["๐Ÿ’ช", "๐Ÿฆต", "๐Ÿ”„", "๐Ÿƒ", "๐Ÿคธ", "๐Ÿ‹๏ธ", "๐Ÿง˜", "๐Ÿšด", "๐ŸฅŠ", "๐ŸŽฏ", "โšก๏ธ", "๐Ÿ”ฅ"] + private let emojis = ["๐Ÿ’ช", "๐Ÿฆต", "๐Ÿ”„", "๐Ÿƒ", "๐Ÿคธ", "๐Ÿ‹๏ธ", "๐Ÿง˜", "๐Ÿšด", "๐ŸฅŠ", "๐ŸŽฏ", "โšก๏ธ", "๐Ÿซ€"] var body: some View { VStack(spacing: 0) {