Perbarui model dan tampilan untuk mendukung fungsionalitas QR code dalam proses verifikasi penerima. Tambahkan properti qrCodeHash pada PenerimaPenyaluranModel dan implementasikan metode verifikasi QR code di DetailPenyaluranController. Modifikasi tampilan di WargaDetailPenerimaanView dan DetailPenyaluranPage untuk menampilkan QR code dan menambahkan fungsionalitas pemindaian QR code. Perbarui rute aplikasi untuk mendukung navigasi ke halaman pemindaian QR code dan konfirmasi penerima.

This commit is contained in:
Khafidh Fuadi
2025-03-19 13:11:24 +07:00
parent 984b8336f0
commit 0597f0aea0
19 changed files with 839 additions and 263 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><>ס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  NJ<EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
 
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  <08><>ס<EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~ }D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\additional_project_files.txt  NJ<EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  <08><>ס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build.json  NJ<EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD> <EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  <08><>ס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\android_gradle_build_mini.json  NJ<EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2p
n n
lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  <08><>ס<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t lD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja  NJ<EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2t
r r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  <08><>ס<EFBFBD>2y pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build.ninja.txt  NJ<EFBFBD><EFBFBD><EFBFBD>2y
w w
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  <08><>ס<EFBFBD>2 uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\build_file_index.txt  NJ<EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2z K <20><><EFBFBD><EFBFBD><EFBFBD>2z
x x
vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  NJ<><C78A><EFBFBD>2 ~ vD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json  NJ<><C78A><EFBFBD>2 ~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  NJ<><C78A><EFBFBD>2 zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\compile_commands.json.bin  NJ<><C78A><EFBFBD>2
<EFBFBD> <EFBFBD>
<EFBFBD> <EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  NJ<><C78A><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2w <EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\metadata_generation_command.txt  NJ<><C78A><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2w
u u
sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  NJ<><C78A><EFBFBD>2 sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\arm64-v8a\prefab_config.json  NJ<><C78A><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2|  ( <20><><EFBFBD><EFBFBD><EFBFBD>2|

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  ɠס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD> <EFBFBD>
D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  ɠס<EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\additional_project_files.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
~ ~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  ɠס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> |D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build.json  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
<EFBFBD> <EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  ʠס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2r <EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\android_gradle_build_mini.json  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2r
p p
nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  ʠס<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2v nD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2v
t t
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  ʠס<EFBFBD>2{ rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build.ninja.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2{
y y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  ʠס<EFBFBD>2 wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\build_file_index.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2| K <20><><EFBFBD><EFBFBD><EFBFBD>2|
z z
xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 <09> xD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 <09>
~ ~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2 |D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
<EFBFBD> <EFBFBD>
<EFBFBD> <EFBFBD>
<EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2y <EFBFBD>D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2y
w w
uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 uD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\armeabi-v7a\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2~  ( <20><><EFBFBD><EFBFBD><EFBFBD>2~

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><>ס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{ ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2{
y y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  <08><>ס<EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2x wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\additional_project_files.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2x
v v
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  <08><>ס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2} tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2}
{ {
yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  <08><>ס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j yD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\android_gradle_build_mini.json  <08><><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2j
h h
fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  <08><>ס<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n fD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja  <08><><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2n
l l
jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  <08><>ס<EFBFBD>2s jD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build.ninja.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2s
q q
oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  <08><>ס<EFBFBD>2 oD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\build_file_index.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2t K <20><><EFBFBD><EFBFBD><EFBFBD>2t
r r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 x pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 x
v v
tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2 tD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
~ ~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2q zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2q
o o
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2v  ( <20><><EFBFBD><EFBFBD><EFBFBD>2v

View File

@ -2,27 +2,27 @@ C/C++ Structured LogO
M M
KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC KC:\dev\flutter\packages\flutter_tools\gradle\src\main\groovy\CMakeLists.txtC
A A
?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  Ԣס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2~ ?com.android.build.gradle.internal.cxx.io.EncodedFileFingerPrint  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2~
| |
zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  Ԣס<EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2{ zD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\additional_project_files.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2  <20><><EFBFBD><EFBFBD><EFBFBD>2{
y y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  Ԣס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD> wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build.json  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>
~ ~
|D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  Ԣס<EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2m |D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\android_gradle_build_mini.json  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18> <20><><EFBFBD><EFBFBD><EFBFBD>2m
k k
iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  Ԣס<EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q iD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2<18><> <20><><EFBFBD><EFBFBD><EFBFBD>2q
o o
mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  Ԣס<EFBFBD>2v mD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build.ninja.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2v
t t
rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  Ԣס<EFBFBD>2 rD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\build_file_index.txt  <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2
K <20><><EFBFBD><EFBFBD><EFBFBD>2w K <20><><EFBFBD><EFBFBD><EFBFBD>2w
u u
sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 { sD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 {
y y
wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2 wD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\compile_commands.json.bin  <08><><EFBFBD><EFBFBD><EFBFBD>2
<EFBFBD> <EFBFBD>
 
}D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2t }D:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\metadata_generation_command.txt  <08><><EFBFBD><EFBFBD><EFBFBD>2 <18> <20><><EFBFBD><EFBFBD><EFBFBD>2t
r r
pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2 pD:\KULIAH\Matkul\SKRIPSI\penyaluran_app\penyaluran_app\android\app\.cxx\Debug\626b5o2n\x86_64\prefab_config.json  <08><><EFBFBD><EFBFBD><EFBFBD>2
 ( <20><><EFBFBD><EFBFBD><EFBFBD>2y  ( <20><><EFBFBD><EFBFBD><EFBFBD>2y

View File

@ -2,6 +2,7 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
jcenter()
} }
} }
@ -10,7 +11,9 @@ subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
} }
subprojects { subprojects {
project.evaluationDependsOn(":app") if (project.name.contains('qr_code_scanner_plus') || project.name.contains('flutter')) {
project.evaluationDependsOn(":app")
}
} }
tasks.register("clean", Delete) { tasks.register("clean", Delete) {

View File

@ -22,6 +22,7 @@ class PenerimaPenyaluranModel {
final String? deskripsiPenyaluran; // Deskripsi penyaluran final String? deskripsiPenyaluran; // Deskripsi penyaluran
final String? lokasiPenyaluranNama; // Nama lokasi penyaluran final String? lokasiPenyaluranNama; // Nama lokasi penyaluran
final String? lokasiPenyaluranAlamat; // Alamat lokasi penyaluran final String? lokasiPenyaluranAlamat; // Alamat lokasi penyaluran
final String? qrCodeHash; // Hash untuk QR code
PenerimaPenyaluranModel({ PenerimaPenyaluranModel({
this.id, this.id,
@ -45,6 +46,7 @@ class PenerimaPenyaluranModel {
this.deskripsiPenyaluran, this.deskripsiPenyaluran,
this.lokasiPenyaluranNama, this.lokasiPenyaluranNama,
this.lokasiPenyaluranAlamat, this.lokasiPenyaluranAlamat,
this.qrCodeHash,
}); });
factory PenerimaPenyaluranModel.fromRawJson(String str) => factory PenerimaPenyaluranModel.fromRawJson(String str) =>
@ -79,6 +81,7 @@ class PenerimaPenyaluranModel {
deskripsiPenyaluran: json["deskripsi_penyaluran"], deskripsiPenyaluran: json["deskripsi_penyaluran"],
lokasiPenyaluranNama: json["lokasi_penyaluran_nama"], lokasiPenyaluranNama: json["lokasi_penyaluran_nama"],
lokasiPenyaluranAlamat: json["lokasi_penyaluran_alamat"], lokasiPenyaluranAlamat: json["lokasi_penyaluran_alamat"],
qrCodeHash: json["qr_code_hash"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -103,5 +106,6 @@ class PenerimaPenyaluranModel {
"deskripsi_penyaluran": deskripsiPenyaluran, "deskripsi_penyaluran": deskripsiPenyaluran,
"lokasi_penyaluran_nama": lokasiPenyaluranNama, "lokasi_penyaluran_nama": lokasiPenyaluranNama,
"lokasi_penyaluran_alamat": lokasiPenyaluranAlamat, "lokasi_penyaluran_alamat": lokasiPenyaluranAlamat,
"qr_code_hash": qrCodeHash,
}; };
} }

View File

@ -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/models/user_model.dart';
import 'package:penyaluran_app/app/data/providers/auth_provider.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/routes/app_pages.dart';
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
class AuthController extends GetxController { class AuthController extends GetxController {
static AuthController get to => Get.find(); static AuthController get to => Get.find();
@ -265,14 +266,29 @@ class AuthController extends GetxController {
// Metode untuk logout // Metode untuk logout
Future<void> logout() async { Future<void> logout() async {
try { try {
// Ambil semua controller yang mungkin perlu dibersihkan
try {
final wargaController = Get.find<WargaDashboardController>();
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(); await _authProvider.signOut();
// Reset semua state
_user.value = null; _user.value = null;
_hasLoadedProfile.value = false; // Reset flag saat logout _hasLoadedProfile.value = false;
isWargaProfileComplete.value = false; isWargaProfileComplete.value = false;
// Bersihkan dependensi form sebelum navigasi // Bersihkan dependensi form sebelum navigasi
clearFormDependencies(); clearFormDependencies();
// Navigasi ke halaman login
Get.offAllNamed(Routes.login); Get.offAllNamed(Routes.login);
} catch (e) { } catch (e) {
Get.snackbar( Get.snackbar(

View File

@ -445,4 +445,57 @@ class DetailPenyaluranController extends GetxController {
isLoading.value = false; isLoading.value = false;
} }
} }
// Metode untuk verifikasi penerima berdasarkan QR code
Future<bool> 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<String, dynamic> sanitizedPenerimaData =
Map<String, dynamic>.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;
}
}
} }

View File

@ -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/services/supabase_service.dart';
import 'package:penyaluran_app/app/utils/date_time_helper.dart'; import 'package:penyaluran_app/app/utils/date_time_helper.dart';
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:crypto/crypto.dart';
class JadwalPenyaluranController extends GetxController { class JadwalPenyaluranController extends GetxController {
final AuthController _authController = Get.find<AuthController>(); final AuthController _authController = Get.find<AuthController>();
@ -415,38 +417,42 @@ class JadwalPenyaluranController extends GetxController {
// Buat data penerima penyaluran untuk setiap pengajuan yang disetujui // Buat data penerima penyaluran untuk setiap pengajuan yang disetujui
for (var pengajuan in pengajuanData) { for (var pengajuan in pengajuanData) {
// Generate QR code hash unik untuk setiap penerima
final String qrCodeHash =
generateQrCodeHash(penyaluranId, pengajuan['warga_id']);
final penerimaPenyaluran = { final penerimaPenyaluran = {
'penyaluran_bantuan_id': penyaluranId, 'penyaluran_bantuan_id': penyaluranId,
'warga_id': pengajuan['warga_id'], 'warga_id': pengajuan['warga_id'],
'stok_bantuan_id': skemaBantuanCache[skemaId]?.stokBantuanId, 'stok_bantuan_id': skemaBantuanCache[skemaId]?.stokBantuanId,
'status_penerimaan': 'MENUNGGU', 'status_penerimaan': 'BELUMMENERIMA',
'qr_code_hash': qrCodeHash,
}; };
// Simpan data penerima ke database
await _supabaseService.client await _supabaseService.client
.from('penerima_penyaluran') .from('penerima_penyaluran')
.insert(penerimaPenyaluran); .insert(penerimaPenyaluran);
} }
// Refresh data // Setelah berhasil menambahkan, refresh data
await loadJadwalData(); await loadJadwalData();
await loadPermintaanPenjadwalanData();
// Kembali ke halaman sebelumnya // Tampilkan pesan sukses
Get.back();
// Tampilkan notifikasi sukses
Get.snackbar( Get.snackbar(
'Sukses', 'Sukses',
'Penyaluran berhasil ditambahkan', 'Jadwal penyaluran bantuan telah dibuat',
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green, backgroundColor: Colors.green,
colorText: Colors.white, colorText: Colors.white,
); );
} catch (e) { } catch (e) {
print('Error menambahkan penyaluran: $e'); print('Error: $e');
Get.snackbar( Get.snackbar(
'Error', 'Gagal',
'Gagal menambahkan penyaluran: ${e.toString()}', 'Terjadi kesalahan saat menambahkan jadwal penyaluran',
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red, backgroundColor: Colors.red,
colorText: Colors.white, colorText: Colors.white,
); );
@ -454,4 +460,16 @@ class JadwalPenyaluranController extends GetxController {
isLoading.value = false; 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();
}
} }

View File

@ -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:penyaluran_app/app/utils/date_time_helper.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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: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 { class DetailPenyaluranPage extends StatelessWidget {
final controller = Get.put(DetailPenyaluranController()); final controller = Get.put(DetailPenyaluranController());
@ -75,20 +77,31 @@ class DetailPenyaluranPage extends StatelessWidget {
), ),
); );
}), }),
floatingActionButton: Obx(() => showScrollToTop.value floatingActionButton: Obx(() {
? FloatingActionButton( final status = controller.penyaluran.value?.status?.toUpperCase() ?? '';
mini: true, if (status == 'AKTIF') {
backgroundColor: AppTheme.primaryColor, return FloatingActionButton(
child: const Icon(Icons.arrow_upward), backgroundColor: AppTheme.primaryColor,
onPressed: () { onPressed: () => _showQrCodeScanner(context),
scrollController.animateTo( tooltip: 'Scan QR Code',
0, child: const Icon(Icons.qr_code_scanner, color: Colors.white),
duration: const Duration(milliseconds: 500), );
curve: Curves.easeInOut, }
); return showScrollToTop.value
}, ? FloatingActionButton(
) mini: true,
: const SizedBox.shrink()), 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(() { bottomNavigationBar: Obx(() {
final status = controller.penyaluran.value?.status?.toUpperCase() ?? ''; final status = controller.penyaluran.value?.status?.toUpperCase() ?? '';
if (status == 'AKTIF' || 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( Widget _buildPenerimaItem(
BuildContext context, PenerimaPenyaluranModel item) { BuildContext context, PenerimaPenyaluranModel item) {
final warga = item.warga; 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) { Widget _buildStatusBadge(String status) {
Color backgroundColor; Color backgroundColor;
Color textColor = Colors.white; Color textColor = Colors.white;
@ -1111,8 +1090,9 @@ class DetailPenyaluranPage extends StatelessWidget {
); );
} }
void _showDetailPenerima( void _showDetailPenerimaan(
BuildContext context, PenerimaPenyaluranModel penerima) { BuildContext context, PenerimaPenyaluranModel penerima) {
// Tampilkan detail penerimaan menggunakan bottom sheet
final warga = penerima.warga; final warga = penerima.warga;
final bool sudahMenerima = final bool sudahMenerima =
penerima.statusPenerimaan?.toUpperCase() == 'DITERIMA'; penerima.statusPenerimaan?.toUpperCase() == 'DITERIMA';
@ -1125,7 +1105,7 @@ class DetailPenyaluranPage extends StatelessWidget {
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
), ),
builder: (context) { builder: (BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
constraints: BoxConstraints( constraints: BoxConstraints(
@ -1185,7 +1165,26 @@ class DetailPenyaluranPage extends StatelessWidget {
), ),
), ),
const SizedBox(height: 4), 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 // Tombol tutup
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey.shade200, backgroundColor: Colors.grey.shade200,
foregroundColor: Colors.black87, foregroundColor: Colors.black87,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
),
), ),
child: const Text( ),
'Tutup', child: const Text(
style: TextStyle( 'Tutup',
fontSize: 16, style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16,
), fontWeight: FontWeight.bold,
), ),
)), ),
),
),
], ],
), ),
), ),
@ -1527,4 +1527,68 @@ class DetailPenyaluranPage extends StatelessWidget {
return filteredList; 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);
}
} }

View File

@ -29,6 +29,7 @@ class KonfirmasiPenerimaPage extends StatefulWidget {
} }
class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> { class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
late PenerimaPenyaluranModel penerima;
final controller = Get.find<DetailPenyaluranController>(); final controller = Get.find<DetailPenyaluranController>();
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
File? _buktiPenerimaan; File? _buktiPenerimaan;
@ -46,6 +47,16 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
// Untuk menyimpan gambar tanda tangan // Untuk menyimpan gambar tanda tangan
Uint8List? _signatureImage; 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 @override
void dispose() { void dispose() {
// Pastikan controller signature dibersihkan // Pastikan controller signature dibersihkan
@ -55,7 +66,7 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final warga = widget.penerima.warga; final warga = penerima.warga;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -66,40 +77,38 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
), ),
body: Obx( body: _isLoading
() => controller.isProcessing.value || _isLoading ? const Center(
? 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( child: Column(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CircularProgressIndicator(), _buildDetailPenerimaSection(warga),
SizedBox(height: 16), const SizedBox(height: 16),
Text('Sedang memproses konfirmasi...'), _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<KonfirmasiPenerimaPage> {
), ),
const Divider(), const Divider(),
//nama lengkap
_buildInfoRow('Nama Lengkap', warga?['nama_lengkap'] ?? 'Bajiyadi'),
// NIK // NIK
_buildInfoRow('NIK', warga?['nik'] ?? '3201020107030010'), _buildInfoRow('NIK', warga?['nik'] ?? '3201020107030010'),
const Divider(), const Divider(),
@ -215,6 +227,8 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
String satuan = ''; String satuan = '';
if (widget.bentukBantuan?.satuan != null) { if (widget.bentukBantuan?.satuan != null) {
satuan = widget.bentukBantuan!.satuan!; satuan = widget.bentukBantuan!.satuan!;
} else if (penerima.satuan != null) {
satuan = penerima.satuan!;
} else { } else {
// Default satuan jika tidak ada // Default satuan jika tidak ada
satuan = 'Kg'; satuan = 'Kg';
@ -227,10 +241,35 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
final waktuSelesai = DateTimeHelper.formatTime( final waktuSelesai = DateTimeHelper.formatTime(
widget.tanggalPenyaluran!.add(const Duration(hours: 1))); widget.tanggalPenyaluran!.add(const Duration(hours: 1)));
tanggalWaktuPenyaluran = '$tanggal $waktuMulai-$waktuSelesai'; 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 { } else {
tanggalWaktuPenyaluran = '09 April 2025 13:00-14:00'; 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( return Card(
elevation: 2, elevation: 2,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -252,13 +291,11 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Bentuk Bantuan // Bentuk Bantuan
_buildInfoRow( _buildInfoRow('Bentuk Bantuan', namaBantuan),
'Bentuk Bantuan', widget.bentukBantuan?.nama ?? 'Beras'),
const Divider(), const Divider(),
// Nilai Bantuan // Nilai Bantuan
_buildInfoRow( _buildInfoRow('Nilai Bantuan', '$jumlahBantuan$satuan'),
'Nilai Bantuan', '${widget.jumlahBantuan ?? '5'}$satuan'),
const Divider(), const Divider(),
// Tanggal Penyaluran // Tanggal Penyaluran
@ -654,39 +691,62 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
File? signatureFile; File? signatureFile;
try { try {
String imageUrl; String imageUrl = '';
String signatureUrl; String signatureUrl = '';
// Upload bukti penerimaan // 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 // Simpan tanda tangan ke file sementara dan upload
tempDir = await Directory.systemTemp.createTemp('signature'); try {
signatureFile = File('${tempDir.path}/signature.png'); tempDir = await Directory.systemTemp.createTemp('signature');
await signatureFile.writeAsBytes(_signatureImage!); signatureFile = File('${tempDir.path}/signature.png');
await signatureFile.writeAsBytes(_signatureImage!);
print('Signature file path: ${signatureFile.path}'); print('Signature file path: ${signatureFile.path}');
print('Signature file exists: ${signatureFile.existsSync()}'); print('Signature file exists: ${signatureFile.existsSync()}');
print('Signature file size: ${signatureFile.lengthSync()} bytes'); print('Signature file size: ${signatureFile.lengthSync()} bytes');
signatureUrl = await controller.uploadBuktiPenerimaan( signatureUrl = await controller.uploadBuktiPenerimaan(
signatureFile.path, signatureFile.path,
isTandaTangan: true, 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 // Konfirmasi penerimaan
await controller.konfirmasiPenerimaan( try {
widget.penerima, print('Melakukan konfirmasi penerimaan untuk ID: ${penerima.id}');
buktiPenerimaan: imageUrl, await controller.konfirmasiPenerimaan(
tandaTangan: signatureUrl, 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 // Hapus file sementara sebelum navigasi
try { try {
if (signatureFile.existsSync()) { if (signatureFile != null && signatureFile.existsSync()) {
await signatureFile.delete(); await signatureFile.delete();
} }
if (tempDir.existsSync()) { if (tempDir != null && tempDir.existsSync()) {
await tempDir.delete(); await tempDir.delete();
} }
} catch (e) { } catch (e) {
@ -736,9 +796,12 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
print('Error saat menghapus file sementara: $e'); print('Error saat menghapus file sementara: $e');
} }
setState(() { // Pastikan state loading diatur kembali ke false
_isLoading = false; if (mounted) {
}); setState(() {
_isLoading = false;
});
}
} }
} }
} }

View File

@ -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<QrScannerPage> createState() => _QrScannerPageState();
}
class _QrScannerPageState extends State<QrScannerPage> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRViewController? controller;
bool isScanning = true;
final DetailPenyaluranController detailController =
Get.find<DetailPenyaluranController>();
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();
}
}

View File

@ -85,8 +85,11 @@ class WargaDashboardController extends GetxController {
} }
// Fungsi untuk mengambil data penerima penyaluran // Fungsi untuk mengambil data penerima penyaluran
Future<void> fetchPenerimaPenyaluran() async { Future<List<PenerimaPenyaluranModel>> fetchPenerimaPenyaluran() async {
try { try {
// Reset data terlebih dahulu untuk memastikan tidak ada data lama yang tersimpan
penerimaPenyaluran.clear();
// Pertama, cari warga_id berdasarkan user_id // Pertama, cari warga_id berdasarkan user_id
final wargaResponse = await _supabaseService.client final wargaResponse = await _supabaseService.client
.from('warga') .from('warga')
@ -175,13 +178,21 @@ class WargaDashboardController extends GetxController {
penerima.add(model); penerima.add(model);
} }
// Update nilai observable
penerimaPenyaluran.assignAll(penerima); penerimaPenyaluran.assignAll(penerima);
var diterima = var diterima =
penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length; penerima.where((p) => p.statusPenerimaan == 'DITERIMA').length;
totalPenyaluranDiterima.value = diterima; totalPenyaluranDiterima.value = diterima;
// Log untuk debugging
print(
'Berhasil memuat ${penerima.length} data penerimaan untuk warga ID: $wargaId');
return penerima;
} catch (e) { } catch (e) {
print('Error fetchPenerimaPenyaluran: $e'); print('Error fetchPenerimaPenyaluran: $e');
return [];
} }
} }

View File

@ -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/modules/warga/controllers/warga_dashboard_controller.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart';
import 'package:penyaluran_app/app/widgets/status_badge.dart'; import 'package:penyaluran_app/app/widgets/status_badge.dart';
import 'package:qr_flutter/qr_flutter.dart';
class WargaDetailPenerimaanView extends GetView<WargaDashboardController> { class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
const WargaDetailPenerimaanView({super.key}); const WargaDetailPenerimaanView({super.key});
@ -14,6 +15,11 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
final Map<String, dynamic> args = Get.arguments ?? {}; final Map<String, dynamic> args = Get.arguments ?? {};
final id = args['id']; final id = args['id'];
// Segera muat ulang data penerimaan ketika halaman dibuka
WidgetsBinding.instance.addPostFrameCallback((_) {
controller.fetchPenerimaPenyaluran();
});
if (id == null) { if (id == null) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -28,64 +34,67 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
// Konversi id ke string untuk memastikan kompatibilitas dengan model // Konversi id ke string untuk memastikan kompatibilitas dengan model
final String penyaluranId = id.toString(); final String penyaluranId = id.toString();
// Cari data penerimaan berdasarkan ID // Gunakan GetBuilder untuk memastikan widget dibangun ulang ketika data berubah
final PenerimaPenyaluranModel? penyaluran = controller.penerimaPenyaluran return Obx(() {
.firstWhereOrNull((item) => item.id == penyaluranId); // 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( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Detail Penerimaan'), 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( body: Container(
child: Text('Data penerimaan tidak ditemukan'), 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) { Widget _buildHeaderSection(PenerimaPenyaluranModel penyaluran) {
@ -577,12 +586,96 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
.format(penyaluran.tanggalPenerimaan!) .format(penyaluran.tanggalPenerimaan!)
: 'Tidak tersedia', : '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({ Widget _buildDetailItem({
required IconData icon, required IconData icon,
required String title, required String title,

View File

@ -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/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/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/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/penerima_binding.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/donatur_binding.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/bindings/donatur_binding.dart';
@ -146,5 +148,18 @@ class AppPages {
page: () => const RiwayatPengaduanView(), page: () => const RiwayatPengaduanView(),
binding: RiwayatPengaduanBinding(), 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(),
),
]; ];
} }

View File

@ -31,6 +31,8 @@ abstract class Routes {
static const detailPengaduan = _Paths.detailPengaduan; static const detailPengaduan = _Paths.detailPengaduan;
static const wargaDetailPengaduan = _Paths.wargaDetailPengaduan; static const wargaDetailPengaduan = _Paths.wargaDetailPengaduan;
static const riwayatPengaduan = _Paths.riwayatPengaduan; static const riwayatPengaduan = _Paths.riwayatPengaduan;
static const qrScanner = _Paths.qrScanner;
static const konfirmasiPenerimaQr = _Paths.konfirmasiPenerimaQr;
} }
abstract class _Paths { abstract class _Paths {
@ -64,4 +66,6 @@ abstract class _Paths {
static const detailPengaduan = '/detail-pengaduan'; static const detailPengaduan = '/detail-pengaduan';
static const wargaDetailPengaduan = '/warga/detail-pengaduan'; static const wargaDetailPengaduan = '/warga/detail-pengaduan';
static const riwayatPengaduan = '/petugas-desa/riwayat-pengaduan'; static const riwayatPengaduan = '/petugas-desa/riwayat-pengaduan';
static const qrScanner = '/petugas-desa/qr-scanner';
static const konfirmasiPenerimaQr = '/petugas-desa/konfirmasi-penerima/:id';
} }

View File

@ -45,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.7.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -98,7 +98,7 @@ packages:
source: hosted source: hosted
version: "0.3.4+2" version: "0.3.4+2"
crypto: crypto:
dependency: transitive dependency: "direct main"
description: description:
name: crypto name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
@ -141,10 +141,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -409,10 +409,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image_picker_linux name: image_picker_linux
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.1+1" version: "0.2.1+2"
image_picker_macos: image_picker_macos:
dependency: transitive dependency: transitive
description: description:
@ -601,10 +601,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.2" version: "6.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -637,6 +637,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" 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: realtime_client:
dependency: transitive dependency: transitive
description: description:
@ -859,7 +883,7 @@ packages:
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
url_launcher: url_launcher:
dependency: transitive dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
@ -990,10 +1014,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.10.1" version: "5.12.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -72,6 +72,15 @@ dependencies:
flutter_staggered_animations: ^1.1.1 flutter_staggered_animations: ^1.1.1
timeline_tile: ^2.0.0 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: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter