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:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
@ -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 <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>
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
x
|
||||
x
|
||||
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
|
||||
<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
|
||||
u
|
||||
u
|
||||
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|
|
@ -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 ɠס<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>
|
||||
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>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
|
||||
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
|
||||
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
|
||||
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|
|
||||
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>
|
||||
~
|
||||
~
|
||||
|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>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
|
||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2~
|
@ -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 <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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
~
|
||||
|
|
||||
|
|
||||
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
|
||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2v
|
||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2v
|
@ -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 Ԣס<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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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 {
|
||||
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
|
||||
<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
|
||||
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
|
||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2y
|
||||
( <20><><EFBFBD><EFBFBD><EFBFBD>2y
|
@ -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) {
|
||||
|
@ -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<String, dynamic> toJson() => {
|
||||
@ -103,5 +106,6 @@ class PenerimaPenyaluranModel {
|
||||
"deskripsi_penyaluran": deskripsiPenyaluran,
|
||||
"lokasi_penyaluran_nama": lokasiPenyaluranNama,
|
||||
"lokasi_penyaluran_alamat": lokasiPenyaluranAlamat,
|
||||
"qr_code_hash": qrCodeHash,
|
||||
};
|
||||
}
|
||||
|
@ -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<void> logout() async {
|
||||
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();
|
||||
|
||||
// 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(
|
||||
|
@ -445,4 +445,57 @@ class DetailPenyaluranController extends GetxController {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<AuthController>();
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class KonfirmasiPenerimaPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
|
||||
late PenerimaPenyaluranModel penerima;
|
||||
final controller = Get.find<DetailPenyaluranController>();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
File? _buktiPenerimaan;
|
||||
@ -46,6 +47,16 @@ class _KonfirmasiPenerimaPageState extends State<KonfirmasiPenerimaPage> {
|
||||
// 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<KonfirmasiPenerimaPage> {
|
||||
|
||||
@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<KonfirmasiPenerimaPage> {
|
||||
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<KonfirmasiPenerimaPage> {
|
||||
),
|
||||
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<KonfirmasiPenerimaPage> {
|
||||
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<KonfirmasiPenerimaPage> {
|
||||
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<KonfirmasiPenerimaPage> {
|
||||
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<KonfirmasiPenerimaPage> {
|
||||
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<KonfirmasiPenerimaPage> {
|
||||
print('Error saat menghapus file sementara: $e');
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
// Pastikan state loading diatur kembali ke false
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
196
lib/app/modules/petugas_desa/views/qr_scanner_page.dart
Normal file
196
lib/app/modules/petugas_desa/views/qr_scanner_page.dart
Normal 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();
|
||||
}
|
||||
}
|
@ -85,8 +85,11 @@ class WargaDashboardController extends GetxController {
|
||||
}
|
||||
|
||||
// Fungsi untuk mengambil data penerima penyaluran
|
||||
Future<void> fetchPenerimaPenyaluran() async {
|
||||
Future<List<PenerimaPenyaluranModel>> 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 [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<WargaDashboardController> {
|
||||
const WargaDetailPenerimaanView({super.key});
|
||||
@ -14,6 +15,11 @@ class WargaDetailPenerimaanView extends GetView<WargaDashboardController> {
|
||||
final Map<String, dynamic> 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<WargaDashboardController> {
|
||||
// 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<WargaDashboardController> {
|
||||
.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,
|
||||
|
@ -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(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
|
48
pubspec.lock
48
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:
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user