From a28830deb00980b73afc34e317dff31f1941f729 Mon Sep 17 00:00:00 2001 From: sindhu Date: Wed, 26 Feb 2025 19:28:02 +0700 Subject: [PATCH] step 22 : no_login -> login dan list riwayat pake JWT done, masih belum done di refreshToken karena check waktu expired salah --- lib/app/app_extension.dart | 56 ++- lib/app/route.dart | 41 +- lib/main.dart | 2 +- lib/model/no-login/no_login_auth_model.dart | 90 ++++ .../no_login_current_user_provider.dart | 4 + .../no_login_scan_repository.dart | 103 +++++ .../no-login/no_login_auth_repository.dart | 85 ++++ .../no_login_card_riwayat_scan.dart | 183 +++++++++ .../no-login-home/no_login_home_screen.dart | 331 +++++++++++++++ .../no_login_riwayat_scan_provider.dart | 90 ++++ .../no_login_splash_screen.dart | 101 +++++ lib/screen/no-login/no_login_provider.dart | 202 +++++++++ .../no_login_refresh_token_provider.dart | 228 +++++++++++ lib/screen/no-login/no_login_screen.dart | 384 ++++++++++++++++++ 14 files changed, 1897 insertions(+), 3 deletions(-) create mode 100644 lib/model/no-login/no_login_auth_model.dart create mode 100644 lib/provider/no-login/no_login_current_user_provider.dart create mode 100644 lib/repository/no-login-scan/no_login_scan_repository.dart create mode 100644 lib/repository/no-login/no_login_auth_repository.dart create mode 100644 lib/screen/no-login-home/no_login_card_riwayat_scan.dart create mode 100644 lib/screen/no-login-home/no_login_home_screen.dart create mode 100644 lib/screen/no-login-home/no_login_riwayat_scan_provider.dart create mode 100644 lib/screen/no-login-splash/no_login_splash_screen.dart create mode 100644 lib/screen/no-login/no_login_provider.dart create mode 100644 lib/screen/no-login/no_login_refresh_token_provider.dart create mode 100644 lib/screen/no-login/no_login_screen.dart diff --git a/lib/app/app_extension.dart b/lib/app/app_extension.dart index 5ae765a..78c4298 100644 --- a/lib/app/app_extension.dart +++ b/lib/app/app_extension.dart @@ -1,5 +1,59 @@ import 'package:jiffy/jiffy.dart'; +import 'package:shared_preferences/shared_preferences.dart'; String formatDateJiffy(String serverDate) { return Jiffy.parse(serverDate).format(pattern: 'dd-MM-yyyy'); -} \ No newline at end of file +} + +// Future isTokenExpired() async { +// final prefs = await SharedPreferences.getInstance(); +// final String? expireDateStr = prefs.getString("expire_date"); + +// if (expireDateStr == null) return true; + +// final DateTime expireDateTime = DateTime.parse(expireDateStr); +// final DateTime now = DateTime.now(); + +// print('expireDateTime : '+expireDateTime.toString()); +// print('now : '+now.toString()); + +// // return DateTime.now().isAfter(expireDateTime); +// // Jika token kurang dari 1 menit lagi akan kedaluwarsa, +// // atau lebih dari = 2 menit, maka harus refresh +// return (expireDateTime.difference(now).inMinutes == 1) || (expireDateTime.difference(now).inMinutes >= 2); +// } + +Future isTokenExpired() async { + final prefs = await SharedPreferences.getInstance(); + final String? expireDateStr = prefs.getString("expire_date"); + + print("📌 Expire Date dari SharedPreferences: $expireDateStr"); + + if (expireDateStr == null) { + print("❌ Expire date tidak ditemukan, dianggap expired."); + return true; + } + + DateTime? expireDateTime; + try { + expireDateTime = DateTime.parse(expireDateStr); + } catch (e) { + print("❌ Format expire_date tidak valid: $expireDateStr, Error: $e"); + return true; + } + + final DateTime now = DateTime.now(); + final int diffInMinutes = expireDateTime.difference(now).inMinutes; + + print("🕒 Expire date: $expireDateTime"); + print("🕒 Waktu sekarang: $now"); + print("⏳ Selisih waktu dalam menit: $diffInMinutes"); + + // Jika sudah lewat atau kurang dari 4 menit, anggap expired agar bisa di-refresh sebelum benar-benar habis + final bool expired = now.isAfter(expireDateTime) || diffInMinutes <= 4; + print(expired ? "🔴 Token EXPIRED (Perlu Refresh)" : "🟢 Token MASIH AKTIF"); + + // final bool expired = now.isAfter(expireDateTime); + + return expired; +} diff --git a/lib/app/route.dart b/lib/app/route.dart index e737074..4efb32e 100644 --- a/lib/app/route.dart +++ b/lib/app/route.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import '../screen/no-login-home/no_login_home_screen.dart'; +import '../screen/no-login-splash/no_login_splash_screen.dart'; +import '../screen/no-login/no_login_screen.dart'; import '../screen/home/home_screen.dart'; import '../screen/login/login_screen.dart'; @@ -12,6 +15,12 @@ const homeRoute = "/homeRoute"; const scanRoute = "/scanRoute"; const editScanRoute = "/editScanRoute"; +const noLoginSplashRoute = "/noLoginSplashRoute"; +const noLoginRoute = "/noLoginRoute"; +const noLoginHomeRoute = "/noLoginHomeRoute"; +const noLoginScanRoute = "/noLoginScanRoute"; +const noLoginEditScanRoute = "/noLoginEditScanRoute"; + class AppRoute { static Route generateRoute(RouteSettings settings) { // splash screen @@ -25,6 +34,16 @@ class AppRoute { }); } + if (settings.name == noLoginSplashRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)), + child: NoLoginSplashScreen(), + ); + }); + } + // login screen if (settings.name == loginRoute) { return MaterialPageRoute(builder: (context) { @@ -36,6 +55,16 @@ class AppRoute { }); } + if (settings.name == noLoginRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)), + child: NoLoginScreen(), + ); + }); + } + // home screen if (settings.name == homeRoute) { return MaterialPageRoute(builder: (context) { @@ -47,6 +76,16 @@ class AppRoute { }); } + if (settings.name == noLoginHomeRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)), + child: NoLoginHomeScreen(), + ); + }); + } + // scan screen if (settings.name == scanRoute) { return MaterialPageRoute(builder: (context) { @@ -74,7 +113,7 @@ class AppRoute { data: MediaQuery.of(context).copyWith( padding: const EdgeInsets.all(0), textScaler: TextScaler.linear(1.0)), - child: SplashScreen(), + child: NoLoginSplashScreen(), ); }); } diff --git a/lib/main.dart b/lib/main.dart index 982d41f..ee618d0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,7 +31,7 @@ class MyApp extends StatelessWidget { }, ), debugShowCheckedModeBanner: false, - initialRoute: splashRoute, + initialRoute: noLoginSplashRoute, onGenerateRoute: AppRoute.generateRoute, ); } diff --git a/lib/model/no-login/no_login_auth_model.dart b/lib/model/no-login/no_login_auth_model.dart new file mode 100644 index 0000000..015f215 --- /dev/null +++ b/lib/model/no-login/no_login_auth_model.dart @@ -0,0 +1,90 @@ +class NoLoginAuthModel { + final String token; + final String host; + final String client_id; + final String expire_date; + final bool isLogin; + + NoLoginAuthModel({ + required this.host, + required this.token, + required this.client_id, + required this.expire_date, + required this.isLogin, + }); + + Map toJson() { + return { + 'host':host, + 'token': token, + 'client_id' :client_id, + 'expire_date':expire_date, + 'isLogin':isLogin, + }; + } +} + +// old +class UserModel { + final String userId; + final String username; + final String groupDashboard; + final String defaultSampleStationId; + final String staffName; + final String isCourier; + final String timeAutoLogout; + final String ip; + final String agent; + final String version; + final String lastLogin; + final int satelliteId; + + UserModel({ + required this.userId, + required this.username, + required this.groupDashboard, + required this.defaultSampleStationId, + required this.staffName, + required this.isCourier, + required this.timeAutoLogout, + required this.ip, + required this.agent, + required this.version, + required this.lastLogin, + required this.satelliteId, + }); + + factory UserModel.fromJson(Map json) { + return UserModel( + userId: json['M_UserID'] ?? '', + username: json['M_UserUsername'] ?? '', + groupDashboard: json['M_UserGroupDashboard'] ?? '', + defaultSampleStationId: json['M_UserDefaultT_SampleStationID'] ?? '', + staffName: json['M_StaffName'] ?? '', + isCourier: json['is_courier'] ?? '', + timeAutoLogout: json['time_autologout'] ?? '', + ip: json['ip'] ?? '', + agent: json['agent'] ?? '', + version: json['version'] ?? '', + lastLogin: json['last-login'] ?? '', + satelliteId: json['M_SatelliteID'] ?? 0, + ); + } + + Map toJson() { + return { + 'M_UserID': userId, + 'M_UserUsername': username, + 'M_UserGroupDashboard': groupDashboard, + 'M_UserDefaultT_SampleStationID': defaultSampleStationId, + 'M_StaffName': staffName, + 'is_courier': isCourier, + 'time_autologout': timeAutoLogout, + 'ip': ip, + 'agent': agent, + 'version': version, + 'last-login': lastLogin, + 'M_SatelliteID': satelliteId, + }; + } +} diff --git a/lib/provider/no-login/no_login_current_user_provider.dart b/lib/provider/no-login/no_login_current_user_provider.dart new file mode 100644 index 0000000..411d0dd --- /dev/null +++ b/lib/provider/no-login/no_login_current_user_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../model/no-login/no_login_auth_model.dart'; + +final noLoginCurrentUserProvider = StateProvider((ref) => null); \ No newline at end of file diff --git a/lib/repository/no-login-scan/no_login_scan_repository.dart b/lib/repository/no-login-scan/no_login_scan_repository.dart new file mode 100644 index 0000000..715b7c3 --- /dev/null +++ b/lib/repository/no-login-scan/no_login_scan_repository.dart @@ -0,0 +1,103 @@ +import '../../model/person_ktp_model.dart'; +import '../../model/sex_model.dart'; + +import '../../model/sukses_person_model.dart'; +import '../base_repository.dart'; + +class NoLoginScanRepository extends BaseRepository { + NoLoginScanRepository({required super.dio}); + + Future> listRiwayatScanRepo({ + required String host, + required String userId, + required String token, + }) async { + // final service = "${Constant.baseUrl}xauth/login"; + final service = + "http://${host}/one-api/scan-ktpv2-no-login/Scanktpv2/listRiwayatScan"; + final resp = await post( + param: {"userId": userId}, + service: service, + token: token, + ); + + final result = List.empty(growable: true); + resp['data'].forEach((e) { + final model = PersonKtp.fromJson(e); + result.add(model); + }); + + return result; + } + + Future> prosesScan({ + required String host, + required String base64File, + required String userId, + }) async { + final service = + "http://${host}/one-api/scan-ktpv2-no-login/Scanktp/proses_scan"; + final resp = await post(param: { + "base64File": base64File, + "userId": userId, + }, service: service); + + final result = List.empty(growable: true); + resp['data'].forEach((e) { + final model = SuksesPersonModel.fromJson(e); + result.add(model); + }); + + return result; + } + + // sex + Future> sexRepo({ + required String host, + }) async { + // final service = "${Constant.baseUrl}xauth/login"; + final service = "http://${host}/one-api/scan-ktpv2-no-login/Scanktp/getSex"; + final resp = await post(param: {}, service: service); + + final result = List.empty(growable: true); + resp['data'].forEach((e) { + final model = SexModel.fromJson(e); + result.add(model); + }); + + return result; + } + + // edit + Future prosesEdit({ + required String host, + required String userId, + required String Person_ID, + required String Person_NIK, + required String Person_Name, + required String Person_Dob, + required String Person_Sex, + }) async { + final service = + "http://${host}/one-api/scan-ktpv2-no-login/Scanktp/proses_edit"; + final resp = await post( + param: { + "Person_ID": Person_ID, + "Person_NIK": Person_NIK, + "Person_Name": Person_Name, + "Person_Dob": Person_Dob, + "Person_Sex": Person_Sex, + "userId": userId, + }, + service: service, + ); + + if (resp['status'] == "OK") { + return "Sukses Update Data"; + } else { + resp['message']; + } + + return resp['message']; + } +} diff --git a/lib/repository/no-login/no_login_auth_repository.dart b/lib/repository/no-login/no_login_auth_repository.dart new file mode 100644 index 0000000..a33a889 --- /dev/null +++ b/lib/repository/no-login/no_login_auth_repository.dart @@ -0,0 +1,85 @@ +import '../../model/no-login/no_login_auth_model.dart'; + +import '../base_repository.dart'; + +class NoLoginAuthRepository extends BaseRepository { + NoLoginAuthRepository({required super.dio}); + + // loginF + Future loginF({ + required String client_id, + required String host, + required String password, + }) async { + final param = {"client_id": client_id, "password": password, "host":host}; + final service = "http://$host/one-api/scan-ktpv2-no-login/Scanktpv2/loginF"; + final resp = await post(param: param, service: service); + + // if (resp['status'] != "OK") { + // return resp['message']; + // } + + // return resp['status']; + + final result = NoLoginAuthModel( + host: resp['data']['host'], + token: resp["data"]["token"], + client_id: resp['data']['client_id'], + expire_date: resp['data']['expire_date'], + isLogin: resp['data']['isLogin'], + ); + + return result; + } + + // createToken + Future createToken({ + required String client_id, + required String host, + }) async { + final param = { + "client_id": client_id, + "host": host, + }; + // final service = "${Constant.baseUrl}xauth/login"; + final service = + "http://$host/one-api/scan-ktpv2-no-login/Scanktpv2/createToken"; + final resp = await post(param: param, service: service); + + final result = NoLoginAuthModel( + host: resp['data']['host'], + token: resp["data"]["token"], + client_id: resp['data']['client_id'], + expire_date: resp['data']['expire_date'], + isLogin: resp['data']['isLogin'], + ); + + return result; + } + + // refreshToken + Future refreshToken( + {required String client_id, + required String host, + required String expire_date}) async { + final param = { + "client_id": client_id, + "host": host, + "expire_date": expire_date, + }; + // final service = "${Constant.baseUrl}xauth/login"; + final service = + "http://$host/one-api/scan-ktpv2-no-login/Scanktpv2/refreshToken"; + final resp = await post(param: param, service: service); + + final result = NoLoginAuthModel( + host: resp['data']['host'], + token: resp["data"]["token"], + client_id: resp['data']['client_id'], + expire_date: resp['data']['expire_date'], + isLogin: resp['data']['isLogin'], + ); + + return result; + } +} diff --git a/lib/screen/no-login-home/no_login_card_riwayat_scan.dart b/lib/screen/no-login-home/no_login_card_riwayat_scan.dart new file mode 100644 index 0000000..1216800 --- /dev/null +++ b/lib/screen/no-login-home/no_login_card_riwayat_scan.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:scanktpflutter/model/sex_model.dart'; +import '../../app/app_extension.dart'; +import '../../app/route.dart'; + +import '../../app/constant.dart'; +import '../../model/edit_person_model.dart'; +import '../../model/person_ktp_model.dart'; +import '../../provider/scan_provider.dart'; + +class NoLoginCardRiwayatScan extends HookConsumerWidget { + final PersonKtp data; + const NoLoginCardRiwayatScan({ + super.key, + required this.data, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + return GestureDetector( + onTap: () { + FocusManager.instance.primaryFocus!.unfocus(); + }, + child: Card( + elevation: 2, + color: Constant.textWhite, + child: Padding( + padding: EdgeInsets.all( + Constant.getActualYPhone(context: context, y: 12), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + data.personNIK, + style: Constant.titleInputan600(context: context).copyWith( + color: Constant.textBlack, + ), + ), + InkWell( + onTap: () { + // print('id : ${data.personID}'); + ref.read(selectedPersonIdx.notifier).state = + data.personID; + + // set SEX + ref.read(eSexSelected.notifier).state = SexModel( + M_SexID: data.personSex, + M_SexCode: "", + m_sexname: data.m_sexname, + M_SexNameLang: "" + ); + + ref.read(selectedEdit.notifier).state = EditPersonModel( + personID: data.personID, + personNIK: data.personNIK, + personName: data.personName, + m_sexname: data.m_sexname, + personDob: data.personDob, + personSex: data.personSex, + personUrl: data.personUrl, + ); + Navigator.of(context).pushNamed( + editScanRoute, + ); + }, + child: Icon( + Icons.edit, + color: Constant.bgButton, + size: 20, + ), + ), + ], + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 12, + ), + ), + // row nama + Row( + children: [ + Icon( + Icons.person_2_outlined, + color: Constant.bgIcon, + size: 20, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, + x: 8, + ), + ), + Text( + data.personName, + style: Constant.cardText(context: context).copyWith( + color: Constant.textCardGrey, + ), + ), + ], + ), + // row nama + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 10, + ), + ), + // row dob + Row( + children: [ + Icon( + Icons.date_range_outlined, + color: Constant.bgIcon, + size: 20, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, + x: 8, + ), + ), + Text( + formatDateJiffy(data.personDob), + style: Constant.cardText(context: context).copyWith( + color: Constant.textCardGrey, + ), + ), + ], + ), + // row dob + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 10, + ), + ), + // row jenis kelamin + Row( + children: [ + Image.asset( + 'images/icongender.png', + width: Constant.getActualXPhone( + context: context, + x: 20, + ), + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + fit: BoxFit.cover, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, + x: 8, + ), + ), + Text( + data.m_sexname, + style: Constant.cardText(context: context).copyWith( + color: Constant.textCardGrey, + ), + ), + ], + ), + // row jenis kelamin + ], + ), + ), + ), + ); + } +} diff --git a/lib/screen/no-login-home/no_login_home_screen.dart b/lib/screen/no-login-home/no_login_home_screen.dart new file mode 100644 index 0000000..5277a3c --- /dev/null +++ b/lib/screen/no-login-home/no_login_home_screen.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../app/app_extension.dart'; +import '../../screen/no-login-home/no_login_riwayat_scan_provider.dart'; +// import 'package:shared_preferences/shared_preferences.dart'; +import '../../provider/no-login/no_login_current_user_provider.dart'; +import '../../provider/scan_provider.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; +import '../../widget/customsnackbarwidget.dart'; +import '../no-login/no_login_refresh_token_provider.dart'; +import 'no_login_card_riwayat_scan.dart'; + +class NoLoginHomeScreen extends HookConsumerWidget { + const NoLoginHomeScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + final currentUser = ref.watch(noLoginCurrentUserProvider); + // final username = currentUser?.model.username ?? "-"; + final host = currentUser?.host ?? ""; + final isLoading = useState(false); + final listScanArr = ref.watch(listScanRwt); + final userId = currentUser?.client_id ?? ""; + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final userID = currentUser?.client_id ?? "0"; + // final prefs = await SharedPreferences.getInstance(); + final token = currentUser?.token ?? ""; + + if (userID == "0") { + // not logged in + Navigator.of(context) + .pushNamedAndRemoveUntil(noLoginRoute, (route) => false); + return; + } + + if (token == "") { + Navigator.of(context) + .pushNamedAndRemoveUntil(noLoginRoute, (route) => false); + return; + } + }); + return () {}; + }, [currentUser]); + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timestap) async { + ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan( + host: host, + userId: userId, + token: currentUser?.token ?? "", + expire_date: currentUser?.expire_date ?? "", + ); + }); + return () {}; + }, []); + + // listRiwayProvider + ref.listen(noLoginRiwayatScanProvider, (prev, next) async { + if (next is NoLoginRiwayatScanStateLoading) { + isLoading.value = true; + } else if (next is NoLoginRiwayatScanStateError) { + isLoading.value = false; + // errorMessage.value = next.message; + snackbarWidget( + context, + next.message, + snackbarType.error, + Duration(seconds: 3), + ); + } else if (next is NoLoginRiwayatScanStateDone) { + isLoading.value = false; + } + }); + + void getRiwayat() async { + final currentUser = ref.read(noLoginCurrentUserProvider); + final host = currentUser?.host ?? ""; + final userId = currentUser?.client_id ?? ""; + final expireDate = currentUser?.expire_date ?? ""; + + if (await isTokenExpired()) { + print("Token expired, refreshing..."); + ref.read(noLoginRefreshTokenProvider.notifier).refreshToken( + client_id: userId, + host: host, + expire_date: expireDate, + ); + } + + // Ambil data riwayat scan setelah token diperbarui + ref.read(noLoginRiwayatScanProvider.notifier).noLoginRiwayatScan( + host: host, + userId: userId, + token: ref.read(noLoginCurrentUserProvider)?.token ?? "", + expire_date: + ref.read(noLoginCurrentUserProvider)?.expire_date ?? "", + ); + } + + return GestureDetector( + onTap: () { + FocusManager.instance.primaryFocus!.unfocus(); + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: Constant.bgGrey, + body: Column( + children: [ + // atas + Image.asset( + 'images/vektoratas.png', + width: double.infinity, + fit: BoxFit.cover, + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 34, + ), + ), + // button scan ktp + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: SizedBox( + width: double.infinity, + height: Constant.getActualYPhone( + context: context, + y: 48, + ), + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pushNamed(scanRoute); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Constant.bgButton, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 8, + shadowColor: Constant.bgButton.withOpacity(0.24), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'images/iconbarcode.png', + width: Constant.getActualXPhone( + context: context, + x: 20, + ), + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + fit: BoxFit.cover, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, + x: 10, + ), + ), + Text( + 'SCAN KTP BARU', + style: + Constant.titleButton500(context: context).copyWith( + color: Constant.textWhite, + ), + ), + ], + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 56, + ), + ), + // judul + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Column( + children: [ + // DEBUG + Text(currentUser?.token ?? ""), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 24, + ), + ), + Text(currentUser?.expire_date ?? ""), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 24, + ), + ), + // DEBUG + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Riwayat', + style: Constant.titleRiwayat(context: context).copyWith( + color: Constant.textBlack, + ), + ), + Text( + '20 Scan Terakhir', + style: + Constant.titleInputan500(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ], + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 24, + ), + ), + ], + ), + ), + // loading + if (isLoading.value) + Expanded( + child: Center( + child: CircularProgressIndicator( + color: Constant.bgButton, + ), + ), + ) + // belum ada riwayat + else if (listScanArr.isEmpty) + Expanded( + child: RefreshIndicator( + color: Constant.bgButton, + onRefresh: () async { + getRiwayat(); + }, + child: ListView( + physics: + AlwaysScrollableScrollPhysics(), // Agar bisa di-refresh meski kosong + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * + 0.3), // Atur tinggi agar teks di tengah + Align( + alignment: Alignment.center, + child: Text( + 'Belum Ada Riwayat', + style: Constant.titleInputan500(context: context), + ), + ), + ], + ), + ), + ) + // list card + else + Expanded( + child: RefreshIndicator( + color: Constant.bgButton, + onRefresh: () async { + getRiwayat(); + }, + child: Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 13), + right: Constant.getActualXPhone(context: context, x: 13), + bottom: Constant.getActualYPhone(context: context, y: 20), + ), + child: ListView.builder( + physics: AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: listScanArr.length, + itemBuilder: (context, i) { + final obj = listScanArr[i]; + return Padding( + padding: EdgeInsets.only( + top: Constant.getActualYPhone( + context: context, y: 15), + ), + child: NoLoginCardRiwayatScan( + data: obj, + ), + ); + }, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screen/no-login-home/no_login_riwayat_scan_provider.dart b/lib/screen/no-login-home/no_login_riwayat_scan_provider.dart new file mode 100644 index 0000000..a8ff67b --- /dev/null +++ b/lib/screen/no-login-home/no_login_riwayat_scan_provider.dart @@ -0,0 +1,90 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:scanktpflutter/screen/no-login/no_login_refresh_token_provider.dart'; +import '../../app/app_extension.dart'; +import '../../repository/no-login-scan/no_login_scan_repository.dart'; +import '../../provider/scan_provider.dart'; + +import '../../model/person_ktp_model.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; + +// 3. state provider +final noLoginRiwayatScanProvider = + StateNotifierProvider( + (ref) => NoLoginRiwayatScanNotifier(ref: ref)); + +// 2. notifier +class NoLoginRiwayatScanNotifier + extends StateNotifier { + final Ref ref; + NoLoginRiwayatScanNotifier({required this.ref}) + : super(NoLoginRiwayatScanStateInit()); + void noLoginRiwayatScan({ + required String host, + required String userId, + required String token, + required String expire_date, + }) async { + try { + state = NoLoginRiwayatScanStateLoading(); + final resp = await NoLoginScanRepository( + dio: ref.read(dioProvider), + ).listRiwayatScanRepo( + host: host, + userId: userId, + token: token, + ); + + // print(resp); + state = NoLoginRiwayatScanStateDone(model: resp); + ref.read(listScanRwt.notifier).state = resp; + + // // fungsi check token expired + // if (await isTokenExpired()) { + // print("Token expired, refreshing..."); + // ref.read(noLoginRefreshTokenProvider.notifier).refreshToken( + // client_id: userId, + // host: host, + // expire_date:expire_date, + // ); + // } + } catch (e) { + if (e is BaseRepositoryException) { + state = NoLoginRiwayatScanStateError(message: e.message); + } else { + state = NoLoginRiwayatScanStateError(message: e.toString()); + } + } + } +} + +// 1. state +abstract class NoLoginRiwayatScanState extends Equatable { + final DateTime date; + const NoLoginRiwayatScanState(this.date); + @override + List get props => [date]; +} + +class NoLoginRiwayatScanStateInit extends NoLoginRiwayatScanState { + NoLoginRiwayatScanStateInit() : super(DateTime.now()); +} + +class NoLoginRiwayatScanStateLoading extends NoLoginRiwayatScanState { + NoLoginRiwayatScanStateLoading() : super(DateTime.now()); +} + +class NoLoginRiwayatScanStateError extends NoLoginRiwayatScanState { + final String message; + NoLoginRiwayatScanStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class NoLoginRiwayatScanStateDone extends NoLoginRiwayatScanState { + final List model; + NoLoginRiwayatScanStateDone({ + required this.model, + }) : super(DateTime.now()); +} diff --git a/lib/screen/no-login-splash/no_login_splash_screen.dart b/lib/screen/no-login-splash/no_login_splash_screen.dart new file mode 100644 index 0000000..fa90a69 --- /dev/null +++ b/lib/screen/no-login-splash/no_login_splash_screen.dart @@ -0,0 +1,101 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../model/no-login/no_login_auth_model.dart'; +import '../../provider/no-login/no_login_current_user_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../app/constant.dart'; +import '../../app/route.dart'; + +class NoLoginSplashScreen extends HookConsumerWidget { + const NoLoginSplashScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final shared = await SharedPreferences.getInstance(); + final bearerString = shared.getString(Constant.bearerName); + + if (bearerString == null || bearerString == "null") { + Timer(const Duration(seconds: 3), () { + Navigator.of(context).pushNamedAndRemoveUntil( + noLoginRoute, + (route) => false, + ); + }); + return; + } + + final xmodel = jsonDecode(bearerString); + if (xmodel == null) return; + + final authModel = NoLoginAuthModel( + host: xmodel["host"], + token: xmodel["token"], + client_id: xmodel['client_id'], + expire_date: xmodel['expire_date'], + isLogin: xmodel['isLogin'], + ); + + ref.read(noLoginCurrentUserProvider.notifier).state = authModel; + + Timer(const Duration(seconds: 2), () { + Navigator.of(context).pushNamedAndRemoveUntil( + noLoginHomeRoute, + (route) => false, + ); + }); + }); + return () {}; + }, []); + + return Scaffold( + backgroundColor: Constant.textWhite, + body: Column( + children: [ + // Bagian atas + Align( + alignment: Alignment.topRight, + child: SizedBox( + width: Constant.getActualXPhone(context: context, x: 246), + child: Image.asset( + 'images/splashatas.png', + fit: BoxFit.fitWidth, + ), + ), + ), + Spacer(), + // Logo di tengah + Center( + child: Image.asset( + 'images/logo.png', + width: Constant.getActualXPhone(context: context, x: 164), + ), + ), + Spacer(), + // Bagian bawah + Align( + alignment: Alignment.bottomLeft, + child: SizedBox( + width: Constant.getActualXPhone(context: context, x: 246), + child: Image.asset( + 'images/splashbawah.png', + fit: BoxFit.fitWidth, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screen/no-login/no_login_provider.dart b/lib/screen/no-login/no_login_provider.dart new file mode 100644 index 0000000..6bb9cb3 --- /dev/null +++ b/lib/screen/no-login/no_login_provider.dart @@ -0,0 +1,202 @@ +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:scanktpflutter/model/no-login/no_login_auth_model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../../app/constant.dart'; +import '../../provider/no-login/no_login_current_user_provider.dart'; +import '../../repository/no-login/no_login_auth_repository.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; + +// // 3. state provider +// final noLoginProvider = StateNotifierProvider( +// (ref) => NoLoginNotifier(ref: ref)); + +// // 2. notifier +// class NoLoginNotifier extends StateNotifier { +// final Ref ref; +// NoLoginNotifier({required this.ref}) : super(NoLoginStateInit()); +// void prosesLoginF({ +// required String client_id, +// required String host, +// required String password, +// }) async { +// try { +// state = NoLoginStateLoading(); +// final resp = await NoLoginAuthRepository( +// dio: ref.read(dioProvider), +// ).loginF( +// client_id: client_id, +// host: host, +// password: password, +// ); + +// // print(resp); +// state = NoLoginStateDone(model: resp); + +// final shared = await SharedPreferences.getInstance(); +// String expireDateStr = resp.expire_date; +// if (!expireDateStr.contains("T")) { +// expireDateStr = expireDateStr.replaceFirst(" ", "T"); +// } + +// final token = jsonEncode({ +// "host": host, +// "date": DateTime.now().toString(), +// "token": resp.token, +// "client_id": resp.client_id, +// // "expire_date": resp.expire_date, +// "expire_date": expireDateStr, +// "isLogin": resp.isLogin, +// }); +// await shared.setString(Constant.bearerName, token); +// ref.read(noLoginCurrentUserProvider.notifier).state = resp; +// } catch (e) { +// if (e is BaseRepositoryException) { +// state = NoLoginStateError(message: e.message); +// } else { +// state = NoLoginStateError(message: e.toString()); +// } +// } +// } +// } + +// // 1. state +// abstract class NoLoginState extends Equatable { +// final DateTime date; +// const NoLoginState(this.date); +// @override +// List get props => [date]; +// } + +// class NoLoginStateInit extends NoLoginState { +// NoLoginStateInit() : super(DateTime.now()); +// } + +// class NoLoginStateLoading extends NoLoginState { +// NoLoginStateLoading() : super(DateTime.now()); +// } + +// class NoLoginStateError extends NoLoginState { +// final String message; +// NoLoginStateError({ +// required this.message, +// }) : super(DateTime.now()); +// } + +// class NoLoginStateDone extends NoLoginState { +// // final String pesan; +// final NoLoginAuthModel model; +// NoLoginStateDone({ +// required this.model, +// }) : super(DateTime.now()); +// } + +// new +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:scanktpflutter/model/no-login/no_login_auth_model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../../app/constant.dart'; +import '../../provider/no-login/no_login_current_user_provider.dart'; +import '../../repository/no-login/no_login_auth_repository.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; + +// 3. state provider +final noLoginProvider = StateNotifierProvider( + (ref) => NoLoginNotifier(ref: ref)); + +// 2. notifier +class NoLoginNotifier extends StateNotifier { + final Ref ref; + NoLoginNotifier({required this.ref}) : super(NoLoginStateInit()); + + void prosesLoginF({ + required String client_id, + required String host, + required String password, + }) async { + try { + state = NoLoginStateLoading(); + final resp = await NoLoginAuthRepository( + dio: ref.read(dioProvider), + ).loginF( + client_id: client_id, + host: host, + password: password, + ); + + state = NoLoginStateDone(model: resp); + + // Simpan ke SharedPreferences + final shared = await SharedPreferences.getInstance(); + String expireDateStr = resp.expire_date; + + // Pastikan format ISO 8601 dengan "T" + if (!expireDateStr.contains("T")) { + expireDateStr = expireDateStr.replaceFirst(" ", "T"); + } + + // Cek apakah expire_date valid + try { + DateTime.parse(expireDateStr); + } catch (e) { + print( + "❌ Format expire_date tidak valid: $expireDateStr, menggunakan waktu default."); + expireDateStr = DateTime.now().toIso8601String(); + } + + // Simpan token dalam format JSON + final token = jsonEncode({ + "host": host, + "date": DateTime.now().toIso8601String(), + "token": resp.token, + "client_id": resp.client_id, + "expire_date": expireDateStr, + "isLogin": resp.isLogin, + }); + + await shared.setString(Constant.bearerName, token); + ref.read(noLoginCurrentUserProvider.notifier).state = resp; + + print("✅ Token berhasil disimpan ke SharedPreferences!"); + } catch (e) { + if (e is BaseRepositoryException) { + state = NoLoginStateError(message: e.message); + } else { + state = NoLoginStateError(message: e.toString()); + } + } + } +} + +// 1. state +abstract class NoLoginState extends Equatable { + final DateTime date; + const NoLoginState(this.date); + @override + List get props => [date]; +} + +class NoLoginStateInit extends NoLoginState { + NoLoginStateInit() : super(DateTime.now()); +} + +class NoLoginStateLoading extends NoLoginState { + NoLoginStateLoading() : super(DateTime.now()); +} + +class NoLoginStateError extends NoLoginState { + final String message; + NoLoginStateError({required this.message}) : super(DateTime.now()); +} + +class NoLoginStateDone extends NoLoginState { + final NoLoginAuthModel model; + NoLoginStateDone({required this.model}) : super(DateTime.now()); +} diff --git a/lib/screen/no-login/no_login_refresh_token_provider.dart b/lib/screen/no-login/no_login_refresh_token_provider.dart new file mode 100644 index 0000000..4d402e4 --- /dev/null +++ b/lib/screen/no-login/no_login_refresh_token_provider.dart @@ -0,0 +1,228 @@ +// import 'dart:convert'; + +// import 'package:equatable/equatable.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import '../../provider/no-login/no_login_current_user_provider.dart'; +// import '../../model/no-login/no_login_auth_model.dart'; +// import '../../repository/no-login/no_login_auth_repository.dart'; +// import 'package:shared_preferences/shared_preferences.dart'; + +// import '../../app/constant.dart'; +// import '../../provider/dio_provider.dart'; +// import '../../repository/base_repository.dart'; + +// // 3. state provider +// final noLoginRefreshTokenProvider = StateNotifierProvider< +// NoLoginRefreshTokenNotifier, +// NoLoginRefreshTokenState>((ref) => NoLoginRefreshTokenNotifier(ref: ref)); + +// // 2. notifier +// class NoLoginRefreshTokenNotifier +// extends StateNotifier { +// final Ref ref; +// NoLoginRefreshTokenNotifier({required this.ref}) +// : super(NoLoginRefreshTokenStateInit()); +// void refreshToken({ +// required String client_id, +// required String host, +// required String expire_date, +// }) async { +// try { +// state = NoLoginRefreshTokenStateLoading(); +// final resp = await NoLoginAuthRepository( +// dio: ref.read(dioProvider), +// ).refreshToken( +// client_id: client_id, +// host: host, +// expire_date: expire_date, +// ); + +// // print(resp); +// state = NoLoginRefreshTokenStateDone(model: resp); +// //Simpan ke token +// final shared = await SharedPreferences.getInstance(); +// String expireDateStr = resp.expire_date; +// if (!expireDateStr.contains("T")) { +// expireDateStr = expireDateStr.replaceFirst(" ", "T"); +// } +// // Simpan dalam format ISO 8601 +// // await shared.setString("expire_date", expireDateStr); + +// final token = jsonEncode({ +// "host": host, +// "date": DateTime.now().toString(), +// "token": resp.token, +// "client_id": resp.client_id, +// // "expire_date": resp.expire_date, +// "expire_date": expireDateStr, +// "isLogin": resp.isLogin, +// }); +// await shared.setString(Constant.bearerName, token); +// ref.read(noLoginCurrentUserProvider.notifier).state = resp; + +// // print(shared.getString(Constant.bearerName)); +// } catch (e) { +// if (e is BaseRepositoryException) { +// state = NoLoginRefreshTokenStateError(message: e.message); +// } else { +// state = NoLoginRefreshTokenStateError(message: e.toString()); +// } +// } +// } +// } + +// // 1. state +// abstract class NoLoginRefreshTokenState extends Equatable { +// final DateTime date; +// const NoLoginRefreshTokenState(this.date); +// @override +// List get props => [date]; +// } + +// class NoLoginRefreshTokenStateInit extends NoLoginRefreshTokenState { +// NoLoginRefreshTokenStateInit() : super(DateTime.now()); +// } + +// class NoLoginRefreshTokenStateLoading extends NoLoginRefreshTokenState { +// NoLoginRefreshTokenStateLoading() : super(DateTime.now()); +// } + +// class NoLoginRefreshTokenStateError extends NoLoginRefreshTokenState { +// final String message; +// NoLoginRefreshTokenStateError({ +// required this.message, +// }) : super(DateTime.now()); +// } + +// class NoLoginRefreshTokenStateDone extends NoLoginRefreshTokenState { +// final NoLoginAuthModel model; +// NoLoginRefreshTokenStateDone({ +// required this.model, +// }) : super(DateTime.now()); +// } + +// new +import 'dart:convert'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../provider/no-login/no_login_current_user_provider.dart'; +import '../../model/no-login/no_login_auth_model.dart'; +import '../../repository/no-login/no_login_auth_repository.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../app/constant.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; + +// 3. State Provider +final noLoginRefreshTokenProvider = StateNotifierProvider< + NoLoginRefreshTokenNotifier, + NoLoginRefreshTokenState>((ref) => NoLoginRefreshTokenNotifier(ref: ref)); + +// 2. Notifier +class NoLoginRefreshTokenNotifier + extends StateNotifier { + final Ref ref; + NoLoginRefreshTokenNotifier({required this.ref}) + : super(NoLoginRefreshTokenStateInit()); + + void refreshToken({ + required String client_id, + required String host, + required String expire_date, + }) async { + try { + state = NoLoginRefreshTokenStateLoading(); + final resp = await NoLoginAuthRepository( + dio: ref.read(dioProvider), + ).refreshToken( + client_id: client_id, + host: host, + expire_date: expire_date, + ); + + print("📌 Token diperbarui: ${resp.token}"); + print("📌 Expire date dari API: ${resp.expire_date}"); + + // Cek apakah expire_date dari API null atau kosong + if (resp.expire_date == null || resp.expire_date.isEmpty) { + print("❌ Error: expire_date dari API kosong!"); + return; + } + + // Konversi format expire_date ke ISO 8601 + String expireDateStr = resp.expire_date.replaceFirst(" ", "T"); + + // Validasi apakah expire_date dapat dikonversi ke DateTime + try { + DateTime parsedExpireDate = DateTime.parse(expireDateStr); + print("✅ expire_date berhasil dikonversi: $parsedExpireDate"); + } catch (e) { + print("❌ Error parsing expire_date: $e"); + return; + } + + // Simpan token ke SharedPreferences + final shared = await SharedPreferences.getInstance(); + + // await shared.setString("expire_date", expireDateStr); + print("✅ expire_date berhasil disimpan: $expireDateStr"); + + final tokenData = jsonEncode({ + "host": host, + "date": DateTime.now().toString(), + "token": resp.token, + "client_id": resp.client_id, + "expire_date": expireDateStr, + "isLogin": resp.isLogin, + }); + + await shared.setString(Constant.bearerName, tokenData); + print("✅ Token data berhasil diperbarui: $tokenData"); + + // Cek ulang apakah expire_date berhasil disimpan dengan benar + String? cekExpireDate = shared.getString("expire_date"); + print( + "📌 expire_date yang tersimpan di SharedPreferences: $cekExpireDate"); + + ref.read(noLoginCurrentUserProvider.notifier).state = resp; + state = NoLoginRefreshTokenStateDone(model: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = NoLoginRefreshTokenStateError(message: e.message); + } else { + state = NoLoginRefreshTokenStateError(message: e.toString()); + } + } + } +} + +// 1. State +abstract class NoLoginRefreshTokenState extends Equatable { + final DateTime date; + const NoLoginRefreshTokenState(this.date); + @override + List get props => [date]; +} + +class NoLoginRefreshTokenStateInit extends NoLoginRefreshTokenState { + NoLoginRefreshTokenStateInit() : super(DateTime.now()); +} + +class NoLoginRefreshTokenStateLoading extends NoLoginRefreshTokenState { + NoLoginRefreshTokenStateLoading() : super(DateTime.now()); +} + +class NoLoginRefreshTokenStateError extends NoLoginRefreshTokenState { + final String message; + NoLoginRefreshTokenStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class NoLoginRefreshTokenStateDone extends NoLoginRefreshTokenState { + final NoLoginAuthModel model; + NoLoginRefreshTokenStateDone({ + required this.model, + }) : super(DateTime.now()); +} diff --git a/lib/screen/no-login/no_login_screen.dart b/lib/screen/no-login/no_login_screen.dart new file mode 100644 index 0000000..08b65e6 --- /dev/null +++ b/lib/screen/no-login/no_login_screen.dart @@ -0,0 +1,384 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../app/route.dart'; + +import '../../app/constant.dart'; +import '../../widget/customsnackbarwidget.dart'; +import 'no_login_provider.dart'; + +class NoLoginScreen extends HookConsumerWidget { + const NoLoginScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ + SystemUiOverlay.bottom, + ]); + + final clientIdCtr = useTextEditingController( + text: "sasdev", + ); + + final passwordCtr = useTextEditingController( + text: "sasdev123", + ); + + final hostCtr = useTextEditingController( + text: "devone.aplikasi.web.id", + ); + + final isLoading = useState(false); + final isSuccess = useState(false); + + // loginF + ref.listen(noLoginProvider, (prev, next) { + if (next is NoLoginStateLoading) { + isLoading.value = true; + } else if (next is NoLoginStateError) { + isLoading.value = false; + // errorMessage.value = next.message; + snackbarWidget( + context, + next.message, + snackbarType.error, + const Duration(seconds: 3), + ); + } else if (next is NoLoginStateDone) { + isLoading.value = false; + isSuccess.value = true; + Navigator.of(context) + .pushNamedAndRemoveUntil(noLoginHomeRoute, (route) => false); + } + }); + + void login() { + if (clientIdCtr.text.isEmpty || + passwordCtr.text.isEmpty || + hostCtr.text.isEmpty) { + snackbarWidget( + context, + 'Inputan wajib diisi', + snackbarType.error, + const Duration(seconds: 3), + ); + } else { + // print('proses login'); + ref.read(noLoginProvider.notifier).prosesLoginF( + client_id: clientIdCtr.text, + host: hostCtr.text, + password: passwordCtr.text, + ); + } + } + + return GestureDetector( + onTap: () { + FocusManager.instance.primaryFocus!.unfocus(); + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + backgroundColor: Constant.textWhite, + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // atas + Image.asset( + 'images/vektoratas.png', + width: double.infinity, + fit: BoxFit.cover, + ), + + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 40, + ), + ), + // konten didalamnya + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Selamat Datang', + style: Constant.title_700(context: context).copyWith( + color: Constant.textBlack, + ), + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Silahkan masuk untuk mengakses akun Anda', + style: Constant.title_400(context: context).copyWith( + color: Constant.textBlack, + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 64, + ), + ), + + // inputan + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Client ID', + style: Constant.title_400(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: TextField( + controller: clientIdCtr, + decoration: const InputDecoration( + hintText: "Masukkan Client ID", + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Password', + style: Constant.title_400(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: TextField( + controller: passwordCtr, + obscureText: true, + decoration: const InputDecoration( + hintText: "Masukkan Password", + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: Text( + 'Host', + style: Constant.title_400(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: TextField( + controller: hostCtr, + decoration: const InputDecoration( + hintText: "Masukkan Host", + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 64, + ), + ), + // inputan + + // button login + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, + ), + ), + child: SizedBox( + width: double.infinity, + height: Constant.getActualYPhone( + context: context, + y: 48, + ), + child: ElevatedButton( + onPressed: () async { + (isLoading.value || (isSuccess.value == true)) + ? null + : login(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Constant.bgButton, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 8, + shadowColor: Constant.bgButton.withOpacity(0.24), + ), + child: Text( + (isLoading.value) ? 'Loading...' : 'LOGIN', + style: Constant.titleButton500(context: context).copyWith( + color: Constant.textWhite, + ), + ), + ), + ), + ), + // konten didalamnya + + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 60, + ), + ), + ], + ), + ), + ), + ); + } +}