fix: API field mapping, HealthKit entitlement, profile tab, forgot password
This commit is contained in:
@@ -26,14 +26,16 @@ struct FinanceCategory: Codable, Identifiable {
|
|||||||
|
|
||||||
struct FinanceSummary: Codable {
|
struct FinanceSummary: Codable {
|
||||||
var totalIncome: Double?
|
var totalIncome: Double?
|
||||||
var totalExpenses: Double?
|
var totalExpense: Double?
|
||||||
var balance: Double?
|
var balance: Double?
|
||||||
|
var carriedOver: Double?
|
||||||
var month: String?
|
var month: String?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case totalIncome = "total_income"
|
|
||||||
case totalExpenses = "total_expenses"
|
|
||||||
case balance, month
|
case balance, month
|
||||||
|
case totalIncome = "total_income"
|
||||||
|
case totalExpense = "total_expense"
|
||||||
|
case carriedOver = "carried_over"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ struct Habit: Codable, Identifiable {
|
|||||||
var color: String?
|
var color: String?
|
||||||
var frequency: HabitFrequency
|
var frequency: HabitFrequency
|
||||||
var reminderTime: String?
|
var reminderTime: String?
|
||||||
var targetDays: Int?
|
var targetDays: [Int]?
|
||||||
|
var targetCount: Int?
|
||||||
var currentStreak: Int?
|
var currentStreak: Int?
|
||||||
var longestStreak: Int?
|
var longestStreak: Int?
|
||||||
var completedToday: Bool?
|
var completedToday: Bool?
|
||||||
@@ -29,6 +30,7 @@ struct Habit: Codable, Identifiable {
|
|||||||
case id, name, description, icon, color, frequency
|
case id, name, description, icon, color, frequency
|
||||||
case reminderTime = "reminder_time"
|
case reminderTime = "reminder_time"
|
||||||
case targetDays = "target_days"
|
case targetDays = "target_days"
|
||||||
|
case targetCount = "target_count"
|
||||||
case currentStreak = "current_streak"
|
case currentStreak = "current_streak"
|
||||||
case longestStreak = "longest_streak"
|
case longestStreak = "longest_streak"
|
||||||
case completedToday = "completed_today"
|
case completedToday = "completed_today"
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum TaskPriority: String, Codable, CaseIterable {
|
|
||||||
case low, medium, high, urgent
|
|
||||||
var displayName: String {
|
|
||||||
switch self {
|
|
||||||
case .low: return "Низкий"
|
|
||||||
case .medium: return "Средний"
|
|
||||||
case .high: return "Высокий"
|
|
||||||
case .urgent: return "Срочный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var color: String {
|
|
||||||
switch self {
|
|
||||||
case .low: return "8888aa"
|
|
||||||
case .medium: return "ffa502"
|
|
||||||
case .high: return "ff4757"
|
|
||||||
case .urgent: return "ff0000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PulseTask: Codable, Identifiable {
|
struct PulseTask: Codable, Identifiable {
|
||||||
let id: Int
|
let id: Int
|
||||||
var title: String
|
var title: String
|
||||||
var description: String?
|
var description: String?
|
||||||
var done: Bool
|
var completed: Bool
|
||||||
var priority: TaskPriority?
|
var priority: Int?
|
||||||
|
var icon: String?
|
||||||
|
var color: String?
|
||||||
var dueDate: String?
|
var dueDate: String?
|
||||||
var reminderTime: String?
|
var reminderTime: String?
|
||||||
var createdAt: String?
|
var createdAt: String?
|
||||||
|
|
||||||
|
var priorityColor: String {
|
||||||
|
switch priority {
|
||||||
|
case 4: return "ff0000"
|
||||||
|
case 3: return "ff4757"
|
||||||
|
case 2: return "ffa502"
|
||||||
|
default: return "8888aa"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var priorityDisplayName: String {
|
||||||
|
switch priority {
|
||||||
|
case 1: return "Низкий"
|
||||||
|
case 2: return "Средний"
|
||||||
|
case 3: return "Высокий"
|
||||||
|
case 4: return "Срочный"
|
||||||
|
default: return "Без приоритета"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case id, title, description, done, priority
|
case id, title, description, completed, priority, icon, color
|
||||||
case dueDate = "due_date"
|
case dueDate = "due_date"
|
||||||
case reminderTime = "reminder_time"
|
case reminderTime = "reminder_time"
|
||||||
case createdAt = "created_at"
|
case createdAt = "created_at"
|
||||||
@@ -41,7 +42,7 @@ struct PulseTask: Codable, Identifiable {
|
|||||||
struct CreateTaskRequest: Codable {
|
struct CreateTaskRequest: Codable {
|
||||||
var title: String
|
var title: String
|
||||||
var description: String?
|
var description: String?
|
||||||
var priority: TaskPriority?
|
var priority: Int?
|
||||||
var dueDate: String?
|
var dueDate: String?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
|
|||||||
@@ -4,5 +4,9 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.healthkit</key>
|
<key>com.apple.developer.healthkit</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.developer.healthkit.access</key>
|
||||||
|
<array>
|
||||||
|
<string>health-records</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct DashboardView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pendingTasks: [PulseTask] { tasks.filter { !$0.done } }
|
var pendingTasks: [PulseTask] { tasks.filter { !$0.completed } }
|
||||||
var completedHabitsToday: Int { habits.filter { $0.completedToday == true }.count }
|
var completedHabitsToday: Int { habits.filter { $0.completedToday == true }.count }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -67,7 +67,7 @@ struct DashboardView: View {
|
|||||||
StatCard(icon: "checkmark.circle.fill", value: "\(pendingTasks.count)", label: "Задач", color: "00d4aa")
|
StatCard(icon: "checkmark.circle.fill", value: "\(pendingTasks.count)", label: "Задач", color: "00d4aa")
|
||||||
StatCard(icon: "flame.fill", value: "\(completedHabitsToday)/\(habits.count)", label: "Привычек", color: "ffa502")
|
StatCard(icon: "flame.fill", value: "\(completedHabitsToday)/\(habits.count)", label: "Привычек", color: "ffa502")
|
||||||
if let s = summary, let balance = s.balance {
|
if let s = summary, let balance = s.balance {
|
||||||
StatCard(icon: "rublesign.circle.fill", value: "\(Int(balance))₽", label: "Баланс", color: "7c3aed")
|
StatCard(icon: "rublesign.circle.fill", value: "\(Int(balance))₽", label: "Финансы", color: "7c3aed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ struct FinanceSummaryCard: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
Text("Расходы").font(.caption).foregroundColor(Color(hex: "8888aa"))
|
Text("Расходы").font(.caption).foregroundColor(Color(hex: "8888aa"))
|
||||||
Text("-\(Int(summary.totalExpenses ?? 0))₽").font(.headline).foregroundColor(Color(hex: "ff4757"))
|
Text("-\(Int(summary.totalExpense ?? 0))₽").font(.headline).foregroundColor(Color(hex: "ff4757"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ struct LoginView: View {
|
|||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
@State private var showPassword = false
|
@State private var showPassword = false
|
||||||
@State private var isRegistering = false
|
@State private var isRegistering = false
|
||||||
|
@State private var showForgotSheet = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -83,6 +84,14 @@ struct LoginView: View {
|
|||||||
.cornerRadius(12)
|
.cornerRadius(12)
|
||||||
.disabled(isLoading || email.isEmpty || password.isEmpty)
|
.disabled(isLoading || email.isEmpty || password.isEmpty)
|
||||||
|
|
||||||
|
if !isRegistering {
|
||||||
|
Button(action: { showForgotSheet = true }) {
|
||||||
|
Text("Забыли пароль?")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(Color(hex: "00d4aa"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle login/register
|
// Toggle login/register
|
||||||
HStack {
|
HStack {
|
||||||
Text(isRegistering ? "Уже есть аккаунт?" : "Нет аккаунта?")
|
Text(isRegistering ? "Уже есть аккаунт?" : "Нет аккаунта?")
|
||||||
@@ -103,6 +112,9 @@ struct LoginView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showForgotSheet) {
|
||||||
|
ForgotPasswordView(isPresented: $showForgotSheet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func login() {
|
func login() {
|
||||||
@@ -144,3 +156,54 @@ struct LoginView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - ForgotPasswordView
|
||||||
|
|
||||||
|
struct ForgotPasswordView: View {
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
@State private var email = ""
|
||||||
|
@State private var isSent = false
|
||||||
|
@State private var isLoading = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color(hex: "0a0a1a").ignoresSafeArea()
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
Text("Сброс пароля").font(.title2.bold()).foregroundColor(.white)
|
||||||
|
if isSent {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Text("✅").font(.system(size: 50))
|
||||||
|
Text("Письмо отправлено!\nПроверьте \(email)")
|
||||||
|
.foregroundColor(.white).multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
Button("Закрыть") { isPresented = false }.foregroundColor(Color(hex: "00d4aa"))
|
||||||
|
} else {
|
||||||
|
Text("Введите email — пришлём ссылку для сброса пароля")
|
||||||
|
.foregroundColor(.white.opacity(0.6)).multilineTextAlignment(.center)
|
||||||
|
TextField("Email", text: $email)
|
||||||
|
.keyboardType(.emailAddress).autocapitalization(.none)
|
||||||
|
.padding().background(Color.white.opacity(0.1)).cornerRadius(12).foregroundColor(.white)
|
||||||
|
Button(action: send) {
|
||||||
|
if isLoading { ProgressView().tint(.black) }
|
||||||
|
else { Text("Отправить").font(.headline).foregroundColor(.black) }
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity).padding()
|
||||||
|
.background(Color(hex: "00d4aa")).cornerRadius(12).disabled(email.isEmpty || isLoading)
|
||||||
|
Button("Отмена") { isPresented = false }.foregroundColor(.white.opacity(0.5))
|
||||||
|
}
|
||||||
|
}.padding(32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func send() {
|
||||||
|
isLoading = true
|
||||||
|
Task {
|
||||||
|
var req = URLRequest(url: URL(string: "https://api.digital-home.site/auth/forgot-password")!)
|
||||||
|
req.httpMethod = "POST"
|
||||||
|
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
req.httpBody = try? JSONEncoder().encode(["email": email])
|
||||||
|
_ = try? await URLSession.shared.data(for: req)
|
||||||
|
await MainActor.run { isSent = true; isLoading = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ struct MainTabView: View {
|
|||||||
|
|
||||||
FinanceView()
|
FinanceView()
|
||||||
.tabItem { Label("Финансы", systemImage: "rublesign.circle.fill") }
|
.tabItem { Label("Финансы", systemImage: "rublesign.circle.fill") }
|
||||||
|
|
||||||
|
ProfileView()
|
||||||
|
.tabItem { Label("Профиль", systemImage: "person.fill") }
|
||||||
}
|
}
|
||||||
.accentColor(Color(hex: "00d4aa"))
|
.accentColor(Color(hex: "00d4aa"))
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
|
|||||||
148
PulseHealth/Views/Profile/ProfileView.swift
Normal file
148
PulseHealth/Views/Profile/ProfileView.swift
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ProfileView: View {
|
||||||
|
@EnvironmentObject var authManager: AuthManager
|
||||||
|
@State private var showChangePassword = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color(hex: "0a0a1a").ignoresSafeArea()
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Header
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
ZStack {
|
||||||
|
Circle().fill(Color(hex: "00d4aa").opacity(0.2)).frame(width: 80, height: 80)
|
||||||
|
Text(String(authManager.userName.prefix(1)).uppercased())
|
||||||
|
.font(.largeTitle.bold()).foregroundColor(Color(hex: "00d4aa"))
|
||||||
|
}
|
||||||
|
Text(authManager.userName).font(.title2.bold()).foregroundColor(.white)
|
||||||
|
}
|
||||||
|
.padding(.top, 40).padding(.bottom, 32)
|
||||||
|
|
||||||
|
// Settings list
|
||||||
|
VStack(spacing: 2) {
|
||||||
|
ProfileRow(icon: "lock.fill", title: "Сменить пароль", color: "7c3aed") {
|
||||||
|
showChangePassword = true
|
||||||
|
}
|
||||||
|
ProfileRow(icon: "heart.fill", title: "Health API ключ", subtitle: authManager.healthApiKey, color: "ff4757") {}
|
||||||
|
Divider().background(Color.white.opacity(0.1)).padding(.vertical, 8)
|
||||||
|
ProfileRow(icon: "rectangle.portrait.and.arrow.right", title: "Выйти", color: "ff4757", isDestructive: true) {
|
||||||
|
authManager.logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("Pulse v1.0").font(.caption).foregroundColor(Color(hex: "8888aa")).padding(.bottom, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showChangePassword) {
|
||||||
|
ChangePasswordView(isPresented: $showChangePassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfileRow: View {
|
||||||
|
let icon: String
|
||||||
|
let title: String
|
||||||
|
var subtitle: String? = nil
|
||||||
|
let color: String
|
||||||
|
var isDestructive: Bool = false
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack(spacing: 14) {
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 8).fill(Color(hex: color).opacity(0.2)).frame(width: 36, height: 36)
|
||||||
|
Image(systemName: icon).foregroundColor(Color(hex: color)).font(.subheadline)
|
||||||
|
}
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(title).foregroundColor(isDestructive ? Color(hex: "ff4757") : .white).font(.callout)
|
||||||
|
if let sub = subtitle {
|
||||||
|
Text(sub).font(.caption).foregroundColor(Color(hex: "8888aa"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
if !isDestructive {
|
||||||
|
Image(systemName: "chevron.right").foregroundColor(Color(hex: "8888aa")).font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(14)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 12).fill(Color.white.opacity(0.05)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChangePasswordView: View {
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
@EnvironmentObject var authManager: AuthManager
|
||||||
|
@State private var oldPassword = ""
|
||||||
|
@State private var newPassword = ""
|
||||||
|
@State private var confirm = ""
|
||||||
|
@State private var isLoading = false
|
||||||
|
@State private var errorMessage = ""
|
||||||
|
@State private var success = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
ZStack {
|
||||||
|
Color(hex: "0a0a1a").ignoresSafeArea()
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
if success {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Text("✅").font(.system(size: 50))
|
||||||
|
Text("Пароль изменён!").font(.title2.bold()).foregroundColor(.white)
|
||||||
|
}.padding(.top, 40)
|
||||||
|
Button("Закрыть") { isPresented = false }.foregroundColor(Color(hex: "00d4aa"))
|
||||||
|
} else {
|
||||||
|
SecureField("Текущий пароль", text: $oldPassword)
|
||||||
|
.padding().background(Color.white.opacity(0.08)).cornerRadius(12).foregroundColor(.white)
|
||||||
|
SecureField("Новый пароль", text: $newPassword)
|
||||||
|
.padding().background(Color.white.opacity(0.08)).cornerRadius(12).foregroundColor(.white)
|
||||||
|
SecureField("Подтвердите пароль", text: $confirm)
|
||||||
|
.padding().background(Color.white.opacity(0.08)).cornerRadius(12).foregroundColor(.white)
|
||||||
|
if !errorMessage.isEmpty {
|
||||||
|
Text(errorMessage).foregroundColor(.red).font(.caption)
|
||||||
|
}
|
||||||
|
Button(action: change) {
|
||||||
|
if isLoading { ProgressView().tint(.black) }
|
||||||
|
else { Text("Сменить").font(.headline).foregroundColor(.black) }
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity).padding()
|
||||||
|
.background(Color(hex: "00d4aa")).cornerRadius(12)
|
||||||
|
.disabled(oldPassword.isEmpty || newPassword.isEmpty || isLoading)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}.padding()
|
||||||
|
}
|
||||||
|
.navigationTitle("Смена пароля")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar { ToolbarItem(placement: .cancellationAction) { Button("Отмена") { isPresented = false } } }
|
||||||
|
.preferredColorScheme(.dark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func change() {
|
||||||
|
guard newPassword == confirm else { errorMessage = "Пароли не совпадают"; return }
|
||||||
|
guard newPassword.count >= 8 else { errorMessage = "Минимум 8 символов"; return }
|
||||||
|
isLoading = true
|
||||||
|
Task {
|
||||||
|
var req = URLRequest(url: URL(string: "https://api.digital-home.site/auth/password")!)
|
||||||
|
req.httpMethod = "PUT"
|
||||||
|
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
req.setValue("Bearer \(authManager.token)", forHTTPHeaderField: "Authorization")
|
||||||
|
req.httpBody = try? JSONEncoder().encode(["old_password": oldPassword, "new_password": newPassword])
|
||||||
|
let (_, response) = (try? await URLSession.shared.data(for: req)) ?? (Data(), nil)
|
||||||
|
await MainActor.run {
|
||||||
|
if (response as? HTTPURLResponse)?.statusCode == 200 {
|
||||||
|
success = true
|
||||||
|
} else {
|
||||||
|
errorMessage = "Неверный текущий пароль"
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,11 @@ struct AddTaskView: View {
|
|||||||
|
|
||||||
@State private var title = ""
|
@State private var title = ""
|
||||||
@State private var description = ""
|
@State private var description = ""
|
||||||
@State private var priority: TaskPriority = .medium
|
@State private var priority: Int = 2
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
|
|
||||||
|
private let priorities = [(1, "Низкий"), (2, "Средний"), (3, "Высокий"), (4, "Срочный")]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -20,7 +22,7 @@ struct AddTaskView: View {
|
|||||||
TextField("Описание (необязательно)", text: $description)
|
TextField("Описание (необязательно)", text: $description)
|
||||||
.padding().background(Color.white.opacity(0.08)).cornerRadius(12).foregroundColor(.white)
|
.padding().background(Color.white.opacity(0.08)).cornerRadius(12).foregroundColor(.white)
|
||||||
Picker("Приоритет", selection: $priority) {
|
Picker("Приоритет", selection: $priority) {
|
||||||
ForEach(TaskPriority.allCases, id: \.self) { p in Text(p.displayName).tag(p) }
|
ForEach(priorities, id: \.0) { p in Text(p.1).tag(p.0) }
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -5,25 +5,20 @@ struct TaskRowView: View {
|
|||||||
let onComplete: () async -> Void
|
let onComplete: () async -> Void
|
||||||
|
|
||||||
var priorityColor: Color {
|
var priorityColor: Color {
|
||||||
switch task.priority {
|
Color(hex: task.priorityColor)
|
||||||
case .urgent: return Color(hex: "ff0000")
|
|
||||||
case .high: return Color(hex: "ff4757")
|
|
||||||
case .medium: return Color(hex: "ffa502")
|
|
||||||
default: return Color(hex: "8888aa")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Button(action: { Task { await onComplete() } }) {
|
Button(action: { Task { await onComplete() } }) {
|
||||||
Image(systemName: task.done ? "checkmark.circle.fill" : "circle")
|
Image(systemName: task.completed ? "checkmark.circle.fill" : "circle")
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
.foregroundColor(task.done ? Color(hex: "00d4aa") : Color(hex: "8888aa"))
|
.foregroundColor(task.completed ? Color(hex: "00d4aa") : Color(hex: "8888aa"))
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(task.title)
|
Text(task.title)
|
||||||
.foregroundColor(task.done ? Color(hex: "8888aa") : .white)
|
.foregroundColor(task.completed ? Color(hex: "8888aa") : .white)
|
||||||
.strikethrough(task.done)
|
.strikethrough(task.completed)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
if let desc = task.description, !desc.isEmpty {
|
if let desc = task.description, !desc.isEmpty {
|
||||||
Text(desc).font(.caption).foregroundColor(Color(hex: "8888aa")).lineLimit(1)
|
Text(desc).font(.caption).foregroundColor(Color(hex: "8888aa")).lineLimit(1)
|
||||||
@@ -33,7 +28,7 @@ struct TaskRowView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
if let priority = task.priority, priority != .low {
|
if let priority = task.priority, priority > 1 {
|
||||||
Circle().fill(priorityColor).frame(width: 8, height: 8)
|
Circle().fill(priorityColor).frame(width: 8, height: 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ struct TasksView: View {
|
|||||||
|
|
||||||
var filteredTasks: [PulseTask] {
|
var filteredTasks: [PulseTask] {
|
||||||
switch filter {
|
switch filter {
|
||||||
case .pending: return tasks.filter { !$0.done }
|
case .pending: return tasks.filter { !$0.completed }
|
||||||
case .completed: return tasks.filter { $0.done }
|
case .completed: return tasks.filter { $0.completed }
|
||||||
case .all: return tasks
|
case .all: return tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user