Commit 97763d9b authored by DatHV's avatar DatHV
Browse files

update health book

parent fc2caf86
......@@ -16,6 +16,8 @@ extension NullableString on String? {
}
bool get hasText => (this?.trim().isNotEmpty ?? false);
bool get isNullOrBlank => (this == null || this!.trim().isEmpty);
}
extension StringUrlExtension on String {
......
......@@ -1048,9 +1048,9 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient {
});
}
Future<BaseResponseModel<VerifyRegisterCampaignModel>> verifyRegisterForm(String path) async {
Future<BaseResponseModel<VerifyRegisterCampaignModel>> verifyRegisterForm(String path, Json body) async {
var path_ = path.startsWith('/') ? path : '/$path';
return requestNormal(path_, Method.POST, {}, (data) {
return requestNormal(path_, Method.POST, body, (data) {
return VerifyRegisterCampaignModel.fromJson(data as Json);
});
}
......
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../extensions/date_format.dart';
import '../../widgets/custom_navigation_bar.dart';
import '../../widgets/custom_toast_message.dart';
import 'health_book_card_detail_viewmodel.dart';
import 'health_book_model.dart';
class HealthBookCardDetail extends BaseScreen {
const HealthBookCardDetail({super.key});
@override
State<HealthBookCardDetail> createState() => _HealthBookCardDetailState();
}
class _HealthBookCardDetailState extends BaseState<HealthBookCardDetail> with BasicState {
final _viewModel = Get.put(HealthBookCardDetailViewModel());
@override
void initState() {
super.initState();
HealthBookCardItemModel? data;
String? cardId;
final args = Get.arguments;
if (args is Map) {
data = args['health_book_card'];
cardId = args['id'];
}
if (data == null && cardId.isNullOrBlank) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.back();
});
return;
}
if (data != null) {
_viewModel?.card.value = data;
} else if (cardId.hasText) {
_viewModel?.getHealthBookCardDetail(cardId!);
}
_viewModel.onShowAlertError = (message) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showAlertError(content: message, onConfirmed: () => Get.back());
});
};
}
@override
Widget createBody() {
final theme = Theme.of(context);
return Scaffold(
appBar: CustomNavigationBar(title: 'Chi tiết thẻ'),
body: SafeArea(
child: Obx(() {
final card = _viewModel?.card.value;
if (card == null) return EmptyWidget();
final name = card.fullName.orIfBlank('--');
final phone = card.phoneNumber.orIfBlank('--');
final cardCode = card.cardCode.orIfBlank('--');
final expireDate = (card.expireDate ?? '').toDate()?.toFormattedString() ?? '--';
final lastUpdated =
(card.updatedAt ?? '').toDate()?.toFormattedString(format: DateFormat.ddMMyyyyhhmm) ?? '--';
final remaining = (card.countCheckupUnused ?? 0).toString();
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_CardPreview(cardName: card.cardName.orIfBlank('--'), fullName: name, expireDate: expireDate),
const SizedBox(height: 20),
Text('Thông tin thẻ', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600)),
const SizedBox(height: 12),
_InfoField(label: 'Họ và tên', value: name),
const SizedBox(height: 12),
_InfoField(label: 'Số điện thoại', value: phone),
const SizedBox(height: 12),
_InfoField(
label: 'Mã thẻ',
value: cardCode,
trailing: cardCode == '--' ? null : _CopyChip(onTap: () => _copy(context, cardCode)),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _InfoField(label: 'Lượt chưa sử dụng', value: remaining)),
const SizedBox(width: 12),
Expanded(child: _InfoField(label: 'Hạn sử dụng', value: expireDate)),
],
),
const SizedBox(height: 12),
_InfoField(label: 'Cập nhật gần nhất', value: lastUpdated),
if (card.bottomButton?.hiden != true && card.bottomButton?.text.hasText == true) ...[
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: parseHexColor(
card.bottomButton?.color ?? '',
fallbackColor: theme.primaryColor,
),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: () => card.bottomButton?.directional?.begin(),
child: Text(
card.bottomButton?.text ?? '',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
),
),
),
],
],
),
);
}),
),
);
}
void _copy(BuildContext context, String text) {
Clipboard.setData(ClipboardData(text: text));
showToastMessage('Đã sao chép mã thẻ');
}
}
class _CardPreview extends StatelessWidget {
final String cardName;
final String fullName;
final String expireDate;
const _CardPreview({required this.cardName, required this.fullName, required this.expireDate});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 343 / 230,
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
fit: StackFit.expand,
children: [
const Image(image: AssetImage('assets/images/bg_medon_card.png'), fit: BoxFit.cover),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
cardName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w600),
),
// Holder name
Text(
fullName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: 'FBF09C'.toColor() ?? Colors.white,
fontWeight: FontWeight.w600,
),
),
Text(
'Hạn sử dụng: $expireDate',
style: TextStyle(
fontSize: 12,
color: 'FBF09C'.toColor() ?? Colors.white,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
);
}
}
class _InfoField extends StatelessWidget {
final String label;
final String value;
final Widget? trailing;
const _InfoField({required this.label, required this.value, this.trailing});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500)),
const SizedBox(height: 6),
Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade300),
),
child: Row(
children: [
Expanded(child: Text(value, style: theme.textTheme.bodyMedium)),
if (trailing != null) ...[const SizedBox(width: 12), trailing!],
],
),
),
],
);
}
}
class _CopyChip extends StatelessWidget {
final VoidCallback onTap;
const _CopyChip({required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(color: const Color(0xFFE5F8F1), borderRadius: BorderRadius.circular(8)),
child: const Text('COPY', style: TextStyle(color: Color(0xFF00A676), fontWeight: FontWeight.w600)),
),
);
}
}
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../configs/constants.dart';
import '../../networking/restful_api_viewmodel.dart';
import 'health_book_model.dart';
class HealthBookCardDetailViewModel extends RestfulApiViewModel {
var card = Rxn<HealthBookCardItemModel>();
void Function(String message)? onShowAlertError;
Future<void> getHealthBookCardDetail(String cardId) async {
showProgressIndicator();
final response = await client.getDetailHealthBookCard(cardId);
showProgressIndicator();
if (response.isSuccess) {
card.value = response.data;
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../extensions/date_format.dart';
import '../../resources/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
import '../traffic_service/traffic_service_model.dart';
import 'health_book_viewmodel.dart';
import 'widgets/health_book_item.dart';
class HealthBookScreen extends StatefulWidget {
const HealthBookScreen({super.key});
......@@ -28,55 +26,12 @@ class _HealthBookScreenState extends State<HealthBookScreen> {
@override
Widget build(BuildContext context) {
final tags = _viewModel.headerFilterOrder;
return Scaffold(
appBar: CustomNavigationBar(title: "Sổ sức khoẻ điện tử"),
body: Column(
children: [
const SizedBox(height: 8),
Obx(()
=> SizedBox(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: List.generate(tags.length, (index) {
final isSelected = index == _viewModel.selectedIndex.value;
return GestureDetector(
onTap: () {
if (_viewModel.selectedIndex.value == index) return;
setState(() {
_viewModel.selectedIndex.value = index;
_viewModel.getHealthBookCards();
});
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
border: Border.all(
color: isSelected ? BaseColor.primary500 : Colors.grey.shade300,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
tags[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(
color: isSelected ? BaseColor.primary500 : Colors.black87,
),
),
),
);
}),
),
),
)
),
Obx(() => SizedBox(child: _buildTag())),
const Divider(height: 14, color: Colors.black12),
const SizedBox(height: 8),
Expanded(
......@@ -85,81 +40,99 @@ class _HealthBookScreenState extends State<HealthBookScreen> {
return products.isEmpty
? Center(child: EmptyWidget())
: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final item = products[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
onTap: () {
print('TODO Tapped on item: ${item.phoneNumber}');
// Get.toNamed(trafficServiceDetailScreen, arguments: {'serviceId': item.itemId});
},
leading: SizedBox(
width: 60, // <= giới hạn rõ
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: loadNetworkImage(
url: item.media?.firstOrNull?.url ?? '',
fit: BoxFit.cover,
placeholderAsset: 'assets/images/bg_default_11.png',
),
),
itemCount: products.length,
itemBuilder: (context, index) {
final item = products[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: HealthBookItem(
item: item,
onTap: () {
Get.toNamed(healthBookCardDetail, arguments: {'health_book_card': item, 'id': item.itemId.toString() ?? ''});
},
),
title: Text(item.fullName ?? '', style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
item.cardName ?? '',
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
);
},
);
}),
),
],
),
);
}
Widget _buildTag() {
final tags = _viewModel.headerFilterOrder;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: List.generate(tags.length, (index) {
final item = tags[index];
final isSelected = index == _viewModel.selectedIndex.value;
return GestureDetector(
onTap: () {
if (_viewModel.selectedIndex.value == index && item.sort == null) return;
setState(() {
if (item.sort == SortFilter.asc) {
tags[index].sort = SortFilter.desc;
} else if (item.sort == SortFilter.desc) {
tags[index].sort = SortFilter.asc;
}
_viewModel.selectedIndex.value = index;
_viewModel.getHealthBookCards();
});
},
child: Container(
height: 52,
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: isSelected ? BaseColor.primary500 : Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Text(
item.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(color: isSelected ? BaseColor.primary500 : Colors.black87),
),
if (item.sort != null)
Row(
children: [
const SizedBox(width: 4),
SizedBox(
height: 24,
child: Column(
children: [
Icon(
Icons.keyboard_double_arrow_up_sharp,
size: 12,
color:
(isSelected && item.sort == SortFilter.asc) ? BaseColor.primary500 : Colors.black54,
),
),
const SizedBox(height: 4),
Text(
(item.expireDate ?? '').toDate()?.toFormattedString() ?? '',
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
Icon(
Icons.keyboard_double_arrow_down_sharp,
size: 12,
color:
(isSelected && item.sort == SortFilter.desc)
? BaseColor.primary500
: Colors.black54,
),
),
const SizedBox(height: 4),
Text(
'Cập nhật lúc ${(item.updatedAt ?? '').toDate()?.toFormattedString(format: DateFormat.ddMMyyyyhhmm) ?? ''}',
style: const TextStyle(fontSize: 11, color: Colors.black54),
),
],
),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
item.active?.text ?? '',
style: const TextStyle(fontSize: 12, color: Colors.green, fontWeight: FontWeight.w500),
],
),
),
),
],
),
);
},
);
}),
),
],
],
),
),
);
}),
),
);
}
......
......@@ -10,16 +10,21 @@ class HealthBookViewModel extends RestfulApiViewModel {
var healthBookDataDetail = Rxn<HealthBookCardItemModel>();
void Function(String message)? onShowAlertError;
RxInt selectedIndex = 0.obs;
late List<HeaderFilterOrderModel> headerFilterOrder;
List<HeaderFilterOrderModel> get headerFilterOrder {
return [
@override
onInit() {
super.onInit();
headerFilterOrder = [
HeaderFilterOrderModel(
title: 'Tất cả',
suffixChecking: 'tatca',
expired: '',
suffixChecking: 'all',
selected: true,
),
HeaderFilterOrderModel(
title: 'Hiệu lực',
expired: "false",
suffixChecking: 'hieuluc',
),
HeaderFilterOrderModel(
......@@ -44,6 +49,8 @@ class HealthBookViewModel extends RestfulApiViewModel {
try {
final response = await client.getHealthBookCards(body);
hideLoading();
// var data = HealthBookResponseModel(total: 20, products: makeFakeHealthBookCards(20));
// healthBookData.value = data;
if (response.isSuccess) {
healthBookData.value = response.data;
} else {
......@@ -55,19 +62,41 @@ class HealthBookViewModel extends RestfulApiViewModel {
}
}
Future<void> getDetailHealthBookCard(String id) async {
showLoading();
try {
final response = await client.getDetailHealthBookCard(id);
hideLoading();
if (response.isSuccess) {
healthBookDataDetail.value = response.data;
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
}
// Future<void> getDetailHealthBookCard(String id) async {
// showLoading();
// try {
// final response = await client.getDetailHealthBookCard(id);
// hideLoading();
// if (response.isSuccess) {
// healthBookDataDetail.value = response.data;
// } else {
// onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
// }
// } catch (error) {
// hideLoading();
// onShowAlertError?.call("Error fetching product detail: $error");
// }
// }
// List<HealthBookCardItemModel> makeFakeHealthBookCards(int n) {
// return List.generate(n, (i) {
// final id = 2000 + i;
// return HealthBookCardItemModel(
// itemId: id,
// cardName: 'Thẻ Khám #$id',
// fullName: 'User #$id',
// cardCode: 'MP-${id.toString().padLeft(4, '0')}',
// expireDate: '2025-12-31T00:00:00Z',
// phoneNumber: '09${(10000000 + i).toString().padLeft(8, '0')}',
// updatedAt: '2025-10-10T10:00:00Z',
// countCheckupUnused: i % 6,
// bottomButton: ButtonConfigModel(text: 'Sử dụng', action: 'use'),
// media: [
// ProductMediaItem(url: 'https://picsum.photos/seed/$id/300/300'),
// ],
// buyMoreNote: ButtonConfigModel(text: 'Mua thêm', action: 'buy_more'),
// active: ActiveTextConfig(text: 'Còn hiệu lực', textColor: '#0F9D58', bgColor: '#E6F4EA'),
// );
// });
// }
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../../extensions/date_format.dart';
import '../health_book_model.dart';
class HealthBookItem extends StatelessWidget {
final HealthBookCardItemModel item;
final VoidCallback? onTap;
const HealthBookItem({super.key, required this.item, this.onTap});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 60,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: loadNetworkImage(
url: item.media?.firstOrNull?.url ?? '',
fit: BoxFit.cover,
placeholderAsset: 'assets/images/bg_default_11.png',
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.fullName ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
item.cardName ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14, color: Colors.black87, fontWeight: FontWeight.w500),
),
const SizedBox(height: 4),
Text(
(item.expireDate ?? '').toDate()?.toFormattedString() ?? '',
style: const TextStyle(fontSize: 14, color: Colors.black87, fontWeight: FontWeight.w500),
),
const SizedBox(height: 4),
Text(
'Cập nhật lúc ${(item.updatedAt ?? '').toDate()?.toFormattedString(format: DateFormat.ddMMyyyyhhmm) ?? ''}',
style: const TextStyle(fontSize: 11, color: Colors.black54),
),
],
),
),
const SizedBox(width: 8),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Icon(Icons.keyboard_arrow_right_outlined, size: 20),
const SizedBox(height: 4),
Text(
(item.countCheckupUnused ?? 0) > 0 ? 'Còn ${item.countCheckupUnused} lượt khám' : 'Hết lượt',
style: TextStyle(
fontSize: 12,
color: (item.countCheckupUnused ?? 0) > 0 ? Colors.green : Colors.red,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
item.active?.text ?? '',
style: TextStyle(
fontSize: 12,
color: parseHexColor(item.active?.textColor ?? '', fallbackColor: Colors.green),
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 6),
GestureDetector(
onTap: () {
item.buyMoreNote?.directional?.begin();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
border: Border.all(color: Colors.green),
borderRadius: BorderRadius.circular(16),
color: Colors.blue.shade50,
),
child: Text(
item.buyMoreNote?.text ?? 'Mua thêm',
style: const TextStyle(fontSize: 12, color: Colors.green, fontWeight: FontWeight.w600),
),
),
),
],
),
],
),
),
);
}
}
......@@ -23,15 +23,15 @@ class VerifyRegisteredPackageModel {
}
@JsonSerializable(explicitToJson: true)
class VerifyRegisterVnTraAlertModel {
class VerifyRegisterCampaignAlertModel {
final bool? alert;
final String? description;
const VerifyRegisterVnTraAlertModel({this.alert, this.description});
const VerifyRegisterCampaignAlertModel({this.alert, this.description});
factory VerifyRegisterVnTraAlertModel.fromJson(Map<String, dynamic> json)
=> _$VerifyRegisterVnTraAlertModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisterVnTraAlertModelToJson(this);
factory VerifyRegisterCampaignAlertModel.fromJson(Map<String, dynamic> json)
=> _$VerifyRegisterCampaignAlertModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisterCampaignAlertModelToJson(this);
}
@JsonSerializable(explicitToJson: true)
......@@ -45,7 +45,7 @@ class VerifyRegisterCampaignModel {
final String? licensePlate;
@JsonKey(name: 'registed_package')
final VerifyRegisteredPackageModel? registeredPackage;
final VerifyRegisterVnTraAlertModel? alert;
final VerifyRegisterCampaignAlertModel? alert;
const VerifyRegisterCampaignModel({
this.valid,
......@@ -57,8 +57,8 @@ class VerifyRegisterCampaignModel {
});
factory VerifyRegisterCampaignModel.fromJson(Map<String, dynamic> json)
=> _$VerifyRegisterVnTraModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisterVnTraModelToJson(this);
=> _$VerifyRegisterCampaignModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisterCampaignModelToJson(this);
}
class VerifyFormRegisterModel {
......
......@@ -24,15 +24,15 @@ Map<String, dynamic> _$VerifyRegisteredPackageModelToJson(
'description': instance.description,
};
VerifyRegisterVnTraAlertModel _$VerifyRegisterVnTraAlertModelFromJson(
VerifyRegisterCampaignAlertModel _$VerifyRegisterCampaignAlertModelFromJson(
Map<String, dynamic> json,
) => VerifyRegisterVnTraAlertModel(
) => VerifyRegisterCampaignAlertModel(
alert: json['alert'] as bool?,
description: json['description'] as String?,
);
Map<String, dynamic> _$VerifyRegisterVnTraAlertModelToJson(
VerifyRegisterVnTraAlertModel instance,
Map<String, dynamic> _$VerifyRegisterCampaignAlertModelToJson(
VerifyRegisterCampaignAlertModel instance,
) => <String, dynamic>{
'alert': instance.alert,
'description': instance.description,
......@@ -54,7 +54,7 @@ VerifyRegisterCampaignModel _$VerifyRegisterCampaignModelFromJson(
alert:
json['alert'] == null
? null
: VerifyRegisterVnTraAlertModel.fromJson(
: VerifyRegisterCampaignAlertModel.fromJson(
json['alert'] as Map<String, dynamic>,
),
);
......
......@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/register_campaign/register_form_input_viewmodel.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../resources/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
......@@ -9,14 +11,14 @@ import '../voucher/models/product_model.dart';
import 'input_form_cell.dart';
import 'model/registration_form_package_model.dart';
class RegisterFormInputScreen extends StatefulWidget {
class RegisterFormInputScreen extends BaseScreen {
const RegisterFormInputScreen({super.key});
@override
State<RegisterFormInputScreen> createState() => _RegisterFormInputScreenState();
}
class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
class _RegisterFormInputScreenState extends BaseState<RegisterFormInputScreen> with BasicState {
late final RegisterFormInputViewModel _viewModel;
String _title = '';
final _isFormValid = false.obs;
......@@ -27,14 +29,28 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
void initState() {
super.initState();
_viewModel = Get.put(RegisterFormInputViewModel());
_viewModel.onShowAlertError = (message) {
showAlertError(content: message);
};
_viewModel.verifyRegisterFormSuccess = (data) {
final alert = data.alert;
final des = alert?.description ?? '';
if (alert?.alert == true && des.isNotEmpty) {
showAlertError(content: des, onConfirmed: () {
_gotoPaymentScreen();
});
} else {
_gotoPaymentScreen();
}
};
final args = Get.arguments as Map<String, dynamic>?;
_product = args?['product'] as ProductModel?;
if (args?['formConfirm'] != null) {
_isConfirmScreen = true;
final data = args!['formConfirm'] as RegistrationFormPackageModel;
_title = data?.formConfirm?.title ?? '';
final data = args?['formConfirm'] as RegistrationFormPackageModel;
_title = data.formConfirm?.title ?? '';
_viewModel.form.value = data;
} else {
_isConfirmScreen = false;
......@@ -49,7 +65,7 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
}
@override
Widget build(BuildContext context) {
Widget createBody() {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
......@@ -67,18 +83,18 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header HTML
if (form?.headerDescription?.title?.isNotEmpty == true)
if (form.headerDescription?.title?.isNotEmpty == true)
Container(
color: BaseColor.primary50,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlWidget(form!.headerDescription!.title!),
child: HtmlWidget(form.headerDescription!.title!),
),
),
const SizedBox(height: 12),
// Inputs
if (inputItem != null)
...inputItem!.map(
...inputItem.map(
(item) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: InputFormCell(model: item, onChanged: _validateForm, isViewOnly: _isConfirmScreen),
......@@ -93,7 +109,7 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (form?.checked == true && _isConfirmScreen == false)
if (form.checked == true && _isConfirmScreen == false)
Obx(
() => Checkbox(
activeColor: BaseColor.primary400,
......@@ -109,7 +125,7 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
onTap: () {
form.footerDescription?.directional?.begin();
},
child: HtmlWidget(form!.footerDescription!.title!)),
child: HtmlWidget(form.footerDescription!.title!)),
),
],
),
......@@ -148,7 +164,7 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: _gotoPaymentScreen,
onPressed: _continueToPaymentHandle,
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary400,
padding: const EdgeInsets.symmetric(vertical: 14),
......@@ -186,6 +202,15 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
void _onSubmit() {
if (_viewModel.form.value?.formConfirm?.checked == true) {
Get.toNamed(registerFormInputScreen, arguments: {"formConfirm": _viewModel.form.value, "product": _product}, preventDuplicates: false);
} else {
_continueToPaymentHandle();
}
}
void _continueToPaymentHandle() {
final verifyURL = _viewModel.form.value?.verify?.url ?? '';
if (verifyURL.isNotEmpty) {
_viewModel.verifyRegisterForm();
} else {
_gotoPaymentScreen();
}
......
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import 'model/registration_form_package_model.dart';
......@@ -7,32 +8,29 @@ import 'model/verify_register_model.dart';
class RegisterFormInputViewModel extends RestfulApiViewModel {
var form = Rxn<RegistrationFormPackageModel>();
var verifyData = Rxn<VerifyRegisterCampaignModel>();
var isLoading = false.obs;
var isChecked = true.obs;
void Function(String message)? onShowAlertError;
void Function(VerifyRegisterCampaignModel data)? verifyRegisterFormSuccess;
Future<void> fetchRegisterFormInput(String id) async {
try {
isLoading.value = true;
final response = await client.getRegistrationForm(id);
form.value = response.data;
} catch (error) {
// onShowAlertError?.call("Error fetching product detail: $error");
print("Error fetching product detail: $error");
} finally {
isLoading.value = false;
final response = await client.getRegistrationForm(id);
form.value = response.data;
if (!response.isSuccess) {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
}
Future<void> verifyRegisterForm() async {
final path = form.value?.formRegistration?.verify?.url ?? '';
try {
isLoading.value = true;
final response = await client.verifyRegisterForm(path);
verifyData.value = response.data;
} catch (error) {
print("Error fetching product detail: $error");
} finally {
isLoading.value = false;
showProgressIndicator();
final path = form.value?.formRegistration?.verify?.url ?? '/accountPasswordReset/1.0.0';
final metaData = (form.value?.submitParams ?? {}).toJsonString();
final response = await client.verifyRegisterForm(path, {'metadata': metaData});
hideProgressIndicator();
final data = response.data;
if (!response.isSuccess && data != null) {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
return;
}
verifyRegisterFormSuccess?.call(data!);
}
}
\ No newline at end of file
import 'package:mypoint_flutter_app/directional/directional_screen.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
......@@ -188,19 +189,45 @@ class ProductMediaItem {
class ButtonConfigModel {
String? text;
String? color;
String? action;
String? clickActionType;
String? clickActionParam;
bool? hiden;
DirectionalScreen? get directional {
return DirectionalScreen.build(
clickActionType: clickActionType,
clickActionParam: clickActionParam,
);
}
ButtonConfigModel({this.text, this.action});
ButtonConfigModel({
this.text,
this.color,
this.action,
this.clickActionType,
this.clickActionParam,
this.hiden,
});
factory ButtonConfigModel.fromJson(Map<String, dynamic> json) {
return ButtonConfigModel(
text: json['text'],
color: json['color'],
action: json['action'],
clickActionType: json['click_action_type'],
clickActionParam: json['click_action_param'],
hiden: json['hiden'],
);
}
Map<String, dynamic> toJson() => {
'text': text,
'color': color,
'action': action,
'click_action_type': clickActionType,
'click_action_param': clickActionParam,
'hiden': hiden,
};
}
\ No newline at end of file
......@@ -16,6 +16,7 @@ import '../screen/electric_payment/electric_payment_history_screen.dart';
import '../screen/electric_payment/electric_payment_screen.dart';
import '../screen/game/game_cards/game_card_screen.dart';
import '../screen/game/game_tab_screen.dart';
import '../screen/health_book/health_book_card_detail.dart';
import '../screen/health_book/health_book_screen.dart';
import '../screen/history_point/history_point_screen.dart';
import '../screen/history_point_cashback/history_point_cashback_screen.dart';
......@@ -101,6 +102,7 @@ const bankAccountManagerScreen = '/bankAccountManagerScreen';
const historyPointScreen = '/historyPointScreen';
const qrCodeScreen = '/qrCodeScreen';
const myMobileCardDetailScreen = '/myMobileCardDetailScreen';
const healthBookCardDetail = '/healthBookCardDetail';
class RouterPage {
static List<GetPage> pages() {
......@@ -168,6 +170,7 @@ class RouterPage {
GetPage(name: qrCodeScreen, page: () => QRCodeScreen()),
GetPage(name: myMobileCardDetailScreen, page: () => MyMobileCardDetailScreen()),
GetPage(name: healthBookScreen, page: () => HealthBookScreen()),
GetPage(name: healthBookCardDetail, page: () => HealthBookCardDetail()),
];
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment