42 Commits

Author SHA1 Message Date
sindhu
40d6f5ec2f step 18 : perbaikan login pada useeffect dan logout pada mutation 2024-08-29 11:05:18 +07:00
sindhu
5bf87205cc step 17 : fix cors ketika menggunakan base url graphql di server 2024-08-28 17:13:32 +07:00
sindhu
1dd0e61a02 step 16 : get user location google map, refresh location google map absen selfie dan absen normal (bukan selfie) 2024-08-28 16:29:54 +07:00
sindhu
ab9089f8b4 step 15 : buat custom google map widget 2024-08-28 15:46:38 +07:00
sindhu
e06bd02a21 step 14 : custom marker google map 2024-08-28 15:26:42 +07:00
sindhu
6bab2dbef3 step 13 : show google map flutter web & current location function 2024-08-28 12:02:47 +07:00
sindhu
40453ab16b step 12 : ubah dari nomitism ke google apis untuk address 2024-08-28 10:34:27 +07:00
sindhu
102760babc step 11 : get location now using javascript in flutter 2024-08-27 17:58:59 +07:00
sindhu
7bd8527df2 step 10 : clock in & clock out tanpa wajah 2024-08-27 15:40:54 +07:00
sindhu
a7d83956ec step 9 : clock in & clock out selfie sudah bisa, need fix at clear camera controller after take photo 2024-08-27 14:13:00 +07:00
sindhu
3806462d7b step 8 : fix issue camera prevent hardware karena OBS terinstall 2024-08-27 08:48:34 +07:00
sindhu
f85afe7103 step 7 : presensi selfie get address and coordinate fix 2024-08-26 12:39:53 +07:00
sindhu
62409f9feb step 6 : perbaikan login_screen jsonDecode obj and redirect home 2024-08-26 07:46:59 +07:00
sindhu
08dd105fe5 step 5 : perbaikan login logic di login screen 2024-08-25 12:22:15 +07:00
sindhu1993
b4dea334d1 step 4 : setting google sign di platform web 2024-08-23 11:16:20 +07:00
sindhu1993
461fb00aed step 3 : create index.html and backup 2024-08-23 09:07:43 +07:00
sindhu1993
6e15b23ded step 2 : update dependecy 2024-08-23 09:01:06 +07:00
sindhu1993
9a78ec1d92 step 1 : create branch from main, upgrade flutter 3.24.1 2024-08-23 08:22:07 +07:00
Sas Andy
7d1e528546 Merge branch 'andy/coba' 2024-02-22 10:56:12 +07:00
Sas Andy
eb7236671e route , approval screen & approval detail 2024-02-22 10:38:38 +07:00
Sas Andy
3790e9e11a approval menu 2024-02-22 10:37:29 +07:00
Sas Andy
c23e68de36 theme & package 2024-02-22 10:37:01 +07:00
Sas Andy
d35ca7ab8b Revisi constant & model 2024-02-22 10:36:22 +07:00
sindhu
f4df67fe7f step 22 : komen ketidakhadiran & lembur 2024-02-05 14:42:15 +07:00
sindhu
409b8561e7 step 21 : rekap kehadiran di home screen & update versi 1.0.2 2024-02-05 11:56:54 +07:00
sindhu
8d8c5a38bc step 20 : bug logout google sign disconnect done 2024-02-05 09:17:43 +07:00
sindhu
46d04f5aff step 19 : icon aplikasi, setting build apk 2024-01-29 11:09:58 +07:00
sindhu
0bdc5df67f step 18 : icon apps, renaming package name, build apk 2024-01-29 08:43:52 +07:00
sindhu
84057f32b0 step 17 : proteksi presensi selfie button clock in & clock out 2024-01-27 18:36:07 +07:00
sindhu
79c0729979 step 16 : add drawer di home screen, sukses msg custom dialog untuk presensi selfie 2024-01-27 11:12:03 +07:00
sindhu
a38a983561 step 15 : fix route navigator, not fixed 2024-01-26 23:37:16 +07:00
sindhu
0596fd4a75 step 14 : fix bug loginscreen current user from package google sign in 2024-01-26 23:04:24 +07:00
sindhu
82d403b210 step 13 : fix ui based figma, fix isSelfie button di presensi screen & presensi selfie screen 2024-01-26 17:11:04 +07:00
sindhu
a0d383ea6c step 12 : proses absen masuk selfie, absen pulang selfie 2024-01-26 16:47:32 +07:00
sindhu
c551ee2cdb step 11 : proses clock in, clock out 2024-01-26 15:10:36 +07:00
sindhu
1af974881a step 10 : check distance selfie or not from back end 2024-01-26 10:53:46 +07:00
sindhu
757b72a9f0 step 9 : add fungsi permission handler, get current latitude, longitude 2024-01-26 08:01:33 +07:00
sindhu
b85e3515e8 step 8 : add ui homescreen 2024-01-25 15:08:52 +07:00
sindhu
b772e74e17 step 7 : login, logout (bug disconnect), home screen top info user login 2024-01-25 14:17:50 +07:00
sindhu
aa22053180 step 6 : add package graphql, configure base repo, constant, graph provider.dart 2024-01-24 08:19:12 +07:00
sindhu
3cfc545d89 Merge branch 'main' of https://devone.aplikasi.web.id/gitea/sindhu/absensi_sas_flutter 2024-01-24 08:00:46 +07:00
sindhu
092b11e14f step 5 : add public key, login try using public and failed 2024-01-24 07:56:43 +07:00
142 changed files with 11466 additions and 1074 deletions

31
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,31 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "absensi_sas_flutter_web",
"request": "launch",
"type": "dart",
"args": ["--web-port","5000"]
},
{
"name": "absensi_sas_flutter",
"request": "launch",
"type": "dart"
},
{
"name": "absensi_sas_flutter (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "absensi_sas_flutter (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"
}
]
}

View File

@@ -53,6 +53,7 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
buildTypes {
@@ -71,4 +72,5 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation 'com.android.support:multidex:1.0.3'
}

View File

@@ -1,11 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:label="absensi_sas_flutter"
android:label="Absensi"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/btn_approval.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
images/camera_selfie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

BIN
images/clockin_presensi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

BIN
images/custom_marker1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
images/finger_presensi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

BIN
images/icon_absensi_app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

BIN
images/presensi_finger1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/presensi_finger2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
images/sync_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

BIN
images/warning_selfie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -4,15 +4,43 @@ class Constant {
// static double designHeight = 1024;
// static double designWidth = 1440;
// base url graphql web di server
static String baseURLGraphQl = "https://devone.aplikasi.web.id/query";
// base url graphql web pada waktu develop
// static String baseURLGraphQl = "http://devone.aplikasi.web.id:3300/query";
// static String baseURLGraphQl = "http://localhost:8080/query";
static String bearerName = "absensi-sas";
static String accountGoogle = "absensi-google-account";
// api key google map
static String apikeyGoogleMap = "AIzaSyCiN7EeJsUpXVLQKFfrj3sE5OTKebjpzek";
static String baseUrlGoogleMapApis =
"https://maps.googleapis.com/maps/api/geocode/json?";
// position longitude dan latitude awal
static double positionLatitudeAwal = -7.566957;
static double positionLongitudeAwal = 110.8080284;
static double designHeightPhone = 844;
static double designWidthPhone = 390;
// NOTE VERSI HARUS SAMA DENGAN PUBSPEC.YAML
static String version = "1.0.2";
// color theme
static Color textTrueBlack = const Color(0xff000000);
static Color textBlack = const Color(0xff212B36);
static Color textDarkGrey = const Color(0xff637381);
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);
static Color textRed = Color(0xffFF4842);
// background upload file
static Color bgUploadFile = Color.fromRGBO(207, 207, 207, 0.20);
// size convertion
static double getActualXPhone({
@@ -37,14 +65,6 @@ class Constant {
);
}
static TextStyle titleH1_500_18({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 18),
fontWeight: FontWeight.w500,
fontFamily: 'Public Sans'
);
}
static TextStyle titleH2_600({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 12),
@@ -52,12 +72,46 @@ class Constant {
);
}
static TextStyle logintitle_700({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 15),
fontWeight: FontWeight.w700,
);
}
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),
fontWeight: FontWeight.w600,
fontFamily: 'Public Sans'
);
fontSize: Constant.getActualYPhone(context: context, y: 14),
fontWeight: FontWeight.w600,
fontFamily: 'Public Sans');
}
static TextStyle titleH2_400_14({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 14),
fontWeight: FontWeight.w400,
fontFamily: 'Public Sans');
}
static TextStyle titleH1_500_18({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 18),
fontWeight: FontWeight.w500,
fontFamily: 'Public Sans');
}
static TextStyle titleH1_700_18({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 18),
fontWeight: FontWeight.w700,
fontFamily: 'Public Sans');
}
static TextStyle titleH2_700({required BuildContext context}) {
@@ -68,10 +122,34 @@ class Constant {
);
}
static TextStyle logintitle_700({required BuildContext context}) {
static TextStyle titleH3_700({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 15),
fontFamily: 'Quicksand',
fontSize: Constant.getActualYPhone(context: context, y: 16),
fontWeight: FontWeight.w700,
);
}
static TextStyle titleH2_500({required BuildContext context}) {
return TextStyle(
fontFamily: 'Quicksand',
fontSize: Constant.getActualYPhone(context: context, y: 12),
fontWeight: FontWeight.w500,
);
}
static TextStyle titleH2_400({required BuildContext context}) {
return TextStyle(
fontFamily: 'Public Sans',
fontSize: Constant.getActualYPhone(context: context, y: 12),
fontWeight: FontWeight.w400,
);
}
static TextStyle titlePresensiH2_700({required BuildContext context}) {
return TextStyle(
fontFamily: 'Quicksand',
fontSize: Constant.getActualYPhone(context: context, y: 24),
fontWeight: FontWeight.w700,
);
}
@@ -107,4 +185,47 @@ class Constant {
fontFamily: 'Quicksand',
);
}
static TextStyle title_screen({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 24),
fontWeight: FontWeight.w600,
fontFamily: 'Public Sans',
);
}
static TextStyle body_16({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 16),
fontWeight: FontWeight.w600,
fontFamily: 'Public Sans',
);
}
static TextStyle body_14({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 14),
fontWeight: FontWeight.w600,
fontFamily: 'Public Sans',
);
}
static TextStyle body_12({required BuildContext context}) {
return TextStyle(
fontSize: Constant.getActualYPhone(context: context, y: 12),
fontWeight: FontWeight.w600,
fontFamily: 'Public Sans',
);
}
static Color primaryBlue = const Color(0xff0C53B7);
static Color bgBlue = const Color(0xff1890FF).withOpacity(0.16);
static Color primaryOrange = const Color(0xffF15A29);
static Color bgOrange = const Color(0xffF15A29).withOpacity(0.16);
static Color secondaryBlue = const Color(0xff43ADA5);
static Color bgSecondaryBlue = const Color(0xff43ADA5).withOpacity(0.16);
static Color primaryGreen = const Color(0xff229A16);
static Color bgGreen = const Color(0xff54D62C).withOpacity(0.08);
static Color primaryRed = const Color(0xffB72136);
static Color bgRed = const Color(0xffFF4842).withOpacity(0.08);
}

View File

@@ -0,0 +1,41 @@
import 'package:dio/dio.dart';
import 'package:latlong2/latlong.dart';
Future<String> searchPosition(LatLng position) async {
const apiKey = "AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs";
final latitudeString = position.latitude.toString();
final longitudeString = position.longitude.toString();
const url = 'https://maps.googleapis.com/maps/api/geocode/json';
try {
final response = await Dio().get(
url,
queryParameters: {
'latlng': '$latitudeString,$longitudeString',
'key': apiKey,
},
);
if (response.statusCode == 200) {
final data = response.data;
// Mengambil compound_code dari hasil JSON
final plusCode = data['plus_code'];
if (plusCode != null && plusCode['compound_code'] != null) {
return plusCode['compound_code'];
} else {
print('Alamat Tidak Ditemukan');
return "";
}
} else {
print('Failed to load data');
return "";
}
} catch (e) {
print('Error: $e');
return "";
}
}
Future<String> positionToAddressGoogleApis(LatLng position) async {
return await searchPosition(position);
}

View File

@@ -1,5 +1,11 @@
import 'package:absensi_sas_flutter/screen/homepage/homepage_screen.dart';
import 'package:absensi_sas/screen/approval/approval_screen.dart';
import 'package:absensi_sas/screen/approval_detail/approval_detail_screen.dart';
import 'package:absensi_sas/screen/home/home_screen_v1.dart';
import 'package:absensi_sas/screen/presensi/presensi_screen.dart';
import 'package:absensi_sas/screen/presensi/presensi_selfie_screen.dart';
import 'package:absensi_sas/test_flutter_web_map.dart';
import 'package:flutter/material.dart';
import '../screen/home/home_screen.dart';
import '../test_flutter_map.dart';
import '../screen/login/login_screen.dart';
import '../screen/splash/splash_screen.dart';
@@ -7,10 +13,25 @@ import '../screen/splash/splash_screen.dart';
const loginRoute = "/loginRoute";
const splashRoute = "/splashRoute";
const testFlutterMapRoute = "/testFlutterMapRoute";
const homepageRoute = "/homepageRoute";
const homeRoute = "/homeRoute";
const presensiRoute = "/presensiRoute";
const presensiSelfieRoute = "/presensiSelfieRoute";
const approvalRoute = "/approvalRoute";
const approvalDetailRoute = "/approvalDetailRoute";
const testFlutterWebMapRoute = "/testFlutterWebMapRoute";
class AppRoute {
static Route<dynamic> generateRoute(RouteSettings settings) {
// flutter web map
if (settings.name == testFlutterWebMapRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context)
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
child: TestFlutterWebMap(),
);
});
}
// test flutter map
if (settings.name == testFlutterMapRoute) {
@@ -23,6 +44,39 @@ class AppRoute {
});
}
// home route
if (settings.name == homeRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context)
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
child: HomeScreen(),
);
});
}
// 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(),
);
});
}
// presensi selfie route
if (settings.name == presensiSelfieRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context)
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
child: PresensiSelfieScreen(),
);
});
}
// splash screen
if (settings.name == splashRoute) {
return MaterialPageRoute(builder: (context) {
@@ -44,14 +98,23 @@ class AppRoute {
);
});
}
// homepage
if (settings.name == homepageRoute) {
// approval
if (settings.name == approvalRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context)
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
child: HomepageScreen(),
child: ApprovalScreen(),
);
});
}
// approvalDetail
if (settings.name == approvalDetailRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context)
.copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)),
child: ApprovalDetailScreen(),
);
});
}

View File

@@ -1,13 +1,35 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/test_flutter_web_map.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/date_symbol_data_local.dart';
import '../app/route.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'test_map_x.dart';
// import '../test_map.dart';
// final routerProvider = Provider((_) => GlobalKey<NavigatorState>());
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting('id_ID', null);
runApp(const ProviderScope(child: MyApp()));
initializeDateFormatting();
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarIconBrightness:
Brightness.dark, // this will change the brightness of the icons
statusBarColor: Colors.white, // or any color you want
));
runApp(
ProviderScope(
// overrides: [
// routerProvider.overrideWithValue(GlobalKey<NavigatorState>()),
// ],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@@ -25,9 +47,27 @@ class MyApp extends StatelessWidget {
// ),
theme: ThemeData(
primarySwatch: Colors.orange,
primaryColor: Constant.textOrange,
primaryColorDark: Constant.textOrange,
primaryColorLight: Constant.textOrange,
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: Constant.textOrange,
background: Colors.white,
surface: Colors.white),
// datePickerTheme: DatePickerThemeData(
// // backgroundColor: Colors.white,
// // headerHeadlineStyle:
// // Constant.h3_400(context: context).copyWith(color: Colors.white),
// headerBackgroundColor: Constant.textOrange,
// headerForegroundColor: Colors.white,
// surfaceTintColor: Colors.white,
// backgroundColor: Colors.white),
),
// home: TestMap(),
initialRoute: homepageRoute,
initialRoute: loginRoute,
// home: TestMapFlutterWeb(),
// home:TestMapX(),
// initialRoute: testFlutterMapRoute,
onGenerateRoute: AppRoute.generateRoute,
);

View File

@@ -0,0 +1,56 @@
class ApprovalDetailModel {
String? id;
String? name;
String? nip;
String? date;
String? time;
String? address;
double? lat;
double? long;
String? imagePath;
String? statusID;
String? statusName;
ApprovalDetailModel(
{this.id,
this.name,
this.nip,
this.date,
this.time,
this.address,
this.lat,
this.long,
this.imagePath,
this.statusID,
this.statusName});
ApprovalDetailModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['Name'];
nip = json['nip'];
date = json['date'];
time = json['time'];
address = json['address'];
lat = json['lat'];
long = json['long'];
imagePath = json['imagePath'];
statusID = json['statusID'];
statusName = json['statusName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['Name'] = this.name;
data['nip'] = this.nip;
data['date'] = this.date;
data['time'] = this.time;
data['address'] = this.address;
data['lat'] = this.lat;
data['long'] = this.long;
data['imagePath'] = this.imagePath;
data['statusID'] = this.statusID;
data['statusName'] = this.statusName;
return data;
}
}

View File

@@ -0,0 +1,68 @@
class ApprovalModel {
String? id;
String? name;
String? date;
String? time;
String? typename;
String? typeID;
String? presensiTypeName;
String? presensitypeID;
String? address;
String? reasonType;
String? reasontypeID;
String? reasonDescription;
String? statusID;
String? statusName;
ApprovalModel(
{this.id,
this.name,
this.date,
this.time,
this.typename,
this.typeID,
this.presensiTypeName,
this.presensitypeID,
this.address,
this.reasonType,
this.reasontypeID,
this.reasonDescription,
this.statusID,
this.statusName});
ApprovalModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
date = json['date'];
time = json['time'];
typename = json['typename'];
typeID = json['typeID'];
presensiTypeName = json['presensiTypeName'];
presensitypeID = json['presensitypeID'];
address = json['address'];
reasonType = json['reasonType'];
reasontypeID = json['reasontypeID'];
reasonDescription = json['reasonDescription'];
statusID = json['statusID'];
statusName = json['statusName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['date'] = this.date;
data['time'] = this.time;
data['typename'] = this.typename;
data['typeID'] = this.typeID;
data['presensiTypeName'] = this.presensiTypeName;
data['presensitypeID'] = this.presensitypeID;
data['address'] = this.address;
data['reasonType'] = this.reasonType;
data['reasontypeID'] = this.reasontypeID;
data['reasonDescription'] = this.reasonDescription;
data['statusID'] = this.statusID;
data['statusName'] = this.statusName;
return data;
}
}

