diff --git a/images/clockin_presensi.png b/images/clockin_presensi.png new file mode 100644 index 0000000..7b21264 Binary files /dev/null and b/images/clockin_presensi.png differ diff --git a/images/clockout_presensi.png b/images/clockout_presensi.png new file mode 100644 index 0000000..89eb71d Binary files /dev/null and b/images/clockout_presensi.png differ diff --git a/images/finger_presensi.png b/images/finger_presensi.png new file mode 100644 index 0000000..4ad467f Binary files /dev/null and b/images/finger_presensi.png differ diff --git a/images/presensi_finger1.png b/images/presensi_finger1.png new file mode 100644 index 0000000..a280a0b Binary files /dev/null and b/images/presensi_finger1.png differ diff --git a/images/presensi_finger2.png b/images/presensi_finger2.png new file mode 100644 index 0000000..b5bb72c Binary files /dev/null and b/images/presensi_finger2.png differ diff --git a/images/sync_white.png b/images/sync_white.png new file mode 100644 index 0000000..727de28 Binary files /dev/null and b/images/sync_white.png differ diff --git a/lib/app/constant.dart b/lib/app/constant.dart index 0efe329..107e38b 100644 --- a/lib/app/constant.dart +++ b/lib/app/constant.dart @@ -19,6 +19,8 @@ class Constant { static Color textLightGrey = const Color(0xff919EAB); static Color textOrange = const Color(0xffF15A29); static Color textDarkGrey = const Color(0xff637381); + static Color bgAddressPresensi = const Color.fromRGBO(241, 90, 41, 0.08); + static Color textWhite = Color(0xffFDFDFD); // size convertion static double getActualXPhone({ @@ -57,6 +59,13 @@ class Constant { ); } + static TextStyle titleH2_400_12({required BuildContext context}) { + return TextStyle( + fontSize: Constant.getActualYPhone(context: context, y: 12), + fontWeight: FontWeight.w400, + fontFamily: 'Public Sans'); + } + static TextStyle titleH2_600_14({required BuildContext context}) { return TextStyle( fontSize: Constant.getActualYPhone(context: context, y: 14), @@ -79,6 +88,14 @@ class Constant { ); } + static TextStyle titlePresensiH2_700({required BuildContext context}) { + return TextStyle( + fontFamily: 'Quicksand', + fontSize: Constant.getActualYPhone(context: context, y: 24), + fontWeight: FontWeight.w700, + ); + } + static TextStyle time_700({required BuildContext context}) { return TextStyle( fontSize: Constant.getActualYPhone(context: context, y: 28), diff --git a/lib/app/route.dart b/lib/app/route.dart index c7d1069..f9bd7c3 100644 --- a/lib/app/route.dart +++ b/lib/app/route.dart @@ -1,4 +1,5 @@ import 'package:absensi_sas_flutter/screen/home/home_screen_v1.dart'; +import 'package:absensi_sas_flutter/screen/presensi/presensi_screen.dart'; import 'package:flutter/material.dart'; import '../screen/home/home_screen.dart'; import '../test_flutter_map.dart'; @@ -9,6 +10,7 @@ const loginRoute = "/loginRoute"; const splashRoute = "/splashRoute"; const testFlutterMapRoute = "/testFlutterMapRoute"; const homeRoute = "/homeRoute"; +const presensiRoute = "/presensiRoute"; class AppRoute { static Route generateRoute(RouteSettings settings) { @@ -35,6 +37,17 @@ class AppRoute { }); } + // presensi route + if (settings.name == presensiRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)), + child: PresensiScreen(), + ); + }); + } + // splash screen if (settings.name == splashRoute) { return MaterialPageRoute(builder: (context) { diff --git a/lib/main.dart b/lib/main.dart index 52e66e5..ee89c99 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -46,7 +46,7 @@ class MyApp extends StatelessWidget { ), // home: TestMap(), // initialRoute: loginRoute, - initialRoute: homeRoute, + initialRoute: presensiRoute, // initialRoute: testFlutterMapRoute, onGenerateRoute: AppRoute.generateRoute, ); diff --git a/lib/screen/login/login_screen.dart b/lib/screen/login/login_screen.dart index 988b19d..093c903 100644 --- a/lib/screen/login/login_screen.dart +++ b/lib/screen/login/login_screen.dart @@ -163,27 +163,27 @@ class LoginScreen extends HookConsumerWidget { SizedBox( height: Constant.getActualYPhone(context: context, y: 100), ), - // (currentUserGoogle != null) - // ? Container( - // child: ListTile( - // leading: GoogleUserCircleAvatar( - // identity: currentUserGoogle, - // ), - // title: Text( - // currentUserGoogle.displayName ?? "", - // ), - // subtitle: Text( - // currentUserGoogle.email, - // ), - // trailing: IconButton( - // icon: Icon(Icons.logout_outlined), - // onPressed: () async { - // await googleSignIn.disconnect(); - // }, - // ), - // ), - // ) - // : + (currentUserGoogle != null) + ? Container( + child: ListTile( + leading: GoogleUserCircleAvatar( + identity: currentUserGoogle, + ), + title: Text( + currentUserGoogle.displayName ?? "", + ), + subtitle: Text( + currentUserGoogle.email, + ), + trailing: IconButton( + icon: Icon(Icons.logout_outlined), + onPressed: () async { + await googleSignIn.disconnect(); + }, + ), + ), + ) + : // Logo Landscape Padding( padding: EdgeInsets.only( diff --git a/lib/screen/presensi/presensi_screen.dart b/lib/screen/presensi/presensi_screen.dart new file mode 100644 index 0000000..3123056 --- /dev/null +++ b/lib/screen/presensi/presensi_screen.dart @@ -0,0 +1,438 @@ +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 '../../widget/real_time.dart'; + +class PresensiScreen extends HookConsumerWidget { + const PresensiScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentAddressUserLocation = useState(""); + final isLoadingAddressUserLocation = useState(false); + + 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 (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"); + + if (address != "") { + currentAddressUserLocation.value = address; + } + } else { + isLoadingAddressUserLocation.value = false; + SanckbarWidget( + context, 'Tidak dapat menemukan alamat.', snackbarType.error); + print("Tidak dapat menemukan alamat."); + } + } 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); + } + } + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + getAddressFromLocation(); + }); + return () {}; + }, []); + + 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, + ), + 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: [ + // 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(); + }, + 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), + ), + + // gambar icon presensi + Container( + width: Constant.getActualXPhone(context: context, x: 130), + height: Constant.getActualYPhone(context: context, y: 130), + child: FittedBox( + child: FloatingActionButton( + onPressed: () {}, + 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 + ), + ), + ), + ), + ), + ), + + // Container( + // width: Constant.getActualXPhone(context: context, x: 100), + // height: Constant.getActualYPhone(context: context, y: 200), + // decoration: BoxDecoration( + // image: DecorationImage( + // fit: BoxFit.cover, + // image: AssetImage( + // 'images/presensi_finger1.png'), // Ganti dengan path gambar Anda + // ), + // ), + // ), + + Spacer(), + + // Expanded( + // child: SizedBox(), + // ), + + // 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), + ), + Text( + '--:--', + style: Constant.titlePresensiH2_700( + context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + Text( + 'Clock In', + style: Constant.titleH2_700(context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + ], + ), + 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), + ), + Text( + '--:--', + style: Constant.titlePresensiH2_700( + context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + Text( + 'Clock Out', + style: Constant.titleH2_700(context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + // bottomNavigationBar: Container( + // width: Constant.getActualXPhone(context: context, x: 390), + // height: Constant.getActualYPhone(context: context, y: 84), + // decoration: BoxDecoration( + // color: Color(0xFFFFFFFF), + // boxShadow: [ + // BoxShadow( + // offset: Offset(0, -1), + // blurRadius: 8, + // spreadRadius: -8, + // color: Color.fromRGBO(0, 0, 0, 0.10), + // ), + // ], + // ), + // child: Row( + // children: [ + // Expanded( + // child: Container( + // child: 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), + // ), + // Text( + // '--:--', + // style: Constant.titlePresensiH2_700(context: context) + // .copyWith( + // color: Constant.textTrueBlack, + // ), + // ), + // Text( + // 'Clock In', + // style: Constant.titleH2_700(context: context).copyWith( + // color: Constant.textTrueBlack, + // ), + // ), + // ], + // ), + // )), + // Expanded( + // child: Container( + // child: 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), + // ), + // Text( + // '--:--', + // style: Constant.titlePresensiH2_700(context: context) + // .copyWith( + // color: Constant.textTrueBlack, + // ), + // ), + // Text( + // 'Clock Out', + // style: Constant.titleH2_700(context: context).copyWith( + // color: Constant.textTrueBlack, + // ), + // ), + // ], + // ), + // )), + // ], + // ), + // ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 6780abe..732bac8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -576,6 +576,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" + url: "https://pub.dev" + source: hosted + version: "11.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e + url: "https://pub.dev" + source: hosted + version: "11.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + url: "https://pub.dev" + source: hosted + version: "9.1.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + url: "https://pub.dev" + source: hosted + version: "3.12.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + url: "https://pub.dev" + source: hosted + version: "0.1.3" pigeon: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2237c6c..82db703 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: intl: ^0.17.0 graphql: ^5.1.3 top_snackbar_flutter: ^3.1.0 + permission_handler: ^11.0.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 1ece8f2..5be7b60 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7f101a7..b949ced 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST geolocator_windows + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST