Perbarui PengaduanController dan tampilan terkait untuk mendukung pengunggahan bukti tindakan. Tambahkan fungsionalitas pemilihan gambar dari kamera atau galeri, serta perbarui metode untuk menambahkan dan memperbarui tindakan pengaduan dengan bukti yang diunggah. Sederhanakan proses penyimpanan tindakan dan perbarui tampilan detail pengaduan untuk meningkatkan pengalaman pengguna.
This commit is contained in:
@ -5,12 +5,14 @@ import 'package:penyaluran_app/app/data/models/tindakan_pengaduan_model.dart';
|
||||
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class PengaduanController extends GetxController {
|
||||
final AuthController _authController = Get.find<AuthController>();
|
||||
final SupabaseService _supabaseService = SupabaseService.to;
|
||||
|
||||
final RxBool isLoading = false.obs;
|
||||
final RxBool isUploading = false.obs;
|
||||
|
||||
// Indeks kategori yang dipilih untuk filter
|
||||
final RxInt selectedCategoryIndex = 0.obs;
|
||||
@ -29,6 +31,12 @@ class PengaduanController extends GetxController {
|
||||
// Form key
|
||||
final GlobalKey<FormState> tindakanFormKey = GlobalKey<FormState>();
|
||||
|
||||
// List untuk menyimpan path file bukti tindakan
|
||||
final RxList<String> buktiTindakanPaths = <String>[].obs;
|
||||
|
||||
// Image picker
|
||||
final ImagePicker _imagePicker = ImagePicker();
|
||||
|
||||
UserModel? get user => _authController.user;
|
||||
|
||||
@override
|
||||
@ -95,31 +103,66 @@ class PengaduanController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> tambahTindakan(String pengaduanId) async {
|
||||
if (!tindakanFormKey.currentState!.validate()) return;
|
||||
|
||||
isLoading.value = true;
|
||||
Future<void> tambahTindakanPengaduan({
|
||||
required String pengaduanId,
|
||||
required String tindakan,
|
||||
required String kategoriTindakan,
|
||||
required String statusTindakan,
|
||||
required String prioritas,
|
||||
String? catatan,
|
||||
String? hasilTindakan,
|
||||
required List<String> buktiTindakanPaths,
|
||||
}) async {
|
||||
try {
|
||||
final tindakan = TindakanPengaduanModel(
|
||||
pengaduanId: pengaduanId,
|
||||
tindakan: tindakanController.text,
|
||||
catatan: catatanController.text,
|
||||
tanggalTindakan: DateTime.now(),
|
||||
petugasId: user?.id,
|
||||
);
|
||||
isLoading.value = true;
|
||||
|
||||
await _supabaseService.tambahTindakanPengaduan(tindakan.toJson());
|
||||
await _supabaseService.updateStatusPengaduan(pengaduanId, 'TINDAKAN');
|
||||
// Upload bukti tindakan jika ada
|
||||
List<String> buktiTindakanUrls = [];
|
||||
if (buktiTindakanPaths.isNotEmpty) {
|
||||
for (var path in buktiTindakanPaths) {
|
||||
final String? fileUrl = await SupabaseService.to
|
||||
.uploadFile(path, 'tindakan_pengaduan', 'bukti_tindakan');
|
||||
if (fileUrl != null) {
|
||||
buktiTindakanUrls.add(fileUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear form
|
||||
tindakanController.clear();
|
||||
catatanController.clear();
|
||||
// Buat objek tindakan
|
||||
final Map<String, dynamic> tindakanData = {
|
||||
'pengaduan_id': pengaduanId,
|
||||
'tindakan': tindakan,
|
||||
'catatan': catatan,
|
||||
'status_tindakan': statusTindakan,
|
||||
'prioritas': prioritas,
|
||||
'kategori_tindakan': kategoriTindakan,
|
||||
'hasil_tindakan': hasilTindakan,
|
||||
'tanggal_tindakan': DateTime.now().toIso8601String(),
|
||||
'petugas_id': user?.id,
|
||||
'bukti_tindakan': buktiTindakanUrls,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
await loadPengaduanData();
|
||||
Get.back(); // Close dialog
|
||||
// Simpan tindakan ke Supabase
|
||||
await SupabaseService.to.tambahTindakanPengaduan(tindakanData);
|
||||
|
||||
// Update status pengaduan jika perlu
|
||||
if (statusTindakan == 'SELESAI') {
|
||||
await SupabaseService.to.updateStatusPengaduan(pengaduanId, 'SELESAI');
|
||||
} else {
|
||||
await SupabaseService.to.updateStatusPengaduan(pengaduanId, 'TINDAKAN');
|
||||
}
|
||||
|
||||
// Reset paths setelah berhasil
|
||||
buktiTindakanPaths.clear();
|
||||
|
||||
//refresh page
|
||||
Get.forceAppUpdate();
|
||||
Get.back(); // Tutup dialog
|
||||
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Berhasil',
|
||||
'Tindakan berhasil ditambahkan',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.green,
|
||||
@ -139,22 +182,65 @@ class PengaduanController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTindakan(
|
||||
String tindakanId, Map<String, dynamic> data) async {
|
||||
isLoading.value = true;
|
||||
Future<void> updateTindakanPengaduan({
|
||||
required String tindakanId,
|
||||
required String pengaduanId,
|
||||
required String tindakan,
|
||||
required String kategoriTindakan,
|
||||
required String statusTindakan,
|
||||
required String prioritas,
|
||||
String? catatan,
|
||||
String? hasilTindakan,
|
||||
required List<String> buktiTindakanPaths,
|
||||
}) async {
|
||||
try {
|
||||
await _supabaseService.updateTindakanPengaduan(tindakanId, data);
|
||||
isLoading.value = true;
|
||||
|
||||
// Upload bukti tindakan jika ada file baru (yang belum diupload)
|
||||
List<String> buktiTindakanUrls = [];
|
||||
for (var path in buktiTindakanPaths) {
|
||||
// Jika path sudah berupa URL, tambahkan langsung
|
||||
if (path.startsWith('http')) {
|
||||
buktiTindakanUrls.add(path);
|
||||
} else {
|
||||
// Jika path adalah file lokal, upload dulu
|
||||
final String? fileUrl = await SupabaseService.to
|
||||
.uploadFile(path, 'tindakan_pengaduan', 'bukti_tindakan');
|
||||
if (fileUrl != null) {
|
||||
buktiTindakanUrls.add(fileUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buat objek tindakan
|
||||
final Map<String, dynamic> tindakanData = {
|
||||
'tindakan': tindakan,
|
||||
'catatan': catatan,
|
||||
'status_tindakan': statusTindakan,
|
||||
'prioritas': prioritas,
|
||||
'kategori_tindakan': kategoriTindakan,
|
||||
'hasil_tindakan': hasilTindakan,
|
||||
'bukti_tindakan': buktiTindakanUrls,
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
// Update tindakan di Supabase
|
||||
await SupabaseService.to
|
||||
.updateTindakanPengaduan(tindakanId, tindakanData);
|
||||
|
||||
// Reset paths setelah berhasil
|
||||
buktiTindakanPaths.clear();
|
||||
|
||||
//refresh page
|
||||
Get.forceAppUpdate();
|
||||
Get.back(); // Tutup dialog
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Berhasil',
|
||||
'Tindakan berhasil diperbarui',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
|
||||
// Refresh data
|
||||
Get.forceAppUpdate();
|
||||
} catch (e) {
|
||||
print('Error updating tindakan: $e');
|
||||
Get.snackbar(
|
||||
@ -308,4 +394,35 @@ class PengaduanController extends GetxController {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk memilih bukti tindakan
|
||||
Future<void> pickBuktiTindakan({bool fromCamera = true}) async {
|
||||
try {
|
||||
final pickedFile = await _imagePicker.pickImage(
|
||||
source: fromCamera ? ImageSource.camera : ImageSource.gallery,
|
||||
imageQuality: 70,
|
||||
maxWidth: 1000,
|
||||
);
|
||||
|
||||
if (pickedFile != null) {
|
||||
buktiTindakanPaths.add(pickedFile.path);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error picking image: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal mengambil gambar: ${e.toString()}',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk menghapus bukti tindakan
|
||||
void removeBuktiTindakan(int index) {
|
||||
if (index >= 0 && index < buktiTindakanPaths.length) {
|
||||
buktiTindakanPaths.removeAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -593,7 +593,20 @@ class PengaduanView extends GetView<PengaduanController> {
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
controller.tambahTindakan(item.id!);
|
||||
if (controller.tindakanFormKey.currentState!.validate()) {
|
||||
Navigator.pop(context);
|
||||
controller.tambahTindakanPengaduan(
|
||||
pengaduanId: item.id!,
|
||||
tindakan: controller.tindakanController.text,
|
||||
kategoriTindakan: 'VERIFIKASI_DATA',
|
||||
statusTindakan: 'PROSES',
|
||||
prioritas: 'SEDANG',
|
||||
catatan: controller.catatanController.text.isEmpty
|
||||
? null
|
||||
: controller.catatanController.text,
|
||||
buktiTindakanPaths: [],
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Simpan'),
|
||||
),
|
||||
|
@ -6,6 +6,8 @@ import 'package:penyaluran_app/app/data/models/tindakan_pengaduan_model.dart';
|
||||
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class WargaDetailPengaduanView extends GetView<WargaDashboardController> {
|
||||
const WargaDetailPengaduanView({Key? key}) : super(key: key);
|
||||
@ -447,3 +449,563 @@ class WargaDetailPengaduanView extends GetView<WargaDashboardController> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TambahTindakanPengaduanView extends StatefulWidget {
|
||||
final String pengaduanId;
|
||||
|
||||
const TambahTindakanPengaduanView({Key? key, required this.pengaduanId})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<TambahTindakanPengaduanView> createState() =>
|
||||
_TambahTindakanPengaduanViewState();
|
||||
}
|
||||
|
||||
class _TambahTindakanPengaduanViewState
|
||||
extends State<TambahTindakanPengaduanView> {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final tindakanController = TextEditingController();
|
||||
final catatanController = TextEditingController();
|
||||
String? selectedKategori;
|
||||
String? selectedPrioritas;
|
||||
|
||||
// List untuk menyimpan path file lokal
|
||||
final List<String> buktiTindakanPaths = [];
|
||||
bool isUploading = false;
|
||||
|
||||
final List<String> kategoriOptions = [
|
||||
'VERIFIKASI_DATA',
|
||||
'KUNJUNGAN_LAPANGAN',
|
||||
'KOORDINASI_LINTAS_INSTANSI',
|
||||
'PERBAIKAN_DATA_PENERIMA',
|
||||
'PENYALURAN_ULANG',
|
||||
'PENGGANTIAN_BANTUAN',
|
||||
'MEDIASI',
|
||||
'KLARIFIKASI',
|
||||
'PENYESUAIAN_JUMLAH_BANTUAN',
|
||||
'PEMERIKSAAN_KUALITAS_BANTUAN',
|
||||
'PERBAIKAN_PROSES_DISTRIBUSI',
|
||||
'EDUKASI_PENERIMA',
|
||||
'PENYELESAIAN_ADMINISTRATIF',
|
||||
'INVESTIGASI_PENYALAHGUNAAN',
|
||||
'PELAPORAN_KE_PIHAK_BERWENANG',
|
||||
];
|
||||
|
||||
final List<String> prioritasOptions = [
|
||||
'RENDAH',
|
||||
'SEDANG',
|
||||
'TINGGI',
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Tambah Tindakan Pengaduan'),
|
||||
elevation: 0,
|
||||
),
|
||||
body: Form(
|
||||
key: formKey,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Kategori Tindakan
|
||||
Text(
|
||||
'Kategori Tindakan',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
hint: const Text('Pilih kategori tindakan'),
|
||||
value: selectedKategori,
|
||||
items: kategoriOptions.map((kategori) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: kategori,
|
||||
child: Text(
|
||||
kategori
|
||||
.split('_')
|
||||
.map((word) =>
|
||||
word[0].toUpperCase() +
|
||||
word.substring(1).toLowerCase())
|
||||
.join(' '),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectedKategori = value;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Pilih kategori tindakan';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Prioritas
|
||||
Text(
|
||||
'Prioritas',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
hint: const Text('Pilih prioritas'),
|
||||
value: selectedPrioritas,
|
||||
items: prioritasOptions.map((prioritas) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: prioritas,
|
||||
child: Text(
|
||||
prioritas[0].toUpperCase() +
|
||||
prioritas.substring(1).toLowerCase(),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectedPrioritas = value;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Pilih prioritas tindakan';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Deskripsi Tindakan
|
||||
Text(
|
||||
'Deskripsi Tindakan',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: tindakanController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
hintText: 'Jelaskan tindakan yang dilakukan',
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
maxLines: 3,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Deskripsi tindakan tidak boleh kosong';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Catatan (opsional)
|
||||
Text(
|
||||
'Catatan (opsional)',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: catatanController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
hintText: 'Tambahkan catatan jika diperlukan',
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Bukti Tindakan
|
||||
Text(
|
||||
'Bukti Tindakan',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Area upload bukti tindakan
|
||||
if (buktiTindakanPaths.isEmpty)
|
||||
InkWell(
|
||||
onTap: () => _showPilihSumberFoto(context),
|
||||
child: Container(
|
||||
height: 150,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey.shade400),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.camera_alt,
|
||||
size: 48,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tambah Bukti Tindakan',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: buktiTindakanPaths.length +
|
||||
1, // +1 untuk tombol tambah
|
||||
itemBuilder: (context, index) {
|
||||
if (index == buktiTindakanPaths.length) {
|
||||
// Tombol tambah foto
|
||||
return InkWell(
|
||||
onTap: () => _showPilihSumberFoto(context),
|
||||
child: Container(
|
||||
width: 100,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border:
|
||||
Border.all(color: Colors.grey.shade400),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add_photo_alternate,
|
||||
size: 32,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Tambah',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Tampilkan foto yang sudah diambil
|
||||
return Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => _showFullScreenImage(
|
||||
context, buktiTindakanPaths[index]),
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
image: DecorationImage(
|
||||
image: FileImage(
|
||||
File(buktiTindakanPaths[index])),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 4,
|
||||
right: 12,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
buktiTindakanPaths.removeAt(index);
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, -1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: isUploading ? null : _simpanTindakan,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
child: isUploading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text('Simpan'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showPilihSumberFoto(BuildContext context) {
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Pilih Sumber Foto',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.camera_alt),
|
||||
title: const Text('Kamera'),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_pickBuktiTindakan(true);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: const Text('Galeri'),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_pickBuktiTindakan(false);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickBuktiTindakan(bool fromCamera) async {
|
||||
try {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
final XFile? pickedFile = await picker.pickImage(
|
||||
source: fromCamera ? ImageSource.camera : ImageSource.gallery,
|
||||
imageQuality: 70,
|
||||
maxWidth: 1000,
|
||||
);
|
||||
|
||||
if (pickedFile != null) {
|
||||
setState(() {
|
||||
buktiTindakanPaths.add(pickedFile.path);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error picking image: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal mengambil gambar: ${e.toString()}',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showFullScreenImage(BuildContext context, String imagePath) {
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
insetPadding: EdgeInsets.zero,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
InteractiveViewer(
|
||||
panEnabled: true,
|
||||
minScale: 0.5,
|
||||
maxScale: 4,
|
||||
child: Image.file(
|
||||
File(imagePath),
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey.shade300,
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
size: 50,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 20,
|
||||
right: 20,
|
||||
child: GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _simpanTindakan() async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
if (buktiTindakanPaths.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Bukti tindakan harus diupload',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isUploading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
// Di sini kita baru melakukan upload file ke server
|
||||
// Contoh implementasi:
|
||||
|
||||
// 1. Upload semua file bukti tindakan
|
||||
// final List<String> buktiTindakanUrls = await uploadMultipleFiles(buktiTindakanPaths);
|
||||
|
||||
// 2. Simpan data tindakan ke database
|
||||
// await saveTindakanPengaduan(
|
||||
// pengaduanId: widget.pengaduanId,
|
||||
// kategoriTindakan: selectedKategori!,
|
||||
// prioritas: selectedPrioritas!,
|
||||
// tindakan: tindakanController.text,
|
||||
// catatan: catatanController.text,
|
||||
// buktiTindakanUrls: buktiTindakanUrls,
|
||||
// );
|
||||
|
||||
// Tampilkan pesan sukses
|
||||
Get.back(); // Kembali ke halaman sebelumnya
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Tindakan berhasil disimpan',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error saving tindakan: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal menyimpan tindakan: ${e.toString()}',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
setState(() {
|
||||
isUploading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,6 @@ class AppTheme {
|
||||
foregroundColor: primaryColor,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
iconTheme: IconThemeData(color: primaryColor),
|
||||
),
|
||||
|
||||
// Tombol
|
||||
@ -131,7 +130,6 @@ class AppTheme {
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
),
|
||||
|
||||
// Tombol
|
||||
|
Reference in New Issue
Block a user