step 15 : selected ke edit

This commit is contained in:
sindhu
2025-02-18 05:46:59 +07:00
parent a13271fa73
commit 6af18dbe58
16 changed files with 739 additions and 19 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import '../screen/home/home_screen.dart';
import '../screen/login/login_screen.dart';
import '../screen/scan/edit_scan_screen.dart';
import '../screen/scan/scan_screen.dart';
import '../screen/splash/splash_screen.dart';
@@ -9,6 +10,7 @@ const splashRoute = "/splashRoute";
const loginRoute = "/loginRoute";
const homeRoute = "/homeRoute";
const scanRoute = "/scanRoute";
const editScanRoute = "/editScanRoute";
class AppRoute {
static Route<dynamic> generateRoute(RouteSettings settings) {
@@ -56,6 +58,17 @@ class AppRoute {
});
}
// edit screen
if (settings.name == editScanRoute) {
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), padding: EdgeInsets.all(0)),
child: EditScanScreen(),
);
});
}
return MaterialPageRoute(builder: (context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(

View File

@@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/app/route.dart';
import '../../app/route.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();

View File

@@ -0,0 +1,45 @@
class EditPersonModel {
final String personID;
final String personNIK;
final String personName;
final String personDob;
final String personSex;
final String personUrl;
final String m_sexname;
EditPersonModel({
required this.personID,
required this.personNIK,
required this.personName,
required this.personDob,
required this.personSex,
required this.personUrl,
required this.m_sexname,
});
// Convert JSON to Model
factory EditPersonModel.fromJson(Map<String, dynamic> json) {
return EditPersonModel(
personID: json['Person_ID'] ?? "",
personNIK: json['Person_NIK'] ?? "",
personName: json['Person_Name'] ?? "",
personDob: json['Person_Dob'] ?? DateTime.now(),
personSex: json['Person_Sex'] ?? "",
personUrl: json['Person_Url'] ?? "",
m_sexname: json['m_sexname'] ?? "",
);
}
// 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,
'm_sexname':m_sexname,
};
}
}

View File

@@ -20,13 +20,13 @@ class PersonKtp {
// 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'],
m_sexname: json['m_sexname'],
personID: json['Person_ID'] ?? "",
personNIK: json['Person_NIK'] ?? "",
personName: json['Person_Name'] ?? "",
personDob: json['Person_Dob'] ?? DateTime.now(),
personSex: json['Person_Sex'] ?? "",
personUrl: json['Person_Url'] ?? "",
m_sexname: json['m_sexname'] ?? "",
);
}

31
lib/model/sex_model.dart Normal file
View File

@@ -0,0 +1,31 @@
class SexModel {
final String M_SexID;
final String M_SexCode;
final String m_sexname;
final String M_SexNameLang;
SexModel({
required this.M_SexID,
required this.M_SexCode,
required this.m_sexname,
required this.M_SexNameLang,
});
factory SexModel.fromJson(Map<String, dynamic> json) {
return SexModel(
M_SexID: json['M_SexID'],
M_SexCode: json['M_SexCode'],
m_sexname: json['m_sexname'],
M_SexNameLang: json['M_SexNameLang'],
);
}
Map<String, dynamic> toJson() {
return {
'M_SexID': M_SexID,
'M_SexCode': M_SexCode,
'm_sexname': m_sexname,
'M_SexNameLang': M_SexNameLang,
};
}
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:scanktpflutter/model/sex_model.dart';
import '../model/edit_person_model.dart';
final selectedPersonIdx = StateProvider<String>((ref) => "0");
final selectedEdit = StateProvider<EditPersonModel>(
(ref) => EditPersonModel(
personID: "",
personNIK: "",
personName: "",
personDob: "",
personSex: "",
personUrl: "",
m_sexname: "",
),
);
// inputan edit
final eNikCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eNamaCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eDobCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eDobDt = StateProvider<DateTime>(
(ref) => DateTime.now(),
);
final eSexCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final eSexSelected = StateProvider<SexModel>(
(ref) => SexModel(
M_SexID: "",
M_SexCode: "",
m_sexname: "",
M_SexNameLang: "",
),
);

View File

@@ -1,4 +1,7 @@
import '../../model/sex_model.dart';
import '../model/person_ktp_model.dart';
import '../model/edit_person_model.dart';
import 'base_repository.dart';
class ScanRepository extends BaseRepository {
@@ -42,4 +45,21 @@ class ScanRepository extends BaseRepository {
return resp['message'];
}
// sex
Future<List<SexModel>> sexRepo({
required String host,
}) async {
// final service = "${Constant.baseUrl}xauth/login";
final service = "http://${host}/one-api/scan-ktp/Scanktp/getSex";
final resp = await post(param: {}, service: service);
final result = List<SexModel>.empty(growable: true);
resp['data'].forEach((e) {
final model = SexModel.fromJson(e);
result.add(model);
});
return result;
}
}

View File

@@ -1,10 +1,13 @@
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/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 CardRiwayatScan extends HookConsumerWidget {
final PersonKtp data;
@@ -42,8 +45,22 @@ class CardRiwayatScan extends HookConsumerWidget {
),
),
InkWell(
onTap: (){
print('id : ${data.personID}');
onTap: () {
// print('id : ${data.personID}');
ref.read(selectedPersonIdx.notifier).state =
data.personID;
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,

View File

@@ -2,9 +2,9 @@ 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 '../../model/person_ktp_model.dart';
import '../../screen/home/card_riwayat_scan.dart';
import '../../screen/home/list_riwayat_scan_provider.dart';
import '../../app/constant.dart';
import '../../app/route.dart';

View File

@@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/repository/scan_repository.dart';
import '../../repository/scan_repository.dart';
import '../../model/person_ktp_model.dart';
import '../../provider/dio_provider.dart';

View File

@@ -2,7 +2,7 @@ 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/app/route.dart';
import '../../app/route.dart';
import '../../app/constant.dart';
import '../../provider/current_user_provider.dart';

View File

@@ -0,0 +1,476 @@
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/sex_model.dart';
import '../../screen/scan/sex_provider.dart';
import '../../provider/scan_provider.dart';
import '../../widget/customsnackbarwidget.dart';
import '../../app/app_extension.dart';
import '../../app/constant.dart';
import '../../app/route.dart';
import '../../provider/current_user_provider.dart';
class EditScanScreen extends HookConsumerWidget {
const EditScanScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
SystemUiOverlay.bottom,
]);
final currentUser = ref.watch(currentUserProvider);
final host = currentUser?.host ?? "";
final userId = currentUser?.model.userId ?? "";
final baseUrl = "https://$host/";
final selectedPersonId = ref.watch(selectedPersonIdx);
final isLoading = useState<bool>(false);
final listDataEdit = ref.watch(selectedEdit);
final listSex = useState<List<SexModel>>(
List.empty(growable: true),
);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final userID = currentUser?.model.userId ?? "0";
if (userID == "0") {
// not logged in
Navigator.of(context)
.pushNamedAndRemoveUntil(loginRoute, (route) => false);
return;
}
});
return () {};
}, [currentUser]);
// sex
ref.listen(sexProvider, (prev, next) {
if (next is SexStateLoading) {
isLoading.value = true;
} else if (next is SexStateError) {
isLoading.value = false;
// errorMessage.value = next.message;
snackbarWidget(
context,
next.message,
snackbarType.error,
Duration(seconds: 3),
);
} else if (next is SexStateDone) {
isLoading.value = false;
listSex.value = next.model;
ref.read(eSexCtr.notifier).state =
TextEditingController(text: next.model[0].m_sexname);
}
});
// isLoading
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) async {
if (isLoading.value == true) {
snackbarWidget(
context,
"Sedang Memuat Data",
snackbarType.warning,
Duration(days: 1),
);
}
});
return () {};
}, [isLoading]);
// check person id
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) async {
final personIDx = selectedPersonId;
if (personIDx == "0") {
snackbarWidget(
context,
'Gagal mendapatkan data',
snackbarType.error,
Duration(seconds: 3),
);
Navigator.of(context)
.pushNamedAndRemoveUntil(homeRoute, (route) => false);
return;
} else {
// listDataEdit
// set ke inputan
ref.read(eNikCtr.notifier).state =
TextEditingController(text: listDataEdit.personNIK);
ref.read(eNamaCtr.notifier).state =
TextEditingController(text: listDataEdit.personName);
ref.read(eDobCtr.notifier).state = TextEditingController(
text: formatDateJiffy(listDataEdit.personDob));
ref.read(eDobDt.notifier).state =
DateTime.parse(listDataEdit.personDob);
// sex
ref.read(sexProvider.notifier).sex(
host: host,
);
}
});
return () {};
}, [selectedPersonId]);
// date picker
Future<void> _selectDate(BuildContext context, WidgetRef ref) async {
DateTime? newSelectedDate = await showDatePicker(
context: context,
initialDate: (listDataEdit.personDob == "0000-00-00")
? DateTime.parse(listDataEdit.personDob)
: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (newSelectedDate != null) {
ref.read(eDobDt.notifier).state = newSelectedDate;
ref.read(eDobCtr.notifier).state.text =
formatDateJiffy(newSelectedDate.toLocal().toString());
}
}
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
},
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Constant.bgGrey,
body: ListView(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
// atas
Image.asset(
'images/vektoratas.png',
width: double.infinity,
fit: BoxFit.cover,
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 34,
),
),
// image ktp
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Image.network(
baseUrl + listDataEdit.personUrl,
fit: BoxFit.fitWidth,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 32,
),
),
// inputan nik
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'NIK',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
controller: ref.read(eNikCtr),
decoration: InputDecoration(
// hintText: "NIK",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan nama
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'Nama',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
controller: ref.read(eNamaCtr),
decoration: InputDecoration(
// hintText: "NIK",
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan dob
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'DOB',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: TextField(
readOnly: true,
controller: ref.read(eDobCtr),
onTap: () {
_selectDate(context, ref);
},
decoration: InputDecoration(
// hintText: "NIK",
suffixIcon: IconButton(
icon: Icon(Icons.calendar_today,
color: Constant.textCardGrey),
onPressed: () {
_selectDate(context, ref);
},
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
// inputan jenis kelamin
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: Text(
'Jenis Kelamin',
style: Constant.title_400(context: context).copyWith(
color: Constant.inputanGrey,
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 16,
),
),
Padding(
padding: EdgeInsets.only(
left: Constant.getActualYPhone(context: context, y: 12),
right: Constant.getActualYPhone(context: context, y: 12),
),
child: DropdownMenu<SexModel>(
initialSelection:
(listSex.value.isNotEmpty) ? listSex.value[0] : null,
controller: ref.read(eSexCtr),
width: Constant.getActualXPhone(
context: context, x: double.infinity),
// hintText: "Jenis Kelamin",
requestFocusOnTap: true,
enableFilter: true,
// label: const Text('Jenis Kelamin'),
onSelected: (SexModel? sex) {
if (sex != null) {
ref.read(eSexSelected.notifier).state = sex;
FocusScope.of(context).unfocus();
}
},
dropdownMenuEntries: listSex.value
.map<DropdownMenuEntry<SexModel>>((SexModel sex) {
return DropdownMenuEntry<SexModel>(
value: sex,
label: sex.m_sexname,
);
}).toList(),
inputDecorationTheme: InputDecorationTheme(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 44,
),
),
// button save
Padding(
padding: EdgeInsets.only(
left: Constant.getActualXPhone(
context: context,
x: 16,
),
right: Constant.getActualXPhone(
context: context,
x: 16,
),
),
child: SizedBox(
width: double.infinity,
height: Constant.getActualYPhone(
context: context,
y: 48,
),
child: ElevatedButton(
onPressed: () {
var param = {
"Person_ID":selectedPersonId,
"Person_Name":ref.read(eNamaCtr).text,
"Person_Dob":ref.read(eDobCtr).text,
"Person_Sex":ref.read(eSexSelected).M_SexID,
};
print(param);
// Navigator.of(context).pushNamedAndRemoveUntil(
// homeRoute,
// (route) => false,
// );
},
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: [
Text(
'SAVE',
style:
Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
],
),
),
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 20,
),
),
],
),
),
);
}
}

