diff --git a/lib/app/app_extension.dart b/lib/app/app_extension.dart new file mode 100644 index 0000000..5ae765a --- /dev/null +++ b/lib/app/app_extension.dart @@ -0,0 +1,5 @@ +import 'package:jiffy/jiffy.dart'; + +String formatDateJiffy(String serverDate) { + return Jiffy.parse(serverDate).format(pattern: 'dd-MM-yyyy'); +} \ No newline at end of file diff --git a/lib/model/auth_model.dart b/lib/model/auth_model.dart index 7ff5b52..d935922 100644 --- a/lib/model/auth_model.dart +++ b/lib/model/auth_model.dart @@ -1,14 +1,17 @@ class AuthModel { final String token; + final String host; final UserModel model; AuthModel({ + required this.host, required this.token, required this.model, }); Map toJson() { return { + 'host':host, 'token': token, 'model': model.toJson(), }; diff --git a/lib/model/person_ktp_model.dart b/lib/model/person_ktp_model.dart new file mode 100644 index 0000000..a336ebf --- /dev/null +++ b/lib/model/person_ktp_model.dart @@ -0,0 +1,41 @@ +class PersonKtp { + final String personID; + final String personNIK; + final String personName; + final String personDob; + final String personSex; + final String personUrl; + + PersonKtp({ + required this.personID, + required this.personNIK, + required this.personName, + required this.personDob, + required this.personSex, + required this.personUrl, + }); + + // Convert JSON to Model + factory PersonKtp.fromJson(Map json) { + return PersonKtp( + personID: json['Person_ID'], + personNIK: json['Person_NIK'], + personName: json['Person_Name'], + personDob: json['Person_Dob'], + personSex: json['Person_Sex'], + personUrl: json['Person_Url'], + ); + } + + // Convert Model to JSON + Map toJson() { + return { + 'Person_ID':personID, + 'Person_NIK': personNIK, + 'Person_Name': personName, + 'Person_Dob': personDob, + 'Person_Sex': personSex, + 'Person_Url': personUrl, + }; + } +} diff --git a/lib/repository/auth_repository.dart b/lib/repository/auth_repository.dart index 3754b74..935d84c 100644 --- a/lib/repository/auth_repository.dart +++ b/lib/repository/auth_repository.dart @@ -11,10 +11,11 @@ class AuthRepository extends BaseRepository { }) async { final param = {"username": username, "password": password}; // final service = "${Constant.baseUrl}xauth/login"; - final service = "http://${host}/one-api/v1/system/auth/login"; + final service = "http://$host/one-api/v1/system/auth/login"; final resp = await post(param: param, service: service); final result = AuthModel( + host: host, token: resp["data"]["token"], model: UserModel.fromJson(resp["data"]["user"]), ); diff --git a/lib/repository/scan_repository.dart b/lib/repository/scan_repository.dart new file mode 100644 index 0000000..1129f1e --- /dev/null +++ b/lib/repository/scan_repository.dart @@ -0,0 +1,22 @@ +import '../model/person_ktp_model.dart'; +import 'base_repository.dart'; + +class ScanRepository extends BaseRepository { + ScanRepository({required super.dio}); + + Future> listRiwayatScanRepo({ + required String host, + }) async { + // final service = "${Constant.baseUrl}xauth/login"; + final service = "http://${host}/one-api/scan-ktp/Scanktp/listRiwayatScan"; + final resp = await post(param: {}, service: service); + + final result = List.empty(growable: true); + resp['data'].forEach((e) { + final model = PersonKtp.fromJson(e); + result.add(model); + }); + + return result; + } +} diff --git a/lib/screen/home/card_riwayat_scan.dart b/lib/screen/home/card_riwayat_scan.dart index 81bba90..a36f276 100644 --- a/lib/screen/home/card_riwayat_scan.dart +++ b/lib/screen/home/card_riwayat_scan.dart @@ -1,11 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:scanktpflutter/app/app_extension.dart'; import '../../app/constant.dart'; +import '../../model/person_ktp_model.dart'; class CardRiwayatScan extends HookConsumerWidget { - const CardRiwayatScan({super.key}); + final PersonKtp data; + const CardRiwayatScan({ + super.key, + required this.data, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,15 +36,20 @@ class CardRiwayatScan extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '3171234567890123', + data.personNIK, style: Constant.titleInputan600(context: context).copyWith( color: Constant.textBlack, ), ), - Icon( - Icons.edit, - color: Constant.bgButton, - size: 20, + InkWell( + onTap: (){ + print('id : ${data.personID}'); + }, + child: Icon( + Icons.edit, + color: Constant.bgButton, + size: 20, + ), ), ], ), @@ -63,7 +74,7 @@ class CardRiwayatScan extends HookConsumerWidget { ), ), Text( - 'MIRA SETIAWAN', + data.personName, style: Constant.cardText(context: context).copyWith( color: Constant.textCardGrey, ), @@ -92,7 +103,7 @@ class CardRiwayatScan extends HookConsumerWidget { ), ), Text( - '18-02-1996', + formatDateJiffy(data.personDob), style: Constant.cardText(context: context).copyWith( color: Constant.textCardGrey, ), @@ -128,7 +139,7 @@ class CardRiwayatScan extends HookConsumerWidget { ), ), Text( - 'Perempuan', + data.personSex, style: Constant.cardText(context: context).copyWith( color: Constant.textCardGrey, ), diff --git a/lib/screen/home/home_screen.dart b/lib/screen/home/home_screen.dart index 4e87ebd..a772084 100644 --- a/lib/screen/home/home_screen.dart +++ b/lib/screen/home/home_screen.dart @@ -2,11 +2,14 @@ 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 'package:scanktpflutter/model/person_ktp_model.dart'; import 'package:scanktpflutter/screen/home/card_riwayat_scan.dart'; +import 'package:scanktpflutter/screen/home/list_riwayat_scan_provider.dart'; import '../../app/constant.dart'; import '../../app/route.dart'; import '../../provider/current_user_provider.dart'; +import '../../widget/customsnackbarwidget.dart'; class HomeScreen extends HookConsumerWidget { const HomeScreen({super.key}); @@ -18,6 +21,12 @@ class HomeScreen extends HookConsumerWidget { ]); final currentUser = ref.watch(currentUserProvider); + // final username = currentUser?.model.username ?? "-"; + final host = currentUser?.host ?? ""; + final isLoading = useState(false); + final listScanArr = useState>( + List.empty(growable: true), + ); useEffect(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { @@ -32,7 +41,33 @@ class HomeScreen extends HookConsumerWidget { return () {}; }, [currentUser]); - final username = currentUser?.model.username ?? "-"; + // listRiwayProvider + ref.listen(listRiwayatScanProvider, (prev, next) { + if (next is ListRiwayatScanStateLoading) { + isLoading.value = true; + } else if (next is ListRiwayatScanStateError) { + isLoading.value = false; + // errorMessage.value = next.message; + snackbarWidget( + context, + next.message, + snackbarType.error, + Duration(seconds: 3), + ); + } else if (next is ListRiwayatScanStateDone) { + isLoading.value = false; + listScanArr.value = next.model; + } + }); + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timestap) async { + ref.read(listRiwayatScanProvider.notifier).listRiwayatScan( + host: host, + ); + }); + return () {}; + }, []); return GestureDetector( onTap: () { @@ -41,136 +76,202 @@ class HomeScreen extends HookConsumerWidget { child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: Constant.bgGrey, - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // atas - Image.asset( - 'images/vektoratas.png', + 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, - fit: BoxFit.cover, - ), - SizedBox( height: Constant.getActualYPhone( context: context, - y: 34, + y: 48, ), - ), - // button scan ktp - Padding( - padding: EdgeInsets.only( - left: Constant.getActualXPhone( - context: context, - x: 20, + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: Constant.bgButton, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 8, + shadowColor: Constant.bgButton.withOpacity(0.24), ), - right: Constant.getActualXPhone( - context: context, - x: 20, - ), - ), - child: SizedBox( - width: double.infinity, - height: Constant.getActualYPhone( - context: context, - y: 48, - ), - child: ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - backgroundColor: Constant.bgButton, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + 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, ), - 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, ), - SizedBox( - width: Constant.getActualXPhone( - context: context, - x: 10, - ), + ), + Text( + 'SCAN KTP BARU', + style: + Constant.titleButton500(context: context).copyWith( + color: Constant.textWhite, ), - Text( - 'SCAN KTP BARU', - style: Constant.titleButton500(context: context) - .copyWith( - color: Constant.textWhite, - ), - ), - ], - ), + ), + ], ), ), ), - SizedBox( - height: Constant.getActualYPhone( + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 56, + ), + ), + // judul + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( context: context, - y: 56, + x: 20, + ), + right: Constant.getActualXPhone( + context: context, + x: 20, ), ), - Padding( - padding: EdgeInsets.only( - left: Constant.getActualXPhone( - context: context, - x: 20, - ), - right: Constant.getActualXPhone( - context: context, - x: 20, - ), - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Riwayat', - style: - Constant.titleRiwayat(context: context).copyWith( - color: Constant.textBlack, - ), + child: Column( + children: [ + 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, ), + Text( + '20 Scan Terakhir', + style: + Constant.titleInputan500(context: context).copyWith( + color: Constant.inputanGrey, + ), + ), + ], + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 24, ), - - // card riwayat - CardRiwayatScan(), - // card riwayat - ], + ), + ], + ), + ), + // loading + if (isLoading.value) + Expanded( + child: Center( + child: CircularProgressIndicator( + color: Constant.bgButton, + ), + ), + ) + // belum ada riwayat + else if (listScanArr.value.isEmpty) + Expanded( + child: RefreshIndicator( + color: Constant.bgButton, + onRefresh: () async { + ref.read(listRiwayatScanProvider.notifier).listRiwayatScan( + host: host, + ); + }, + 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 { + ref.read(listRiwayatScanProvider.notifier).listRiwayatScan( + host: host, + ); + }, + 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.value.length, + itemBuilder: (context, i) { + final obj = listScanArr.value[i]; + return Padding( + padding: EdgeInsets.only( + top: Constant.getActualYPhone( + context: context, y: 15), + ), + child: CardRiwayatScan( + data: obj, + ), + ); + }, + ), + ), ), ), - ], - ), + ], ), ), ); diff --git a/lib/screen/home/list_riwayat_scan_provider.dart b/lib/screen/home/list_riwayat_scan_provider.dart new file mode 100644 index 0000000..3eab01c --- /dev/null +++ b/lib/screen/home/list_riwayat_scan_provider.dart @@ -0,0 +1,70 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:scanktpflutter/repository/scan_repository.dart'; + +import '../../model/person_ktp_model.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; + +// 3. state provider +final listRiwayatScanProvider = + StateNotifierProvider( + (ref) => ListRiwayatScanNotifier(ref: ref)); + +// 2. notifier +class ListRiwayatScanNotifier extends StateNotifier { + final Ref ref; + ListRiwayatScanNotifier({required this.ref}) + : super(ListRiwayatScanStateInit()); + void listRiwayatScan({ + required String host, + }) async { + try { + state = ListRiwayatScanStateLoading(); + final resp = await ScanRepository( + dio: ref.read(dioProvider), + ).listRiwayatScanRepo( + host: host, + ); + + // print(resp); + state = ListRiwayatScanStateDone(model: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = ListRiwayatScanStateError(message: e.message); + } else { + state = ListRiwayatScanStateError(message: e.toString()); + } + } + } +} + +// 1. state +abstract class ListRiwayatScanState extends Equatable { + final DateTime date; + const ListRiwayatScanState(this.date); + @override + List get props => [date]; +} + +class ListRiwayatScanStateInit extends ListRiwayatScanState { + ListRiwayatScanStateInit() : super(DateTime.now()); +} + +class ListRiwayatScanStateLoading extends ListRiwayatScanState { + ListRiwayatScanStateLoading() : super(DateTime.now()); +} + +class ListRiwayatScanStateError extends ListRiwayatScanState { + final String message; + ListRiwayatScanStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class ListRiwayatScanStateDone extends ListRiwayatScanState { + final List model; + ListRiwayatScanStateDone({ + required this.model, + }) : super(DateTime.now()); +} diff --git a/lib/screen/login/login_provider.dart b/lib/screen/login/login_provider.dart index 95d0c67..9aa8638 100644 --- a/lib/screen/login/login_provider.dart +++ b/lib/screen/login/login_provider.dart @@ -39,6 +39,7 @@ class LoginNotifier extends StateNotifier { //Simpan ke token final shared = await SharedPreferences.getInstance(); final token = jsonEncode({ + "host":host, "date": DateTime.now().toString(), "model": resp.model, "token": resp.token diff --git a/lib/screen/splash/splash_screen.dart b/lib/screen/splash/splash_screen.dart index a424b4b..64bfa2b 100644 --- a/lib/screen/splash/splash_screen.dart +++ b/lib/screen/splash/splash_screen.dart @@ -40,6 +40,7 @@ class SplashScreen extends HookConsumerWidget { if (xmodel == null) return; final authModel = AuthModel( + host: xmodel["host"], token: xmodel["token"], model: UserModel( userId: xmodel["model"]['M_UserID'],