View File

@@ -0,0 +1,24 @@
class ApprovalTypeModel {
String? id;
String? value;
String? name;
bool? isActive;
ApprovalTypeModel({this.id, this.value, this.name, this.isActive});
ApprovalTypeModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
value = json['value'];
name = json['name'];
isActive = json['isActive'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['value'] = this.value;
data['name'] = this.name;
data['isActive'] = this.isActive;
return data;
}
}

62
lib/model/auth_model.dart Normal file
View File

@@ -0,0 +1,62 @@
class AuthModel {
final String token;
final StaffModel model;
final String? idTokenGoogle;
final String? accessTokenGoogle;
AuthModel({
required this.token,
required this.model,
this.idTokenGoogle,
this.accessTokenGoogle,
});
}
class StaffModel {
String? staffId;
String? nip;
String? name;
String? email;
String? phoneNumber;
String? token;
String? idGoogleSignIn;
String? companyId;
String? companyName;
StaffModel(
{this.staffId,
this.nip,
this.name,
this.email,
this.phoneNumber,
this.token,
this.idGoogleSignIn,
this.companyId,
this.companyName});
StaffModel.fromJson(Map<String, dynamic> json) {
staffId = json['staff_id'];
nip = json['nip'];
name = json['name'];
email = json['email'];
phoneNumber = json['phone_number'];
token = json['token'];
idGoogleSignIn = json['id_google_sign_in'];
companyId = json['company_id'];
companyName = json['company_name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['staff_id'] = this.staffId;
data['nip'] = this.nip;
data['name'] = this.name;
data['email'] = this.email;
data['phone_number'] = this.phoneNumber;
data['token'] = this.token;
data['id_google_sign_in'] = this.idGoogleSignIn;
data['company_id'] = this.companyId;
data['company_name'] = this.companyName;
return data;
}
}

View File

@@ -0,0 +1,36 @@
class CheckDistanceModel {
String? status;
String? message;
String? selfie;
String? unit;
String? maxDistance;
String? currentDistance;
CheckDistanceModel(
{this.status,
this.message,
this.selfie,
this.unit,
this.maxDistance,
this.currentDistance});
CheckDistanceModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
message = json['message'];
selfie = json['selfie'];
unit = json['unit'];
maxDistance = json['max_distance'];
currentDistance = json['current_distance'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['message'] = this.message;
data['selfie'] = this.selfie;
data['unit'] = this.unit;
data['max_distance'] = this.maxDistance;
data['current_distance'] = this.currentDistance;
return data;
}
}

View File

@@ -0,0 +1,36 @@
class CheckPresensiJamModel {
String? status;
String? message;
String? jamClockIn;
String? jamClockOut;
String? isAbsenClockIn;
String? isAbsenClockOut;
CheckPresensiJamModel(
{this.status,
this.message,
this.jamClockIn,
this.jamClockOut,
this.isAbsenClockIn,
this.isAbsenClockOut});
CheckPresensiJamModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
message = json['message'];
jamClockIn = json['jam_clock_in'];
jamClockOut = json['jam_clock_out'];
isAbsenClockIn = json['is_absen_clock_in'];
isAbsenClockOut = json['is_absen_clock_out'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['message'] = this.message;
data['jam_clock_in'] = this.jamClockIn;
data['jam_clock_out'] = this.jamClockOut;
data['is_absen_clock_in'] = this.isAbsenClockIn;
data['is_absen_clock_out'] = this.isAbsenClockOut;
return data;
}
}

View File

@@ -0,0 +1,18 @@
class LogoutResponseModel {
String? status;
String? message;
LogoutResponseModel({this.status, this.message});
LogoutResponseModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['message'] = this.message;
return data;
}
}

View File

@@ -0,0 +1,33 @@
class RekapKehadiranHomeScreenModel {
String? status;
String? message;
String? kehadiran;
String? tidakHadir;
String? lembur;
RekapKehadiranHomeScreenModel({
this.status,
this.message,
this.kehadiran,
this.tidakHadir,
this.lembur,
});
RekapKehadiranHomeScreenModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
message = json['message'];
kehadiran = json['kehadiran'];
tidakHadir = json['tidak_hadir'];
lembur = json['lembur'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['message'] = this.message;
data['kehadiran'] = this.kehadiran;
data['tidak_hadir'] = this.tidakHadir;
data['lembur'] = this.lembur;
return data;
}
}

View File

@@ -0,0 +1,28 @@
import 'package:absensi_sas/model/approval_type_model.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ApprovalFilterProvider {
DateTime startDate = DateTime.now();
DateTime endDate = DateTime.now();
String keyword = "";
List<String> selectedApprovalType = [];
final approvalTypeList = [
ApprovalTypeModel(id: "1", name: "Presensi", isActive: false, value: "p"),
ApprovalTypeModel(id: "2", name: "Cuti", isActive: true, value: "c"),
ApprovalTypeModel(id: "3", name: "Lembur", isActive: false, value: "l"),
];
ApprovalFilterProvider(
{required this.endDate,
required this.keyword,
required this.selectedApprovalType,
required this.startDate});
}
final approvalFilterProvider = StateProvider<ApprovalFilterProvider>((ref) {
return ApprovalFilterProvider(
selectedApprovalType: [],
endDate: DateTime.now(),
keyword: "",
startDate: DateTime.now());
});

View File

@@ -0,0 +1,68 @@
import 'dart:convert';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
final imgPhotoWebProvider = StateProvider<String?>((ref) => "");
final cameraControllerProvider =
StateNotifierProvider<CameraControllerNotifier, CameraController?>((ref) {
return CameraControllerNotifier();
});
// StateNotifier to manage CameraController
class CameraControllerNotifier extends StateNotifier<CameraController?> {
CameraControllerNotifier() : super(null) {
_initializeCamera();
}
Future<void> _initializeCamera() async {
WidgetsFlutterBinding.ensureInitialized();
try {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
final controller = CameraController(cameras[0], ResolutionPreset.max);
await controller.initialize();
state = controller;
}
} catch (e) {
print('Error initializing camera: $e');
}
}
Future<void> takePicture(
// ValueNotifier<XFile?> imageFile,
ValueNotifier<String> base64ImageString,
ValueNotifier<String> imagePath,
) async {
// final flag = ValueNotifier<bool>(false);
if (state != null) {
try {
final image = await state!.takePicture();
final bytes = await image.readAsBytes();
String base64Image = base64Encode(bytes);
// imageFile.value = image;
base64ImageString.value = base64Image;
imagePath.value = image.path;
// final shared = await SharedPreferences.getInstance();
// shared.setString("base64Image", base64Image);
} catch (e) {
print('Error taking picture: $e');
// flag.value = false;
// RespErr(flag: false, message: 'Error taking picture: $e');
}
} else {
print('CameraController is not initialized.');
}
}
@override
void dispose() {
state?.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,4 @@
import 'package:absensi_sas/model/check_distance_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final currentCheckDistanceProvider = StateProvider<CheckDistanceModel?>((ref) => null);

View File

@@ -0,0 +1,4 @@
import 'package:absensi_sas/model/check_presensi_jam_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final currentCheckJamPresensiProvider = StateProvider<CheckPresensiJamModel?>((ref) => null);

View File

@@ -0,0 +1,3 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
final currentPageProvider = StateProvider<int?>((ref) => null);

View File

@@ -0,0 +1,5 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/rekap_kehadiran_home_screen_model.dart';
final currentRekapKehadiranHomeProvider = StateProvider<RekapKehadiranHomeScreenModel?>((ref) => null);

View File

@@ -0,0 +1,4 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/auth_model.dart';
final currentUserProvider = StateProvider<AuthModel?>((ref) => null);

View File

@@ -0,0 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final dioProvider = Provider<Dio>((ref) => Dio());

View File

@@ -1,8 +1,12 @@
import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
const String googleClientID =
"856240587825-klh0dfjc44bovajg1rpq5vbvs4g7rh5j.apps.googleusercontent.com";
final googleSignInProvider = StateProvider<GoogleSignIn>((ref) {
return GoogleSignIn(
clientId: googleClientID,
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
@@ -10,4 +14,10 @@ final googleSignInProvider = StateProvider<GoogleSignIn>((ref) {
);
});
final currentUserProvider = StateProvider<GoogleSignInAccount?>((ref) => null);
final currentUserGoogleProvider =
StateProvider<GoogleSignInAccount?>((ref) => null);
final isNotifyFromLogout = StateProvider<bool>((ref) => false);
final currentLatitudeProvider = StateProvider<double>((ref) => 0.0);
final currentLongitudeProvider = StateProvider<double>((ref) => 0.0);

View File

@@ -0,0 +1,30 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:graphql/client.dart';
import '../app/constant.dart';
final graphqlProvider = Provider.family<GraphQLClient, String>(
(_, token) {
final policies = Policies(
fetch: FetchPolicy.noCache,
);
return GraphQLClient(
link: (token != "")
? HttpLink(
Constant.baseURLGraphQl,
defaultHeaders: {
'Authorization': 'Bearer $token',
},
)
: HttpLink(
Constant.baseURLGraphQl,
),
cache: GraphQLCache(),
defaultPolicies: DefaultPolicies(
watchQuery: policies,
query: policies,
mutate: policies,
),
);
},
);

View File

@@ -0,0 +1,10 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
final locationProvider = StateProvider<Map<String, dynamic>>((ref) {
return {
'status': null,
'message': null,
'latitude': null,
'longitude': null,
};
});

View File

@@ -0,0 +1,55 @@
import 'package:absensi_sas/model/logout_response_model.dart';
import 'package:google_sign_in/google_sign_in.dart';
import '../model/auth_model.dart';
import 'base_repository.dart';
class AuthRepository extends BaseRepository {
AuthRepository({required super.graphql, required super.dio});
// login
Future<AuthModel> login(
String email,
String idGoogleSignIn,
) async {
const String query =
r'''mutation($emailParam:String!, $id_google_sign_in_Param:String!){ loginAttendance(email:$emailParam, id_google_sign_in:$id_google_sign_in_Param){ staff_id nip name email phone_number token id_google_sign_in company_id company_name } }''';
Map<String, dynamic> inpVariables = {
"emailParam": email,
"id_google_sign_in_Param": idGoogleSignIn
};
final resp = await postGraphQlMutation(query, inpVariables);
// final loginData = AuthModel.fromJson(resp['userLogin']);
print('obj loginAttendance : ${resp["loginAttendance"]}');
print('token : ${resp["loginAttendance"]["token"]}');
final result = AuthModel(
token: resp["loginAttendance"]["token"],
model: StaffModel.fromJson(resp["loginAttendance"]),
);
return result;
}
// logout
Future<LogoutResponseModel> logout(
String email, String idGoogleSignIn) async {
const String query =
r'''mutation($emailParam:String!, $id_google_sign_in_Param:String!){ logoutAttendance(email:$emailParam, id_google_sign_in:$id_google_sign_in_Param){ status message } }''';
Map<String, dynamic> inpVariables = {
"emailParam": email,
"id_google_sign_in_Param": idGoogleSignIn
};
final resp = await postGraphQlMutation(query, inpVariables);
// final loginData = AuthModel.fromJson(resp['userLogin']);
// final result = resp['logoutAttendance']['status'];
final result = LogoutResponseModel(
message: resp['logoutAttendance']['message'],
status: resp['logoutAttendance']['status']);
return result;
}
}

View File

@@ -0,0 +1,189 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:graphql/client.dart';
abstract class BaseRepository {
final Dio dio;
final GraphQLClient graphql;
BaseRepository({required this.dio, required this.graphql});
// POST PAKE GRAPHQL
Future<Map<String, dynamic>> postGraphQlMutation(
String query, Map<String, dynamic> inpVariables) async {
try {
final options =
MutationOptions(document: gql(query), variables: inpVariables);
final QueryResult result = await graphql.mutate(options);
if (result.hasException) {
// throw BaseRepositoryException(message: result.exception.toString());
if (result.exception != null) {
if (result.exception!.graphqlErrors.isNotEmpty) {
String error = "";
if (result.exception!.graphqlErrors[0].message != "") {
error += result.exception!.graphqlErrors[0].message;
throw BaseRepositoryException(message: error);
}
}
}
}
// return result.data!;
return result.data!;
} catch (e) {
throw BaseRepositoryException(message: e.toString());
}
}
Future<Map<String, dynamic>> postGraphQlQuery(
String query, Map<String, dynamic> inpVariables) async {
try {
final options =
QueryOptions(document: gql(query), variables: inpVariables);
final QueryResult result = await graphql.query(options);
if (result.hasException) {
// throw BaseRepositoryException(message: result.exception.toString());
if (result.exception != null) {
if (result.exception!.graphqlErrors.isNotEmpty) {
if (result.exception!.graphqlErrors[0].message != "") {
throw BaseRepositoryException(
message: result.exception!.graphqlErrors[0].message.toString(),
);
}
}
}
}
// throw BaseRepositoryException(message: result.exception.toString());
return result.data!;
} catch (e) {
throw BaseRepositoryException(message: e.toString());
}
}
// with handling
Future<Map<String, dynamic>> postGraphQlQueryX(
String query, Map<String, dynamic> inpVariables) async {
try {
final options =
QueryOptions(document: gql(query), variables: inpVariables);
final QueryResult result = await graphql.query(options);
if (result.hasException) {
if (result.exception != null) {
if (result.exception!.graphqlErrors.isNotEmpty
is BaseRepositoryException) {
final error = result.exception!.graphqlErrors[0];
BaseRepositoryException exception =
result.exception!.linkException as BaseRepositoryException;
var errorMessage = exception.message;
print(errorMessage);
throw BaseRepositoryException(
message: errorMessage,
);
}
}
}
return result.data!;
} catch (e) {
throw BaseRepositoryException(message: e.toString());
}
}
// NATIVE POST PAKE DIO
Future<Map<String, dynamic>> post({
required Map<String, dynamic> param,
required String service,
String? token,
}) async {
try {
final response = await dio.post(
service,
data: jsonEncode(param),
options: Options(
headers: token != null
? {
HttpHeaders.contentTypeHeader: "application/json",
HttpHeaders.authorizationHeader: "Bearer $token",
}
: {
HttpHeaders.contentTypeHeader: "application/json",
},
contentType: "application/json",
),
);
if (response.statusCode != 200) {
throw BaseRepositoryException(
message: "Invalid Http Response ${response.statusCode}",
);
}
Map<String, dynamic> jsonData = jsonDecode(response.data);
if (jsonData["status"] != "OK") {
throw BaseRepositoryException(
message: jsonData["message"],
);
} else {
return jsonData;
}
} on DioError catch (e) {
throw BaseRepositoryException(message: e.message);
} on SocketException catch (e) {
throw BaseRepositoryException(message: e.message);
} on BaseRepositoryException catch (e) {
throw BaseRepositoryException(message: e.message);
}
}
Future<Map<String, dynamic>> get({
required String service,
String? token,
}) async {
try {
final response = await dio.get(
service,
options: Options(
headers: token != null
? {
HttpHeaders.contentTypeHeader: "application/json",
HttpHeaders.authorizationHeader: "Bearer $token",
}
: {
HttpHeaders.contentTypeHeader: "application/json",
},
contentType: "application/json",
),
);
if (response.statusCode != 200) {
throw BaseRepositoryException(
message: "Invalid Http Response ${response.statusCode}",
);
}
Map<String, dynamic> jsonData = jsonDecode(response.data);
if (jsonData["status"] != "OK") {
throw BaseRepositoryException(
message: jsonData["message"],
);
} else {
return jsonData;
}
} on DioError catch (e) {
throw BaseRepositoryException(message: e.message);
} on SocketException catch (e) {
throw BaseRepositoryException(message: e.message);
} on BaseRepositoryException catch (e) {
throw BaseRepositoryException(message: e.message);
}
}
}
class BaseRepositoryException implements Exception {
final String? message;
BaseRepositoryException({
required this.message,
});
}

View File

@@ -0,0 +1,45 @@
import 'package:dio/dio.dart';
import 'base_repository.dart';
class GoogleApisRepository extends BaseRepository {
GoogleApisRepository({required super.graphql, required super.dio});
Future<String> getAddressFromCoordinates(
String latitude, String longitude) async {
final dio = Dio();
const apiKey = "AIzaSyAVUr4Ku4O1HlSkK8n9KGnUyqvsXBL-yfs";
final latitudeString = latitude.toString();
final longitudeString = longitude.toString();
const url =
'https://maps.googleapis.com/maps/api/geocode/json';
try {
final response = await dio.get(
url,
queryParameters: {
'latlng': '$latitudeString,$longitudeString',
'key': apiKey,
},
);
if (response.statusCode == 200) {
final data = response.data;
// Mengambil compound_code dari hasil JSON
final plusCode = data['plus_code'];
if (plusCode != null && plusCode['compound_code'] != null) {
return plusCode['compound_code'];
} else {
print('Alamat Tidak Ditemukan');
return "";
}
} else {
print('Failed to load data');
return "";
}
} catch (e) {
print('Error: $e');
return "";
}
}
}

View File

@@ -0,0 +1,226 @@
import 'package:absensi_sas/model/check_distance_model.dart';
import 'package:absensi_sas/model/check_presensi_jam_model.dart';
import '../model/rekap_kehadiran_home_screen_model.dart';
import 'base_repository.dart';
class PresensiRepository extends BaseRepository {
PresensiRepository({required super.graphql, required super.dio});
// check distance
Future<CheckDistanceModel> checkDistance(
String M_StaffID,
String M_CompanyID,
String CurrentLatitude,
String CurrentLongitude,
) async {
const String query =
r'''query($M_StaffID:String!, $M_CompanyID:String!, $CurrentLatitude:String!, $CurrentLongitude:String!){ queryCheckDistance(M_StaffID:$M_StaffID, M_CompanyID:$M_CompanyID, CurrentLatitude:$CurrentLatitude, CurrentLongitude:$CurrentLongitude){ status message selfie unit max_distance current_distance } }''';
Map<String, dynamic> inpVariables = {
"M_StaffID": M_StaffID,
"M_CompanyID": M_CompanyID,
"CurrentLatitude": CurrentLatitude,
"CurrentLongitude": CurrentLongitude,
};
final resp = await postGraphQlMutation(query, inpVariables);
// final loginData = AuthModel.fromJson(resp['userLogin']);
// print(inpVariables);
print('obj queryCheckDistance : ${resp["queryCheckDistance"]}');
// final result = AuthModel(
// token: resp["loginAttendance"]["token"],
// model: StaffModel.fromJson(resp["loginAttendance"]),
// );
final result = CheckDistanceModel(
currentDistance: resp['queryCheckDistance']['current_distance'],
maxDistance: resp['queryCheckDistance']['max_distance'],
message: resp['queryCheckDistance']['message'],
selfie: resp['queryCheckDistance']['selfie'],
status: resp['queryCheckDistance']['status'],
unit: resp['queryCheckDistance']['unit']);
return result;
}
// presensi normal Clock In
Future<String> presensiNormalClockIn(
String T_TransactionM_StaffID,
String T_TransactionM_CompanyID,
String T_TransactionCurrentLatitude,
String T_TransactionCurrentLongitude,
String T_TransactionCurrentDistance,
String T_TransactionSelfiePhoto,
String token,
String isSelfie,
Map<String, dynamic> paramInpVariables) async {
const String query =
r'''mutation($T_TransactionM_StaffID:String!, $T_TransactionM_CompanyID:String!, $T_TransactionCurrentLatitude:String!, $T_TransactionCurrentLongitude:String!, $T_TransactionCurrentDistance:String!, $T_TransactionSelfiePhoto:String, $token:String!, $isSelfie:String!){ mutationClockInAttendance(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:$isSelfie){ status message } }''';
Map<String, dynamic> inpVariables = paramInpVariables;
print(paramInpVariables);
final resp = await postGraphQlMutation(query, inpVariables);
// print(inpVariables);
print(
'obj mutationClockInAttendance : ${resp["mutationClockInAttendance"]}');
// final result = AuthModel(
// token: resp["loginAttendance"]["token"],
// model: StaffModel.fromJson(resp["loginAttendance"]),
// );
final result = resp["mutationClockInAttendance"]['status'];
return result;
}
// presensi normal clock Out
Future<String> presensiNormalClockOut(
String T_TransactionM_StaffID,
String T_TransactionM_CompanyID,
String T_TransactionCurrentLatitude,
String T_TransactionCurrentLongitude,
String T_TransactionCurrentDistance,
String T_TransactionSelfiePhoto,
String token,
String isSelfie,
Map<String, dynamic> paramInpVariables) async {
const String query =
r'''mutation(
$T_TransactionM_StaffID:String!,
$T_TransactionM_CompanyID:String!,
$T_TransactionCurrentLatitude:String!,
$T_TransactionCurrentLongitude:String!,
$T_TransactionCurrentDistance:String!,
$T_TransactionSelfiePhoto:String,
$token:String!,
$isSelfie:String!
){
mutationClockOutAttendance(
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:$isSelfie
){
status
message
}
}''';
Map<String, dynamic> inpVariables = paramInpVariables;
print(paramInpVariables);
final resp = await postGraphQlMutation(query, inpVariables);
// print(inpVariables);
print(
'obj mutationClockOutAttendance : ${resp["mutationClockOutAttendance"]}');
// final result = AuthModel(
// token: resp["loginAttendance"]["token"],
// model: StaffModel.fromJson(resp["loginAttendance"]),
// );
final result = resp["mutationClockOutAttendance"]['status'];
return result;
}
// check presensi jam
Future<CheckPresensiJamModel> checkPresensiJam(
String M_StaffID,
String M_CompanyID,
String token,
Map<String, dynamic> paramInpVariables,
) async {
const String query =
r'''query($M_StaffID:String!, $M_CompanyID:String!, $token:String!){
queryCheckTimeAttendance(M_StaffID:$M_StaffID, M_CompanyID:$M_CompanyID, token:$token){
status
message
jam_clock_in
jam_clock_out
is_absen_clock_in
is_absen_clock_out
}
}''';
// Map<String, dynamic> inpVariables = paramInpVariables;
// Map<String, dynamic> X = {
// "M_StaffID": M_StaffID,
// "M_CompanyID": M_CompanyID,
// "token": token+"1",
// };
print(paramInpVariables);
final resp = await postGraphQlQuery(query, paramInpVariables);
// final resp = await postGraphQlQueryX(query, X);
// print(inpVariables);
print('obj queryCheckTimeAttendance : ${resp["queryCheckTimeAttendance"]}');
final result = CheckPresensiJamModel(
isAbsenClockIn: resp['queryCheckTimeAttendance']['is_absen_clock_in'],
isAbsenClockOut: resp['queryCheckTimeAttendance']['is_absen_clock_out'],
jamClockIn: resp['queryCheckTimeAttendance']['jam_clock_in'],
jamClockOut: resp['queryCheckTimeAttendance']['jam_clock_out'],
message: resp['queryCheckTimeAttendance']['message'],
status: resp['queryCheckTimeAttendance']['status'],
);
return result;
}
// rekap kehadiran home screen
Future<RekapKehadiranHomeScreenModel> rekapKehadiranHomeScreen(
String M_StaffID,
String M_CompanyID,
String token,
Map<String, dynamic> paramInpVariables,
) async {
const String query =
r'''query($M_StaffID:String!, $M_CompanyID:String!, $token:String!){
queryRekapKehadiranHomeScreen(M_StaffID:$M_StaffID, M_CompanyID:$M_CompanyID, token:$token){
status
message
kehadiran
tidak_hadir
lembur
}
}''';
// Map<String, dynamic> inpVariables = paramInpVariables;
// Map<String, dynamic> X = {
// "M_StaffID": M_StaffID,
// "M_CompanyID": M_CompanyID,
// "token": token+"1",
// };
print(paramInpVariables);
final resp = await postGraphQlQuery(query, paramInpVariables);
// final resp = await postGraphQlQueryX(query, X);
// print(inpVariables);
print('obj queryRekapKehadiranHomeScreen : ${resp["queryRekapKehadiranHomeScreen"]}');
final result = RekapKehadiranHomeScreenModel(
kehadiran: resp['queryRekapKehadiranHomeScreen']['kehadiran'],
tidakHadir: resp['queryRekapKehadiranHomeScreen']['tidak_hadir'],
lembur: resp['queryRekapKehadiranHomeScreen']['lembur'],
message: resp['queryRekapKehadiranHomeScreen']['message'],
status: resp['queryRekapKehadiranHomeScreen']['status'],
);
return result;
}
}

View File

@@ -0,0 +1,158 @@
import 'package:absensi_sas/model/approval_detail_model.dart';
import 'package:absensi_sas/model/approval_model.dart';
List<ApprovalModel> approvalCardList = [
ApprovalModel(
id: "1",
name: "Alfianto Andy P",
date: "21 Feb 2024",
time: "08:03",
typeID: "1",
typename: "Presensi",
presensitypeID: "1",
presensiTypeName: "Clock In",
address:
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
statusID: "",
statusName: ""),
ApprovalModel(
id: "1",
name: "Alfianto Andy P",
date: "21 Feb 2024",
time: "08:03",
typeID: "1",
typename: "Presensi",
presensitypeID: "1",
presensiTypeName: "Clock In",
address:
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
statusID: "1",
statusName: "Approve"),
ApprovalModel(
id: "2",
name: "Alfianto Andy P",
date: "21 Feb 2024",
time: "17:03",
typeID: "1",
typename: "Presensi",
presensitypeID: "2",
presensiTypeName: "Clock Out",
address:
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
statusID: "2",
statusName: "Reject"),
ApprovalModel(
id: "3",
name: "Hanan Askarim",
date: "06 Jan 2024 - 06 Jan 2024",
time: "0",
typeID: "2",
typename: "Cuti",
statusID: "0",
reasonType: "Cuti Tahunan",
reasonDescription: "Kondangan",
reasontypeID: "1",
statusName: "0"),
ApprovalModel(
id: "4",
name: "Hanan Askarim",
date: "06 Jan 2024 - 06 Jan 2024",
time: "0",
typeID: "2",
typename: "Cuti",
statusID: "1",
reasonType: "Cuti Tahunan",
reasonDescription: "Tidur",
reasontypeID: "1",
statusName: "Approve"),
ApprovalModel(
id: "4",
name: "Hanan Askarim",
date: "06 Jan 2024 - 06 Jan 2024",
time: "0",
typeID: "2",
typename: "Cuti",
statusID: "2",
reasonType: "Cuti Tahunan",
reasonDescription: "Tidur",
reasontypeID: "1",
statusName: "Reject"),
ApprovalModel(
id: "5",
name: "Stephen Kusumo",
date: "06 Jan 2024",
time: "17:00 - 19:00",
typeID: "3",
typename: "Lembur",
statusID: "0",
reasonType: "0",
reasonDescription: "Menyelesaikan project Petty Cash",
reasontypeID: "0",
statusName: ""),
ApprovalModel(
id: "5",
name: "Stephen Kusumo",
date: "06 Jan 2024",
time: "17:00 - 19:00",
typeID: "3",
typename: "Lembur",
statusID: "1",
reasonType: "0",
reasonDescription: "Menyelesaikan project Petty Cash",
reasontypeID: "0",
statusName: "Approve"),
ApprovalModel(
id: "5",
name: "Stephen Kusumo",
date: "06 Jan 2024",
time: "17:00 - 19:00",
typeID: "3",
typename: "Lembur",
statusID: "2",
reasonType: "0",
reasonDescription: "Menyelesaikan project Petty Cash",
reasontypeID: "0",
statusName: "Reject"),
];
List<ApprovalDetailModel> approvalDetailMockup = [
ApprovalDetailModel(
id: "1",
name: "Alfianto Andy P",
address:
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
date: "06 Feb 2024",
time: "08:08",
nip: "SS202308",
lat: -7.539538,
long: 110.798357,
imagePath: "https://images4.alphacoders.com/262/262196.jpg",
statusID: "1",
statusName: "Approve"),
ApprovalDetailModel(
id: "2",
name: "Alfianto Andy P",
address:
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
date: "06 Feb 2024",
time: "08:08",
nip: "SS202308",
lat: -7.539538,
long: 110.798357,
imagePath: "https://images3.alphacoders.com/147/147465.jpg",
statusID: "2",
statusName: "Reject"),
ApprovalDetailModel(
id: "3",
name: "Alfianto Andy P",
address:
"Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410",
date: "06 Feb 2024",
time: "08:08",
nip: "SS202308",
lat: -7.539538,
long: 110.798357,
imagePath: "https://images4.alphacoders.com/146/146466.jpg",
statusID: "0",
statusName: ""),
];

View File

@@ -0,0 +1,159 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/app/route.dart';
import 'package:absensi_sas/model/approval_model.dart';
import 'package:absensi_sas/provider/approval_filter_provider.dart';
import 'package:absensi_sas/screen/approval/approval_mockup.dart';
import 'package:absensi_sas/screen/approval/widget/approval_card_cuti.dart';
import 'package:absensi_sas/screen/approval/widget/approval_card_lembur.dart';
import 'package:absensi_sas/screen/approval/widget/approval_card_presensi.dart';
import 'package:absensi_sas/screen/approval/widget/approval_filter_chip_widget.dart';
import 'package:absensi_sas/screen/approval/widget/approval_filter_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ApprovalScreen extends HookConsumerWidget {
const ApprovalScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final approvalFilterTempProvider = ref.watch(approvalFilterProvider);
final approvalTypeList = approvalFilterTempProvider.approvalTypeList;
final scrollCtr = useScrollController();
final List<ApprovalModel> dataMockup = approvalCardList;
onTap(String tipeID) {
if (tipeID == "1") {
print("Tipe Id 1");
Navigator.pushNamed(context, approvalDetailRoute);
}
}
return SafeArea(
minimum: EdgeInsets.only(
top: Constant.getActualYPhone(context: context, y: 30)),
child: Scaffold(
backgroundColor: Constant.textWhite,
appBar: AppBar(
backgroundColor: Colors.white,
shadowColor: Colors.white,
surfaceTintColor: Colors.white,
scrolledUnderElevation: 0,
elevation: 0,
leading: Container(
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.arrow_back_ios_new_rounded)),
),
centerTitle: true,
title: Text(
"Approval",
style: Constant.title_screen(context: context),
),
),
body: Padding(
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(context: context, x: 20)),
child: Column(
children: [
Container(
color: Colors.white,
child: Row(
children: [
ApprovalFilterWidget(),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
width: constraints.maxWidth,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
...approvalTypeList.map(
(e) => ApprovalFilterChipWidget(data: e))
],
),
),
);
},
),
)
],
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 27),
),
Expanded(child: Container(
// color: Colors.red,
child: LayoutBuilder(builder: (context, Constraints) {
return Container(
height: Constraints.maxWidth,
child: RefreshIndicator(
onRefresh: () async {
print("Refresh");
},
child: ListView(
controller: scrollCtr,
children: [
Container(
margin: EdgeInsets.only(bottom: 28),
width: MediaQuery.of(context).size.width,
child: Row(
children: [
Container(
child: Text(
"Februari 2024",
style: Constant.body_16(context: context)
.copyWith(fontWeight: FontWeight.w600),
),
),
Expanded(
child: Container(
margin: EdgeInsets.only(left: 10),
child: Divider(
height: 10,
),
),
)
],
),
),
...dataMockup.map((e) {
switch (e.typeID) {
case "1":
return ApprovalCardPresensi(
data: e,
onTap: onTap,
);
case "2":
return ApprovalCardCuti(
data: e,
);
case "3":
return ApprovalCardLembur(
data: e,
);
default:
return Container();
}
}).toList()
],
),
),
);
}),
))
],
),
),
),
);
}
}

View File

@@ -0,0 +1,200 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/model/approval_model.dart';
import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart';
import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart';
import 'package:absensi_sas/widget/custom_button_approval.dart';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
import 'package:iconify_flutter/iconify_flutter.dart';
import 'package:iconify_flutter/icons/heroicons.dart';
class ApprovalCardCuti extends StatelessWidget {
const ApprovalCardCuti({super.key, required this.data});
final ApprovalModel data;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
bottom: Constant.getActualYPhone(context: context, y: 20)),
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
splashColor: Constant.bgOrange,
overlayColor: MaterialStatePropertyAll(Constant.bgOrange),
onTap: () {
print("tapped");
},
child: Ink(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.12),
offset: Offset(0.0, 10), //(x,y)
blurRadius: 4.0,
),
BoxShadow(
color: Colors.grey.withOpacity(0.2),
offset: Offset(0.0, 0), //(x,y)
blurRadius: 2.0,
),
], color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal:
Constant.getActualXPhone(context: context, x: 20),
vertical:
Constant.getActualYPhone(context: context, y: 16)),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
data.name ?? "",
style: Constant.body_16(context: context),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Constant.primaryOrange.withOpacity(0.2)),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(
context: context, x: 8),
vertical: Constant.getActualYPhone(
context: context, y: 1)),
child: Text(
data.typename ?? "",
style: Constant.body_12(context: context)
.copyWith(color: Constant.primaryOrange),
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 8),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
EvaIcons.calendar,
color: Constant.primaryOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Flexible(
child: Text(
data.date ?? "",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 8),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Icon(
// ,
// color: Constant.primaryOrange,
// size: 14,
// ),
Iconify(
Heroicons.chat_bubble_bottom_center_text_solid,
color: Constant.primaryOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Flexible(
child: Text(
"${data.reasonType} ▪️ ${data.reasonDescription}",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
),
),
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
Row(
children: List.generate(
1000 ~/ 10,
(index) => Expanded(
child: Container(
color: index % 2 == 0
? Colors.transparent
: Constant.textLightGrey
.withOpacity(0.24),
height: 1,
),
)),
),
if (data.statusID != "1" && data.statusID != "2")
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
if (data.statusID != "1" && data.statusID != "2")
Row(
children: [
Expanded(
child: CustomButtonWhite(
btnText: "Reject",
onPressed: () {
ApprovalDialogConfirmation(
context: context,
confirmFunc: () {},
textInformation: "Menolak ",
staffName: data.name ?? "",
typename: data.typename ?? "");
}),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 24),
),
Expanded(
child: CustomButtonOrange(
btnText: "Approve",
onPressed: () {
ApprovalDialogConfirmation(
context: context,
confirmFunc: () {},
textInformation: "Menyetujui ",
staffName: data.name ?? "",
typename: data.typename ?? "");
}),
),
],
)
],
),
),
AprrovalCardStatusWidget(
statusID: data.statusID ?? "",
statusName: data.statusName ?? '',
)
],
)),
),
);
}
}

View File

@@ -0,0 +1,200 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/model/approval_model.dart';
import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart';
import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart';
import 'package:absensi_sas/widget/custom_button_approval.dart';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
import 'package:iconify_flutter/iconify_flutter.dart';
import 'package:iconify_flutter/icons/heroicons.dart';
class ApprovalCardLembur extends StatelessWidget {
const ApprovalCardLembur({super.key, required this.data});
final ApprovalModel data;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
bottom: Constant.getActualYPhone(context: context, y: 20)),
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
splashColor: Constant.bgOrange,
overlayColor: MaterialStatePropertyAll(Constant.bgOrange),
onTap: () {
print("tapped");
},
child: Ink(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.12),
offset: Offset(0.0, 10), //(x,y)
blurRadius: 4.0,
),
BoxShadow(
color: Colors.grey.withOpacity(0.2),
offset: Offset(0.0, 0), //(x,y)
blurRadius: 2.0,
),
], color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal:
Constant.getActualXPhone(context: context, x: 20),
vertical:
Constant.getActualYPhone(context: context, y: 16)),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
data.name ?? "",
style: Constant.body_16(context: context),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Constant.bgSecondaryBlue),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(
context: context, x: 8),
vertical: Constant.getActualYPhone(
context: context, y: 1)),
child: Text(
data.typename ?? "",
style: Constant.body_12(context: context)
.copyWith(color: Constant.secondaryBlue),
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 8),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
EvaIcons.calendar,
color: Constant.textOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Flexible(
child: Text(
data.date ?? "",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 8),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Icon(
// ,
// color: Constant.textOrange,
// size: 14,
// ),
Iconify(
Heroicons.chat_bubble_bottom_center_text_solid,
color: Constant.textOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Flexible(
child: Text(
data.reasonDescription ?? '',
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
),
),
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
Row(
children: List.generate(
1000 ~/ 10,
(index) => Expanded(
child: Container(
color: index % 2 == 0
? Colors.transparent
: Constant.textLightGrey
.withOpacity(0.24),
height: 1,
),
)),
),
if (data.statusID != "1" && data.statusID != "2")
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
if (data.statusID != "1" && data.statusID != "2")
Row(
children: [
Expanded(
child: CustomButtonWhite(
btnText: "Reject",
onPressed: () {
ApprovalDialogConfirmation(
context: context,
confirmFunc: () {},
textInformation: "Menolak ",
staffName: data.name ?? "",
typename: data.typename ?? "");
}),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 24),
),
Expanded(
child: CustomButtonOrange(
btnText: "Approve",
onPressed: () {
ApprovalDialogConfirmation(
context: context,
confirmFunc: () {},
textInformation: "Menyetujui ",
staffName: data.name ?? "",
typename: data.typename ?? "");
}),
),
],
),
],
),
),
AprrovalCardStatusWidget(
statusID: data.statusID ?? "",
statusName: data.statusName ?? '',
)
],
)),
),
);
}
}

