code merged
parent
371a0d7439
commit
be6705e11c
@ -0,0 +1,88 @@
|
|||||||
|
class HISProjectOffersList {
|
||||||
|
int? id;
|
||||||
|
int? projectId;
|
||||||
|
String? projectEnglish;
|
||||||
|
String? projectArabic;
|
||||||
|
String? titleArabic;
|
||||||
|
String? titleName;
|
||||||
|
String? descriptionArabic;
|
||||||
|
String? descriptionEnglish;
|
||||||
|
String? detailsArabic;
|
||||||
|
String? detailsEnglish;
|
||||||
|
num? amount;
|
||||||
|
num? amountWithTax;
|
||||||
|
String? validTo;
|
||||||
|
bool? isActive;
|
||||||
|
String? procedureId;
|
||||||
|
bool? isTax;
|
||||||
|
int? serviceId;
|
||||||
|
bool? isGenerateInvoice;
|
||||||
|
num? taxAmount;
|
||||||
|
|
||||||
|
HISProjectOffersList(
|
||||||
|
{this.id,
|
||||||
|
this.projectId,
|
||||||
|
this.projectEnglish,
|
||||||
|
this.projectArabic,
|
||||||
|
this.titleArabic,
|
||||||
|
this.titleName,
|
||||||
|
this.descriptionArabic,
|
||||||
|
this.descriptionEnglish,
|
||||||
|
this.detailsArabic,
|
||||||
|
this.detailsEnglish,
|
||||||
|
this.amount,
|
||||||
|
this.amountWithTax,
|
||||||
|
this.validTo,
|
||||||
|
this.isActive,
|
||||||
|
this.procedureId,
|
||||||
|
this.isTax,
|
||||||
|
this.serviceId,
|
||||||
|
this.isGenerateInvoice,
|
||||||
|
this.taxAmount});
|
||||||
|
|
||||||
|
HISProjectOffersList.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['Id'];
|
||||||
|
projectId = json['ProjectId'];
|
||||||
|
projectEnglish = json['ProjectEnglish'];
|
||||||
|
projectArabic = json['ProjectArabic'];
|
||||||
|
titleArabic = json['TitleArabic'];
|
||||||
|
titleName = json['TitleName'];
|
||||||
|
descriptionArabic = json['DescriptionArabic'];
|
||||||
|
descriptionEnglish = json['DescriptionEnglish'];
|
||||||
|
detailsArabic = json['DetailsArabic'];
|
||||||
|
detailsEnglish = json['DetailsEnglish'];
|
||||||
|
amount = json['Amount'];
|
||||||
|
amountWithTax = json['AmountWithTax'];
|
||||||
|
validTo = json['ValidTo'];
|
||||||
|
isActive = json['IsActive'];
|
||||||
|
procedureId = json['ProcedureId'];
|
||||||
|
isTax = json['IsTax'];
|
||||||
|
serviceId = json['ServiceId'];
|
||||||
|
isGenerateInvoice = json['IsGenerateInvoice'];
|
||||||
|
taxAmount = json['TaxAmount'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['Id'] = this.id;
|
||||||
|
data['ProjectId'] = this.projectId;
|
||||||
|
data['ProjectEnglish'] = this.projectEnglish;
|
||||||
|
data['ProjectArabic'] = this.projectArabic;
|
||||||
|
data['TitleArabic'] = this.titleArabic;
|
||||||
|
data['TitleName'] = this.titleName;
|
||||||
|
data['DescriptionArabic'] = this.descriptionArabic;
|
||||||
|
data['DescriptionEnglish'] = this.descriptionEnglish;
|
||||||
|
data['DetailsArabic'] = this.detailsArabic;
|
||||||
|
data['DetailsEnglish'] = this.detailsEnglish;
|
||||||
|
data['Amount'] = this.amount;
|
||||||
|
data['AmountWithTax'] = this.amountWithTax;
|
||||||
|
data['ValidTo'] = this.validTo;
|
||||||
|
data['IsActive'] = this.isActive;
|
||||||
|
data['ProcedureId'] = this.procedureId;
|
||||||
|
data['IsTax'] = this.isTax;
|
||||||
|
data['ServiceId'] = this.serviceId;
|
||||||
|
data['IsGenerateInvoice'] = this.isGenerateInvoice;
|
||||||
|
data['TaxAmount'] = this.taxAmount;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/date_uitl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class DiscountCardWidget extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<DiscountCardWidget> createState() => _DiscountCardWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DiscountCardWidgetState extends State<DiscountCardWidget> {
|
||||||
|
late ProjectViewModel projectViewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
projectViewModel = Provider.of(context);
|
||||||
|
return Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 16, right: 16, top: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Left icon (replace with your asset if needed)
|
||||||
|
SvgPicture.asset("assets/images/svg/main_banner.svg", height: 170, width: MediaQuery.sizeOf(context).width, fit: BoxFit.fill),
|
||||||
|
// Main content
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0, left: 8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(child: SizedBox.shrink()),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
/// Arabic Section (top-right)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.titleArabic!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.descriptionArabic!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
/// English Section (middle-left)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.titleName!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.descriptionEnglish!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 16),
|
||||||
|
// Bottom row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'This Offer is valid till ${DateUtil.getMonthDayYearDateFormatted(DateUtil.convertStringToDate(projectViewModel.hisProjectOffers.first.validTo))}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"يسري العرض حتى ${DateUtil.getMonthDayYearDateFormattedAr(DateUtil.convertStringToDate(projectViewModel.hisProjectOffers.first.validTo))}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,544 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart' show AutoSizeText;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:hmg_patient_app/config/config.dart';
|
||||||
|
import 'package:hmg_patient_app/config/shared_pref_kay.dart';
|
||||||
|
import 'package:hmg_patient_app/core/enum/PayfortEnums.dart';
|
||||||
|
import 'package:hmg_patient_app/core/model/hospitals/hospitals_model.dart';
|
||||||
|
import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app/models/Appointments/AppoimentAllHistoryResultList.dart';
|
||||||
|
import 'package:hmg_patient_app/models/Authentication/authenticated_user.dart';
|
||||||
|
import 'package:hmg_patient_app/models/LiveCare/ApplePayInsertRequest.dart';
|
||||||
|
import 'package:hmg_patient_app/pages/landing/landing_page.dart';
|
||||||
|
import 'package:hmg_patient_app/services/appointment_services/GetDoctorsList.dart';
|
||||||
|
import 'package:hmg_patient_app/services/livecare_services/livecare_provider.dart';
|
||||||
|
import 'package:hmg_patient_app/services/payfort_services/payfort_project_details_resp_model.dart';
|
||||||
|
import 'package:hmg_patient_app/services/payfort_services/payfort_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/app_shared_preferences.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/app_toast.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/gif_loader_dialog_utils.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/utils.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/utils_new.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/buttons/custom_text_button.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/buttons/defaultButton.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/dialogs/alert_dialog.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/dialogs/location_selection_dialog.dart' show LocationSelectionDialog;
|
||||||
|
import 'package:hmg_patient_app/widgets/in_app_browser/InAppBrowser.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/transitions/fade_page.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../../widgets/dialogs/confirm_dialog.dart';
|
||||||
|
import '../../ToDoList/payment_method_select.dart';
|
||||||
|
|
||||||
|
class OfferDetailsPage extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
VoidCallback? onLoginClick;
|
||||||
|
|
||||||
|
OfferDetailsPage({super.key, required this.title, required this.onLoginClick});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OfferDetailsPage> createState() => _OfferDetailsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OfferDetailsPageState extends State<OfferDetailsPage> {
|
||||||
|
HospitalsModel? selectedHospital;
|
||||||
|
|
||||||
|
late ProjectViewModel projectViewModel;
|
||||||
|
|
||||||
|
String selectedPaymentMethod = "VISA";
|
||||||
|
String transID = "";
|
||||||
|
|
||||||
|
late String tamaraPaymentStatus;
|
||||||
|
late String tamaraOrderID;
|
||||||
|
late MyInAppBrowser browser;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
projectViewModel = Provider.of(context);
|
||||||
|
return Directionality(
|
||||||
|
// lock the whole page to LTR
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: AppScaffold(
|
||||||
|
isShowDecPage: false,
|
||||||
|
isShowAppBar: false,
|
||||||
|
showNewAppBar: true,
|
||||||
|
isHelp: true,
|
||||||
|
showNewAppBarTitle: true,
|
||||||
|
appBarTitle: widget.title,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
/// Banner
|
||||||
|
SvgPicture.asset(
|
||||||
|
"assets/images/svg/details_page_banner.svg",
|
||||||
|
height: 120,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Arabic Offer Section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 32),
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.titleArabic!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Color(0xff008b4c),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.descriptionArabic!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.detailsArabic!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Divider(thickness: 1, color: Color(0xff008b4c)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
/// English Offer Section
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.titleName!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Color(0xff008b4c),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.descriptionEnglish!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
projectViewModel.hisProjectOffers.first.detailsEnglish!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
/// Hospital Dropdown (mock UI)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
openHospitalSelection(context);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: Text(
|
||||||
|
selectedHospital?.nameN ?? TranslationBase.of(context).selectHospital,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Cairo',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
selectedHospital?.name ?? TranslationBase.of(context).selectHospital,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(Icons.arrow_drop_down),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
/// Buttons
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width - 24,
|
||||||
|
height: 50,
|
||||||
|
child: DefaultButton("Pay Now ادفع الآن", () {
|
||||||
|
if (projectViewModel.isLogin) {
|
||||||
|
if (selectedHospital != null) {
|
||||||
|
startPaymentProcess(context);
|
||||||
|
} else {
|
||||||
|
AppToast.showErrorToast(message: TranslationBase.of(context).selectHospital);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ConfirmDialog dialog = new ConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
confirmMessage: TranslationBase.of(context).loginToUseService,
|
||||||
|
okText: TranslationBase.of(context).confirm,
|
||||||
|
cancelText: TranslationBase.of(context).cancel_nocaps,
|
||||||
|
okFunction: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
widget.onLoginClick!();
|
||||||
|
},
|
||||||
|
cancelFunction: () => {});
|
||||||
|
dialog.showAlertDialog(context);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
startPaymentProcess(BuildContext context) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
FadePage(
|
||||||
|
page: PaymentMethod(
|
||||||
|
onSelectedMethod: (String metohd, [String? selectedInstallmentPlan]) {
|
||||||
|
selectedPaymentMethod = metohd;
|
||||||
|
},
|
||||||
|
patientShare: projectViewModel.hisProjectOffers.first.amountWithTax,
|
||||||
|
isShowInstallments: false,
|
||||||
|
isFromAdvancePayment: false),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (selectedPaymentMethod == "ApplePay") {
|
||||||
|
if (projectViewModel.havePrivilege(103)) {
|
||||||
|
startApplePay(context);
|
||||||
|
} else {
|
||||||
|
openPayment(selectedPaymentMethod, projectViewModel.authenticatedUserObject.user, projectViewModel.hisProjectOffers.first.amountWithTax!, AppoitmentAllHistoryResultList());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openPayment(selectedPaymentMethod, projectViewModel.authenticatedUserObject.user, projectViewModel.hisProjectOffers.first.amountWithTax!, AppoitmentAllHistoryResultList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void startApplePay(BuildContext context) async {
|
||||||
|
transID = Utils.getAdvancePaymentTransID(selectedHospital!.iD!, projectViewModel.authenticatedUserObject.user.patientID!);
|
||||||
|
print("TransactionID: $transID");
|
||||||
|
GifLoaderDialogUtils.showMyDialog(context);
|
||||||
|
|
||||||
|
LiveCareService service = new LiveCareService();
|
||||||
|
ApplePayInsertRequest applePayInsertRequest = new ApplePayInsertRequest();
|
||||||
|
|
||||||
|
PayfortProjectDetailsRespModel? payfortProjectDetailsRespModel;
|
||||||
|
await context.read<PayfortViewModel>().getProjectDetailsForPayfort(projectId: selectedHospital!.iD!, serviceId: projectViewModel.hisProjectOffers.first.serviceId).then((value) {
|
||||||
|
payfortProjectDetailsRespModel = value!;
|
||||||
|
});
|
||||||
|
|
||||||
|
applePayInsertRequest.clientRequestID = transID;
|
||||||
|
applePayInsertRequest.clinicID = 0;
|
||||||
|
applePayInsertRequest.currency = projectViewModel.authenticatedUserObject.user.outSA == 1 ? "AED" : "SAR";
|
||||||
|
// applePayInsertRequest.customerEmail = projectViewModel.authenticatedUserObject.user.emailAddress;
|
||||||
|
applePayInsertRequest.customerEmail = "CustID_${projectViewModel.authenticatedUserObject.user.patientID}@HMG.com";
|
||||||
|
applePayInsertRequest.customerID = projectViewModel.authenticatedUserObject.user.patientID;
|
||||||
|
applePayInsertRequest.customerName = projectViewModel.authenticatedUserObject.user.firstName! + " " + projectViewModel.authenticatedUserObject.user.lastName!;
|
||||||
|
applePayInsertRequest.deviceToken = await AppSharedPreferences().getString(PUSH_TOKEN);
|
||||||
|
applePayInsertRequest.voipToken = await AppSharedPreferences().getString(ONESIGNAL_APNS_TOKEN);
|
||||||
|
applePayInsertRequest.doctorID = 0;
|
||||||
|
applePayInsertRequest.projectID = selectedHospital!.iD.toString();
|
||||||
|
applePayInsertRequest.serviceID = projectViewModel.hisProjectOffers.first.serviceId.toString();
|
||||||
|
applePayInsertRequest.channelID = 3;
|
||||||
|
applePayInsertRequest.patientID = projectViewModel.authenticatedUserObject.user.patientID;
|
||||||
|
applePayInsertRequest.patientTypeID = projectViewModel.authenticatedUserObject.user.patientType;
|
||||||
|
applePayInsertRequest.patientOutSA = projectViewModel.authenticatedUserObject.user.outSA;
|
||||||
|
applePayInsertRequest.appointmentDate = null;
|
||||||
|
applePayInsertRequest.appointmentNo = 0;
|
||||||
|
applePayInsertRequest.orderDescription = "Advance Payment";
|
||||||
|
applePayInsertRequest.liveServiceID = "0";
|
||||||
|
applePayInsertRequest.latitude = "0.0";
|
||||||
|
applePayInsertRequest.longitude = "0.0";
|
||||||
|
applePayInsertRequest.amount = projectViewModel.hisProjectOffers.first.amountWithTax.toString();
|
||||||
|
applePayInsertRequest.isSchedule = "0";
|
||||||
|
applePayInsertRequest.language = projectViewModel.isArabic ? 'ar' : 'en';
|
||||||
|
applePayInsertRequest.languageID = projectViewModel.isArabic ? 1 : 2;
|
||||||
|
applePayInsertRequest.userName = projectViewModel.authenticatedUserObject.user.patientID;
|
||||||
|
applePayInsertRequest.responseContinueURL = "http://hmg.com/Documents/success.html";
|
||||||
|
applePayInsertRequest.backClickUrl = "http://hmg.com/Documents/success.html";
|
||||||
|
applePayInsertRequest.paymentOption = "ApplePay";
|
||||||
|
|
||||||
|
applePayInsertRequest.isMobSDK = true;
|
||||||
|
applePayInsertRequest.merchantReference = transID;
|
||||||
|
applePayInsertRequest.merchantIdentifier = payfortProjectDetailsRespModel!.merchantIdentifier;
|
||||||
|
applePayInsertRequest.commandType = "PURCHASE";
|
||||||
|
applePayInsertRequest.signature = payfortProjectDetailsRespModel!.signature;
|
||||||
|
applePayInsertRequest.accessCode = payfortProjectDetailsRespModel!.accessCode;
|
||||||
|
applePayInsertRequest.shaRequestPhrase = payfortProjectDetailsRespModel!.shaRequest;
|
||||||
|
applePayInsertRequest.shaResponsePhrase = payfortProjectDetailsRespModel!.shaResponse;
|
||||||
|
applePayInsertRequest.returnURL = "";
|
||||||
|
|
||||||
|
service.applePayInsertRequest(applePayInsertRequest, context).then((res) async {
|
||||||
|
GifLoaderDialogUtils.hideDialog(context);
|
||||||
|
if (res["MessageStatus"] == 1) {
|
||||||
|
await context.read<PayfortViewModel>().initiateApplePayWithPayfort(
|
||||||
|
customerName: projectViewModel.authenticatedUserObject.user.firstName! + " " + projectViewModel.authenticatedUserObject.user.lastName!,
|
||||||
|
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
|
||||||
|
customerEmail: "CustID_${projectViewModel.authenticatedUserObject.user.patientID}@HMG.com",
|
||||||
|
orderDescription: "Advance Payment",
|
||||||
|
orderAmount: projectViewModel.hisProjectOffers.first.amountWithTax,
|
||||||
|
merchantReference: transID,
|
||||||
|
payfortProjectDetailsRespModel: payfortProjectDetailsRespModel,
|
||||||
|
currency: projectViewModel.authenticatedUserObject.user.outSA == 1 ? "AED" : "SAR",
|
||||||
|
onFailed: (failureResult) async {
|
||||||
|
// GifLoaderDialogUtils.hideDialog(context);
|
||||||
|
log("failureResult: ${failureResult.message.toString()}");
|
||||||
|
AppToast.showErrorToast(message: failureResult.message.toString());
|
||||||
|
},
|
||||||
|
onSuccess: (successResult) async {
|
||||||
|
// GifLoaderDialogUtils.hideDialog(context);
|
||||||
|
GifLoaderDialogUtils.showMyDialog(context);
|
||||||
|
log("Payfort: ${successResult.responseMessage}");
|
||||||
|
await context.read<PayfortViewModel>().addPayfortApplePayResponse(projectViewModel.authenticatedUserObject.user.patientID!, result: successResult);
|
||||||
|
await Future.delayed(Duration(milliseconds: 300));
|
||||||
|
GifLoaderDialogUtils.hideDialog(context);
|
||||||
|
await Future.delayed(Duration(milliseconds: 300));
|
||||||
|
checkPaymentStatus(AppoitmentAllHistoryResultList());
|
||||||
|
},
|
||||||
|
projectId: selectedHospital!.iD,
|
||||||
|
serviceTypeEnum: ServiceTypeEnum.advancePayment,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
GifLoaderDialogUtils.hideDialog(context);
|
||||||
|
AppToast.showErrorToast(message: "An error occurred while processing your request");
|
||||||
|
}
|
||||||
|
}).catchError((err) {
|
||||||
|
print(err);
|
||||||
|
GifLoaderDialogUtils.hideDialog(context);
|
||||||
|
AppToast.showErrorToast(message: err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openPayment(String paymentMethod, AuthenticatedUser authenticatedUser, num amount, AppoitmentAllHistoryResultList appo) {
|
||||||
|
browser = new MyInAppBrowser(onExitCallback: onBrowserExit, appo: appo, onLoadStartCallback: onBrowserLoadStart);
|
||||||
|
|
||||||
|
transID = Utils.getAdvancePaymentTransID(selectedHospital!.iD, projectViewModel.authenticatedUserObject.user.patientID!);
|
||||||
|
|
||||||
|
browser.openPaymentBrowser(
|
||||||
|
amount,
|
||||||
|
"Advance Payment",
|
||||||
|
transID,
|
||||||
|
selectedHospital!.iD.toString(),
|
||||||
|
projectViewModel.authenticatedUserObject.user.emailAddress!,
|
||||||
|
paymentMethod,
|
||||||
|
projectViewModel.authenticatedUserObject.user.patientType,
|
||||||
|
"${projectViewModel.authenticatedUserObject.user.firstName} ${projectViewModel.authenticatedUserObject.user.lastName}",
|
||||||
|
projectViewModel.authenticatedUserObject.user.patientID,
|
||||||
|
authenticatedUser,
|
||||||
|
browser,
|
||||||
|
false,
|
||||||
|
"3",
|
||||||
|
"0",
|
||||||
|
context,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
3);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBrowserLoadStart(String url) {
|
||||||
|
print("onBrowserLoadStart");
|
||||||
|
print(url);
|
||||||
|
|
||||||
|
if (selectedPaymentMethod == "TAMARA") {
|
||||||
|
Uri uri = new Uri.dataFromString(url);
|
||||||
|
tamaraPaymentStatus = uri.queryParameters['paymentStatus']!;
|
||||||
|
tamaraOrderID = uri.queryParameters['orderId']!;
|
||||||
|
print(tamaraPaymentStatus);
|
||||||
|
print(tamaraOrderID);
|
||||||
|
}
|
||||||
|
|
||||||
|
MyInAppBrowser.successURLS.forEach((element) {
|
||||||
|
if (url.contains(element)) {
|
||||||
|
if (browser.isOpened()) browser.close();
|
||||||
|
MyInAppBrowser.isPaymentDone = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MyInAppBrowser.errorURLS.forEach((element) {
|
||||||
|
if (url.contains(element)) {
|
||||||
|
if (browser.isOpened()) browser.close();
|
||||||
|
MyInAppBrowser.isPaymentDone = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBrowserExit(AppoitmentAllHistoryResultList appo, bool isPaymentMade) {
|
||||||
|
print("onBrowserExit Called");
|
||||||
|
if (selectedPaymentMethod == "TAMARA" && tamaraPaymentStatus != null && tamaraPaymentStatus == "approved") {
|
||||||
|
var res = {
|
||||||
|
"Amount": projectViewModel.hisProjectOffers.first.amountWithTax!,
|
||||||
|
"ErrorMessage": null,
|
||||||
|
"Fort_id": tamaraOrderID,
|
||||||
|
"Merchant_Reference": "5058637919318707883366",
|
||||||
|
"PaymentMethod": "TAMARA",
|
||||||
|
"Response_Message": "Success"
|
||||||
|
};
|
||||||
|
purchaseOfferAPICall(res);
|
||||||
|
} else {
|
||||||
|
checkPaymentStatus(appo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPaymentStatus(AppoitmentAllHistoryResultList appo) {
|
||||||
|
String txn_ref;
|
||||||
|
String amount;
|
||||||
|
String payment_method;
|
||||||
|
final currency = projectViewModel.user!.outSA == 0 ? "sar" : 'aed';
|
||||||
|
GifLoaderDialogUtils.showMyDialog(context);
|
||||||
|
DoctorsListService service = new DoctorsListService();
|
||||||
|
service.checkPaymentStatus(transID, false, context).then((res) {
|
||||||
|
String paymentInfo = res['Response_Message'];
|
||||||
|
if (paymentInfo == 'Success') {
|
||||||
|
txn_ref = res['Merchant_Reference'];
|
||||||
|
amount = res['Amount'].toString();
|
||||||
|
payment_method = res['PaymentMethod'];
|
||||||
|
purchaseOfferAPICall(res);
|
||||||
|
} else {
|
||||||
|
GifLoaderDialogUtils.hideDialog(AppGlobal.context);
|
||||||
|
AppToast.showErrorToast(message: res['Response_Message']);
|
||||||
|
amount = projectViewModel.hisProjectOffers.first.amountWithTax!.toString();
|
||||||
|
payment_method = selectedPaymentMethod;
|
||||||
|
}
|
||||||
|
}).catchError((err) {
|
||||||
|
GifLoaderDialogUtils.hideDialog(AppGlobal.context);
|
||||||
|
AppToast.showErrorToast(message: err);
|
||||||
|
print(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
purchaseOfferAPICall(res) {
|
||||||
|
DoctorsListService service = new DoctorsListService();
|
||||||
|
String paymentReference = res['Fort_id'].toString();
|
||||||
|
service.purchaseOfferAPICall(transID, selectedHospital!.iD!).then((res) {
|
||||||
|
GifLoaderDialogUtils.hideDialog(AppGlobal.context);
|
||||||
|
if (res['MessageStatus'] == 1) {
|
||||||
|
// AppToast.showSuccessToast(message: "Your payment has been made successfully");
|
||||||
|
// Navigator.pop(context, true);
|
||||||
|
|
||||||
|
AlertDialogBox(
|
||||||
|
context: context,
|
||||||
|
confirmMessage: projectViewModel.isArabic
|
||||||
|
? "تمت عملية الدفع بنجاح. بإمكانك زيارة ${selectedHospital!.nameN!} والإستفاده من العرض. نتمنى لك الصحة والعافية"
|
||||||
|
: "Your payment has been successfully received. Please visit ${selectedHospital!.name!} to avail the offer. Wishing you good health & wellness.",
|
||||||
|
okText: TranslationBase.of(context).ok,
|
||||||
|
okFunction: () {
|
||||||
|
AlertDialogBox.closeAlertDialog(context);
|
||||||
|
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => LandingPage()), (Route<dynamic> r) => false);
|
||||||
|
}).showAlertDialog(context);
|
||||||
|
} else {
|
||||||
|
AppToast.showErrorToast(message: res['Message'] ?? "");
|
||||||
|
}
|
||||||
|
}).catchError((err) {
|
||||||
|
GifLoaderDialogUtils.hideDialog(AppGlobal.context);
|
||||||
|
AppToast.showErrorToast(message: err);
|
||||||
|
print(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openHospitalSelection(BuildContext context) {
|
||||||
|
var projectViewModel = Provider.of<ProjectViewModel>(context, listen: false);
|
||||||
|
int _selectedHospitalIndex = 0;
|
||||||
|
List<HospitalsModel> projectsListLocal = [];
|
||||||
|
|
||||||
|
projectViewModel.hisProjectOffers.forEach((project) {
|
||||||
|
projectsListLocal.add(new HospitalsModel(
|
||||||
|
iD: project.projectId,
|
||||||
|
name: project.projectEnglish,
|
||||||
|
nameN: project.projectArabic,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (cxt) => LocationSelectionDialog(
|
||||||
|
isArabic: projectViewModel.isArabic,
|
||||||
|
data: projectsListLocal,
|
||||||
|
title: TranslationBase.of(context).selectHospital,
|
||||||
|
selectedIndex: _selectedHospitalIndex,
|
||||||
|
onValueSelected: (index) {
|
||||||
|
_selectedHospitalIndex = index;
|
||||||
|
setState(() {
|
||||||
|
selectedHospital = projectsListLocal[index];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import 'package:hmg_patient_app/core/viewModels/medical/labs_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app/pages/base/base_view.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/lab_result_graph.dart';
|
||||||
|
import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FullScreenGraph extends StatelessWidget {
|
||||||
|
final List<DataPoint> completeeGraphValues;
|
||||||
|
|
||||||
|
final List<ThresholdRange> threshold;
|
||||||
|
|
||||||
|
final double maxY;
|
||||||
|
|
||||||
|
final String appBarTitle;
|
||||||
|
|
||||||
|
const FullScreenGraph({super.key, required this.completeeGraphValues, required this.threshold, required this.maxY, required this.appBarTitle});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
isShowAppBar: true,
|
||||||
|
// appBarTitle: TranslationBase.of(context).labResult,
|
||||||
|
appBarTitle: appBarTitle,
|
||||||
|
showNewAppBar: true,
|
||||||
|
showNewAppBarTitle: true,
|
||||||
|
showHomeAppBarIcon: false,
|
||||||
|
backgroundColor: Color(0xffF8F8F8),
|
||||||
|
body: RotatedBox(
|
||||||
|
quarterTurns: 1,
|
||||||
|
child: SizedBox(
|
||||||
|
// width: MediaQuery.sizeOf(context).height,
|
||||||
|
height: MediaQuery.sizeOf(context).width,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.white,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DynamicResultChart(
|
||||||
|
dataPoints: completeeGraphValues,
|
||||||
|
thresholds: threshold,
|
||||||
|
maxY: maxY,
|
||||||
|
width: ((completeeGraphValues.length <= 2)
|
||||||
|
? MediaQuery.sizeOf(context).height
|
||||||
|
: (MediaQuery.sizeOf(context).height * (completeeGraphValues.length <= 15 ? 1 : (completeeGraphValues.length / 15)))) -
|
||||||
|
100,
|
||||||
|
maxX: completeeGraphValues.length + 1,
|
||||||
|
// width: MediaQuery.sizeOf(context).height - 100,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
height: MediaQuery.sizeOf(context).width,
|
||||||
|
showBottomTitleDates: true,
|
||||||
|
isFullScreeGraph: true,
|
||||||
|
// isFullScreenGraph: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// double getMax(List<DataPoint> dataPoints) {
|
||||||
|
// double max = double.negativeInfinity;
|
||||||
|
// for (var point in dataPoints) {
|
||||||
|
// if (point.value > max) {
|
||||||
|
// max = point.y;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return max;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,337 @@
|
|||||||
|
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class DynamicResultChart extends StatelessWidget {
|
||||||
|
final List<DataPoint> dataPoints;
|
||||||
|
final List<ThresholdRange> thresholds;
|
||||||
|
final double? width;
|
||||||
|
final double height;
|
||||||
|
final double? maxY;
|
||||||
|
final double? maxX;
|
||||||
|
|
||||||
|
final Axis scrollDirection;
|
||||||
|
final bool showBottomTitleDates;
|
||||||
|
final bool isFullScreeGraph;
|
||||||
|
|
||||||
|
DynamicResultChart(
|
||||||
|
{super.key, required this.dataPoints, required this.thresholds, this.width, required this.scrollDirection, required this.height, this.maxY,this.maxX, this.showBottomTitleDates = true, this.isFullScreeGraph = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var minY = 0.0;
|
||||||
|
// var maxY = 0.0;
|
||||||
|
double interval = 20;
|
||||||
|
if((maxY??0)>10 &&(maxY??0)<=20) interval = 2;
|
||||||
|
else if((maxY??0)>5 &&(maxY??0)<=10) interval = 1;
|
||||||
|
else if((maxY??0)>=0 &&(maxY??0)<=5) interval = .4;
|
||||||
|
// else if((maxY??0)>=100) interval = 20;
|
||||||
|
|
||||||
|
// if (thresholds.isNotEmpty) {
|
||||||
|
// print("the thresholds are $thresholds");
|
||||||
|
// minY = thresholds.first.value;
|
||||||
|
// maxY = thresholds.last.value + 1;
|
||||||
|
// } else {
|
||||||
|
// return SizedBox.shrink();
|
||||||
|
// }
|
||||||
|
|
||||||
|
final spots = dataPoints.asMap().entries.map((entry) {
|
||||||
|
return FlSpot(entry.key.toDouble(), entry.value.value);
|
||||||
|
}).toList();
|
||||||
|
final widthPerPoint = 30.0; // Customize as needed
|
||||||
|
final chartWidth = (dataPoints.length + 1) * widthPerPoint;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.white,
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0, bottom: 8),
|
||||||
|
child: LineChart(
|
||||||
|
LineChartData(
|
||||||
|
minY: 0,
|
||||||
|
maxY: ((maxY?.ceilToDouble()??0.0)+interval ).floorToDouble(),
|
||||||
|
// minX: dataPoints.first.labelValue - 1,
|
||||||
|
maxX: maxX,
|
||||||
|
lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) {
|
||||||
|
if (touchedSpots.isEmpty) return [];
|
||||||
|
|
||||||
|
// Only show tooltip for the first touched spot, hide others
|
||||||
|
return touchedSpots.map((spot) {
|
||||||
|
if (spot == touchedSpots.first) {
|
||||||
|
final dataPoint = dataPoints[spot.x.toInt()];
|
||||||
|
|
||||||
|
if (dataPoint.isStringResource) {
|
||||||
|
return LineTooltipItem(
|
||||||
|
'${double.parse(dataPoint.actualValue).toStringAsFixed(2)}',
|
||||||
|
const TextStyle(color: Colors.white),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LineTooltipItem(
|
||||||
|
// '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
|
||||||
|
'${dataPoint.label} ${double.parse(dataPoint.actualValue).toStringAsFixed(2)}',
|
||||||
|
const TextStyle(color: Colors.white),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null; // hides the rest
|
||||||
|
}).toList();
|
||||||
|
})),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
|
||||||
|
sideTitles : SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 50,
|
||||||
|
interval: interval,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
// Skip the last (maxY) label
|
||||||
|
if (value == ((maxY?.ceilToDouble()??0.0)+interval ).floorToDouble()) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
value.toStringAsFixed(1),
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
),
|
||||||
|
// leftTitles: AxisTitles(
|
||||||
|
// sideTitles: SideTitles(
|
||||||
|
// showTitles: true,
|
||||||
|
// reservedSize: 77,
|
||||||
|
// interval: .1, // Let fl_chart handle it
|
||||||
|
// getTitlesWidget: (value, _) {
|
||||||
|
// // print("the value is ======== ${value}");
|
||||||
|
//
|
||||||
|
// // Compare with 2-decimal precision to avoid close duplicates
|
||||||
|
// final matchingThreshold = thresholds.firstWhere(
|
||||||
|
// (t) => t.value.toStringAsFixed(1) == value.toStringAsFixed(1),
|
||||||
|
// orElse: () => ThresholdRange(label: '', value: 0, color: Colors.transparent, lineColor: Colors.transparent),
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// var actualValue = (matchingThreshold.actualValue != null)
|
||||||
|
// ? "${TranslationBase.of(context).getTranslation(matchingThreshold.label)} (${matchingThreshold.actualValue})"
|
||||||
|
// : '${TranslationBase.of(context).getTranslation(matchingThreshold.label)}';
|
||||||
|
// if (matchingThreshold.label.isNotEmpty) {
|
||||||
|
// return Text(
|
||||||
|
// "${value.toStringAsFixed(0)}",
|
||||||
|
// style: const TextStyle(fontSize: 10),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return const SizedBox.shrink();
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
axisNameSize: 60,
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: showBottomTitleDates,
|
||||||
|
reservedSize: 50,
|
||||||
|
getTitlesWidget: (value, _) {
|
||||||
|
if (value.toInt() >= 0 && value.toInt() < dataPoints.length) {
|
||||||
|
var label = dataPoints[value.toInt()].label;
|
||||||
|
if (dataPoints[value.toInt()].isStringResource) {
|
||||||
|
label = TranslationBase.of(context).getTranslation(dataPoints[value.toInt()].label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
top: 8.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
interval: 1, // ensures 1:1 mapping with spots
|
||||||
|
),
|
||||||
|
),
|
||||||
|
topTitles: AxisTitles(),
|
||||||
|
rightTitles: AxisTitles(),
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(show: true,border: const Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Colors.grey,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
left: BorderSide(
|
||||||
|
color: Colors.grey,
|
||||||
|
width: .5
|
||||||
|
),
|
||||||
|
right:BorderSide.none,
|
||||||
|
top: BorderSide.none,
|
||||||
|
),),
|
||||||
|
lineBarsData: _buildColoredLineSegments(dataPoints, thresholds),
|
||||||
|
gridData: FlGridData(show: true, drawVerticalLine: false, getDrawingHorizontalLine:(value)=> FlLine(color: Colors.grey, strokeWidth: 0.5)),
|
||||||
|
// extraLinesData: ExtraLinesData(
|
||||||
|
// horizontalLines: [
|
||||||
|
// HorizontalLine(
|
||||||
|
// color: Colors.black,
|
||||||
|
// strokeWidth: 2,
|
||||||
|
// dashArray: [5, 5], // optional: dashed line
|
||||||
|
// label: HorizontalLineLabel(
|
||||||
|
// show: true,
|
||||||
|
// alignment: Alignment.centerRight,
|
||||||
|
// style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
||||||
|
// labelResolver: (line) => 'Threshold ${line.y.toInt()}',
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// rangeAnnotations: RangeAnnotations(
|
||||||
|
// horizontalRangeAnnotations: _buildRangeShades(thresholds),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints, List<ThresholdRange> thresholds) {
|
||||||
|
List<LineChartBarData> segments = [];
|
||||||
|
Color getColor(String value) {
|
||||||
|
for (int i = thresholds.length - 1; i >= 0; i--) {
|
||||||
|
if (value == thresholds[i].label) {
|
||||||
|
return thresholds[i].lineColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Colors.grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < dataPoints.length - 1; i++) {
|
||||||
|
final dp1 = dataPoints[i];
|
||||||
|
final dp2 = dataPoints[i + 1];
|
||||||
|
|
||||||
|
final spot1 = FlSpot(i.toDouble(), dp1.value);
|
||||||
|
final spot2 = FlSpot((i + 1).toDouble(), dp2.value);
|
||||||
|
|
||||||
|
final color1 = getColor(dp1.referenceRangeValue);
|
||||||
|
final color2 = getColor(dp2.referenceRangeValue);
|
||||||
|
|
||||||
|
segments.add(LineChartBarData(
|
||||||
|
spots: [spot1, spot2],
|
||||||
|
isCurved: true,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
isStrokeJoinRound: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [color1, color2],
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
),
|
||||||
|
// dotData: FlDotData(
|
||||||
|
// show: true,
|
||||||
|
// getDotPainter: (spot, _, __, index) {
|
||||||
|
// final value = spot.y;
|
||||||
|
// final color = getColor(value);
|
||||||
|
// return FlDotCirclePainter(
|
||||||
|
// radius: 4,
|
||||||
|
// color: color,
|
||||||
|
// strokeWidth: 1,
|
||||||
|
// strokeColor: Colors.white,
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add dot markers separately so they are on top
|
||||||
|
|
||||||
|
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
|
||||||
|
return FlSpot(entry.key.toDouble(), entry.value.value);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
var data = [
|
||||||
|
// ...segments
|
||||||
|
LineChartBarData(
|
||||||
|
spots: allSpots,
|
||||||
|
isCurved: true,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
isStrokeJoinRound: true,
|
||||||
|
barWidth: 2,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Color(0xFF5dc36b), Color(0xFF5dc36b)],
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
),
|
||||||
|
dotData: FlDotData(
|
||||||
|
show: true,
|
||||||
|
getDotPainter: (spot, _, __, index) {
|
||||||
|
final value = spot.y;
|
||||||
|
final color = Color(0xFF5dc36b);
|
||||||
|
return FlDotCirclePainter(
|
||||||
|
radius: 4,
|
||||||
|
color: color,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeColor: Colors.white,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
// var max = LineChartHelper.calculateMaxAxisValues(data).maxX;
|
||||||
|
// print("the maxX is the -------> $max");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HorizontalRangeAnnotation> _buildRangeShades(List<ThresholdRange> thresholds) {
|
||||||
|
List<HorizontalRangeAnnotation> ranges = [];
|
||||||
|
for (int i = 0; i < thresholds.length - 1; i++) {
|
||||||
|
ranges.add(HorizontalRangeAnnotation(
|
||||||
|
y1: thresholds[i].value,
|
||||||
|
y2: thresholds[i + 1].value,
|
||||||
|
color: thresholds[i].color,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<DataPoint> sampleData = [
|
||||||
|
DataPoint(value: 1.4, label: 'Jan 2024', date: DateTime(2024, 1, 1)),
|
||||||
|
DataPoint(value: 3.6, label: 'Feb 2024', date: DateTime(2024, 2, 1)),
|
||||||
|
DataPoint(value: 1.96, label: 'This result', date: DateTime(2024, 3, 1)),
|
||||||
|
];
|
||||||
|
final List<ThresholdRange> thresholdLevels = [
|
||||||
|
ThresholdRange(label: 'Critical Low', value: 1.4, color: Color(0xfff6e9e9), lineColor: Color(0xFFe9a2a4)),
|
||||||
|
ThresholdRange(label: 'Low', value: 3.6, color: Color(0xFFf2fbf5), lineColor: Color(0xFFeecd94)),
|
||||||
|
ThresholdRange(label: 'Normal', value: 5.96, color: Color(0xFFf2fbf5), lineColor: Color(0xFF5dc36b)),
|
||||||
|
ThresholdRange(label: 'High', value: 7.15, color: Color(0xfff6e9e9), lineColor: Color(0xFFeecd94)),
|
||||||
|
ThresholdRange(label: 'Critical High', value: 10.15, color: Color(0xfff6e9e9), lineColor: Color(0xFFe9a2a4)),
|
||||||
|
];
|
||||||
|
|
||||||
|
class DataPoint {
|
||||||
|
final double value;
|
||||||
|
double labelValue;
|
||||||
|
String label;
|
||||||
|
String actualValue;
|
||||||
|
final DateTime date;
|
||||||
|
bool isStringResource;
|
||||||
|
String referenceRangeValue;
|
||||||
|
|
||||||
|
DataPoint({required this.value, required this.label, required this.date, this.isStringResource = false, this.labelValue = 0.0, this.actualValue = "", this.referenceRangeValue= ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThresholdRange {
|
||||||
|
final String label;
|
||||||
|
final double value;
|
||||||
|
final Color color;
|
||||||
|
final Color lineColor;
|
||||||
|
final String? actualValue;
|
||||||
|
|
||||||
|
ThresholdRange({required this.label, required this.value, required this.color, required this.lineColor, this.actualValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ThresholdRange(label: $label, value: $value, color: ${color.value.toRadixString(16)}, lineColor: ${lineColor.value.toRadixString(16)})';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
import 'package:hmg_patient_app/core/model/labs/lab_result.dart';
|
||||||
|
import 'package:hmg_patient_app/core/model/labs/patient_lab_orders.dart';
|
||||||
|
import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app/theme/colors.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:readmore/readmore.dart';
|
||||||
|
|
||||||
|
import '../FlowChartPage.dart';
|
||||||
|
import 'labWidgets.dart';
|
||||||
|
|
||||||
|
class LabItem extends StatefulWidget {
|
||||||
|
final LabResultList item;
|
||||||
|
PatientLabOrders? patientLabOrders;
|
||||||
|
|
||||||
|
LabItem({super.key, required this.item, this.patientLabOrders});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LabItem> createState() => _LabItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LabItemState extends State<LabItem> {
|
||||||
|
bool _isShowMoreGeneral = true;
|
||||||
|
bool showBottomSheet = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (showBottomSheet) {
|
||||||
|
print('the bottom sheet is showing');
|
||||||
|
openFlowChart(context, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isShowMoreGeneral = !_isShowMoreGeneral;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.item.filterName ?? '',
|
||||||
|
style: TextStyle(fontSize: 21, fontWeight: FontWeight.bold, color: Color(0xff2B353E), letterSpacing: -0.64, height: 25 / 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
_isShowMoreGeneral ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
|
||||||
|
color: Color(0xff2B353E),
|
||||||
|
size: 26,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
ReadMoreText(
|
||||||
|
widget.item.description ?? '',
|
||||||
|
trimMode: TrimMode.Line,
|
||||||
|
trimLines: 3,
|
||||||
|
style: const TextStyle(color: Color(0xff575757), fontSize: 11),
|
||||||
|
colorClickableText: CustomColors.accentColor,
|
||||||
|
trimCollapsedText: '${TranslationBase.of(context).readMore}',
|
||||||
|
trimExpandedText: ' show less',
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
if (_isShowMoreGeneral)
|
||||||
|
...List.generate(widget.item.patientLabResultList?.length ?? 0, (index) {
|
||||||
|
var data = widget.item.patientLabResultList?[index];
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ItemResultCardWidgetWithParams(
|
||||||
|
title: data?.description ?? '',
|
||||||
|
subTitle: data?.resultValue ?? '',
|
||||||
|
referenceRange: data?.referanceRange ?? '',
|
||||||
|
percentage: data?.percentage ?? 0.0,
|
||||||
|
note: data?.notes ?? '',
|
||||||
|
source: "",
|
||||||
|
buttonText: "",
|
||||||
|
type: data?.calculatedResultFlag?.getType() ?? ResultTypes.unknown,
|
||||||
|
shouldShowResultBarAndGraph: data?.shouldShowResultBarAndGraph() ?? true,
|
||||||
|
onButtonPressed: () {
|
||||||
|
openFlowChart(context, data?.description ?? '');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 13,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
})
|
||||||
|
// Expanded(
|
||||||
|
// child: ListView.builder(
|
||||||
|
// itemCount: widget.item.patientLabResultList?.length ?? 0,
|
||||||
|
// itemBuilder: (context, index) {
|
||||||
|
// var data = widget.item.patientLabResultList?[index];
|
||||||
|
// Column(
|
||||||
|
// children: [
|
||||||
|
// EllipsisTextWithMore(
|
||||||
|
// text: data?.packageShortDescription ?? '',
|
||||||
|
// ),
|
||||||
|
// SizedBox(
|
||||||
|
// height: 8,
|
||||||
|
// ),
|
||||||
|
// ItemResultCardWidgetWithParams(
|
||||||
|
// title: data?.description ?? '',
|
||||||
|
// subTitle: data?.resultValue ?? '',
|
||||||
|
// referenceRange: data?.referanceRange ?? '',
|
||||||
|
// percentage: 20,
|
||||||
|
// note: data?.testShortDescription ?? '',
|
||||||
|
// source: "",
|
||||||
|
// buttonText: "",
|
||||||
|
// type: data?.calculatedResultFlag?.getType() ??
|
||||||
|
// ResultTypes.unknown),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
openFlowChart(BuildContext context, String procedure) {
|
||||||
|
print('openFlowChart: the bottom sheet is showing');
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
scrollControlDisabledMaxHeightRatio: .75,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20))),
|
||||||
|
builder: (context) {
|
||||||
|
return FlowChartPage(
|
||||||
|
filterName: procedure,
|
||||||
|
patientLabOrder: widget.patientLabOrders,
|
||||||
|
);
|
||||||
|
}).then((value) {
|
||||||
|
setState(() {
|
||||||
|
showBottomSheet = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,551 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:hmg_patient_app/app_state/app_state.dart';
|
||||||
|
import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
|
||||||
|
import 'package:hmg_patient_app/uitl/utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
///@author
|
||||||
|
///
|
||||||
|
enum ResultTypes { lowCriticalLow, criticalLow, low, normal, high, criticalHigh, highCriticalHigh, IRR, unknown }
|
||||||
|
|
||||||
|
class ItemResultCardWidget extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
ItemResultCardWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 12),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomResultProgressBar extends StatelessWidget {
|
||||||
|
final num percentage;
|
||||||
|
final String value;
|
||||||
|
final ResultTypes type;
|
||||||
|
|
||||||
|
CustomResultProgressBar({Key? key, required this.percentage, required this.value, required this.type}) : super(key: key);
|
||||||
|
|
||||||
|
final GlobalKey lcl = GlobalKey();
|
||||||
|
|
||||||
|
final GlobalKey cl = GlobalKey();
|
||||||
|
|
||||||
|
final GlobalKey l = GlobalKey();
|
||||||
|
|
||||||
|
final GlobalKey n = GlobalKey();
|
||||||
|
|
||||||
|
final GlobalKey h = GlobalKey();
|
||||||
|
|
||||||
|
final GlobalKey ch = GlobalKey();
|
||||||
|
|
||||||
|
final GlobalKey hch = GlobalKey();
|
||||||
|
|
||||||
|
OverlayEntry? overlayEntry;
|
||||||
|
|
||||||
|
late ProjectViewModel projectViewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
projectViewModel = Provider.of(context);
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
final double totalWidth = constraints.maxWidth;
|
||||||
|
final double spacing = 2;
|
||||||
|
final int spacingCount = 4; // between 5 bars
|
||||||
|
final double flexWidth = totalWidth - 50;
|
||||||
|
|
||||||
|
final double tooltipPosition = (flexWidth * (percentage / 100)).clamp(0, flexWidth);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildResultBar(
|
||||||
|
key: cl,
|
||||||
|
flex: 3,
|
||||||
|
color: Color(0xFFDE7676),
|
||||||
|
title: TranslationBase.of(context).criticalLow,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
_buildResultBar(
|
||||||
|
key: l,
|
||||||
|
flex: 2,
|
||||||
|
color: Color(0xFFEAB157),
|
||||||
|
title: TranslationBase.of(context).low,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
_buildResultBar(
|
||||||
|
key: n,
|
||||||
|
flex: 5,
|
||||||
|
color: Color(0xFF09AA28),
|
||||||
|
title: TranslationBase.of(context).normal,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
_buildResultBar(
|
||||||
|
key: h,
|
||||||
|
flex: 2,
|
||||||
|
color: Color(0xFFEAB157),
|
||||||
|
title: TranslationBase.of(context).high,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
_buildResultBar(
|
||||||
|
key: ch,
|
||||||
|
flex: 3,
|
||||||
|
color: Color(0xFFDE7676),
|
||||||
|
title: TranslationBase.of(context).criticalHigh,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!percentage.isNegative)
|
||||||
|
projectViewModel.isArabic
|
||||||
|
? Positioned(
|
||||||
|
right: tooltipPosition,
|
||||||
|
top: 33,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextCloud(
|
||||||
|
text: "${value}",
|
||||||
|
color: getColorForResultType(type),
|
||||||
|
width: 75,
|
||||||
|
height: 20,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
axisDirection: AxisDirection.up,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Positioned(
|
||||||
|
left: tooltipPosition,
|
||||||
|
top: 33,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextCloud(
|
||||||
|
text: "${value}",
|
||||||
|
color: getColorForResultType(type),
|
||||||
|
width: 75,
|
||||||
|
height: 20,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
axisDirection: AxisDirection.up,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getColorForResultType(ResultTypes type) {
|
||||||
|
switch (type) {
|
||||||
|
case ResultTypes.lowCriticalLow:
|
||||||
|
case ResultTypes.highCriticalHigh:
|
||||||
|
case ResultTypes.criticalLow:
|
||||||
|
case ResultTypes.criticalHigh:
|
||||||
|
return Color(0xFFDE7676);
|
||||||
|
case ResultTypes.low:
|
||||||
|
case ResultTypes.high:
|
||||||
|
return Color(0xFFEAB157);
|
||||||
|
case ResultTypes.normal:
|
||||||
|
return Color(0xFF09AA28);
|
||||||
|
default:
|
||||||
|
return Color(0xFF09AA28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildResultBar({required int flex, required Color color, required String title, Key? key}) {
|
||||||
|
return Expanded(
|
||||||
|
key: key,
|
||||||
|
flex: flex,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 8,
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
letterSpacing: -0.3,
|
||||||
|
color: color,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Container(
|
||||||
|
height: 5,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextCloud extends StatelessWidget {
|
||||||
|
final String text; // write here box content
|
||||||
|
final Color color; // Set box Color
|
||||||
|
final EdgeInsets padding; // Set content padding
|
||||||
|
final double width; // Box width
|
||||||
|
final double height; // Box Height
|
||||||
|
final AxisDirection axisDirection; // Set triangle location up,left,right,down
|
||||||
|
final double locationOfArrow; // set between 0 and 1, If 0.5 is set triangle position will be centered
|
||||||
|
const TextCloud({
|
||||||
|
super.key,
|
||||||
|
required this.text,
|
||||||
|
this.color = Colors.white,
|
||||||
|
this.padding = const EdgeInsets.all(10),
|
||||||
|
this.width = 200,
|
||||||
|
this.height = 100,
|
||||||
|
this.axisDirection = AxisDirection.down,
|
||||||
|
this.locationOfArrow = 0.5,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Size arrowSize = const Size(12, 12);
|
||||||
|
return Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
padding: padding,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 9, letterSpacing: -.3),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Builder(builder: (context) {
|
||||||
|
double angle = 0;
|
||||||
|
switch (axisDirection) {
|
||||||
|
case AxisDirection.left:
|
||||||
|
angle = pi * -0.5;
|
||||||
|
break;
|
||||||
|
case AxisDirection.up:
|
||||||
|
angle = pi * -2;
|
||||||
|
break;
|
||||||
|
case AxisDirection.right:
|
||||||
|
angle = pi * 0.5;
|
||||||
|
break;
|
||||||
|
case AxisDirection.down:
|
||||||
|
angle = pi;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
angle = 0;
|
||||||
|
}
|
||||||
|
return Positioned(
|
||||||
|
left: axisDirection == AxisDirection.left
|
||||||
|
? -arrowSize.width + 5
|
||||||
|
: (axisDirection == AxisDirection.up || axisDirection == AxisDirection.down ? width * locationOfArrow - arrowSize.width / 2 : null),
|
||||||
|
right: axisDirection == AxisDirection.right ? -arrowSize.width + 5 : null,
|
||||||
|
top: axisDirection == AxisDirection.up
|
||||||
|
? -arrowSize.width + 5
|
||||||
|
: (axisDirection == AxisDirection.right || axisDirection == AxisDirection.left ? height * locationOfArrow - arrowSize.width / 2 : null),
|
||||||
|
bottom: axisDirection == AxisDirection.down ? -arrowSize.width + 5 : null,
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: angle,
|
||||||
|
child: CustomPaint(
|
||||||
|
size: arrowSize,
|
||||||
|
painter: ArrowPaint(color: color),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrowPaint extends CustomPainter {
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
ArrowPaint({required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Path path_0 = Path();
|
||||||
|
path_0.moveTo(size.width * 0.5745375, size.height * 0.06573708);
|
||||||
|
path_0.lineTo(size.width * 0.9813667, size.height * 0.8794000);
|
||||||
|
path_0.cubicTo(size.width * 1.001950, size.height * 0.9205625, size.width * 0.9852625, size.height * 0.9706208, size.width * 0.9441000, size.height * 0.9912000);
|
||||||
|
path_0.cubicTo(size.width * 0.9325250, size.height * 0.9969875, size.width * 0.9197667, size.height, size.width * 0.9068292, size.height);
|
||||||
|
path_0.lineTo(size.width * 0.09316958, size.height);
|
||||||
|
path_0.cubicTo(size.width * 0.04714583, size.height, size.width * 0.009836208, size.height * 0.9626917, size.width * 0.009836208, size.height * 0.9166667);
|
||||||
|
path_0.cubicTo(size.width * 0.009836208, size.height * 0.9037292, size.width * 0.01284829, size.height * 0.8909708, size.width * 0.01863392, size.height * 0.8794000);
|
||||||
|
path_0.lineTo(size.width * 0.4254625, size.height * 0.06573708);
|
||||||
|
path_0.cubicTo(size.width * 0.4460458, size.height * 0.02457225, size.width * 0.4961042, size.height * 0.007886875, size.width * 0.5372667, size.height * 0.02846929);
|
||||||
|
path_0.cubicTo(size.width * 0.5533958, size.height * 0.03653296, size.width * 0.5664708, size.height * 0.04961000, size.width * 0.5745375, size.height * 0.06573708);
|
||||||
|
path_0.close();
|
||||||
|
|
||||||
|
Paint paint_0_fill = Paint()..style = PaintingStyle.fill;
|
||||||
|
paint_0_fill.color = color;
|
||||||
|
canvas.drawPath(path_0, paint_0_fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ItemResultCardWidgetWithParams extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String subTitle;
|
||||||
|
final String note;
|
||||||
|
final String source;
|
||||||
|
final String referenceRange;
|
||||||
|
final num percentage;
|
||||||
|
final String buttonText;
|
||||||
|
final ResultTypes type;
|
||||||
|
final bool isArabic;
|
||||||
|
final Function()? onButtonPressed;
|
||||||
|
final bool shouldShowResultBarAndGraph;
|
||||||
|
|
||||||
|
const ItemResultCardWidgetWithParams({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.subTitle,
|
||||||
|
required this.referenceRange,
|
||||||
|
required this.percentage,
|
||||||
|
required this.note,
|
||||||
|
required this.source,
|
||||||
|
required this.buttonText,
|
||||||
|
required this.type,
|
||||||
|
this.onButtonPressed,
|
||||||
|
this.isArabic = false,
|
||||||
|
this.shouldShowResultBarAndGraph = true,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final fontFamily = isArabic ? 'Cairo' : 'Poppins';
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(top: 21),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||||
|
border: BorderDirectional(
|
||||||
|
start: BorderSide(
|
||||||
|
color: getColorForResultType(type),
|
||||||
|
width: MediaQuery.of(context).size.width * 0.015,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.2),
|
||||||
|
spreadRadius: 2,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
// Title Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(fontSize: 16, fontFamily: fontFamily, fontWeight: FontWeight.w600, color: const Color(0xff2E303A), letterSpacing: -.64),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// GestureDetector(
|
||||||
|
// onTap: onButtonPressed,
|
||||||
|
// child: Utils.tableColumnValueWithUnderLine(TranslationBase.of(context).viewFlowChart, isLast: true, isCapitable: false),
|
||||||
|
// ),
|
||||||
|
Visibility(
|
||||||
|
visible: true,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onButtonPressed,
|
||||||
|
child: Utils.tableColumnValueWithUnderLine(TranslationBase.of(context).viewFlowChart, isLast: true, isCapitable: false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ResultStatusWidget(type: type),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
subTitle,
|
||||||
|
style: TextStyle(fontSize: 14, fontFamily: fontFamily, fontWeight: FontWeight.w600, color: const Color(0xff2E303A), letterSpacing: -0.56),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${TranslationBase.of(context).referenceRange} \n $referenceRange",
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
fontSize: 10,
|
||||||
|
fontFamily: fontFamily,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: const Color(0xff2E303A),
|
||||||
|
letterSpacing: -0.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
// Progress Bar
|
||||||
|
Visibility(
|
||||||
|
visible: shouldShowResultBarAndGraph,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 55,
|
||||||
|
child: CustomResultProgressBar(
|
||||||
|
percentage: percentage,
|
||||||
|
value: subTitle,
|
||||||
|
type: type,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Note Section
|
||||||
|
if (note.isNotEmpty)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(color: Color(0xFFEFEFEF), thickness: 1),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
"${TranslationBase.of(context).notes}:",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: const Color(0xFF2E303A),
|
||||||
|
letterSpacing: -0.4,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
note,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: const Color(0xFF575757),
|
||||||
|
letterSpacing: -0.4,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getColorForResultType(ResultTypes type) {
|
||||||
|
switch (type) {
|
||||||
|
case ResultTypes.criticalLow:
|
||||||
|
case ResultTypes.criticalHigh:
|
||||||
|
case ResultTypes.lowCriticalLow:
|
||||||
|
case ResultTypes.highCriticalHigh:
|
||||||
|
return Color(0xFFDE7676);
|
||||||
|
case ResultTypes.low:
|
||||||
|
case ResultTypes.high:
|
||||||
|
return Color(0xFFEAB157);
|
||||||
|
case ResultTypes.normal:
|
||||||
|
return Color(0xFF09AA28);
|
||||||
|
//todo handle irr here
|
||||||
|
default:
|
||||||
|
return Color(0xFF09AA28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResultStatusWidget extends StatelessWidget {
|
||||||
|
final ResultTypes type;
|
||||||
|
|
||||||
|
const ResultStatusWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.type,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color? color;
|
||||||
|
IconData? icon;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ResultTypes.criticalLow:
|
||||||
|
case ResultTypes.lowCriticalLow:
|
||||||
|
color = const Color(0xFFDE7676);
|
||||||
|
icon = Icons.arrow_circle_down;
|
||||||
|
break;
|
||||||
|
case ResultTypes.low:
|
||||||
|
color = const Color(0xFFEAB157);
|
||||||
|
icon = Icons.arrow_circle_down;
|
||||||
|
break;
|
||||||
|
case ResultTypes.criticalHigh:
|
||||||
|
case ResultTypes.highCriticalHigh:
|
||||||
|
color = const Color(0xFFDE7676);
|
||||||
|
icon = Icons.arrow_circle_up;
|
||||||
|
break;
|
||||||
|
case ResultTypes.high:
|
||||||
|
color = const Color(0xFFEAB157);
|
||||||
|
icon = Icons.arrow_circle_up;
|
||||||
|
break;
|
||||||
|
case ResultTypes.normal:
|
||||||
|
color = const Color(0xFF09AA28);
|
||||||
|
icon = Icons.check_circle;
|
||||||
|
break;
|
||||||
|
case ResultTypes.unknown:
|
||||||
|
case ResultTypes.IRR:
|
||||||
|
color = Color(0xFF09AA28);
|
||||||
|
icon = Icons.check_circle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the icon if defined, otherwise a placeholder widget
|
||||||
|
return icon != null
|
||||||
|
? Icon(
|
||||||
|
icon,
|
||||||
|
color: color,
|
||||||
|
)
|
||||||
|
: const SizedBox(); // Use SizedBox to keep the layout consistent
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue