     1→//
     2→//  NotificationService.swift
     3→//  ProductivityHub-iOS
     4→//
     5→//  Service for managing local notifications
     6→//
     7→
     8→import Foundation
     9→@preconcurrency import UserNotifications
    10→
    11→@MainActor
    12→class NotificationService: NSObject, ObservableObject {
    13→    static let shared = NotificationService()
    14→
    15→    private let logger = Logger.notification
    16→
    17→    @Published var isAuthorized = false
    18→
    19→    private override init() {
    20→        super.init()
    21→        checkAuthorizationStatus()
    22→    }
    23→
    24→    // MARK: - Authorization
    25→
    26→    func requestAuthorization() async -> Bool {
    27→        do {
    28→            let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge])
    29→            isAuthorized = granted
    30→            logger.info("Notification authorization \(granted ? "granted" : "denied")", category: .general)
    31→            return granted
    32→        } catch {
    33→            logger.error("Failed to request notification authorization", category: .error, error: error)
    34→            isAuthorized = false
    35→            return false
    36→        }
    37→    }
    38→
    39→    func checkAuthorizationStatus() {
    40→        UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
    41→            DispatchQueue.main.async {
    42→                self?.isAuthorized = settings.authorizationStatus == .authorized
    43→            }
    44→        }
    45→    }
    46→
    47→    // MARK: - Category Registration
    48→
    49→    func registerNotificationCategories() {
    50→        let timerCategory = UNNotificationCategory(
    51→            identifier: "TIMER_COMPLETE",
    52→            actions: [],
    53→            intentIdentifiers: []
    54→        )
    55→
    56→        let taskDueCategory = UNNotificationCategory(
    57→            identifier: "TASK_DUE_SOON",
    58→            actions: [],
    59→            intentIdentifiers: []
    60→        )
    61→
    62→        let overdueCategory = UNNotificationCategory(
    63→            identifier: "OVERDUE_TASK",
    64→            actions: [],
    65→            intentIdentifiers: []
    66→        )
    67→
    68→        let journalCategory = UNNotificationCategory(
    69→            identifier: "JOURNAL_REMINDER",
    70→            actions: [],
    71→            intentIdentifiers: []
    72→        )
    73→
    74→        let dailySummaryCategory = UNNotificationCategory(
    75→            identifier: "DAILY_SUMMARY",
    76→            actions: [],
    77→            intentIdentifiers: []
    78→        )
    79→
    80→        let weeklyCategory = UNNotificationCategory(
    81→            identifier: "WEEKLY_PROGRESS",
    82→            actions: [],
    83→            intentIdentifiers: []
    84→        )
    85→
    86→        UNUserNotificationCenter.current().setNotificationCategories([
    87→            timerCategory, taskDueCategory, overdueCategory,
    88→            journalCategory, dailySummaryCategory, weeklyCategory
    89→        ])
    90→
    91→        logger.info("Notification categories registered", category: .general)
    92→    }
    93→
    94→    // MARK: - Schedule Notification
    95→
    96→    func scheduleTimerCompletion(sessionType: String, duration: TimeInterval) {
    97→        guard isAuthorized else {
    98→            logger.warning("Cannot schedule notification - not authorized", category: .general)
    99→            return
   100→        }
   101→
   102→        // Create notification content
   103→        let content = UNMutableNotificationContent()
   104→        content.title = "Focus Session Complete!"
   105→        content.body = "\(sessionType) session finished. Great work!"
   106→        content.sound = .default
   107→        content.badge = 1
   108→        content.categoryIdentifier = "TIMER_COMPLETE"
   109→
   110→        // Create trigger (fire after duration)
   111→        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration, repeats: false)
   112→
   113→        // Create request
   114→        let request = UNNotificationRequest(
   115→            identifier: "timer-completion",
   116→            content: content,
   117→            trigger: trigger
   118→        )
   119→
   120→        // Schedule notification
   121→        UNUserNotificationCenter.current().add(request) { [weak self] error in
   122→            _Concurrency.Task { @MainActor in
   123→                if let error = error {
   124→                    self?.logger.error("Failed to schedule notification", category: .error, error: error)
   125→                } else {
   126→                    self?.logger.info("Notification scheduled", category: .general, context: [
   127→                        "sessionType": sessionType,
   128→                        "duration": "\(Int(duration / 60))min"
   129→                    ])
   130→                }
   131→            }
   132→        }
   133→    }
   134→
   135→    // MARK: - Cancel Notifications
   136→
   137→    func cancelTimerNotification() {
   138→        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["timer-completion"])
   139→        logger.info("Timer notification canceled", category: .general)
   140→    }
   141→
   142→    func cancelAllNotifications() {
   143→        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
   144→    }
   145→
   146→    // MARK: - Task Due Date Reminders
   147→
   148→    /// Schedule a reminder for a task before its due date
   149→    func scheduleTaskDueDateReminder(taskId: UUID, taskTitle: String, dueDate: Date, reminderOffset: TimeInterval = -3600) {
   150→        guard isAuthorized else {
   151→            logger.warning("Cannot schedule due date reminder - not authorized", category: .general)
   152→            return
   153→        }
   154→
   155→        // Don't schedule if due date is in the past
   156→        guard dueDate > Date() else { return }
   157→
   158→        // Calculate trigger time (default: 1 hour before due)
   159→        let triggerDate = dueDate.addingTimeInterval(reminderOffset)
   160→
   161→        // Don't schedule if trigger time is in the past
   162→        guard triggerDate > Date() else { return }
   163→
   164→        let content = UNMutableNotificationContent()
   165→        content.title = "Task Due Soon"
   166→        content.body = "\"\(taskTitle)\" is due in about 1 hour."
   167→        content.sound = .default
   168→        content.categoryIdentifier = "TASK_DUE_SOON"
   169→        content.userInfo = ["taskId": taskId.uuidString]
   170→
   171→        let triggerComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: triggerDate)
   172→        let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: false)
   173→
   174→        let request = UNNotificationRequest(
   175→            identifier: "task-due-\(taskId.uuidString)",
   176→            content: content,
   177→            trigger: trigger
   178→        )
   179→
   180→        UNUserNotificationCenter.current().add(request) { [weak self] error in
   181→            _Concurrency.Task { @MainActor in
   182→                if let error = error {
   183→                    self?.logger.error("Failed to schedule due date reminder", category: .error, error: error)
   184→                } else {
   185→                    self?.logger.info("Due date reminder scheduled", category: .general, context: [
   186→                        "taskTitle": taskTitle,
   187→                        "dueDate": dueDate.formatted()
   188→                    ])
   189→                }
   190→            }
   191→        }
   192→    }
   193→
   194→    /// Cancel a specific task's due date reminder
   195→    func cancelTaskDueDateReminder(taskId: UUID) {
   196→        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["task-due-\(taskId.uuidString)"])
   197→    }
   198→
   199→    /// Schedule reminders for all tasks with due dates
   200→    func scheduleAllTaskDueDateReminders(tasks: [Task]) {
   201→        // First cancel all existing task due reminders
   202→        let center = UNUserNotificationCenter.current()
   203→        center.getPendingNotificationRequests { [weak self] requests in
   204→            let taskDueIds = requests.filter { $0.identifier.hasPrefix("task-due-") }.map { $0.identifier }
   205→            center.removePendingNotificationRequests(withIdentifiers: taskDueIds)
   206→
   207→            // Schedule new reminders for incomplete tasks with future due dates
   208→            let tasksWithDueDates = tasks.filter { task in
   209→                guard let dueDate = task.dueDate else { return false }
   210→                return !task.isCompleted && dueDate > Date()
   211→            }
   212→
   213→            _Concurrency.Task { @MainActor in
   214→                for task in tasksWithDueDates {
   215→                    if let dueDate = task.dueDate {
   216→                        self?.scheduleTaskDueDateReminder(taskId: task.id, taskTitle: task.title, dueDate: dueDate)
   217→                    }
   218→                }
   219→
   220→                self?.logger.info("Scheduled \(tasksWithDueDates.count) task due date reminders", category: .general)
   221→            }
   222→        }
   223→    }
   224→
   225→    // MARK: - Overdue Task Reminders
   226→
   227→    func scheduleOverdueTaskReminder(taskTitle: String, dueDate: Date) {
   228→        guard isAuthorized else {
   229→            logger.warning("Cannot schedule overdue reminder - not authorized", category: .general)
   230→            return
   231→        }
   232→
   233→        // Only schedule if task is actually overdue
   234→        guard dueDate < Date() else { return }
   235→
   236→        let content = UNMutableNotificationContent()
   237→        content.title = "Overdue Task"
   238→        content.body = "\"\(taskTitle)\" is past due. Time to complete it!"
   239→        content.sound = .default
   240→        content.categoryIdentifier = "OVERDUE_TASK"
   241→
   242→        // Trigger immediately for already overdue tasks
   243→        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
   244→
   245→        let request = UNNotificationRequest(
   246→            identifier: "overdue-\(UUID().uuidString)",
   247→            content: content,
   248→            trigger: trigger
   249→        )
   250→
   251→        UNUserNotificationCenter.current().add(request) { [weak self] error in
   252→            _Concurrency.Task { @MainActor in
   253→                if let error = error {
   254→                    self?.logger.error("Failed to schedule overdue reminder", category: .error, error: error)
   255→                }
   256→            }
   257→        }
   258→    }
   259→
   260→    func scheduleDailyJournalReminder(hour: Int = 20, minute: Int = 0) {
   261→        guard isAuthorized else {
   262→            logger.warning("Cannot schedule journal reminder - not authorized", category: .general)
   263→            return
   264→        }
   265→
   266→        let content = UNMutableNotificationContent()
   267→        content.title = "Daily Journal"
   268→        content.body = "Take a moment to reflect on your day. How did it go?"
   269→        content.sound = .default
   270→        content.categoryIdentifier = "JOURNAL_REMINDER"
   271→
   272→        // Schedule for specific time daily
   273→        var dateComponents = DateComponents()
   274→        dateComponents.hour = hour
   275→        dateComponents.minute = minute
   276→
   277→        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
   278→
   279→        let request = UNNotificationRequest(
   280→            identifier: "daily-journal-reminder",
   281→            content: content,
   282→            trigger: trigger
   283→        )
   284→
   285→        UNUserNotificationCenter.current().add(request) { [weak self] error in
   286→            _Concurrency.Task { @MainActor in
   287→                if let error = error {
   288→                    self?.logger.error("Failed to schedule journal reminder", category: .error, error: error)
   289→                } else {
   290→                    self?.logger.info("Daily journal reminder scheduled", category: .general, context: [
   291→                        "hour": "\(hour)",
   292→                        "minute": "\(minute)"
   293→                    ])
   294→                }
   295→            }
   296→        }
   297→    }
   298→
   299→    func cancelDailyJournalReminder() {
   300→        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["daily-journal-reminder"])
   301→        logger.info("Daily journal reminder canceled", category: .general)
   302→    }
   303→
   304→    func scheduleDailySummary(hour: Int = 9, minute: Int = 0, pendingTaskCount: Int, overdueCount: Int) {
   305→        guard isAuthorized, pendingTaskCount > 0 else { return }
   306→
   307→        let content = UNMutableNotificationContent()
   308→        content.title = "Daily Summary"
   309→
   310→        if overdueCount > 0 {
   311→            content.body = "You have \(pendingTaskCount) pending tasks (\(overdueCount) overdue). Let's tackle them!"
   312→        } else {
   313→            content.body = "You have \(pendingTaskCount) tasks to complete today. Let's get started!"
   314→        }
   315→
   316→        content.sound = .default
   317→        content.categoryIdentifier = "DAILY_SUMMARY"
   318→
   319→        var dateComponents = DateComponents()
   320→        dateComponents.hour = hour
   321→        dateComponents.minute = minute
   322→
   323→        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
   324→
   325→        let request = UNNotificationRequest(
   326→            identifier: "daily-summary",
   327→            content: content,
   328→            trigger: trigger
   329→        )
   330→
   331→        UNUserNotificationCenter.current().add(request) { [weak self] error in
   332→            _Concurrency.Task { @MainActor in
   333→                if let error = error {
   334→                    self?.logger.error("Failed to schedule daily summary", category: .error, error: error)
   335→                }
   336→            }
   337→        }
   338→    }
   339→
   340→    // MARK: - Weekly Progress Notification
   341→
   342→    /// Schedule a weekly progress summary notification (default: Sunday at 7 PM)
   343→    func scheduleWeeklyProgress(
   344→        tasksCompleted: Int,
   345→        focusMinutes: Int,
   346→        journalEntries: Int,
   347→        dayOfWeek: Int = 1,  // 1 = Sunday
   348→        hour: Int = 19,
   349→        minute: Int = 0
   350→    ) {
   351→        let content = UNMutableNotificationContent()
   352→        content.title = "Weekly Progress Summary"
   353→
   354→        var summaryParts: [String] = []
   355→        if tasksCompleted > 0 {
   356→            summaryParts.append("\(tasksCompleted) tasks completed")
   357→        }
   358→        if focusMinutes > 0 {
   359→            let hours = focusMinutes / 60
   360→            let mins = focusMinutes % 60
   361→            if hours > 0 {
   362→                summaryParts.append("\(hours)h \(mins)m focused")
   363→            } else {
   364→                summaryParts.append("\(mins)m focused")
   365→            }
   366→        }
   367→        if journalEntries > 0 {
   368→            summaryParts.append("\(journalEntries) journal entries")
   369→        }
   370→
   371→        if summaryParts.isEmpty {
   372→            content.body = "Start fresh this week! Create tasks, focus on work, and journal your thoughts."
   373→        } else {
   374→            content.body = "This week: " + summaryParts.joined(separator: ", ") + ". Keep up the great work!"
   375→        }
   376→
   377→        content.sound = .default
   378→        content.categoryIdentifier = "WEEKLY_PROGRESS"
   379→
   380→        var dateComponents = DateComponents()
   381→        dateComponents.weekday = dayOfWeek
   382→        dateComponents.hour = hour
   383→        dateComponents.minute = minute
   384→
   385→        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
   386→
   387→        let request = UNNotificationRequest(
   388→            identifier: "weekly-progress",
   389→            content: content,
   390→            trigger: trigger
   391→        )
   392→
   393→        UNUserNotificationCenter.current().add(request) { [weak self] error in
   394→            _Concurrency.Task { @MainActor in
   395→                if let error = error {
   396→                    self?.logger.error("Failed to schedule weekly progress", category: .error, error: error)
   397→                } else {
   398→                    self?.logger.info("Weekly progress notification scheduled", category: .general)
   399→                }
   400→            }
   401→        }
   402→    }
   403→
   404→    func cancelWeeklyProgress() {
   405→        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["weekly-progress"])
   406→    }
   407→
   408→    // MARK: - Clear Badge
   409→
   410→    func clearBadge() {
   411→        UNUserNotificationCenter.current().setBadgeCount(0)
   412→    }
   413→}
   414→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  NotificationService.swift
//  ProductivityHub-iOS
//
//  Service for managing local notifications
//

import Foundation
@preconcurrency import UserNotifications

@MainActor
class NotificationService: NSObject, ObservableObject {
    static let shared = NotificationService()

    private let logger = Logger.notification

    @Published var isAuthorized = false

    private override init() {
        super.init()
        checkAuthorizationStatus()
    }

    // MARK: - Authorization

    func requestAuthorization() async -> Bool {
        do {
            let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge])
            isAuthorized = granted
            logger.info("Notification authorization \(granted ? "granted" : "denied")", category: .general)
            return granted
        } catch {
            logger.error("Failed to request notification authorization", category: .error, error: error)
            isAuthorized = false
            return false
        }
    }

    func checkAuthorizationStatus() {
        UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
            DispatchQueue.main.async {
                self?.isAuthorized = settings.authorizationStatus == .authorized
            }
        }
    }

    // MARK: - Category Registration

    func registerNotificationCategories() {
        let timerCategory = UNNotificationCategory(
            identifier: "TIMER_COMPLETE",
            actions: [],
            intentIdentifiers: []
        )

        let taskDueCategory = UNNotificationCategory(
            identifier: "TASK_DUE_SOON",
            actions: [],
            intentIdentifiers: []
        )

        let overdueCategory = UNNotificationCategory(
            identifier: "OVERDUE_TASK",
            actions: [],
            intentIdentifiers: []
        )

        let journalCategory = UNNotificationCategory(
            identifier: "JOURNAL_REMINDER",
            actions: [],
            intentIdentifiers: []
        )

        let dailySummaryCategory = UNNotificationCategory(
            identifier: "DAILY_SUMMARY",
            actions: [],
            intentIdentifiers: []
        )

        let weeklyCategory = UNNotificationCategory(
            identifier: "WEEKLY_PROGRESS",
            actions: [],
            intentIdentifiers: []
        )

        UNUserNotificationCenter.current().setNotificationCategories([
            timerCategory, taskDueCategory, overdueCategory,
            journalCategory, dailySummaryCategory, weeklyCategory
        ])

        logger.info("Notification categories registered", category: .general)
    }

    // MARK: - Schedule Notification

    func scheduleTimerCompletion(sessionType: String, duration: TimeInterval) {
        guard isAuthorized else {
            logger.warning("Cannot schedule notification - not authorized", category: .general)
            return
        }

        // Create notification content
        let content = UNMutableNotificationContent()
        content.title = "Focus Session Complete!"
        content.body = "\(sessionType) session finished. Great work!"
        content.sound = .default
        content.badge = 1
        content.categoryIdentifier = "TIMER_COMPLETE"

        // Create trigger (fire after duration)
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration, repeats: false)

        // Create request
        let request = UNNotificationRequest(
            identifier: "timer-completion",
            content: content,
            trigger: trigger
        )

        // Schedule notification
        UNUserNotificationCenter.current().add(request) { [weak self] error in
            _Concurrency.Task { @MainActor in
                if let error = error {
                    self?.logger.error("Failed to schedule notification", category: .error, error: error)
                } else {
                    self?.logger.info("Notification scheduled", category: .general, context: [
                        "sessionType": sessionType,
                        "duration": "\(Int(duration / 60))min"
                    ])
                }
            }
        }
    }

    // MARK: - Cancel Notifications

    func cancelTimerNotification() {
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["timer-completion"])
        logger.info("Timer notification canceled", category: .general)
    }

    func cancelAllNotifications() {
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
    }

    // MARK: - Task Due Date Reminders

    /// Schedule a reminder for a task before its due date
    func scheduleTaskDueDateReminder(taskId: UUID, taskTitle: String, dueDate: Date, reminderOffset: TimeInterval = -3600) {
        guard isAuthorized else {
            logger.warning("Cannot schedule due date reminder - not authorized", category: .general)
            return
        }

        // Don't schedule if due date is in the past
        guard dueDate > Date() else { return }

        // Calculate trigger time (default: 1 hour before due)
        let triggerDate = dueDate.addingTimeInterval(reminderOffset)

        // Don't schedule if trigger time is in the past
        guard triggerDate > Date() else { return }

        let content = UNMutableNotificationContent()
        content.title = "Task Due Soon"
        content.body = "\"\(taskTitle)\" is due in about 1 hour."
        content.sound = .default
        content.categoryIdentifier = "TASK_DUE_SOON"
        content.userInfo = ["taskId": taskId.uuidString]

        let triggerComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: triggerDate)
        let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: false)

        let request = UNNotificationRequest(
            identifier: "task-due-\(taskId.uuidString)",
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            _Concurrency.Task { @MainActor in
                if let error = error {
                    self?.logger.error("Failed to schedule due date reminder", category: .error, error: error)
                } else {
                    self?.logger.info("Due date reminder scheduled", category: .general, context: [
                        "taskTitle": taskTitle,
                        "dueDate": dueDate.formatted()
                    ])
                }
            }
        }
    }

    /// Cancel a specific task's due date reminder
    func cancelTaskDueDateReminder(taskId: UUID) {
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["task-due-\(taskId.uuidString)"])
    }

    /// Schedule reminders for all tasks with due dates
    func scheduleAllTaskDueDateReminders(tasks: [Task]) {
        // First cancel all existing task due reminders
        let center = UNUserNotificationCenter.current()
        center.getPendingNotificationRequests { [weak self] requests in
            let taskDueIds = requests.filter { $0.identifier.hasPrefix("task-due-") }.map { $0.identifier }
            center.removePendingNotificationRequests(withIdentifiers: taskDueIds)

            // Schedule new reminders for incomplete tasks with future due dates
            let tasksWithDueDates = tasks.filter { task in
                guard let dueDate = task.dueDate else { return false }
                return !task.isCompleted && dueDate > Date()
            }

            _Concurrency.Task { @MainActor in
                for task in tasksWithDueDates {
                    if let dueDate = task.dueDate {
                        self?.scheduleTaskDueDateReminder(taskId: task.id, taskTitle: task.title, dueDate: dueDate)
                    }
                }

                self?.logger.info("Scheduled \(tasksWithDueDates.count) task due date reminders", category: .general)
            }
        }
    }

    // MARK: - Overdue Task Reminders

    func scheduleOverdueTaskReminder(taskTitle: String, dueDate: Date) {
        guard isAuthorized else {
            logger.warning("Cannot schedule overdue reminder - not authorized", category: .general)
            return
        }

        // Only schedule if task is actually overdue
        guard dueDate < Date() else { return }

        let content = UNMutableNotificationContent()
        content.title = "Overdue Task"
        content.body = "\"\(taskTitle)\" is past due. Time to complete it!"
        content.sound = .default
        content.categoryIdentifier = "OVERDUE_TASK"

        // Trigger immediately for already overdue tasks
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)

        let request = UNNotificationRequest(
            identifier: "overdue-\(UUID().uuidString)",
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            _Concurrency.Task { @MainActor in
                if let error = error {
                    self?.logger.error("Failed to schedule overdue reminder", category: .error, error: error)
                }
            }
        }
    }

    func scheduleDailyJournalReminder(hour: Int = 20, minute: Int = 0) {
        guard isAuthorized else {
            logger.warning("Cannot schedule journal reminder - not authorized", category: .general)
            return
        }

        let content = UNMutableNotificationContent()
        content.title = "Daily Journal"
        content.body = "Take a moment to reflect on your day. How did it go?"
        content.sound = .default
        content.categoryIdentifier = "JOURNAL_REMINDER"

        // Schedule for specific time daily
        var dateComponents = DateComponents()
        dateComponents.hour = hour
        dateComponents.minute = minute

        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

        let request = UNNotificationRequest(
            identifier: "daily-journal-reminder",
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            _Concurrency.Task { @MainActor in
                if let error = error {
                    self?.logger.error("Failed to schedule journal reminder", category: .error, error: error)
                } else {
                    self?.logger.info("Daily journal reminder scheduled", category: .general, context: [
                        "hour": "\(hour)",
                        "minute": "\(minute)"
                    ])
                }
            }
        }
    }

    func cancelDailyJournalReminder() {
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["daily-journal-reminder"])
        logger.info("Daily journal reminder canceled", category: .general)
    }

    func scheduleDailySummary(hour: Int = 9, minute: Int = 0, pendingTaskCount: Int, overdueCount: Int) {
        guard isAuthorized, pendingTaskCount > 0 else { return }

        let content = UNMutableNotificationContent()
        content.title = "Daily Summary"

        if overdueCount > 0 {
            content.body = "You have \(pendingTaskCount) pending tasks (\(overdueCount) overdue). Let's tackle them!"
        } else {
            content.body = "You have \(pendingTaskCount) tasks to complete today. Let's get started!"
        }

        content.sound = .default
        content.categoryIdentifier = "DAILY_SUMMARY"

        var dateComponents = DateComponents()
        dateComponents.hour = hour
        dateComponents.minute = minute

        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

        let request = UNNotificationRequest(
            identifier: "daily-summary",
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            _Concurrency.Task { @MainActor in
                if let error = error {
                    self?.logger.error("Failed to schedule daily summary", category: .error, error: error)
                }
            }
        }
    }

    // MARK: - Weekly Progress Notification

    /// Schedule a weekly progress summary notification (default: Sunday at 7 PM)
    func scheduleWeeklyProgress(
        tasksCompleted: Int,
        focusMinutes: Int,
        journalEntries: Int,
        dayOfWeek: Int = 1,  // 1 = Sunday
        hour: Int = 19,
        minute: Int = 0
    ) {
        let content = UNMutableNotificationContent()
        content.title = "Weekly Progress Summary"

        var summaryParts: [String] = []
        if tasksCompleted > 0 {
            summaryParts.append("\(tasksCompleted) tasks completed")
        }
        if focusMinutes > 0 {
            let hours = focusMinutes / 60
            let mins = focusMinutes % 60
            if hours > 0 {
                summaryParts.append("\(hours)h \(mins)m focused")
            } else {
                summaryParts.append("\(mins)m focused")
            }
        }
        if journalEntries > 0 {
            summaryParts.append("\(journalEntries) journal entries")
        }

        if summaryParts.isEmpty {
            content.body = "Start fresh this week! Create tasks, focus on work, and journal your thoughts."
        } else {
            content.body = "This week: " + summaryParts.joined(separator: ", ") + ". Keep up the great work!"
        }

        content.sound = .default
        content.categoryIdentifier = "WEEKLY_PROGRESS"

        var dateComponents = DateComponents()
        dateComponents.weekday = dayOfWeek
        dateComponents.hour = hour
        dateComponents.minute = minute

        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

        let request = UNNotificationRequest(
            identifier: "weekly-progress",
            content: content,
            trigger: trigger
        )

        UNUserNotificationCenter.current().add(request) { [weak self] error in
            _Concurrency.Task { @MainActor in
                if let error = error {
                    self?.logger.error("Failed to schedule weekly progress", category: .error, error: error)
                } else {
                    self?.logger.info("Weekly progress notification scheduled", category: .general)
                }
            }
        }
    }

    func cancelWeeklyProgress() {
        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["weekly-progress"])
    }

    // MARK: - Clear Badge

    func clearBadge() {
        UNUserNotificationCenter.current().setBadgeCount(0)
    }
}


TodoWrite

Explore ProductivityHub architecture and patterns

Exploring ProductivityHub architecture and patterns

Explore HabitTracker2026 SwiftData patterns

Exploring HabitTracker2026 SwiftData patterns

Explore 200FlexoesDiarias fitness tracking patterns

Exploring 200FlexoesDiarias fitness tracking patterns

Design MassGainer2026 data models

Design project structure and architecture

Designing project structure and architecture

Create comprehensive implementation plan

Creating comprehensive implementation plan


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Explore ProductivityHub architecture and patterns

Exploring ProductivityHub architecture and patterns

Explore HabitTracker2026 SwiftData patterns

Exploring HabitTracker2026 SwiftData patterns

Explore 200FlexoesDiarias fitness tracking patterns

Exploring 200FlexoesDiarias fitness tracking patterns

Design MassGainer2026 data models

Design project structure and architecture

Designing project structure and architecture

Create comprehensive implementation plan

Creating comprehensive implementation plan

Design MassGainer2026 data models


Now let me explore HabitTracker2026 for SwiftData patterns:


     1→import Foundation
     2→import SwiftData
     3→
     4→/// Modelo principal de Hábito
     5→@Model
     6→final class Habit: Identifiable {
     7→    var id: UUID
     8→    var name: String
     9→    var emoji: String
    10→    var color: String
    11→    var targetDays: [Int] // 0 = Sunday, 1 = Monday, etc.
    12→    var reminderTime: Date?
    13→    var createdAt: Date
    14→    var updatedAt: Date
    15→    var isArchived: Bool
    16→
    17→    // CloudKit sync
    18→    var cloudKitRecordID: String?
    19→    var lastSyncedAt: Date?
    20→
    21→    // Relacionamento com entries
    22→    @Relationship(deleteRule: .cascade, inverse: \HabitEntry.habit)
    23→    var entries: [HabitEntry] = []
    24→
    25→    init(
    26→        id: UUID = UUID(),
    27→        name: String,
    28→        emoji: String = "✨",
    29→        color: String = "blue",
    30→        targetDays: [Int] = [0, 1, 2, 3, 4, 5, 6], // Todos os dias por padrão
    31→        reminderTime: Date? = nil
    32→    ) {
    33→        self.id = id
    34→        self.name = name
    35→        self.emoji = emoji
    36→        self.color = color
    37→        self.targetDays = targetDays
    38→        self.reminderTime = reminderTime
    39→        self.createdAt = Date()
    40→        self.updatedAt = Date()
    41→        self.isArchived = false
    42→    }
    43→
    44→    // MARK: - Computed Properties
    45→
    46→    /// Streak atual (dias consecutivos completados)
    47→    var currentStreak: Int {
    48→        calculateStreak()
    49→    }
    50→
    51→    /// Maior streak já alcançado
    52→    var longestStreak: Int {
    53→        calculateLongestStreak()
    54→    }
    55→
    56→    /// Verifica se o hábito foi completado hoje
    57→    var isCompletedToday: Bool {
    58→        let today = Calendar.current.startOfDay(for: Date())
    59→        return entries.contains { entry in
    60→            Calendar.current.isDate(entry.date, inSameDayAs: today) && entry.isCompleted
    61→        }
    62→    }
    63→
    64→    /// Taxa de conclusão (últimos 30 dias)
    65→    var completionRate: Double {
    66→        let thirtyDaysAgo = Calendar.current.date(byAdding: .day, value: -30, to: Date())!
    67→        let recentEntries = entries.filter { $0.date >= thirtyDaysAgo && $0.isCompleted }
    68→        let targetDaysCount = calculateTargetDaysInRange(from: thirtyDaysAgo, to: Date())
    69→        guard targetDaysCount > 0 else { return 0 }
    70→        return Double(recentEntries.count) / Double(targetDaysCount)
    71→    }
    72→
    73→    // MARK: - Methods
    74→
    75→    /// Registra check-in para hoje
    76→    func checkIn() {
    77→        let today = Calendar.current.startOfDay(for: Date())
    78→
    79→        // Verifica se já existe entry para hoje
    80→        if let existingEntry = entries.first(where: { Calendar.current.isDate($0.date, inSameDayAs: today) }) {
    81→            existingEntry.isCompleted.toggle()
    82→            existingEntry.completedAt = existingEntry.isCompleted ? Date() : nil
    83→        } else {
    84→            let newEntry = HabitEntry(date: today, isCompleted: true)
    85→            newEntry.habit = self
    86→            entries.append(newEntry)
    87→        }
    88→
    89→        updatedAt = Date()
    90→    }
    91→
    92→    /// Calcula streak atual
    93→    private func calculateStreak() -> Int {
    94→        let calendar = Calendar.current
    95→        var streak = 0
    96→        var currentDate = calendar.startOfDay(for: Date())
    97→
    98→        // Se não completou hoje, começa de ontem
    99→        if !isCompletedToday {
   100→            guard let yesterday = calendar.date(byAdding: .day, value: -1, to: currentDate) else {
   101→                return 0
   102→            }
   103→            currentDate = yesterday
   104→        }
   105→
   106→        while true {
   107→            // Verifica se este dia é um dia alvo
   108→            let weekday = calendar.component(.weekday, from: currentDate) - 1
   109→
   110→            if targetDays.contains(weekday) {
   111→                // Verifica se foi completado
   112→                let wasCompleted = entries.contains { entry in
   113→                    calendar.isDate(entry.date, inSameDayAs: currentDate) && entry.isCompleted
   114→                }
   115→
   116→                if wasCompleted {
   117→                    streak += 1
   118→                } else {
   119→                    break
   120→                }
   121→            }
   122→
   123→            guard let previousDay = calendar.date(byAdding: .day, value: -1, to: currentDate) else {
   124→                break
   125→            }
   126→            currentDate = previousDay
   127→
   128→            // Limite de segurança
   129→            if streak > 365 { break }
   130→        }
   131→
   132→        return streak
   133→    }
   134→
   135→    /// Calcula o maior streak histórico
   136→    private func calculateLongestStreak() -> Int {
   137→        guard !entries.isEmpty else { return 0 }
   138→
   139→        let calendar = Calendar.current
   140→        let sortedEntries = entries.filter { $0.isCompleted }.sorted { $0.date < $1.date }
   141→
   142→        var longest = 0
   143→        var current = 0
   144→        var lastDate: Date?
   145→
   146→        for entry in sortedEntries {
   147→            if let last = lastDate {
   148→                let daysDiff = calendar.dateComponents([.day], from: last, to: entry.date).day ?? 0
   149→
   150→                if daysDiff == 1 {
   151→                    current += 1
   152→                } else if daysDiff > 1 {
   153→                    longest = max(longest, current)
   154→                    current = 1
   155→                }
   156→            } else {
   157→                current = 1
   158→            }
   159→
   160→            lastDate = entry.date
   161→        }
   162→
   163→        return max(longest, current)
   164→    }
   165→
   166→    /// Conta dias alvo em um intervalo
   167→    private func calculateTargetDaysInRange(from startDate: Date, to endDate: Date) -> Int {
   168→        let calendar = Calendar.current
   169→        var count = 0
   170→        var currentDate = startDate
   171→
   172→        while currentDate <= endDate {
   173→            let weekday = calendar.component(.weekday, from: currentDate) - 1
   174→            if targetDays.contains(weekday) {
   175→                count += 1
   176→            }
   177→            guard let nextDay = calendar.date(byAdding: .day, value: 1, to: currentDate) else {
   178→                break
   179→            }
   180→            currentDate = nextDay
   181→        }
   182→
   183→        return count
   184→    }
   185→}
   186→
   187→// MARK: - Habit Colors
   188→
   189→extension Habit {
   190→    static let availableColors = [
   191→        "blue", "purple", "pink", "red", "orange",
   192→        "yellow", "green", "teal", "indigo", "mint"
   193→    ]
   194→
   195→    static let availableEmojis = [
   196→        "✨", "💪", "📚", "🏃", "💧", "🧘", "💤", "🥗",
   197→        "✍", "🎯", "🧠", "❤", "🌅", "🎨", "🎵", "💻"
   198→    ]
   199→}
   200→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import SwiftData

