Files
pulse-mobile/PulseHealth/Views/Habits/EditHabitView.swift

230 lines
13 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
struct EditHabitView: View {
@Binding var isPresented: Bool
@EnvironmentObject var authManager: AuthManager
let habit: Habit
let onSaved: () async -> Void
@State private var name: String
@State private var selectedIcon: String
@State private var selectedColor: String
@State private var frequency: HabitFrequency
@State private var selectedWeekdays: Set<Int>
@State private var intervalDays: String
@State private var isLoading = false
@State private var showArchiveConfirm = false
let weekdayNames = ["Вс","Пн","Вт","Ср","Чт","Пт","Сб"]
let icons = ["🔥", "💪", "🏃", "📚", "💧", "🧘", "🎯", "⭐️", "🌟", "",
"🏋️", "🚴", "🍎", "😴", "🧠", "🎨", "🎵", "💊", "🌿", "💰",
"✍️", "🧹", "🏊", "🚶", "🎮", "📝", "🌅", "🥗", "🧃", "🫁"]
let colors = ["#0D9488", "#7c3aed", "#ff4757", "#ffa502", "#6366f1",
"#ec4899", "#14b8a6", "#f59e0b", "#10b981", "#3b82f6"]
let frequencies: [(HabitFrequency, String, String)] = [
(.daily, "Каждый день", "calendar"),
(.weekly, "По дням недели", "calendar.badge.clock"),
(.interval, "Каждые N дней", "repeat"),
(.monthly, "Каждый месяц", "calendar.badge.plus")
]
init(isPresented: Binding<Bool>, habit: Habit, onSaved: @escaping () async -> Void) {
self._isPresented = isPresented
self.habit = habit
self.onSaved = onSaved
self._name = State(initialValue: habit.name)
self._selectedIcon = State(initialValue: habit.icon ?? "🔥")
self._selectedColor = State(initialValue: habit.color ?? "#0D9488")
self._frequency = State(initialValue: habit.frequency)
self._selectedWeekdays = State(initialValue: Set(habit.targetDays ?? [1,2,3,4,5]))
self._intervalDays = State(initialValue: String(habit.targetCount ?? 2))
}
var body: some View {
ZStack {
Color(hex: "0a0a1a").ignoresSafeArea()
VStack(spacing: 0) {
RoundedRectangle(cornerRadius: 3)
.fill(Color.white.opacity(0.2)).frame(width: 40, height: 4).padding(.top, 12)
HStack {
Button("Отмена") { isPresented = false }.foregroundColor(Color(hex: "8888aa"))
Spacer()
Text("Редактировать привычку").font(.headline).foregroundColor(.white)
Spacer()
Button(action: save) {
if isLoading { ProgressView().tint(Color(hex: "0D9488")).scaleEffect(0.8) }
else { Text("Сохранить").foregroundColor(name.isEmpty ? Color(hex: "8888aa") : Color(hex: "0D9488")).fontWeight(.semibold) }
}.disabled(name.isEmpty || isLoading)
}
.padding(.horizontal, 20).padding(.vertical, 16)
Divider().background(Color.white.opacity(0.1))
ScrollView {
VStack(spacing: 20) {
// Preview
HStack(spacing: 16) {
ZStack {
Circle()
.fill(Color(hex: String(selectedColor.dropFirst())).opacity(0.25))
.frame(width: 56, height: 56)
Text(selectedIcon).font(.title2)
}
VStack(alignment: .leading, spacing: 4) {
Text(name.isEmpty ? "Название привычки" : name)
.font(.callout.bold())
.foregroundColor(name.isEmpty ? Color(hex: "8888aa") : .white)
Text(frequencies.first { $0.0 == frequency }?.1 ?? "")
.font(.caption).foregroundColor(Color(hex: "8888aa"))
}
Spacer()
}
.padding(16)
.background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05)))
// Name
VStack(alignment: .leading, spacing: 8) {
Label("Название", systemImage: "pencil").font(.caption).foregroundColor(Color(hex: "8888aa"))
TextField("Например: Читать 30 минут", text: $name)
.foregroundColor(.white).padding(14)
.background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.07)))
}
// Frequency
VStack(alignment: .leading, spacing: 8) {
Label("Периодичность", systemImage: "calendar").font(.caption).foregroundColor(Color(hex: "8888aa"))
VStack(spacing: 6) {
ForEach(frequencies, id: \.0) { f in
Button(action: { frequency = f.0 }) {
HStack {
Image(systemName: f.2).foregroundColor(frequency == f.0 ? Color(hex: "0D9488") : Color(hex: "8888aa"))
Text(f.1).foregroundColor(frequency == f.0 ? .white : Color(hex: "8888aa"))
Spacer()
if frequency == f.0 { Image(systemName: "checkmark").foregroundColor(Color(hex: "0D9488")) }
}
.padding(14)
.background(RoundedRectangle(cornerRadius: 12).fill(frequency == f.0 ? Color(hex: "0D9488").opacity(0.15) : Color.white.opacity(0.05)))
}
}
}
if frequency == .weekly {
HStack(spacing: 8) {
ForEach(0..<7) { i in
Button(action: {
if selectedWeekdays.contains(i) { if selectedWeekdays.count > 1 { selectedWeekdays.remove(i) } }
else { selectedWeekdays.insert(i) }
}) {
Text(weekdayNames[i])
.font(.caption.bold())
.foregroundColor(selectedWeekdays.contains(i) ? .black : .white)
.frame(width: 32, height: 32)
.background(Circle().fill(selectedWeekdays.contains(i) ? Color(hex: "0D9488") : Color.white.opacity(0.08)))
}
}
}
}
if frequency == .interval {
HStack {
Text("Каждые").foregroundColor(Color(hex: "8888aa")).font(.callout)
TextField("2", text: $intervalDays).keyboardType(.numberPad)
.foregroundColor(.white)
.frame(width: 50)
.padding(10)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.white.opacity(0.07)))
Text("дней").foregroundColor(Color(hex: "8888aa")).font(.callout)
}
}
}
// Icon picker
VStack(alignment: .leading, spacing: 8) {
Label("Иконка", systemImage: "face.smiling").font(.caption).foregroundColor(Color(hex: "8888aa"))
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: 8) {
ForEach(icons, id: \.self) { icon in
Button(action: { selectedIcon = icon }) {
Text(icon).font(.title3)
.frame(width: 40, height: 40)
.background(Circle().fill(selectedIcon == icon ? Color(hex: "0D9488").opacity(0.25) : Color.white.opacity(0.05)))
.overlay(Circle().stroke(selectedIcon == icon ? Color(hex: "0D9488") : Color.clear, lineWidth: 2))
}
}
}
}
// Color picker
VStack(alignment: .leading, spacing: 8) {
Label("Цвет", systemImage: "paintpalette").font(.caption).foregroundColor(Color(hex: "8888aa"))
HStack(spacing: 10) {
ForEach(colors, id: \.self) { color in
Button(action: { selectedColor = color }) {
Circle()
.fill(Color(hex: String(color.dropFirst())))
.frame(width: 32, height: 32)
.overlay(Circle().stroke(.white, lineWidth: selectedColor == color ? 2 : 0))
.scaleEffect(selectedColor == color ? 1.15 : 1.0)
.animation(.easeInOut(duration: 0.15), value: selectedColor)
}
}
}
}
// Archive / Restore button
Button(action: { showArchiveConfirm = true }) {
HStack {
Image(systemName: habit.isArchived == true ? "arrow.uturn.backward" : "archivebox")
Text(habit.isArchived == true ? "Восстановить" : "Архивировать")
}
.foregroundColor(habit.isArchived == true ? Color(hex: "0D9488") : Color(hex: "ff4757"))
.frame(maxWidth: .infinity)
.padding(14)
.background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.05)))
}
}.padding(20)
}
}
}
.confirmationDialog(
habit.isArchived == true ? "Восстановить привычку?" : "Архивировать привычку?",
isPresented: $showArchiveConfirm,
titleVisibility: .visible
) {
Button(habit.isArchived == true ? "Восстановить" : "Архивировать",
role: habit.isArchived == true ? .none : .destructive) {
Task { await toggleArchive() }
}
Button("Отмена", role: .cancel) {}
}
}
func save() {
isLoading = true
Task {
var body: [String: Any] = [
"name": name,
"frequency": frequency.rawValue,
"icon": selectedIcon,
"color": selectedColor,
"target_count": 1
]
if frequency == .weekly {
body["target_days"] = Array(selectedWeekdays).sorted()
}
if frequency == .interval {
body["target_count"] = Int(intervalDays) ?? 2
}
if let reqBody = try? JSONSerialization.data(withJSONObject: body) {
try? await APIService.shared.updateHabit(token: authManager.token, id: habit.id, body: reqBody)
}
await onSaved()
await MainActor.run { isPresented = false }
}
}
func toggleArchive() async {
let params: [String: Any] = ["is_archived": !(habit.isArchived == true)]
if let body = try? JSONSerialization.data(withJSONObject: params) {
try? await APIService.shared.updateHabit(token: authManager.token, id: habit.id, body: body)
}
await onSaved()
await MainActor.run { isPresented = false }
}
}