View File

@@ -0,0 +1,203 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/model/approval_model.dart';
import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
class ApprovalCardPresensi extends StatelessWidget {
const ApprovalCardPresensi(
{super.key, required this.data, required this.onTap});
final ApprovalModel data;
final Function onTap;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
bottom: Constant.getActualYPhone(context: context, y: 20)),
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
splashColor: Constant.bgOrange,
// hoverColor: Colors.red,
overlayColor: MaterialStatePropertyAll(Constant.bgOrange),
onTap: () {
onTap(data.typeID);
},
child: Ink(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.12),
offset: Offset(0.0, 10), //(x,y)
blurRadius: 4.0,
),
BoxShadow(
color: Colors.grey.withOpacity(0.2),
offset: Offset(0.0, 0), //(x,y)
blurRadius: 2.0,
),
], color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal:
Constant.getActualXPhone(context: context, x: 20),
vertical:
Constant.getActualYPhone(context: context, y: 16)),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
data.name ?? "",
style: Constant.body_16(context: context),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Constant.bgBlue),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(
context: context, x: 8),
vertical: Constant.getActualYPhone(
context: context, y: 1)),
child: Text(
data.typename ?? "",
style: Constant.body_12(context: context)
.copyWith(color: Constant.primaryBlue),
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 8),
),
Row(
children: [
Row(
children: [
Icon(
EvaIcons.calendar,
color: Constant.primaryOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Text(
data.date ?? "",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
)
],
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 20),
),
Row(
children: [
Icon(
EvaIcons.clock,
color: Constant.primaryOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Text(
data.time ?? "",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
)
],
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 20),
),
Row(
children: [
Image.asset(
'images/finger_tap_orange_botnav.png',
width: Constant.getActualXPhone(
context: context, x: 14),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Text(
data.presensiTypeName ?? "",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
)
],
),
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 8),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
EvaIcons.pin,
color: Constant.primaryOrange,
size: 14,
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 8),
),
Flexible(
child: Text(
data.address ?? "",
style: Constant.titleH2_400_14(context: context)
.copyWith(color: Constant.textDarkGrey),
),
)
],
),
if (data.statusID == "1" || data.statusID == "2")
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
if (data.statusID == "1" || data.statusID == "2")
Row(
children: List.generate(
1000 ~/ 10,
(index) => Expanded(
child: Container(
color: index % 2 == 0
? Colors.transparent
: Constant.textLightGrey
.withOpacity(0.24),
height: 1,
),
)),
),
],
),
),
AprrovalCardStatusWidget(
statusID: data.statusID ?? "",
statusName: data.statusName ?? "",
)
],
)),
),
);
}
}

View File

@@ -0,0 +1,52 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:flutter/material.dart';
class AprrovalCardStatusWidget extends StatelessWidget {
const AprrovalCardStatusWidget(
{super.key, required this.statusID, required this.statusName});
final String statusID;
final String statusName;
@override
Widget build(BuildContext context) {
switch (statusID) {
case "1":
return Container(
decoration: BoxDecoration(
color: Constant.bgGreen,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16))),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(context: context, x: 20),
vertical: Constant.getActualYPhone(context: context, y: 16)),
width: MediaQuery.of(context).size.width,
child: Text(
statusName,
style: Constant.body_12(context: context)
.copyWith(color: Constant.primaryGreen),
),
);
case "2":
return Container(
decoration: BoxDecoration(
color: Constant.bgRed,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16))),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(context: context, x: 20),
vertical: Constant.getActualYPhone(context: context, y: 16)),
width: MediaQuery.of(context).size.width,
child: Text(
statusName,
style: Constant.body_12(context: context)
.copyWith(color: Constant.primaryRed),
),
);
default:
return Container();
}
}
}

View File

@@ -0,0 +1,75 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/widget/custom_button_approval.dart';
import 'package:flutter/material.dart';
Future ApprovalDialogConfirmation(
{required BuildContext context,
required String typename,
required String textInformation,
required String staffName,
required Function() confirmFunc}) {
return showDialog(
context: context,
builder: (BuildContext context) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
Color(0xff161C24).withOpacity(0.91),
Color(0xff161C24).withOpacity(0.3)
])),
child: AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
shadowColor: Colors.white,
title: Text(
'Konfirmasi',
style: Constant.titleH1_700_18(context: context),
),
content: Text.rich(TextSpan(
text: "Apakah anda yakin untuk ",
style: Constant.body_16(context: context)
.copyWith(fontWeight: FontWeight.w400),
children: [
TextSpan(
text: textInformation,
style: Constant.body_16(context: context)),
TextSpan(
text: "${typename} ",
style: Constant.body_16(context: context)
.copyWith(fontWeight: FontWeight.w400)),
TextSpan(
text: "${staffName} ?",
style: Constant.body_16(context: context))
])),
actions: <Widget>[
Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
child: CustomButtonOrange(
btnText: "Yakin",
onPressed: confirmFunc,
),
),
SizedBox(
height: 3,
),
Container(
width: MediaQuery.of(context).size.width,
child: CustomButtonWhite(
btnText: "Batal",
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
],
),
),
);
}

View File

@@ -0,0 +1,71 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/model/approval_type_model.dart';
import 'package:absensi_sas/provider/approval_filter_provider.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ApprovalFilterChipWidget extends HookConsumerWidget {
const ApprovalFilterChipWidget({
super.key,
required this.data,
});
final ApprovalTypeModel data;
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentApprovalFilter = ref.watch(approvalFilterProvider);
handleOnTap(String value) {
var tmp = currentApprovalFilter.selectedApprovalType.toList();
var cek = tmp.contains(value);
if (cek) {
tmp.remove(value);
} else {
tmp.add(value);
}
ref.read(approvalFilterProvider.notifier).state = ApprovalFilterProvider(
endDate: currentApprovalFilter.endDate,
keyword: currentApprovalFilter.keyword,
selectedApprovalType: tmp,
startDate: currentApprovalFilter.startDate);
}
return Container(
margin: EdgeInsets.only(
right: Constant.getActualXPhone(context: context, x: 16)),
child: ElevatedButton(
onPressed: () {
handleOnTap(data.value!);
},
style: ButtonStyle(
elevation: MaterialStatePropertyAll(0),
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
side: BorderSide(
color: currentApprovalFilter.selectedApprovalType
.contains(data.value)
? Constant.textOrange
: Colors.grey.shade300,
),
borderRadius: BorderRadius.circular(16))),
backgroundColor: MaterialStateProperty.resolveWith((states) {
if (currentApprovalFilter.selectedApprovalType
.contains(data.value) ==
true) {
return Constant.textOrange.withOpacity(0.1);
}
return Colors.white;
}),
overlayColor:
MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)),
foregroundColor: MaterialStatePropertyAll(Colors.white),
shadowColor: MaterialStatePropertyAll(Colors.white),
surfaceTintColor: MaterialStatePropertyAll(Colors.white)),
child: Text(data.name ?? "",
style: Constant.subtitle_600_14(context: context).copyWith(
color: currentApprovalFilter.selectedApprovalType
.contains(data.value)
? Constant.textOrange
: Colors.black)),
),
);
}
}

View File

@@ -0,0 +1,69 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/screen/approval/widget/filter_bottom_sheet.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ApprovalFilterWidget extends HookConsumerWidget {
const ApprovalFilterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Container(
margin: EdgeInsets.only(
right: Constant.getActualXPhone(context: context, x: 16)),
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32))),
context: context,
isScrollControlled: true,
enableDrag: true,
useSafeArea: true,
// useRootNavigator: true,
builder: (context) {
return FilterBottomSheet();
},
);
},
style: ButtonStyle(
elevation: MaterialStatePropertyAll(0),
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
side: BorderSide(
color: Colors.grey.shade300,
),
borderRadius: BorderRadius.circular(16))),
backgroundColor: MaterialStateProperty.resolveWith((states) {
return Colors.white;
}),
overlayColor:
MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)),
foregroundColor: MaterialStatePropertyAll(Colors.white),
shadowColor: MaterialStatePropertyAll(Colors.white),
surfaceTintColor: MaterialStatePropertyAll(Colors.white)),
child: Row(
children: [
Icon(
FluentIcons.options_24_filled,
size: 20,
color: Colors.black,
),
SizedBox(
width: 8,
),
Text(
"Filter",
style: Constant.subtitle_600_14(context: context)
.copyWith(color: Colors.black),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,534 @@
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/model/approval_type_model.dart';
import 'package:absensi_sas/provider/approval_filter_provider.dart';
import 'package:absensi_sas/widget/custom_button_approval.dart';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart';
class FilterBottomSheet extends HookConsumerWidget {
const FilterBottomSheet({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currApprovalTypeState = ref.read(approvalFilterProvider);
final approvalTypeList = [
ApprovalTypeModel(id: "1", name: "Presensi", isActive: false, value: "p"),
ApprovalTypeModel(id: "2", name: "Cuti", isActive: true, value: "c"),
ApprovalTypeModel(id: "3", name: "Lembur", isActive: false, value: "l"),
];
final staffFocuseNode = useFocusNode();
final startDateFocusNode = useFocusNode();
final endDateFocusNode = useFocusNode();
final staffCtr =
useTextEditingController(text: currApprovalTypeState.keyword);
final startDateCtr = useTextEditingController(
text: DateFormat('dd-MM-yyyy').format(currApprovalTypeState.startDate));
final startDateState = useState(currApprovalTypeState.startDate);
final endDateCtr = useTextEditingController(
text: DateFormat('dd-MM-yyyy').format(currApprovalTypeState.endDate));
final endDateState = useState(currApprovalTypeState.endDate);
final dateKey = useState(0);
final selectedApprovalTypeState = useState<List<String>>(
currApprovalTypeState.selectedApprovalType.toList());
final checkboxKey = useState(1000);
selectApprovalTypeFunction(String value) {
var tmp = selectedApprovalTypeState.value;
var cek = tmp.contains(value);
if (cek) {
tmp.remove(value);
} else {
tmp.add(value);
}
selectedApprovalTypeState.value = tmp;
checkboxKey.value = checkboxKey.value + 1;
}
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom * 0.75),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32), topRight: Radius.circular(32))),
child: SingleChildScrollView(
child: Container(
margin: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(context: context, x: 24)),
height: MediaQuery.of(context).size.height * 0.75,
child: Column(
children: [
Container(
height: MediaQuery.of(context).size.height * 0.1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Filter",
style: Constant.titleH1_700(context: context),
),
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.close))
],
),
),
Container(
height: MediaQuery.of(context).size.height * 0.55,
// color: Colors.green,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Staff",
style: Constant.titleH1_700_18(context: context)
.copyWith(fontWeight: FontWeight.w700),
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 16),
),
TextFormField(
controller: staffCtr,
focusNode: staffFocuseNode,
style: Constant.titleH1_500_18(context: context),
decoration: InputDecoration(
focusColor: Constant.textOrange,
// hoverColor: Constant.textOrange,
suffixIcon: Icon(
EvaIcons.searchOutline,
color: staffFocuseNode.hasFocus
? Constant.textOrange
: Color(0xff212B36),
),
label: Text(
"Cari Staff",
style: Constant.titleH1_500_18(context: context)
.copyWith(
color: staffFocuseNode.hasFocus
? Constant.textOrange
: Colors.grey.shade400),
),
hintText: "Cari Staff",
isDense: false,
contentPadding: EdgeInsets.symmetric(
vertical: Constant.getActualYPhone(
context: context, y: 16),
horizontal: Constant.getActualXPhone(
context: context, x: 14)),
hintStyle:
Constant.titleH1_500_18(context: context),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Constant.textOrange),
borderRadius: BorderRadius.circular(8)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade500, width: 1),
borderRadius: BorderRadius.circular(8)),
border: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.grey.shade500),
borderRadius: BorderRadius.circular(8))),
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
Row(
children: [
Expanded(
child: Text(
"Tanggal Awal",
style: Constant.titleH1_700_18(context: context)
.copyWith(fontWeight: FontWeight.w700),
),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 20),
),
Expanded(
child: Text(
"Tanggal Akhir",
style: Constant.titleH1_700_18(context: context)
.copyWith(fontWeight: FontWeight.w700),
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
Row(
children: [
Expanded(
child: TextFormField(
key: ValueKey(dateKey.value),
controller: startDateCtr,
focusNode: startDateFocusNode,
readOnly: true,
style: Constant.titleH1_500_18(context: context),
decoration: InputDecoration(
focusColor: Constant.textOrange,
// hoverColor: Constant.textOrange,
suffixIcon: Icon(
EvaIcons.calendar,
color: startDateFocusNode.hasFocus
? Constant.textOrange
: Color(0xff212B36),
),
label: Text(
"Tanggal Awal",
style: Constant.titleH1_500_18(
context: context)
.copyWith(
overflow: TextOverflow.ellipsis,
color: startDateFocusNode.hasFocus
? Constant.textOrange
: Colors.grey.shade400),
),
hintText: "Tanggal Awal",
isDense: false,
contentPadding: EdgeInsets.symmetric(
vertical: Constant.getActualYPhone(
context: context, y: 16),
horizontal: Constant.getActualXPhone(
context: context, x: 14)),
hintStyle:
Constant.titleH1_500_18(context: context)
.copyWith(
overflow: TextOverflow.ellipsis),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Constant.textOrange),
borderRadius: BorderRadius.circular(8)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade500,
width: 1),
borderRadius: BorderRadius.circular(8)),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade500),
borderRadius: BorderRadius.circular(8))),
onTap: () async {
startDateFocusNode.requestFocus();
dateKey.value = dateKey.value + 1;
DateTime? pickedDate = await showDatePicker(
helpText: "Tanggal Awal",
// locale: Locale('id', 'ID'),
builder: (context, child) {
return Theme(
data: ThemeData().copyWith(
datePickerTheme:
DatePickerThemeData(
dayBackgroundColor:
MaterialStateProperty
.resolveWith(
(states) {
if (states.contains(
MaterialState
.selected)) {
return Constant
.textOrange;
}
return Colors.white;
}),
todayBackgroundColor:
MaterialStateProperty
.resolveWith(
(states) {
if (states.contains(
MaterialState
.selected)) {
return Constant
.textOrange;
}
return Colors.white;
}),
todayBorder: BorderSide(
style:
BorderStyle.solid,
color: Constant
.textOrange,
width: 1),
headerBackgroundColor:
Constant.textOrange,
headerForegroundColor:
Colors.white,
surfaceTintColor:
Colors.white,
backgroundColor:
Colors.white),
primaryColor: Constant.textOrange,
primaryColorDark:
Constant.textOrange,
primaryColorLight:
Constant.textOrange),
child: child!);
},
confirmText: "OK",
cancelText: "Batal",
context: context,
initialDate: startDateState.value,
firstDate: DateTime(1950),
initialEntryMode:
DatePickerEntryMode.calendarOnly,
lastDate: DateTime(2100));
if (pickedDate != null) {
print(
pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
String formattedDate =
DateFormat('dd-MM-yyyy')
.format(pickedDate);
print(
formattedDate); //formatted date output using intl package => 2021-03-16
startDateCtr.text =
formattedDate; //set output date to TextField value.
startDateState.value = pickedDate;
startDateFocusNode.requestFocus();
} else {}
},
),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 20),
),
Expanded(
child: TextFormField(
key: ValueKey(dateKey.value),
controller: endDateCtr,
focusNode: endDateFocusNode,
readOnly: true,
style: Constant.titleH1_500_18(context: context),
decoration: InputDecoration(
focusColor: Constant.textOrange,
// hoverColor: Constant.textOrange,
suffixIcon: Icon(
EvaIcons.calendar,
color: endDateFocusNode.hasFocus
? Constant.textOrange
: Color(0xff212B36),
),
label: Text(
"Tanggal Akhir",
style: Constant.titleH1_500_18(
context: context)
.copyWith(
overflow: TextOverflow.ellipsis,
color: endDateFocusNode.hasFocus
? Constant.textOrange
: Colors.grey.shade400),
),
hintText: "Tanggal Akhir",
isDense: false,
contentPadding: EdgeInsets.symmetric(
vertical: Constant.getActualYPhone(
context: context, y: 16),
horizontal: Constant.getActualXPhone(
context: context, x: 14)),
hintStyle:
Constant.titleH1_500_18(context: context)
.copyWith(
overflow: TextOverflow.ellipsis),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Constant.textOrange),
borderRadius: BorderRadius.circular(8)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade500,
width: 1),
borderRadius: BorderRadius.circular(8)),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade500),
borderRadius: BorderRadius.circular(8))),
onTap: () async {
endDateFocusNode.requestFocus();
dateKey.value = dateKey.value + 1;
DateTime? pickedDate = await showDatePicker(
helpText: "Tanggal Akhir",
// locale: Locale('id', 'ID'),
builder: (context, child) {
return Theme(
data: ThemeData().copyWith(
datePickerTheme:
DatePickerThemeData(
dayBackgroundColor:
MaterialStateProperty
.resolveWith(
(states) {
if (states.contains(
MaterialState
.selected)) {
return Constant
.textOrange;
}
return Colors.white;
}),
todayBackgroundColor:
MaterialStateProperty
.resolveWith(
(states) {
if (states.contains(
MaterialState
.selected)) {
return Constant
.textOrange;
}
return Colors.white;
}),
todayBorder: BorderSide(
style:
BorderStyle.solid,
color: Constant
.textOrange,
width: 1),
headerBackgroundColor:
Constant.textOrange,
headerForegroundColor:
Colors.white,
surfaceTintColor:
Colors.white,
backgroundColor:
Colors.white),
primaryColor: Constant.textOrange,
primaryColorDark:
Constant.textOrange,
primaryColorLight:
Constant.textOrange),
child: child!);
},
confirmText: "OK",
cancelText: "Batal",
context: context,
initialDate: startDateState.value,
firstDate: DateTime(1950),
initialEntryMode:
DatePickerEntryMode.calendarOnly,
lastDate: DateTime(2100));
if (pickedDate != null) {
print(
pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
String formattedDate =
DateFormat('dd-MM-yyyy')
.format(pickedDate);
print(
formattedDate); //formatted date output using intl package => 2021-03-16
endDateCtr.text =
formattedDate; //set output date to TextField value.
endDateState.value = pickedDate;
endDateFocusNode.requestFocus();
} else {}
},
),
)
],
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 16),
),
// Text(
// ref
// .watch(approvalFilterProvider)
// .selectedApprovalType
// .toString(),
// style: Constant.titleH1_700_18(context: context),
// ),
Text(
"Approval",
style: Constant.titleH1_700_18(context: context)
.copyWith(fontWeight: FontWeight.w700),
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 16),
),
...approvalTypeList.map(
(e) {
return Row(
children: [
Checkbox(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4)),
value: selectedApprovalTypeState.value
.contains(e.value),
onChanged: (val) {
selectApprovalTypeFunction(e.value!);
},
),
Text(
e.name ?? "",
style: Constant.titleH1_700_18(context: context)
.copyWith(fontWeight: FontWeight.normal),
)
],
);
},
)
],
),
),
),
Container(
height: MediaQuery.of(context).size.height * 0.1,
child: Row(
children: [
Expanded(
child: CustomButtonWhite(
btnText: "Reset",
onPressed: () {
ref.read(approvalFilterProvider.notifier).state =
ApprovalFilterProvider(
startDate: DateTime.now(),
endDate: DateTime.now(),
keyword: "",
selectedApprovalType: []);
Navigator.pop(context);
}),
),
SizedBox(
width: Constant.getActualXPhone(context: context, x: 24),
),
Expanded(
child: CustomButtonOrange(
btnText: "Simpan",
onPressed: () {
ref.read(approvalFilterProvider.notifier).state =
ApprovalFilterProvider(
endDate: endDateState.value,
keyword: staffCtr.text,
selectedApprovalType:
selectedApprovalTypeState.value,
startDate: startDateState.value);
Navigator.pop(context);
}),
),
],
),
)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,368 @@
import 'dart:async';
import 'dart:math';
import 'package:absensi_sas/app/constant.dart';
import 'package:absensi_sas/model/approval_detail_model.dart';
import 'package:absensi_sas/screen/approval/approval_mockup.dart';
import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart';
import 'package:absensi_sas/widget/custom_button_approval.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:latlong2/latlong.dart';
class ApprovalDetailScreen extends HookConsumerWidget {
const ApprovalDetailScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final loading = useState(true);
final data = useState<ApprovalDetailModel>(
ApprovalDetailModel(imagePath: "", lat: 0, long: 0));
init() {
loading.value = true;
var randIdx = Random().nextInt(approvalDetailMockup.length);
var tmp = approvalDetailMockup[randIdx];
Timer(Duration(seconds: 2), () {
data.value = tmp;
loading.value = false;
});
}
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
init();
return;
});
return () {};
}, []);
return SafeArea(
minimum: EdgeInsets.only(
top: Constant.getActualYPhone(context: context, y: 30)),
child: Scaffold(
backgroundColor: Constant.textWhite,
appBar: AppBar(
backgroundColor: Colors.white,
shadowColor: Colors.white,
surfaceTintColor: Colors.white,
scrolledUnderElevation: 0,
elevation: 0,
leading: Container(
child: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.arrow_back_ios_new_rounded)),
),
centerTitle: true,
title: Text(
"Detail",
style: Constant.title_screen(context: context),
),
),
body: loading.value
? Center(
child: CircularProgressIndicator(),
)
: Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal:
Constant.getActualXPhone(context: context, x: 20)),
child: RefreshIndicator(
onRefresh: () async {
init();
},
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 36),
),
if (data.value.statusID == "1" ||
data.value.statusID == "2")
Row(
children: [
Spacer(),
if (data.value.statusID == "1")
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Constant.bgGreen),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(
context: context, x: 8),
vertical: Constant.getActualYPhone(
context: context, y: 2)),
child: Text(
"Approve",
style: Constant.body_12(context: context)
.copyWith(
color: Constant.primaryGreen),
),
),
if (data.value.statusID == "2")
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Constant.bgRed),
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(
context: context, x: 8),
vertical: Constant.getActualYPhone(
context: context, y: 2)),
child: Text(
"Reject",
style: Constant.body_12(context: context)
.copyWith(color: Constant.primaryRed),
),
)
],
),
if (data.value.statusID == "1" ||
data.value.statusID == "2")
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
Container(
margin: EdgeInsets.only(bottom: 8),
child: Row(
children: [
Expanded(
flex: 4,
child: Text("Nama : ",
style: Constant.body_16(context: context)
.copyWith(
color: Constant.textDarkGrey,
fontWeight: FontWeight.w400)),
),
Expanded(
flex: 9,
child: Text(data.value.name ?? "",
style: Constant.body_16(context: context)
.copyWith(
fontWeight: FontWeight.w400)),
)
],
),
),
Container(
margin: EdgeInsets.only(bottom: 8),
child: Row(
children: [
Expanded(
flex: 4,
child: Text("NIP : ",
style: Constant.body_16(context: context)
.copyWith(
color: Constant.textDarkGrey,
fontWeight: FontWeight.w400)),
),
Expanded(
flex: 9,
child: Text(data.value.nip ?? "",
style: Constant.body_16(context: context)
.copyWith(
fontWeight: FontWeight.w400)),
)
],
),
),
Container(
margin: EdgeInsets.only(bottom: 8),
child: Row(
children: [
Expanded(
flex: 4,
child: Text("Tanggal : ",
style: Constant.body_16(context: context)
.copyWith(
color: Constant.textDarkGrey,
fontWeight: FontWeight.w400)),
),
Expanded(
flex: 9,
child: Text(data.value.date ?? "",
style: Constant.body_16(context: context)
.copyWith(
fontWeight: FontWeight.w400)),
)
],
),
),
Container(
margin: EdgeInsets.only(bottom: 8),
child: Row(
children: [
Expanded(
flex: 4,
child: Text("Jam : ",
style: Constant.body_16(context: context)
.copyWith(
color: Constant.textDarkGrey,
fontWeight: FontWeight.w400)),
),
Expanded(
flex: 9,
child: Text(data.value.time ?? "",
style: Constant.body_16(context: context)
.copyWith(
fontWeight: FontWeight.w400)),
)
],
),
),
Container(
margin: EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: Text("Alamat : ",
style: Constant.body_16(context: context)
.copyWith(
color: Constant.textDarkGrey,
fontWeight: FontWeight.w400)),
),
Expanded(
flex: 9,
child: Text(data.value.address ?? "",
style: Constant.body_16(context: context)
.copyWith(
fontWeight: FontWeight.w400)),
)
],
),
),
Container(
margin: EdgeInsets.only(top: 16),
width: MediaQuery.of(context).size.width,
height: Constant.getActualYPhone(
context: context, y: 400),
child: FlutterMap(
options: MapOptions(
center: LatLng(data.value.lat ?? 0,
data.value.long ?? 0),
zoom: 16,
),
children: [
TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.example.app',
),
MarkerLayer(
markers: [
Marker(
point: LatLng(-7.539538, 110.798357),
child: const Icon(
Icons.location_on,
color: Colors.red,
size: 50,
),
)
// Marker(
// point: LatLng(lat, long),
// width: 100,
// height: 100,
// builder: (context) => const Icon(
// Icons.location_on,
// color: Colors.red,
// size: 50,
// )),
],
),
]),
),
if (data.value.imagePath != "" &&
data.value.imagePath != null)
Container(
margin: EdgeInsets.only(top: 16, bottom: 48),
child: Image.network(
data.value.imagePath!,
errorBuilder: (context, error, stackTrace) {
return Text(
"Error load image : ${error.toString()}",
style: Constant.body_12(context: context)
.copyWith(color: Constant.primaryRed),
);
},
loadingBuilder:
(context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
child: Column(
children: [
Text(loadingProgress.expectedTotalBytes !=
null
? ((loadingProgress.cumulativeBytesLoaded /
loadingProgress
.expectedTotalBytes!) *
100)
.round()
.toString() +
" %"
: ""),
LinearProgressIndicator(
value: loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
loadingProgress
.expectedTotalBytes!
: null,
),
],
));
},
),
),
if (data.value.statusID != "1" &&
data.value.statusID != "2")
Container(
margin: EdgeInsets.symmetric(vertical: 24),
child: Row(
children: [
Expanded(
child: CustomButtonWhite(
btnText: "Reject",
onPressed: () {
ApprovalDialogConfirmation(
context: context,
typename: "presensi ",
textInformation: "menolak ",
staffName: data.value.name ?? "",
confirmFunc: () {});
}),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 24),
),
Expanded(
child: CustomButtonOrange(
btnText: "Approve",
onPressed: () {
ApprovalDialogConfirmation(
context: context,
typename: "presensi ",
textInformation: "menyetujui ",
staffName: data.value.name ?? "",
confirmFunc: () {});
}),
),
],
),
)
],
),
),
),
),
));
}
}