/// Modelo principal de Hábito
@Model
final class Habit: Identifiable {
    var id: UUID
    var name: String
    var emoji: String
    var color: String
    var targetDays: [Int] // 0 = Sunday, 1 = Monday, etc.
    var reminderTime: Date?
    var createdAt: Date
    var updatedAt: Date
    var isArchived: Bool

    // CloudKit sync
    var cloudKitRecordID: String?
    var lastSyncedAt: Date?

    // Relacionamento com entries
    @Relationship(deleteRule: .cascade, inverse: \HabitEntry.habit)
    var entries: [HabitEntry] = []

    init(
        id: UUID = UUID(),
        name: String,
        emoji: String = "✨",
        color: String = "blue",
        targetDays: [Int] = [0, 1, 2, 3, 4, 5, 6], // Todos os dias por padrão
        reminderTime: Date? = nil
    ) {
        self.id = id
        self.name = name
        self.emoji = emoji
        self.color = color
        self.targetDays = targetDays
        self.reminderTime = reminderTime
        self.createdAt = Date()
        self.updatedAt = Date()
        self.isArchived = false
    }

    // MARK: - Computed Properties

    /// Streak atual (dias consecutivos completados)
    var currentStreak: Int {
        calculateStreak()
    }

    /// Maior streak já alcançado
    var longestStreak: Int {
        calculateLongestStreak()
    }

    /// Verifica se o hábito foi completado hoje
    var isCompletedToday: Bool {
        let today = Calendar.current.startOfDay(for: Date())
        return entries.contains { entry in
            Calendar.current.isDate(entry.date, inSameDayAs: today) && entry.isCompleted
        }
    }

    /// Taxa de conclusão (últimos 30 dias)
    var completionRate: Double {
        let thirtyDaysAgo = Calendar.current.date(byAdding: .day, value: -30, to: Date())!
        let recentEntries = entries.filter { $0.date >= thirtyDaysAgo && $0.isCompleted }
        let targetDaysCount = calculateTargetDaysInRange(from: thirtyDaysAgo, to: Date())
        guard targetDaysCount > 0 else { return 0 }
        return Double(recentEntries.count) / Double(targetDaysCount)
    }

    // MARK: - Methods

    /// Registra check-in para hoje
    func checkIn() {
        let today = Calendar.current.startOfDay(for: Date())

        // Verifica se já existe entry para hoje
        if let existingEntry = entries.first(where: { Calendar.current.isDate($0.date, inSameDayAs: today) }) {
            existingEntry.isCompleted.toggle()
            existingEntry.completedAt = existingEntry.isCompleted ? Date() : nil
        } else {
            let newEntry = HabitEntry(date: today, isCompleted: true)
            newEntry.habit = self
            entries.append(newEntry)
        }

        updatedAt = Date()
    }

    /// Calcula streak atual
    private func calculateStreak() -> Int {
        let calendar = Calendar.current
        var streak = 0
        var currentDate = calendar.startOfDay(for: Date())

        // Se não completou hoje, começa de ontem
        if !isCompletedToday {
            guard let yesterday = calendar.date(byAdding: .day, value: -1, to: currentDate) else {
                return 0
            }
            currentDate = yesterday
        }

        while true {
            // Verifica se este dia é um dia alvo
            let weekday = calendar.component(.weekday, from: currentDate) - 1

            if targetDays.contains(weekday) {
                // Verifica se foi completado
                let wasCompleted = entries.contains { entry in
                    calendar.isDate(entry.date, inSameDayAs: currentDate) && entry.isCompleted
                }

                if wasCompleted {
                    streak += 1
                } else {
                    break
                }
            }

            guard let previousDay = calendar.date(byAdding: .day, value: -1, to: currentDate) else {
                break
            }
            currentDate = previousDay

            // Limite de segurança
            if streak > 365 { break }
        }

        return streak
    }

    /// Calcula o maior streak histórico
    private func calculateLongestStreak() -> Int {
        guard !entries.isEmpty else { return 0 }

        let calendar = Calendar.current
        let sortedEntries = entries.filter { $0.isCompleted }.sorted { $0.date < $1.date }

        var longest = 0
        var current = 0
        var lastDate: Date?

        for entry in sortedEntries {
            if let last = lastDate {
                let daysDiff = calendar.dateComponents([.day], from: last, to: entry.date).day ?? 0

                if daysDiff == 1 {
                    current += 1
                } else if daysDiff > 1 {
                    longest = max(longest, current)
                    current = 1
                }
            } else {
                current = 1
            }

            lastDate = entry.date
        }

        return max(longest, current)
    }

    /// Conta dias alvo em um intervalo
    private func calculateTargetDaysInRange(from startDate: Date, to endDate: Date) -> Int {
        let calendar = Calendar.current
        var count = 0
        var currentDate = startDate

        while currentDate <= endDate {
            let weekday = calendar.component(.weekday, from: currentDate) - 1
            if targetDays.contains(weekday) {
                count += 1
            }
            guard let nextDay = calendar.date(byAdding: .day, value: 1, to: currentDate) else {
                break
            }
            currentDate = nextDay
        }

        return count
    }
}

// MARK: - Habit Colors

extension Habit {
    static let availableColors = [
        "blue", "purple", "pink", "red", "orange",
        "yellow", "green", "teal", "indigo", "mint"
    ]

    static let availableEmojis = [
        "✨", "💪", "📚", "🏃", "💧", "🧘", "💤", "🥗",
        "✍", "🎯", "🧠", "❤", "🌅", "🎨", "🎵", "💻"
    ]
}


     1→import Foundation
     2→import SwiftData
     3→
     4→/// Registro de conclusão de um hábito em uma data específica
     5→@Model
     6→final class HabitEntry: Identifiable {
     7→    var id: UUID
     8→    var date: Date
     9→    var isCompleted: Bool
    10→    var completedAt: Date?
    11→    var notes: String?
    12→
    13→    // CloudKit sync
    14→    var cloudKitRecordID: String?
    15→
    16→    // Relacionamento com Habit
    17→    var habit: Habit?
    18→
    19→    init(
    20→        id: UUID = UUID(),
    21→        date: Date,
    22→        isCompleted: Bool = false,
    23→        notes: String? = nil
    24→    ) {
    25→        self.id = id
    26→        self.date = Calendar.current.startOfDay(for: date)
    27→        self.isCompleted = isCompleted
    28→        self.completedAt = isCompleted ? Date() : nil
    29→        self.notes = notes
    30→    }
    31→}
    32→
    33→// MARK: - HabitEntry Helpers
    34→
    35→extension HabitEntry {
    36→    /// Formata a data para exibição
    37→    var formattedDate: String {
    38→        let formatter = DateFormatter()
    39→        formatter.dateStyle = .medium
    40→        return formatter.string(from: date)
    41→    }
    42→
    43→    /// Verifica se a entry é de hoje
    44→    var isToday: Bool {
    45→        Calendar.current.isDateInToday(date)
    46→    }
    47→
    48→    /// Verifica se a entry é de ontem
    49→    var isYesterday: Bool {
    50→        Calendar.current.isDateInYesterday(date)
    51→    }
    52→}
    53→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import SwiftData

/// Registro de conclusão de um hábito em uma data específica
@Model
final class HabitEntry: Identifiable {
    var id: UUID
    var date: Date
    var isCompleted: Bool
    var completedAt: Date?
    var notes: String?

    // CloudKit sync
    var cloudKitRecordID: String?

    // Relacionamento com Habit
    var habit: Habit?

    init(
        id: UUID = UUID(),
        date: Date,
        isCompleted: Bool = false,
        notes: String? = nil
    ) {
        self.id = id
        self.date = Calendar.current.startOfDay(for: date)
        self.isCompleted = isCompleted
        self.completedAt = isCompleted ? Date() : nil
        self.notes = notes
    }
}

// MARK: - HabitEntry Helpers

extension HabitEntry {
    /// Formata a data para exibição
    var formattedDate: String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter.string(from: date)
    }

    /// Verifica se a entry é de hoje
    var isToday: Bool {
        Calendar.current.isDateInToday(date)
    }

    /// Verifica se a entry é de ontem
    var isYesterday: Bool {
        Calendar.current.isDateInYesterday(date)
    }
}


     1→import SwiftUI
     2→import SwiftData
     3→
     4→@main
     5→struct HabitTracker2026App: App {
     6→    @StateObject private var authService = AuthService.shared
     7→    @StateObject private var cloudKit = CloudKitService.shared
     8→    @StateObject private var notifications = NotificationService.shared
     9→
    10→    var sharedModelContainer: ModelContainer = {
    11→        let schema = Schema([
    12→            Habit.self,
    13→            HabitEntry.self,
    14→            User.self
    15→        ])
    16→
    17→        let modelConfiguration = ModelConfiguration(
    18→            schema: schema,
    19→            isStoredInMemoryOnly: false,
    20→            cloudKitDatabase: .none  // CloudKit disabled for MVP - enable after App Store Connect setup
    21→        )
    22→
    23→        do {
    24→            return try ModelContainer(for: schema, configurations: [modelConfiguration])
    25→        } catch {
    26→            fatalError("Could not create ModelContainer: \(error)")
    27→        }
    28→    }()
    29→
    30→    var body: some Scene {
    31→        WindowGroup {
    32→            ContentView()
    33→                .environmentObject(authService)
    34→                .environmentObject(cloudKit)
    35→                .environmentObject(notifications)
    36→        }
    37→        .modelContainer(sharedModelContainer)
    38→    }
    39→}
    40→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI
import SwiftData

@main
struct HabitTracker2026App: App {
    @StateObject private var authService = AuthService.shared
    @StateObject private var cloudKit = CloudKitService.shared
    @StateObject private var notifications = NotificationService.shared

    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Habit.self,
            HabitEntry.self,
            User.self
        ])

        let modelConfiguration = ModelConfiguration(
            schema: schema,
            isStoredInMemoryOnly: false,
            cloudKitDatabase: .none  // CloudKit disabled for MVP - enable after App Store Connect setup
        )

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(authService)
                .environmentObject(cloudKit)
                .environmentObject(notifications)
        }
        .modelContainer(sharedModelContainer)
    }
}


     1→import Foundation
     2→import UserNotifications
     3→
     4→/// Gerencia notificações locais para lembretes de hábitos
     5→final class NotificationService: ObservableObject {
     6→    static let shared = NotificationService()
     7→
     8→    @Published var isAuthorized = false
     9→
    10→    private let center = UNUserNotificationCenter.current()
    11→
    12→    private init() {
    13→        checkAuthorizationStatus()
    14→    }
    15→
    16→    // MARK: - Authorization
    17→
    18→    /// Solicita permissão para notificações
    19→    func requestAuthorization() async -> Bool {
    20→        do {
    21→            let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
    22→            await MainActor.run {
    23→                isAuthorized = granted
    24→            }
    25→            return granted
    26→        } catch {
    27→            print("❌ Erro ao solicitar permissão: \(error)")
    28→            return false
    29→        }
    30→    }
    31→
    32→    /// Verifica status atual de autorização
    33→    func checkAuthorizationStatus() {
    34→        center.getNotificationSettings { [weak self] settings in
    35→            DispatchQueue.main.async {
    36→                self?.isAuthorized = settings.authorizationStatus == .authorized
    37→            }
    38→        }
    39→    }
    40→
    41→    // MARK: - Schedule Notifications
    42→
    43→    /// Agenda lembrete para um hábito
    44→    func scheduleReminder(for habit: Habit) async {
    45→        guard isAuthorized, let reminderTime = habit.reminderTime else { return }
    46→
    47→        // Remove notificações antigas deste hábito
    48→        await removeReminder(for: habit)
    49→
    50→        let calendar = Calendar.current
    51→        let components = calendar.dateComponents([.hour, .minute], from: reminderTime)
    52→
    53→        // Agenda para cada dia alvo
    54→        for weekday in habit.targetDays {
    55→            var dateComponents = DateComponents()
    56→            dateComponents.hour = components.hour
    57→            dateComponents.minute = components.minute
    58→            dateComponents.weekday = weekday + 1 // Calendar usa 1-7
    59→
    60→            let trigger = UNCalendarNotificationTrigger(
    61→                dateMatching: dateComponents,
    62→                repeats: true
    63→            )
    64→
    65→            let content = UNMutableNotificationContent()
    66→            content.title = "Hora do seu hábito! \(habit.emoji)"
    67→            content.body = habit.name
    68→            content.sound = .default
    69→            content.badge = 1
    70→
    71→            // Adiciona ação rápida
    72→            content.categoryIdentifier = "HABIT_REMINDER"
    73→            content.userInfo = ["habitId": habit.id.uuidString]
    74→
    75→            let identifier = "\(habit.id.uuidString)-\(weekday)"
    76→            let request = UNNotificationRequest(
    77→                identifier: identifier,
    78→                content: content,
    79→                trigger: trigger
    80→            )
    81→
    82→            do {
    83→                try await center.add(request)
    84→                print("✅ Lembrete agendado: \(habit.name) - dia \(weekday)")
    85→            } catch {
    86→                print("❌ Erro ao agendar: \(error)")
    87→            }
    88→        }
    89→    }
    90→
    91→    /// Remove lembretes de um hábito
    92→    func removeReminder(for habit: Habit) async {
    93→        let identifiers = habit.targetDays.map { "\(habit.id.uuidString)-\($0)" }
    94→        center.removePendingNotificationRequests(withIdentifiers: identifiers)
    95→    }
    96→
    97→    /// Remove todas as notificações
    98→    func removeAllReminders() {
    99→        center.removeAllPendingNotificationRequests()
   100→        center.removeAllDeliveredNotifications()
   101→    }
   102→
   103→    // MARK: - Notification Categories
   104→
   105→    /// Configura categorias de notificação (ações)
   106→    func setupNotificationCategories() {
   107→        let checkInAction = UNNotificationAction(
   108→            identifier: "CHECK_IN",
   109→            title: "Marcar como feito ✅",
   110→            options: [.foreground]
   111→        )
   112→
   113→        let snoozeAction = UNNotificationAction(
   114→            identifier: "SNOOZE",
   115→            title: "Lembrar em 1h",
   116→            options: []
   117→        )
   118→
   119→        let category = UNNotificationCategory(
   120→            identifier: "HABIT_REMINDER",
   121→            actions: [checkInAction, snoozeAction],
   122→            intentIdentifiers: [],
   123→            options: []
   124→        )
   125→
   126→        center.setNotificationCategories([category])
   127→    }
   128→}
   129→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import UserNotifications

/// Gerencia notificações locais para lembretes de hábitos
final class NotificationService: ObservableObject {
    static let shared = NotificationService()

    @Published var isAuthorized = false

    private let center = UNUserNotificationCenter.current()

    private init() {
        checkAuthorizationStatus()
    }

    // MARK: - Authorization

    /// Solicita permissão para notificações
    func requestAuthorization() async -> Bool {
        do {
            let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
            await MainActor.run {
                isAuthorized = granted
            }
            return granted
        } catch {
            print("❌ Erro ao solicitar permissão: \(error)")
            return false
        }
    }

    /// Verifica status atual de autorização
    func checkAuthorizationStatus() {
        center.getNotificationSettings { [weak self] settings in
            DispatchQueue.main.async {
                self?.isAuthorized = settings.authorizationStatus == .authorized
            }
        }
    }

    // MARK: - Schedule Notifications

    /// Agenda lembrete para um hábito
    func scheduleReminder(for habit: Habit) async {
        guard isAuthorized, let reminderTime = habit.reminderTime else { return }

        // Remove notificações antigas deste hábito
        await removeReminder(for: habit)

        let calendar = Calendar.current
        let components = calendar.dateComponents([.hour, .minute], from: reminderTime)

        // Agenda para cada dia alvo
        for weekday in habit.targetDays {
            var dateComponents = DateComponents()
            dateComponents.hour = components.hour
            dateComponents.minute = components.minute
            dateComponents.weekday = weekday + 1 // Calendar usa 1-7

            let trigger = UNCalendarNotificationTrigger(
                dateMatching: dateComponents,
                repeats: true
            )

            let content = UNMutableNotificationContent()
            content.title = "Hora do seu hábito! \(habit.emoji)"
            content.body = habit.name
            content.sound = .default
            content.badge = 1

            // Adiciona ação rápida
            content.categoryIdentifier = "HABIT_REMINDER"
            content.userInfo = ["habitId": habit.id.uuidString]

            let identifier = "\(habit.id.uuidString)-\(weekday)"
            let request = UNNotificationRequest(
                identifier: identifier,
                content: content,
                trigger: trigger
            )

            do {
                try await center.add(request)
                print("✅ Lembrete agendado: \(habit.name) - dia \(weekday)")
            } catch {
                print("❌ Erro ao agendar: \(error)")
            }
        }
    }

    /// Remove lembretes de um hábito
    func removeReminder(for habit: Habit) async {
        let identifiers = habit.targetDays.map { "\(habit.id.uuidString)-\($0)" }
        center.removePendingNotificationRequests(withIdentifiers: identifiers)
    }

    /// Remove todas as notificações
    func removeAllReminders() {
        center.removeAllPendingNotificationRequests()
        center.removeAllDeliveredNotifications()
    }

    // MARK: - Notification Categories

    /// Configura categorias de notificação (ações)
    func setupNotificationCategories() {
        let checkInAction = UNNotificationAction(
            identifier: "CHECK_IN",
            title: "Marcar como feito ✅",
            options: [.foreground]
        )

        let snoozeAction = UNNotificationAction(
            identifier: "SNOOZE",
            title: "Lembrar em 1h",
            options: []
        )

        let category = UNNotificationCategory(
            identifier: "HABIT_REMINDER",
            actions: [checkInAction, snoozeAction],
            intentIdentifiers: [],
            options: []
        )

        center.setNotificationCategories([category])
    }
}


Explore ProductivityHub architecture and patterns

Exploring ProductivityHub architecture and patterns

Explore HabitTracker2026 SwiftData patterns

Exploring HabitTracker2026 SwiftData patterns

Explore 200FlexoesDiarias fitness tracking patterns

Exploring 200FlexoesDiarias fitness tracking patterns

Design MassGainer2026 data models

Design project structure and architecture

Designing project structure and architecture

Create comprehensive implementation plan

Creating comprehensive implementation plan


Design MassGainer2026 data models

Design MassGainer2026 data models


Now let me explore 200FlexoesDiarias for fitness tracking patterns:


Bash

find /Users/neog/Projects/core/200FlexoesDiarias -name "*.swift" -type f 2>/dev/null

Find Swift files in 200FlexoesDiarias


     1→//
     2→//  WorkoutSession.swift
     3→//  200 Flexões Diárias
     4→//
     5→//  Model para sessão de treino (Codable para UserDefaults)
     6→//
     7→
     8→import Foundation
     9→
    10→struct WorkoutSession: Codable, Identifiable {
    11→    var id: UUID = UUID()
    12→    var date: Date
    13→    var pushupsCompleted: Int
    14→    var seriesCompleted: Int
    15→    var timeSpent: Int  // em segundos
    16→    var isCompleted: Bool
    17→    var extraPushups: Int = 0  // Flexões feitas após completar as 200 do dia
    18→
    19→    // Helpers para UI
    20→    var pushupsCount: Int {
    21→        pushupsCompleted
    22→    }
    23→
    24→    var seriesCount: Int {
    25→        seriesCompleted
    26→    }
    27→
    28→    var timeSpentFormatted: String {
    29→        let minutes = timeSpent / 60
    30→        let seconds = timeSpent % 60
    31→        return String(format: "%d:%02d", minutes, seconds)
    32→    }
    33→
    34→    var dateFormatted: String {
    35→        let formatter = DateFormatter()
    36→        formatter.dateFormat = "dd/MM/yyyy"
    37→        return formatter.string(from: date)
    38→    }
    39→
    40→    var progressPercentage: Double {
    41→        Double(pushupsCompleted) / 200.0
    42→    }
    43→}
    44→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  WorkoutSession.swift
//  200 Flexões Diárias
//
//  Model para sessão de treino (Codable para UserDefaults)
//

import Foundation

struct WorkoutSession: Codable, Identifiable {
    var id: UUID = UUID()
    var date: Date
    var pushupsCompleted: Int
    var seriesCompleted: Int
    var timeSpent: Int  // em segundos
    var isCompleted: Bool
    var extraPushups: Int = 0  // Flexões feitas após completar as 200 do dia

    // Helpers para UI
    var pushupsCount: Int {
        pushupsCompleted
    }

    var seriesCount: Int {
        seriesCompleted
    }

    var timeSpentFormatted: String {
        let minutes = timeSpent / 60
        let seconds = timeSpent % 60
        return String(format: "%d:%02d", minutes, seconds)
    }

    var dateFormatted: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: date)
    }

    var progressPercentage: Double {
        Double(pushupsCompleted) / 200.0
    }
}


     1→//
     2→//  UserSettings.swift
     3→//  200 Flexões Diárias
     4→//
     5→//  Wrapper para UserDefaults - Configurações do usuário
     6→//
     7→
     8→import Foundation
     9→
    10→@MainActor
    11→class UserSettings: ObservableObject {
    12→    static let shared = UserSettings()
    13→
    14→    private let defaults = UserDefaults.standard
    15→
    16→    // MARK: - Keys
    17→    private enum Keys {
    18→        static let restDuration = "restDuration"
    19→        static let soundEnabled = "soundEnabled"
    20→        static let vibrationEnabled = "vibrationEnabled"
    21→        static let streak = "streak"
    22→        static let lastCompletedDate = "lastCompletedDate"
    23→        static let totalPushupsAllTime = "totalPushupsAllTime"
    24→    }
    25→
    26→    // MARK: - Properties
    27→
    28→    @Published var restDuration: Int {
    29→        didSet {
    30→            defaults.set(restDuration, forKey: Keys.restDuration)
    31→        }
    32→    }
    33→
    34→    @Published var soundEnabled: Bool {
    35→        didSet {
    36→            defaults.set(soundEnabled, forKey: Keys.soundEnabled)
    37→        }
    38→    }
    39→
    40→    @Published var vibrationEnabled: Bool {
    41→        didSet {
    42→            defaults.set(vibrationEnabled, forKey: Keys.vibrationEnabled)
    43→        }
    44→    }
    45→
    46→    var streak: Int {
    47→        get { defaults.integer(forKey: Keys.streak) }
    48→        set { defaults.set(newValue, forKey: Keys.streak) }
    49→    }
    50→
    51→    var lastCompletedDate: Date? {
    52→        get { defaults.object(forKey: Keys.lastCompletedDate) as? Date }
    53→        set { defaults.set(newValue, forKey: Keys.lastCompletedDate) }
    54→    }
    55→
    56→    var totalPushupsAllTime: Int {
    57→        get { defaults.integer(forKey: Keys.totalPushupsAllTime) }
    58→        set { defaults.set(newValue, forKey: Keys.totalPushupsAllTime) }
    59→    }
    60→
    61→    // MARK: - Initialization
    62→
    63→    private init() {
    64→        // Valores padrão
    65→        self.restDuration = defaults.integer(forKey: Keys.restDuration) == 0 ? 60 : defaults.integer(forKey: Keys.restDuration)
    66→        self.soundEnabled = defaults.object(forKey: Keys.soundEnabled) as? Bool ?? true
    67→        self.vibrationEnabled = defaults.object(forKey: Keys.vibrationEnabled) as? Bool ?? true
    68→    }
    69→
    70→    // MARK: - Methods
    71→
    72→    func updateStreak(completedToday: Bool) {
    73→        guard completedToday else { return }
    74→
    75→        if let lastDate = lastCompletedDate {
    76→            let calendar = Calendar.current
    77→            if calendar.isDateInToday(lastDate) {
    78→                // Já completou hoje, não muda streak
    79→                return
    80→            } else if calendar.isDateInYesterday(lastDate) {
    81→                // Dia consecutivo!
    82→                streak += 1
    83→            } else {
    84→                // Quebrou a sequência
    85→                streak = 1
    86→            }
    87→        } else {
    88→            // Primeira vez
    89→            streak = 1
    90→        }
    91→
    92→        lastCompletedDate = Date()
    93→
    94→        // Verifica se atingiu um milestone de streak
    95→        checkStreakMilestone()
    96→    }
    97→
    98→    /// Verifica e envia notificação se atingiu milestone de streak
    99→    private func checkStreakMilestone() {
   100→        let milestones = [7, 30, 100, 365]
   101→        if milestones.contains(streak) {
   102→            NotificationService.shared.sendStreakMilestone(streak)
   103→            SoundService.shared.hapticStreakMilestone(streak)
   104→        }
   105→    }
   106→
   107→    func resetStreak() {
   108→        streak = 0
   109→        lastCompletedDate = nil
   110→    }
   111→
   112→    func addPushups(_ count: Int) {
   113→        totalPushupsAllTime += count
   114→    }
   115→}
   116→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  UserSettings.swift
//  200 Flexões Diárias
//
//  Wrapper para UserDefaults - Configurações do usuário
//

import Foundation

@MainActor
class UserSettings: ObservableObject {
    static let shared = UserSettings()

    private let defaults = UserDefaults.standard

    // MARK: - Keys
    private enum Keys {
        static let restDuration = "restDuration"
        static let soundEnabled = "soundEnabled"
        static let vibrationEnabled = "vibrationEnabled"
        static let streak = "streak"
        static let lastCompletedDate = "lastCompletedDate"
        static let totalPushupsAllTime = "totalPushupsAllTime"
    }

    // MARK: - Properties

    @Published var restDuration: Int {
        didSet {
            defaults.set(restDuration, forKey: Keys.restDuration)
        }
    }

    @Published var soundEnabled: Bool {
        didSet {
            defaults.set(soundEnabled, forKey: Keys.soundEnabled)
        }
    }

    @Published var vibrationEnabled: Bool {
        didSet {
            defaults.set(vibrationEnabled, forKey: Keys.vibrationEnabled)
        }
    }

    var streak: Int {
        get { defaults.integer(forKey: Keys.streak) }
        set { defaults.set(newValue, forKey: Keys.streak) }
    }

    var lastCompletedDate: Date? {
        get { defaults.object(forKey: Keys.lastCompletedDate) as? Date }
        set { defaults.set(newValue, forKey: Keys.lastCompletedDate) }
    }

    var totalPushupsAllTime: Int {
        get { defaults.integer(forKey: Keys.totalPushupsAllTime) }
        set { defaults.set(newValue, forKey: Keys.totalPushupsAllTime) }
    }

    // MARK: - Initialization

    private init() {
        // Valores padrão
        self.restDuration = defaults.integer(forKey: Keys.restDuration) == 0 ? 60 : defaults.integer(forKey: Keys.restDuration)
        self.soundEnabled = defaults.object(forKey: Keys.soundEnabled) as? Bool ?? true
        self.vibrationEnabled = defaults.object(forKey: Keys.vibrationEnabled) as? Bool ?? true
    }

    // MARK: - Methods

    func updateStreak(completedToday: Bool) {
        guard completedToday else { return }

        if let lastDate = lastCompletedDate {
            let calendar = Calendar.current
            if calendar.isDateInToday(lastDate) {
                // Já completou hoje, não muda streak
                return
            } else if calendar.isDateInYesterday(lastDate) {
                // Dia consecutivo!
                streak += 1
            } else {
                // Quebrou a sequência
                streak = 1
            }
        } else {
            // Primeira vez
            streak = 1
        }

        lastCompletedDate = Date()

        // Verifica se atingiu um milestone de streak
        checkStreakMilestone()
    }

    /// Verifica e envia notificação se atingiu milestone de streak
    private func checkStreakMilestone() {
        let milestones = [7, 30, 100, 365]
        if milestones.contains(streak) {
            NotificationService.shared.sendStreakMilestone(streak)
            SoundService.shared.hapticStreakMilestone(streak)
        }
    }

    func resetStreak() {
        streak = 0
        lastCompletedDate = nil
    }

    func addPushups(_ count: Int) {
        totalPushupsAllTime += count
    }
}


     1→//
     2→//  DashboardView.swift
     3→//  200 Flexões Diárias
     4→//
     5→//  Tela inicial - Dashboard com progresso do dia
     6→//  Otimizado para iPhone 15 (v2.1)
     7→//
     8→
     9→import SwiftUI
    10→
    11→struct DashboardView: View {
    12→    @StateObject private var viewModel = DashboardViewModel()
    13→    @State private var showingWorkout = false
    14→    @State private var showingHistory = false
    15→    @State private var showingSettings = false
    16→    @State private var startExtraMode = false
    17→
    18→    var body: some View {
    19→        NavigationStack {
    20→            GeometryReader { geometry in
    21→                let screenHeight = geometry.size.height
    22→                let screenWidth = geometry.size.width
    23→                let isCompact = screenHeight < 700
    24→
    25→                ScrollView(showsIndicators: false) {
    26→                    VStack(spacing: isCompact ? 16 : 24) {
    27→                        // Header com logo
    28→                        headerView(screenWidth: screenWidth, isCompact: isCompact)
    29→                            .padding(.top, isCompact ? 8 : 16)
    30→
    31→                        // Progresso circular - principal
    32→                        progressCircle(screenWidth: screenWidth, screenHeight: screenHeight)
    33→
    34→                        // Stats cards
    35→                        statsGrid(screenWidth: screenWidth)
    36→
    37→                        // Botão principal - grande e fácil de tocar
    38→                        startButton(screenWidth: screenWidth)
    39→                            .padding(.vertical, 8)
    40→
    41→                        // Link histórico
    42→                        quickLinks
    43→
    44→                        Spacer(minLength: 20)
    45→                    }
    46→                    .padding(.horizontal, 20)
    47→                }
    48→            }
    49→            .navigationTitle("200 Flexões")
    50→            .navigationBarTitleDisplayMode(.inline)
    51→            .toolbar {
    52→                ToolbarItem(placement: .navigationBarTrailing) {
    53→                    Button {
    54→                        showingSettings = true
    55→                    } label: {
    56→                        Image(systemName: "gearshape.fill")
    57→                            .font(.title3)
    58→                    }
    59→                }
    60→            }
    61→            .sheet(isPresented: $showingSettings) {
    62→                SettingsView()
    63→            }
    64→            .fullScreenCover(isPresented: $showingWorkout) {
    65→                ActiveWorkoutView(startInExtraMode: startExtraMode)
    66→            }
    67→            .sheet(isPresented: $showingHistory) {
    68→                HistoryView()
    69→            }
    70→            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("DayChanged"))) { _ in
    71→                viewModel.refresh()
    72→            }
    73→            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("RefreshDashboard"))) { _ in
    74→                viewModel.refresh()
    75→            }
    76→        }
    77→    }
    78→
    79→    // MARK: - Subviews
    80→
    81→    private func headerView(screenWidth: CGFloat, isCompact: Bool) -> some View {
    82→        let iconSize: CGFloat = isCompact ? 70 : 90
    83→
    84→        return VStack(spacing: isCompact ? 8 : 12) {
    85→            // Ícone do app
    86→            ZStack {
    87→                Circle()
    88→                    .fill(LinearGradient(
    89→                        colors: [.orange, .red],
    90→                        startPoint: .topLeading,
    91→                        endPoint: .bottomTrailing
    92→                    ))
    93→                    .frame(width: iconSize, height: iconSize)
    94→                    .shadow(color: .orange.opacity(0.4), radius: 10, y: 5)
    95→
    96→                Image(systemName: "figure.strengthtraining.traditional")
    97→                    .font(.system(size: iconSize * 0.45, weight: .semibold))
    98→                    .foregroundColor(.white)
    99→            }
   100→
   101→            // Título
   102→            Text("200 Flexões Diárias")
   103→                .font(.title2)
   104→                .fontWeight(.bold)
   105→
   106→            // Streak
   107→            HStack(spacing: 6) {
   108→                Image(systemName: viewModel.streak > 0 ? "flame.fill" : "flame")
   109→                    .foregroundColor(viewModel.streak > 0 ? .orange : .gray)
   110→                    .font(.title3)
   111→                    .symbolEffect(.bounce, value: viewModel.streak)
   112→
   113→                Text(viewModel.streakText)
   114→                    .font(.headline)
   115→                    .foregroundColor(viewModel.streak > 0 ? .orange : .secondary)
   116→                    .contentTransition(.numericText())
   117→            }
   118→            .animation(.spring(response: 0.3), value: viewModel.streak)
   119→        }
   120→    }
   121→
   122→    private func progressCircle(screenWidth: CGFloat, screenHeight: CGFloat) -> some View {
   123→        // Tamanho responsivo baseado na tela
   124→        let circleSize: CGFloat = min(screenWidth * 0.6, screenHeight * 0.3)
   125→        let strokeWidth: CGFloat = circleSize * 0.08
   126→        let fontSize: CGFloat = circleSize * 0.28
   127→
   128→        return VStack(spacing: 16) {
   129→            ZStack {
   130→                // Background circle
   131→                Circle()
   132→                    .stroke(Color.gray.opacity(0.2), lineWidth: strokeWidth)
   133→                    .frame(width: circleSize, height: circleSize)
   134→
   135→                // Progress circle
   136→                Circle()
   137→                    .trim(from: 0, to: viewModel.progressPercentage)
   138→                    .stroke(
   139→                        LinearGradient(
   140→                            colors: viewModel.todayCompleted ? [.green, .mint] : [.orange, .red],
   141→                            startPoint: .topLeading,
   142→                            endPoint: .bottomTrailing
   143→                        ),
   144→                        style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round)
   145→                    )
   146→                    .rotationEffect(.degrees(-90))
   147→                    .frame(width: circleSize, height: circleSize)
   148→                    .animation(.easeInOut(duration: 0.5), value: viewModel.progressPercentage)
   149→                    .shadow(color: viewModel.todayCompleted ? .green.opacity(0.3) : .orange.opacity(0.3), radius: 8)
   150→
   151→                // Centro com número
   152→                VStack(spacing: 2) {
   153→                    Text("\(viewModel.todayPushups)")
   154→                        .font(.system(size: fontSize, weight: .bold, design: .rounded))
   155→                        .foregroundColor(viewModel.todayCompleted ? .green : .primary)
   156→                        .contentTransition(.numericText())
   157→                        .animation(.spring(response: 0.3), value: viewModel.todayPushups)
   158→
   159→                    Text("de 200")
   160→                        .font(.subheadline)
   161→                        .foregroundColor(.secondary)
   162→                }
   163→            }
   164→
   165→            // Mensagem motivacional
   166→            Text(viewModel.motivationalMessage)
   167→                .font(.headline)
   168→                .foregroundColor(viewModel.todayCompleted ? .green : .primary)
   169→                .multilineTextAlignment(.center)
   170→                .lineLimit(2)
   171→                .animation(.easeInOut, value: viewModel.todayCompleted)
   172→        }
   173→    }
   174→
   175→    private func statsGrid(screenWidth: CGFloat) -> some View {
   176→        HStack(spacing: 12) {
   177→            StatCard(
   178→                value: viewModel.seriesText,
   179→                label: "Séries",
   180→                icon: "checkmark.circle.fill",
   181→                color: .blue
   182→            )
   183→
   184→            StatCard(
   185→                value: "\(viewModel.totalAllTime)",
   186→                label: "Total",
   187→                icon: "chart.line.uptrend.xyaxis",
   188→                color: .purple
   189→            )
   190→
   191→            StatCard(
   192→                value: viewModel.streak > 0 ? "\(viewModel.streak)d" : "-",
   193→                label: "Streak",
   194→                icon: "flame.fill",
   195→                color: .orange
   196→            )
   197→        }
   198→    }
   199→
   200→    private func startButton(screenWidth: CGFloat) -> some View {
   201→        Button {
   202→            startExtraMode = viewModel.isExtraMode
   203→            showingWorkout = true
   204→        } label: {
   205→            HStack(spacing: 12) {
   206→                if viewModel.isExtraMode {
   207→                    Image(systemName: "star.circle.fill")
   208→                        .font(.title)
   209→
   210→                    VStack(alignment: .leading, spacing: 2) {
   211→                        Text("Treino Extra")
   212→                            .font(.title3)
   213→                            .fontWeight(.bold)
   214→                        if viewModel.todayExtraPushups > 0 {
   215→                            Text("+\(viewModel.todayExtraPushups) extras hoje")
   216→                                .font(.caption)
   217→                                .opacity(0.9)
   218→                        }
   219→                    }
   220→                } else {
   221→                    Image(systemName: "play.circle.fill")
   222→                        .font(.title)
   223→
   224→                    Text("Iniciar Treino")
   225→                        .font(.title3)
   226→                        .fontWeight(.bold)
   227→                }
   228→
   229→                Spacer()
   230→
   231→                Image(systemName: "chevron.right")
   232→                    .font(.title3)
   233→            }
   234→            .frame(maxWidth: .infinity)
   235→            .padding(.vertical, 18)
   236→            .padding(.horizontal, 24)
   237→            .background(
   238→                LinearGradient(
   239→                    colors: viewModel.isExtraMode ? [.orange, .red] : [.blue, .cyan],
   240→                    startPoint: .leading,
   241→                    endPoint: .trailing
   242→                )
   243→            )
   244→            .foregroundColor(.white)
   245→            .cornerRadius(16)
   246→            .shadow(color: viewModel.isExtraMode ? .orange.opacity(0.4) : .blue.opacity(0.4), radius: 10, y: 5)
   247→        }
   248→        .buttonStyle(ScaleButtonStyle())
   249→    }
   250→
   251→    private var quickLinks: some View {
   252→        Button {
   253→            showingHistory = true
   254→        } label: {
   255→            HStack {
   256→                Image(systemName: "clock.arrow.circlepath")
   257→                    .font(.title3)
   258→                Text("Histórico de Treinos")
   259→                    .font(.body)
   260→                Spacer()
   261→                Image(systemName: "chevron.right")
   262→                    .font(.body)
   263→                    .foregroundColor(.secondary)
   264→            }
   265→            .padding(16)
   266→            .background(Color(.systemGray6))
   267→            .cornerRadius(12)
   268→        }
   269→        .foregroundColor(.primary)
   270→    }
   271→}
   272→
   273→// MARK: - Scale Button Style
   274→
   275→struct ScaleButtonStyle: ButtonStyle {
   276→    func makeBody(configuration: Configuration) -> some View {
   277→        configuration.label
   278→            .scaleEffect(configuration.isPressed ? 0.96 : 1.0)
   279→            .animation(.easeInOut(duration: 0.15), value: configuration.isPressed)
   280→    }
   281→}
   282→
   283→// MARK: - StatCard Component
   284→
   285→struct StatCard: View {
   286→    let value: String
   287→    let label: String
   288→    let icon: String
   289→    let color: Color
   290→
   291→    var body: some View {
   292→        VStack(spacing: 8) {
   293→            Image(systemName: icon)
   294→                .font(.title2)
   295→                .foregroundColor(color)
   296→
   297→            Text(value)
   298→                .font(.title3)
   299→                .fontWeight(.bold)
   300→                .contentTransition(.numericText())
   301→
   302→            Text(label)
   303→                .font(.caption)
   304→                .foregroundColor(.secondary)
   305→        }
   306→        .frame(maxWidth: .infinity)
   307→        .padding(.vertical, 16)
   308→        .background(Color(.systemGray6))
   309→        .cornerRadius(12)
   310→    }
   311→}
   312→
   313→// MARK: - Preview
   314→
   315→#Preview {
   316→    DashboardView()
   317→}
   318→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

//
//  DashboardView.swift
//  200 Flexões Diárias
//
//  Tela inicial - Dashboard com progresso do dia
//  Otimizado para iPhone 15 (v2.1)
//

import SwiftUI

struct DashboardView: View {
    @StateObject private var viewModel = DashboardViewModel()
    @State private var showingWorkout = false
    @State private var showingHistory = false
    @State private var showingSettings = false
    @State private var startExtraMode = false

    var body: some View {
        NavigationStack {
            GeometryReader { geometry in
                let screenHeight = geometry.size.height
                let screenWidth = geometry.size.width
                let isCompact = screenHeight < 700

                ScrollView(showsIndicators: false) {
                    VStack(spacing: isCompact ? 16 : 24) {
                        // Header com logo
                        headerView(screenWidth: screenWidth, isCompact: isCompact)
                            .padding(.top, isCompact ? 8 : 16)

                        // Progresso circular - principal
                        progressCircle(screenWidth: screenWidth, screenHeight: screenHeight)

                        // Stats cards
                        statsGrid(screenWidth: screenWidth)

                        // Botão principal - grande e fácil de tocar
                        startButton(screenWidth: screenWidth)
                            .padding(.vertical, 8)

                        // Link histórico
                        quickLinks

                        Spacer(minLength: 20)
                    }
                    .padding(.horizontal, 20)
                }
            }
            .navigationTitle("200 Flexões")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        showingSettings = true
                    } label: {
                        Image(systemName: "gearshape.fill")
                            .font(.title3)
                    }
                }
            }
            .sheet(isPresented: $showingSettings) {
                SettingsView()
            }
            .fullScreenCover(isPresented: $showingWorkout) {
                ActiveWorkoutView(startInExtraMode: startExtraMode)
            }
            .sheet(isPresented: $showingHistory) {
                HistoryView()
            }
            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("DayChanged"))) { _ in
                viewModel.refresh()
            }
            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("RefreshDashboard"))) { _ in
                viewModel.refresh()
            }
        }
    }

    // MARK: - Subviews

    private func headerView(screenWidth: CGFloat, isCompact: Bool) -> some View {
        let iconSize: CGFloat = isCompact ? 70 : 90

        return VStack(spacing: isCompact ? 8 : 12) {
            // Ícone do app
            ZStack {
                Circle()
                    .fill(LinearGradient(
                        colors: [.orange, .red],
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    ))
                    .frame(width: iconSize, height: iconSize)
                    .shadow(color: .orange.opacity(0.4), radius: 10, y: 5)

                Image(systemName: "figure.strengthtraining.traditional")
                    .font(.system(size: iconSize * 0.45, weight: .semibold))
                    .foregroundColor(.white)
            }

            // Título
            Text("200 Flexões Diárias")
                .font(.title2)
                .fontWeight(.bold)

            // Streak
            HStack(spacing: 6) {
                Image(systemName: viewModel.streak > 0 ? "flame.fill" : "flame")
                    .foregroundColor(viewModel.streak > 0 ? .orange : .gray)
                    .font(.title3)
                    .symbolEffect(.bounce, value: viewModel.streak)

                Text(viewModel.streakText)
                    .font(.headline)
                    .foregroundColor(viewModel.streak > 0 ? .orange : .secondary)
                    .contentTransition(.numericText())
            }
            .animation(.spring(response: 0.3), value: viewModel.streak)
        }
    }

    private func progressCircle(screenWidth: CGFloat, screenHeight: CGFloat) -> some View {
        // Tamanho responsivo baseado na tela
        let circleSize: CGFloat = min(screenWidth * 0.6, screenHeight * 0.3)
        let strokeWidth: CGFloat = circleSize * 0.08
        let fontSize: CGFloat = circleSize * 0.28

        return VStack(spacing: 16) {
            ZStack {
                // Background circle
                Circle()
                    .stroke(Color.gray.opacity(0.2), lineWidth: strokeWidth)
                    .frame(width: circleSize, height: circleSize)

                // Progress circle
                Circle()
                    .trim(from: 0, to: viewModel.progressPercentage)
                    .stroke(
                        LinearGradient(
                            colors: viewModel.todayCompleted ? [.green, .mint] : [.orange, .red],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        ),
                        style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round)
                    )
                    .rotationEffect(.degrees(-90))
                    .frame(width: circleSize, height: circleSize)
                    .animation(.easeInOut(duration: 0.5), value: viewModel.progressPercentage)
                    .shadow(color: viewModel.todayCompleted ? .green.opacity(0.3) : .orange.opacity(0.3), radius: 8)

                // Centro com número
                VStack(spacing: 2) {
                    Text("\(viewModel.todayPushups)")
                        .font(.system(size: fontSize, weight: .bold, design: .rounded))
                        .foregroundColor(viewModel.todayCompleted ? .green : .primary)
                        .contentTransition(.numericText())
                        .animation(.spring(response: 0.3), value: viewModel.todayPushups)

                    Text("de 200")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
            }

            // Mensagem motivacional
            Text(viewModel.motivationalMessage)
                .font(.headline)
                .foregroundColor(viewModel.todayCompleted ? .green : .primary)
                .multilineTextAlignment(.center)
                .lineLimit(2)
                .animation(.easeInOut, value: viewModel.todayCompleted)
        }
    }

    private func statsGrid(screenWidth: CGFloat) -> some View {
        HStack(spacing: 12) {
            StatCard(
                value: viewModel.seriesText,
                label: "Séries",
                icon: "checkmark.circle.fill",
                color: .blue
            )

            StatCard(
                value: "\(viewModel.totalAllTime)",
                label: "Total",
                icon: "chart.line.uptrend.xyaxis",
                color: .purple
            )

            StatCard(
                value: viewModel.streak > 0 ? "\(viewModel.streak)d" : "-",
                label: "Streak",
                icon: "flame.fill",
                color: .orange
            )
        }
    }

    private func startButton(screenWidth: CGFloat) -> some View {
        Button {
            startExtraMode = viewModel.isExtraMode
            showingWorkout = true
        } label: {
            HStack(spacing: 12) {
                if viewModel.isExtraMode {
                    Image(systemName: "star.circle.fill")
                        .font(.title)

                    VStack(alignment: .leading, spacing: 2) {
                        Text("Treino Extra")
                            .font(.title3)
                            .fontWeight(.bold)
                        if viewModel.todayExtraPushups > 0 {
                            Text("+\(viewModel.todayExtraPushups) extras hoje")
                                .font(.caption)
                                .opacity(0.9)
                        }
                    }
                } else {
                    Image(systemName: "play.circle.fill")
                        .font(.title)

                    Text("Iniciar Treino")
                        .font(.title3)
                        .fontWeight(.bold)
                }

                Spacer()

                Image(systemName: "chevron.right")
                    .font(.title3)
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 18)
            .padding(.horizontal, 24)
            .background(
                LinearGradient(
                    colors: viewModel.isExtraMode ? [.orange, .red] : [.blue, .cyan],
                    startPoint: .leading,
                    endPoint: .trailing
                )
            )
            .foregroundColor(.white)
            .cornerRadius(16)
            .shadow(color: viewModel.isExtraMode ? .orange.opacity(0.4) : .blue.opacity(0.4), radius: 10, y: 5)
        }
        .buttonStyle(ScaleButtonStyle())
    }

    private var quickLinks: some View {
        Button {
            showingHistory = true
        } label: {
            HStack {
                Image(systemName: "clock.arrow.circlepath")
                    .font(.title3)
                Text("Histórico de Treinos")
                    .font(.body)
                Spacer()
                Image(systemName: "chevron.right")
                    .font(.body)
                    .foregroundColor(.secondary)
            }
            .padding(16)
            .background(Color(.systemGray6))
            .cornerRadius(12)
        }
        .foregroundColor(.primary)
    }
}

// MARK: - Scale Button Style

struct ScaleButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.96 : 1.0)
            .animation(.easeInOut(duration: 0.15), value: configuration.isPressed)
    }
}

// MARK: - StatCard Component

struct StatCard: View {
    let value: String
    let label: String
    let icon: String
    let color: Color

    var body: some View {
        VStack(spacing: 8) {
            Image(systemName: icon)
                .font(.title2)
                .foregroundColor(color)

            Text(value)
                .font(.title3)
                .fontWeight(.bold)
                .contentTransition(.numericText())

            Text(label)
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 16)
        .background(Color(.systemGray6))
        .cornerRadius(12)
    }
}

// MARK: - Preview

#Preview {
    DashboardView()
}