You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
539 lines
16 KiB
Dart
539 lines
16 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
|
|
import 'package:diplomaticquarterapp/uitl/utils.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
///@author
|
|
///
|
|
enum ResultTypes {
|
|
lowCriticalLow,
|
|
criticalLow,
|
|
low,
|
|
normal,
|
|
high,
|
|
criticalHigh,
|
|
highCriticalHigh,
|
|
IRR,
|
|
unknown
|
|
}
|
|
|
|
class ItemResultCardWidget extends StatelessWidget {
|
|
final Widget child;
|
|
|
|
const 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;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(builder: (context, constraints) {
|
|
final double totalWidth = constraints.maxWidth;
|
|
final double spacing = 2;
|
|
final double tooltipPosition = (totalWidth * (percentage / 100)).clamp(0, totalWidth - 50);
|
|
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)
|
|
Positioned(
|
|
left: tooltipPosition,
|
|
top: 33,
|
|
child: Column(
|
|
children: [
|
|
TextCloud(
|
|
text: "${value}",
|
|
color: getColorForResultType(type),
|
|
width: 100,
|
|
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;
|
|
|
|
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,
|
|
}) : 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),
|
|
),
|
|
],
|
|
),
|
|
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(
|
|
fontSize: 10,
|
|
fontFamily: fontFamily,
|
|
fontWeight: FontWeight.w600,
|
|
color: const Color(0xff2E303A),
|
|
letterSpacing: -0.4,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
// Progress Bar
|
|
Visibility(
|
|
visible: type != ResultTypes.IRR,
|
|
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
|
|
}
|
|
}
|