// ContentView.swift - Main Screen import SwiftUI struct ContentView: View { @State private var message = "Klik tombol untuk mengirim notifikasi" @State private var selectedConditions: Set = [] @State private var additionalNotes = "" @State private var showEvacuationRoute = false @FocusState private var isNotesFieldFocused: Bool let conditions = [ "🔥 Kebakaran", "⛈️ Banjir", "🌊 Tsunami", "🌋 Gunung Meletus", "🌏 Gempa Bumi", "👿 Huru hara", "🐍 Binatang Buas", "☢️ Radiasi Nuklir", "☣️ Biohazard" ] var body: some View { NavigationView { ScrollView { VStack(alignment: .leading, spacing: 16) { Text("Terjadi Kondisi Darurat") .font(.system(size: 20, weight: .bold)) .foregroundColor(.red) .padding(.bottom, 8) // Daftar kondisi dengan checkbox ForEach(conditions, id: \.self) { condition in HStack { Button(action: { isNotesFieldFocused = false toggleCondition(condition) }) { HStack { Image(systemName: selectedConditions.contains(condition) ? "checkmark.square.fill" : "square") .foregroundColor(selectedConditions.contains(condition) ? .blue : .gray) Text(condition) .foregroundColor(.primary) } } Spacer() } .padding(.vertical, 4) .contentShape(Rectangle()) .onTapGesture { isNotesFieldFocused = false toggleCondition(condition) } } Spacer().frame(height: 16) Text("Catatan tambahan:") .font(.system(size: 16)) ZStack(alignment: .topLeading) { TextEditor(text: $additionalNotes) .frame(height: 100) .padding(8) .scrollContentBackground(.hidden) .background(Color(.systemBackground)) .cornerRadius(4) .overlay( RoundedRectangle(cornerRadius: 4) .stroke(Color.gray, lineWidth: 1) ) .focused($isNotesFieldFocused) // Placeholder text if additionalNotes.isEmpty { Text("Tulis catatan di sini...") .foregroundColor(Color(.placeholderText)) .padding(.horizontal, 12) .padding(.vertical, 16) .allowsHitTesting(false) } } Spacer().frame(height: 16) Button(action: { isNotesFieldFocused = false sendReport() }) { Text("Kirim Laporan") .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(Color.red) .cornerRadius(8) } Spacer().frame(height: 16) Text("\"JANGAN PANIK! SEGERA EVAKUASI\nDIRI ANDA KE TITIK KUMPUL\"") .foregroundColor(.red) .font(.system(size: 15, weight: .medium)) .multilineTextAlignment(.center) .frame(maxWidth: .infinity) Spacer().frame(height: 16) Text(message) .padding(.top, 16) Button(action: { isNotesFieldFocused = false showEvacuationRoute = true }) { Text("Lihat Jalur Evakuasi") .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(Color.green) .cornerRadius(8) } } .padding(16) } .navigationBarHidden(true) .sheet(isPresented: $showEvacuationRoute) { EvacuationRouteView() } } } func toggleCondition(_ condition: String) { if selectedConditions.contains(condition) { selectedConditions.remove(condition) } else { selectedConditions.insert(condition) } } func sendReport() { let conditions = Array(selectedConditions).joined(separator: ", ") let report = "Kondisi: \(conditions)\nCatatan: \(additionalNotes)" sendNotification(condition: conditions, report: report) { response in DispatchQueue.main.async { self.message = response } } } } // NetworkManager.swift - HTTP Request Handler func sendNotification(condition: String, report: String, completion: @escaping (String) -> Void) { let url = URL(string: "https://ntfy.ubharajaya.ac.id/panic-button")! let tagMapping: [String: String] = [ "🔥 Kebakaran": "fire", "⛈️ Banjir": "cloud_with_lightning_and_rain", "🌊 Tsunami": "ocean", "🌋 Gunung Meletus": "volcano", "🌏 Gempa Bumi": "earth_asia", "👿 Huru hara": "imp", "🐍 Binatang Buas": "snake", "☢️ Radiasi Nuklir": "radioactive", "☣️ Biohazard": "biohazard" ] let selectedList = condition .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespaces) } .filter { !$0.isEmpty } let cleanConditionText = selectedList.joined(separator: ", ") let emojiTags = selectedList.compactMap { tagMapping[String($0)] } let finalTags = ["Alert"] + emojiTags let notesPart = report.components(separatedBy: "Catatan:").last ?? "" let finalReport = "Kondisi: \(cleanConditionText)\nCatatan:\(notesPart)" var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Alert", forHTTPHeaderField: "Title") request.setValue("urgent", forHTTPHeaderField: "Priority") request.setValue(finalTags.joined(separator: ","), forHTTPHeaderField: "Tags") request.setValue("text/plain", forHTTPHeaderField: "Content-Type") request.httpBody = finalReport.data(using: .utf8) let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { completion("Error: \(error.localizedDescription)") return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 { completion("Notifikasi berhasil dikirim!") } else { completion("Gagal mengirim notifikasi: \(httpResponse.statusCode)") } } } task.resume() } // EvacuationRouteView.swift - Evacuation Route Screen struct EvacuationRouteView: View { @Environment(\.dismiss) var dismiss var body: some View { VStack { Text("Jalur Evakuasi") .font(.system(size: 20, weight: .bold)) .padding(.top, 16) Spacer().frame(height: 16) // Tampilkan gambar jalur evakuasi // Ganti "jalur_evakuasi" dengan nama file gambar Anda Image("jalur_evakuasi") .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: .infinity) .frame(height: 450) .padding() Spacer().frame(height: 20) Button(action: { dismiss() }) { Text("Close") .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(Color.red) .cornerRadius(8) } .padding(.horizontal, 16) Spacer() } } }