1→import SwiftUI 2→import Charts 3→ 4→struct BudgetView: View { 5→ @StateObject private var budgetManager = BudgetManager.shared 6→ @StateObject private var aiService = ClaudeAIService.shared 7→ @State private var showingAddCategory = false 8→ @State private var showingEditLimit = false 9→ @State private var selectedCategory: BudgetCategory? 10→ @State private var aiSuggestions: [ClaudeAIService.BudgetSuggestion] = [] 11→ @State private var isLoadingAISuggestions = false 12→ 13→ var body: some View { 14→ ScrollView { 15→ VStack(spacing: 24) { 16→ // Header 17→ headerSection 18→ 19→ // AI Suggestions Section 20→ aiSuggestionsSection 21→ 22→ // Overview 23→ overviewSection 24→ 25→ // Health indicator 26→ healthSection 27→ 28→ // Category breakdown chart 29→ if !budgetManager.activeCategories.isEmpty { 30→ chartSection 31→ } 32→ 33→ // Categories list 34→ categoriesSection 35→ 36→ // Alerts 37→ if !budgetManager.categoriesNeedingAttention.isEmpty { 38→ alertsSection 39→ } 40→ } 41→ .padding(24) 42→ } 43→ .background(Color(NSColor.windowBackgroundColor)) 44→ .navigationTitle("Orcamento Mensal") 45→ .sheet(isPresented: $showingAddCategory) { 46→ AddBudgetCategorySheet(budgetManager: budgetManager) 47→ } 48→ .sheet(item: $selectedCategory) { category in 49→ EditBudgetCategorySheet(category: category, budgetManager: budgetManager) 50→ } 51→ .onAppear { 52→ budgetManager.updateSpentFromTransactions() 53→ } 54→ } 55→ 56→ // MARK: - Header 57→ private var headerSection: some View { 58→ HStack { 59→ VStack(alignment: .leading, spacing: 4) { 60→ HStack { 61→ Image(systemName: "chart.pie.fill") 62→ .font(.title) 63→ .foregroundColor(.indigo) 64→ Text("Orcamento Mensal") 65→ .font(.title2) 66→ .fontWeight(.bold) 67→ } 68→ Text(budgetManager.currentBudget.monthName) 69→ .font(.subheadline) 70→ .foregroundColor(.secondary) 71→ } 72→ 73→ Spacer() 74→ 75→ HStack(spacing: 12) { 76→ Button { 77→ budgetManager.updateSpentFromTransactions() 78→ } label: { 79→ Image(systemName: "arrow.clockwise") 80→ } 81→ .help("Atualizar gastos") 82→ 83→ Button { 84→ showingAddCategory = true 85→ } label: { 86→ Label("Categoria", systemImage: "plus") 87→ } 88→ .buttonStyle(.borderedProminent) 89→ } 90→ } 91→ } 92→ 93→ // MARK: - Overview Section 94→ private var overviewSection: some View { 95→ HStack(spacing: 16) { 96→ // Total budget card 97→ VStack(alignment: .leading, spacing: 8) { 98→ Text("Orcamento Total") 99→ .font(.subheadline) 100→ .foregroundColor(.secondary) 101→ 102→ Text(budgetManager.currentBudget.totalLimit.toBRL()) 103→ .font(.title) 104→ .fontWeight(.bold) 105→ .foregroundColor(.indigo) 106→ 107→ Text("\(budgetManager.activeCategories.count) categorias") 108→ .font(.caption) 109→ .foregroundColor(.secondary) 110→ } 111→ .frame(maxWidth: .infinity, alignment: .leading) 112→ .padding() 113→ .background(Color(NSColor.controlBackgroundColor)) 114→ .cornerRadius(12) 115→ 116→ // Spent card 117→ VStack(alignment: .leading, spacing: 8) { 118→ Text("Gasto Atual") 119→ .font(.subheadline) 120→ .foregroundColor(.secondary) 121→ 122→ Text(budgetManager.currentBudget.totalSpent.toBRL()) 123→ .font(.title) 124→ .fontWeight(.bold) 125→ .foregroundColor(budgetManager.currentBudget.totalSpent > budgetManager.currentBudget.totalLimit ? .red : .orange) 126→ 127→ Text(String(format: "%.0f%% do orcamento", budgetManager.currentBudget.overallProgress * 100)) 128→ .font(.caption) 129→ .foregroundColor(.secondary) 130→ } 131→ .frame(maxWidth: .infinity, alignment: .leading) 132→ .padding() 133→ .background(Color(NSColor.controlBackgroundColor)) 134→ .cornerRadius(12) 135→ 136→ // Remaining card 137→ VStack(alignment: .leading, spacing: 8) { 138→ Text("Disponivel") 139→ .font(.subheadline) 140→ .foregroundColor(.secondary) 141→ 142→ Text(budgetManager.currentBudget.totalRemaining.toBRL()) 143→ .font(.title) 144→ .fontWeight(.bold) 145→ .foregroundColor(.green) 146→ 147→ if budgetManager.currentBudget.categoriesOverBudget > 0 { 148→ Text("\(budgetManager.currentBudget.categoriesOverBudget) estouradas") 149→ .font(.caption) 150→ .foregroundColor(.red) 151→ } else { 152→ Text("Todas no limite") 153→ .font(.caption) 154→ .foregroundColor(.green) 155→ } 156→ } 157→ .frame(maxWidth: .infinity, alignment: .leading) 158→ .padding() 159→ .background(Color(NSColor.controlBackgroundColor)) 160→ .cornerRadius(12) 161→ } 162→ } 163→ 164→ // MARK: - Health Section 165→ private var healthSection: some View { 166→ VStack(spacing: 12) { 167→ HStack { 168→ Text("Saude do Orcamento") 169→ .font(.headline) 170→ Spacer() 171→ 172→ HStack(spacing: 8) { 173→ Text("\(budgetManager.healthScore)%") 174→ .font(.title2) 175→ .fontWeight(.bold) 176→ .foregroundColor(budgetManager.healthColor) 177→ 178→ Text(budgetManager.healthStatus) 179→ .font(.subheadline) 180→ .foregroundColor(.secondary) 181→ } 182→ } 183→ 184→ // Progress bar 185→ GeometryReader { geometry in 186→ ZStack(alignment: .leading) { 187→ RoundedRectangle(cornerRadius: 8) 188→ .fill(Color.gray.opacity(0.2)) 189→ 190→ RoundedRectangle(cornerRadius: 8) 191→ .fill( 192→ LinearGradient( 193→ colors: [budgetManager.healthColor, budgetManager.healthColor.opacity(0.7)], 194→ startPoint: .leading, 195→ endPoint: .trailing 196→ ) 197→ ) 198→ .frame(width: geometry.size.width * (Double(budgetManager.healthScore) / 100)) 199→ } 200→ } 201→ .frame(height: 12) 202→ } 203→ .padding() 204→ .background(Color(NSColor.controlBackgroundColor)) 205→ .cornerRadius(12) 206→ } 207→ 208→ // MARK: - Chart Section 209→ private var chartSection: some View { 210→ VStack(alignment: .leading, spacing: 16) { 211→ Text("Distribuicao por Categoria") 212→ .font(.headline) 213→ 214→ Chart { 215→ ForEach(budgetManager.activeCategories) { category in 216→ BarMark( 217→ x: .value("Categoria", category.category.rawValue), 218→ y: .value("Gasto", category.spent) 219→ ) 220→ .foregroundStyle(category.status.color.gradient) 221→ 222→ // Limit marker as a thin bar 223→ BarMark( 224→ x: .value("Categoria", category.category.rawValue), 225→ y: .value("Limite", category.limit), 226→ width: .fixed(30) 227→ ) 228→ .foregroundStyle(Color.gray.opacity(0.3)) 229→ } 230→ } 231→ .frame(height: 200) 232→ .chartYAxis { 233→ AxisMarks(position: .leading) { value in 234→ AxisValueLabel { 235→ if let val = value.as(Double.self) { 236→ Text(val.toBRLCompact()) 237→ .font(.caption2) 238→ } 239→ } 240→ } 241→ } 242→ 243→ // Legend 244→ HStack(spacing: 16) { 245→ HStack(spacing: 4) { 246→ Rectangle() 247→ .fill(Color.green) 248→ .frame(width: 12, height: 12) 249→ .cornerRadius(2) 250→ Text("Gasto") 251→ .font(.caption) 252→ .foregroundColor(.secondary) 253→ } 254→ 255→ HStack(spacing: 4) { 256→ Rectangle() 257→ .stroke(Color.gray, style: StrokeStyle(lineWidth: 2, dash: [4, 2])) 258→ .frame(width: 20, height: 2) 259→ Text("Limite") 260→ .font(.caption) 261→ .foregroundColor(.secondary) 262→ } 263→ } 264→ } 265→ .padding() 266→ .background(Color(NSColor.controlBackgroundColor)) 267→ .cornerRadius(12) 268→ } 269→ 270→ // MARK: - Categories Section 271→ private var categoriesSection: some View { 272→ VStack(alignment: .leading, spacing: 16) { 273→ HStack { 274→ Text("Categorias") 275→ .font(.headline) 276→ Spacer() 277→ Text("\(budgetManager.activeCategories.count) ativas") 278→ .font(.subheadline) 279→ .foregroundColor(.secondary) 280→ } 281→ 282→ LazyVStack(spacing: 12) { 283→ ForEach(budgetManager.activeCategories) { category in 284→ BudgetCategoryRow(category: category) { 285→ selectedCategory = category 286→ } 287→ } 288→ } 289→ } 290→ .padding() 291→ .background(Color(NSColor.controlBackgroundColor)) 292→ .cornerRadius(12) 293→ } 294→ 295→ // MARK: - Alerts Section 296→ private var alertsSection: some View { 297→ VStack(alignment: .leading, spacing: 12) { 298→ HStack { 299→ Image(systemName: "exclamationmark.triangle.fill") 300→ .foregroundColor(.orange) 301→ Text("Categorias que Precisam de Atencao") 302→ .font(.headline) 303→ } 304→ 305→ ForEach(budgetManager.categoriesNeedingAttention) { category in 306→ HStack { 307→ Image(systemName: category.category.icon) 308→ .foregroundColor(category.status.color) 309→ 310→ Text(category.category.rawValue) 311→ .fontWeight(.medium) 312→ 313→ Spacer() 314→ 315→ if category.isOverBudget { 316→ Text("+\(category.overAmount.toBRL())") 317→ .foregroundColor(.red) 318→ .fontWeight(.semibold) 319→ } else { 320→ Text("\(category.progressPercentage)") 321→ .foregroundColor(.orange) 322→ } 323→ } 324→ .padding(.vertical, 4) 325→ } 326→ } 327→ .padding() 328→ .background(Color.orange.opacity(0.1)) 329→ .cornerRadius(12) 330→ } 331→ 332→ // MARK: - AI Suggestions Section 333→ private var aiSuggestionsSection: some View { 334→ VStack(alignment: .leading, spacing: 16) { 335→ HStack { 336→ HStack(spacing: 8) { 337→ Image(systemName: "brain.head.profile") 338→ .font(.title2) 339→ .foregroundStyle( 340→ LinearGradient( 341→ colors: [.cyan, .purple], 342→ startPoint: .topLeading, 343→ endPoint: .bottomTrailing 344→ ) 345→ ) 346→ 347→ VStack(alignment: .leading, spacing: 2) { 348→ Text("Sugestoes da IA") 349→ .font(.headline) 350→ Text("Otimizacao do orcamento") 351→ .font(.caption) 352→ .foregroundColor(.secondary) 353→ } 354→ } 355→ 356→ Spacer() 357→ 358→ Button { 359→ loadAISuggestions() 360→ } label: { 361→ HStack(spacing: 4) { 362→ if isLoadingAISuggestions { 363→ ProgressView() 364→ .scaleEffect(0.7) 365→ } else { 366→ Image(systemName: "sparkles") 367→ } 368→ Text("Analisar") 369→ } 370→ } 371→ .buttonStyle(.borderedProminent) 372→ .tint(.cyan) 373→ .disabled(isLoadingAISuggestions) 374→ } 375→ 376→ if isLoadingAISuggestions { 377→ HStack { 378→ Spacer() 379→ VStack(spacing: 8) { 380→ ProgressView() 381→ Text("Analisando seu orcamento...") 382→ .font(.caption) 383→ .foregroundColor(.secondary) 384→ } 385→ .padding() 386→ Spacer() 387→ } 388→ } else if aiSuggestions.isEmpty { 389→ HStack { 390→ Spacer() 391→ VStack(spacing: 8) { 392→ Image(systemName: "lightbulb.fill") 393→ .font(.largeTitle) 394→ .foregroundColor(.gray.opacity(0.5)) 395→ Text("Clique em Analisar para receber sugestoes") 396→ .font(.callout) 397→ .foregroundColor(.secondary) 398→ } 399→ .padding() 400→ Spacer() 401→ } 402→ } else { 403→ VStack(spacing: 12) { 404→ ForEach(aiSuggestions, id: \.category) { suggestion in 405→ AISuggestionRow(suggestion: suggestion) { 406→ applySuggestion(suggestion) 407→ } 408→ } 409→ } 410→ } 411→ } 412→ .padding() 413→ .background( 414→ RoundedRectangle(cornerRadius: 12) 415→ .fill(Color(NSColor.controlBackgroundColor)) 416→ .overlay( 417→ RoundedRectangle(cornerRadius: 12) 418→ .strokeBorder( 419→ LinearGradient( 420→ colors: [.cyan.opacity(0.3), .purple.opacity(0.3)], 421→ startPoint: .topLeading, 422→ endPoint: .bottomTrailing 423→ ), 424→ lineWidth: 1 425→ ) 426→ ) 427→ ) 428→ } 429→ 430→ private func loadAISuggestions() { 431→ isLoadingAISuggestions = true 432→ Task { 433→ do { 434→ let suggestions = try await aiService.suggestBudgetAdjustments() 435→ await MainActor.run { 436→ aiSuggestions = suggestions 437→ isLoadingAISuggestions = false 438→ } 439→ } catch { 440→ await MainActor.run { 441→ isLoadingAISuggestions = false 442→ } 443→ } 444→ } 445→ } 446→ 447→ private func applySuggestion(_ suggestion: ClaudeAIService.BudgetSuggestion) { 448→ // Find the category and update its limit 449→ if let category = budgetManager.activeCategories.first(where: { 450→ $0.category.rawValue == suggestion.category 451→ }) { 452→ budgetManager.updateCategoryLimit(categoryId: category.id, newLimit: suggestion.suggestedLimit) 453→ // Remove applied suggestion 454→ aiSuggestions.removeAll { $0.category == suggestion.category } 455→ } 456→ } 457→} 458→ 459→// MARK: - AI Suggestion Row 460→struct AISuggestionRow: View { 461→ let suggestion: ClaudeAIService.BudgetSuggestion 462→ let onApply: () -> Void 463→ 464→ @State private var isHovered = false 465→ 466→ var difference: Double { 467→ suggestion.suggestedLimit - suggestion.currentLimit 468→ } 469→ 470→ var isIncrease: Bool { 471→ difference > 0 472→ } 473→ 474→ var body: some View { 475→ HStack(spacing: 12) { 476→ // Indicator 477→ ZStack { 478→ Circle() 479→ .fill(isIncrease ? Color.orange.opacity(0.15) : Color.green.opacity(0.15)) 480→ .frame(width: 36, height: 36) 481→ 482→ Image(systemName: isIncrease ? "arrow.up.circle.fill" : "arrow.down.circle.fill") 483→ .foregroundColor(isIncrease ? .orange : .green) 484→ } 485→ 486→ VStack(alignment: .leading, spacing: 4) { 487→ Text(suggestion.category) 488→ .font(.subheadline) 489→ .fontWeight(.medium) 490→ 491→ Text(suggestion.reason) 492→ .font(.caption) 493→ .foregroundColor(.secondary) 494→ .lineLimit(2) 495→ } 496→ 497→ Spacer() 498→ 499→ VStack(alignment: .trailing, spacing: 4) { 500→ HStack(spacing: 4) { 501→ Text(suggestion.currentLimit.toBRL()) 502→ .font(.caption) 503→ .foregroundColor(.secondary) 504→ .strikethrough() 505→ 506→ Image(systemName: "arrow.right") 507→ .font(.caption2) 508→ .foregroundColor(.secondary) 509→ 510→ Text(suggestion.suggestedLimit.toBRL()) 511→ .font(.subheadline) 512→ .fontWeight(.semibold) 513→ .foregroundColor(isIncrease ? .orange : .green) 514→ } 515→ 516→ Text(isIncrease ? "+\(abs(difference).toBRL())" : "-\(abs(difference).toBRL())") 517→ .font(.caption) 518→ .foregroundColor(isIncrease ? .orange : .green) 519→ } 520→ 521→ Button("Aplicar") { 522→ onApply() 523→ } 524→ .buttonStyle(.bordered) 525→ .controlSize(.small) 526→ .opacity(isHovered ? 1 : 0.7) 527→ } 528→ .padding() 529→ .background( 530→ RoundedRectangle(cornerRadius: 8) 531→ .fill(isHovered ? Color(NSColor.windowBackgroundColor) : Color.clear) 532→ ) 533→ .onHover { hovering in 534→ isHovered = hovering 535→ } 536→ } 537→} 538→ 539→// MARK: - Budget Category Row 540→struct BudgetCategoryRow: View { 541→ let category: BudgetCategory 542→ let onEdit: () -> Void 543→ 544→ @State private var isHovering = false 545→ 546→ var body: some View { 547→ VStack(spacing: 12) { 548→ HStack { 549→ // Icon and name 550→ HStack(spacing: 12) { 551→ Image(systemName: category.category.icon) 552→ .font(.title3) 553→ .foregroundColor(category.status.color) 554→ .frame(width: 32) 555→ 556→ VStack(alignment: .leading, spacing: 2) { 557→ Text(category.category.rawValue) 558→ .fontWeight(.medium) 559→ 560→ Text("\(category.spent.toBRL()) de \(category.limit.toBRL())") 561→ .font(.caption) 562→ .foregroundColor(.secondary) 563→ } 564→ } 565→ 566→ Spacer() 567→ 568→ // Status and percentage 569→ HStack(spacing: 12) { 570→ VStack(alignment: .trailing, spacing: 2) { 571→ Text(category.progressPercentage) 572→ .font(.headline) 573→ .foregroundColor(category.status.color) 574→ 575→ Text(category.remaining.toBRL() + " restante") 576→ .font(.caption) 577→ .foregroundColor(.secondary) 578→ } 579→ 580→ Image(systemName: category.status.icon) 581→ .foregroundColor(category.status.color) 582→ 583→ if isHovering { 584→ Button { 585→ onEdit() 586→ } label: { 587→ Image(systemName: "pencil.circle.fill") 588→ .foregroundColor(.blue) 589→ } 590→ .buttonStyle(.plain) 591→ } 592→ } 593→ } 594→ 595→ // Progress bar 596→ GeometryReader { geometry in 597→ ZStack(alignment: .leading) { 598→ RoundedRectangle(cornerRadius: 4) 599→ .fill(Color.gray.opacity(0.2)) 600→ 601→ RoundedRectangle(cornerRadius: 4) 602→ .fill(category.status.color) 603→ .frame(width: min(geometry.size.width * category.progress, geometry.size.width)) 604→ } 605→ } 606→ .frame(height: 8) 607→ } 608→ .padding() 609→ .background(category.isOverBudget ? Color.red.opacity(0.05) : Color(NSColor.windowBackgroundColor)) 610→ .cornerRadius(8) 611→ .onHover { hovering in 612→ isHovering = hovering 613→ } 614→ } 615→} 616→ 617→// MARK: - Add Budget Category Sheet 618→struct AddBudgetCategorySheet: View { 619→ @ObservedObject var budgetManager: BudgetManager 620→ @Environment(\.dismiss) private var dismiss 621→ 622→ @State private var selectedCategory: ExpenseCategory = .other 623→ @State private var limit = "" 624→ 625→ var availableCategories: [ExpenseCategory] { 626→ let existingCategories = Set(budgetManager.currentBudget.categories.map { $0.category }) 627→ return ExpenseCategory.allCases.filter { !existingCategories.contains($0) } 628→ } 629→ 630→ var body: some View { 631→ VStack(spacing: 20) { 632→ Text("Nova Categoria") 633→ .font(.title2) 634→ .fontWeight(.bold) 635→ 636→ Form { 637→ Picker("Categoria", selection: $selectedCategory) { 638→ ForEach(availableCategories, id: \.self) { category in 639→ Label(category.rawValue, systemImage: category.icon).tag(category) 640→ } 641→ } 642→ 643→ TextField("Limite mensal (R$)", text: $limit) 644→ .font(.title3) 645→ } 646→ .formStyle(.grouped) 647→ 648→ HStack { 649→ Button("Cancelar") { dismiss() } 650→ .keyboardShortcut(.escape) 651→ 652→ Spacer() 653→ 654→ Button("Adicionar") { 655→ if let limitValue = Double(limit.replacingOccurrences(of: ",", with: ".")) { 656→ budgetManager.addCategory(selectedCategory, limit: limitValue) 657→ dismiss() 658→ } 659→ } 660→ .buttonStyle(.borderedProminent) 661→ .disabled(limit.isEmpty) 662→ } 663→ } 664→ .padding() 665→ .frame(width: 350, height: 280) 666→ .onAppear { 667→ if let first = availableCategories.first { 668→ selectedCategory = first 669→ } 670→ } 671→ } 672→} 673→ 674→// MARK: - Edit Budget Category Sheet 675→struct EditBudgetCategorySheet: View { 676→ let category: BudgetCategory 677→ @ObservedObject var budgetManager: BudgetManager 678→ @Environment(\.dismiss) private var dismiss 679→ 680→ @State private var limit: String 681→ @State private var showingDelete = false 682→ 683→ init(category: BudgetCategory, budgetManager: BudgetManager) { 684→ self.category = category 685→ self.budgetManager = budgetManager 686→ _limit = State(initialValue: String(format: "%.0f", category.limit)) 687→ } 688→ 689→ var body: some View { 690→ VStack(spacing: 20) { 691→ HStack { 692→ Image(systemName: category.category.icon) 693→ .font(.largeTitle) 694→ .foregroundColor(category.status.color) 695→ 696→ VStack(alignment: .leading) { 697→ Text(category.category.rawValue) 698→ .font(.title2) 699→ .fontWeight(.bold) 700→ Text("Editar limite") 701→ .foregroundColor(.secondary) 702→ } 703→ 704→ Spacer() 705→ } 706→ 707→ // Current status 708→ HStack(spacing: 24) { 709→ VStack { 710→ Text("Gasto") 711→ .font(.caption) 712→ .foregroundColor(.secondary) 713→ Text(category.spent.toBRL()) 714→ .font(.headline) 715→ .foregroundColor(.orange) 716→ } 717→ 718→ VStack { 719→ Text("Limite Atual") 720→ .font(.caption) 721→ .foregroundColor(.secondary) 722→ Text(category.limit.toBRL()) 723→ .font(.headline) 724→ } 725→ 726→ VStack { 727→ Text("Status") 728→ .font(.caption) 729→ .foregroundColor(.secondary) 730→ Text(category.status.rawValue) 731→ .font(.headline) 732→ .foregroundColor(category.status.color) 733→ } 734→ } 735→ 736→ Divider() 737→ 738→ // New limit 739→ VStack(alignment: .leading, spacing: 8) { 740→ Text("Novo Limite") 741→ .font(.subheadline) 742→ .foregroundColor(.secondary) 743→ 744→ HStack { 745→ Text("R$") 746→ .foregroundColor(.secondary) 747→ TextField("0", text: $limit) 748→ .textFieldStyle(.plain) 749→ .font(.title2) 750→ .fontWeight(.bold) 751→ } 752→ .padding() 753→ .background(Color(NSColor.controlBackgroundColor)) 754→ .cornerRadius(8) 755→ } 756→ 757→ Spacer() 758→ 759→ HStack { 760→ Button(role: .destructive) { 761→ showingDelete = true 762→ } label: { 763→ Label("Remover", systemImage: "trash") 764→ } 765→ 766→ Spacer() 767→ 768→ Button("Cancelar") { dismiss() } 769→ 770→ Button("Salvar") { 771→ if let newLimit = Double(limit.replacingOccurrences(of: ",", with: ".")) { 772→ budgetManager.updateCategoryLimit(categoryId: category.id, newLimit: newLimit) 773→ dismiss() 774→ } 775→ } 776→ .buttonStyle(.borderedProminent) 777→ } 778→ } 779→ .padding() 780→ .frame(width: 400, height: 380) 781→ .alert("Remover Categoria?", isPresented: $showingDelete) { 782→ Button("Cancelar", role: .cancel) { } 783→ Button("Remover", role: .destructive) { 784→ budgetManager.removeCategory(categoryId: category.id) 785→ dismiss() 786→ } 787→ } 788→ } 789→} 790→ 791→#Preview { 792→ BudgetView() 793→ .frame(width: 1000, height: 800) 794→} 795→ 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.