View File

@@ -0,0 +1,75 @@
import 'package:absensi_sas/repository/googleapis_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
abstract class GoogleApisProviderState extends Equatable {
final DateTime date;
const GoogleApisProviderState(this.date);
@override
List<Object?> get props => [date];
}
class GoogleApisProviderStateInit extends GoogleApisProviderState {
GoogleApisProviderStateInit() : super(DateTime.now());
}
class GoogleApisProviderStateLoading extends GoogleApisProviderState {
GoogleApisProviderStateLoading() : super(DateTime.now());
}
class GoogleApisProviderStateError extends GoogleApisProviderState {
final String message;
GoogleApisProviderStateError({
required this.message,
}) : super(DateTime.now());
}
class GoogleApisProviderStateDone extends GoogleApisProviderState {
final String model;
GoogleApisProviderStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class GoogleApisProviderNotifier extends StateNotifier<GoogleApisProviderState> {
final Ref ref;
GoogleApisProviderNotifier({
required this.ref,
}) : super(GoogleApisProviderStateInit());
void googleApisProvider(
String latitude,
String longitude,
) async {
try {
state = GoogleApisProviderStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await GoogleApisRepository(graphql: graphql, dio: dio)
.getAddressFromCoordinates(
latitude,
longitude
);
state = GoogleApisProviderStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
print(e.message);
state = GoogleApisProviderStateError(message: e.message ?? "");
} else {
state = GoogleApisProviderStateError(message: e.toString());
}
}
}
}
// provider
final googleApisProviderProvider =
StateNotifierProvider<GoogleApisProviderNotifier, GoogleApisProviderState>(
(ref) => GoogleApisProviderNotifier(ref: ref),
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,860 @@
import 'package:absensi_sas/app/constant.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:google_sign_in/google_sign_in.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../app/route.dart';
import '../../provider/current_check_distance_provider.dart';
import '../../provider/current_check_jam_presensi_provider.dart';
import '../../provider/current_menu_provider.dart';
import '../../provider/current_user_provider.dart';
import '../../provider/google_login_provider.dart';
import '../../widget/custom_drawer.dart';
import '../../widget/real_date.dart';
import '../../widget/real_time.dart';
import '../../widget/sankbar_widget.dart';
import '../presensi/check_distance_provider.dart';
import '../presensi/check_presensi_jam_provider.dart';
class HomeScreenV1 extends HookConsumerWidget {
const HomeScreenV1({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedUser = ref.read(currentUserProvider);
final isLoadingProsesCheckDistance = useState<bool>(false);
final varCurrentDistanceProvider = ref.watch(currentCheckDistanceProvider);
final varCurrentCheckJamProvider =
ref.watch(currentCheckJamPresensiProvider);
final positionLatitude = useState<String>("");
final positionLongitude = useState<String>("");
// GoogleSignInAccount? currentUserGoogle =
// ref.watch(currentUserGoogleProvider);
final googleSignIn = ref.watch(googleSignInProvider);
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;
}
});
return () {};
}, []);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0";
if (staffID != "0") {
// panggil check jam presensi provider
Map<String, dynamic> inpVariablesCheckPresensiJam = {
"M_StaffID": selectedUser?.model.staffId ?? "",
"M_CompanyID": selectedUser?.model.companyId ?? "",
"token": selectedUser?.token ?? "",
};
ref.read(checkPresensiJamProvider.notifier).checkPresensiJam(
selectedUser?.model.staffId ?? "",
selectedUser?.model.companyId ?? "",
selectedUser?.token ?? "",
inpVariablesCheckPresensiJam,
);
}
});
return () {};
}, []);
Future<void> getAddressFromLocation() async {
try {
isLoadingProsesCheckDistance.value = true;
// Mendapatkan posisi pengguna
LocationPermission permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
isLoadingProsesCheckDistance.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<Placemark> placemarks = await placemarkFromCoordinates(
// position.latitude, position.longitude);
// if (placemarks.isNotEmpty) {
// isLoadingProsesCheckDistance.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();
// // panggil check distance provider
// ref.read(checkDistanceProvider.notifier).checkDistance(
// selectedUser?.model.staffId ?? "",
// selectedUser?.model.companyId ?? "",
// positionLatitude.value,
// positionLongitude.value,
// );
// } else {
// isLoadingProsesCheckDistance.value = false;
// SanckbarWidget(
// context, 'Tidak dapat menemukan alamat.', snackbarType.error);
// print("Tidak dapat menemukan alamat.");
// }
} catch (e) {
print("Error: $e");
isLoadingProsesCheckDistance.value = false;
SanckbarWidget(context, 'Error : $e', snackbarType.error);
}
}
Future<void> requestLocationPermission() async {
var status = await Permission.location.request();
isLoadingProsesCheckDistance.value = true;
if (status.isGranted) {
// Izin diberikan, lanjutkan dengan mendapatkan lokasi
getAddressFromLocation();
} else {
isLoadingProsesCheckDistance.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) {
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;
if (next.model.selfie == "TRUE") {
ref.read(currentPageProvider.notifier).update((state) => 99);
// Navigator.of(context).pop();
Navigator.of(context).restorablePushNamed(presensiSelfieRoute);
} else {
if (next.model.selfie == "FALSE") {
ref.read(currentPageProvider.notifier).update((state) => 99);
Navigator.of(context).restorablePushNamed(presensiRoute);
}
}
}
});
// check jam presensi
ref.listen(checkPresensiJamProvider, (prev, next) {
if (next is CheckPresensiJamStateLoading) {
isLoadingProsesCheckDistance.value = true;
} else if (next is CheckPresensiJamStateError) {
isLoadingProsesCheckDistance.value = false;
print("Error : " + next.toString());
SanckbarWidget(
context, "Error : " + next.toString(), snackbarType.warning);
} else if (next is CheckPresensiJamStateDone) {
isLoadingProsesCheckDistance.value = false;
}
});
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Container(
width: Constant.getActualXPhone(context: context, x: 100),
height: Constant.getActualYPhone(context: context, y: 100),
child: FittedBox(
child: (isLoadingProsesCheckDistance.value)
? SizedBox(
width: Constant.getActualXPhone(context: context, x: 50),
height: Constant.getActualYPhone(context: context, y: 50),
child: Center(
child: CircularProgressIndicator(
color: Constant.textOrange,
),
),
)
: FloatingActionButton(
onPressed: () async {
await requestLocationPermission();
},
backgroundColor: Color(0xFFFFFFFF),
shape: CircleBorder(),
child: Container(
width: Constant.getActualXPhone(context: context, x: 50),
height: Constant.getActualYPhone(context: context, y: 50),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
'images/finger_tap_orange_botnav.png'), // Ganti dengan path gambar Anda
),
),
),
),
),
),
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: <Widget>[
Expanded(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: AssetImage('images/home_orange.png'),
),
Text(
'Beranda',
style: Constant.subtitle_500_12(context: context).copyWith(
color: Constant.textOrange,
),
)
],
),
)),
Expanded(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: AssetImage('images/person_grey.png'),
),
Text(
'Profile',
style: Constant.subtitle_500_12(context: context).copyWith(
color: Constant.textLightGrey,
),
)
],
),
)),
],
),
),
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: Constant.getActualXPhone(context: context, x: 390),
// height: Constant.getActualYPhone(context: context, y: 844),
child: RefreshIndicator(
onRefresh: () async {},
child: Column(
children: [
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(
child: (googleSignIn.currentUser == null)
? Center(
child: CircularProgressIndicator(),
)
: ListTile(
// leading: Container(
// width: Constant.getActualXPhone(
// context: context, x: 36),
// height: Constant.getActualYPhone(
// context: context, y: 36),
// child: Image(
// image: AssetImage('images/avatar_c.png'),
// ),
// ),
leading: GoogleUserCircleAvatar(
identity: googleSignIn.currentUser!,
),
title: Text(
// "Stephen Kusumo",
// selectedUser?.model.name ?? "",
// currentUserGoogle.displayName ?? "",
googleSignIn.currentUser?.displayName ?? "",
overflow: TextOverflow.ellipsis,
style: Constant.titleH1_700(context: context)
..copyWith(
color: Constant.textBlack,
),
),
subtitle: Text(
// "Step@example.com",
// currentUserGoogle?.email ?? "",
// selectedUser?.model.email ?? "",
googleSignIn.currentUser?.email ?? "",
style:
Constant.subtitle_500_12(context: context)
.copyWith(
color: Constant.textLightGrey,
),
),
trailing: Container(
width: Constant.getActualXPhone(
context: context, x: 36),
height: Constant.getActualYPhone(
context: context, y: 36),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Colors.white,
shape: BoxShape.rectangle,
boxShadow: [
BoxShadow(
offset: Offset(0, 12),
blurRadius: 24,
color:
Color.fromRGBO(145, 158, 171, 0.12),
),
],
),
child: IconButton(
onPressed: () {},
icon: Container(
width: Constant.getActualXPhone(
context: context, x: 20),
height: Constant.getActualYPhone(
context: context, y: 20),
child: Image(
image:
AssetImage('images/alert_badge.png'),
),
),
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 44),
),
//Card Time
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(context: context, x: 33),
right: Constant.getActualXPhone(context: context, x: 27),
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 330),
height:
Constant.getActualYPhone(context: context, y: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
image: DecorationImage(
image: AssetImage(
'images/card_bg_1.png'), // Ganti dengan path gambar Anda
fit: BoxFit.fill, // Sesuaikan cara gambar ditampilkan
),
),
child: Padding(
padding: EdgeInsets.only(
top:
Constant.getActualYPhone(context: context, y: 16),
left:
Constant.getActualXPhone(context: context, x: 25),
right:
Constant.getActualXPhone(context: context, x: 25),
),
child: Container(
width: Constant.getActualXPhone(
context: context, x: 280),
height: Constant.getActualYPhone(
context: context, y: 150),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Date
RealTimeFormattedDate(),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
//Time
RealTimeClock(), // Menampilkan waktu real-time menggunakan RealTimeClock
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 20),
),
Row(
mainAxisAlignment: MainAxisAlignment
.center, // Menengahkan secara horizontal
children: [
Spacer(), // Spasi di sebelah kiri "Check In"
Column(
children: [
Image.asset(
'images/finger_tap.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),
),
(varCurrentCheckJamProvider
?.isAbsenClockIn ==
"TRUE")
? Text(
// '--:--',
varCurrentCheckJamProvider
?.jamClockIn ??
"NULL",
)
: Text(
'--:--',
style: TextStyle(
// Atur gaya teks '--:--' sesuai kebutuhan
),
),
Text(
'Clock In',
style: Constant.titleH2_700(
context: context)
.copyWith(
color: Constant.textLightGrey,
),
),
],
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 96),
), // Jarak antara "Check In" dan "Check Out"
Column(
children: [
Image.asset(
'images/finger_tap.png', // Path gambar untuk "Check Out"
width: Constant.getActualXPhone(
context: context, x: 22),
height: Constant.getActualYPhone(
context: context, y: 22),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
(varCurrentCheckJamProvider
?.isAbsenClockOut ==
"TRUE")
? Text(
// '--:--',
varCurrentCheckJamProvider
?.jamClockOut ??
"NULL",
)
: Text(
'--:--',
style: TextStyle(
// Atur gaya teks '--:--' sesuai kebutuhan
),
),
Text(
'Clock Out',
style: Constant.titleH2_700(
context: context)
.copyWith(
color: Constant.textLightGrey,
),
),
],
),
Spacer(), // Spasi di sebelah kanan "Check Out"
],
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 16),
),
],
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 56),
),
//Menu Cuti Lembur
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(context: context, x: 33),
right: Constant.getActualXPhone(context: context, x: 27),
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 330),
child: Row(
children: [
//Menu Cuti
Container(
width: Constant.getActualXPhone(
context: context, x: 98),
// color: Colors.amber,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(145, 158, 171, 0.20),
),
],
),
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: 8),
bottom: Constant.getActualYPhone(
context: context, y: 8),
),
child: InkWell(
onTap: () {},
child: Column(
children: [
Container(
child: Image(
width: Constant.getActualXPhone(
context: context, x: 50),
height: Constant.getActualYPhone(
context: context, y: 50),
image: AssetImage('images/person.png'),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
Text(
'Cuti',
style: Constant.titleH2_600_14(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
),
),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 18),
),
//Menu Lembur
Container(
width: Constant.getActualXPhone(
context: context, x: 98),
// color: Colors.amber,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(145, 158, 171, 0.20),
),
],
),
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: 8),
bottom: Constant.getActualYPhone(
context: context, y: 8),
),
child: InkWell(
onTap: () {},
child: Column(
children: [
Container(
child: Image(
width: Constant.getActualXPhone(
context: context, x: 50),
height: Constant.getActualYPhone(
context: context, y: 50),
image: AssetImage('images/task.png'),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
Text(
'Lembur',
style: Constant.titleH2_600_14(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
),
),
),
],
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 56),
),
//Menu Rekap Presensi
Padding(
padding: EdgeInsets.only(
right:
Constant.getActualXPhone(context: context, x: 27),
left: Constant.getActualXPhone(context: context, x: 33),
bottom:
Constant.getActualYPhone(context: context, y: 40)),
child: Container(
width: Constant.getActualXPhone(context: context, x: 330),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Rekap Presensi Bulan Ini',
style: Constant.titleH1_500_18(context: context)
.copyWith(
color: Constant.textTrueBlack,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 20),
),
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(16),
color:
Colors.white, // Set background color to #FFF
boxShadow: [
BoxShadow(
color: Color.fromRGBO(145, 158, 171, 0.20),
blurRadius: 2,
),
],
),
child: Padding(
padding: EdgeInsets.only(
top: Constant.getActualYPhone(
context: context, y: 12),
bottom: Constant.getActualYPhone(
context: context, y: 12),
left: Constant.getActualXPhone(
context: context, x: 24),
right: Constant.getActualXPhone(
context: context, x: 24),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Container(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'24 hari',
style: Constant.subtitle_600_14(
context: context)
.copyWith(
color: Constant.textOrange,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 4),
),
SizedBox(
child: Row(
children: [
Image(
image: AssetImage(
'images/person_available_grey.png'),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 4),
),
Text(
'Kehadiran',
style: Constant.subtitle_500_12(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
)
],
),
),
Image(
image: AssetImage('images/divider.png'),
),
//Tidak Hadir
Container(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'2 hari',
style: Constant.subtitle_600_14(
context: context)
.copyWith(
color: Constant.textOrange,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 4),
),
SizedBox(
child: Row(
children: [
Image(
image: AssetImage(
'images/person_delete_grey.png'),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 4),
),
Text(
'Tidak Hadir',
style: Constant.subtitle_500_12(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
)
],
),
),
Image(
image: AssetImage('images/divider.png'),
),
//Tidak Hadir
Container(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'5 hari',
style: Constant.subtitle_600_14(
context: context)
.copyWith(
color: Constant.textOrange,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 4),
),
SizedBox(
child: Row(
children: [
Image(
image: AssetImage(
'images/task_pending_grey.png'),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 4),
),
Text(
'Lembur',
style: Constant.subtitle_500_12(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
),
],
),
),
],
),
),
),
],
),
),
),
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,83 @@
import '../../model/rekap_kehadiran_home_screen_model.dart';
import '../../provider/current_rekap_kehadiran_home_provider.dart';
import '../../repository/presensi_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
abstract class RekapKehadiranHomeScreenState extends Equatable {
final DateTime date;
const RekapKehadiranHomeScreenState(this.date);
@override
List<Object?> get props => [date];
}
class RekapKehadiranHomeScreenStateInit extends RekapKehadiranHomeScreenState {
RekapKehadiranHomeScreenStateInit() : super(DateTime.now());
}
class RekapKehadiranHomeScreenStateLoading extends RekapKehadiranHomeScreenState {
RekapKehadiranHomeScreenStateLoading() : super(DateTime.now());
}
class RekapKehadiranHomeScreenStateError extends RekapKehadiranHomeScreenState {
final String message;
RekapKehadiranHomeScreenStateError({
required this.message,
}) : super(DateTime.now());
}
class RekapKehadiranHomeScreenStateDone extends RekapKehadiranHomeScreenState {
final RekapKehadiranHomeScreenModel model;
RekapKehadiranHomeScreenStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class RekapKehadiranHomeScreenNotifier extends StateNotifier<RekapKehadiranHomeScreenState> {
final Ref ref;
RekapKehadiranHomeScreenNotifier({
required this.ref,
}) : super(RekapKehadiranHomeScreenStateInit());
void rekapKehadiranHomeScreen(
String M_StaffID,
String M_CompanyID,
String token,
Map<String, dynamic> paramInpVariables,
) async {
try {
state = RekapKehadiranHomeScreenStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await PresensiRepository(graphql: graphql, dio: dio)
.rekapKehadiranHomeScreen(
M_StaffID,
M_CompanyID,
token,
paramInpVariables,
);
// set ke global state provider currentRekapKehadiranHomeProvider
ref.read(currentRekapKehadiranHomeProvider.notifier).update((state) => resp);
state = RekapKehadiranHomeScreenStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
print(e.message);
state = RekapKehadiranHomeScreenStateError(message: e.message ?? "");
} else {
state = RekapKehadiranHomeScreenStateError(message: e.toString());
}
}
}
}
// provider
final rekapKehadiranHomeScreenProvider =
StateNotifierProvider<RekapKehadiranHomeScreenNotifier, RekapKehadiranHomeScreenState>(
(ref) => RekapKehadiranHomeScreenNotifier(ref: ref),
);

View File

@@ -1,623 +0,0 @@
import 'package:absensi_sas_flutter/app/constant.dart';
import 'package:absensi_sas_flutter/screen/widget/real_date.dart';
import 'package:absensi_sas_flutter/screen/widget/real_time.dart';
import 'package:flutter/material.dart';
class HomepageScreen extends StatelessWidget {
const HomepageScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Container(
width: Constant.getActualXPhone(context: context, x: 100),
height: Constant.getActualYPhone(context: context, y: 100),
child: FittedBox(
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Color(0xFFFFFFFF),
shape: CircleBorder(),
child: Container(
width: Constant.getActualXPhone(context: context, x: 50),
height: Constant.getActualYPhone(context: context, y: 50),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
'images/finger_tap_orange_botnav.png'), // Ganti dengan path gambar Anda
),
),
),
),
),
),
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: <Widget>[
Expanded(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: AssetImage('images/home_orange.png'),
),
Text(
'Beranda',
style: Constant.subtitle_500_12(context: context).copyWith(
color: Constant.textOrange,
),
)
],
),
)),
Expanded(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: AssetImage('images/person_grey.png'),
),
Text(
'Profile',
style: Constant.subtitle_500_12(context: context).copyWith(
color: Constant.textLightGrey,
),
)
],
),
)),
],
),
),
body: SafeArea(
child: Container(
width: Constant.getActualXPhone(context: context, x: 390),
height: Constant.getActualYPhone(context: context, y: 844),
child: Column(
children: [
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(
child: ListTile(
leading: Container(
width:
Constant.getActualXPhone(context: context, x: 36),
height:
Constant.getActualYPhone(context: context, y: 36),
child: Image(
image: AssetImage('images/avatar_c.png'),
)),
title: Text(
"Stephen Kusumo",
style: Constant.titleH1_700(context: context)
..copyWith(
color: Constant.textBlack,
),
),
subtitle: Text(
"Step@example.com",
style:
Constant.subtitle_500_12(context: context).copyWith(
color: Constant.textLightGrey,
),
),
trailing: Container(
width: Constant.getActualXPhone(context: context, x: 36),
height: Constant.getActualYPhone(context: context, y: 36),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Colors.white,
shape: BoxShape.rectangle,
boxShadow: [
BoxShadow(
offset: Offset(0, 12),
blurRadius: 24,
color: Color.fromRGBO(145, 158, 171, 0.12),
),
],
),
child: IconButton(
onPressed: () {},
icon: Container(
width:
Constant.getActualXPhone(context: context, x: 20),
height:
Constant.getActualYPhone(context: context, y: 20),
child: Image(
image: AssetImage('images/alert_badge.png'),
),
),
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 44),
),
//Card Time
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(context: context, x: 33),
right: Constant.getActualXPhone(context: context, x: 27),
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 330),
height: Constant.getActualYPhone(context: context, y: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
image: DecorationImage(
image: AssetImage(
'images/card_bg_1.png'), // Ganti dengan path gambar Anda
fit: BoxFit.fill, // Sesuaikan cara gambar ditampilkan
),
),
child: Padding(
padding: EdgeInsets.only(
top: Constant.getActualYPhone(context: context, y: 16),
left: Constant.getActualXPhone(context: context, x: 25),
right: Constant.getActualXPhone(context: context, x: 25),
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 280),
height:
Constant.getActualYPhone(context: context, y: 150),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Date
RealTimeFormattedDate(),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
//Time
RealTimeClock(), // Menampilkan waktu real-time menggunakan RealTimeClock
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 20),
),
Row(
mainAxisAlignment: MainAxisAlignment
.center, // Menengahkan secara horizontal
children: [
Spacer(), // Spasi di sebelah kiri "Check In"
Column(
children: [
Image.asset(
'images/finger_tap.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: TextStyle(
// Atur gaya teks '--:--' sesuai kebutuhan
),
),
Text(
'Clock In',
style:
Constant.titleH2_700(context: context)
.copyWith(
color: Constant.textLightGrey,
),
),
],
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 96),
), // Jarak antara "Check In" dan "Check Out"
Column(
children: [
Image.asset(
'images/finger_tap.png', // Path gambar untuk "Check Out"
width: Constant.getActualXPhone(
context: context, x: 22),
height: Constant.getActualYPhone(
context: context, y: 22),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
Text(
'--:--',
style: TextStyle(
// Atur gaya teks '--:--' sesuai kebutuhan
),
),
Text(
'Clock Out',
style:
Constant.titleH2_700(context: context)
.copyWith(
color: Constant.textLightGrey,
),
),
],
),
Spacer(), // Spasi di sebelah kanan "Check Out"
],
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 16),
),
],
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 56),
),
//Menu Cuti Lembur
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(context: context, x: 33),
right: Constant.getActualXPhone(context: context, x: 27),
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 330),
child: Row(
children: [
//Menu Cuti
Container(
width:
Constant.getActualXPhone(context: context, x: 98),
// color: Colors.amber,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(145, 158, 171, 0.20),
),
],
),
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: 8),
bottom: Constant.getActualYPhone(
context: context, y: 8),
),
child: InkWell(
onTap: () {},
child: Column(
children: [
Container(
child: Image(
width: Constant.getActualXPhone(
context: context, x: 50),
height: Constant.getActualYPhone(
context: context, y: 50),
image: AssetImage('images/person.png'),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
Text(
'Cuti',
style:
Constant.titleH2_600_14(context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
),
),
),
SizedBox(
width:
Constant.getActualXPhone(context: context, x: 18),
),
//Menu Lembur
Container(
width:
Constant.getActualXPhone(context: context, x: 98),
// color: Colors.amber,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(145, 158, 171, 0.20),
),
],
),
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: 8),
bottom: Constant.getActualYPhone(
context: context, y: 8),
),
child: InkWell(
onTap: () {},
child: Column(
children: [
Container(
child: Image(
width: Constant.getActualXPhone(
context: context, x: 50),
height: Constant.getActualYPhone(
context: context, y: 50),
image: AssetImage('images/task.png'),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 8),
),
Text(
'Lembur',
style:
Constant.titleH2_600_14(context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
),
),
),
],
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 56),
),
//Menu Rekap Presensi
Padding(
padding: EdgeInsets.only(
right: Constant.getActualXPhone(context: context, x: 27),
left: Constant.getActualXPhone(context: context, x: 33),
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 330),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Rekap Presensi Bulan Ini',
style:
Constant.titleH1_500_18(context: context).copyWith(
color: Constant.textTrueBlack,
),
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(16),
color: Colors.white, // Set background color to #FFF
boxShadow: [
BoxShadow(
color: Color.fromRGBO(145, 158, 171, 0.20),
blurRadius: 2,
),
],
),
child: Padding(
padding: EdgeInsets.only(
top: Constant.getActualYPhone(
context: context, y: 12),
bottom: Constant.getActualYPhone(
context: context, y: 12),
left: Constant.getActualXPhone(
context: context, x: 24),
right: Constant.getActualXPhone(
context: context, x: 24),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'24 hari',
style: Constant.subtitle_600_14(
context: context)
.copyWith(
color: Constant.textOrange,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 4),
),
SizedBox(
child: Row(
children: [
Image(
image: AssetImage(
'images/person_available_grey.png'),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 4),
),
Text(
'Kehadiran',
style: Constant.subtitle_500_12(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
)
],
),
),
Image(
image: AssetImage('images/divider.png'),
),
//Tidak Hadir
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'2 hari',
style: Constant.subtitle_600_14(
context: context)
.copyWith(
color: Constant.textOrange,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 4),
),
SizedBox(
child: Row(
children: [
Image(
image: AssetImage(
'images/person_delete_grey.png'),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 4),
),
Text(
'Tidak Hadir',
style: Constant.subtitle_500_12(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
)
],
),
),
Image(
image: AssetImage('images/divider.png'),
),
//Tidak Hadir
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'5 hari',
style: Constant.subtitle_600_14(
context: context)
.copyWith(
color: Constant.textOrange,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context, y: 4),
),
SizedBox(
child: Row(
children: [
Image(
image: AssetImage(
'images/task_pending_grey.png'),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 4),
),
Text(
'Lembur',
style: Constant.subtitle_500_12(
context: context)
.copyWith(
color: Constant.textDarkGrey,
),
),
],
),
),
],
),
),
],
),
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,98 @@
import 'dart:convert';
import 'package:absensi_sas/provider/google_login_provider.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../model/auth_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/current_user_provider.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/auth_repository.dart';
import '../../repository/base_repository.dart';
abstract class LoginState extends Equatable {
final DateTime date;
const LoginState(this.date);
@override
List<Object?> get props => [date];
}
class LoginStateInit extends LoginState {
LoginStateInit() : super(DateTime.now());
}
class LoginStateLoading extends LoginState {
LoginStateLoading() : super(DateTime.now());
}
class LoginStateError extends LoginState {
final String message;
LoginStateError({
required this.message,
}) : super(DateTime.now());
}
class LoginStateDone extends LoginState {
final AuthModel model;
LoginStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class LoginNotifier extends StateNotifier<LoginState> {
final Ref ref;
LoginNotifier({
required this.ref,
}) : super(LoginStateInit());
void login(
String email,
String idGoogleSignIn,
) async {
try {
state = LoginStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await AuthRepository(graphql: graphql, dio: dio).login(
email,
idGoogleSignIn,
);
//Simpan ke token
final shared = await SharedPreferences.getInstance();
final token = jsonEncode({
"date": DateTime.now().toString(),
"model": resp.model,
"token": resp.token
});
await shared.setString(Constant.bearerName, token);
ref.read(currentUserProvider.notifier).state = resp;
// // set google sign in account
// final accountEncode = jsonEncode({
// "account": account,
// });
// await shared.setString(Constant.accountGoogle, accountEncode);
// ref.read(currentUserGoogleProvider.notifier).state = acc
state = LoginStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = LoginStateError(message: e.message ?? "");
} else {
state = LoginStateError(message: e.toString());
}
}
}
}
// provider
final loginProvider = StateNotifierProvider<LoginNotifier, LoginState>(
(ref) => LoginNotifier(ref: ref),
);

View File

