fix shaerd
parent
ed4f949053
commit
9292b38d5a
@ -0,0 +1,91 @@
|
|||||||
|
import '../patiant_info_model.dart';
|
||||||
|
|
||||||
|
class PatientProfileAppBarModel {
|
||||||
|
double? height;
|
||||||
|
bool? isInpatient;
|
||||||
|
bool? isDischargedPatient;
|
||||||
|
bool? isFromLiveCare;
|
||||||
|
PatiantInformtion? patient;
|
||||||
|
String? doctorName;
|
||||||
|
String? branch;
|
||||||
|
DateTime? appointmentDate;
|
||||||
|
String? profileUrl;
|
||||||
|
String? invoiceNO;
|
||||||
|
String? orderNo;
|
||||||
|
bool? isPrescriptions;
|
||||||
|
bool? isMedicalFile;
|
||||||
|
String? episode;
|
||||||
|
String? visitDate;
|
||||||
|
String? clinic;
|
||||||
|
bool? isAppointmentHeader;
|
||||||
|
bool? isFromLabResult;
|
||||||
|
Stream <String> ?videoCallDurationStream;
|
||||||
|
|
||||||
|
|
||||||
|
PatientProfileAppBarModel(
|
||||||
|
{this.height = 0.0,
|
||||||
|
this.isInpatient= false,
|
||||||
|
this.isDischargedPatient= false,
|
||||||
|
this.isFromLiveCare= false,
|
||||||
|
this.patient,
|
||||||
|
this.doctorName,
|
||||||
|
this.branch,
|
||||||
|
this.appointmentDate,
|
||||||
|
this.profileUrl,
|
||||||
|
this.invoiceNO,
|
||||||
|
this.orderNo,
|
||||||
|
this.isPrescriptions= false,
|
||||||
|
this.isMedicalFile= false,
|
||||||
|
this.episode,
|
||||||
|
this.visitDate,
|
||||||
|
this.clinic,
|
||||||
|
this.isAppointmentHeader = false,
|
||||||
|
this.isFromLabResult =false, this.videoCallDurationStream});
|
||||||
|
|
||||||
|
PatientProfileAppBarModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
height = json['height'];
|
||||||
|
isInpatient = json['isInpatient'];
|
||||||
|
isDischargedPatient = json['isDischargedPatient'];
|
||||||
|
isFromLiveCare = json['isFromLiveCare'];
|
||||||
|
patient = json['patient'];
|
||||||
|
doctorName = json['doctorName'];
|
||||||
|
branch = json['branch'];
|
||||||
|
appointmentDate = json['appointmentDate'];
|
||||||
|
profileUrl = json['profileUrl'];
|
||||||
|
invoiceNO = json['invoiceNO'];
|
||||||
|
orderNo = json['orderNo'];
|
||||||
|
isPrescriptions = json['isPrescriptions'];
|
||||||
|
isMedicalFile = json['isMedicalFile'];
|
||||||
|
episode = json['episode'];
|
||||||
|
visitDate = json['visitDate'];
|
||||||
|
clinic = json['clinic'];
|
||||||
|
isAppointmentHeader = json['isAppointmentHeader'];
|
||||||
|
isFromLabResult = json['isFromLabResult'];
|
||||||
|
videoCallDurationStream = json['videoCallDurationStream'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||||
|
data['height'] = this.height;
|
||||||
|
data['isInpatient'] = this.isInpatient;
|
||||||
|
data['isDischargedPatient'] = this.isDischargedPatient;
|
||||||
|
data['isFromLiveCare'] = this.isFromLiveCare;
|
||||||
|
data['patient'] = this.patient;
|
||||||
|
data['doctorName'] = this.doctorName;
|
||||||
|
data['branch'] = this.branch;
|
||||||
|
data['appointmentDate'] = this.appointmentDate;
|
||||||
|
data['profileUrl'] = this.profileUrl;
|
||||||
|
data['invoiceNO'] = this.invoiceNO;
|
||||||
|
data['orderNo'] = this.orderNo;
|
||||||
|
data['isPrescriptions'] = this.isPrescriptions;
|
||||||
|
data['isMedicalFile'] = this.isMedicalFile;
|
||||||
|
data['episode'] = this.episode;
|
||||||
|
data['visitDate'] = this.visitDate;
|
||||||
|
data['clinic'] = this.clinic;
|
||||||
|
data['isAppointmentHeader'] = this.isAppointmentHeader;
|
||||||
|
data['isFromLabResult'] = this.isFromLabResult;
|
||||||
|
data['videoCallDurationStream'] = this.videoCallDurationStream;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
|
|
||||||
import 'package:expandable/expandable.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AppExpandableNotifier extends StatelessWidget {
|
|
||||||
final Widget headerWid;
|
|
||||||
final Widget bodyWid;
|
|
||||||
|
|
||||||
AppExpandableNotifier({this.headerWid, this.bodyWid});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ExpandableNotifier(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Card(
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
child: headerWid,
|
|
||||||
),
|
|
||||||
ScrollOnExpand(
|
|
||||||
scrollOnExpand: true,
|
|
||||||
scrollOnCollapse: false,
|
|
||||||
child: ExpandablePanel(
|
|
||||||
theme: const ExpandableThemeData(
|
|
||||||
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
|
||||||
tapBodyToCollapse: true,
|
|
||||||
),
|
|
||||||
header: Padding(
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
child: Text(
|
|
||||||
"${TranslationBase.of(context).graphDetails}",
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
)),
|
|
||||||
collapsed: Text(''),
|
|
||||||
expanded: bodyWid,
|
|
||||||
builder: (_, collapsed, expanded) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),
|
|
||||||
child: Expandable(
|
|
||||||
collapsed: collapsed,
|
|
||||||
expanded: expanded,
|
|
||||||
theme: const ExpandableThemeData(crossFadePoint: 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
initialExpanded: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
import 'package:doctor_app_flutter/config/size_config.dart';
|
|
||||||
import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
|
|
||||||
import 'package:expandable/expandable.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
|
|
||||||
/// App Expandable Notifier with animation
|
|
||||||
/// [headerWidget] widget want to show in the header
|
|
||||||
/// [bodyWidget] widget want to show in the body
|
|
||||||
/// [title] the widget title
|
|
||||||
/// [collapsed] The widget shown in the collapsed state
|
|
||||||
class AppExpandableNotifier extends StatefulWidget {
|
|
||||||
final Widget headerWidget;
|
|
||||||
final Widget bodyWidget;
|
|
||||||
final String title;
|
|
||||||
final Widget collapsed;
|
|
||||||
final bool isExpand;
|
|
||||||
bool expandFlag = false;
|
|
||||||
var controller = new ExpandableController();
|
|
||||||
AppExpandableNotifier(
|
|
||||||
{this.headerWidget,
|
|
||||||
this.bodyWidget,
|
|
||||||
this.title,
|
|
||||||
this.collapsed,
|
|
||||||
this.isExpand = false});
|
|
||||||
|
|
||||||
_AppExpandableNotifier createState() => _AppExpandableNotifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppExpandableNotifier extends State<AppExpandableNotifier> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
setState(() {
|
|
||||||
if (widget.isExpand) {
|
|
||||||
widget.expandFlag = widget.isExpand;
|
|
||||||
widget.controller.expanded = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
|
|
||||||
return ExpandableNotifier(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10, right: 10, top: 4),
|
|
||||||
child: Card(
|
|
||||||
color: Colors.grey[200],
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
child: widget.headerWidget,
|
|
||||||
),
|
|
||||||
ScrollOnExpand(
|
|
||||||
scrollOnExpand: true,
|
|
||||||
scrollOnCollapse: false,
|
|
||||||
child: ExpandablePanel(
|
|
||||||
hasIcon: false,
|
|
||||||
theme: const ExpandableThemeData(
|
|
||||||
headerAlignment: ExpandablePanelHeaderAlignment.center,
|
|
||||||
tapBodyToCollapse: true,
|
|
||||||
),
|
|
||||||
header: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
child: Text(
|
|
||||||
widget.title ?? TranslationBase.of(context).details,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: SizeConfig.textMultiplier * 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: new Container(
|
|
||||||
height: 28.0,
|
|
||||||
width: 30.0,
|
|
||||||
decoration: new BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: new Center(
|
|
||||||
child: new Icon(
|
|
||||||
widget.expandFlag
|
|
||||||
? Icons.keyboard_arrow_up
|
|
||||||
: Icons.keyboard_arrow_down,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 30.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
widget.expandFlag = !widget.expandFlag;
|
|
||||||
widget.controller.expanded = widget.expandFlag;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
collapsed: widget.collapsed ?? Container(),
|
|
||||||
expanded: widget.bodyWidget,
|
|
||||||
builder: (_, collapsed, expanded) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(left: 5, right: 5, bottom: 5),
|
|
||||||
child: Expandable(
|
|
||||||
controller: widget.controller,
|
|
||||||
collapsed: collapsed,
|
|
||||||
expanded: expanded,
|
|
||||||
theme: const ExpandableThemeData(crossFadePoint: 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import 'package:charts_flutter/flutter.dart' as charts;
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/*
|
|
||||||
*@author: Elham Rababah
|
|
||||||
*@Date:03/6/2020
|
|
||||||
*@param:
|
|
||||||
*@return:
|
|
||||||
*@desc: AppLineChart
|
|
||||||
*/
|
|
||||||
class AppLineChart extends StatelessWidget {
|
|
||||||
const AppLineChart({
|
|
||||||
Key ? key,
|
|
||||||
@required this.seriesList,
|
|
||||||
this.chartTitle,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final List<charts.Series> seriesList;
|
|
||||||
|
|
||||||
final String chartTitle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
'Body Mass Index',
|
|
||||||
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: charts.LineChart(seriesList,
|
|
||||||
defaultRenderer: new charts.LineRendererConfig(
|
|
||||||
includeArea: false, stacked: true),
|
|
||||||
animate: true),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import 'package:charts_flutter/flutter.dart' as charts;
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../../config/size_config.dart';
|
|
||||||
import '../../../models/patient/vital_sign/vital_sign_res_model.dart';
|
|
||||||
import '../../../widgets/shared/rounded_container_widget.dart';
|
|
||||||
|
|
||||||
/*
|
|
||||||
*@author: Elham Rababah
|
|
||||||
*@Date:03/6/2020
|
|
||||||
*@param:
|
|
||||||
*@return:
|
|
||||||
*@desc: AppTimeSeriesChart
|
|
||||||
*/
|
|
||||||
class AppTimeSeriesChart extends StatelessWidget {
|
|
||||||
AppTimeSeriesChart(
|
|
||||||
{Key ? key,
|
|
||||||
@required this.vitalList,
|
|
||||||
@required this.viewKey,
|
|
||||||
this.chartName = ''});
|
|
||||||
|
|
||||||
final List<VitalSignResModel> vitalList;
|
|
||||||
final String chartName;
|
|
||||||
final String viewKey;
|
|
||||||
List<charts.Series> seriesList;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
seriesList = generateData();
|
|
||||||
return RoundedContainer(
|
|
||||||
height: SizeConfig.realScreenHeight * 0.47,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
chartName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: SizeConfig.textMultiplier * 3),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: SizeConfig.realScreenHeight * 0.37,
|
|
||||||
child: Center(
|
|
||||||
child: Container(
|
|
||||||
child: charts.TimeSeriesChart(
|
|
||||||
seriesList,
|
|
||||||
animate: true,
|
|
||||||
behaviors: [
|
|
||||||
new charts.RangeAnnotation(
|
|
||||||
[
|
|
||||||
new charts.RangeAnnotationSegment(
|
|
||||||
DateTime(
|
|
||||||
vitalList[vitalList.length - 1]
|
|
||||||
.vitalSignDate
|
|
||||||
.year,
|
|
||||||
vitalList[vitalList.length - 1]
|
|
||||||
.vitalSignDate
|
|
||||||
.month +
|
|
||||||
3,
|
|
||||||
vitalList[vitalList.length - 1]
|
|
||||||
.vitalSignDate
|
|
||||||
.day),
|
|
||||||
vitalList[0].vitalSignDate,
|
|
||||||
charts.RangeAnnotationAxisType.domain),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*@author: Elham Rababah
|
|
||||||
*@Date:03/6/2020
|
|
||||||
*@param:
|
|
||||||
*@return:
|
|
||||||
*@desc: generateData
|
|
||||||
*/
|
|
||||||
generateData() {
|
|
||||||
final List<TimeSeriesSales> data = [];
|
|
||||||
if (vitalList.length > 0) {
|
|
||||||
vitalList.forEach(
|
|
||||||
(element) {
|
|
||||||
data.add(
|
|
||||||
TimeSeriesSales(
|
|
||||||
new DateTime(element.vitalSignDate.year,
|
|
||||||
element.vitalSignDate.month, element.vitalSignDate.day),
|
|
||||||
element.toJson()[viewKey].toInt(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
new charts.Series<TimeSeriesSales, DateTime>(
|
|
||||||
id: 'Sales',
|
|
||||||
domainFn: (TimeSeriesSales sales, _) => sales.time,
|
|
||||||
measureFn: (TimeSeriesSales sales, _) => sales.sales,
|
|
||||||
data: data,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*@author: Elham Rababah
|
|
||||||
*@Date:03/6/2020
|
|
||||||
*@param:
|
|
||||||
*@return:
|
|
||||||
*@desc: TimeSeriesSales
|
|
||||||
*/
|
|
||||||
class TimeSeriesSales {
|
|
||||||
final DateTime time;
|
|
||||||
final int sales;
|
|
||||||
|
|
||||||
TimeSeriesSales(this.time, this.sales);
|
|
||||||
}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ListSelectDialog extends StatefulWidget {
|
|
||||||
final List<dynamic> list;
|
|
||||||
final String attributeName;
|
|
||||||
final String attributeValueId;
|
|
||||||
final okText;
|
|
||||||
final Function(dynamic) okFunction;
|
|
||||||
dynamic selectedValue;
|
|
||||||
|
|
||||||
ListSelectDialog(
|
|
||||||
{@required this.list,
|
|
||||||
@required this.attributeName,
|
|
||||||
@required this.attributeValueId,
|
|
||||||
@required this.okText,
|
|
||||||
@required this.okFunction});
|
|
||||||
|
|
||||||
@override
|
|
||||||
_ListSelectDialogState createState() => _ListSelectDialogState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ListSelectDialogState extends State<ListSelectDialog> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
widget.selectedValue = widget.selectedValue ?? widget.list[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return showAlertDialog(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
showAlertDialog(BuildContext context) {
|
|
||||||
// set up the buttons
|
|
||||||
Widget cancelButton = FlatButton(
|
|
||||||
child: Text(TranslationBase.of(context).cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
});
|
|
||||||
Widget continueButton = FlatButton(
|
|
||||||
child: Text(this.widget.okText),
|
|
||||||
onPressed: () {
|
|
||||||
this.widget.okFunction(widget.selectedValue);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
});
|
|
||||||
// set up the AlertDialog
|
|
||||||
AlertDialog alert = AlertDialog(
|
|
||||||
// title: Text(widget.title),
|
|
||||||
content: createDialogList(),
|
|
||||||
actions: [
|
|
||||||
cancelButton,
|
|
||||||
continueButton,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
return alert;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget createDialogList() {
|
|
||||||
return Container(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.5,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
...widget.list
|
|
||||||
.map((item) => RadioListTile(
|
|
||||||
title: Text("${item[widget.attributeName].toString()}"),
|
|
||||||
groupValue: widget.selectedValue[widget.attributeValueId]
|
|
||||||
.toString(),
|
|
||||||
value: item[widget.attributeValueId].toString(),
|
|
||||||
activeColor: Colors.blue.shade700,
|
|
||||||
selected: item[widget.attributeValueId].toString() ==
|
|
||||||
widget.selectedValue[widget.attributeValueId]
|
|
||||||
.toString(),
|
|
||||||
onChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
widget.selectedValue = item;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.toList()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static closeAlertDialog(BuildContext context) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020, Simform Solutions
|
|
||||||
* All rights reserved.
|
|
||||||
* https://github.com/simformsolutions/flutter_showcaseview
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Customized By: Ibrahim Albitar
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Displays an overlay Widget anchored directly above the center of this
|
|
||||||
/// [AnchoredOverlay].
|
|
||||||
///
|
|
||||||
/// The overlay Widget is created by invoking the provided [overlayBuilder].
|
|
||||||
///
|
|
||||||
/// The [anchor] position is provided to the [overlayBuilder], but the builder
|
|
||||||
/// does not have to respect it. In other words, the [overlayBuilder] can
|
|
||||||
/// interpret the meaning of "anchor" however it wants - the overlay will not
|
|
||||||
/// be forced to be centered about the [anchor].
|
|
||||||
///
|
|
||||||
/// The overlay built by this [AnchoredOverlay] can be conditionally shown
|
|
||||||
/// and hidden by settings the [showOverlay] property to true or false.
|
|
||||||
///
|
|
||||||
/// The [overlayBuilder] is invoked every time this Widget is rebuilt.
|
|
||||||
///
|
|
||||||
class AnchoredOverlay extends StatelessWidget {
|
|
||||||
final bool showOverlay;
|
|
||||||
final Widget Function(BuildContext, Rect anchorBounds, Offset anchor)
|
|
||||||
overlayBuilder;
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
AnchoredOverlay({
|
|
||||||
key,
|
|
||||||
this.showOverlay = false,
|
|
||||||
this.overlayBuilder,
|
|
||||||
this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
return OverlayBuilder(
|
|
||||||
showOverlay: showOverlay,
|
|
||||||
overlayBuilder: (BuildContext overlayContext) {
|
|
||||||
// To calculate the "anchor" point we grab the render box of
|
|
||||||
// our parent Container and then we find the center of that box.
|
|
||||||
RenderBox box = context.findRenderObject() as RenderBox;
|
|
||||||
final topLeft =
|
|
||||||
box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
final bottomRight =
|
|
||||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
final Rect anchorBounds = Rect.fromLTRB(
|
|
||||||
topLeft.dx,
|
|
||||||
topLeft.dy,
|
|
||||||
bottomRight.dx,
|
|
||||||
bottomRight.dy,
|
|
||||||
);
|
|
||||||
final anchorCenter = box.size.center(topLeft);
|
|
||||||
return overlayBuilder(overlayContext, anchorBounds, anchorCenter);
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Displays an overlay Widget as constructed by the given [overlayBuilder].
|
|
||||||
//
|
|
||||||
// The overlay built by the [overlayBuilder] can be conditionally shown and hidden by settings the [showOverlay]
|
|
||||||
// property to true or false.
|
|
||||||
//
|
|
||||||
// The [overlayBuilder] is invoked every time this Widget is rebuilt.
|
|
||||||
//
|
|
||||||
// Implementation note: the reason we rebuild the overlay every time our state changes is because there doesn't seem
|
|
||||||
// to be any better way to invalidate the overlay itself than to invalidate this Widget.
|
|
||||||
// Remember, overlay Widgets exist in [OverlayEntry]s which are inaccessible to outside Widgets.
|
|
||||||
// But if a better approach is found then feel free to use it.
|
|
||||||
//
|
|
||||||
class OverlayBuilder extends StatefulWidget {
|
|
||||||
final bool showOverlay;
|
|
||||||
final Widget Function(BuildContext) overlayBuilder;
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
OverlayBuilder({
|
|
||||||
key,
|
|
||||||
this.showOverlay = false,
|
|
||||||
this.overlayBuilder,
|
|
||||||
this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_OverlayBuilderState createState() => _OverlayBuilderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _OverlayBuilderState extends State<OverlayBuilder> {
|
|
||||||
OverlayEntry _overlayEntry;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
if (widget.showOverlay) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => showOverlay());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(OverlayBuilder oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reassemble() {
|
|
||||||
super.reassemble();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (isShowingOverlay()) {
|
|
||||||
hideOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isShowingOverlay() => _overlayEntry != null;
|
|
||||||
|
|
||||||
void showOverlay() {
|
|
||||||
if (_overlayEntry == null) {
|
|
||||||
// Create the overlay.
|
|
||||||
_overlayEntry = OverlayEntry(
|
|
||||||
builder: widget.overlayBuilder,
|
|
||||||
);
|
|
||||||
addToOverlay(_overlayEntry);
|
|
||||||
} else {
|
|
||||||
// Rebuild overlay.
|
|
||||||
buildOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void addToOverlay(OverlayEntry overlayEntry) async {
|
|
||||||
Overlay.of(context).insert(overlayEntry);
|
|
||||||
final overlay = Overlay.of(context);
|
|
||||||
if (overlayEntry == null)
|
|
||||||
WidgetsBinding.instance
|
|
||||||
.addPostFrameCallback((_) => overlay.insert(overlayEntry));
|
|
||||||
}
|
|
||||||
|
|
||||||
void hideOverlay() {
|
|
||||||
if (_overlayEntry != null) {
|
|
||||||
_overlayEntry.remove();
|
|
||||||
_overlayEntry = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void syncWidgetAndOverlay() {
|
|
||||||
if (isShowingOverlay() && !widget.showOverlay) {
|
|
||||||
hideOverlay();
|
|
||||||
} else if (!isShowingOverlay() && widget.showOverlay) {
|
|
||||||
showOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void buildOverlay() async {
|
|
||||||
WidgetsBinding.instance
|
|
||||||
.addPostFrameCallback((_) => _overlayEntry?.markNeedsBuild());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
buildOverlay();
|
|
||||||
|
|
||||||
return widget.child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020, Simform Solutions
|
|
||||||
* All rights reserved.
|
|
||||||
* https://github.com/simformsolutions/flutter_showcaseview
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Customized By: Ibrahim Albitar
|
|
||||||
|
|
||||||
*/
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class GetPosition {
|
|
||||||
final GlobalKey key;
|
|
||||||
|
|
||||||
GetPosition({this.key});
|
|
||||||
|
|
||||||
Rect getRect() {
|
|
||||||
RenderBox box = key.currentContext.findRenderObject();
|
|
||||||
|
|
||||||
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
final bottomRight =
|
|
||||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
|
|
||||||
Rect rect = Rect.fromLTRB(
|
|
||||||
topLeft.dx,
|
|
||||||
topLeft.dy,
|
|
||||||
bottomRight.dx,
|
|
||||||
bottomRight.dy,
|
|
||||||
);
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
///Get the bottom position of the widget
|
|
||||||
double getBottom() {
|
|
||||||
RenderBox box = key.currentContext.findRenderObject();
|
|
||||||
final bottomRight =
|
|
||||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
return bottomRight.dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
///Get the top position of the widget
|
|
||||||
double getTop() {
|
|
||||||
RenderBox box = key.currentContext.findRenderObject();
|
|
||||||
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
return topLeft.dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
///Get the left position of the widget
|
|
||||||
double getLeft() {
|
|
||||||
RenderBox box = key.currentContext.findRenderObject();
|
|
||||||
final topLeft = box.size.topLeft(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
return topLeft.dx;
|
|
||||||
}
|
|
||||||
|
|
||||||
///Get the right position of the widget
|
|
||||||
double getRight() {
|
|
||||||
RenderBox box = key.currentContext.findRenderObject();
|
|
||||||
final bottomRight =
|
|
||||||
box.size.bottomRight(box.localToGlobal(const Offset(0.0, 0.0)));
|
|
||||||
return bottomRight.dx;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getHeight() {
|
|
||||||
return getBottom() - getTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
double getWidth() {
|
|
||||||
return getRight() - getLeft();
|
|
||||||
}
|
|
||||||
|
|
||||||
double getCenter() {
|
|
||||||
return (getLeft() + getRight()) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020, Simform Solutions
|
|
||||||
* All rights reserved.
|
|
||||||
* https://github.com/simformsolutions/flutter_showcaseview
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Customized By: Ibrahim Albitar
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ShapePainter extends CustomPainter {
|
|
||||||
Rect rect;
|
|
||||||
final ShapeBorder shapeBorder;
|
|
||||||
final Color color;
|
|
||||||
final double opacity;
|
|
||||||
|
|
||||||
ShapePainter({
|
|
||||||
@required this.rect,
|
|
||||||
this.color,
|
|
||||||
this.shapeBorder,
|
|
||||||
this.opacity,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint = Paint();
|
|
||||||
paint.color = color.withOpacity(opacity);
|
|
||||||
RRect outer =
|
|
||||||
RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(0));
|
|
||||||
|
|
||||||
double radius = shapeBorder == CircleBorder() ? 50 : 3;
|
|
||||||
|
|
||||||
RRect inner = RRect.fromRectAndRadius(rect, Radius.circular(radius));
|
|
||||||
canvas.drawDRRect(outer, inner, paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
@ -1,349 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020, Simform Solutions
|
|
||||||
* All rights reserved.
|
|
||||||
* https://github.com/simformsolutions/flutter_showcaseview
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Customized By: Ibrahim Albitar
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
|
|
||||||
import 'app_anchored_overlay_widget.dart';
|
|
||||||
import 'app_get_position.dart';
|
|
||||||
import 'app_shape_painter.dart';
|
|
||||||
import 'app_showcase_widget.dart';
|
|
||||||
import 'app_tool_tip_widget.dart';
|
|
||||||
|
|
||||||
class AppShowcase extends StatefulWidget {
|
|
||||||
final Widget child;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final ShapeBorder shapeBorder;
|
|
||||||
final TextStyle titleTextStyle;
|
|
||||||
final TextStyle descTextStyle;
|
|
||||||
final GlobalKey key;
|
|
||||||
final Color overlayColor;
|
|
||||||
final double overlayOpacity;
|
|
||||||
final Widget container;
|
|
||||||
final Color showcaseBackgroundColor;
|
|
||||||
final Color textColor;
|
|
||||||
final bool showArrow;
|
|
||||||
final double height;
|
|
||||||
final double width;
|
|
||||||
final Duration animationDuration;
|
|
||||||
final VoidCallback onToolTipClick;
|
|
||||||
final VoidCallback onTargetClick;
|
|
||||||
final VoidCallback onSkipClick;
|
|
||||||
final bool disposeOnTap;
|
|
||||||
final bool disableAnimation;
|
|
||||||
|
|
||||||
const AppShowcase(
|
|
||||||
{@required this.key,
|
|
||||||
@required this.child,
|
|
||||||
this.title,
|
|
||||||
@required this.description,
|
|
||||||
this.shapeBorder,
|
|
||||||
this.overlayColor = Colors.black,
|
|
||||||
this.overlayOpacity = 0.75,
|
|
||||||
this.titleTextStyle,
|
|
||||||
this.descTextStyle,
|
|
||||||
this.showcaseBackgroundColor = Colors.white,
|
|
||||||
this.textColor = Colors.black,
|
|
||||||
this.showArrow = true,
|
|
||||||
this.onTargetClick,
|
|
||||||
this.onSkipClick,
|
|
||||||
this.disposeOnTap,
|
|
||||||
this.animationDuration = const Duration(milliseconds: 2000),
|
|
||||||
this.disableAnimation = false})
|
|
||||||
: height = null,
|
|
||||||
width = null,
|
|
||||||
container = null,
|
|
||||||
this.onToolTipClick = null,
|
|
||||||
assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0,
|
|
||||||
"overlay opacity should be >= 0.0 and <= 1.0."),
|
|
||||||
assert(
|
|
||||||
onTargetClick == null
|
|
||||||
? true
|
|
||||||
: (disposeOnTap == null ? false : true),
|
|
||||||
"disposeOnTap is required if you're using onTargetClick"),
|
|
||||||
assert(
|
|
||||||
disposeOnTap == null
|
|
||||||
? true
|
|
||||||
: (onTargetClick == null ? false : true),
|
|
||||||
"onTargetClick is required if you're using disposeOnTap"),
|
|
||||||
assert(key != null ||
|
|
||||||
child != null ||
|
|
||||||
title != null ||
|
|
||||||
showArrow != null ||
|
|
||||||
description != null ||
|
|
||||||
shapeBorder != null ||
|
|
||||||
overlayColor != null ||
|
|
||||||
titleTextStyle != null ||
|
|
||||||
descTextStyle != null ||
|
|
||||||
showcaseBackgroundColor != null ||
|
|
||||||
textColor != null ||
|
|
||||||
shapeBorder != null ||
|
|
||||||
animationDuration != null);
|
|
||||||
|
|
||||||
const AppShowcase.withWidget(
|
|
||||||
{this.key,
|
|
||||||
@required this.child,
|
|
||||||
@required this.container,
|
|
||||||
@required this.height,
|
|
||||||
@required this.width,
|
|
||||||
this.title,
|
|
||||||
this.description,
|
|
||||||
this.shapeBorder,
|
|
||||||
this.overlayColor = Colors.black,
|
|
||||||
this.overlayOpacity = 0.75,
|
|
||||||
this.titleTextStyle,
|
|
||||||
this.descTextStyle,
|
|
||||||
this.showcaseBackgroundColor = Colors.white,
|
|
||||||
this.textColor = Colors.black,
|
|
||||||
this.onTargetClick,
|
|
||||||
this.onSkipClick,
|
|
||||||
this.disposeOnTap,
|
|
||||||
this.animationDuration = const Duration(milliseconds: 2000),
|
|
||||||
this.disableAnimation = false})
|
|
||||||
: this.showArrow = false,
|
|
||||||
this.onToolTipClick = null,
|
|
||||||
assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0,
|
|
||||||
"overlay opacity should be >= 0.0 and <= 1.0."),
|
|
||||||
assert(key != null ||
|
|
||||||
child != null ||
|
|
||||||
title != null ||
|
|
||||||
description != null ||
|
|
||||||
shapeBorder != null ||
|
|
||||||
overlayColor != null ||
|
|
||||||
titleTextStyle != null ||
|
|
||||||
descTextStyle != null ||
|
|
||||||
showcaseBackgroundColor != null ||
|
|
||||||
textColor != null ||
|
|
||||||
shapeBorder != null ||
|
|
||||||
animationDuration != null);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_AppShowcaseState createState() => _AppShowcaseState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppShowcaseState extends State<AppShowcase>
|
|
||||||
with TickerProviderStateMixin {
|
|
||||||
bool _showShowCase = false;
|
|
||||||
Animation<double> _slideAnimation;
|
|
||||||
AnimationController _slideAnimationController;
|
|
||||||
|
|
||||||
GetPosition position;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_slideAnimationController = AnimationController(
|
|
||||||
duration: widget.animationDuration,
|
|
||||||
vsync: this,
|
|
||||||
)..addStatusListener((AnimationStatus status) {
|
|
||||||
if (status == AnimationStatus.completed) {
|
|
||||||
_slideAnimationController.reverse();
|
|
||||||
}
|
|
||||||
if (_slideAnimationController.isDismissed) {
|
|
||||||
if (!widget.disableAnimation) {
|
|
||||||
_slideAnimationController.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_slideAnimation = CurvedAnimation(
|
|
||||||
parent: _slideAnimationController,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
|
|
||||||
position = GetPosition(key: widget.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_slideAnimationController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
showOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// show overlay if there is any target widget
|
|
||||||
///
|
|
||||||
void showOverlay() {
|
|
||||||
GlobalKey activeStep = ShowCaseWidget.activeTargetWidget(context);
|
|
||||||
setState(() {
|
|
||||||
_showShowCase = activeStep == widget.key;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (activeStep == widget.key) {
|
|
||||||
if (!widget.disableAnimation) {
|
|
||||||
_slideAnimationController.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Size size = MediaQuery.of(context).size;
|
|
||||||
return AnchoredOverlay(
|
|
||||||
overlayBuilder: (BuildContext context, Rect rectBound, Offset offset) =>
|
|
||||||
buildOverlayOnTarget(offset, rectBound.size, rectBound, size),
|
|
||||||
showOverlay: true,
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_nextIfAny() {
|
|
||||||
ShowCaseWidget.of(context).completed(widget.key);
|
|
||||||
if (!widget.disableAnimation) {
|
|
||||||
_slideAnimationController.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getOnTargetTap() {
|
|
||||||
if (widget.disposeOnTap == true) {
|
|
||||||
return widget.onTargetClick == null
|
|
||||||
? () {
|
|
||||||
ShowCaseWidget.of(context).dismiss();
|
|
||||||
}
|
|
||||||
: () {
|
|
||||||
ShowCaseWidget.of(context).dismiss();
|
|
||||||
widget.onTargetClick();
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return widget.onTargetClick ?? _nextIfAny;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getOnTooltipTap() {
|
|
||||||
if (widget.disposeOnTap == true) {
|
|
||||||
return widget.onToolTipClick == null
|
|
||||||
? () {
|
|
||||||
ShowCaseWidget.of(context).dismiss();
|
|
||||||
}
|
|
||||||
: () {
|
|
||||||
ShowCaseWidget.of(context).dismiss();
|
|
||||||
widget.onToolTipClick();
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return widget.onToolTipClick ?? () {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildOverlayOnTarget(
|
|
||||||
Offset offset,
|
|
||||||
Size size,
|
|
||||||
Rect rectBound,
|
|
||||||
Size screenSize,
|
|
||||||
) =>
|
|
||||||
Visibility(
|
|
||||||
visible: _showShowCase,
|
|
||||||
maintainAnimation: true,
|
|
||||||
maintainState: true,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: _nextIfAny,
|
|
||||||
child: Container(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
child: CustomPaint(
|
|
||||||
painter: ShapePainter(
|
|
||||||
opacity: widget.overlayOpacity,
|
|
||||||
rect: position.getRect(),
|
|
||||||
shapeBorder: widget.shapeBorder,
|
|
||||||
color: widget.overlayColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_TargetWidget(
|
|
||||||
offset: offset,
|
|
||||||
size: size,
|
|
||||||
onTap: _getOnTargetTap(),
|
|
||||||
shapeBorder: widget.shapeBorder,
|
|
||||||
),
|
|
||||||
AppToolTipWidget(
|
|
||||||
position: position,
|
|
||||||
offset: offset,
|
|
||||||
screenSize: screenSize,
|
|
||||||
title: widget.title,
|
|
||||||
description: widget.description,
|
|
||||||
animationOffset: _slideAnimation,
|
|
||||||
titleTextStyle: widget.titleTextStyle,
|
|
||||||
descTextStyle: widget.descTextStyle,
|
|
||||||
container: widget.container,
|
|
||||||
tooltipColor: widget.showcaseBackgroundColor,
|
|
||||||
textColor: widget.textColor,
|
|
||||||
showArrow: widget.showArrow,
|
|
||||||
contentHeight: widget.height,
|
|
||||||
contentWidth: widget.width,
|
|
||||||
onTooltipTap: _getOnTooltipTap(),
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
child: AppText(
|
|
||||||
"Skip",
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 20,
|
|
||||||
marginRight: 15,
|
|
||||||
marginLeft: 15,
|
|
||||||
marginTop: 15,
|
|
||||||
),
|
|
||||||
onTap: widget.onSkipClick)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TargetWidget extends StatelessWidget {
|
|
||||||
final Offset offset;
|
|
||||||
final Size size;
|
|
||||||
final Animation<double> widthAnimation;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
final ShapeBorder shapeBorder;
|
|
||||||
|
|
||||||
_TargetWidget({
|
|
||||||
Key ? key,
|
|
||||||
@required this.offset,
|
|
||||||
this.size,
|
|
||||||
this.widthAnimation,
|
|
||||||
this.onTap,
|
|
||||||
this.shapeBorder,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Positioned(
|
|
||||||
top: offset.dy,
|
|
||||||
left: offset.dx,
|
|
||||||
child: FractionalTranslation(
|
|
||||||
translation: const Offset(-0.5, -0.5),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Container(
|
|
||||||
height: size.height + 16,
|
|
||||||
width: size.width + 16,
|
|
||||||
decoration: ShapeDecoration(
|
|
||||||
shape: shapeBorder ??
|
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020, Simform Solutions
|
|
||||||
* All rights reserved.
|
|
||||||
* https://github.com/simformsolutions/flutter_showcaseview
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Customized By: Ibrahim Albitar
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ShowCaseWidget extends StatefulWidget {
|
|
||||||
final Builder builder;
|
|
||||||
final VoidCallback onFinish;
|
|
||||||
|
|
||||||
const ShowCaseWidget({@required this.builder, this.onFinish});
|
|
||||||
|
|
||||||
static activeTargetWidget(BuildContext context) {
|
|
||||||
return context
|
|
||||||
.dependOnInheritedWidgetOfExactType<_InheritedShowCaseView>()
|
|
||||||
.activeWidgetIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ShowCaseWidgetState of(BuildContext context) {
|
|
||||||
ShowCaseWidgetState state =
|
|
||||||
context.findAncestorStateOfType<ShowCaseWidgetState>();
|
|
||||||
if (state != null) {
|
|
||||||
return context.findAncestorStateOfType<ShowCaseWidgetState>();
|
|
||||||
} else {
|
|
||||||
throw Exception('Please provide ShowCaseView context');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
ShowCaseWidgetState createState() => ShowCaseWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowCaseWidgetState extends State<ShowCaseWidget> {
|
|
||||||
List<GlobalKey> ids;
|
|
||||||
int activeWidgetId;
|
|
||||||
|
|
||||||
void startShowCase(List<GlobalKey> widgetIds) {
|
|
||||||
setState(() {
|
|
||||||
this.ids = widgetIds;
|
|
||||||
activeWidgetId = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void completed(GlobalKey id) {
|
|
||||||
if (ids != null && ids[activeWidgetId] == id) {
|
|
||||||
setState(() {
|
|
||||||
++activeWidgetId;
|
|
||||||
|
|
||||||
if (activeWidgetId >= ids.length) {
|
|
||||||
_cleanupAfterSteps();
|
|
||||||
if (widget.onFinish != null) {
|
|
||||||
widget.onFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dismiss() {
|
|
||||||
setState(() {
|
|
||||||
_cleanupAfterSteps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _cleanupAfterSteps() {
|
|
||||||
ids = null;
|
|
||||||
activeWidgetId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _InheritedShowCaseView(
|
|
||||||
child: widget.builder,
|
|
||||||
activeWidgetIds: ids?.elementAt(activeWidgetId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InheritedShowCaseView extends InheritedWidget {
|
|
||||||
final GlobalKey activeWidgetIds;
|
|
||||||
|
|
||||||
_InheritedShowCaseView({
|
|
||||||
@required this.activeWidgetIds,
|
|
||||||
@required child,
|
|
||||||
}) : super(child: child);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool updateShouldNotify(_InheritedShowCaseView oldWidget) =>
|
|
||||||
oldWidget.activeWidgetIds != activeWidgetIds;
|
|
||||||
}
|
|
||||||
@ -1,290 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020, Simform Solutions
|
|
||||||
* All rights reserved.
|
|
||||||
* https://github.com/simformsolutions/flutter_showcaseview
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Customized By: Ibrahim Albitar
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'package:doctor_app_flutter/icons_app/doctor_app_icons.dart';
|
|
||||||
import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'app_get_position.dart';
|
|
||||||
|
|
||||||
class AppToolTipWidget extends StatelessWidget {
|
|
||||||
final GetPosition position;
|
|
||||||
final Offset offset;
|
|
||||||
final Size screenSize;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final Animation<double> animationOffset;
|
|
||||||
final TextStyle titleTextStyle;
|
|
||||||
final TextStyle descTextStyle;
|
|
||||||
final Widget container;
|
|
||||||
final Color tooltipColor;
|
|
||||||
final Color textColor;
|
|
||||||
final bool showArrow;
|
|
||||||
final double contentHeight;
|
|
||||||
final double contentWidth;
|
|
||||||
static bool isArrowUp;
|
|
||||||
final VoidCallback onTooltipTap;
|
|
||||||
|
|
||||||
AppToolTipWidget({
|
|
||||||
this.position,
|
|
||||||
this.offset,
|
|
||||||
this.screenSize,
|
|
||||||
this.title,
|
|
||||||
this.description,
|
|
||||||
this.animationOffset,
|
|
||||||
this.titleTextStyle,
|
|
||||||
this.descTextStyle,
|
|
||||||
this.container,
|
|
||||||
this.tooltipColor,
|
|
||||||
this.textColor,
|
|
||||||
this.showArrow,
|
|
||||||
this.contentHeight,
|
|
||||||
this.contentWidth,
|
|
||||||
this.onTooltipTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
bool isCloseToTopOrBottom(Offset position) {
|
|
||||||
double height = 120;
|
|
||||||
if (contentHeight != null) {
|
|
||||||
height = contentHeight;
|
|
||||||
}
|
|
||||||
return (screenSize.height - position.dy) <= height;
|
|
||||||
}
|
|
||||||
|
|
||||||
String findPositionForContent(Offset position) {
|
|
||||||
if (isCloseToTopOrBottom(position)) {
|
|
||||||
return 'ABOVE';
|
|
||||||
} else {
|
|
||||||
return 'BELOW';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _getTooltipWidth() {
|
|
||||||
double titleLength = title == null ? 0 : (title.length * 10.0);
|
|
||||||
double descriptionLength = (description.length * 7.0);
|
|
||||||
if (titleLength > descriptionLength) {
|
|
||||||
return titleLength + 10;
|
|
||||||
} else {
|
|
||||||
return descriptionLength + 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isLeft() {
|
|
||||||
double screenWidth = screenSize.width / 3;
|
|
||||||
return !(screenWidth <= position.getCenter());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isRight() {
|
|
||||||
double screenWidth = screenSize.width / 3;
|
|
||||||
return ((screenWidth * 2) <= position.getCenter());
|
|
||||||
}
|
|
||||||
|
|
||||||
double _getLeft() {
|
|
||||||
if (_isLeft()) {
|
|
||||||
double leftPadding = position.getCenter() - (_getTooltipWidth() * 0.1);
|
|
||||||
if (leftPadding + _getTooltipWidth() > screenSize.width) {
|
|
||||||
leftPadding = (screenSize.width - 20) - _getTooltipWidth();
|
|
||||||
}
|
|
||||||
if (leftPadding < 20) {
|
|
||||||
leftPadding = 14;
|
|
||||||
}
|
|
||||||
return leftPadding;
|
|
||||||
} else if (!(_isRight())) {
|
|
||||||
return position.getCenter() - (_getTooltipWidth() * 0.5);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _getRight() {
|
|
||||||
if (_isRight()) {
|
|
||||||
double rightPadding = position.getCenter() + (_getTooltipWidth() / 2);
|
|
||||||
if (rightPadding + _getTooltipWidth() > screenSize.width) {
|
|
||||||
rightPadding = 14;
|
|
||||||
}
|
|
||||||
return rightPadding;
|
|
||||||
} else if (!(_isLeft())) {
|
|
||||||
return position.getCenter() - (_getTooltipWidth() * 0.5);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _getSpace() {
|
|
||||||
double space = position.getCenter() - (contentWidth / 2);
|
|
||||||
if (space + contentWidth > screenSize.width) {
|
|
||||||
space = screenSize.width - contentWidth - 8;
|
|
||||||
} else if (space < (contentWidth / 2)) {
|
|
||||||
space = 16;
|
|
||||||
}
|
|
||||||
return space;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final contentOrientation = findPositionForContent(offset);
|
|
||||||
final contentOffsetMultiplier = contentOrientation == "BELOW" ? 1.0 : -1.0;
|
|
||||||
isArrowUp = contentOffsetMultiplier == 1.0 ? true : false;
|
|
||||||
|
|
||||||
final contentY = isArrowUp
|
|
||||||
? position.getBottom() + (contentOffsetMultiplier * 3)
|
|
||||||
: position.getTop() + (contentOffsetMultiplier * 3);
|
|
||||||
|
|
||||||
final contentFractionalOffset = contentOffsetMultiplier.clamp(-1.0, 0.0);
|
|
||||||
|
|
||||||
double paddingTop = isArrowUp ? 22 : 0;
|
|
||||||
double paddingBottom = isArrowUp ? 0 : 27;
|
|
||||||
|
|
||||||
if (!showArrow) {
|
|
||||||
paddingTop = 10;
|
|
||||||
paddingBottom = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container == null) {
|
|
||||||
return Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
showArrow ? _getArrow(contentOffsetMultiplier) : Container(),
|
|
||||||
Positioned(
|
|
||||||
top: contentY,
|
|
||||||
left: _getLeft(),
|
|
||||||
right: _getRight(),
|
|
||||||
child: FractionalTranslation(
|
|
||||||
translation: Offset(0.0, contentFractionalOffset),
|
|
||||||
child: SlideTransition(
|
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: Offset(0.0, contentFractionalOffset / 10),
|
|
||||||
end: Offset(0.0, 0.100),
|
|
||||||
).animate(animationOffset),
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: Container(
|
|
||||||
padding:
|
|
||||||
EdgeInsets.only(top: paddingTop, bottom: paddingBottom),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: onTooltipTap,
|
|
||||||
child: Container(
|
|
||||||
width: _getTooltipWidth(),
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 8),
|
|
||||||
color: tooltipColor,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: title != null
|
|
||||||
? CrossAxisAlignment.start
|
|
||||||
: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
title != null
|
|
||||||
? Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.all(8.0),
|
|
||||||
child: Icon(
|
|
||||||
DoctorApp.search_patient),
|
|
||||||
),
|
|
||||||
AppText(
|
|
||||||
title,
|
|
||||||
color: textColor,
|
|
||||||
margin: 2,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Container(),
|
|
||||||
AppText(
|
|
||||||
description,
|
|
||||||
color: textColor,
|
|
||||||
margin: 8,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
Positioned(
|
|
||||||
left: _getSpace(),
|
|
||||||
top: contentY - 10,
|
|
||||||
child: FractionalTranslation(
|
|
||||||
translation: Offset(0.0, contentFractionalOffset),
|
|
||||||
child: SlideTransition(
|
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: Offset(0.0, contentFractionalOffset / 5),
|
|
||||||
end: Offset(0.0, 0.100),
|
|
||||||
).animate(animationOffset),
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: onTooltipTap,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: paddingTop,
|
|
||||||
),
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: Center(
|
|
||||||
child: container,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _getArrow(contentOffsetMultiplier) {
|
|
||||||
final contentFractionalOffset = contentOffsetMultiplier.clamp(-1.0, 0.0);
|
|
||||||
return Positioned(
|
|
||||||
top: isArrowUp ? position.getBottom() : position.getTop() - 1,
|
|
||||||
left: position.getCenter() - 24,
|
|
||||||
child: FractionalTranslation(
|
|
||||||
translation: Offset(0.0, contentFractionalOffset),
|
|
||||||
child: SlideTransition(
|
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: Offset(0.0, contentFractionalOffset / 5),
|
|
||||||
end: Offset(0.0, 0.150),
|
|
||||||
).animate(animationOffset),
|
|
||||||
child: isArrowUp
|
|
||||||
? Icon(
|
|
||||||
Icons.arrow_drop_up,
|
|
||||||
color: tooltipColor,
|
|
||||||
size: 50,
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
Icons.arrow_drop_down,
|
|
||||||
color: tooltipColor,
|
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue