import 'dart:convert'; import 'package:absensi_sas_flutter/provider/current_check_jam_presensi_provider.dart'; import 'package:absensi_sas_flutter/screen/presensi/check_distance_provider.dart'; import 'package:absensi_sas_flutter/screen/presensi/check_presensi_jam_provider.dart'; import 'package:absensi_sas_flutter/screen/presensi/presensi_clock_in_provider.dart'; import 'package:absensi_sas_flutter/widget/real_date.dart'; import 'package:absensi_sas_flutter/widget/sankbar_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../app/constant.dart'; import '../../app/route.dart'; import '../../provider/current_check_distance_provider.dart'; import '../../provider/current_user_provider.dart'; import '../../widget/custom_drawer.dart'; import '../../widget/real_time.dart'; import 'presensi_clock_out_provider.dart'; class PresensiScreen extends HookConsumerWidget { const PresensiScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final currentAddressUserLocation = useState(""); final isLoadingAddressUserLocation = useState(false); final isLoadingProsesCheckDistance = useState(false); final varCurrentDistanceProvider = ref.watch(currentCheckDistanceProvider); final varCurrentCheckJamProvider = ref.watch(currentCheckJamPresensiProvider); final selectedUser = ref.watch(currentUserProvider); final positionLatitude = useState(""); final positionLongitude = useState(""); // final tTransactionCurrentDistance = useState("NULL"); Future getAddressFromLocation() async { try { isLoadingAddressUserLocation.value = true; // Mendapatkan posisi pengguna LocationPermission permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { isLoadingAddressUserLocation.value = false; SanckbarWidget(context, 'Izin lokasi ditolak', snackbarType.error); // Handle jika pengguna menolak izin lokasi print("Izin lokasi ditolak"); return; } Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); // Mendapatkan alamat dari posisi List placemarks = await placemarkFromCoordinates( position.latitude, position.longitude); if (positionLongitude.value.isEmpty && positionLatitude.value.isEmpty) { if (placemarks.isNotEmpty) { isLoadingAddressUserLocation.value = false; Placemark placemark = placemarks.first; // String address = // "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},"; String address = "${placemark.street}, ${placemark.subLocality}, ${placemark.subAdministrativeArea}, ${placemark.postalCode}"; print("Alamat: $address"); positionLatitude.value = position.latitude.toString(); positionLongitude.value = position.longitude.toString(); if (address != "") { currentAddressUserLocation.value = address; } } else { isLoadingAddressUserLocation.value = false; SanckbarWidget( context, 'Tidak dapat menemukan alamat.', snackbarType.error); print("Tidak dapat menemukan alamat."); } } else { // jika sudah dapat latitude dan logitude baru panggil check distance provider if (positionLatitude.value.isNotEmpty && positionLongitude.value.isNotEmpty) { print('check distance provider'); // panggil check distance provider ref.read(checkDistanceProvider.notifier).checkDistance( selectedUser?.model.staffId ?? "", selectedUser?.model.companyId ?? "", positionLatitude.value, positionLongitude.value, ); } } } catch (e) { print("Error: $e"); isLoadingAddressUserLocation.value = false; SanckbarWidget(context, 'Error : $e', snackbarType.error); } } Future requestLocationPermission() async { var status = await Permission.location.request(); isLoadingAddressUserLocation.value = true; if (status.isGranted) { // Izin diberikan, lanjutkan dengan mendapatkan lokasi getAddressFromLocation(); } else { isLoadingAddressUserLocation.value = false; // Izin ditolak, berikan pemberitahuan atau instruksi // print('Izin lokasi ditolak'); SanckbarWidget(context, 'Izin Ditolak', snackbarType.error); } } // check distance provider ref.listen(checkDistanceProvider, (prev, next) { print('status check distance ' + next.toString()); if (next is CheckDistanceStateLoading) { isLoadingProsesCheckDistance.value = true; } else if (next is CheckDistanceStateError) { isLoadingProsesCheckDistance.value = false; SanckbarWidget(context, next.message, snackbarType.warning); } else if (next is CheckDistanceStateDone) { isLoadingProsesCheckDistance.value = false; // tTransactionCurrentDistance.value = // varCurrentDistanceProvider?.currentDistance ?? ""; // print("distance XXX : "+tTransactionCurrentDistance.value); if (varCurrentDistanceProvider?.selfie == "TRUE") { Navigator.pushNamed(context, presensiSelfieRoute); } } }); // check jam presensi ref.listen(checkPresensiJamProvider, (prev, next) { if (next is CheckPresensiJamStateLoading) { isLoadingProsesCheckDistance.value = true; } else if (next is CheckPresensiJamStateError) { isLoadingProsesCheckDistance.value = false; SanckbarWidget( context, "Error : " + next.toString(), snackbarType.warning); } else if (next is CheckPresensiJamStateDone) { isLoadingProsesCheckDistance.value = false; } }); // proses presensi clock in ref.listen(presensiClockInProvider, (prev, next) { if (next is PresensiClockInStateLoading) { isLoadingProsesCheckDistance.value = true; } else if (next is PresensiClockInStateError) { isLoadingProsesCheckDistance.value = false; SanckbarWidget( context, "Error : " + next.toString(), snackbarType.warning); } else if (next is PresensiClockInStateDone) { isLoadingProsesCheckDistance.value = false; if (next.model == "OK") { SanckbarWidget(context, "Berhasil Absen Masuk", snackbarType.success); requestLocationPermission(); } // else{ // if(next.model == "WARNING"){ // SanckbarWidget(context, "Berhasil Absen Masuk", snackbarType.success); // } // } } }); // proses presensi clock in ref.listen(presensiClockOutProvider, (prev, next) { if (next is PresensiClockOutStateLoading) { isLoadingProsesCheckDistance.value = true; } else if (next is PresensiClockOutStateError) { isLoadingProsesCheckDistance.value = false; SanckbarWidget( context, "Error : " + next.toString(), snackbarType.warning); } else if (next is PresensiClockOutStateDone) { isLoadingProsesCheckDistance.value = false; if (next.model == "OK") { SanckbarWidget( context, "Berhasil Absen Pulang", snackbarType.success); requestLocationPermission(); } // else{ // if(next.model == "WARNING"){ // SanckbarWidget(context, "Berhasil Absen Masuk", snackbarType.success); // } // } } }); useEffect(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0"; if (staffID == "0") { //not login Navigator.of(context) .pushNamedAndRemoveUntil(loginRoute, (route) => true); // Navigator.popAndPushNamed(context, loginRoute); return; } // requestLocationPermission(); }); return () {}; }, []); useEffect(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { requestLocationPermission(); }); return () {}; }, []); // show konfirmasi dialog Future showConfirmationDialog( BuildContext context, String T_TransactionM_StaffID, String T_TransactionM_CompanyID, String T_TransactionCurrentLatitude, String T_TransactionCurrentLongitude, String T_TransactionCurrentDistance, String T_TransactionSelfiePhoto, String token, String tipeAbsen, ) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Konfirmasi'), content: Text('Apakah anda yakin untuk melakukan $tipeAbsen?'), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), actions: [ ElevatedButton( onPressed: () { T_TransactionSelfiePhoto = ""; Map param = { "T_TransactionM_StaffID": T_TransactionM_StaffID, "T_TransactionM_CompanyID": T_TransactionM_CompanyID, "T_TransactionCurrentLatitude": T_TransactionCurrentLatitude, "T_TransactionCurrentLongitude": T_TransactionCurrentLongitude, "T_TransactionCurrentDistance": T_TransactionCurrentDistance, "T_TransactionSelfiePhoto": T_TransactionSelfiePhoto, "token": token, "isSelfie": "FALSE" }; // print(param); if (tipeAbsen == "Clock In") { ref.read(presensiClockInProvider.notifier).presensiClockIn( T_TransactionM_StaffID, T_TransactionM_CompanyID, T_TransactionCurrentLatitude, T_TransactionCurrentLongitude, T_TransactionCurrentDistance, T_TransactionSelfiePhoto, token, "FALSE", param, ); Navigator.of(context).pop(); } else { if (tipeAbsen == "Clock Out") { ref .read(presensiClockOutProvider.notifier) .presensiClockOut( T_TransactionM_StaffID, T_TransactionM_CompanyID, T_TransactionCurrentLatitude, T_TransactionCurrentLongitude, T_TransactionCurrentDistance, T_TransactionSelfiePhoto, token, "FALSE", param, ); Navigator.of(context).pop(); } } }, child: Text( 'Yakin', style: Constant.logintitle_700(context: context).copyWith( fontWeight: FontWeight.w600, color: Constant.textWhite, ), ), style: ButtonStyle( backgroundColor: MaterialStateColor.resolveWith( (st) => Constant.textOrange), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide( color: Constant.textOrange, ), ), ), shadowColor: MaterialStateProperty.all(Color(0xffff48423d)), elevation: MaterialStateProperty.all(4.0), ), ), TextButton( onPressed: () { Navigator.of(context).pop(); // Tutup dialog }, style: ButtonStyle( backgroundColor: MaterialStateColor.resolveWith( (st) => Constant.textWhite), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide( color: Constant.textBlack, ), ), ), shadowColor: MaterialStateProperty.all(Color(0xffff48423d)), elevation: MaterialStateProperty.all(4.0), ), child: Text( 'Batal', style: TextStyle( color: Constant.textTrueBlack, ), ), ), ], ); }, ); } return Padding( padding: EdgeInsets.only( top: Constant.getActualYPhone(context: context, y: 30), ), child: Scaffold( appBar: AppBar( title: Text( // 'Home Screen', 'Presensi', overflow: TextOverflow.ellipsis, style: TextStyle( color: Constant.textBlack, ), ), backgroundColor: Constant.textWhite, iconTheme: IconThemeData( color: Constant.textBlack, ), // elevation: 1.0, elevation: 0.5, ), drawer: CustomDrawer(), body: SafeArea( child: Padding( padding: EdgeInsets.only( top: Constant.getActualYPhone(context: context, y: 58), left: Constant.getActualXPhone(context: context, x: 33), right: Constant.getActualXPhone(context: context, x: 27), ), child: Container( width: Constant.getActualXPhone(context: context, x: 390), // height: Constant.getActualYPhone(context: context, y: 844), height: MediaQuery.of(context).size.height, child: Column( children: [ // Text(ref.watch(currentCheckDistanceProvider)?.currentDistance ?? "NULL"), // Spacer(), // tanggal sekarang RealTimeFormattedDate(), SizedBox( height: Constant.getActualYPhone(context: context, y: 8), ), // jam sekarang RealTimeClock(), SizedBox( height: Constant.getActualYPhone(context: context, y: 24), ), // address dan refresh Container( width: Constant.getActualXPhone(context: context, x: 350), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), color: Constant.bgAddressPresensi, ), child: Padding( padding: EdgeInsets.only( left: Constant.getActualXPhone(context: context, x: 12), right: Constant.getActualXPhone(context: context, x: 12), top: Constant.getActualYPhone(context: context, y: 12), bottom: Constant.getActualYPhone(context: context, y: 12), ), child: Row( children: [ // Bagian kiri Padding( padding: EdgeInsets.only(left: 12, right: 8), child: Column( children: [ Icon( Icons.location_on_outlined, color: Constant.textDarkGrey, ), ], ), ), // Bagian tengah Expanded( child: Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Column( children: [ (isLoadingAddressUserLocation.value) ? Center( child: CircularProgressIndicator(), ) : Text( // 'Perumahan Grand House of Klodran No. 5, Klodran, Kec. Colomadu, Kabupaten Karanganyar, Jawa Tengah 57172', currentAddressUserLocation.value, overflow: TextOverflow.ellipsis, maxLines: 10, style: Constant.titleH2_400_12( context: context) .copyWith( color: Constant.textDarkGrey, ), ), ], ), ), ), // Bagian kanan Padding( padding: EdgeInsets.only(right: 12), child: Column( children: [ InkWell( onTap: () async { getAddressFromLocation(); // jika sudah dapat latitude dan logitude baru panggil check distance provider if (positionLatitude.value.isNotEmpty && positionLongitude.value.isNotEmpty) { // panggil check distance provider ref .read(checkDistanceProvider.notifier) .checkDistance( selectedUser?.model.staffId ?? "", selectedUser?.model.companyId ?? "", positionLatitude.value, positionLongitude.value, ); } }, child: Container( width: Constant.getActualXPhone( context: context, x: 36), height: Constant.getActualYPhone( context: context, y: 36), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Constant.textOrange, ), child: Image.asset( 'images/sync_white.png', // Path gambar untuk "Refresh" width: Constant.getActualXPhone( context: context, x: 20), height: Constant.getActualYPhone( context: context, y: 20), ), ), ), ], ), ), ], ), ), ), SizedBox( height: Constant.getActualYPhone(context: context, y: 50), ), // tombol aksi absen masuk & pulang if (varCurrentCheckJamProvider?.isAbsenClockIn == "TRUE" && varCurrentCheckJamProvider?.isAbsenClockOut == "TRUE") ...[ SizedBox.shrink() ] else ...[ if (varCurrentCheckJamProvider?.isAbsenClockIn == "FALSE" && varCurrentCheckJamProvider?.jamClockIn == "") ...[ // gambar icon presensi clock in (isLoadingAddressUserLocation.value) ? Center( child: CircularProgressIndicator(), ) : Container( width: Constant.getActualXPhone( context: context, x: 130), height: Constant.getActualYPhone( context: context, y: 130), child: FittedBox( child: FloatingActionButton( onPressed: () async { final T_TransactionM_StaffID = selectedUser?.model.staffId ?? ""; final T_TransactionM_CompanyID = selectedUser?.model.companyId ?? ""; final T_TransactionCurrentLatitude = positionLatitude.value; final T_TransactionCurrentLongitude = positionLongitude.value; final T_TransactionCurrentDistance = varCurrentDistanceProvider ?.currentDistance ?? ""; final T_TransactionSelfiePhoto = ""; final token = selectedUser?.token ?? ""; showConfirmationDialog( context, T_TransactionM_StaffID, T_TransactionM_CompanyID, T_TransactionCurrentLatitude, T_TransactionCurrentLongitude, T_TransactionCurrentDistance, T_TransactionSelfiePhoto, token, "Clock In"); }, backgroundColor: Color(0xFFFFFFFF), shape: CircleBorder(), child: Container( width: Constant.getActualXPhone( context: context, x: 130), height: Constant.getActualYPhone( context: context, y: 130), decoration: BoxDecoration( image: DecorationImage( // fit: BoxFit.cover, image: AssetImage( 'images/presensi_finger2.png'), // Ganti dengan path gambar Anda ), ), ), ), ), ), ] else ...[ // gambar icon presensi clock out if (varCurrentCheckJamProvider?.isAbsenClockIn == "TRUE" && varCurrentCheckJamProvider?.jamClockIn != "") ...[ (isLoadingAddressUserLocation.value) ? Center( child: CircularProgressIndicator(), ) : Container( width: Constant.getActualXPhone( context: context, x: 130), height: Constant.getActualYPhone( context: context, y: 130), child: FittedBox( child: FloatingActionButton( onPressed: () async { final T_TransactionM_StaffID = selectedUser?.model.staffId ?? ""; final T_TransactionM_CompanyID = selectedUser?.model.companyId ?? ""; final T_TransactionCurrentLatitude = positionLatitude.value; final T_TransactionCurrentLongitude = positionLongitude.value; final T_TransactionCurrentDistance = varCurrentDistanceProvider ?.currentDistance ?? ""; final T_TransactionSelfiePhoto = ""; final token = selectedUser?.token ?? ""; showConfirmationDialog( context, T_TransactionM_StaffID, T_TransactionM_CompanyID, T_TransactionCurrentLatitude, T_TransactionCurrentLongitude, T_TransactionCurrentDistance, T_TransactionSelfiePhoto, token, "Clock Out"); }, backgroundColor: Color(0xFFFFFFFF), shape: CircleBorder(), child: Container( width: Constant.getActualXPhone( context: context, x: 130), height: Constant.getActualYPhone( context: context, y: 130), decoration: BoxDecoration( image: DecorationImage( // fit: BoxFit.cover, image: AssetImage( 'images/presensi_finger_clok_out.png'), // Ganti dengan path gambar Anda ), ), ), ), ), ), ] ], ], Spacer(), // clock in & clock out Padding( padding: EdgeInsets.only( left: Constant.getActualXPhone(context: context, x: 70), right: Constant.getActualXPhone(context: context, x: 70), // top: // Constant.getActualYPhone(context: context, y: 12), bottom: Constant.getActualYPhone(context: context, y: 55), ), child: Container( width: Constant.getActualXPhone(context: context, x: 390), // color: Colors.amber, child: Row( children: [ Column( children: [ Image.asset( 'images/clockin_presensi.png', // Path gambar untuk "Clock In" width: Constant.getActualXPhone( context: context, x: 22), height: Constant.getActualYPhone( context: context, y: 22), ), SizedBox( height: Constant.getActualYPhone( context: context, y: 8), ), (isLoadingAddressUserLocation.value) ? Center( child: CircularProgressIndicator(), ) : (varCurrentCheckJamProvider ?.isAbsenClockIn == "TRUE" && varCurrentCheckJamProvider ?.jamClockIn != "") ? Text( // '--:--', varCurrentCheckJamProvider ?.jamClockIn ?? "NULL", style: Constant.titlePresensiH2_700( context: context) .copyWith( color: Constant.textTrueBlack, ), ) : Text( '--:--', style: Constant.titlePresensiH2_700( context: context) .copyWith( color: Constant.textTrueBlack, ), ), SizedBox( height: Constant.getActualYPhone( context: context, y: 4), ), Text( 'Clock In', style: Constant.titleH2_700(context: context) .copyWith( color: Constant.textLightGrey, ), ), ], ), Spacer(), Column( children: [ Image.asset( 'images/clockout_presensi.png', // Path gambar untuk "Check In" width: Constant.getActualXPhone( context: context, x: 22), height: Constant.getActualYPhone( context: context, y: 22), ), SizedBox( height: Constant.getActualYPhone( context: context, y: 8), ), (isLoadingAddressUserLocation.value) ? Center( child: CircularProgressIndicator(), ) : (varCurrentCheckJamProvider ?.isAbsenClockOut == "TRUE" && varCurrentCheckJamProvider ?.jamClockOut != "") ? Text( // '--:--', varCurrentCheckJamProvider ?.jamClockOut ?? "NULL", style: Constant.titlePresensiH2_700( context: context) .copyWith( color: Constant.textTrueBlack, ), ) : Text( '--:--', style: Constant.titlePresensiH2_700( context: context) .copyWith( color: Constant.textTrueBlack, ), ), SizedBox( height: Constant.getActualYPhone( context: context, y: 4), ), Text( 'Clock Out', style: Constant.titleH2_700(context: context) .copyWith( color: Constant.textLightGrey, ), ), ], ), ], ), ), ), ], ), ), ), ), ), ); } }