|
|
|
|
@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/app_state.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/context_extensions.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
|
|
|
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
|
|
|
|
@ -47,6 +48,8 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
this.physics,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
final ScrollController _controller = ScrollController();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
AppState appState = getIt.get<AppState>();
|
|
|
|
|
@ -56,20 +59,36 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
children: [
|
|
|
|
|
CustomScrollView(
|
|
|
|
|
physics: physics,
|
|
|
|
|
controller: _controller,
|
|
|
|
|
slivers: [
|
|
|
|
|
SliverAppBar(
|
|
|
|
|
automaticallyImplyLeading: false,
|
|
|
|
|
automaticallyImplyLeading: isLeading,
|
|
|
|
|
pinned: true,
|
|
|
|
|
expandedHeight: MediaQuery.of(context).size.height * 0.11.h,
|
|
|
|
|
stretch: true,
|
|
|
|
|
toolbarHeight: isLeading ? 24.h : kToolbarHeight,
|
|
|
|
|
leadingWidth: isLeading ? null : 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,
|
|
|
|
|
bottomChild: bottomChild,
|
|
|
|
|
trailing: trailing,
|
|
|
|
|
)
|
|
|
|
|
: null,
|
|
|
|
|
leading: isLeading
|
|
|
|
|
? Transform.flip(
|
|
|
|
|
flipX: appState.isArabic(),
|
|
|
|
|
child: IconButton(
|
|
|
|
|
icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h),
|
|
|
|
|
icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 24.h, height: 24.h),
|
|
|
|
|
padding: EdgeInsets.only(left: 12),
|
|
|
|
|
onPressed: () {
|
|
|
|
|
if (leadingCallback != null) {
|
|
|
|
|
@ -81,67 +100,18 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
highlightColor: Colors.transparent,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
: SizedBox.shrink(),
|
|
|
|
|
flexibleSpace: LayoutBuilder(
|
|
|
|
|
builder: (context, constraints) {
|
|
|
|
|
final double maxHeight = 100.h;
|
|
|
|
|
final double minHeight = kToolbarHeight;
|
|
|
|
|
double t = (constraints.maxHeight - minHeight) / (maxHeight - minHeight);
|
|
|
|
|
t = t - 1;
|
|
|
|
|
if (t < 0.7) t = 0.7;
|
|
|
|
|
t = t.clamp(0.0, 1.0);
|
|
|
|
|
|
|
|
|
|
final double fontSize = lerpDouble(14, 18, t)!;
|
|
|
|
|
final double bottomPadding = lerpDouble(0, 0, t)!;
|
|
|
|
|
final double leftPadding = lerpDouble(150, 24, t)!;
|
|
|
|
|
|
|
|
|
|
return Stack(
|
|
|
|
|
children: [
|
|
|
|
|
Align(
|
|
|
|
|
alignment: Alignment.lerp(
|
|
|
|
|
Alignment.center,
|
|
|
|
|
Alignment.bottomLeft,
|
|
|
|
|
t,
|
|
|
|
|
)!,
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.only(
|
|
|
|
|
left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding),
|
|
|
|
|
child: Row(
|
|
|
|
|
spacing: 4.h,
|
|
|
|
|
children: [
|
|
|
|
|
Text(
|
|
|
|
|
title,
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
fontSize: (27 - (5 * (2 - t))).f,
|
|
|
|
|
fontWeight: FontWeight.lerp(
|
|
|
|
|
FontWeight.w300,
|
|
|
|
|
FontWeight.w600,
|
|
|
|
|
t,
|
|
|
|
|
)!,
|
|
|
|
|
color: AppColors.blackColor,
|
|
|
|
|
letterSpacing: -0.5),
|
|
|
|
|
).expanded,
|
|
|
|
|
if (logout != null)
|
|
|
|
|
actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!),
|
|
|
|
|
if (report != null)
|
|
|
|
|
actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(report!),
|
|
|
|
|
if (history != null)
|
|
|
|
|
actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon)
|
|
|
|
|
.onPress(history!),
|
|
|
|
|
if (instructions != null)
|
|
|
|
|
actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!),
|
|
|
|
|
if (requests != null)
|
|
|
|
|
actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon)
|
|
|
|
|
.onPress(requests!),
|
|
|
|
|
if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24),
|
|
|
|
|
if (trailing != null) trailing!,
|
|
|
|
|
],
|
|
|
|
|
)),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
: ScrollAnimatedTitle(
|
|
|
|
|
title: title,
|
|
|
|
|
showBack: false,
|
|
|
|
|
controller: _controller,
|
|
|
|
|
search: search,
|
|
|
|
|
report: report,
|
|
|
|
|
logout: logout,
|
|
|
|
|
history: history,
|
|
|
|
|
instructions: instructions,
|
|
|
|
|
requests: requests,
|
|
|
|
|
bottomChild: bottomChild,
|
|
|
|
|
trailing: trailing,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SliverList(
|
|
|
|
|
@ -158,13 +128,161 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Widget actionButton(BuildContext context, double t, {required String title, required String icon}) {
|
|
|
|
|
// return AnimatedSize(
|
|
|
|
|
// duration: Duration(milliseconds: 150),
|
|
|
|
|
// child: Container(
|
|
|
|
|
// height: 40.h,
|
|
|
|
|
// padding: EdgeInsets.all(8.w),
|
|
|
|
|
// margin: EdgeInsets.only(right: 24.w),
|
|
|
|
|
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
|
|
|
// color: AppColors.secondaryLightRedColor,
|
|
|
|
|
// borderRadius: 10.r,
|
|
|
|
|
// ),
|
|
|
|
|
// child: Row(
|
|
|
|
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
// mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
// mainAxisSize: MainAxisSize.min,
|
|
|
|
|
// spacing: 8.h,
|
|
|
|
|
// children: [
|
|
|
|
|
// Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor),
|
|
|
|
|
// if (t == 1)
|
|
|
|
|
// Text(
|
|
|
|
|
// title,
|
|
|
|
|
// style: context.dynamicTextStyle(
|
|
|
|
|
// color: AppColors.primaryRedColor,
|
|
|
|
|
// letterSpacing: -0.4,
|
|
|
|
|
// fontSize: (12 - (2 * (1 - t))).f,
|
|
|
|
|
// fontWeight: FontWeight.lerp(
|
|
|
|
|
// FontWeight.w300,
|
|
|
|
|
// FontWeight.w500,
|
|
|
|
|
// t,
|
|
|
|
|
// )!,
|
|
|
|
|
// ),
|
|
|
|
|
// ),
|
|
|
|
|
// ],
|
|
|
|
|
// ),
|
|
|
|
|
// ),
|
|
|
|
|
// );
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
//
|
|
|
|
|
// class ActionModel {
|
|
|
|
|
// bool requireT;
|
|
|
|
|
//
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget {
|
|
|
|
|
final String title;
|
|
|
|
|
final bool showBack;
|
|
|
|
|
final ScrollController controller;
|
|
|
|
|
|
|
|
|
|
VoidCallback? search;
|
|
|
|
|
VoidCallback? report;
|
|
|
|
|
VoidCallback? logout;
|
|
|
|
|
VoidCallback? history;
|
|
|
|
|
VoidCallback? instructions;
|
|
|
|
|
VoidCallback? requests;
|
|
|
|
|
Widget? bottomChild;
|
|
|
|
|
Widget? trailing;
|
|
|
|
|
|
|
|
|
|
ScrollAnimatedTitle({
|
|
|
|
|
super.key,
|
|
|
|
|
required this.title,
|
|
|
|
|
required this.controller,
|
|
|
|
|
required this.showBack,
|
|
|
|
|
this.search,
|
|
|
|
|
this.report,
|
|
|
|
|
this.logout,
|
|
|
|
|
this.history,
|
|
|
|
|
this.instructions,
|
|
|
|
|
this.requests,
|
|
|
|
|
this.bottomChild,
|
|
|
|
|
this.trailing,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Size get preferredSize => const Size.fromHeight(50);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<ScrollAnimatedTitle> createState() => _ScrollAnimatedTitleState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
|
|
|
|
|
static const double _maxFont = 24;
|
|
|
|
|
static const double _minFont = 14;
|
|
|
|
|
|
|
|
|
|
static const double _maxHeight = 80;
|
|
|
|
|
static const double _minHeight = 56;
|
|
|
|
|
|
|
|
|
|
double _fontSize = _maxFont;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
widget.controller.addListener(_onScroll);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
widget.controller.removeListener(_onScroll);
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double t = 0;
|
|
|
|
|
|
|
|
|
|
void _onScroll() {
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
final isRtl = Directionality.of(context) == TextDirection.rtl;
|
|
|
|
|
return Container(
|
|
|
|
|
height: (widget.preferredSize.height - _fontSize / 2).h,
|
|
|
|
|
alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft),
|
|
|
|
|
padding: EdgeInsets.fromLTRB(24, 0, 24, 0),
|
|
|
|
|
child: Row(
|
|
|
|
|
spacing: 4.h,
|
|
|
|
|
children: [
|
|
|
|
|
Text(
|
|
|
|
|
widget.title,
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
fontSize: _fontSize,
|
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|
),
|
|
|
|
|
).expanded,
|
|
|
|
|
...[
|
|
|
|
|
if (widget.logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(widget.logout!),
|
|
|
|
|
if (widget.report != null) actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(widget.report!),
|
|
|
|
|
if (widget.history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.history!),
|
|
|
|
|
if (widget.instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(widget.instructions!),
|
|
|
|
|
if (widget.requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(widget.requests!),
|
|
|
|
|
if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!),
|
|
|
|
|
if (widget.trailing != null) widget.trailing!,
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget actionButton(BuildContext context, double t, {required String title, required String icon}) {
|
|
|
|
|
return AnimatedSize(
|
|
|
|
|
duration: Duration(milliseconds: 150),
|
|
|
|
|
child: Container(
|
|
|
|
|
height: 40.h,
|
|
|
|
|
height: 36.h + (4.h * t),
|
|
|
|
|
padding: EdgeInsets.all(8.w),
|
|
|
|
|
margin: EdgeInsets.only(right: 24.w),
|
|
|
|
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
|
|
|
|
color: AppColors.secondaryLightRedColor,
|
|
|
|
|
borderRadius: 10.r,
|
|
|
|
|
@ -176,7 +294,7 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
spacing: 8.h,
|
|
|
|
|
children: [
|
|
|
|
|
Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor),
|
|
|
|
|
if (t == 1)
|
|
|
|
|
if (t >= .5)
|
|
|
|
|
Text(
|
|
|
|
|
title,
|
|
|
|
|
style: context.dynamicTextStyle(
|
|
|
|
|
@ -184,7 +302,7 @@ class CollapsingListView extends StatelessWidget {
|
|
|
|
|
letterSpacing: -0.4,
|
|
|
|
|
fontSize: (12 - (2 * (1 - t))).f,
|
|
|
|
|
fontWeight: FontWeight.lerp(
|
|
|
|
|
FontWeight.w300,
|
|
|
|
|
FontWeight.w400,
|
|
|
|
|
FontWeight.w500,
|
|
|
|
|
t,
|
|
|
|
|
)!,
|
|
|
|
|
|