View File

@@ -6,7 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image/image.dart' as img;
import 'package:scanktpflutter/app/route.dart';
import '../../app/route.dart';
import '../../app/constant.dart';
import '../../provider/current_user_provider.dart';

View File

@@ -0,0 +1,70 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../model/sex_model.dart';
import '../../repository/scan_repository.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';
// 3. state provider
final sexProvider =
StateNotifierProvider<SexNotifier, SexState>(
(ref) => SexNotifier(ref: ref));
// 2. notifier
class SexNotifier extends StateNotifier<SexState> {
final Ref ref;
SexNotifier({required this.ref})
: super(SexStateInit());
void sex({
required String host,
}) async {
try {
state = SexStateLoading();
final resp = await ScanRepository(
dio: ref.read(dioProvider),
).sexRepo(
host: host
);
// print(resp);
state = SexStateDone(model: resp);
} catch (e) {
if (e is BaseRepositoryException) {
state = SexStateError(message: e.message);
} else {
state = SexStateError(message: e.toString());
}
}
}
}
// 1. state
abstract class SexState extends Equatable {
final DateTime date;
const SexState(this.date);
@override
List<Object?> get props => [date];
}
class SexStateInit extends SexState {
SexStateInit() : super(DateTime.now());
}
class SexStateLoading extends SexState {
SexStateLoading() : super(DateTime.now());
}
class SexStateError extends SexState {
final String message;
SexStateError({
required this.message,
}) : super(DateTime.now());
}
class SexStateDone extends SexState {
final List<SexModel> model;
SexStateDone({
required this.model,
}) : super(DateTime.now());
}

View File

@@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:scanktpflutter/repository/scan_repository.dart';
import '../../repository/scan_repository.dart';
import '../../provider/dio_provider.dart';
import '../../repository/base_repository.dart';

View File

@@ -8,7 +8,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:scanktpflutter/main.dart';
import '../../main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {