diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dc3b895 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin index 3902c56..36b5223 100644 --- a/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/arm64-v8a/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ס2 2 +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  NJ2 2  -}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  ס2  2~ +}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  NJ2  2~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  ס2 2 +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  NJ2 2  -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  ס2 2p +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  NJ2 2p n -lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  ס2 2t +lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  NJ2 2t r -pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  ס2y +pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  NJ2y w -uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  ס2 K 2z +uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  NJ2 K 2z x -vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  ס2 ~ +vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  NJ2 ~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  ס2 +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  NJ2   -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  ס2  2w +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  NJ2  2w u -sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  ס2  ( 2| +sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  NJ2  ( 2| z -xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\symbol_folder_index.txt  ס2  o 2 \ No newline at end of file +xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\symbol_folder_index.txt  NJ2  o 2 \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin index de14102..8112d56 100644 --- a/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/armeabi-v7a/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ɠס2 2 +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2  -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  ɠס2  2 +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  2  2 ~ -|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  ɠס2 2 +|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  2 2  -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  ʠס2 2r +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  2 2r p -nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  ʠס2 2v +nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  2 2v t -rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  ʠס2{ +rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  2{ y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  ʠס2 K 2| +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  2 K 2| z -xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  ʠס2  +xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  2  ~ -|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  ʠס2 +|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  2   -D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  ʠס2  2y +D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  2  2y w -uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  ʠס2  ( 2~ +uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  2  ( 2~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\symbol_folder_index.txt  ʠס2  q 2 \ No newline at end of file +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\symbol_folder_index.txt  2  q 2 \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin index d20ab28..7781258 100644 --- a/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/x86/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ס2 2{ +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2{ y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  ס2  2x +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  2  2x v -tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  ס2 2} +tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  2 2} { -yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  ס2 2j +yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  2 2j h -fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  ס2 2n +fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  2 2n l -jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  ס2s +jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  2s q -oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  ס2 K 2t +oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  2 K 2t r -pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  ס2 x +pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  2 x v -tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  ס2 +tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  2 ~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  ס2  2q +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  2  2q o -mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  ס2  ( 2v +mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  2  ( 2v t -rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\symbol_folder_index.txt  ס2  i 2 \ No newline at end of file +rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\symbol_folder_index.txt  2  i 2 \ No newline at end of file diff --git a/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin b/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin index bb50cf4..bf13839 100644 --- a/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin +++ b/android/app/.cxx/Debug/626b5o2n/x86_64/configure_fingerprint.bin @@ -2,27 +2,27 @@ C/C++ Structured LogO M KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC A -?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  Ԣס2 2~ +?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  2 2~ | -zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  Ԣס2  2{ +zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  2  2{ y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  Ԣס2 2 +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  2 2 ~ -|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  Ԣס2 2m +|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  2 2m k -iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  Ԣס2 2q +iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  2 2q o -mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  Ԣס2v +mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  2v t -rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  Ԣס2 K 2w +rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  2 K 2w u -sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  Ԣס2 { +sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  2 { y -wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  Ԣס2 +wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  2   -}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  Ԣס2  2t +}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  2  2t r -pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  Ԣס2  ( 2y +pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  2  ( 2y w -uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\symbol_folder_index.txt  Ԣס2  l 2 \ No newline at end of file +uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\symbol_folder_index.txt  2  l 2 \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index d2ffbff..f596659 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,6 +2,7 @@ allprojects { repositories { google() mavenCentral() + jcenter() } } @@ -10,7 +11,9 @@ subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(":app") + if (project.name.contains('qr_code_scanner_plus') || project.name.contains('flutter')) { + project.evaluationDependsOn(":app") + } } tasks.register("clean", Delete) { diff --git a/lib/app/data/models/penerima_penyaluran_model.dart b/lib/app/data/models/penerima_penyaluran_model.dart index 62bdfff..6264c56 100644 --- a/lib/app/data/models/penerima_penyaluran_model.dart +++ b/lib/app/data/models/penerima_penyaluran_model.dart @@ -22,6 +22,7 @@ class PenerimaPenyaluranModel { final String? deskripsiPenyaluran; // Deskripsi penyaluran final String? lokasiPenyaluranNama; // Nama lokasi penyaluran final String? lokasiPenyaluranAlamat; // Alamat lokasi penyaluran + final String? qrCodeHash; // Hash untuk QR code PenerimaPenyaluranModel({ this.id, @@ -45,6 +46,7 @@ class PenerimaPenyaluranModel { this.deskripsiPenyaluran, this.lokasiPenyaluranNama, this.lokasiPenyaluranAlamat, + this.qrCodeHash, }); factory PenerimaPenyaluranModel.fromRawJson(String str) => @@ -79,6 +81,7 @@ class PenerimaPenyaluranModel { deskripsiPenyaluran: json["deskripsi_penyaluran"], lokasiPenyaluranNama: json["lokasi_penyaluran_nama"], lokasiPenyaluranAlamat: json["lokasi_penyaluran_alamat"], + qrCodeHash: json["qr_code_hash"], ); Map toJson() => { @@ -103,5 +106,6 @@ class PenerimaPenyaluranModel { "deskripsi_penyaluran": deskripsiPenyaluran, "lokasi_penyaluran_nama": lokasiPenyaluranNama, "lokasi_penyaluran_alamat": lokasiPenyaluranAlamat, + "qr_code_hash": qrCodeHash, }; } diff --git a/lib/app/modules/auth/controllers/auth_controller.dart b/lib/app/modules/auth/controllers/auth_controller.dart index f2186aa..92ecdb6 100644 --- a/lib/app/modules/auth/controllers/auth_controller.dart +++ b/lib/app/modules/auth/controllers/auth_controller.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:penyaluran_app/app/data/models/user_model.dart'; import 'package:penyaluran_app/app/data/providers/auth_provider.dart'; import 'package:penyaluran_app/app/routes/app_pages.dart'; +import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart'; class AuthController extends GetxController { static AuthController get to => Get.find(); @@ -265,14 +266,29 @@ class AuthController extends GetxController { // Metode untuk logout Future logout() async { try { + // Ambil semua controller yang mungkin perlu dibersihkan + try { + final wargaController = Get.find(); + wargaController.penerimaPenyaluran.clear(); + wargaController.pengajuanKelayakan.clear(); + wargaController.pengaduan.clear(); + } catch (e) { + // Jika controller tidak ditemukan, abaikan + print('Controller tidak ditemukan: $e'); + } + + // Logout dari Supabase await _authProvider.signOut(); + + // Reset semua state _user.value = null; - _hasLoadedProfile.value = false; // Reset flag saat logout + _hasLoadedProfile.value = false; isWargaProfileComplete.value = false; // Bersihkan dependensi form sebelum navigasi clearFormDependencies(); + // Navigasi ke halaman login Get.offAllNamed(Routes.login); } catch (e) { Get.snackbar( diff --git a/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart index b720f8d..7505271 100644 --- a/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart @@ -445,4 +445,57 @@ class DetailPenyaluranController extends GetxController { isLoading.value = false; } } + + // Metode untuk verifikasi penerima berdasarkan QR code + Future verifikasiPenerimaByQrCode( + String penyaluranId, String qrHash) async { + try { + isProcessing.value = true; + + // Cari penerima dengan QR hash yang sesuai + final data = await _supabaseService.client + .from('penerima_penyaluran') + .select('*, warga:warga_id(*)') + .eq('penyaluran_bantuan_id', penyaluranId) + .eq('qr_code_hash', qrHash) + .single(); + + if (data != null) { + // Jika penerima ditemukan, konversi ke model + final Map sanitizedPenerimaData = + Map.from(data); + + // Konversi jumlah_bantuan ke double jika bertipe String + if (sanitizedPenerimaData['jumlah_bantuan'] is String) { + sanitizedPenerimaData['jumlah_bantuan'] = double.tryParse( + sanitizedPenerimaData['jumlah_bantuan'] as String); + } + + // Konversi data ke model + final penerima = + PenerimaPenyaluranModel.fromJson(sanitizedPenerimaData); + + // Set isProcessing ke false sebelum navigasi untuk menghindari masalah loading + isProcessing.value = false; + + // Navigasi ke halaman konfirmasi dengan data terbaru + await Get.toNamed('/petugas-desa/konfirmasi-penerima/${penerima.id}', + arguments: { + 'penerima': penerima, + 'tanggal_penyaluran': penyaluran.value?.tanggalPenyaluran + }); + + // Refresh data + await refreshData(); + return true; + } + + return false; + } catch (e) { + print('Error verifikasi QR code: $e'); + return false; + } finally { + isProcessing.value = false; + } + } } diff --git a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart index bb34a47..9de570e 100644 --- a/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart @@ -9,6 +9,8 @@ import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart import 'package:penyaluran_app/app/services/supabase_service.dart'; import 'package:penyaluran_app/app/utils/date_time_helper.dart'; import 'dart:async'; +import 'dart:convert'; +import 'package:crypto/crypto.dart'; class JadwalPenyaluranController extends GetxController { final AuthController _authController = Get.find(); @@ -415,38 +417,42 @@ class JadwalPenyaluranController extends GetxController { // Buat data penerima penyaluran untuk setiap pengajuan yang disetujui for (var pengajuan in pengajuanData) { + // Generate QR code hash unik untuk setiap penerima + final String qrCodeHash = + generateQrCodeHash(penyaluranId, pengajuan['warga_id']); + final penerimaPenyaluran = { 'penyaluran_bantuan_id': penyaluranId, 'warga_id': pengajuan['warga_id'], 'stok_bantuan_id': skemaBantuanCache[skemaId]?.stokBantuanId, - 'status_penerimaan': 'MENUNGGU', + 'status_penerimaan': 'BELUMMENERIMA', + 'qr_code_hash': qrCodeHash, }; + // Simpan data penerima ke database await _supabaseService.client .from('penerima_penyaluran') .insert(penerimaPenyaluran); } - // Refresh data + // Setelah berhasil menambahkan, refresh data await loadJadwalData(); + await loadPermintaanPenjadwalanData(); - // Kembali ke halaman sebelumnya - Get.back(); - - // Tampilkan notifikasi sukses + // Tampilkan pesan sukses Get.snackbar( 'Sukses', - 'Penyaluran berhasil ditambahkan', - snackPosition: SnackPosition.TOP, + 'Jadwal penyaluran bantuan telah dibuat', + snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { - print('Error menambahkan penyaluran: $e'); + print('Error: $e'); Get.snackbar( - 'Error', - 'Gagal menambahkan penyaluran: ${e.toString()}', - snackPosition: SnackPosition.TOP, + 'Gagal', + 'Terjadi kesalahan saat menambahkan jadwal penyaluran', + snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -454,4 +460,16 @@ class JadwalPenyaluranController extends GetxController { isLoading.value = false; } } + + // Fungsi untuk generate hash QR code berdasarkan ID penyaluran dan ID warga + String generateQrCodeHash(String penyaluranId, String wargaId) { + // Kombinasikan ID penyaluran dan ID warga dengan timestamp untuk keunikan + final String combinedData = + '$penyaluranId-$wargaId-${DateTime.now().millisecondsSinceEpoch}'; + // Gunakan SHA-256 untuk menghasilkan hash yang aman + final bytes = utf8.encode(combinedData); + final hash = sha256.convert(bytes); + // Kembalikan representasi string dari hash + return hash.toString(); + } } diff --git a/lib/app/modules/petugas_desa/views/detail_penyaluran_page.dart b/lib/app/modules/petugas_desa/views/detail_penyaluran_page.dart index 4d7501b..c874dfd 100644 --- a/lib/app/modules/petugas_desa/views/detail_penyaluran_page.dart +++ b/lib/app/modules/petugas_desa/views/detail_penyaluran_page.dart @@ -6,6 +6,8 @@ import 'package:penyaluran_app/app/theme/app_theme.dart'; import 'package:penyaluran_app/app/utils/date_time_helper.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/qr_scanner_page.dart'; class DetailPenyaluranPage extends StatelessWidget { final controller = Get.put(DetailPenyaluranController()); @@ -75,20 +77,31 @@ class DetailPenyaluranPage extends StatelessWidget { ), ); }), - floatingActionButton: Obx(() => showScrollToTop.value - ? FloatingActionButton( - mini: true, - backgroundColor: AppTheme.primaryColor, - child: const Icon(Icons.arrow_upward), - onPressed: () { - scrollController.animateTo( - 0, - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); - }, - ) - : const SizedBox.shrink()), + floatingActionButton: Obx(() { + final status = controller.penyaluran.value?.status?.toUpperCase() ?? ''; + if (status == 'AKTIF') { + return FloatingActionButton( + backgroundColor: AppTheme.primaryColor, + onPressed: () => _showQrCodeScanner(context), + tooltip: 'Scan QR Code', + child: const Icon(Icons.qr_code_scanner, color: Colors.white), + ); + } + return showScrollToTop.value + ? FloatingActionButton( + mini: true, + backgroundColor: AppTheme.primaryColor, + child: const Icon(Icons.arrow_upward), + onPressed: () { + scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + }, + ) + : const SizedBox.shrink(); + }), bottomNavigationBar: Obx(() { final status = controller.penyaluran.value?.status?.toUpperCase() ?? ''; if (status == 'AKTIF' || @@ -749,6 +762,18 @@ class DetailPenyaluranPage extends StatelessWidget { ); } + // Method untuk mendapatkan warna status + Color _getStatusColor(String status) { + switch (status.toUpperCase()) { + case 'DITERIMA': + return AppTheme.successColor; + case 'BELUMMENERIMA': + return AppTheme.warningColor; + default: + return Colors.grey; + } + } + Widget _buildPenerimaItem( BuildContext context, PenerimaPenyaluranModel item) { final warga = item.warga; @@ -848,52 +873,6 @@ class DetailPenyaluranPage extends StatelessWidget { ); } - Widget _buildStatusChipNew(String status) { - Color backgroundColor; - Color textColor = Colors.white; - String statusText = _getStatusPenerimaanText(status); - IconData iconData; - - // Konversi status ke format yang diinginkan - if (status.toUpperCase() == 'DITERIMA') { - backgroundColor = AppTheme.successColor; - statusText = 'Sudah Menerima'; - iconData = Icons.check_circle; - } else { - // Semua status selain DITERIMA dianggap sebagai BELUMMENERIMA - backgroundColor = AppTheme.warningColor; - statusText = 'Belum Menerima'; - iconData = Icons.pending; - } - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - iconData, - color: textColor, - size: 12, - ), - const SizedBox(width: 4), - Text( - statusText, - style: TextStyle( - color: textColor, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ); - } - Widget _buildStatusBadge(String status) { Color backgroundColor; Color textColor = Colors.white; @@ -1111,8 +1090,9 @@ class DetailPenyaluranPage extends StatelessWidget { ); } - void _showDetailPenerima( + void _showDetailPenerimaan( BuildContext context, PenerimaPenyaluranModel penerima) { + // Tampilkan detail penerimaan menggunakan bottom sheet final warga = penerima.warga; final bool sudahMenerima = penerima.statusPenerimaan?.toUpperCase() == 'DITERIMA'; @@ -1125,7 +1105,7 @@ class DetailPenyaluranPage extends StatelessWidget { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), - builder: (context) { + builder: (BuildContext context) { return Container( padding: const EdgeInsets.all(20), constraints: BoxConstraints( @@ -1185,7 +1165,26 @@ class DetailPenyaluranPage extends StatelessWidget { ), ), const SizedBox(height: 4), - _buildStatusChipNew(penerima.statusPenerimaan ?? '-'), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: statusColor.withOpacity(0.3)), + ), + child: Text( + sudahMenerima + ? 'Sudah Menerima' + : 'Belum Menerima', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: statusColor, + ), + ), + ), ], ), ), @@ -1366,25 +1365,26 @@ class DetailPenyaluranPage extends StatelessWidget { // Tombol tutup SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () => Navigator.pop(context), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey.shade200, - foregroundColor: Colors.black87, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey.shade200, + foregroundColor: Colors.black87, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - child: const Text( - 'Tutup', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + ), + child: const Text( + 'Tutup', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), - )), + ), + ), + ), ], ), ), @@ -1527,4 +1527,68 @@ class DetailPenyaluranPage extends StatelessWidget { return filteredList; } + + // Fungsi untuk membuka scanner QR code + void _showQrCodeScanner(BuildContext context) async { + if (controller.penyaluran.value?.id == null) return; + + final result = await Get.to( + () => QrScannerPage( + penyaluranId: controller.penyaluran.value!.id!, + ), + ); + + if (result == true) { + // Refresh data setelah kembali dari scanner jika berhasil + await controller.refreshData(); + Get.snackbar( + 'Berhasil', + 'Penerima berhasil diverifikasi', + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } + } + + // Widget untuk menampilkan QR Code (dikosongkan untuk petugas desa) + Widget _buildQrCodeSection(PenerimaPenyaluranModel penerima) { + // Widget QR Code tetap dibuat tapi tidak digunakan di petugas desa + return const SizedBox.shrink(); + } + + // Widget untuk status chip baru + Widget _buildStatusChipNew(String status) { + final Color statusColor; + final String statusText; + + if (status.toUpperCase() == 'DITERIMA') { + statusColor = AppTheme.successColor; + statusText = 'Sudah Menerima'; + } else { + statusColor = AppTheme.warningColor; + statusText = 'Belum Menerima'; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: statusColor.withOpacity(0.3)), + ), + child: Text( + statusText, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: statusColor, + ), + ), + ); + } + + void _showDetailPenerima( + BuildContext context, PenerimaPenyaluranModel penerima) { + _showDetailPenerimaan(context, penerima); + } } diff --git a/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart b/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart index 354e408..fbb5c01 100644 --- a/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart +++ b/lib/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart @@ -29,6 +29,7 @@ class KonfirmasiPenerimaPage extends StatefulWidget { } class _KonfirmasiPenerimaPageState extends State { + late PenerimaPenyaluranModel penerima; final controller = Get.find(); final ImagePicker _picker = ImagePicker(); File? _buktiPenerimaan; @@ -46,6 +47,16 @@ class _KonfirmasiPenerimaPageState extends State { // Untuk menyimpan gambar tanda tangan Uint8List? _signatureImage; + @override + void initState() { + super.initState(); + // Menggunakan data penerima yang diberikan dari arguments + penerima = widget.penerima; + print('KonfirmasiPenerimaPage - ID Penerima: ${penerima.id}'); + print( + 'KonfirmasiPenerimaPage - Nama Penerima: ${penerima.warga?['nama_lengkap']}'); + } + @override void dispose() { // Pastikan controller signature dibersihkan @@ -55,7 +66,7 @@ class _KonfirmasiPenerimaPageState extends State { @override Widget build(BuildContext context) { - final warga = widget.penerima.warga; + final warga = penerima.warga; return Scaffold( appBar: AppBar( @@ -66,40 +77,38 @@ class _KonfirmasiPenerimaPageState extends State { onPressed: () => Get.back(), ), ), - body: Obx( - () => controller.isProcessing.value || _isLoading - ? const Center( + body: _isLoading + ? const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Sedang memproses konfirmasi...'), + ], + ), + ) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), child: Column( - mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text('Sedang memproses konfirmasi...'), + _buildDetailPenerimaSection(warga), + const SizedBox(height: 16), + _buildDetailBantuanSection(), + const SizedBox(height: 16), + _buildFotoBuktiSection(), + const SizedBox(height: 16), + _buildTandaTanganSection(), + const SizedBox(height: 16), + _buildFormPersetujuanSection(), + const SizedBox(height: 24), + _buildKonfirmasiButton(), ], ), - ) - : SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildDetailPenerimaSection(warga), - const SizedBox(height: 16), - _buildDetailBantuanSection(), - const SizedBox(height: 16), - _buildFotoBuktiSection(), - const SizedBox(height: 16), - _buildTandaTanganSection(), - const SizedBox(height: 16), - _buildFormPersetujuanSection(), - const SizedBox(height: 24), - _buildKonfirmasiButton(), - ], - ), - ), ), - ), + ), ); } @@ -156,6 +165,9 @@ class _KonfirmasiPenerimaPageState extends State { ), const Divider(), + //nama lengkap + _buildInfoRow('Nama Lengkap', warga?['nama_lengkap'] ?? 'Bajiyadi'), + // NIK _buildInfoRow('NIK', warga?['nik'] ?? '3201020107030010'), const Divider(), @@ -215,6 +227,8 @@ class _KonfirmasiPenerimaPageState extends State { String satuan = ''; if (widget.bentukBantuan?.satuan != null) { satuan = widget.bentukBantuan!.satuan!; + } else if (penerima.satuan != null) { + satuan = penerima.satuan!; } else { // Default satuan jika tidak ada satuan = 'Kg'; @@ -227,10 +241,35 @@ class _KonfirmasiPenerimaPageState extends State { final waktuSelesai = DateTimeHelper.formatTime( widget.tanggalPenyaluran!.add(const Duration(hours: 1))); tanggalWaktuPenyaluran = '$tanggal $waktuMulai-$waktuSelesai'; + } else if (penerima.penyaluranBantuan != null && + penerima.penyaluranBantuan!['tanggal_penyaluran'] != null) { + final tanggalPenyaluran = + DateTime.parse(penerima.penyaluranBantuan!['tanggal_penyaluran']); + final tanggal = DateTimeHelper.formatDate(tanggalPenyaluran); + final waktuMulai = DateTimeHelper.formatTime(tanggalPenyaluran); + final waktuSelesai = DateTimeHelper.formatTime( + tanggalPenyaluran.add(const Duration(hours: 1))); + tanggalWaktuPenyaluran = '$tanggal $waktuMulai-$waktuSelesai'; } else { tanggalWaktuPenyaluran = '09 April 2025 13:00-14:00'; } + // Ambil nama bantuan dari model jika tersedia + String namaBantuan = 'Beras'; + if (widget.bentukBantuan?.nama != null) { + namaBantuan = widget.bentukBantuan!.nama!; + } else if (penerima.kategoriNama != null) { + namaBantuan = penerima.kategoriNama!; + } + + // Ambil jumlah bantuan + String jumlahBantuan = '5'; + if (widget.jumlahBantuan != null) { + jumlahBantuan = widget.jumlahBantuan!; + } else if (penerima.jumlahBantuan != null) { + jumlahBantuan = penerima.jumlahBantuan.toString(); + } + return Card( elevation: 2, shape: RoundedRectangleBorder( @@ -252,13 +291,11 @@ class _KonfirmasiPenerimaPageState extends State { const SizedBox(height: 16), // Bentuk Bantuan - _buildInfoRow( - 'Bentuk Bantuan', widget.bentukBantuan?.nama ?? 'Beras'), + _buildInfoRow('Bentuk Bantuan', namaBantuan), const Divider(), // Nilai Bantuan - _buildInfoRow( - 'Nilai Bantuan', '${widget.jumlahBantuan ?? '5'}$satuan'), + _buildInfoRow('Nilai Bantuan', '$jumlahBantuan$satuan'), const Divider(), // Tanggal Penyaluran @@ -654,39 +691,62 @@ class _KonfirmasiPenerimaPageState extends State { File? signatureFile; try { - String imageUrl; - String signatureUrl; + String imageUrl = ''; + String signatureUrl = ''; // Upload bukti penerimaan - imageUrl = await controller.uploadBuktiPenerimaan(_buktiPenerimaan!.path); + try { + imageUrl = + await controller.uploadBuktiPenerimaan(_buktiPenerimaan!.path); + print('Berhasil upload bukti penerimaan: $imageUrl'); + } catch (e) { + // Jika upload bukti penerimaan gagal, tampilkan pesan dan hentikan proses + print('Error upload bukti penerimaan: $e'); + throw Exception('Gagal mengupload bukti penerimaan: $e'); + } // Simpan tanda tangan ke file sementara dan upload - tempDir = await Directory.systemTemp.createTemp('signature'); - signatureFile = File('${tempDir.path}/signature.png'); - await signatureFile.writeAsBytes(_signatureImage!); + try { + tempDir = await Directory.systemTemp.createTemp('signature'); + signatureFile = File('${tempDir.path}/signature.png'); + await signatureFile.writeAsBytes(_signatureImage!); - print('Signature file path: ${signatureFile.path}'); - print('Signature file exists: ${signatureFile.existsSync()}'); - print('Signature file size: ${signatureFile.lengthSync()} bytes'); + print('Signature file path: ${signatureFile.path}'); + print('Signature file exists: ${signatureFile.existsSync()}'); + print('Signature file size: ${signatureFile.lengthSync()} bytes'); - signatureUrl = await controller.uploadBuktiPenerimaan( - signatureFile.path, - isTandaTangan: true, - ); + signatureUrl = await controller.uploadBuktiPenerimaan( + signatureFile.path, + isTandaTangan: true, + ); + print('Berhasil upload tanda tangan: $signatureUrl'); + } catch (e) { + // Jika upload tanda tangan gagal, tampilkan pesan dan hentikan proses + print('Error upload tanda tangan: $e'); + throw Exception('Gagal mengupload tanda tangan: $e'); + } // Konfirmasi penerimaan - await controller.konfirmasiPenerimaan( - widget.penerima, - buktiPenerimaan: imageUrl, - tandaTangan: signatureUrl, - ); + try { + print('Melakukan konfirmasi penerimaan untuk ID: ${penerima.id}'); + await controller.konfirmasiPenerimaan( + penerima, + buktiPenerimaan: imageUrl, + tandaTangan: signatureUrl, + ); + print('Konfirmasi penerimaan berhasil'); + } catch (e) { + // Jika konfirmasi penerimaan gagal, tampilkan pesan dan hentikan proses + print('Error konfirmasi penerimaan: $e'); + throw Exception('Gagal melakukan konfirmasi penerimaan: $e'); + } // Hapus file sementara sebelum navigasi try { - if (signatureFile.existsSync()) { + if (signatureFile != null && signatureFile.existsSync()) { await signatureFile.delete(); } - if (tempDir.existsSync()) { + if (tempDir != null && tempDir.existsSync()) { await tempDir.delete(); } } catch (e) { @@ -736,9 +796,12 @@ class _KonfirmasiPenerimaPageState extends State { print('Error saat menghapus file sementara: $e'); } - setState(() { - _isLoading = false; - }); + // Pastikan state loading diatur kembali ke false + if (mounted) { + setState(() { + _isLoading = false; + }); + } } } } diff --git a/lib/app/modules/petugas_desa/views/qr_scanner_page.dart b/lib/app/modules/petugas_desa/views/qr_scanner_page.dart new file mode 100644 index 0000000..6442d21 --- /dev/null +++ b/lib/app/modules/petugas_desa/views/qr_scanner_page.dart @@ -0,0 +1,196 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; +import 'package:penyaluran_app/app/theme/app_theme.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/controllers/detail_penyaluran_controller.dart'; + +class QrScannerPage extends StatefulWidget { + final String penyaluranId; + + const QrScannerPage({ + super.key, + required this.penyaluranId, + }); + + @override + State createState() => _QrScannerPageState(); +} + +class _QrScannerPageState extends State { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + QRViewController? controller; + bool isScanning = true; + final DetailPenyaluranController detailController = + Get.find(); + bool isProcessing = false; + + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller!.pauseCamera(); + } else if (Platform.isIOS) { + controller!.resumeCamera(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Scan QR Code Penerima'), + backgroundColor: AppTheme.primaryColor, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Get.back(), + ), + actions: [ + IconButton( + icon: const Icon(Icons.flash_on), + onPressed: () async { + await controller?.toggleFlash(); + }, + ), + IconButton( + icon: const Icon(Icons.flip_camera_ios), + onPressed: () async { + await controller?.flipCamera(); + }, + ), + ], + ), + body: Column( + children: [ + Expanded( + flex: 5, + child: Stack( + alignment: Alignment.center, + children: [ + QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + overlay: QrScannerOverlayShape( + borderColor: AppTheme.primaryColor, + borderRadius: 10, + borderLength: 30, + borderWidth: 10, + cutOutSize: MediaQuery.of(context).size.width * 0.8, + ), + ), + // Tampilkan animasi loading saat memproses QR + if (isProcessing) + Container( + width: double.infinity, + height: double.infinity, + color: Colors.black45, + child: const Center( + child: CircularProgressIndicator( + color: Colors.white, + ), + ), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + color: Colors.black, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Arahkan kamera ke QR Code penerima bantuan', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'QR Code akan otomatis terbaca', + style: TextStyle( + color: Colors.grey[400], + fontSize: 12, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ), + ); + } + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((scanData) async { + if (!isScanning || isProcessing) return; + + if (scanData.code != null) { + isScanning = false; + setState(() { + isProcessing = true; + }); + + try { + final qrHash = scanData.code!; + print('QR Hash yang terbaca: $qrHash'); + + final bool result = await detailController.verifikasiPenerimaByQrCode( + widget.penyaluranId, qrHash); + + if (result) { + // Success - Kembali ke halaman sebelumnya dengan hasil true + Get.back(result: true); + } else { + // QR Code tidak valid atau tidak ditemukan + Get.snackbar( + 'Gagal Memverifikasi', + 'QR Code tidak valid atau tidak terdaftar pada penyaluran ini', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + + // Lanjutkan pemindaian setelah delay + await Future.delayed(const Duration(seconds: 2)); + isScanning = true; + } + } catch (e) { + print('Error pemindaian QR: $e'); + Get.snackbar( + 'Error', + 'Terjadi kesalahan saat memproses QR code: ${e.toString()}', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + + // Lanjutkan pemindaian setelah delay + await Future.delayed(const Duration(seconds: 2)); + isScanning = true; + } finally { + if (mounted) { + setState(() { + isProcessing = false; + }); + } + } + } + }); + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } +} diff --git a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart index af50e9f..161d022 100644 --- a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart +++ b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart @@ -85,8 +85,11 @@ class WargaDashboardController extends GetxController { } // Fungsi untuk mengambil data penerima penyaluran - Future fetchPenerimaPenyaluran() async { + Future> fetchPenerimaPenyaluran() async { try { + // Reset data terlebih dahulu untuk memastikan tidak ada data lama yang tersimpan + penerimaPenyaluran.clear(); + // Pertama, cari warga_id berdasarkan user_id final wargaResponse = await _supabaseService.client .from('warga') @@ -175,13 +178,21 @@ class WargaDashboardController extends GetxController { penerima.add(model); } + // Update nilai observable penerimaPenyaluran.assignAll(penerima); var diterima = penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length; totalPenyaluranDiterima.value = diterima; + + // Log untuk debugging + print( + 'Berhasil memuat ${penerima.length} data penerimaan untuk warga ID: $wargaId'); + + return penerima; } catch (e) { print('Error fetchPenerimaPenyaluran: $e'); + return []; } } diff --git a/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart b/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart index eb8db50..5f99b89 100644 --- a/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart +++ b/lib/app/modules/warga/views/warga_detail_penerimaan_view.dart @@ -5,6 +5,7 @@ import 'package:penyaluran_app/app/data/models/penerima_penyaluran_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:penyaluran_app/app/widgets/status_badge.dart'; +import 'package:qr_flutter/qr_flutter.dart'; class WargaDetailPenerimaanView extends GetView { const WargaDetailPenerimaanView({super.key}); @@ -14,6 +15,11 @@ class WargaDetailPenerimaanView extends GetView { final Map args = Get.arguments ?? {}; final id = args['id']; + // Segera muat ulang data penerimaan ketika halaman dibuka + WidgetsBinding.instance.addPostFrameCallback((_) { + controller.fetchPenerimaPenyaluran(); + }); + if (id == null) { return Scaffold( appBar: AppBar( @@ -28,64 +34,67 @@ class WargaDetailPenerimaanView extends GetView { // Konversi id ke string untuk memastikan kompatibilitas dengan model final String penyaluranId = id.toString(); - // Cari data penerimaan berdasarkan ID - final PenerimaPenyaluranModel? penyaluran = controller.penerimaPenyaluran - .firstWhereOrNull((item) => item.id == penyaluranId); + // Gunakan GetBuilder untuk memastikan widget dibangun ulang ketika data berubah + return Obx(() { + // Cari data penerimaan berdasarkan ID + final PenerimaPenyaluranModel? penyaluran = controller.penerimaPenyaluran + .firstWhereOrNull((item) => item.id == penyaluranId); + + if (penyaluran == null) { + return Scaffold( + appBar: AppBar( + title: const Text('Detail Penerimaan'), + ), + body: const Center( + child: Text('Data penerimaan tidak ditemukan'), + ), + ); + } + + final bool isDiterima = penyaluran.statusPenerimaan == 'DITERIMA'; - if (penyaluran == null) { return Scaffold( appBar: AppBar( title: const Text('Detail Penerimaan'), + elevation: 0, + backgroundColor: Get.theme.primaryColor, + foregroundColor: Colors.white, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Get.back(), + ), ), - body: const Center( - child: Text('Data penerimaan tidak ditemukan'), + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Get.theme.primaryColor.withOpacity(0.05), + Colors.white, + ], + ), + ), + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeaderSection(penyaluran), + const SizedBox(height: 16), + _buildDetailSection(penyaluran), + const SizedBox(height: 16), + _buildLocationSection(penyaluran), + const SizedBox(height: 16), + if (isDiterima) _buildBuktiPenerimaanSection(penyaluran), + if (isDiterima) const SizedBox(height: 16), + _buildAdditionalInfoSection(penyaluran), + ], + ), + ), ), ); - } - - final bool isDiterima = penyaluran.statusPenerimaan == 'DITERIMA'; - - return Scaffold( - appBar: AppBar( - title: const Text('Detail Penerimaan'), - elevation: 0, - backgroundColor: Get.theme.primaryColor, - foregroundColor: Colors.white, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Get.back(), - ), - ), - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Get.theme.primaryColor.withOpacity(0.05), - Colors.white, - ], - ), - ), - child: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeaderSection(penyaluran), - const SizedBox(height: 16), - _buildDetailSection(penyaluran), - const SizedBox(height: 16), - _buildLocationSection(penyaluran), - const SizedBox(height: 16), - if (isDiterima) _buildBuktiPenerimaanSection(penyaluran), - if (isDiterima) const SizedBox(height: 16), - _buildAdditionalInfoSection(penyaluran), - ], - ), - ), - ), - ); + }); } Widget _buildHeaderSection(PenerimaPenyaluranModel penyaluran) { @@ -577,12 +586,96 @@ class WargaDetailPenerimaanView extends GetView { .format(penyaluran.tanggalPenerimaan!) : 'Tidak tersedia', ), + + // Tambahkan QR Code untuk verifikasi + if (penyaluran.qrCodeHash != null && + penyaluran.qrCodeHash!.isNotEmpty) ...[ + const Divider(height: 24), + _buildQrCodeSection(penyaluran), + ], ], ), ), ); } + // Tambahkan widget untuk menampilkan QR Code + Widget _buildQrCodeSection(PenerimaPenyaluranModel penyaluran) { + // Pastikan menggunakan data terbaru dari model dan cetak ke log untuk debugging + final qrData = penyaluran.qrCodeHash ?? 'invalid-qr-code'; + print('QR Code Hash: $qrData'); // Log untuk debugging + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.qr_code, + color: Get.theme.primaryColor, + ), + const SizedBox(width: 8), + const Text( + 'QR Code Verifikasi', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 16), + Center( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.2), + blurRadius: 4, + spreadRadius: 1, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + // Gunakan UniqueKey() untuk memaksa rebuild widget QR code + QrImageView( + key: UniqueKey(), + data: qrData, + version: QrVersions.auto, + size: 200.0, + backgroundColor: Colors.white, + errorStateBuilder: (cxt, err) { + return const Center( + child: Text( + "QR Code tidak tersedia", + textAlign: TextAlign.center, + ), + ); + }, + ), + const SizedBox(height: 12), + Text( + 'Tunjukkan QR Code ini kepada petugas untuk verifikasi penerimaan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade700, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ); + } + Widget _buildDetailItem({ required IconData icon, required String title, diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 1b02b21..6b6f2dc 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -15,6 +15,8 @@ import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_penyaluran_ import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penyaluran_binding.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/views/riwayat_pengaduan_view.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/riwayat_pengaduan_binding.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/qr_scanner_page.dart'; +import 'package:penyaluran_app/app/modules/petugas_desa/views/konfirmasi_penerima_page.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/donatur_binding.dart'; @@ -146,5 +148,18 @@ class AppPages { page: () => const RiwayatPengaduanView(), binding: RiwayatPengaduanBinding(), ), + GetPage( + name: _Paths.qrScanner, + page: () => QrScannerPage(penyaluranId: Get.parameters['id'] ?? ''), + binding: PenyaluranBinding(), + ), + GetPage( + name: _Paths.konfirmasiPenerimaQr, + page: () => KonfirmasiPenerimaPage( + penerima: Get.arguments['penerima'], + tanggalPenyaluran: Get.arguments['tanggal_penyaluran'], + ), + binding: PenyaluranBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 759fc64..19cdcee 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -31,6 +31,8 @@ abstract class Routes { static const detailPengaduan = _Paths.detailPengaduan; static const wargaDetailPengaduan = _Paths.wargaDetailPengaduan; static const riwayatPengaduan = _Paths.riwayatPengaduan; + static const qrScanner = _Paths.qrScanner; + static const konfirmasiPenerimaQr = _Paths.konfirmasiPenerimaQr; } abstract class _Paths { @@ -64,4 +66,6 @@ abstract class _Paths { static const detailPengaduan = '/detail-pengaduan'; static const wargaDetailPengaduan = '/warga/detail-pengaduan'; static const riwayatPengaduan = '/petugas-desa/riwayat-pengaduan'; + static const qrScanner = '/petugas-desa/qr-scanner'; + static const konfirmasiPenerimaQr = '/petugas-desa/konfirmasi-penerima/:id'; } diff --git a/pubspec.lock b/pubspec.lock index 675ae16..2203e56 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -98,7 +98,7 @@ packages: source: hosted version: "0.3.4+2" crypto: - dependency: transitive + dependency: "direct main" description: name: crypto sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -409,10 +409,10 @@ packages: dependency: transitive description: name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_macos: dependency: transitive description: @@ -601,10 +601,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" platform: dependency: transitive description: @@ -637,6 +637,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_code_scanner_plus: + dependency: "direct main" + description: + name: qr_code_scanner_plus + sha256: "39696b50d277097ee4d90d4292de36f38c66213a4f5216a06b2bdd2b63117859" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" realtime_client: dependency: transitive description: @@ -859,7 +883,7 @@ packages: source: hosted version: "1.4.0" url_launcher: - dependency: transitive + dependency: "direct main" description: name: url_launcher sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" @@ -990,10 +1014,10 @@ packages: dependency: transitive description: name: win32 - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.12.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5c26ce8..29f12b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,15 @@ dependencies: flutter_staggered_animations: ^1.1.1 timeline_tile: ^2.0.0 + # Library untuk QR code + qr_flutter: ^4.1.0 + qr_code_scanner_plus: ^2.0.10+1 + + # Untuk URL launcher + url_launcher: ^6.2.5 + # Untuk fungsi hash + crypto: ^3.0.3 + dev_dependencies: flutter_test: sdk: flutter