step 9 : listing history scan dan pull to refresh, perbaikan auth model add host
This commit is contained in:
5
lib/app/app_extension.dart
Normal file
5
lib/app/app_extension.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
|
||||
String formatDateJiffy(String serverDate) {
|
||||
return Jiffy.parse(serverDate).format(pattern: 'dd-MM-yyyy');
|
||||
}
|
||||
@@ -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<String, dynamic> toJson() {
|
||||
return {
|
||||
'host':host,
|
||||
'token': token,
|
||||
'model': model.toJson(),
|
||||
};
|
||||
|
||||
41
lib/model/person_ktp_model.dart
Normal file
41
lib/model/person_ktp_model.dart
Normal file
@@ -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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return {
|
||||
'Person_ID':personID,
|
||||
'Person_NIK': personNIK,
|
||||
'Person_Name': personName,
|
||||
'Person_Dob': personDob,
|
||||
'Person_Sex': personSex,
|
||||
'Person_Url': personUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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"]),
|
||||
);
|
||||
|
||||
22
lib/repository/scan_repository.dart
Normal file
22
lib/repository/scan_repository.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import '../model/person_ktp_model.dart';
|
||||
import 'base_repository.dart';
|
||||
|
||||
class ScanRepository extends BaseRepository {
|
||||
ScanRepository({required super.dio});
|
||||
|
||||
Future<List<PersonKtp>> 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<PersonKtp>.empty(growable: true);
|
||||
resp['data'].forEach((e) {
|
||||
final model = PersonKtp.fromJson(e);
|
||||
result.add(model);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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<bool>(false);
|
||||
final listScanArr = useState<List<PersonKtp>>(
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
70
lib/screen/home/list_riwayat_scan_provider.dart
Normal file
70
lib/screen/home/list_riwayat_scan_provider.dart
Normal file
@@ -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<ListRiwayatScanNotifier, ListRiwayatScanState>(
|
||||
(ref) => ListRiwayatScanNotifier(ref: ref));
|
||||
|
||||
// 2. notifier
|
||||
class ListRiwayatScanNotifier extends StateNotifier<ListRiwayatScanState> {
|
||||
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<Object?> 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<PersonKtp> model;
|
||||
ListRiwayatScanStateDone({
|
||||
required this.model,
|
||||
}) : super(DateTime.now());
|
||||
}
|
||||
@@ -39,6 +39,7 @@ class LoginNotifier extends StateNotifier<LoginState> {
|
||||
//Simpan ke token
|
||||
final shared = await SharedPreferences.getInstance();
|
||||
final token = jsonEncode({
|
||||
"host":host,
|
||||
"date": DateTime.now().toString(),
|
||||
"model": resp.model,
|
||||
"token": resp.token
|
||||
|
||||
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user