import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:image_picker/image_picker.dart'; import 'dashboard_page.dart'; import 'package:http/http.dart' as http; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:geocoding/geocoding.dart'; import 'package:map_picker/map_picker.dart'; import 'dart:io'; import 'package:permission_handler/permission_handler.dart'; import 'dart:async'; import 'package:path_provider/path_provider.dart'; import './services/api_services.dart'; import 'package:dio/dio.dart'; import './config/theme_colors.dart'; import './widgets/custom_bottom_bar.dart'; class ReportJalan extends StatefulWidget { @override _ReportJalanState createState() => _ReportJalanState(); } class _ReportJalanState extends State { double? _latitude; double? _longitude; Uint8List? _imageData; String? _selectedDisasterType; final GlobalKey _formKey = GlobalKey(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _locationController = TextEditingController(); final mapController = MapController(); MapPickerController mapPickerController = MapPickerController(); List provinsiList = []; List kabupatenList = []; List kecamatanList = []; List kelurahanList = []; String? selectedProvinsi; String? selectedKabupaten; String? selectedKecamatan; String? selectedKelurahan; List _markers = []; // Tambahkan variabel untuk menyimpan file foto XFile? _photoFile; final ApiService _apiService = ApiService(); @override void initState() { super.initState(); _markers.add( Marker( width: 30.0, height: 30.0, point: LatLng(-8.009560, 112.950670), child: Container( child: FlutterLogo(), ), ), ); } void _handleMarkerDrag(Marker marker, LatLng newPosition) { setState(() { _latitude = newPosition.latitude; _longitude = newPosition.longitude; }); } void _updateLocationText() { setState(() { _locationController.text = 'Lat: $_latitude, Lng: $_longitude'; }); } void _pickLocation() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), title: Column( children: [ Icon( Icons.location_on, color: ThemeColors.accent(context), size: 50, ), SizedBox(height: 10), Text( 'Konfirmasi Lokasi', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Apakah Anda yakin ingin menggunakan lokasi ini?', textAlign: TextAlign.center, style: TextStyle(fontSize: 16), ), if (cameraPosition != null) ...[ SizedBox(height: 10), Text( 'Koordinat: (${cameraPosition!.latitude.toStringAsFixed(6)}, ${cameraPosition!.longitude.toStringAsFixed(6)})', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), textAlign: TextAlign.center, ), ], ], ), actions: [ Row( children: [ Expanded( child: TextButton( onPressed: () => Navigator.of(context).pop(), style: TextButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), ), child: Text( 'Batal', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), ), ), SizedBox(width: 8), Expanded( child: ElevatedButton( onPressed: () { if (cameraPosition != null) { setState(() { _latitude = cameraPosition!.latitude; _longitude = cameraPosition!.longitude; _locationController.text = 'Lat: ${_latitude!.toStringAsFixed(6)}, Lng: ${_longitude!.toStringAsFixed(6)}'; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon(Icons.check_circle, color: Colors.white), SizedBox(width: 8), Text('Lokasi berhasil dipilih'), ], ), backgroundColor: Colors.green, duration: Duration(seconds: 2), behavior: SnackBarBehavior.floating, margin: EdgeInsets.all(8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ); } Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom( backgroundColor: ThemeColors.accent(context), padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'Pilih Lokasi', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ), ], ), ], actionsAlignment: MainAxisAlignment.spaceBetween, contentPadding: EdgeInsets.fromLTRB(24, 20, 24, 0), actionsPadding: EdgeInsets.all(16), ); }, ); } Future _getCurrentLocation() async { bool serviceEnabled; LocationPermission permission; serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { return Future.error('Location services are disabled.'); } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return Future.error('Location permissions are denied'); } } if (permission == LocationPermission.deniedForever) { return Future.error( 'Location permissions are permanently denied, we cannot request permissions.'); } Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); setState(() { _latitude = position.latitude; _longitude = position.longitude; _locationController.text = 'Lat: $_latitude, Lng: $_longitude'; cameraPosition = LatLng(_latitude!, _longitude!); mapController.move(cameraPosition, cameraZoom); }); } LatLng cameraPosition = LatLng(-8.135565, 113.221188); double cameraZoom = 14; var textController = TextEditingController(); Future _pickImage() async { try { // Bersihkan memori dari foto sebelumnya jika ada if (_imageData != null) { setState(() { _imageData = null; _photoFile = null; }); } // Cek permission kamera dengan timeout final status = await Permission.camera.status.timeout( Duration(seconds: 5), onTimeout: () => throw TimeoutException('Timeout saat meminta izin kamera'), ); if (!status.isGranted) { final result = await Permission.camera.request().timeout( Duration(seconds: 5), onTimeout: () => throw TimeoutException('Timeout saat meminta izin kamera'), ); if (result.isDenied) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Izin kamera diperlukan untuk mengambil foto'), backgroundColor: Colors.red, duration: Duration(seconds: 2), ), ); } return; } } // Buka kamera dengan pengaturan yang lebih optimal final ImagePicker picker = ImagePicker(); final XFile? photo = await picker.pickImage( source: ImageSource.camera, imageQuality: 60, // Kualitas lebih rendah untuk performa lebih baik maxWidth: 1024, // Ukuran lebih kecil maxHeight: 768, // Aspek ratio 4:3 untuk foto preferredCameraDevice: CameraDevice.rear, ).timeout( Duration(seconds: 120), onTimeout: () => throw TimeoutException('Timeout saat mengambil foto'), ); if (photo != null && mounted) { // Baca dan kompres file final bytes = await photo.readAsBytes(); // Perbarui state setState(() { _photoFile = photo; _imageData = bytes; }); // Feedback sukses if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Foto berhasil diambil'), backgroundColor: Colors.green, duration: Duration(seconds: 1), ), ); } } } on TimeoutException catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Waktu pengambilan foto habis: ${e.message}'), backgroundColor: Colors.orange, duration: Duration(seconds: 2), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal mengambil foto: ${e.toString()}'), backgroundColor: Colors.red, duration: Duration(seconds: 2), ), ); } } } Future _submitReport() async { if (!mounted) return; if (!_formKey.currentState!.validate()) { showErrorDialog(context, 'Mohon lengkapi semua field yang diperlukan'); return; } try { showLoadingDialog(context); if (_imageData == null) { Navigator.pop(context); // Tutup loading dialog showErrorDialog(context, 'Mohon ambil foto kerusakan jalan terlebih dahulu'); return; } if (_latitude == null || _longitude == null) { Navigator.pop(context); // Tutup loading dialog showErrorDialog(context, 'Mohon pilih lokasi jalan yang rusak'); return; } if (_selectedDisasterType == null || _selectedDisasterType!.isEmpty) { Navigator.pop(context); // Tutup loading dialog showErrorDialog(context, 'Mohon pilih tingkat kerusakan jalan'); return; } final tempDir = await getTemporaryDirectory(); final tempFile = File('${tempDir.path}/temp_image.jpg'); await tempFile.writeAsBytes(_imageData!); // Tambahkan waktu WIB final now = DateTime.now(); final wibTime = now.toUtc().add(Duration(hours: 7)); // Konversi ke WIB (UTC+7) final wibTimeString = wibTime.toIso8601String().replaceAll('Z', '+07:00'); // Format ke WIB final response = await _apiService.laporJalan( lokasi: _locationController.text, latitude: _latitude!, longitude: _longitude!, jenisRusak: _selectedDisasterType!, deskripsi: _descriptionController.text, foto: tempFile, waktu: wibTimeString, ); if (!mounted) return; Navigator.pop(context); if (response.statusCode == 200 || response.statusCode == 201) { await showSuccessDialog(context); Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => DashboardPage()), (route) => false, ); } else { // Handle error response final responseData = response.data; String errorMessage = responseData['message'] ?? 'Terjadi kesalahan saat mengirim laporan'; if (errorMessage.contains('luar wilayah Lumajang')) { showErrorDialog(context, 'Lokasi yang dipilih berada di luar wilayah Lumajang. Mohon pilih lokasi yang berada dalam wilayah Lumajang.'); } else if (errorMessage.contains('batas maksimal laporan')) { showErrorDialog(context, 'Anda telah mencapai batas maksimal laporan hari ini (10 laporan). Silakan coba lagi besok.'); } else { showErrorDialog(context, errorMessage); } } } catch (e) { if (!mounted) return; Navigator.pop(context); // Tutup loading dialog String errorMessage = ''; if (e is NetworkException) { errorMessage = 'Tidak dapat terhubung ke server. Mohon periksa koneksi internet Anda.'; } else if (e is TimeoutException) { errorMessage = 'Waktu koneksi habis. Silakan coba lagi.'; } else if (e is ServerException) { errorMessage = 'Terjadi kesalahan pada server: ${e.message}'; } else if (e is ValidationException) { errorMessage = e.message; } else { errorMessage = 'Terjadi kesalahan yang tidak diketahui. Silakan coba lagi nanti.'; } showErrorDialog(context, errorMessage); } } void _onMapMove(MapPosition position, bool hasGesture) { if (hasGesture) { setState(() { cameraPosition = position.center!; // textController.text = "checking ..."; }); } } @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: ThemeColors.primary(context), body: Stack( children: [ // Layer 1: Background image Positioned.fill( child: Image.asset( isDarkMode ? 'assets/images/background_dark.png' : 'assets/images/background_light.png', fit: BoxFit.cover, ), ), // Layer 2: Content SafeArea( child: Column( children: [ // Custom AppBar Container( padding: EdgeInsets.all(16), child: Text( 'Lapor Jalan Rusak', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: ThemeColors.textPrimary(context), ), ), ), // Main Content dengan padding bottom untuk bottom bar Expanded( child: SingleChildScrollView( physics: ClampingScrollPhysics(), child: Padding( padding: EdgeInsets.only( left: 16, right: 16, top: 16, bottom: 100, // Tambahkan padding untuk bottom bar ), child: Column( children: [ // Map Container Container( height: 300, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: ThemeColors.shadowColor(context), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: MapPicker( mapPickerController: mapPickerController, child: FlutterMap( mapController: mapController, options: MapOptions( initialCenter: cameraPosition, initialZoom: cameraZoom, onTap: (_, point) { setState(() { cameraPosition = point; _latitude = point.latitude; _longitude = point.longitude; _locationController.text = 'Lat: $_latitude, Lng: $_longitude'; }); }, ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayer( markers: [ Marker( width: 40, height: 40, point: cameraPosition, child: Icon( Icons.location_on, color: ThemeColors.error, size: 40, ), ), ], ), ], ), ), ), ), SizedBox(height: 20), // Form Container Container( decoration: BoxDecoration( color: ThemeColors.background(context), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: ThemeColors.shadowColor(context), blurRadius: 15, offset: Offset(0, 5), ), ], ), padding: EdgeInsets.all(20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Location Field TextFormField( controller: _locationController, readOnly: true, decoration: InputDecoration( hintText: 'Lokasi', hintStyle: TextStyle(color: ThemeColors.textGrey(context)), prefixIcon: Icon(Icons.location_on, color: ThemeColors.inputIcon(context)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), filled: true, fillColor: ThemeColors.inputFill(context), ), ), SizedBox(height: 16), // Get Location Button SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _getCurrentLocation, icon: Icon(Icons.my_location), label: Text('Gunakan Lokasi Saat Ini'), style: ElevatedButton.styleFrom( backgroundColor: ThemeColors.accent(context), foregroundColor: ThemeColors.buttonText(context), padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), SizedBox(height: 16), // Dropdown Jenis Kerusakan DropdownButtonFormField( value: _selectedDisasterType, onChanged: (String? newValue) { setState(() { _selectedDisasterType = newValue; }); }, decoration: InputDecoration( // labelText: 'Jenis Kerusakan', hintText: 'Pilih Jenis Kerusakan', prefixIcon: Icon(Icons.warning_amber, color: ThemeColors.inputIcon(context)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), filled: true, fillColor: ThemeColors.inputFill(context), ), items: [ DropdownMenuItem( value: 'jalan rusak ringan', child: Row( children: [ Icon(Icons.warning, color: Colors.orange), SizedBox(width: 8.0), Text('Kerusakan Ringan'), ], ), ), DropdownMenuItem( value: 'jalan rusak berat', child: Row( children: [ Icon(Icons.dangerous, color: Colors.red), SizedBox(width: 8.0), Text('Kerusakan Berat'), ], ), ), ], validator: (value) { if (value == null || value.isEmpty) { return 'Mohon pilih jenis kerusakan'; } return null; }, hint: Text('Pilih Jenis Kerusakan'), ), SizedBox(height: 16), // Description Field TextFormField( controller: _descriptionController, maxLines: 3, decoration: InputDecoration( hintText: 'Deskripsi Kerusakan', hintStyle: TextStyle(color: ThemeColors.textGrey(context)), prefixIcon: Icon(Icons.description, color: ThemeColors.inputIcon(context)), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), filled: true, fillColor: ThemeColors.inputFill(context), ), validator: (value) { if (value == null || value.isEmpty) { return 'Mohon isi deskripsi kerusakan'; } return null; }, ), SizedBox(height: 16), // Photo Container Container( width: double.infinity, height: 200, decoration: BoxDecoration( color: ThemeColors.inputFill(context), borderRadius: BorderRadius.circular(12), border: Border.all( color: ThemeColors.inputBorder(context), width: 1, ), ), child: _imageData != null ? ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.memory( _imageData!, fit: BoxFit.cover, ), ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.camera_alt, size: 48, color: ThemeColors.textGrey(context), ), SizedBox(height: 8), Text( 'Tambahkan Foto', style: TextStyle( color: ThemeColors.textGrey(context), ), ), ], ), ), ), SizedBox(height: 16), // Take Photo Button SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _pickImage, icon: Icon(Icons.camera_alt), label: Text('Ambil Foto'), style: ElevatedButton.styleFrom( backgroundColor: ThemeColors.accent(context), foregroundColor: ThemeColors.buttonText(context), padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), SizedBox(height: 24), // Submit Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { _submitReport(); } }, style: ElevatedButton.styleFrom( backgroundColor: ThemeColors.accent(context), foregroundColor: ThemeColors.buttonText(context), padding: EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( 'KIRIM LAPORAN', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ], ), ), ), ), ], ), ), // Layer 3: Bottom Bar yang mengambang Positioned( left: 0, right: 0, bottom: 0, child: CustomBottomBar(currentPage: 'lapor'), ), ], ), ); } Widget _buildDisasterTypeDropdown() { return DropdownButtonFormField( value: _selectedDisasterType, onChanged: (String? newValue) { setState(() { _selectedDisasterType = newValue; }); }, decoration: InputDecoration( labelText: 'Jenis Kerusakan', border: OutlineInputBorder(), ), items: [ DropdownMenuItem( value: 'jalan rusak ringan', child: Row( children: [ Icon(Icons.invert_colors, color: Colors.blue), SizedBox(width: 8.0), Text('Kerusakan Ringan'), ], ), ), DropdownMenuItem( value: 'jalan rusak berat', child: Row( children: [ Icon(Icons.terrain, color: Colors.brown), SizedBox(width: 8.0), Text('Kerusakan Berat'), ], ), ), ], hint: Text('Pilih Jenis Kerusakan'), ); } Widget _buildLocationSelection() { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ElevatedButton.icon( onPressed: () { _pickImage(); }, icon: Icon(Icons.camera_alt, color: Colors.white), label: Text( 'Ambil Foto dengan Kamera', style: TextStyle(fontSize: 16.0, color: Colors.white), ), style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: const Color(0xFF4CAF50), // Warna hijau shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), elevation: 5.0, padding: const EdgeInsets.symmetric(vertical: 16.0), ), ), SizedBox(height: 16.0), _buildImagePreview(), SizedBox(height: 16.0), ElevatedButton.icon( onPressed: () { _getCurrentLocation(); }, icon: Icon(Icons.my_location, color: const Color(0xFF020306)), label: Text( 'Pilih Lokasi Anda Sekarang', style: TextStyle(fontSize: 16.0, color: const Color(0xFF020306)), ), style: ElevatedButton.styleFrom( foregroundColor: const Color(0xFFF9C416), elevation: 5.0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), ), ), ], ); } Widget _buildImagePreview() { if (_imageData != null) { return Container( height: 250, decoration: BoxDecoration( color: Colors.black.withOpacity(0.1), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, spreadRadius: 2, ), ], ), child: Stack( fit: StackFit.expand, children: [ // Preview Foto ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.memory( _imageData!, fit: BoxFit.cover, ), ), // Overlay gelap semi-transparan Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.3), Colors.transparent, Colors.transparent, Colors.black.withOpacity(0.3), ], ), ), ), // Label Preview Foto Positioned( top: 8, right: 8, child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), borderRadius: BorderRadius.circular(20), ), padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.camera_alt, color: Colors.white, size: 16), SizedBox(width: 4), Text( 'Preview Foto', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ], ), ), ), // Tombol Aksi Positioned( bottom: 8, left: 8, right: 8, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ // Tombol Ambil Ulang Container( child: IconButton( icon: Icon(Icons.refresh, color: Colors.white), onPressed: _pickImage, tooltip: 'Ambil Ulang Foto', ), ), SizedBox(width: 8), // Tombol Hapus Container( child: IconButton( icon: Icon(Icons.delete_outline, color: Colors.white), onPressed: () { setState(() { _imageData = null; _photoFile = null; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Foto telah dihapus'), backgroundColor: Colors.grey, duration: Duration(seconds: 2), ), ); }, tooltip: 'Hapus Foto', ), ), ], ), ), ], ), ); } // Tampilan saat tidak ada foto return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.camera_alt_outlined, size: 48, color: Colors.grey[600], ), SizedBox(height: 8), Text( 'Belum ada foto yang diambil', style: TextStyle( color: Colors.grey[600], fontSize: 14, ), ), ], ), ); } void _showConfirmationDialog(BuildContext context) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Konfirmasi Laporkan'), content: Text('Data Anda akan segera dikonfirmasi oleh admin.'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Batal'), ), ElevatedButton( onPressed: () { _submitReport(); // Memanggil fungsi untuk mengirim laporan Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom( foregroundColor: const Color(0xFF00BFF3), ), child: Text('Laporkan'), ), ], ); }, ); } @override void dispose() { // Bersihkan memori _imageData = null; _photoFile = null; _descriptionController.dispose(); _locationController.dispose(); textController.dispose(); super.dispose(); } } TileLayer get openStreetMapTileLayer => TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'dev.fleaflet.flutter_map.example', ); class ValidationException implements Exception { final String message; ValidationException(this.message); } class NetworkException implements Exception { final String message; NetworkException(this.message); } class ServerException implements Exception { final String message; ServerException(this.message); } Future showSuccessDialog(BuildContext context) async { return showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), title: Column( children: [ Icon( Icons.check_circle, color: Colors.green, size: 50, ), SizedBox(height: 10), Text( 'Berhasil!', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ], ), content: Text( 'Laporan kerusakan jalan Anda telah berhasil dikirim dan akan segera diproses oleh tim kami.', textAlign: TextAlign.center, ), actions: [ Center( child: ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom( backgroundColor: ThemeColors.accent(context), minimumSize: Size(200, 45), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'OK, Saya Mengerti', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ), ], actionsAlignment: MainAxisAlignment.center, contentPadding: EdgeInsets.fromLTRB(24, 20, 24, 0), actionsPadding: EdgeInsets.all(16), ); }, ); } void showErrorDialog(BuildContext context, String message) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), title: Column( children: [ Icon( Icons.error_outline, color: Colors.red, size: 50, ), SizedBox(height: 10), Text( 'Terjadi Kesalahan', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ], ), content: Text( message, textAlign: TextAlign.center, ), actions: [ Center( child: ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, minimumSize: Size(200, 45), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'Tutup', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ), ], actionsAlignment: MainAxisAlignment.center, contentPadding: EdgeInsets.fromLTRB(24, 20, 24, 0), actionsPadding: EdgeInsets.all(16), ); }, ); } void showLoadingDialog(BuildContext context) { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return WillPopScope( onWillPop: () async => false, child: AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), content: Container( padding: EdgeInsets.symmetric(vertical: 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 20), Text( 'Sedang memproses...', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), ), ), ); }, ); }