@@ -1,37 +1,166 @@
import 'dart:convert';
import 'package:absensi_sas/screen/login/login_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
import '../../model/auth_model.dart';
import '../../provider/current_menu_provider.dart';
import '../../provider/current_user_provider.dart';
import '../../provider/google_login_provider.dart';
import '../../widget/sankbar_widget.dart';
class LoginScreen extends HookConsumerWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// inisialisasi
// ref.watch itu sama spt memantau state terus menerus
// ref.read itu memantau namun hny 1x saja
GoogleSignInAccount? currentUser = ref.watch(currentUserProvider);
// GoogleSignInAccount? currentUserGoogle =
// ref.watch(currentUserGoogleProvider);
final googleSignIn = ref.watch(googleSignInProvider);
final isLoading = useState(false);
// final errorMessage = useState("");
final isSuccess = useState(false);
useEffect(() {
googleSignIn.onCurrentUserChanged.listen((account) {
// untuk update value ke provider google_login_provider yaitu currentUserProvider
ref.read(currentUserProvider.notifier).update((state) => account);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
// final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0";
final shared = await SharedPreferences.getInstance();
final bearerString = shared.get(Constant.bearerName).toString();
final xmodel = jsonDecode(bearerString);
if (xmodel == null) return;
final authModel = AuthModel(
token: xmodel["token"],
model: StaffModel(
companyId: xmodel["model"]["company_id"],
companyName: xmodel["model"]["company_name"],
email: xmodel["model"]["email"],
staffId: xmodel["model"]["staff_id"],
idGoogleSignIn: xmodel['model']['id_google_sign_in'],
name: xmodel['model']['name'],
nip: xmodel['model']['nip'],
phoneNumber: xmodel["model"]["phone_number"],
token: xmodel["model"]["token"],
),
);
// NEW
// if (xmodel != null) {
// ref.read(loginProvider.notifier).login(
// authModel.model.email ?? "",
// authModel.model.idGoogleSignIn ?? "",
// );
// }
// OLD
await googleSignIn.signInSilently();
googleSignIn.onCurrentUserChanged.listen((account) {
// ref
// .read(currentUserGoogleProvider.notifier)
// .update((state) => account);
ref.read(currentUserProvider.notifier).state = authModel;
ref.read(currentPageProvider.state).update((state) => 0);
if (account != null &&
ref.read(currentUserProvider)?.model.staffId == "0") {
// Lakukan login
ref.read(loginProvider.notifier).login(
account.email,
account.id,
);
}
});
// kalau sudah pernah login
if (googleSignIn.currentUser?.email != null) {
googleSignIn.signInSilently();
// ref.read(currentUserGoogleProvider.notifier).update(
// (state) => googleSignIn.currentUser,
// );
ref.read(loginProvider.notifier).login(
googleSignIn.currentUser?.email ?? "",
googleSignIn.currentUser?.id ?? "",
);
}
// ref.read(currentUserProvider.notifier).state = authModel;
// Navigator.of(context).pushNamedAndRemoveUntil(
// homeRoute,
// (route) => false,
// );
});
googleSignIn.signInSilently();
return (){};
}, const []);
return () {};
}, []);
// LISTEN PROVIDER
ref.listen(loginProvider, (prev, next) {
if (next is LoginStateLoading) {
isLoading.value = true;
} else if (next is LoginStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
// Timer(const Duration(seconds: 3), () {
// errorMessage.value = "";
// });
SanckbarWidget(context, next.message, snackbarType.warning);
} else if (next is LoginStateDone) {
print("Login Done");
isLoading.value = false;
isSuccess.value = true;
ref.read(currentPageProvider.state).update((state) => 0);
Navigator.of(context)
.pushNamedAndRemoveUntil(homeRoute, (route) => false);
}
});
// fungsi untuk sync ke google mail
Future<void> handleSignIn() async {
Future<void> handleSignInGmail() async {
try {
// auth ke email
await googleSignIn.signIn();
googleSignIn.onCurrentUserChanged.listen((account) async {
// ref
// .read(currentUserGoogleProvider.notifier)
// .update((state) => account);
// Check jika account tidak null dan belum login
if (account != null &&
ref.read(currentUserProvider)?.model.staffId == "0") {
// Lakukan login
ref.read(loginProvider.notifier).login(
account.email,
account.id,
);
}
});
googleSignIn.signInSilently();
// kalau sudah pernah login
if (googleSignIn.currentUser?.email != null) {
googleSignIn.signInSilently();
// ref.read(currentUserGoogleProvider.notifier).update(
// (state) => googleSignIn.currentUser,
// );
ref.read(loginProvider.notifier).login(
googleSignIn.currentUser?.email ?? "",
googleSignIn.currentUser?.id ?? "",
);
}
// Navigator.of(context)
// .pushNamedAndRemoveUntil(homeRoute, (route) => false);
} catch (error) {
if (kDebugMode) {
print(error);
@@ -47,51 +176,47 @@ class LoginScreen extends HookConsumerWidget {
SizedBox(
height: Constant.getActualYPhone(context: context, y: 100),
),
(currentUser != null)
? Container(
child: ListTile(
leading: GoogleUserCircleAvatar(
identity: currentUser,
),
title: Text(
currentUser.displayName ?? "",
),
subtitle: Text(
currentUser.email,
),
trailing: IconButton(
icon: Icon(Icons.logout_outlined),
onPressed: () async {
await googleSignIn.disconnect();
},
),
),
)
:
// Logo Landscape
Padding(
padding: EdgeInsets.only(
top: Constant.getActualYPhone(context: context, y: 120),
left: Constant.getActualXPhone(context: context, x: 91),
right:
Constant.getActualXPhone(context: context, x: 90),
// bottom: Constant.getActualYPhone(context: context, y: y)
),
child: Container(
width:
Constant.getActualXPhone(context: context, x: 209),
height:
Constant.getActualYPhone(context: context, y: 70),
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage(
'images/logo_sismedika_landscape.png'),
),
),
),
// (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(
top: Constant.getActualYPhone(context: context, y: 120),
left: Constant.getActualXPhone(context: context, x: 91),
right: Constant.getActualXPhone(context: context, x: 90),
// bottom: Constant.getActualYPhone(context: context, y: y)
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 209),
height: Constant.getActualYPhone(context: context, y: 70),
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage('images/logo_sismedika_landscape.png'),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 100),
@@ -138,6 +263,17 @@ class LoginScreen extends HookConsumerWidget {
],
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 10),
),
// ElevatedButton(
// onPressed: () async {
// await googleSignIn.disconnect();
// },
// child: Text('logout'),
// ),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 64),
),
@@ -153,35 +289,53 @@ class LoginScreen extends HookConsumerWidget {
height: Constant.getActualYPhone(context: context, y: 48),
child: ElevatedButton(
onPressed: () async {
await handleSignIn();
await handleSignInGmail();
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
child: Stack(
children: [
Container(
width:
Constant.getActualXPhone(context: context, x: 24),
height:
Constant.getActualYPhone(context: context, y: 24),
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage('images/icon_google.png'),
),
),
),
SizedBox(
width:
Constant.getActualXPhone(context: context, x: 2),
),
Text(
'Continue with Google',
style: Constant.logintitle_700(context: context)
.copyWith(
color: Constant.textBlack,
),
),
(isLoading.value)
? SizedBox(
width: Constant.getActualXPhone(
context: context, x: 24),
height: Constant.getActualYPhone(
context: context, y: 24),
child: Center(
child: CircularProgressIndicator(
color: Constant.textTrueBlack,
),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: Constant.getActualXPhone(
context: context, x: 24),
height: Constant.getActualYPhone(
context: context, y: 24),
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage(
'images/icon_google.png'),
),
),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 2),
),
Text(
'Continue with Google',
style: Constant.logintitle_700(
context: context)
.copyWith(
color: Constant.textBlack,
),
),
],
),
],
),
style: ButtonStyle(

View File

@@ -0,0 +1,356 @@
import 'dart:convert';
import 'package:absensi_sas/screen/login/login_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
import '../../model/auth_model.dart';
import '../../provider/current_menu_provider.dart';
import '../../provider/current_user_provider.dart';
import '../../provider/google_login_provider.dart';
import '../../widget/sankbar_widget.dart';
class LoginScreenV1 extends HookConsumerWidget {
const LoginScreenV1({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// inisialisasi
// ref.watch itu sama spt memantau state terus menerus
// ref.read itu memantau namun hny 1x saja
// GoogleSignInAccount? currentUserGoogle =
// ref.watch(currentUserGoogleProvider);
final googleSignIn = ref.watch(googleSignInProvider);
final isLoading = useState(false);
// final errorMessage = useState("");
final isSuccess = useState(false);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
// final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0";
final shared = await SharedPreferences.getInstance();
final bearerString = shared.get(Constant.bearerName).toString();
final xmodel = jsonDecode(bearerString);
if (xmodel == null) return;
final authModel = AuthModel(
token: xmodel["token"],
model: StaffModel(
companyId: xmodel["model"]["companyId"],
companyName: xmodel["model"]["companyName"],
email: xmodel["model"]["email"],
staffId: xmodel["model"]["staffId"],
idGoogleSignIn: xmodel['model']['idGoogleSignIn'],
name: xmodel['model']['name'],
nip: xmodel['model']['nip'],
phoneNumber: xmodel["model"]["phoneNumber"],
token: xmodel["model"]["token"],
),
);
// ref.read(currentUserProvider.notifier).state = authModel;
await googleSignIn.signInSilently();
googleSignIn.onCurrentUserChanged.listen((account) {
// ref
// .read(currentUserGoogleProvider.notifier)
// .update((state) => account);
ref.read(currentUserProvider.notifier).state = authModel;
ref.read(currentPageProvider.state).update((state) => 0);
if (account != null &&
ref.read(currentUserProvider)?.model.staffId == "0") {
// Lakukan login
ref.read(loginProvider.notifier).login(
account.email,
account.id,
);
}
});
// kalau sudah pernah login
if (googleSignIn.currentUser?.email != null) {
googleSignIn.signInSilently();
// ref.read(currentUserGoogleProvider.notifier).update(
// (state) => googleSignIn.currentUser,
// );
ref.read(loginProvider.notifier).login(
googleSignIn.currentUser?.email ?? "",
googleSignIn.currentUser?.id ?? "",
);
}
// ref.read(currentUserProvider.notifier).state = authModel;
// Navigator.of(context).pushNamedAndRemoveUntil(
// homeRoute,
// (route) => false,
// );
});
return () {};
}, []);
// LISTEN PROVIDER
ref.listen(loginProvider, (prev, next) {
if (next is LoginStateLoading) {
isLoading.value = true;
} else if (next is LoginStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
// Timer(const Duration(seconds: 3), () {
// errorMessage.value = "";
// });
SanckbarWidget(context, next.message, snackbarType.warning);
} else if (next is LoginStateDone) {
print("Login Done");
isLoading.value = false;
isSuccess.value = true;
ref.read(currentPageProvider.state).update((state) => 0);
Navigator.of(context)
.pushNamedAndRemoveUntil(homeRoute, (route) => false);
}
});
// fungsi untuk sync ke google mail
Future<void> handleSignInGmail() async {
try {
// auth ke email
await googleSignIn.signIn();
googleSignIn.onCurrentUserChanged.listen((account) async {
// ref
// .read(currentUserGoogleProvider.notifier)
// .update((state) => account);
// Check jika account tidak null dan belum login
if (account != null &&
ref.read(currentUserProvider)?.model.staffId == "0") {
// Lakukan login
ref.read(loginProvider.notifier).login(
account.email,
account.id,
);
}
});
googleSignIn.signInSilently();
// kalau sudah pernah login
if (googleSignIn.currentUser?.email != null) {
googleSignIn.signInSilently();
// ref.read(currentUserGoogleProvider.notifier).update(
// (state) => googleSignIn.currentUser,
// );
ref.read(loginProvider.notifier).login(
googleSignIn.currentUser?.email ?? "",
googleSignIn.currentUser?.id ?? "",
);
}
// Navigator.of(context)
// .pushNamedAndRemoveUntil(homeRoute, (route) => false);
} catch (error) {
if (kDebugMode) {
print(error);
}
}
}
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
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();
// },
// ),
// ),
// )
// :
// Logo Landscape
Padding(
padding: EdgeInsets.only(
top: Constant.getActualYPhone(context: context, y: 120),
left: Constant.getActualXPhone(context: context, x: 91),
right: Constant.getActualXPhone(context: context, x: 90),
// bottom: Constant.getActualYPhone(context: context, y: y)
),
child: Container(
width: Constant.getActualXPhone(context: context, x: 209),
height: Constant.getActualYPhone(context: context, y: 70),
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage('images/logo_sismedika_landscape.png'),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 100),
),
// title
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Selamat Datang',
style: Constant.titleH1_700(context: context)
.copyWith(color: Constant.textBlack),
),
Container(
width: Constant.getActualXPhone(context: context, x: 24),
height: Constant.getActualYPhone(context: context, y: 24),
// color: Colors.redAccent,
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage('images/emoji_handshake.png'),
),
),
),
],
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 7),
),
// title grey
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Silahkan masuk untuk mengakses akun Anda',
style: Constant.titleH2_600(context: context).copyWith(
color: Constant.textLightGrey,
),
),
],
),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 10),
),
// ElevatedButton(
// onPressed: () async {
// await googleSignIn.disconnect();
// },
// child: Text('logout'),
// ),
SizedBox(
height: Constant.getActualYPhone(context: context, y: 64),
),
// button login
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(context: context, x: 23),
right: Constant.getActualXPhone(context: context, x: 23),
),
child: SizedBox(
width: Constant.getActualXPhone(context: context, x: 344),
height: Constant.getActualYPhone(context: context, y: 48),
child: ElevatedButton(
onPressed: () async {
await handleSignInGmail();
},
child: Stack(
children: [
(isLoading.value)
? SizedBox(
width: Constant.getActualXPhone(
context: context, x: 24),
height: Constant.getActualYPhone(
context: context, y: 24),
child: Center(
child: CircularProgressIndicator(
color: Constant.textTrueBlack,
),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: Constant.getActualXPhone(
context: context, x: 24),
height: Constant.getActualYPhone(
context: context, y: 24),
decoration: BoxDecoration(
// color: Colors.green,
image: DecorationImage(
// fit: BoxFit.contain,
image: AssetImage(
'images/icon_google.png'),
),
),
),
SizedBox(
width: Constant.getActualXPhone(
context: context, x: 2),
),
Text(
'Continue with Google',
style: Constant.logintitle_700(
context: context)
.copyWith(
color: Constant.textBlack,
),
),
],
),
],
),
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith(
(st) => Colors.white,
),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: BorderSide(
color: Color.fromRGBO(145, 158, 171, 0.32),
),
),
),
),
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,70 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../model/logout_response_model.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/auth_repository.dart';
import '../../repository/base_repository.dart';
// 3. state provider
final logoutProvider = StateNotifierProvider<LogoutNotifier, LogoutState>(
(ref) => LogoutNotifier(ref: ref));
// 2. notifier
class LogoutNotifier extends StateNotifier<LogoutState> {
final Ref ref;
LogoutNotifier({required this.ref}) : super(LogoutStateInit());
void logout({
required String email,
required String idGoogleSignIn,
}) async {
try {
state = LogoutStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await AuthRepository(graphql: graphql, dio: dio).logout(
email,
idGoogleSignIn,
);
// print(resp);
state = LogoutStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = LogoutStateError(message: e.message ?? "");
} else {
state = LogoutStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class LogoutState extends Equatable {
final DateTime date;
const LogoutState(this.date);
@override
List<Object?> get props => [date];
}
class LogoutStateInit extends LogoutState {
LogoutStateInit() : super(DateTime.now());
}
class LogoutStateLoading extends LogoutState {
LogoutStateLoading() : super(DateTime.now());
}
class LogoutStateError extends LogoutState {
final String message;
LogoutStateError({
required this.message,
}) : super(DateTime.now());
}
class LogoutStateDone extends LogoutState {
final LogoutResponseModel model;
LogoutStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,225 @@
import 'dart:convert';
import 'dart:io';
import 'package:absensi_sas/widget/sankbar_widget.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../app/constant.dart';
import '../../provider/camera_controller_provider.dart';
class CameraPage extends HookConsumerWidget {
const CameraPage({
Key? key,
required this.filePathParam,
required this.fileDataBase64Param,
}) : super(key: key);
final ValueNotifier<String> filePathParam;
final ValueNotifier<String> fileDataBase64Param;
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = ref.watch(cameraControllerProvider);
// final imagePath = useState<String?>("");
// final fileDataBase64 = useState<String?>("");
// final fileData = useState<XFile?>(null);
final filePath = useState<String>("");
final fileDataBase64 = useState<String>("");
final cameraController = useState<CameraController?>(null);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
try {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
final controller =
CameraController(cameras[0], ResolutionPreset.max);
await controller.initialize();
cameraController.value = controller;
}
} catch (e) {
print('Error initializing camera: $e');
}
});
return () {};
}, []);
Future<void> initializeCamera() async {
try {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
final controller = CameraController(cameras[0], ResolutionPreset.max);
await controller.initialize();
cameraController.value = controller;
}
} catch (e) {
print('Error initializing camera: $e');
}
}
Future<void> takePicture(
// ValueNotifier<XFile?> imageFile,
ValueNotifier<String> base64ImageString,
ValueNotifier<String> imagePath,
) async {
// final flag = ValueNotifier<bool>(false);
if (cameraController.value != null) {
try {
final image = await cameraController.value!.takePicture();
final bytes = await image.readAsBytes();
String base64Image = base64Encode(bytes);
// imageFile.value = image;
base64ImageString.value = base64Image;
imagePath.value = image.path;
fileDataBase64Param.value = base64Image;
filePathParam.value = image.path;
// final shared = await SharedPreferences.getInstance();
// shared.setString("base64Image", base64Image);
} catch (e) {
print('Error taking picture: $e');
// flag.value = false;
// RespErr(flag: false, message: 'Error taking picture: $e');
}
} else {
print('CameraController is not initialized.');
}
}
processFoto(BuildContext context) async {
await takePicture(
// fileData,
fileDataBase64,
filePath,
);
print("fileDataBase64 : ${fileDataBase64.value}");
if (fileDataBase64.value != "") {
// ref.read(imgPhotoWebProvider.notifier).state = fileDataBase64.value;
Navigator.of(context).pop();
} else {
print('Error: Image result is null');
}
// proses base64
// final shared = await SharedPreferences.getInstance();
// final imageBase64 = shared.getString("base64Image");
// if (imageBase64 != "") {
// fileDataBase64.value = imageBase64;
// ref.read(imgPhotoWebProvider.notifier).state = imageBase64;
// Navigator.of(context).pop();
// } else {
// print('Error: Image result is null');
// }
}
return Scaffold(
appBar: AppBar(
title: const Text('Take Photo'),
),
body: SafeArea(
child: Center(
child: controller == null || !controller.value.isInitialized
? const CircularProgressIndicator()
: Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
// const SizedBox(height: 50),
Expanded(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
),
),
SizedBox(
height:
Constant.getActualYPhone(context: context, y: 20),
),
TextButton(
onPressed: () async {
fileDataBase64Param.value = "";
filePathParam.value = "";
// cameraController.dispose();
// await controller.dispose();
// await initializeCamera();
},
child: const Text("Clear Foto"),
),
Spacer(),
ElevatedButton(
// onPressed: () {
// Navigator.of(context).pushNamed(homeRoute);
// },
onPressed: () async {
processFoto(context);
},
style: ButtonStyle(
backgroundColor: MaterialStateColor.resolveWith(
(st) => (fileDataBase64.value != "")
? Constant.textDarkGrey
: Constant.textOrange),
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: Constant.textOrange,
),
),
),
shadowColor:
MaterialStateProperty.all(Color(0xffff48423d)),
elevation: MaterialStateProperty.all(4.0),
),
child: Stack(
children: [
(fileDataBase64.value != "")
? SizedBox(
width: Constant.getActualXPhone(
context: context, x: 24),
height: Constant.getActualYPhone(
context: context, y: 32),
child: Center(
child: CircularProgressIndicator(
color: Constant.textOrange,
),
),
)
: Align(
alignment: Alignment.center,
child: Text(
'Process Photo',
style: Constant.titleH1_500_18(
context: context)
.copyWith(
color: Constant.textWhite,
),
),
),
],
),
),
// if (imagePath.value.isNotEmpty)
// Container(
// width: 300,
// height: 300,
// child: Image.network(imagePath.value),
// ),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,92 @@
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
class CameraPageV1 extends StatefulWidget {
const CameraPageV1({Key? key}) : super(key: key);
@override
State<CameraPageV1> createState() => _CameraPageState();
}
class _CameraPageState extends State<CameraPageV1> {
CameraController? controller;
String imagePath = "";
List<CameraDescription>? cameras;
@override
void initState() {
super.initState();
initializeCamera();
}
Future<void> initializeCamera() async {
// Initialize cameras
WidgetsFlutterBinding.ensureInitialized();
try {
cameras = await availableCameras();
if (cameras != null && cameras!.isNotEmpty) {
// Use the first available camera
controller = CameraController(cameras![0], ResolutionPreset.max);
await controller?.initialize();
if (mounted) {
setState(() {});
}
}
} catch (e) {
print('Error initializing camera: $e');
}
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (controller == null || !controller!.value.isInitialized) {
return Center(child: CircularProgressIndicator());
}
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
SizedBox(height: 50),
Container(
width: 200,
height: 200,
child: AspectRatio(
aspectRatio: controller!.value.aspectRatio,
child: CameraPreview(controller!),
),
),
TextButton(
onPressed: () async {
try {
final image = await controller!.takePicture();
setState(() {
imagePath = image.path;
});
} catch (e) {
print('Error taking picture: $e');
}
},
child: Text("Take Photo"),
),
if (imagePath.isNotEmpty)
Container(
width: 300,
height: 300,
child: Image.file(File(imagePath)),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,98 @@
import 'package:absensi_sas/provider/current_user_provider.dart';
import 'package:absensi_sas/repository/presensi_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../model/check_distance_model.dart';
import '../../provider/current_check_distance_provider.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
import 'check_presensi_jam_provider.dart';
abstract class CheckDistanceState extends Equatable {
final DateTime date;
const CheckDistanceState(this.date);
@override
List<Object?> get props => [date];
}
class CheckDistanceStateInit extends CheckDistanceState {
CheckDistanceStateInit() : super(DateTime.now());
}
class CheckDistanceStateLoading extends CheckDistanceState {
CheckDistanceStateLoading() : super(DateTime.now());
}
class CheckDistanceStateError extends CheckDistanceState {
final String message;
CheckDistanceStateError({
required this.message,
}) : super(DateTime.now());
}
class CheckDistanceStateDone extends CheckDistanceState {
final CheckDistanceModel model;
CheckDistanceStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class CheckDistanceNotifier extends StateNotifier<CheckDistanceState> {
final Ref ref;
CheckDistanceNotifier({
required this.ref,
}) : super(CheckDistanceStateInit());
void checkDistance(
String M_StaffID,
String M_CompanyID,
String CurrentLatitude,
String CurrentLongitude,
) async {
try {
state = CheckDistanceStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp =
await PresensiRepository(graphql: graphql, dio: dio).checkDistance(
M_StaffID,
M_CompanyID,
CurrentLatitude,
CurrentLongitude,
);
// set ke global state provider
ref.read(currentCheckDistanceProvider.notifier).update((state) => resp);
// panggil provider cek jam masuk dan pulang (checkPresensiJamProvider)
final user = ref.read(currentUserProvider);
Map<String, dynamic> inpVariablesCheckPresensiJam = {
"M_StaffID": M_StaffID,
"M_CompanyID": M_CompanyID,
"token": user?.token ?? "",
};
ref.read(checkPresensiJamProvider.notifier).checkPresensiJam(
M_StaffID,
M_CompanyID,
user?.token ?? "",
inpVariablesCheckPresensiJam,
);
state = CheckDistanceStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = CheckDistanceStateError(message: e.message ?? "");
} else {
state = CheckDistanceStateError(message: e.toString());
}
}
}
}
// provider
final checkDistanceProvider =
StateNotifierProvider<CheckDistanceNotifier, CheckDistanceState>(
(ref) => CheckDistanceNotifier(ref: ref),
);

View File

@@ -0,0 +1,82 @@
import 'package:absensi_sas/model/check_presensi_jam_model.dart';
import 'package:absensi_sas/repository/presensi_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/current_check_jam_presensi_provider.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
abstract class CheckPresensiJamState extends Equatable {
final DateTime date;
const CheckPresensiJamState(this.date);
@override
List<Object?> get props => [date];
}
class CheckPresensiJamStateInit extends CheckPresensiJamState {
CheckPresensiJamStateInit() : super(DateTime.now());
}
class CheckPresensiJamStateLoading extends CheckPresensiJamState {
CheckPresensiJamStateLoading() : super(DateTime.now());
}
class CheckPresensiJamStateError extends CheckPresensiJamState {
final String message;
CheckPresensiJamStateError({
required this.message,
}) : super(DateTime.now());
}
class CheckPresensiJamStateDone extends CheckPresensiJamState {
final CheckPresensiJamModel model;
CheckPresensiJamStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class CheckPresensiJamNotifier extends StateNotifier<CheckPresensiJamState> {
final Ref ref;
CheckPresensiJamNotifier({
required this.ref,
}) : super(CheckPresensiJamStateInit());
void checkPresensiJam(
String M_StaffID,
String M_CompanyID,
String token,
Map<String, dynamic> paramInpVariables,
) async {
try {
state = CheckPresensiJamStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await PresensiRepository(graphql: graphql, dio: dio)
.checkPresensiJam(
M_StaffID,
M_CompanyID,
token,
paramInpVariables,
);
// set ke global state provider currentCheckJamPresensiProvider
ref.read(currentCheckJamPresensiProvider.notifier).update((state) => resp);
state = CheckPresensiJamStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
print(e.message);
state = CheckPresensiJamStateError(message: e.message ?? "");
} else {
state = CheckPresensiJamStateError(message: e.toString());
}
}
}
}
// provider
final checkPresensiJamProvider =
StateNotifierProvider<CheckPresensiJamNotifier, CheckPresensiJamState>(
(ref) => CheckPresensiJamNotifier(ref: ref),
);

View File

@@ -0,0 +1,69 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
import '../../repository/googleapis_repository.dart';
// 3. state provider
final googleApisProvider =
StateNotifierProvider<GoogleApisNotifier, GoogleApisState>(
(ref) => GoogleApisNotifier(ref: ref));
// 2. notifier
class GoogleApisNotifier extends StateNotifier<GoogleApisState> {
final Ref ref;
GoogleApisNotifier({required this.ref}) : super(GoogleApisStateInit());
void getAddressGoogleApis(
{required String latitude, required String longitude}) async {
try {
state = GoogleApisStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await GoogleApisRepository(graphql: graphql, dio: dio).getAddressFromCoordinates(
latitude,
longitude,
);
// print(resp);
state = GoogleApisStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = GoogleApisStateError(message: e.message ?? "");
} else {
state = GoogleApisStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class GoogleApisState extends Equatable {
final DateTime date;
const GoogleApisState(this.date);
@override
List<Object?> get props => [date];
}
class GoogleApisStateInit extends GoogleApisState {
GoogleApisStateInit() : super(DateTime.now());
}
class GoogleApisStateLoading extends GoogleApisState {
GoogleApisStateLoading() : super(DateTime.now());
}
class GoogleApisStateError extends GoogleApisState {
final String message;
GoogleApisStateError({
required this.message,
}) : super(DateTime.now());
}
class GoogleApisStateDone extends GoogleApisState {
final String? model;
GoogleApisStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -0,0 +1,102 @@
import '../../repository/presensi_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
import 'check_presensi_jam_provider.dart';
abstract class PresensiClockInState extends Equatable {
final DateTime date;
const PresensiClockInState(this.date);
@override
List<Object?> get props => [date];
}
class PresensiClockInStateInit extends PresensiClockInState {
PresensiClockInStateInit() : super(DateTime.now());
}
class PresensiClockInStateLoading extends PresensiClockInState {
PresensiClockInStateLoading() : super(DateTime.now());
}
class PresensiClockInStateError extends PresensiClockInState {
final String message;
PresensiClockInStateError({
required this.message,
}) : super(DateTime.now());
}
class PresensiClockInStateDone extends PresensiClockInState {
final String model;
PresensiClockInStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class PresensiClockInNotifier extends StateNotifier<PresensiClockInState> {
final Ref ref;
PresensiClockInNotifier({
required this.ref,
}) : super(PresensiClockInStateInit());
void presensiClockIn(
String T_TransactionM_StaffID,
String T_TransactionM_CompanyID,
String T_TransactionCurrentLatitude,
String T_TransactionCurrentLongitude,
String T_TransactionCurrentDistance,
String T_TransactionSelfiePhoto,
String token,
String isSelfie,
Map<String, dynamic> paramInpVariables,
) async {
try {
state = PresensiClockInStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await PresensiRepository(graphql: graphql, dio: dio)
.presensiNormalClockIn(
T_TransactionM_StaffID,
T_TransactionM_CompanyID,
T_TransactionCurrentLatitude,
T_TransactionCurrentLongitude,
T_TransactionCurrentDistance,
T_TransactionSelfiePhoto,
token,
isSelfie,
paramInpVariables,
);
// panggil provider cek jam masuk dan pulang (checkPresensiJamProvider)
Map<String, dynamic> inpVariablesCheckPresensiJam = {
"M_StaffID": T_TransactionM_StaffID,
"M_CompanyID": T_TransactionM_CompanyID,
"token": token,
};
ref.read(checkPresensiJamProvider.notifier).checkPresensiJam(
T_TransactionM_StaffID,
T_TransactionM_CompanyID,
token,
inpVariablesCheckPresensiJam,
);
print('call check presensi');
state = PresensiClockInStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = PresensiClockInStateError(message: e.message ?? "");
} else {
state = PresensiClockInStateError(message: e.toString());
}
}
}
}
// provider
final presensiClockInProvider =
StateNotifierProvider<PresensiClockInNotifier, PresensiClockInState>(
(ref) => PresensiClockInNotifier(ref: ref),
);

View File

@@ -0,0 +1,102 @@
import 'package:absensi_sas/repository/presensi_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../provider/dio_provider.dart';
import '../../provider/graphql_provider.dart';
import '../../repository/base_repository.dart';
import 'check_presensi_jam_provider.dart';
abstract class PresensiClockOutState extends Equatable {
final DateTime date;
const PresensiClockOutState(this.date);
@override
List<Object?> get props => [date];
}
class PresensiClockOutStateInit extends PresensiClockOutState {
PresensiClockOutStateInit() : super(DateTime.now());
}
class PresensiClockOutStateLoading extends PresensiClockOutState {
PresensiClockOutStateLoading() : super(DateTime.now());
}
class PresensiClockOutStateError extends PresensiClockOutState {
final String message;
PresensiClockOutStateError({
required this.message,
}) : super(DateTime.now());
}
class PresensiClockOutStateDone extends PresensiClockOutState {
final String model;
PresensiClockOutStateDone({
required this.model,
}) : super(DateTime.now());
}
//notifier
class PresensiClockOutNotifier extends StateNotifier<PresensiClockOutState> {
final Ref ref;
PresensiClockOutNotifier({
required this.ref,
}) : super(PresensiClockOutStateInit());
void presensiClockOut(
String T_TransactionM_StaffID,
String T_TransactionM_CompanyID,
String T_TransactionCurrentLatitude,
String T_TransactionCurrentLongitude,
String T_TransactionCurrentDistance,
String T_TransactionSelfiePhoto,
String token,
String isSelfie,
Map<String, dynamic> paramInpVariables,
) async {
try {
state = PresensiClockOutStateLoading();
final graphql = ref.read(graphqlProvider(''));
final dio = ref.read(dioProvider);
final resp = await PresensiRepository(graphql: graphql, dio: dio)
.presensiNormalClockOut(
T_TransactionM_StaffID,
T_TransactionM_CompanyID,
T_TransactionCurrentLatitude,
T_TransactionCurrentLongitude,
T_TransactionCurrentDistance,
T_TransactionSelfiePhoto,
token,
isSelfie,
paramInpVariables,
);
// panggil provider cek jam masuk dan pulang (checkPresensiJamProvider)
Map<String, dynamic> inpVariablesCheckPresensiJam = {
"M_StaffID": T_TransactionM_StaffID,
"M_CompanyID": T_TransactionM_CompanyID,
"token": token,
};
ref.read(checkPresensiJamProvider.notifier).checkPresensiJam(
T_TransactionM_StaffID,
T_TransactionM_CompanyID,
token,
inpVariablesCheckPresensiJam,
);
print('call check presensi clockout');
state = PresensiClockOutStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = PresensiClockOutStateError(message: e.message ?? "");
} else {
state = PresensiClockOutStateError(message: e.toString());
}
}
}
}
// provider
final presensiClockOutProvider =
StateNotifierProvider<PresensiClockOutNotifier, PresensiClockOutState>(
(ref) => PresensiClockOutNotifier(ref: ref),
);

View File

@@ -0,0 +1,859 @@
import 'dart:convert';
import 'package:absensi_sas/app/googleapis_location.dart';
import 'package:camera/camera.dart';
import 'package:camera_web/camera_web.dart';
import 'dart:io';
import 'package:absensi_sas/repository/googleapis_repository.dart';
import 'package:absensi_sas/screen/presensi/presensi_clock_in_provider.dart';
import 'package:absensi_sas/screen/presensi/presensi_clock_out_provider.dart';
import 'package:absensi_sas/widget/custom_drawer.dart';
import 'package:dart_nominatim/dart_nominatim.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
// import 'package:geocoding/geocoding.dart';
// import 'package:geolocator/geolocator.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_picker_web/image_picker_web.dart';
import 'package:latlong2/latlong.dart';
import 'package:location/location.dart';
import 'package:mobkit_dashed_border/mobkit_dashed_border.dart';
import 'package:shared_preferences/shared_preferences.dart';
// import 'package:permission_handler/permission_handler.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
import '../../provider/camera_controller_provider.dart';
import '../../provider/current_check_distance_provider.dart';
import '../../provider/current_check_jam_presensi_provider.dart';
import '../../provider/current_user_provider.dart';
import '../../provider/google_login_provider.dart';
import '../../widget/custom_dialog_presensi_selfie_sukses.dart';
import '../../widget/custom_google_map_widget.dart';
import '../../widget/real_date.dart';
import '../../widget/real_time.dart';
import '../../widget/sankbar_widget.dart';
import 'camera_page.dart';
import 'check_distance_provider.dart';
import 'check_presensi_jam_provider.dart';
import 'googleapis_provider.dart';
import 'dart:io' as io;
class PresensiScreen extends HookConsumerWidget {
const PresensiScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentAddressUserLocation = useState<String>("");
final isLoadingAddressUserLocation = useState<bool>(false);
final isLoadingProsesCheckDistance = useState<bool>(false);
final varCurrentDistanceProvider = ref.watch(currentCheckDistanceProvider);
final varCurrentCheckJamProvider =
ref.watch(currentCheckJamPresensiProvider);
final selectedUser = ref.watch(currentUserProvider);
final positionLatitude = useState<String>("");
final positionLongitude = useState<String>("");
final currLatProvider = ref.read(currentLatitudeProvider);
final currLongProvider = ref.read(currentLongitudeProvider);
// final tTransactionCurrentDistance = useState<String>("NULL");
Location location = new Location();
LocationData _locationData;
Future<void> 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;
// }
final permission = await location.hasPermission();
if (permission == PermissionStatus.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);
location.changeSettings(
accuracy: LocationAccuracy.high,
interval: 1000,
distanceFilter: 5,
);
_locationData = await location.getLocation();
// Mendapatkan alamat dari posisi
// final address = await positionToAddressGoogleApis(
// LatLng(_locationData.latitude!, _locationData.longitude!));
final address = await positionToAddressGoogleApis(
LatLng(currLatProvider, currLongProvider));
if (positionLongitude.value.isEmpty && positionLatitude.value.isEmpty) {
if (address != "") {
isLoadingAddressUserLocation.value = false;
// ref.read(googleApisProvider.notifier).getAddressGoogleApis(
// latitude: _locationData.latitude.toString(),
// longitude: _locationData.longitude.toString(),
// );
ref.read(googleApisProvider.notifier).getAddressGoogleApis(
latitude: currLatProvider.toString(),
longitude: currLongProvider.toString(),
);
// String address =
// "${placemark.thoroughfare}, ${placemark.locality}, ${placemark.administrativeArea}, ${placemark.country},";
// positionLatitude.value = _locationData.latitude.toString();
// positionLongitude.value = _locationData.longitude.toString();
positionLatitude.value = currLatProvider.toString();
positionLongitude.value = currLongProvider.toString();
} 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<void> requestLocationPermission() async {
// var status = await Permission.location.request();
final status = await location.serviceEnabled();
isLoadingAddressUserLocation.value = true;
if (status) {
// 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);
}
}
// googleApis address
ref.listen(googleApisProvider, (prev, next) {
if (next is GoogleApisStateLoading) {
isLoadingProsesCheckDistance.value = true;
} else if (next is GoogleApisStateError) {
isLoadingProsesCheckDistance.value = false;
SanckbarWidget(context, next.message, snackbarType.warning);
} else if (next is GoogleApisStateDone) {
isLoadingProsesCheckDistance.value = false;
currentAddressUserLocation.value = next.model ?? "0";
}
});
// 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<void> 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: <Widget>[
ElevatedButton(
onPressed: () {
T_TransactionSelfiePhoto = "";
Map<String, dynamic> 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>(
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>(
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(
backgroundColor: Constant.textWhite,
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();
// refresh location
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CustomGoogleMapWidget(
positionLastLatitudeParam:
positionLatitude,
positionLastLongitudeParam:
positionLongitude,
),
),
);
// 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,
),
),
],
),
],
),
),
),
],
),
),
),
),
),
);
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More