1→import SwiftUI 2→ 3→// MARK: - Daily Wisdom Quotes 4→ 5→struct WisdomQuote: Identifiable { 6→ let id = UUID() 7→ let text: String 8→ let author: String 9→ let category: QuoteCategory 10→ 11→ enum QuoteCategory: String, CaseIterable { 12→ case stoic = "Estoicismo" 13→ case biblical = "Bíblia" 14→ case justForToday = "Só Por Hoje" 15→ 16→ var icon: String { 17→ switch self { 18→ case .stoic: return "building.columns.fill" 19→ case .biblical: return "book.closed.fill" 20→ case .justForToday: return "sun.max.fill" 21→ } 22→ } 23→ 24→ var color: Color { 25→ switch self { 26→ case .stoic: return .blue 27→ case .biblical: return .purple 28→ case .justForToday: return .orange 29→ } 30→ } 31→ } 32→ 33→ static let stoicQuotes: [WisdomQuote] = [ 34→ WisdomQuote(text: "Não busque que os acontecimentos aconteçam como você quer, mas queira que aconteçam como acontecem, e sua vida fluirá bem.", author: "Epicteto", category: .stoic), 35→ WisdomQuote(text: "A felicidade da sua vida depende da qualidade dos seus pensamentos.", author: "Marco Aurélio", category: .stoic), 36→ WisdomQuote(text: "Não é o homem que tem pouco, mas o que deseja mais, que é pobre.", author: "Sêneca", category: .stoic), 37→ WisdomQuote(text: "O sofrimento é uma escolha. Se você não pode controlar o evento, pode ao menos controlar sua resposta.", author: "Epicteto", category: .stoic), 38→ WisdomQuote(text: "Perde quem tinha, não quem nunca teve. O homem sábio não se apega ao que pode perder.", author: "Sêneca", category: .stoic), 39→ WisdomQuote(text: "Você tem poder sobre sua mente, não sobre eventos externos. Perceba isso e encontrará força.", author: "Marco Aurélio", category: .stoic), 40→ WisdomQuote(text: "A dor que sentimos quando resistimos ao que é inevitável é sempre pior que o evento em si.", author: "Epicteto", category: .stoic), 41→ WisdomQuote(text: "Quando você se levantar de manhã, pense no privilégio de estar vivo, de pensar, de aproveitar, de amar.", author: "Marco Aurélio", category: .stoic), 42→ WisdomQuote(text: "Não são as coisas que nos perturbam, mas nossa opinião sobre as coisas.", author: "Epicteto", category: .stoic), 43→ WisdomQuote(text: "Às vezes, até viver é um ato de coragem.", author: "Sêneca", category: .stoic), 44→ WisdomQuote(text: "A melhor vingança é não ser como seu inimigo.", author: "Marco Aurélio", category: .stoic), 45→ WisdomQuote(text: "Quem vive na esperança morre de fome espiritual. Viva no agora.", author: "Sêneca", category: .stoic) 46→ ] 47→ 48→ static let biblicalQuotes: [WisdomQuote] = [ 49→ WisdomQuote(text: "Tudo posso naquele que me fortalece.", author: "Filipenses 4:13", category: .biblical), 50→ WisdomQuote(text: "O Senhor é a minha força e o meu escudo; nele o meu coração confia, e dele recebo ajuda.", author: "Salmos 28:7", category: .biblical), 51→ WisdomQuote(text: "Não se amoldem ao padrão deste mundo, mas transformem-se pela renovação da sua mente.", author: "Romanos 12:2", category: .biblical), 52→ WisdomQuote(text: "Porque eu sei os planos que tenho para vocês, planos de fazê-los prosperar e não de causar dano.", author: "Jeremias 29:11", category: .biblical), 53→ WisdomQuote(text: "Venham a mim, todos os que estão cansados e sobrecarregados, e eu darei descanso a vocês.", author: "Mateus 11:28", category: .biblical), 54→ WisdomQuote(text: "Entrega o teu caminho ao Senhor; confia nele, e ele tudo fará.", author: "Salmos 37:5", category: .biblical), 55→ WisdomQuote(text: "Mas os que esperam no Senhor renovarão as suas forças; subirão com asas como águias.", author: "Isaías 40:31", category: .biblical), 56→ WisdomQuote(text: "A tentação não os alcançou senão a humana; mas Deus é fiel, não permitirá que sejais tentados além do que podeis.", author: "1 Coríntios 10:13", category: .biblical), 57→ WisdomQuote(text: "Não temas, porque eu sou contigo; não te assombres, porque eu sou teu Deus.", author: "Isaías 41:10", category: .biblical), 58→ WisdomQuote(text: "Deixem as preocupações nas mãos de Deus, pois ele cuida de vocês.", author: "1 Pedro 5:7", category: .biblical), 59→ WisdomQuote(text: "O Senhor é o meu pastor; nada me faltará.", author: "Salmos 23:1", category: .biblical), 60→ WisdomQuote(text: "Sejam fortes e corajosos. Não tenham medo, pois o Senhor estará com vocês.", author: "Deuteronômio 31:6", category: .biblical) 61→ ] 62→ 63→ static let justForTodayQuotes: [WisdomQuote] = [ 64→ WisdomQuote(text: "Só por hoje, não usarei drogas. Só por hoje, vou acreditar que posso viver sem usar.", author: "Só Por Hoje", category: .justForToday), 65→ WisdomQuote(text: "Só por hoje, vou ter fé em alguém do programa que acredita em mim e quer me ajudar.", author: "Só Por Hoje", category: .justForToday), 66→ WisdomQuote(text: "Só por hoje, terei um programa. Vou tentar segui-lo da melhor forma possível.", author: "Só Por Hoje", category: .justForToday), 67→ WisdomQuote(text: "Só por hoje, através do programa, vou tentar ter uma melhor perspectiva da minha vida.", author: "Só Por Hoje", category: .justForToday), 68→ WisdomQuote(text: "Só por hoje, não terei medo. Meus pensamentos se concentrarão na minha nova vida.", author: "Só Por Hoje", category: .justForToday), 69→ WisdomQuote(text: "Só por hoje, serei honesto comigo mesmo. Vou buscar ajuda de um poder superior.", author: "Só Por Hoje", category: .justForToday), 70→ WisdomQuote(text: "Só por hoje, vou estar consciente e agradecido. Tenho uma doença, mas posso me recuperar.", author: "Só Por Hoje", category: .justForToday), 71→ WisdomQuote(text: "O passado já foi, o futuro ainda não chegou. Tudo que tenho é hoje, este momento, agora.", author: "Só Por Hoje", category: .justForToday), 72→ WisdomQuote(text: "Minha doença quer me matar, mas minha recuperação quer me salvar. Hoje escolho a vida.", author: "Só Por Hoje", category: .justForToday), 73→ WisdomQuote(text: "Não preciso usar hoje. Um dia de cada vez é tudo que preciso administrar.", author: "Só Por Hoje", category: .justForToday), 74→ WisdomQuote(text: "Cada dia limpo é uma vitória. Cada momento de sobriedade é um presente.", author: "Só Por Hoje", category: .justForToday), 75→ WisdomQuote(text: "Não tenho que ser perfeito hoje. Só tenho que não usar e pedir ajuda quando precisar.", author: "Só Por Hoje", category: .justForToday) 76→ ] 77→ 78→ static var allQuotes: [WisdomQuote] { 79→ stoicQuotes + biblicalQuotes + justForTodayQuotes 80→ } 81→ 82→ /// Get quote of the day based on date (consistent for the whole day) 83→ static func quoteOfTheDay() -> WisdomQuote { 84→ let calendar = Calendar.current 85→ let dayOfYear = calendar.ordinality(of: .day, in: .year, for: Date()) ?? 1 86→ let index = dayOfYear % allQuotes.count 87→ return allQuotes[index] 88→ } 89→ 90→ /// Get a random quote from specific category 91→ static func randomQuote(from category: QuoteCategory) -> WisdomQuote { 92→ let quotes: [WisdomQuote] 93→ switch category { 94→ case .stoic: quotes = stoicQuotes 95→ case .biblical: quotes = biblicalQuotes 96→ case .justForToday: quotes = justForTodayQuotes 97→ } 98→ return quotes.randomElement() ?? quotes[0] 99→ } 100→} 101→ 102→struct DashboardView: View { 103→ @EnvironmentObject var dataManager: DataManager 104→ @State private var currentTime = Date() 105→ 106→ // Timer to update time display 107→ private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() 108→ 109→ var body: some View { 110→ ScrollView { 111→ LazyVStack(spacing: 24) { 112→ // Daily wisdom quote 113→ DailyWisdomCard() 114→ 115→ // Header with sobriety counter 116→ SobrietyCounterCard() 117→ 118→ // Quick stats row 119→ LazyVGrid(columns: [ 120→ GridItem(.flexible()), 121→ GridItem(.flexible()), 122→ GridItem(.flexible()), 123→ GridItem(.flexible()) 124→ ], spacing: 16) { 125→ QuickStatCard( 126→ title: "Nível", 127→ value: "\(dataManager.progress?.level.level ?? 1)", 128→ subtitle: dataManager.progress?.level.titlePT ?? "Iniciante", 129→ icon: "star.fill", 130→ color: .yellow 131→ ) 132→ 133→ QuickStatCard( 134→ title: "XP Total", 135→ value: formatNumber(dataManager.progress?.totalXP ?? 0), 136→ subtitle: "pontos", 137→ icon: "bolt.fill", 138→ color: .purple 139→ ) 140→ 141→ QuickStatCard( 142→ title: "Check-ins", 143→ value: "\(dataManager.progress?.currentCheckInStreak ?? 0)", 144→ subtitle: "dias seguidos", 145→ icon: "flame.fill", 146→ color: .orange 147→ ) 148→ 149→ QuickStatCard( 150→ title: "Crises", 151→ value: "\(dataManager.progress?.totalCrisesSurvived ?? 0)", 152→ subtitle: "superadas", 153→ icon: "shield.fill", 154→ color: .green 155→ ) 156→ } 157→ 158→ // Journey cards - show all active journeys 159→ if !dataManager.recoveryJourneys.isEmpty { 160→ VStack(alignment: .leading, spacing: 12) { 161→ Text("Jornadas de Recuperação") 162→ .font(.headline) 163→ .foregroundColor(.secondary) 164→ 165→ LazyVGrid(columns: [ 166→ GridItem(.flexible()), 167→ GridItem(.flexible()), 168→ GridItem(.flexible()), 169→ GridItem(.flexible()) 170→ ], spacing: 16) { 171→ ForEach(dataManager.recoveryJourneys) { journey in 172→ JourneyMiniCard(journey: journey) 173→ } 174→ } 175→ } 176→ } 177→ 178→ // Today's check-in status 179→ TodayCheckInCard() 180→ 181→ // Blocklist stats 182→ BlocklistStatsCard() 183→ 184→ // Time Lock status 185→ if let profile = dataManager.userProfile, profile.isTimeLockActive { 186→ TimeLockStatusCard(profile: profile) 187→ } 188→ 189→ // Recent activity 190→ RecentActivityCard() 191→ 192→ // Monitoring status 193→ MonitoringStatusCard() 194→ } 195→ .padding(24) 196→ } 197→ .background(Color(nsColor: .windowBackgroundColor)) 198→ .navigationTitle("Dashboard") 199→ .onReceive(timer) { _ in 200→ currentTime = Date() 201→ } 202→ } 203→ 204→ private func formatNumber(_ number: Int) -> String { 205→ if number >= 1000 { 206→ return String(format: "%.1fK", Double(number) / 1000.0) 207→ } 208→ return "\(number)" 209→ } 210→} 211→ 212→// MARK: - Sobriety Counter Card 213→ 214→struct SobrietyCounterCard: View { 215→ @EnvironmentObject var dataManager: DataManager 216→ 217→ var body: some View { 218→ let totalDays = dataManager.getTotalCleanDays() 219→ 220→ VStack(spacing: 16) { 221→ Text("DIAS LIMPO") 222→ .font(.caption) 223→ .fontWeight(.semibold) 224→ .foregroundColor(.secondary) 225→ .tracking(2) 226→ 227→ Text("\(totalDays)") 228→ .font(.system(size: 120, weight: .bold, design: .rounded)) 229→ .foregroundStyle( 230→ LinearGradient( 231→ colors: [.green, .mint], 232→ startPoint: .topLeading, 233→ endPoint: .bottomTrailing 234→ ) 235→ ) 236→ 237→ if totalDays > 0 { 238→ Text(streakMessage(days: totalDays)) 239→ .font(.headline) 240→ .foregroundColor(.secondary) 241→ } 242→ 243→ // Next milestone 244→ if let journey = dataManager.recoveryJourneys.first, 245→ let nextMilestone = journey.nextMilestone { 246→ HStack { 247→ Image(systemName: nextMilestone.icon) 248→ .foregroundColor(.yellow) 249→ Text("\(journey.daysToNextMilestone) dias para: \(nextMilestone.titlePT)") 250→ .font(.subheadline) 251→ .foregroundColor(.secondary) 252→ } 253→ .padding(.top, 8) 254→ } 255→ } 256→ .frame(maxWidth: .infinity) 257→ .padding(32) 258→ .background( 259→ RoundedRectangle(cornerRadius: 20) 260→ .fill(.ultraThinMaterial) 261→ ) 262→ } 263→ 264→ private func streakMessage(days: Int) -> String { 265→ switch days { 266→ case 1: return "Primeiro dia! O mais difícil." 267→ case 2...6: return "Ótimo começo! Continue assim." 268→ case 7...13: return "Uma semana! Você é incrível." 269→ case 14...29: return "Duas semanas de força!" 270→ case 30...59: return "Um mês! Isso é sério." 271→ case 60...89: return "Dois meses de determinação!" 272→ case 90...179: return "90 dias! Você é um guerreiro." 273→ case 180...364: return "Meio ano de liberdade!" 274→ default: return "Mais de um ano! Lendário." 275→ } 276→ } 277→} 278→ 279→// MARK: - Quick Stat Card 280→ 281→struct QuickStatCard: View { 282→ let title: String 283→ let value: String 284→ let subtitle: String 285→ let icon: String 286→ let color: Color 287→ 288→ var body: some View { 289→ VStack(alignment: .leading, spacing: 8) { 290→ HStack { 291→ Image(systemName: icon) 292→ .foregroundColor(color) 293→ Text(title) 294→ .font(.caption) 295→ .foregroundColor(.secondary) 296→ } 297→ 298→ Text(value) 299→ .font(.title) 300→ .fontWeight(.bold) 301→ 302→ Text(subtitle) 303→ .font(.caption2) 304→ .foregroundColor(.secondary) 305→ } 306→ .frame(maxWidth: .infinity, alignment: .leading) 307→ .padding() 308→ .background( 309→ RoundedRectangle(cornerRadius: 12) 310→ .fill(.ultraThinMaterial) 311→ ) 312→ } 313→} 314→ 315→// MARK: - Journey Mini Card 316→ 317→struct JourneyMiniCard: View { 318→ let journey: RecoveryJourney 319→ 320→ var body: some View { 321→ VStack(alignment: .leading, spacing: 8) { 322→ HStack { 323→ Image(systemName: journey.viceType.icon) 324→ .foregroundColor(journey.viceType.color) 325→ Text(journey.viceType.displayName) 326→ .font(.caption) 327→ .fontWeight(.medium) 328→ } 329→ 330→ Text("\(journey.currentStreak)") 331→ .font(.title2) 332→ .fontWeight(.bold) 333→ 334→ Text("dias") 335→ .font(.caption2) 336→ .foregroundColor(.secondary) 337→ } 338→ .frame(maxWidth: .infinity, alignment: .leading) 339→ .padding() 340→ .background( 341→ RoundedRectangle(cornerRadius: 12) 342→ .fill(.ultraThinMaterial) 343→ .overlay( 344→ RoundedRectangle(cornerRadius: 12) 345→ .stroke(journey.viceType.color.opacity(0.3), lineWidth: 1) 346→ ) 347→ ) 348→ } 349→} 350→ 351→// MARK: - Today Check-In Card 352→ 353→struct TodayCheckInCard: View { 354→ @EnvironmentObject var dataManager: DataManager 355→ 356→ var body: some View { 357→ HStack { 358→ VStack(alignment: .leading, spacing: 4) { 359→ Text("Check-in de Hoje") 360→ .font(.headline) 361→ 362→ if let checkIn = dataManager.todayCheckIn, checkIn.isCompleted { 363→ HStack { 364→ Image(systemName: "checkmark.circle.fill") 365→ .foregroundColor(.green) 366→ Text("Concluído") 367→ .foregroundColor(.secondary) 368→ } 369→ } else { 370→ HStack { 371→ Image(systemName: "circle") 372→ .foregroundColor(.orange) 373→ Text("Pendente") 374→ .foregroundColor(.secondary) 375→ } 376→ } 377→ } 378→ 379→ Spacer() 380→ 381→ if dataManager.todayCheckIn?.isCompleted != true { 382→ Button(action: { 383→ NotificationCenter.default.post(name: .showCheckIn, object: nil) 384→ }) { 385→ Text("Fazer Check-in") 386→ .fontWeight(.medium) 387→ } 388→ .buttonStyle(.borderedProminent) 389→ } 390→ } 391→ .padding() 392→ .background( 393→ RoundedRectangle(cornerRadius: 12) 394→ .fill(.ultraThinMaterial) 395→ ) 396→ } 397→} 398→ 399→// MARK: - Time Lock Status Card 400→ 401→struct TimeLockStatusCard: View { 402→ let profile: UserProfile 403→ 404→ var body: some View { 405→ HStack { 406→ Image(systemName: "lock.shield.fill") 407→ .font(.title2) 408→ .foregroundColor(.blue) 409→ 410→ VStack(alignment: .leading, spacing: 4) { 411→ Text("Time Lock Ativo") 412→ .font(.headline) 413→ 414→ Text("\(profile.timeLockDaysRemaining) dias restantes") 415→ .font(.subheadline) 416→ .foregroundColor(.secondary) 417→ } 418→ 419→ Spacer() 420→ 421→ // Progress ring 422→ ZStack { 423→ Circle() 424→ .stroke(Color.gray.opacity(0.2), lineWidth: 4) 425→ .frame(width: 40, height: 40) 426→ 427→ Circle() 428→ .trim(from: 0, to: profile.timeLockProgress) 429→ .stroke(Color.blue, lineWidth: 4) 430→ .frame(width: 40, height: 40) 431→ .rotationEffect(.degrees(-90)) 432→ 433→ Text("\(Int(profile.timeLockProgress * 100))%") 434→ .font(.caption2) 435→ .fontWeight(.bold) 436→ } 437→ } 438→ .padding() 439→ .background( 440→ RoundedRectangle(cornerRadius: 12) 441→ .fill(.blue.opacity(0.1)) 442→ ) 443→ } 444→} 445→ 446→// MARK: - Recent Activity Card 447→ 448→struct RecentActivityCard: View { 449→ @EnvironmentObject var dataManager: DataManager 450→ 451→ var body: some View { 452→ VStack(alignment: .leading, spacing: 12) { 453→ Text("Atividade Recente") 454→ .font(.headline) 455→ 456→ if dataManager.recentCheckIns.isEmpty { 457→ Text("Nenhuma atividade ainda") 458→ .font(.subheadline) 459→ .foregroundColor(.secondary) 460→ .frame(maxWidth: .infinity, alignment: .center) 461→ .padding() 462→ } else { 463→ ForEach(dataManager.recentCheckIns.prefix(5)) { checkIn in 464→ HStack { 465→ Image(systemName: checkIn.isCompleted ? "checkmark.circle.fill" : "circle") 466→ .foregroundColor(checkIn.isCompleted ? .green : .gray) 467→ 468→ VStack(alignment: .leading) { 469→ Text(formatDate(checkIn.date)) 470→ .font(.subheadline) 471→ 472→ HStack { 473→ Text("Humor: \(checkIn.moodScore)/5") 474→ if checkIn.hadCravings { 475→ Text("• Craving: \(checkIn.cravingIntensity)/10") 476→ .foregroundColor(.orange) 477→ } 478→ } 479→ .font(.caption) 480→ .foregroundColor(.secondary) 481→ } 482→ 483→ Spacer() 484→ 485→ if checkIn.isHighRisk { 486→ Image(systemName: "exclamationmark.triangle.fill") 487→ .foregroundColor(.red) 488→ } 489→ } 490→ .padding(.vertical, 4) 491→ } 492→ } 493→ } 494→ .padding() 495→ .background( 496→ RoundedRectangle(cornerRadius: 12) 497→ .fill(.ultraThinMaterial) 498→ ) 499→ } 500→ 501→ private func formatDate(_ date: Date) -> String { 502→ let formatter = DateFormatter() 503→ formatter.dateStyle = .medium 504→ formatter.locale = Locale(identifier: "pt_BR") 505→ return formatter.string(from: date) 506→ } 507→} 508→ 509→// MARK: - Daily Wisdom Card 510→ 511→struct DailyWisdomCard: View { 512→ @State private var quote = WisdomQuote.quoteOfTheDay() 513→ @State private var showingAllCategories = false 514→ 515→ var body: some View { 516→ VStack(spacing: 16) { 517→ // Category badge 518→ HStack { 519→ Image(systemName: quote.category.icon) 520→ .foregroundColor(quote.category.color) 521→ Text(quote.category.rawValue) 522→ .font(.caption) 523→ .fontWeight(.semibold) 524→ .foregroundColor(quote.category.color) 525→ 526→ Spacer() 527→ 528→ Button(action: { 529→ withAnimation(.spring(response: 0.3)) { 530→ // Cycle through categories 531→ let categories = WisdomQuote.QuoteCategory.allCases 532→ if let currentIndex = categories.firstIndex(of: quote.category) { 533→ let nextIndex = (currentIndex + 1) % categories.count 534→ quote = WisdomQuote.randomQuote(from: categories[nextIndex]) 535→ } 536→ } 537→ }) { 538→ Image(systemName: "arrow.triangle.2.circlepath") 539→ .font(.caption) 540→ .foregroundColor(.secondary) 541→ } 542→ .buttonStyle(.plain) 543→ .help("Próxima categoria") 544→ } 545→ 546→ // Quote text 547→ Text("\"\(quote.text)\"") 548→ .font(.body) 549→ .italic() 550→ .multilineTextAlignment(.center) 551→ .foregroundColor(.primary) 552→ .fixedSize(horizontal: false, vertical: true) 553→ 554→ // Author 555→ Text("— \(quote.author)") 556→ .font(.caption) 557→ .fontWeight(.medium) 558→ .foregroundColor(.secondary) 559→ } 560→ .padding(20) 561→ .frame(maxWidth: .infinity) 562→ .background( 563→ RoundedRectangle(cornerRadius: 16) 564→ .fill( 565→ LinearGradient( 566→ colors: [ 567→ quote.category.color.opacity(0.1), 568→ quote.category.color.opacity(0.05) 569→ ], 570→ startPoint: .topLeading, 571→ endPoint: .bottomTrailing 572→ ) 573→ ) 574→ .overlay( 575→ RoundedRectangle(cornerRadius: 16) 576→ .stroke(quote.category.color.opacity(0.2), lineWidth: 1) 577→ ) 578→ ) 579→ } 580→} 581→ 582→// MARK: - Blocklist Stats Card 583→ 584→struct BlocklistStatsCard: View { 585→ private let stats = BlocklistManager.shared.getStats() 586→ 587→ var body: some View { 588→ VStack(alignment: .leading, spacing: 12) { 589→ HStack { 590→ Image(systemName: "shield.checkered") 591→ .foregroundColor(.blue) 592→ Text("Proteção Ativa") 593→ .font(.headline) 594→ } 595→ 596→ HStack(spacing: 20) { 597→ StatItem( 598→ icon: "eye.slash.fill", 599→ value: "\(stats.adultDomainsCount)", 600→ label: "Sites Adultos", 601→ color: .red 602→ ) 603→ 604→ StatItem( 605→ icon: "pills.fill", 606→ value: "\(stats.drugDomainsCount)", 607→ label: "Sites Drogas", 608→ color: .purple 609→ ) 610→ 611→ StatItem( 612→ icon: "dice.fill", 613→ value: "\(stats.gamblingDomainsCount)", 614→ label: "Sites Gambling", 615→ color: .orange 616→ ) 617→ 618→ StatItem( 619→ icon: "app.badge.checkmark", 620→ value: "\(stats.riskyAppsCount)", 621→ label: "Apps Risco", 622→ color: .yellow 623→ ) 624→ } 625→ 626→ Divider() 627→ 628→ HStack { 629→ Image(systemName: "key.fill") 630→ .foregroundColor(.green) 631→ .font(.caption) 632→ Text("\(stats.totalKeywords) keywords monitoradas") 633→ .font(.caption) 634→ .foregroundColor(.secondary) 635→ 636→ Spacer() 637→ 638→ Text("\(stats.totalDomains) domínios bloqueados") 639→ .font(.caption) 640→ .foregroundColor(.secondary) 641→ } 642→ } 643→ .padding() 644→ .background( 645→ RoundedRectangle(cornerRadius: 12) 646→ .fill(.ultraThinMaterial) 647→ ) 648→ } 649→} 650→ 651→struct StatItem: View { 652→ let icon: String 653→ let value: String 654→ let label: String 655→ let color: Color 656→ 657→ var body: some View { 658→ VStack(spacing: 4) { 659→ Image(systemName: icon) 660→ .font(.title3) 661→ .foregroundColor(color) 662→ 663→ Text(value) 664→ .font(.title2) 665→ .fontWeight(.bold) 666→ 667→ Text(label) 668→ .font(.caption2) 669→ .foregroundColor(.secondary) 670→ .multilineTextAlignment(.center) 671→ } 672→ .frame(maxWidth: .infinity) 673→ } 674→} 675→ 676→// MARK: - Monitoring Status Card 677→ 678→struct MonitoringStatusCard: View { 679→ @State private var isMonitoring = true 680→ 681→ var body: some View { 682→ HStack { 683→ // Status indicator 684→ Circle() 685→ .fill(isMonitoring ? Color.green : Color.red) 686→ .frame(width: 12, height: 12) 687→ .overlay( 688→ Circle() 689→ .stroke(isMonitoring ? Color.green.opacity(0.3) : Color.red.opacity(0.3), lineWidth: 4) 690→ ) 691→ 692→ VStack(alignment: .leading, spacing: 2) { 693→ Text("Monitoramento 24/7") 694→ .font(.headline) 695→ 696→ Text(isMonitoring ? "Ativo - Protegendo você" : "Inativo") 697→ .font(.caption) 698→ .foregroundColor(isMonitoring ? .green : .red) 699→ } 700→ 701→ Spacer() 702→ 703→ VStack(alignment: .trailing, spacing: 4) { 704→ HStack(spacing: 12) { 705→ MonitoringFeature(icon: "globe", label: "Sites", active: true) 706→ MonitoringFeature(icon: "app.badge", label: "Apps", active: true) 707→ MonitoringFeature(icon: "bell.badge", label: "Alertas", active: true) 708→ } 709→ } 710→ } 711→ .padding() 712→ .background( 713→ RoundedRectangle(cornerRadius: 12) 714→ .fill(isMonitoring ? Color.green.opacity(0.1) : Color.red.opacity(0.1)) 715→ .overlay( 716→ RoundedRectangle(cornerRadius: 12) 717→ .stroke(isMonitoring ? Color.green.opacity(0.3) : Color.red.opacity(0.3), lineWidth: 1) 718→ ) 719→ ) 720→ } 721→} 722→ 723→struct MonitoringFeature: View { 724→ let icon: String 725→ let label: String 726→ let active: Bool 727→ 728→ var body: some View { 729→ VStack(spacing: 2) { 730→ Image(systemName: active ? "\(icon).fill" : icon) 731→ .font(.caption) 732→ .foregroundColor(active ? .green : .gray) 733→ 734→ Text(label) 735→ .font(.caption2) 736→ .foregroundColor(.secondary) 737→ } 738→ } 739→} 740→ 741→#Preview { 742→ DashboardView() 743→ .environmentObject(DataManager.shared) 744→} 745→ 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.