|
|
|
|
@ -13,28 +13,30 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
|
|
|
|
|
|
|
|
|
|
import '../../core/dependencies.dart';
|
|
|
|
|
|
|
|
|
|
class CollapsingListView extends StatelessWidget {
|
|
|
|
|
class CollapsingListView extends StatefulWidget {
|
|
|
|
|
final String title;
|
|
|
|
|
Widget? child;
|
|
|
|
|
VoidCallback? search;
|
|
|
|
|
VoidCallback? report;
|
|
|
|
|
VoidCallback? logout;
|
|
|
|
|
VoidCallback? aiOverview;
|
|
|
|
|
VoidCallback? history;
|
|
|
|
|
VoidCallback? instructions;
|
|
|
|
|
VoidCallback? requests;
|
|
|
|
|
VoidCallback? sendEmail;
|
|
|
|
|
VoidCallback? doctorResponse;
|
|
|
|
|
VoidCallback? downloadReport;
|
|
|
|
|
VoidCallback? viewImage;
|
|
|
|
|
VoidCallback? location;
|
|
|
|
|
VoidCallback? downloadInvoice;
|
|
|
|
|
Widget? bottomChild;
|
|
|
|
|
Widget? trailing;
|
|
|
|
|
bool isClose;
|
|
|
|
|
bool isLeading;
|
|
|
|
|
VoidCallback? leadingCallback;
|
|
|
|
|
ScrollPhysics? physics;
|
|
|
|
|
final Widget? child;
|
|
|
|
|
final VoidCallback? search;
|
|
|
|
|
final VoidCallback? report;
|
|
|
|
|
final VoidCallback? logout;
|
|
|
|
|
final VoidCallback? aiOverview;
|
|
|
|
|
final VoidCallback? history;
|
|
|
|
|
final VoidCallback? instructions;
|
|
|
|
|
final VoidCallback? requests;
|
|
|
|
|
final VoidCallback? sendEmail;
|
|
|
|
|
final VoidCallback? doctorResponse;
|
|
|
|
|
final VoidCallback? downloadReport;
|
|
|
|
|
final VoidCallback? viewImage;
|
|
|
|
|
final VoidCallback? location;
|
|
|
|
|
final VoidCallback? downloadInvoice;
|
|
|
|
|
final Widget? bottomChild;
|
|
|
|
|
final Widget? trailing;
|
|
|
|
|
final bool isClose;
|
|
|
|
|
final bool isLeading;
|
|
|
|
|
final VoidCallback? leadingCallback;
|
|
|
|
|
final ScrollPhysics? physics;
|
|
|
|
|
final Widget? stickyWidget;
|
|
|
|
|
final double? stickyWidgetHeight;
|
|
|
|
|
|
|
|
|
|
CollapsingListView({
|
|
|
|
|
super.key,
|
|
|
|
|
@ -59,9 +61,28 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
this.trailing,
|
|
|
|
|
this.leadingCallback,
|
|
|
|
|
this.physics,
|
|
|
|
|
this.stickyWidget,
|
|
|
|
|
this.stickyWidgetHeight,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final ScrollController _controller = ScrollController();
|
|
|
|
|
@override
|
|
|
|
|
State<CollapsingListView> createState() => _CollapsingListViewState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _CollapsingListViewState extends State<CollapsingListView> {
|
|
|
|
|
late final ScrollController _controller;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_controller = ScrollController();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_controller.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
@ -71,82 +92,66 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
body: Column(
|
|
|
|
|
children: [
|
|
|
|
|
CustomScrollView(
|
|
|
|
|
physics: physics,
|
|
|
|
|
physics: widget.physics,
|
|
|
|
|
controller: _controller,
|
|
|
|
|
slivers: [
|
|
|
|
|
SliverAppBar(
|
|
|
|
|
automaticallyImplyLeading: isLeading,
|
|
|
|
|
pinned: true,
|
|
|
|
|
toolbarHeight: 40.h,
|
|
|
|
|
leadingWidth: isLeading ? 40.w + 32.w : double.infinity,
|
|
|
|
|
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light),
|
|
|
|
|
surfaceTintColor: Colors.transparent,
|
|
|
|
|
backgroundColor: AppColors.bgScaffoldColor,
|
|
|
|
|
bottom:
|
|
|
|
|
// isLeading
|
|
|
|
|
// ?
|
|
|
|
|
ScrollAnimatedTitle(
|
|
|
|
|
title: title,
|
|
|
|
|
showBack: true,
|
|
|
|
|
controller: _controller,
|
|
|
|
|
search: search,
|
|
|
|
|
report: report,
|
|
|
|
|
logout: logout,
|
|
|
|
|
history: history,
|
|
|
|
|
instructions: instructions,
|
|
|
|
|
requests: requests,
|
|
|
|
|
sendEmail: sendEmail,
|
|
|
|
|
doctorResponse: doctorResponse,
|
|
|
|
|
downloadReport: downloadReport,
|
|
|
|
|
viewImage: viewImage,
|
|
|
|
|
location: location,
|
|
|
|
|
downloadInvoice: downloadInvoice,
|
|
|
|
|
bottomChild: bottomChild,
|
|
|
|
|
trailing: trailing,
|
|
|
|
|
aiOverview: aiOverview,
|
|
|
|
|
),
|
|
|
|
|
// : null,
|
|
|
|
|
leading: Visibility(
|
|
|
|
|
visible: isLeading,
|
|
|
|
|
child: Container(
|
|
|
|
|
height: 32.h,
|
|
|
|
|
width: 32.w,
|
|
|
|
|
alignment: appState.isArabic() ? Alignment.centerRight : Alignment.centerLeft,
|
|
|
|
|
margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
|
|
|
|
|
child: Transform.flip(
|
|
|
|
|
flipX: appState.isArabic(),
|
|
|
|
|
child: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h),
|
|
|
|
|
),
|
|
|
|
|
).onPress(() {
|
|
|
|
|
if (leadingCallback != null) {
|
|
|
|
|
leadingCallback!();
|
|
|
|
|
} else {
|
|
|
|
|
context.pop();
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
// : ScrollAnimatedTitle(
|
|
|
|
|
// title: title,
|
|
|
|
|
// showBack: false,
|
|
|
|
|
// controller: _controller,
|
|
|
|
|
// search: search,
|
|
|
|
|
// report: report,
|
|
|
|
|
// logout: logout,
|
|
|
|
|
// history: history,
|
|
|
|
|
// instructions: instructions,
|
|
|
|
|
// requests: requests,
|
|
|
|
|
// sendEmail: sendEmail,
|
|
|
|
|
// bottomChild: bottomChild,
|
|
|
|
|
// trailing: trailing,
|
|
|
|
|
// ),
|
|
|
|
|
),
|
|
|
|
|
if (child != null)
|
|
|
|
|
automaticallyImplyLeading: widget.isLeading,
|
|
|
|
|
pinned: true,
|
|
|
|
|
toolbarHeight: 40.h,
|
|
|
|
|
leadingWidth: widget.isLeading ? 40.w + 32.w : double.infinity,
|
|
|
|
|
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light),
|
|
|
|
|
surfaceTintColor: Colors.transparent,
|
|
|
|
|
backgroundColor: AppColors.bgScaffoldColor,
|
|
|
|
|
bottom: ScrollAnimatedTitle(
|
|
|
|
|
title: widget.title,
|
|
|
|
|
showBack: true,
|
|
|
|
|
controller: _controller,
|
|
|
|
|
search: widget.search,
|
|
|
|
|
report: widget.report,
|
|
|
|
|
logout: widget.logout,
|
|
|
|
|
history: widget.history,
|
|
|
|
|
instructions: widget.instructions,
|
|
|
|
|
requests: widget.requests,
|
|
|
|
|
sendEmail: widget.sendEmail,
|
|
|
|
|
doctorResponse: widget.doctorResponse,
|
|
|
|
|
downloadReport: widget.downloadReport,
|
|
|
|
|
viewImage: widget.viewImage,
|
|
|
|
|
location: widget.location,
|
|
|
|
|
downloadInvoice: widget.downloadInvoice,
|
|
|
|
|
bottomChild: widget.bottomChild,
|
|
|
|
|
trailing: widget.trailing,
|
|
|
|
|
aiOverview: widget.aiOverview,
|
|
|
|
|
stickyWidget: widget.stickyWidget,
|
|
|
|
|
stickyWidgetHeight: widget.stickyWidgetHeight,
|
|
|
|
|
),
|
|
|
|
|
leading: Visibility(
|
|
|
|
|
visible: widget.isLeading,
|
|
|
|
|
child: Container(
|
|
|
|
|
height: 32.h,
|
|
|
|
|
width: 32.w,
|
|
|
|
|
alignment: appState.isArabic() ? Alignment.centerRight : Alignment.centerLeft,
|
|
|
|
|
margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
|
|
|
|
|
child: Transform.flip(
|
|
|
|
|
flipX: appState.isArabic(),
|
|
|
|
|
child: Utils.buildSvgWithAssets(icon: widget.isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h),
|
|
|
|
|
),
|
|
|
|
|
).onPress(() {
|
|
|
|
|
if (widget.leadingCallback != null) {
|
|
|
|
|
widget.leadingCallback!();
|
|
|
|
|
} else {
|
|
|
|
|
context.pop();
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
if (widget.child != null)
|
|
|
|
|
SliverToBoxAdapter(
|
|
|
|
|
child: child!,
|
|
|
|
|
child: widget.child!,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
).expanded,
|
|
|
|
|
if (bottomChild != null) bottomChild!
|
|
|
|
|
if (widget.bottomChild != null) widget.bottomChild!
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
@ -216,6 +221,8 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
|
|
|
|
|
VoidCallback? downloadInvoice;
|
|
|
|
|
Widget? bottomChild;
|
|
|
|
|
Widget? trailing;
|
|
|
|
|
Widget? stickyWidget;
|
|
|
|
|
double? stickyWidgetHeight;
|
|
|
|
|
|
|
|
|
|
ScrollAnimatedTitle({
|
|
|
|
|
super.key,
|
|
|
|
|
@ -237,10 +244,12 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget
|
|
|
|
|
this.downloadInvoice,
|
|
|
|
|
this.bottomChild,
|
|
|
|
|
this.trailing,
|
|
|
|
|
this.stickyWidget,
|
|
|
|
|
this.stickyWidgetHeight,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Size get preferredSize => const Size.fromHeight(56.0);
|
|
|
|
|
Size get preferredSize => Size.fromHeight(56.0 + (stickyWidget != null ? (stickyWidgetHeight ?? 58.h) : 0));
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<ScrollAnimatedTitle> createState() => _ScrollAnimatedTitleState();
|
|
|
|
|
@ -254,6 +263,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
|
|
|
|
|
static const double _minHeight = 56;
|
|
|
|
|
|
|
|
|
|
double _fontSize = _maxFont;
|
|
|
|
|
double t = 1.0;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
@ -261,33 +271,53 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
|
|
|
|
|
widget.controller.addListener(_onScroll);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void didUpdateWidget(covariant ScrollAnimatedTitle oldWidget) {
|
|
|
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
|
if (oldWidget.controller != widget.controller) {
|
|
|
|
|
oldWidget.controller.removeListener(_onScroll);
|
|
|
|
|
widget.controller.addListener(_onScroll);
|
|
|
|
|
_onScroll();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
widget.controller.removeListener(_onScroll);
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double t = 1.0;
|
|
|
|
|
|
|
|
|
|
void _onScroll() {
|
|
|
|
|
if (!widget.controller.hasClients) return;
|
|
|
|
|
final double offset = widget.controller.offset;
|
|
|
|
|
|
|
|
|
|
// control animation range
|
|
|
|
|
const double range = 120;
|
|
|
|
|
|
|
|
|
|
final double t = (1 - (offset / range)).clamp(0.0, 1.0);
|
|
|
|
|
this.t = t;
|
|
|
|
|
setState(() {
|
|
|
|
|
_fontSize = _minFont + (_maxFont - _minFont) * t;
|
|
|
|
|
});
|
|
|
|
|
final double newT = (1 - (offset / range)).clamp(0.0, 1.0);
|
|
|
|
|
if (newT != t) {
|
|
|
|
|
setState(() {
|
|
|
|
|
t = newT;
|
|
|
|
|
_fontSize = _minFont + (_maxFont - _minFont) * t;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
final isRtl = Directionality.of(context) == TextDirection.RTL;
|
|
|
|
|
return Container(
|
|
|
|
|
|
|
|
|
|
final double titleHeight = widget.stickyWidget != null
|
|
|
|
|
? (30.h + (44.h - 30.h) * t)
|
|
|
|
|
: 56.h;
|
|
|
|
|
|
|
|
|
|
final double topGap = widget.stickyWidget != null
|
|
|
|
|
? (2.h + (10.h - 2.h) * t)
|
|
|
|
|
: 0.0;
|
|
|
|
|
|
|
|
|
|
final titleWidget = Container(
|
|
|
|
|
// height: (widget.preferredSize.height - _fontSize / 2).h,
|
|
|
|
|
height: 56.h,
|
|
|
|
|
height: titleHeight,
|
|
|
|
|
alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft),
|
|
|
|
|
padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
|
|
|
|
|
child: Row(
|
|
|
|
|
@ -322,6 +352,24 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (widget.stickyWidget == null) {
|
|
|
|
|
return titleWidget;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Container(
|
|
|
|
|
color: AppColors.bgScaffoldColor,
|
|
|
|
|
alignment: Alignment.topCenter,
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
titleWidget,
|
|
|
|
|
SizedBox(height: topGap),
|
|
|
|
|
widget.stickyWidget!,
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget actionButton(BuildContext context, double t, {required String title, required String icon, bool isAiButton = false}) {
|
|
|
|
|
|